Starten eines processes mit Anmeldeinformationen von einem Windows-Dienst

Ich habe einen Windows-Dienst, der als meineDomain \ userA ausgeführt wird. Ich möchte beliebige .exes vom Dienst ausführen können. Normalerweise benutze ich Process.Start () und es funktioniert gut, aber in einigen Fällen möchte ich die ausführbare Datei als einen anderen Benutzer (mydomain \ userB) ausführen.

Wenn ich die ProcessStartInfo ändere, die ich benutze, um den process zu starten, um Anmeldeinformationen aufzunehmen, bekomme ich Fehler – entweder ein Fehlerdialogfeld mit der Meldung “Die Anwendung konnte nicht richtig initialisiert werden (0xc0000142). Klicken Sie auf OK, um die Anwendung zu beenden.”, Oder “Zugriff verweigert” Win32Exception. Wenn ich den Code zum Starten des processes über die Befehlszeile ausführe, anstatt ihn im Dienst auszuführen, verwendet der process die korrekten Anmeldeinformationen (ich habe dies überprüft, indem ich ProcessStartInfo so eingestellt habe, dass whoami.exe ausgeführt und die Befehlszeilenausgabe erfasst wird ).

Ich habe auch versucht, Identitätswechsel mit WindowsIdentity.Impersonate (), aber das hat nicht funktioniert – wie ich es verstehe, betrifft Identitätswechsel nur den aktuellen Thread, und das Starten eines neuen processes erbt die Sicherheitsbeschreibung des processes, nicht der aktuelle Thread.

Ich führe dies in einer isolierten Testdomäne durch, also sind sowohl BenutzerA als auch BenutzerB Domain-Admins, und beide haben das Logon as a Service Recht Domain-weit.

Wenn Sie einen neuen process mit ProcessStartInfo starten, wird der process in derselben Fensterstation und auf demselben Desktop wie der Startprozess gestartet. Wenn Sie andere Anmeldeinformationen verwenden, verfügt der Benutzer im Allgemeinen nicht über ausreichende Rechte zum Ausführen auf diesem Desktop. Das Fehlschlagen der Initialisierung von Fehlern wird verursacht, wenn user32.dll versucht, in dem neuen process zu initialisieren, und nicht kann.

Um dies zu umgehen, müssen Sie zunächst die mit der Fensterstation und dem Desktop verknüpften Sicherheitsbeschreibungen abrufen und der DACL für Ihren Benutzer die entsprechenden Berechtigungen hinzufügen und dann Ihren process unter den neuen Anmeldeinformationen starten.

EDIT: Eine detaillierte Beschreibung, wie dies zu tun und Beispielcode war ein wenig lang für hier, also habe ich einen Artikel mit Code zusammen.

//The following security adjustments are necessary to give the new //process sufficient permission to run in the service's window station //and desktop. This uses classes from the AsproLock library also from //Asprosys. IntPtr hWinSta = GetProcessWindowStation(); WindowStationSecurity ws = new WindowStationSecurity(hWinSta, System.Security.AccessControl.AccessControlSections.Access); ws.AddAccessRule(new WindowStationAccessRule("LaunchProcessUser", WindowStationRights.AllAccess, System.Security.AccessControl.AccessControlType.Allow)); ws.AcceptChanges(); IntPtr hDesk = GetThreadDesktop(GetCurrentThreadId()); DesktopSecurity ds = new DesktopSecurity(hDesk, System.Security.AccessControl.AccessControlSections.Access); ds.AddAccessRule(new DesktopAccessRule("LaunchProcessUser", DesktopRights.AllAccess, System.Security.AccessControl.AccessControlType.Allow)); ds.AcceptChanges(); EventLog.WriteEntry("Launching application.", EventLogEntryType.Information); using (Process process = Process.Start(psi)) { } 

Basierend auf der Antwort von @ StephenMartin .

Ein neuer process, der mit der class ” Process gestartet wird, wird in derselben Fensterstation und auf demselben Desktop wie der Startprozess ausgeführt. Wenn Sie den neuen process mit anderen Anmeldeinformationen ausführen, hat der neue process keine Berechtigungen für den Zugriff auf die Fensterstation und den Desktop. Was zu Fehlern wie 0xC0000142 führt.

