2009-03-24 3 views
9

У меня есть служба Windows, которая работает как mydomain \ userA. Я хочу иметь возможность запускать произвольные .exe из службы. Обычно я использую Process.Start(), и он отлично работает, но в некоторых случаях я хочу запустить исполняемый файл как другой пользователь (mydomain \ userB).Запуск процесса с учетными данными из службы Windows

Если я изменяю ProcessStartInfo, я использую, чтобы запустить процесс для включения учетных данных, я начинаю получать ошибки - либо диалоговое окно с ошибкой, в котором говорится: «Приложению не удалось правильно инициализировать (0xc0000142). Нажмите« ОК », чтобы завершить приложение. »или« Доступ лишен »Win32Exception. Если я запустил код запуска процесса из командной строки вместо его запуска в службе, процесс начнет использовать правильные учетные данные (я проверил это, установив ProcessStartInfo для запуска whoami.exe и захвата вывода командной строки).

Я также пробовал олицетворение с помощью WindowsIdentity.Impersonate(), но это не сработало - как я понимаю, олицетворение влияет только на текущий поток, а запуск нового процесса наследует дескриптор безопасности процесса, а не текущая нить.

Я запускаю это в изолированном тестовом домене, поэтому оба пользователя и пользователя являются админами домена, и оба имеют право входа в систему как право службы в домене.

+0

Это Vista OS? Это происходит при запуске Notepad.exe? –

+0

Можете ли вы поставить пример кода своих аргументов? –

+0

Жаль, что я не смог вернуться к вам раньше. Если вам все еще нужен образец кода, я отредактировал свой ответ, чтобы добавить ссылку на статью, которую я недавно опубликовал с исходным кодом. –

ответ

16

При запуске нового процесса с использованием ProcessStartInfo процесс запускается на той же оконной станции и на рабочем столе, что и процесс запуска. Если вы используете разные учетные данные, тогда у пользователя вообще не будет достаточных прав на запуск на этом рабочем столе. Невозможность инициализации ошибок возникает, когда user32.dll пытается инициализировать новый процесс и не может.

Чтобы обойти это, вы должны сначала восстановить дескрипторы безопасности, связанные с оконной станцией и рабочим столом, и добавить соответствующие разрешения для DACL для своего пользователя, а затем запустить процесс под новыми учетными данными.

EDIT: подробное описание того, как это сделать и пример кода, было немного длинным для этого, поэтому я собрал код article с кодом.

 //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)) 
     { 
     } 
+0

Thanks; похоже, что это является причиной проблемы. У вас есть предложения по наилучшему способу получения DACL и добавлению разрешений для второго пользователя? –

+0

Если вы создаете интерактивную службу, вам не нужно устанавливать DACL на Window Station и Desktop. –

+0

То же самое для запуска процесса с учетными данными из «службы WCF»? – Kiquenet

0

Как вы устанавливаете домен, пользователя и пароль? Вы правильно настроили домен, а также пароль (он должен использовать SecureString).

Также вы устанавливаете свойство WorkingDirectory? При использовании UserName и Password в документации указано, что вы должны установить свойство WorkDirectory.

+0

Я правильно настраиваю домен, имя пользователя и пароль (как SecureString) - если я запускаю этот код за пределами службы Windows, он работает так, как ожидалось. Настройка WorkDirectory тоже не влияет. –

2

Это симптоматика:
- недостаточные права;
- отказ загрузки библиотеки;

Использование Filemon для обнаружения отказа в доступе или
WinDbg для запуска приложения в отладчике и просмотра любой проблемы.

+0

* сейчас * я прочитал ваш ответ ... +1 для ссылки Filemon, я думаю, что это укажет на проблему. – Moose

0

Возможно, любой процесс, инициированный службой, также должен иметь приказ «Вход в систему».

Если идентификатор пользователя, который вы используете для запуска второго процесса, не имеет административных прав на это поле, это может быть так.

Простым тестом было бы изменение локальной политики безопасности, чтобы дать пользователю «Вход в систему как услуга» и повторить попытку.

Edit: После получения дополнительной информации ..

выпаса над Google на этом, кажется, что 0xc0000142 относится не в состоянии инициализировать необходимый DLL. Есть ли что-то, что служба открыла, что требуется процессу порождения? В любом случае, похоже, что это связано с начатым процессом, а не с тем, как вы это делаете.

+0

У обоих пользователей включен вход в систему как услуга. –

+0

Добавил еще мой ответ. Похоже, что у вас могут быть конфликты между службой и порожденным процессом на основе ошибки 0xc0000142. – Moose

0

У меня была эта проблема сегодня, и я потратил немало времени, пытаясь понять это. То, что я закончил, это создать службу как интерактивную (с помощью флажка «Разрешить службу для взаимодействия с рабочим столом» в services.msc). Как только я это сделал, ошибки 0xc0000142 исчезли.

7

Основано на answer by @StephenMartin.

Новый процесс, запущенный с использованием класса Process, запускается на той же оконной станции и на рабочем столе, что и процесс запуска. Если вы используете новый процесс с использованием разных учетных данных, новый процесс не будет иметь прав доступа к оконной станции и настольному компьютеру. Что приводит к ошибкам, например, 0xC0000142.

Ниже приводится «компактный» автономный код, предоставляющий пользователю доступ к текущей станции и настольному окну. Это не требует библиотеки AsproLock.

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

Это помогло мне решить большую проблему. Большое спасибо. Продолжайте работать –

+1

Я уже делал это на прошлой неделе :). –

1

На основании ответа по @Stephen Мартин и Мартин Přikryl.

Этот код поможет вам запустить процесс с различными учетными данными пользователя из службы.
Я теперь оптимизировал исходный код.
Теперь возможно удаление и установка прав.

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<GrantAccessRule>().SingleOrDefault(r => r.IdentityReference.Value.ToLower() == username.ToLower() && accessMask == r.PublicAccessMask); 
      if (userResult == null) 
      { 
       AddGrandAccess(security, accessMask, safeHandle); 
       userResult = ruels.Cast<GrantAccessRule>().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 
    } 
} 

Использование образца

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

Спасибо.