Come avviare un processo dal servizio Windows nella sessione dell’utente attualmente connesso

Devo avviare un programma dal servizio di Windows. Quel programma è un’applicazione utente dell’interfaccia utente. Inoltre, l’applicazione deve essere avviata con un account utente specifico.

Il problema è che un servizio finestra viene eseguito nella sessione n. 0, ma le sessioni utente registrate sono 1,2 ecc.

Quindi la domanda è: come avviare un processo da un servizio di Windows in modo che venga eseguito nella sessione dell’utente attualmente loggato?

Sottolineo che la domanda non riguarda come avviare un processo con un account specifico (è ovvio: Process.Start (new ProcessStartInfo (“..”) {UserName = .., Password = ..})). Anche se installo Windows per essere eseguito con l’account utente corrente, il servizio verrà eseguito comunque nella sessione # 0. L’impostazione “Consenti al servizio di interagire con il desktop” non aiuta.

Il mio servizio Windows è basato su .net.

AGGIORNAMENTO : prima di tutto, .NET non ha nulla da fare qui, è in realtà pura cosa Win32. Ecco cosa sto facendo. Il seguente codice è nel mio servizio Windows (C # utilizzando la funzione win32 tramite P / Inkove, ho ignorato le firme di importazione, sono tutte qui – 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); 

Il codice va alla riga “Il blocco note è stato avviato da WatchdogService. Codice di uscita:” + exitCode. Il codice di uscita è 3221225794. E non è stato avviato alcun nuovo blocco note. Dove mi sbaglio?

Il problema con la risposta di Shrike è che non funziona con un utente connesso su RDP.
Ecco la mia soluzione, che determina correttamente la sessione dell’utente corrente prima di creare il processo. È stato testato per funzionare su XP e 7.

https://github.com/murrayju/CreateProcessAsUser

Tutto ciò di cui hai bisogno è racchiuso in una singola class .NET con un metodo statico:

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

Un blog su MSDN descrive una soluzione

È fantastico post utile sull’avvio di un nuovo processo in sessione intertriggers dal servizio Windows su Vista / 7.

Per i servizi non LocalSystem, l’idea di base è:

  • Enumerare il processo per ottenere l’handle di Explorer.

  • OpenProcessToken dovrebbe darti il ​​token di accesso. Nota: l’account con cui è in esecuzione il servizio deve disporre dei privilegi appropriati per chiamare questa API e ottenere il token di processo.

  • Una volta che il token chiama CreateProcessAsUser con questo token. Questo token ha già l’id della sessione corretta.

È una ctriggers idea farlo. Sebbene probabilmente non del tutto imansible, Microsoft ha fatto di tutto per renderlo il più difficile ansible, in quanto abilita i cosiddetti Shatter Attacks . Scopri cosa ne ha scritto Larry Osterman nel 2005 :

Il motivo principale per cui questa è una ctriggers idea è che i servizi interattivi abilitano una class di minacce note come attacchi “Shatter” (perché “frantumano le windows”, credo).

Se esegui una ricerca per “attacco shatter”, puoi vedere alcuni dettagli su come funzionano queste minacce alla sicurezza. Microsoft ha anche pubblicato l’ articolo 327618 della KB che estende la documentazione sui servizi interattivi e Michael Howard ha scritto un articolo sui servizi interattivi per MSDN Library. Inizialmente, gli attacchi in frantumi andavano dopo che i componenti di Windows che avevano le pompe dei messaggi della finestra di sfondo (che sono stati a lungo riparati), ma sono stati anche usati per attaccare i servizi di terze parti che compaiono nell’interfaccia utente.

La seconda ragione per cui è una ctriggers idea è che il flag SERVICE_INTERACTIVE_PROCESS semplicemente non funzioni correttamente. L’interfaccia utente del servizio viene visualizzata nella sessione di sistema (normalmente la sessione 0). Se, d’altra parte, l’utente è in esecuzione in un’altra sessione, l’utente non vede mai l’interfaccia utente. Esistono due scenari principali in cui un utente si connette in un’altra sessione: Servizi terminal e Cambio rapido utente. TS non è così comune, ma negli scenari domestici in cui ci sono più persone che usano un singolo computer, il FUS è spesso abilitato (abbiamo 4 persone che si collegano praticamente tutto il tempo sul computer nella nostra cucina, per esempio).

Il terzo motivo per cui i servizi interattivi sono una ctriggers idea è che i servizi interattivi non sono garantiti per funzionare con Windows Vista 🙂 Come parte del processo di protezione avanzata in Windows Vista, gli utenti interattivi accedono a sessioni diverse dalla sessione di sistema – il primo utente interattivo viene eseguito nella sessione 1, non nella sessione 0. Ciò ha l’effetto di tagliare completamente gli attacchi in frantumi al ginocchio – le app degli utenti non possono interagire con le windows con privilegi elevati in esecuzione nei servizi.

La soluzione proposta consisterebbe nell’utilizzare un’applicazione nella barra delle applicazioni dell’utente.

Se puoi tranquillamente ignorare i problemi e gli avvisi sopra riportati, potresti seguire le istruzioni fornite qui:

Sviluppo per Windows: isolamento della sessione 0

Ecco come l’ho implementato. Proverà ad avviare un processo come utente attualmente loggato (da un servizio). Questo è basato su diverse fonti messe insieme per qualcosa che funziona.

In realtà è VERAMENTE WIN32 / C ++, quindi potrebbe non essere al 100% utile alle domande originali. Ma spero che possa salvare altre persone un po ‘di tempo alla ricerca di qualcosa di simile.

Richiede Windows XP / 2003 (non funziona con Windows 2000). Devi collegarti 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; } 

Ho trovato la soluzione qui:

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

Penso che questo sia un ottimo link.

Non so come farlo in .NET, ma in generale dovresti usare la funzione CreateProcessAsUser() dell’API Win32 (o qualunque sia il suo equivalente .NET), specificando il token di accesso e il nome del desktop dell’utente desiderato. Questo è quello che uso nei miei servizi C ++ e funziona perfettamente.

Implementato il codice @murrayju in un servizio Windows su W10. L’esecuzione di un eseguibile da Programmi ha sempre generato l’errore -2 in VS. Credo che sia dovuto al percorso di avvio del servizio impostato su System32. Specificare workDir non ha risolto il problema finché non ho aggiunto la seguente riga prima di CreateProcessAsUser in StartProcessAsCurrentUser :

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

PS: Questo avrebbe dovuto essere un commento piuttosto che una risposta, ma non ho ancora la reputazione richiesta. Mi ci è voluto un po ‘per eseguire il debug, spero che risparmi tempo a qualcuno.

La risposta accettata non ha funzionato nel mio caso in quanto l’applicazione che stavo iniziando richiedeva i privilegi di amministratore. Quello che ho fatto è che ho creato un file batch per avviare l’applicazione. Conteneva quanto segue:

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

Quindi, ho passato il percorso di questo file al metodo StartProcessAsCurrentUser (). Ha fatto il trucco