Der folgende Code ist ein “kompakter” eigenständiger Code, der einem Benutzer Zugriff auf die aktuelle Fensterstation und den Desktop gewährt. Es benötigt keine AsproLock-Bibliothek.

 public static void GrantAccessToWindowStationAndDesktop(string username) { IntPtr handle; const int WindowStationAllAccess = 0x000f037f; handle = GetProcessWindowStation(); GrantAccess(username, handle, WindowStationAllAccess); const int DesktopRightsAllAccess = 0x000f01ff; handle = GetThreadDesktop(GetCurrentThreadId()); GrantAccess(username, handle, DesktopRightsAllAccess); } private static void GrantAccess(string username, IntPtr handle, int accessMask) { SafeHandle safeHandle = new NoopSafeHandle(handle); GenericSecurity security = new GenericSecurity( false, ResourceType.WindowObject, safeHandle, AccessControlSections.Access); security.AddAccessRule( new GenericAccessRule( new NTAccount(username), accessMask, AccessControlType.Allow)); security.Persist(safeHandle, AccessControlSections.Access); } [DllImport("user32.dll", SetLastError = true)] private static extern IntPtr GetProcessWindowStation(); [DllImport("user32.dll", SetLastError = true)] private static extern IntPtr GetThreadDesktop(int dwThreadId); [DllImport("kernel32.dll", SetLastError = true)] private static extern int GetCurrentThreadId(); // All the code to manipulate a security object is available in .NET framework, // but its API tries to be type-safe and handle-safe, enforcing a special implementation // (to an otherwise generic WinAPI) for each handle type. This is to make sure // only a correct set of permissions can be set for corresponding object types and // mainly that handles do not leak. // Hence the AccessRule and the NativeObjectSecurity classes are abstract. // This is the simplest possible implementation that yet allows us to make use // of the existing .NET implementation, sparing necessity to // P/Invoke the underlying WinAPI. private class GenericAccessRule : AccessRule { public GenericAccessRule( IdentityReference identity, int accessMask, AccessControlType type) : base(identity, accessMask, false, InheritanceFlags.None, PropagationFlags.None, type) { } } private class GenericSecurity : NativeObjectSecurity { public GenericSecurity( bool isContainer, ResourceType resType, SafeHandle objectHandle, AccessControlSections sectionsRequested) : base(isContainer, resType, objectHandle, sectionsRequested) { } new public void Persist(SafeHandle handle, AccessControlSections includeSections) { base.Persist(handle, includeSections); } new public void AddAccessRule(AccessRule rule) { base.AddAccessRule(rule); } #region NativeObjectSecurity Abstract Method Overrides public override Type AccessRightType { get { throw new NotImplementedException(); } } public override AccessRule AccessRuleFactory( System.Security.Principal.IdentityReference identityReference, int accessMask, bool isInherited, InheritanceFlags inheritanceFlags, PropagationFlags propagationFlags, AccessControlType type) { throw new NotImplementedException(); } public override Type AccessRuleType { get { return typeof(AccessRule); } } public override AuditRule AuditRuleFactory( System.Security.Principal.IdentityReference identityReference, int accessMask, bool isInherited, InheritanceFlags inheritanceFlags, PropagationFlags propagationFlags, AuditFlags flags) { throw new NotImplementedException(); } public override Type AuditRuleType { get { return typeof(AuditRule); } } #endregion } // Handles returned by GetProcessWindowStation and GetThreadDesktop should not be closed private class NoopSafeHandle : SafeHandle { public NoopSafeHandle(IntPtr handle) : base(handle, false) { } public override bool IsInvalid { get { return false; } } protected override bool ReleaseHandle() { return true; } } 

Basierend auf der Antwort von @ Stephen Martin und Martin Prikryl.

