2010-11-25 5 views
37

Ich muss ein Programm von Windows-Dienst starten. Dieses Programm ist eine Benutzer-UI-Anwendung. Außerdem sollte diese Anwendung unter einem bestimmten Benutzerkonto gestartet werden.So starten Sie einen Prozess vom Windows-Dienst in die aktuell angemeldete Benutzersitzung

Das Problem ist, dass ein Fenster Dienste in der Sitzung # 0 laufen, aber ein in Benutzersitzungen angemeldet sind 1,2 usw.

Die Frage ist also: Wie kann ein Prozess von einem Fenster Dienst in einem starten solche Wie läuft es in der aktuell angemeldeten Benutzersitzung?

Ich würde betonen, dass die Frage ist nicht, wie man einen Prozess unter bestimmten Konto starten (es ist offensichtlich - Process.Start (new ProcessStartInfo ("..") {UserName = .., Password = ..})). Selbst wenn ich meine Fenster unter dem aktuellen Benutzerkonto installiere, wird der Dienst trotzdem in der Sitzung # 0 laufen. Einstellung "Dienst mit Desktop interagieren" hilft nicht.

Mein Windows-Dienst ist .net-basiert.

UPDATE: vor allem, .NET hat nichts zu tun, es ist eigentlich reine Win32 Sache. Hier ist was ich mache. Der folgende Code in meinem Windows-Dienst (C# win32-Funktion über P/Inkove verwendet wird, übersprungen ich Import Unterschriften, sie sind alle hier - 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); 

Der Code geht in die Zeile „Notepad gestartet wurde von WatchdogService Exitcode: "+ exitCode. Exitcode ist 3221225794. Und es gibt keinen neuen Notizblock gestartet. Wo liege ich falsch?

Antwort

24

A blog on MSDN describes a solution

Es ist grandios hilfreich Post über einen neuen Prozess in der interaktiven Sitzung von Windows-Dienst auf Vista/7 starten.

Für nicht-lokale Systemdienste, die Grundidee ist:

  • den Prozess Aufzählen den Griff des Explorer zu erhalten.

  • OpenProcessToken sollte Ihnen das Zugriffs-Token geben. Hinweis: Das Konto, unter dem Ihr Dienst ausgeführt wird, muss über entsprechende Berechtigungen zum Aufrufen dieser API und zum Abrufen des Prozesstokens verfügen.

  • Sobald Sie den Token CreateProcessAsUser mit diesem Token aufrufen. Dieses Token hat bereits die richtige Sitzungs-ID.

6

Es ist eine schlechte Idee, dies zu tun. Obwohl wahrscheinlich nicht völlig unmöglich, hat Microsoft alles getan, um dies so schwer wie möglich zu machen, da es so genannte Shatter Attacks ermöglicht. Sehen Sie, was Larry Osterman wrote about it back in 2005:

Der Hauptgrund für das eine schlechte Idee ist, dass interaktive Dienste eine Klasse von Bedrohungen bekannt als „Shatter“ Angriffe ermöglichen (weil sie „zertrümmern Fenster“, glaube ich).

Wenn Sie nach "shatter attack" suchen, sehen Sie einige Details zur Funktionsweise dieser Sicherheitsbedrohungen. Microsoft veröffentlichte auch KB article 327618, die die Dokumentation über interaktive Dienste erweitert, und Michael Howard schrieb einen Artikel über interaktive Dienste für die MSDN-Bibliothek. Anfangs gingen die Shatter-Attacken nach Windows-Komponenten, die Hintergrundfenster-Message-Pumps hatten (die seit langem behoben wurden), aber sie wurden auch verwendet, um Dienste von Drittanbietern anzugreifen, die die Benutzeroberfläche aufrufen.

Der zweite Grund, dass es eine schlechte Idee ist, ist, dass das SERVICE_INTERACTIVE_PROCESS Flag einfach nicht richtig funktioniert. Die Service-Benutzeroberfläche wird in der Systemsitzung angezeigt (normalerweise Sitzung 0). Wenn der Benutzer andererseits in einer anderen Sitzung ausgeführt wird, sieht der Benutzer die Benutzeroberfläche nie. Es gibt zwei Hauptszenarien, bei denen sich ein Benutzer in einer anderen Sitzung verbindet - Terminaldienste und schnelle Benutzerumschaltung. TS ist nicht so üblich, aber in Heimszenarien, in denen mehrere Personen einen einzigen Computer benutzen, ist FUS oft aktiviert (wir haben 4 Leute, die zum Beispiel die meiste Zeit auf dem Computer in unserer Küche angemeldet sind). Der dritte Grund, dass interaktive Dienste keine gute Idee sind, ist, dass interaktive Dienste nicht mit Windows Vista kompatibel sind. Als Teil des Sicherheitshärtungsprozesses, der in Windows Vista integriert wurde, können sich interaktive Benutzer bei anderen Sitzungen anmelden die Systemsitzung - der erste interaktive Benutzer wird in Sitzung 1 und nicht in Sitzung 0 ausgeführt.Dies führt dazu, dass Splitterangriffe an den Knien vollständig ausgeschaltet werden - Benutzer-Apps können nicht mit hoch privilegierten Fenstern interagieren, die in Diensten ausgeführt werden.

Die vorgeschlagene Problemumgehung wäre, eine Anwendung in der Taskleiste des Benutzers zu verwenden.

Wenn Sie sicher die Probleme und Warnungen oben ignorieren können, könnten Sie die hier aufgeführten Hinweise folgen:

Developing for Windows: Session 0 Isolation

+2

Ich möchte nicht, dass mein Dienst interaktiv ist (zeigt irgendeine Benutzeroberfläche), ich möchte nur, dass er einen anderen Prozess (in der Sitzung des Benutzers) starten kann. Das ist ein bisschen anders, oder? – Shrike

+0

@Shrike: Wenn Sie keine Benutzeroberfläche anzeigen möchten, warum reicht es dann nicht, diesen Prozess in Sitzung 0 zu starten? Ich denke, Sie müssen weitere Details hinzufügen, warum dieser Prozess eine interaktive Sitzung erfordert und warum die vorgeschlagene Problemumgehung für Sie nicht geeignet ist. –

+0

Es gibt zwei Akteure: Windows-Service und UI-Programm. Der Server itseft zeigt keine UI, sondern startet das Programm (als neuen Prozess). Das Programm _has_ UI (und erfordert Sitzung 1). Mein Dienst ist eine Art Wachhund, der das Programm regelmäßig "pingt" und es startet/startet, wenn etwas nicht in Ordnung ist. Ich füge eine Klarstellung in den ursprünglichen Beitrag hinzu – Shrike

1

Ich weiß nicht, wie es in .NET zu tun , aber im Allgemeinen müssten Sie die Win32-API-Funktion CreateProcessAsUser() (oder was auch immer ihr .NET-Äquivilent ist) verwenden und den Zugriffstoken und den Desktopnamen des gewünschten Benutzers angeben. Dies ist, was ich in meinen C++ - Diensten verwende und es funktioniert gut.

+0

Ich habe CreateProcessWithLogonW (http://msdn.microsoft.com/en-us/library/ms682431%28VS.85%29.aspx), es ist fast das gleiche – Shrike

28

Das Problem mit Shrike's Antwort ist, dass es nicht mit einem Benutzer funktioniert, der über RDP verbunden ist.
Hier ist meine Lösung, die die Sitzung des aktuellen Benutzers vor dem Erstellen des Prozesses ordnungsgemäß bestimmt. Es wurde getestet auf XP und 7.

https://github.com/murrayju/CreateProcessAsUser

Alles, was Sie brauchen, ist verpackt in einer einzigen .NET-Klasse mit einer statischen Methode zu arbeiten:

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

Awesome! Ich habe damit gekämpft, dein Code funktioniert großartig! –

+0

Was passiert, wenn zwei aktive RDP-Sitzungen gleichzeitig stattfinden? Oder eine RDP-Sitzung und eine lokale Sitzung? –

+0

@zespri Der Code geht davon aus, dass nur eine aktive Sitzung vorhanden ist. Daher wird effektiv eine Sitzung ausgewählt. Ich bezweifle, dass die 'WTSEnumerateSessions'-API irgendwelche Garantien über die Bestellung gibt, aber der Code wird die letzte aktive Sitzung in der Liste auswählen. Ich glaube nicht, dass Sie wissen können, welche Sitzung die "richtige" ist, aber falls ja, können Sie diese "for" -Schleife ändern. Ich nehme an, das ist auf einem Server OS? – murrayju

5

Hier ist, wie ich es umgesetzt . Es wird versucht, einen Prozess als derzeit angemeldeten Benutzer (von einem Dienst) zu starten. Dies basiert auf mehreren Quellen, die zu etwas zusammengefügt wurden, das funktioniert.

Es ist tatsächlich wirklich PURE WIN32/C++, also möglicherweise nicht 100% hilfreich zu den ursprünglichen Fragen. Aber ich hoffe, dass es andere Leute einige Zeit sparen kann, auf der Suche nach etwas ähnlichem.

Es erfordert Windows XP/2003 (funktioniert nicht mit Windows 2000). Sie müssen Wtsapi32.lib Link

#define WINVER 0x0501 
#define _WIN32_WINNT 0x0501 
#include <Windows.h> 
#include <WtsApi32.h> 

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; 
} 
0

Implementiert @murrayju Code in einen Windows-Dienst auf W10. Das Ausführen einer ausführbaren Datei aus Programmdateien verursachte immer Fehler -2 in VS. Ich glaube, dass der Startpfad von Service auf System32 gesetzt wurde. Angeben workdir das Problem nicht beheben hat, bis ich die folgende Zeile vor CreateProcessAsUser in StartProcessAsCurrentUser hinzugefügt:

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

PS: Dies ist als eine Antwort ein Kommentar eher hätte sein sollen, aber ich don‘ Ich habe noch nicht den nötigen Ruf. Nahm mich eine Weile zum Debuggen, hoffe, es spart Zeit.

+0

Beabsichtigen Sie, dass dies eine aktualisierte Antwort ist? Wenn ja, fügen Sie einfach die relevanten Details hinzu und posten Sie als solche. Es sieht mehr als nur ein Kommentar für mich aus, aber ich möchte mir nicht die Zeit nehmen, das ganze Problem herauszufinden. – theMayer

0

Die angenommene Antwort funktionierte in meinem Fall nicht, da die Anwendung, die ich startete, Administratorrechte benötigte. Was ich getan habe, ist, ich habe eine Batch-Datei erstellt, um die Anwendung zu starten. Es enthielt die folgenden:

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

Dann Ich habe die Position dieser Datei zu StartProcessAsCurrentUser() -Methode. Hat den Trick gemacht.

Verwandte Themen