Cómo iniciar un proceso desde el servicio de Windows hasta la sesión del usuario actualmente conectado

Necesito comenzar un progtwig desde el Servicio de Windows. Ese progtwig es una aplicación de interfaz de usuario de usuario. Además, esa aplicación debe iniciarse en una cuenta de usuario específica.

El problema es que los Servicios de Windows se ejecutan en la sesión # 0, pero las sesiones de usuario registradas son 1,2, etc.

Entonces la pregunta es: ¿cómo iniciar un proceso desde un servicio de ventana de tal manera que se ejecute en la sesión del usuario actualmente conectado?

Me gustaría enfatizar que la pregunta no es sobre cómo iniciar un proceso bajo una cuenta específica (es obvio: Process.Start (new ProcessStartInfo (“..”) {UserName = .., Password = ..})). Incluso si instalo mi Windows para ejecutar bajo la cuenta de usuario actual, el servicio se ejecutará en la sesión # 0 de todos modos. Establecer “Permitir que el servicio interactúe con el escritorio” no ayuda.

Mi servicio de Windows está basado en .net.

ACTUALIZACIÓN : primero, .NET no tiene nada que hacer aquí, en realidad es algo puro de Win32. Esto es lo que estoy haciendo. El siguiente código está en mi servicio de Windows (C # usando la función win32 a través de P / Inkove, omití las firmas de importación, están todas aquí – http://www.pinvoke.net/default.aspx/advapi32/CreateProcessWithLogonW.html ):

var startupInfo = new StartupInfo() { lpDesktop = "WinSta0\\Default", cb = Marshal.SizeOf(typeof(StartupInfo)), }; var processInfo = new ProcessInformation(); string command = @"c:\windows\Notepad.exe"; string user = "Administrator"; string password = "password"; string currentDirectory = System.IO.Directory.GetCurrentDirectory(); try { bool bRes = CreateProcessWithLogonW(user, null, password, 0, command, command, 0, Convert.ToUInt32(0), currentDirectory, ref startupInfo, out processInfo); if (!bRes) { throw new Win32Exception(Marshal.GetLastWin32Error()); } } catch (Exception ex) { writeToEventLog(ex); return; } WaitForSingleObject(processInfo.hProcess, Convert.ToUInt32(0xFFFFFFF)); UInt32 exitCode = Convert.ToUInt32(123456); GetExitCodeProcess(processInfo.hProcess, ref exitCode); writeToEventLog("Notepad has been started by WatchdogService. Exitcode: " + exitCode); CloseHandle(processInfo.hProcess); CloseHandle(processInfo.hThread); 

El código va a la línea “Notepad ha sido iniciado por WatchdogService. Exitcode:” + exitCode. Exitcode es 3221225794. Y no hay ningún bloc de notas nuevo iniciado. ¿Dónde estoy equivocado?

El problema con la respuesta de Shrike es que no funciona con un usuario conectado a través de RDP.
Aquí está mi solución, que determina correctamente la sesión del usuario actual antes de crear el proceso. Se ha probado que funciona en XP y 7.

https://github.com/murrayju/CreateProcessAsUser

Todo lo que necesita está envuelto en una única clase .NET con un método estático:

 public static bool StartProcessAsCurrentUser(string appPath, string cmdLine, string workDir, bool visible) 

Un blog en MSDN describe una solución

Es una publicación muy útil acerca de cómo iniciar un nuevo proceso en una sesión interactiva desde el servicio de Windows en Vista / 7.

Para los servicios que no pertenecen a LocalSystem, la idea básica es:

  • Enumere el proceso para obtener el control del Explorer.

  • OpenProcessToken debería darle el token de acceso. Nota: La cuenta bajo la cual se ejecuta su servicio debe tener privilegios apropiados para llamar a esta API y obtener token de proceso.

  • Una vez que tenga el token, invoque CreateProcessAsUser con este token. Este token ya tiene el ID de sesión correcto.

Es una mala idea hacerlo. Aunque probablemente no sea totalmente imposible, Microsoft hizo todo lo posible para que esto sea lo más difícil posible, ya que habilita los denominados Shatter Attacks . Vea lo que Larry Osterman escribió al respecto en 2005 :

La razón principal de que esto sea una mala idea es que los servicios interactivos permiten una clase de amenazas conocidas como ataques “Shatter” (porque “destrozan ventanas”, creo).

Si realiza una búsqueda de “ataque de fragmentación”, puede ver algunos detalles sobre cómo funcionan estas amenazas de seguridad. Microsoft también publicó el artículo KB 327618, que amplía la documentación sobre servicios interactivos, y Michael Howard escribió un artículo sobre servicios interactivos para MSDN Library. Inicialmente, los ataques de destripamiento fueron después de los componentes de Windows que tenían bombas de mensajes de ventana de fondo (que se han corregido durante mucho tiempo), pero también se han utilizado para atacar servicios de terceros que muestran la interfaz de usuario.

La segunda razón por la que es una mala idea es que el indicador SERVICE_INTERACTIVE_PROCESS simplemente no funciona correctamente. La IU del servicio aparece en la sesión del sistema (normalmente sesión 0). Si, por otro lado, el usuario se está ejecutando en otra sesión, el usuario nunca verá la IU. Hay dos escenarios principales que hacen que un usuario se conecte en otra sesión: Servicios de Terminal Server y Cambio rápido de usuario. TS no es tan común, pero en escenarios domésticos donde hay varias personas que usan una sola computadora, a menudo se habilita FUS (tenemos 4 personas conectadas casi todo el tiempo en la computadora de nuestra cocina, por ejemplo).

La tercera razón por la que los servicios interactivos son una mala idea es que no se garantiza que los servicios interactivos funcionen con Windows Vista 🙂 Como parte del proceso de refuerzo de seguridad que entró en Windows Vista, los usuarios interactivos inician sesiones distintas a la del sistema: el primer usuario interactivo se ejecuta en la sesión 1, no en la sesión 0. Esto tiene el efecto de cortar totalmente los ataques de desintegración en las rodillas: las aplicaciones de los usuarios no pueden interactuar con las ventanas con privilegios altos que se ejecutan en los servicios.

La solución alternativa propuesta sería usar una aplicación en la bandeja del sistema del usuario.

Si puede ignorar con seguridad los problemas y advertencias anteriores, puede seguir las instrucciones que figuran a continuación:

Desarrollando para Windows: Aislamiento de la Sesión 0

Así es como lo implementé. Intentará iniciar un proceso como el usuario actualmente conectado (desde un servicio). Esto se basa en varias fonts creadas para algo que funciona.

En realidad, es REALMENTE PURO WIN32 / C ++, por lo que podría no ser 100% útil para las preguntas originales. Pero espero que pueda salvar a otras personas en algún momento buscando algo similar.

Requiere Windows XP / 2003 (no funciona con Windows 2000). Debes enlazar a Wtsapi32.lib

 #define WINVER 0x0501 #define _WIN32_WINNT 0x0501 #include  #include  bool StartInteractiveProcess(LPTSTR cmd, LPCTSTR cmdDir) { STARTUPINFO si; ZeroMemory(&si, sizeof(si)); si.cb = sizeof(si); si.lpDesktop = TEXT("winsta0\\default"); // Use the default desktop for GUIs PROCESS_INFORMATION pi; ZeroMemory(&pi, sizeof(pi)); HANDLE token; DWORD sessionId = ::WTSGetActiveConsoleSessionId(); if (sessionId==0xffffffff) // Noone is logged-in return false; // This only works if the current user is the system-account (we are probably a Windows-Service) HANDLE dummy; if (::WTSQueryUserToken(sessionId, &dummy)) { if (!::DuplicateTokenEx(dummy, TOKEN_ALL_ACCESS, NULL, SecurityDelegation, TokenPrimary, &token)) { ::CloseHandle(dummy); return false; } ::CloseHandle(dummy); // Create process for user with desktop if (!::CreateProcessAsUser(token, NULL, cmd, NULL, NULL, FALSE, CREATE_NEW_CONSOLE, NULL, cmdDir, &si, &pi)) { // The "new console" is necessary. Otherwise the process can hang our main process ::CloseHandle(token); return false; } ::CloseHandle(token); } // Create process for current user else if (!::CreateProcess(NULL, cmd, NULL, NULL, FALSE, CREATE_NEW_CONSOLE, NULL, cmdDir, &si, &pi)) // The "new console" is necessary. Otherwise the process can hang our main process return false; // The following commented lines can be used to wait for the process to exit and terminate it //::WaitForSingleObject(pi.hProcess, INFINITE); //::TerminateProcess(pi.hProcess, 0); ::CloseHandle(pi.hProcess); ::CloseHandle(pi.hThread); return true; } 

Encontré la solución aquí:

http://www.codeproject.com/Articles/35773/Subverting-Vista-UAC-in-Both-32-and-64-bit-Archite

Creo que este es un gran enlace.

No sé cómo hacerlo en .NET, pero en general necesitaría usar la función Win32 API CreateProcessAsUser() (o lo que sea su equivalente de .NET), especificando el token de acceso del usuario deseado y el nombre del escritorio. Esto es lo que uso en mis servicios de C ++ y funciona bien.

Implementé el código @murrayju en un servicio de Windows en W10. Ejecutar un archivo ejecutable desde Archivos de progtwig siempre arrojaba Error -2 en VS. Creo que se debió a la ruta de inicio del servicio establecida en System32. La especificación de workDir no solucionó el problema hasta que agregué la siguiente línea antes de CreateProcessAsUser en StartProcessAsCurrentUser :

 if (workDir != null) Directory.SetCurrentDirectory(workDir); 

PD: Esto debería haber sido un comentario en lugar de una respuesta, pero todavía no tengo la reputación requerida. Me llevó un tiempo depurar, espero que le ahorre tiempo a alguien.

La respuesta aceptada no funcionó en mi caso, ya que la aplicación que estaba iniciando requería privilegios de administrador. Lo que hice fue crear un archivo por lotes para iniciar la aplicación. Contenía lo siguiente:

 C:\Windows\Microsoft.NET\Framework\v4.0.30319\InstallUtil.exe "%~dp0MySoft.exe" 

Luego, pasé la ubicación de este archivo al método StartProcessAsCurrentUser (). Hizo el truco.