Mit diesem Code können Sie einen process mit verschiedenen Benutzeranmeldeinformationen von einem Dienst ausführen.
Ich habe jetzt den Quellcode optimiert.
Das Entfernen und Setzen von Rechten ist nun auch möglich.

 namespace QlikConnectorPSExecute { #region Usings using System; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; using System.Security.AccessControl; using System.Security.Principal; #endregion //inspired by: http://stackoverflow.com/questions/677874/starting-a-process-with-credentials-from-a-windows-service public class WindowsGrandAccess : IDisposable { #region DLL-Import // All the code to manipulate a security object is available in .NET framework, // but its API tries to be type-safe and handle-safe, enforcing a special implementation // (to an otherwise generic WinAPI) for each handle type. This is to make sure // only a correct set of permissions can be set for corresponding object types and // mainly that handles do not leak. // Hence the AccessRule and the NativeObjectSecurity classes are abstract. // This is the simplest possible implementation that yet allows us to make use // of the existing .NET implementation, sparing necessity to // P/Invoke the underlying WinAPI. [DllImport("user32.dll", SetLastError = true)] private static extern IntPtr GetProcessWindowStation(); [DllImport("user32.dll", SetLastError = true)] private static extern IntPtr GetThreadDesktop(int dwThreadId); [DllImport("kernel32.dll", SetLastError = true)] private static extern int GetCurrentThreadId(); #endregion #region Variables && Properties public static int WindowStationAllAccess { get; private set; } = 0x000f037f; public static int DesktopRightsAllAccess { get; private set; } = 0x000f01ff; private GenericSecurity WindowStationSecurity {get; set;} private GenericSecurity DesktopSecurity { get; set; } private int? OldWindowStationMask { get; set; } private int? OldDesktopMask { get; set; } private NTAccount AccountInfo { get; set; } private SafeHandle WsSafeHandle { get; set; } private SafeHandle DSafeHandle { get; set; } #endregion #region Constructor & Dispose public WindowsGrandAccess(NTAccount accountInfo, int windowStationMask, int desktopMask) { if (accountInfo != null) Init(accountInfo, windowStationMask, desktopMask); } public void Dispose() { try { if (AccountInfo == null) return; RestAccessMask(OldWindowStationMask, WindowStationAllAccess, WindowStationSecurity, WsSafeHandle); RestAccessMask(OldDesktopMask, DesktopRightsAllAccess, DesktopSecurity, DSafeHandle); } catch (Exception ex) { throw new Exception($"The object \"{nameof(WindowsGrandAccess)}\" could not be dispose.", ex); } } #endregion #region Methods private void Init(NTAccount accountInfo, int windowStationMask, int desktopMask) { AccountInfo = accountInfo; WsSafeHandle = new NoopSafeHandle(GetProcessWindowStation()); WindowStationSecurity = new GenericSecurity(false, ResourceType.WindowObject, WsSafeHandle, AccessControlSections.Access); DSafeHandle = new NoopSafeHandle(GetThreadDesktop(GetCurrentThreadId())); DesktopSecurity = new GenericSecurity(false, ResourceType.WindowObject, DSafeHandle, AccessControlSections.Access); OldWindowStationMask = ReadAccessMask(WindowStationSecurity, WsSafeHandle, windowStationMask); OldDesktopMask = ReadAccessMask(DesktopSecurity, DSafeHandle, desktopMask); } private AuthorizationRuleCollection GetAccessRules(GenericSecurity security) { return security.GetAccessRules(true, false, typeof(NTAccount)); } private int? ReadAccessMask(GenericSecurity security, SafeHandle safeHandle, int accessMask) { var ruels = GetAccessRules(security); var username = AccountInfo.Value; if (!username.Contains("\\")) username = $"{Environment.MachineName}\\{username}"; var userResult = ruels.Cast().SingleOrDefault(r => r.IdentityReference.Value.ToLower() == username.ToLower() && accessMask == r.PublicAccessMask); if (userResult == null) { AddGrandAccess(security, accessMask, safeHandle); userResult = ruels.Cast().SingleOrDefault(r => r.IdentityReference.Value.ToLower() == username.ToLower()); if (userResult != null) return userResult.PublicAccessMask; } else return userResult.PublicAccessMask; return null; } private void AddGrandAccess(GenericSecurity security, int accessMask, SafeHandle safeHandle) { var rule = new GrantAccessRule(AccountInfo, accessMask, AccessControlType.Allow); security.AddAccessRule(rule); security.Persist(safeHandle, AccessControlSections.Access); } private void RemoveGrantAccess(GenericSecurity security, int accessMask, SafeHandle safeHandle) { var rule = new GrantAccessRule(AccountInfo, accessMask, AccessControlType.Allow); security.RemoveAccessRule(rule); security.Persist(safeHandle, AccessControlSections.Access); } private void SetGrandAccess(GenericSecurity security, int accessMask, SafeHandle safeHandle) { var rule = new GrantAccessRule(AccountInfo, accessMask, AccessControlType.Allow); security.SetAccessRule(rule); security.Persist(safeHandle, AccessControlSections.Access); } private void RestAccessMask(int? oldAccessMask, int fullAccessMask, GenericSecurity security, SafeHandle safeHandle) { if (oldAccessMask == null) RemoveGrantAccess(security, fullAccessMask, safeHandle); else if (oldAccessMask != fullAccessMask) { SetGrandAccess(security, oldAccessMask.Value, safeHandle); } } #endregion #region private classes private class GenericSecurity : NativeObjectSecurity { public GenericSecurity( bool isContainer, ResourceType resType, SafeHandle objectHandle, AccessControlSections sectionsRequested) : base(isContainer, resType, objectHandle, sectionsRequested) { } new public void Persist(SafeHandle handle, AccessControlSections includeSections) { base.Persist(handle, includeSections); } new public void AddAccessRule(AccessRule rule) { base.AddAccessRule(rule); } new public bool RemoveAccessRule(AccessRule rule) { return base.RemoveAccessRule(rule); } new public void SetAccessRule(AccessRule rule) { base.SetAccessRule(rule); } new public AuthorizationRuleCollection GetAccessRules(bool includeExplicit, bool includeInherited, Type targetType) { return base.GetAccessRules(includeExplicit, includeInherited, targetType); } public override Type AccessRightType { get { throw new NotImplementedException(); } } public override AccessRule AccessRuleFactory( System.Security.Principal.IdentityReference identityReference, int accessMask, bool isInherited, InheritanceFlags inheritanceFlags, PropagationFlags propagationFlags, AccessControlType type) { return new GrantAccessRule(identityReference, accessMask, isInherited, inheritanceFlags, propagationFlags, type); } public override Type AccessRuleType { get { return typeof(AccessRule); } } public override AuditRule AuditRuleFactory( System.Security.Principal.IdentityReference identityReference, int accessMask, bool isInherited, InheritanceFlags inheritanceFlags, PropagationFlags propagationFlags, AuditFlags flags) { throw new NotImplementedException(); } public override Type AuditRuleType { get { return typeof(AuditRule); } } } private class GrantAccessRule : AccessRule { public GrantAccessRule(IdentityReference identity, int accessMask, bool isInherited, InheritanceFlags inheritanceFlags, PropagationFlags propagationFlags, AccessControlType type) : base(identity, accessMask, isInherited, inheritanceFlags, propagationFlags, type) { } public GrantAccessRule(IdentityReference identity, int accessMask, AccessControlType type) : base(identity, accessMask, false, InheritanceFlags.None, PropagationFlags.None, type) { } public int PublicAccessMask { get { return base.AccessMask; } } } // Handles returned by GetProcessWindowStation and GetThreadDesktop should not be closed private class NoopSafeHandle : SafeHandle { public NoopSafeHandle(IntPtr handle) : base(handle, false) {} public override bool IsInvalid { get { return false; } } protected override bool ReleaseHandle() { return true; } } #endregion } } 

Verwenden von Probe

 using (var windowsAccess = new WindowsGrandAccess(accountInfo, WindowsGrandAccess.WindowStationAllAccess, WindowsGrandAccess.DesktopRightsAllAccess)) { ... } 

Vielen Dank.

Dies ist symptomatisch für:
– unzureichende Rechte;
– Fehlerlast einer Bibliothek;

Verwenden Sie Filemon, um Zugriffe zu finden, die verweigert wurden oder
WinDbg, um die Anwendung in einem Debugger auszuführen und jedes Problem anzuzeigen.

Wie stellen Sie die Domain, den Benutzer und das Passwort ein? Legen Sie die Domain und das Passwort richtig fest (es muss ein SecureString verwendet werden).

Stellen Sie die WorkingDirectory-Eigenschaft außerdem ein? Wenn Sie einen Benutzernamen und ein Kennwort verwenden, besagt die Dokumentation, dass Sie die WorkingDirectory-Eigenschaft festlegen müssen.

Es kann sein, dass jeder von einem Dienst gestartete process auch die Berechtigung “Als Dienst anmelden” haben muss.

Wenn die Benutzer-ID, die Sie zum Starten des zweiten processes verwenden, keine Administratorrechte für die Box hat, könnte dies der Fall sein.

Ein einfacher Test wäre, die lokale Sicherheitsrichtlinie so zu ändern, dass die Benutzer-ID “Als Dienst anmelden” angezeigt wird und es erneut versucht wird.

Bearbeiten: Nach den zusätzlichen Informationen ..

In diesem Fall scheint es, dass 0xc0000142 nicht in der Lage ist, eine benötigte DLL zu initialisieren. Gibt es etwas, das der Dienst geöffnet hat, den der erzeugte process benötigt? Auf jeden Fall sieht es so aus, als hätte es mit dem process zu tun, der gestartet wurde, und nicht, wie Sie es tun.

Ich hatte dieses Problem heute und ich verbrachte einige Zeit damit, es herauszufinden. Was ich getan habe, war, den Dienst als interaktiv zu erstellen (über das Kontrollkästchen “Dienst mit Desktop interagieren” in services.msc). Sobald ich das tat, verschwanden die 0xc0000142 Fehler.