Iniciar la aplicación desde el servicio ejecutándose como SISTEMA que puede interactuar con el usuario.

Actualmente tengo una aplicación única que debe iniciarse desde un servicio de Windows que estoy codificando en .net 3.5. Esta aplicación se está ejecutando actualmente como el usuario que ejecutó el servicio, en mi caso, el usuario del SISTEMA. Si se ejecuta como usuario del SISTEMA, no muestra la aplicación en el escritorio de los usuarios. ¿Pensamientos? ¿Consejo?

//constructor private Process ETCHNotify = new Process(); //StartService() ETCHNotify.StartInfo.FileName = baseDir + "\\EtchNotify.exe"; ETCHNotify.StartInfo.UseShellExecute = false; //BackgroundWorkerThread_DoWork() if (!systemData.GetUserName().Equals("")) { // start ETCHNotify try { ETCHNotify.Start(); } catch (Exception ex) { systemData.Run("ERR: Notify can't start: " + ex.Message); } } 

Solo ejecuto try / catch si la función que he escrito GetUserName () (que determina el nombre de usuario del usuario que ejecuta explorer.exe) no es nula

nuevamente para reiterar: la funcionalidad deseada es que esto inicie ETCHNotify en un estado que le permita interactuar con el usuario que ha iniciado sesión actualmente según lo determinado por GetUserName ()

Collage de algún post encontrado alrededor ( esto y esto )

Tenga en cuenta que a partir de Windows Vista, los servicios tienen estrictamente prohibido interactuar directamente con un usuario :

Importante: los servicios no pueden interactuar directamente con un usuario a partir de Windows Vista. Por lo tanto, las técnicas mencionadas en la sección titulada Uso de un servicio interactivo no deben utilizarse en el nuevo código.

Esta “característica” está rota y la sabiduría convencional dicta que no deberías haber confiado en ella de todos modos. Los servicios no están destinados a proporcionar una IU o permitir ningún tipo de interacción directa con el usuario. Microsoft ha estado advirtiendo que esta característica se debe evitar desde los primeros días de Windows NT debido a los posibles riesgos de seguridad.

Sin embargo, hay algunas soluciones posibles si es absolutamente necesario tener esta funcionalidad. Pero le recomiendo encarecidamente que considere cuidadosamente su necesidad y explore diseños alternativos para su servicio.

Use WTSEnumerateSessions para encontrar el escritorio correcto, luego CreateProcessAsUser para iniciar la aplicación en ese escritorio (usted lo pasa a la manija del escritorio como parte de la estructura STARTUPINFO ) es correcto.

Sin embargo, recomendaría encarecidamente no hacer esto. En algunos entornos, como los servidores de Terminal Server con muchos usuarios activos, determinar qué escritorio es el “activo” no es fácil y puede que ni siquiera sea posible.

Un enfoque más convencional sería poner un acceso directo a una aplicación de pequeño cliente para su servicio en el grupo de inicio global. Esta aplicación se iniciará junto con cada sesión de usuario, y puede usarse para iniciar otras aplicaciones (si así lo desea) sin hacer malabarismos con las credenciales de usuario, las sesiones y / o los escritorios.

