En ocasiones es necesario lanzar desde un servicio una aplicación GUI con credenciales del usuario dueño de la sesión activa. El problema es que desde un servicio, que corre como usuario SYSTEM, creará procesos también SYSTEM y en una estación de ventana y escritorio diferentes a los del usuario activo con lo que la comunicación con el mismo queda desactivada. Esto es especialmente cierto en Window 7.
Para solventar el problema la estrategia será la siguiente:
1.- Encontrar el Token del usuario de la sesión activa y duplicarlo.
2.- Arrancar el nuevo proceso usando ese Token con la API CreateProcessAsUser.
Para que esto funcione debemos correr dichas API como SYSTEM o fallarán.
El primer paso se puede conseguir con el siguiente código:
WTSQueryUserToken(WtsGetActiveConsoleSessionID, @hToken);
El problema es que la API WtsGetActiveConsoleSessionID no siempre encuentra la sesión, pero afortunadamente podemos enumerarlas para encontrar la que nos interesa. El código se complica un poco pero conseguimos el objetivo:
function GetCurrentUserToken: THandle; var hToken: THandle; pSi: PWTS_SESSION_INFO; pSiA: PWTS_SESSION_INFO_ARRAY; Count, i: DWORD; begin hToken:= 0; Result:= 0; pSi:= nil; Count:= 0; WTSEnumerateSessionsA(0, 0, 1, @pSi, @Count); pSiA:= PWTS_SESSION_INFO_ARRAY(pSi); for i:= 0 to Count-1 do begin if pSiA.State = 0 then begin if WTSQueryUserToken(pSiA[i].SessionId, @hToken) then DuplicateTokenEx(hToken, TOKEN_ASSIGN_PRIMARY or TOKEN_ALL_ACCESS, nil, SecurityImpersonation, TokenPrimary, Result); break; end; end; WTSFreeMemory(pSi); end;
Llegados a este punto usamos la API CreateProcessAsUser para crear nuestro proceso GUI.
Pongo el código completo de un ejemplo consistente en aplicación de consola encargara de abrir el Notepad. Dicha aplicación se debe correr como usuario SYSTEM para que funcione.
program RunAsOwnerSesion; uses windows; type WTS_SESSION_INFO = record SessionId: DWORD; pWinStationName: PCHAR; State: DWORD; end; PWTS_SESSION_INFO = ^WTS_SESSION_INFO; PPWTS_SESSION_INFO = ^PWTS_SESSION_INFO; WTS_SESSION_INFO_ARRAY = array [0..0] of WTS_SESSION_INFO; PWTS_SESSION_INFO_ARRAY = ^WTS_SESSION_INFO_ARRAY; function WTSEnumerateSessionsA(hServer: THandle; Reserved, Version: DWORD; ppSI: PPWTS_SESSION_INFO; pCount: PDWORD): boolean; stdcall external 'Wtsapi32.dll'; function WTSQueryUserToken(SessionId: DWORD; phToken: PHANDLE): boolean; stdcall external 'Wtsapi32.dll'; function WTSFreeMemory(pSi: Pointer): boolean; stdcall external 'Wtsapi32.dll'; function GetCurrentUserToken: THandle; var hToken: THandle; pSi: PWTS_SESSION_INFO; pSiA: PWTS_SESSION_INFO_ARRAY; Count, i: DWORD; begin hToken:= 0; Result:= 0; pSi:= nil; Count:= 0; // Obtener la lista de las sesiones WTSEnumerateSessionsA(0, 0, 1, @pSi, @Count); pSiA:= PWTS_SESSION_INFO_ARRAY(pSi); // Buscamos la sesión activa for i:= 0 to Count-1 do begin if pSiA[i].State = 0 then begin // Duplicamos el token del usuario de la sesión if WTSQueryUserToken(pSiA[i].SessionId, @hToken) then DuplicateTokenEx(hToken, TOKEN_ASSIGN_PRIMARY or TOKEN_ALL_ACCESS, nil, SecurityImpersonation, TokenPrimary, Result); break; end; end; WTSFreeMemory(pSi); end; function RunAsCurrentUser(Cmd: PCHAR): boolean; var hToken: THandle; pInfo: PROCESS_INFORMATION; sInfo: STARTUPINFO; sa: SECURITY_ATTRIBUTES; begin Result:= false; hToken:= GetCurrentUserToken(); if hToken <> 0 then begin ZeroMemory(@sInfo, sizeof(STARTUPINFO)); sInfo.cb:= sizeof(STARTUPINFO); sInfo.wShowWindow:= SW_SHOW; ZeroMemory(@sa, sizeof(STARTUPINFO)); sa.nLength:= sizeof(sa); Result:= CreateProcessAsUser(hToken, nil, Cmd, nil, nil, FALSE, NORMAL_PRIORITY_CLASS, nil, nil, sInfo, pInfo); CloseHandle(hToken); if Result then begin CloseHandle(pInfo.hProcess); CloseHandle(pInfo.hThread); end; end; end; begin RunAsCurrentUser('C:\Windows\notepad.exe'); end.
En este enlace podéis encontrar una aplicación que escribí (MiniSystem.exe) para conseguir lanzarla como SYSTEM. Recordar que para que MiniSystem funcione se debe ejecutar como administrador.
El sistema está probado en Win XP y Win 7
Espero que sea de utilidad y aclare dudas.
Saludos.
Edito para arreglar etiquetas de código cambiadas desde la última "mudanza"