C# 使用来自Windows服务的凭据启动进程

C# 使用来自Windows服务的凭据启动进程,c#,.net,windows-services,C#,.net,Windows Services,我有一个作为mydomain\userA运行的Windows服务。我希望能够从服务中运行任意.exe。通常,我使用Process.Start(),它工作正常,但在某些情况下,我希望以不同的用户(mydomain\userB)运行可执行文件 如果我更改用于启动进程的ProcessStartInfo以包含凭据,则会出现错误-显示“应用程序未能正确初始化(0xc0000142)。单击“确定”终止应用程序”的错误对话框,或者显示“访问被拒绝”Win32Exception。如果我从命令行运行进程启动代码而

我有一个作为mydomain\userA运行的Windows服务。我希望能够从服务中运行任意.exe。通常,我使用Process.Start(),它工作正常,但在某些情况下,我希望以不同的用户(mydomain\userB)运行可执行文件

如果我更改用于启动进程的ProcessStartInfo以包含凭据,则会出现错误-显示“应用程序未能正确初始化(0xc0000142)。单击“确定”终止应用程序”的错误对话框,或者显示“访问被拒绝”Win32Exception。如果我从命令行运行进程启动代码而不是在服务中运行它,则进程将使用正确的凭据启动(我已通过将ProcessStartInfo设置为运行whoami.exe并捕获命令行输出来验证这一点)

我还尝试过使用WindowsIdentity.Impersonate()进行模拟,但这不起作用-据我所知,模拟只影响当前线程,启动新进程会继承进程的安全描述符,而不是当前线程


我在一个独立的测试域中运行这个程序,因此userA和userB都是域管理员,并且都具有作为服务登录的权限。如何设置域、用户和密码?您是否正确设置了域以及密码(它必须使用SecureString)


另外,您是否正在设置WorkingDirectory属性?当使用用户名和密码时,文档说明必须设置WorkingDirectory属性。

这表明:
-权利不足
-图书馆的故障负荷

使用Filemon检测某些访问被拒绝或

WinDbg在调试器中运行应用程序并查看任何问题。

可能是由服务启动的任何进程也必须具有“作为服务登录”权限

如果用于启动第二个进程的用户id没有该框的管理权限,则可能是这种情况

一个简单的测试是更改本地安全策略,给用户标识“作为服务登录”,然后重试

编辑:在附加信息之后


在这一点上浏览Google,似乎0xc0000142与无法初始化所需的DLL有关。服务是否打开了衍生流程需要的某些内容?在任何情况下,它看起来都与启动的流程有关,而与您的操作方式无关。

当您使用ProcessStartInfo启动新流程时,该流程将在启动流程的同一窗口站和桌面上启动。如果您使用不同的凭据,则用户通常没有足够的权限在该桌面上运行。初始化错误失败是由于user32.dll尝试在新进程中初始化而无法初始化时引起的

要解决此问题,您必须首先检索与窗口站和桌面关联的安全描述符,并为您的用户向DACL添加适当的权限,然后在新凭据下启动流程

编辑:关于如何做到这一点的详细描述和示例代码在这里有点长,所以我把代码放在一起

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

我今天遇到了这个问题,我花了相当长的时间试图解决它。我最后做的是将服务创建为交互式的(使用services.msc中的“允许服务与桌面交互”复选框)。当我这样做时,0xc0000142错误就消失了。

基于

使用
进程
类启动的新进程与启动进程在同一窗口站和桌面上运行。如果使用不同的凭据运行新进程,则新进程将没有访问窗口站和桌面的权限。导致0xC0000142等错误的原因

以下是授予用户访问当前窗口站和桌面的“压缩”独立代码。它不需要AsproLock库

在调用
Process.Start
之前,使用用于运行
进程的用户名调用
GrantAccessToWindowStationAndDesktop
方法(
Process.StartInfo.username

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;
    }
}
基于@Stephen Martin和Martin Prikryl的回答

此代码帮助您使用来自服务的不同用户凭据运行进程。
我现在已经优化了源代码。
现在也可以取消和设定权利

名称空间QLIKConnectorPSecute
{
#区域使用
使用制度;
使用System.Collections.Generic;
使用System.Linq;
使用System.Runtime.InteropServices;
使用System.Security.AccessControl;
使用System.Security.Principal;
#端区
//灵感来自:http://stackoverflow.com/questions/677874/starting-a-process-with-credentials-from-a-windows-service
公共类访问:IDisposable
{
#区域DLL导入
//操作安全对象的所有代码都可以在.NET framework中使用,
//但是它的API试图做到类型安全和处理安全,强制执行一个特殊的实现
//(对于其他通用WinAPI)的每个句柄类型。这是为了确保
//只能为相应的对象类型和类型设置正确的权限集
//主要是手柄不会泄漏。
//因此AccessRule和NativeObjectSecurity类是抽象的。
//这是允许我们使用的最简单的实现
//现有的.NET实现,无需
//P/调用底层WinAPI。
[DllImport(“user32.dll”,SetLastError=true)]
私有静态外部IntPtr getProcessWindowsStation();
[DllImport(“user32.dll”,SetLastError=true)]
私有静态外部IntPtr GetThreadDesktop(intdwthreadid);
[DllImport(“kernel32.dll”
# Import .NET objects using pythonnet
from System.Diagnostics import Process

# Use .NET API to run a subprocess using the given executable
# as the target user, in the provided working directory.
process = Process()
process.StartInfo.UseShellExecute = False
process.StartInfo.CreateNoWindow = True
process.StartInfo.LoadUserProfile = True
process.StartInfo.RedirectStandardOutput = True
process.StartInfo.RedirectStandardError = True
process.StartInfo.WorkingDirectory = working_dir
process.StartInfo.Domain = "mydomain"
process.StartInfo.UserName = username.lower().replace("mydomain\\", "")
process.StartInfo.PasswordInClearText = password
process.StartInfo.FileName = executable
process.StartInfo.Arguments = " ".join(args)

# Run the subprocess.
process.Start()

# Read subprocess console output
stdout = process.StandardOutput.ReadToEnd()
stderr = process.StandardError.ReadToEnd()
log.info(f"\n{executable} subprocess stdout:\n\n{stdout}")
log.info(f"{executable} subprocess stderr:\n\n{stderr}")
log.info(f"Done running {executable} as {username}.")
import win32api, win32process, win32service, win32security

WINDOW_STATION_ALL_ACCESS = 983935
DESKTOP_RIGHTS_ALL_ACCESS = 983551
SE_WINDOW_OBJECT = 7
DACL_SECURITY_INFORMATION = 4


def set_access(user, handle, access):
    info = win32security.GetSecurityInfo(
        handle, SE_WINDOW_OBJECT, DACL_SECURITY_INFORMATION
    )
    dacl = info.GetSecurityDescriptorDacl()
    dacl.AddAccessAllowedAce(win32security.ACL_REVISION, access, user)
    win32security.SetSecurityInfo(
        handle, SE_WINDOW_OBJECT, DACL_SECURITY_INFORMATION, None, None, dacl, None
    )


username = "mattsegal"
user, domain, user_type = win32security.LookupAccountName("", username)
thread_id = win32api.GetCurrentThreadId()
station_handle = win32process.GetProcessWindowStation()
desktop_handle = win32service.GetThreadDesktop(thread_id)
set_access(user, station_handle, WINDOW_STATION_ALL_ACCESS)
set_access(user, desktop_handle, DESKTOP_RIGHTS_ALL_ACCESS)