No iba a responder esto ya que ya lo respondiste (y ¡oh, ¡¿qué? ¡¿¡¿¡¿¡¿¡¿¡¿¡¿¡¿¡¿¡¿¡¿¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡AHORA! .

Con el fin de hacer que mi servicio interactúe con el escritorio, no importa QUÉ escritorio, ni cuántos escritorios, ni si el servicio se estaba ejecutando en la MISMA COMPUTADORA como la aplicación de escritorio. Nada de eso importa con lo que tengo aquí … No te aburriré con los detalles, solo te daré la carne y las papas, y tú y yo, si quieres ver más, avísame …

De acuerdo. Lo primero que hice fue crear un Servicio de Publicidad . Este es un hilo que ejecuta el servicio, abre un socket UDP para escuchar las transmisiones en la red. Luego, utilizando el mismo fragmento de código, lo compartí con la aplicación cliente, pero llama Advertise.CLIENT , en lugar de Advertise.SERVER … CLIENT abre el puerto en el que espero que esté el servicio y emite un mensaje. , “Hola … ¿Hay alguien ahí fuera?”, Preguntando si hay algún servidor escuchando, y si es así, responda de nuevo a ESTA dirección IP con el nombre de su computadora, la dirección IP y el número de puerto donde puedo encontrar el .NET remoting Services … “Luego espera un poco de tiempo de espera, recostack las respuestas que recibe, y si es más de una, presenta al usuario un cuadro de diálogo y una lista de servicios que respondieron. .. Luego, el Cliente selecciona uno o, si solo ONE respondió, llamará a Connect ( (TServerResponse) res); para eso, se conectará. En este punto, el servidor está utilizando Servicios Remotos con el WellKnownClientType y WellKnownServerType para ponerse por ahí …

No creo que estés demasiado interesado en mi “Localizador de autoservicio”, porque mucha gente no ve bien el UDP, y más aún cuando tu aplicación comienza a transmitir en redes grandes. Entonces, supongo que estaría más interesado en mi RemotingHelper, que conecta al cliente con el servidor. Se parece a esto:

  public static Object GetObject(Type type) { try { if(_wellKnownTypes == null) { InitTypeCache(); } WellKnownClientTypeEntry entr = (WellKnownClientTypeEntry)_wellKnownTypes[type]; if(entr == null) { throw new RemotingException("Type not found!"); } return System.Activator.GetObject(entr.ObjectType, entr.ObjectUrl); } catch(System.Net.Sockets.SocketException sex) { DebugHelper.Debug.OutputDebugString("SocketException occured in RemotingHelper::GetObject(). Error: {0}.", sex.Message); Disconnect(); if(Connect()) { return GetObject(type); } } return null; } private static void InitTypeCache() { if(m_AdvertiseServer == null) { throw new RemotingException("AdvertisementServer cannot be null when connecting to a server."); } _wellKnownTypes = new Dictionary(); Dictionary channelProperties = new Dictionary(); channelProperties["port"] = 0; channelProperties["name"] = m_AdvertiseServer.ChannelName; Dictionary binFormatterProperties = new Dictionary(); binFormatterProperties["typeFilterLevel"] = "Full"; if(Environment.UserInteractive) { BinaryServerFormatterSinkProvider binFormatterProvider = new BinaryServerFormatterSinkProvider(binFormatterProperties, null); _serverChannel = new TcpServerChannel(channelProperties, binFormatterProvider); // LEF: Only if we are coming form OUTSIDE the SERVICE do we want to register the channel, since the SERVICE already has this // channel registered in this AppDomain. ChannelServices.RegisterChannel(_serverChannel, false); } System.Diagnostics.Debug.Write(string.Format("Registering: {0}...\n", typeof(IPawnStatServiceStatus))); RegisterType(typeof(IPawnStatServiceStatus),m_AdvertiseServer.RunningStatusURL.ToString()); System.Diagnostics.Debug.Write(string.Format("Registering: {0}...\n", typeof(IPawnStatService))); RegisterType(typeof(IPawnStatService), m_AdvertiseServer.RunningServerURL.ToString()); System.Diagnostics.Debug.Write(string.Format("Registering: {0}...\n", typeof(IServiceConfiguration))); RegisterType(typeof(IServiceConfiguration), m_AdvertiseServer.RunningConfigURL.ToString()); } [SecurityPermission(SecurityAction.Demand, Flags=SecurityPermissionFlag.RemotingConfiguration, RemotingConfiguration=true)] public static void RegisterType(Type type, string serviceUrl) { WellKnownClientTypeEntry clientType = new WellKnownClientTypeEntry(type, serviceUrl); if(clientType != RemotingConfiguration.IsWellKnownClientType(type)) { RemotingConfiguration.RegisterWellKnownClientType(clientType); } _wellKnownTypes[type] = clientType; } public static bool Connect() { // Init the Advertisement Service, and Locate any listening services out there... m_AdvertiseServer.InitClient(); if(m_AdvertiseServer.LocateServices(iTimeout)) { if(!Connected) { bConnected = true; } } else { bConnected = false; } return Connected; } public static void Disconnect() { if(_wellKnownTypes != null) { _wellKnownTypes.Clear(); } _wellKnownTypes = null; if(_serverChannel != null) { if(Environment.UserInteractive) { // LEF: Don't unregister the channel, because we are running from the service, and we don't want to unregister the channel... ChannelServices.UnregisterChannel(_serverChannel); // LEF: If we are coming from the SERVICE, we do *NOT* want to unregister the channel, since it is already registered! _serverChannel = null; } } bConnected = false; } } 

Entonces, ESO es parte de mi código remoto, y me permitió escribir un cliente que no tenía que saber dónde estaban instalados los servicios, o cuántos servicios se estaban ejecutando en la red. Esto me permitió comunicarme con él a través de la red o en la máquina local. Y no fue un problema tener a dos o más personas ejecutando la aplicación, sin embargo, la suya podría. Ahora, tengo un código de callback complicado en el que registro eventos para cruzar el canal remoto, así que tengo que tener un código que verifique si el cliente aún está conectado antes de enviar la notificación al cliente de que algo sucedió. . Además, si está ejecutando para más de un usuario, es posible que no desee utilizar objetos Singleton. Estaba bien para mí, porque el servidor OWNS los objetos, y son lo que el servidor DICE que son. Por lo tanto, mi objeto STATS, por ejemplo, es un Singleton. No hay razón para crear una instancia de CADA conexión, cuando todos van a ver los mismos datos, ¿verdad?

Puedo proporcionar más trozos de código si es necesario. Esto es, por supuesto, una pequeña parte de la imagen general de lo que hace que esto funcione … Sin mencionar los proveedores de suscripción, y todo eso.

En aras de la integridad, incluyo el fragmento de código para mantener su servicio conectado durante la vida del proceso.

 public override object InitializeLifetimeService() { ILease lease = (ILease)base.InitializeLifetimeService(); if(lease.CurrentState == LeaseState.Initial) { lease.InitialLeaseTime = TimeSpan.FromHours(24); lease.SponsorshipTimeout = TimeSpan.FromSeconds(30); lease.RenewOnCallTime = TimeSpan.FromHours(1); } return lease; } #region ISponsor Members [SecurityPermissionAttribute(SecurityAction.LinkDemand, Flags=SecurityPermissionFlag.Infrastructure)] public TimeSpan Renewal(ILease lease) { return TimeSpan.FromHours(12); } #endregion 

Si incluye la interfaz ISponsor como parte de su objeto de servidor, puede implementar el código anterior.

Espero que algo de esto sea útil.

Cuando registra su servicio, puede indicarle que permita interacciones con el escritorio. Puede leer este enlace antiguo http://www.codeproject.com/KB/install/cswindowsservicedesktop.aspx

Además, no olvide que puede tener varios usuarios conectados al mismo tiempo.

Al parecer, en Windows Vista y más reciente la interacción con el escritorio se ha vuelto más difícil. Lea esto para una solución potencial: http://www.codeproject.com/KB/cs/ServiceDesktopInteraction.aspx

Finalmente, para resolver esto tomé el consejo de @marco y los mensajes que mencionó. He creado el servicio para que sea completamente independiente de la aplicación de la bandeja que interactúa con el usuario. Sin embargo, instalé la aplicación Tray a través de los métodos de “inicio” del registro con el servicio. El instalador del servicio ahora instalará la aplicación que también interactúa con el usuario … Este fue el método más seguro y completo.

Gracias por su ayuda a todos.