从Windows服务的特定凭据启动进程

我已经花了几天时间来解决这个问题,尽管网上有很多不同的例子,这是一个棘手的问题,我不能让他们在我的场景中工作。

我有一个在本地系统帐户下运行的Windows服务。 它有一个WCF端点侦听API请求。 当通过API告知时,该服务应该在系统会话(0)和“工作者”帐户凭证中启动新进程。 该进程是一个检查队列中的工作并执行此操作的工作程序。 如果找不到工作,它会睡一会儿并再次检查。 如果确实找到了工作,它会在同一会话中启动一个新进程并使用相同的凭据并完成工作。 完成工作后关闭。

“Worker”是域帐户,是计算机上本地管理员组的成员,对可执行文件具有执行权限。 该计算机与该帐户位于同一域中。

问题是,当服务尝试启动进程时,它会从CreateProcessAsUser方法获取ERROR_ACCESS_DENIED (5)错误代码。

我尝试在具有相同凭据的Windows 7计算机上运行相同的代码并且工作正常,但在Windows Server 2008上运行时会获得该错误代码。

代码太大了,不能在这里显示,所以我把它放在其他地方……

ProcessHelper : http : //pastie.org/private/y7idu3nw4xv1fxzeizbn9g

该服务调用StartAsUserFromService方法来启动进程,该进程在建立会话后在内部调用CreateProcessAsUser 。 该进程调用StartAsUserFromApplication方法来启动其后继,后者在内部调用CreateProcessWithLogonW

ImpersonationContext : http : //pastie.org/private/xppc7wnoidajmpq8h8sg

该服务需要获取用户令牌以启动进程。 该过程不需要启动其后继者。 据我所知,模拟在Server 2008上是成功的,但它没有一些权限,我无法弄清楚哪个。

编辑:

我在Windows 7计算机上尝试了本地管理员帐户和域帐户,它们运行正常。 但它们都不能在Server 2008机器上运行。 某处必须有遗失许可,但我不知道在哪里; 错误消息没有帮助。

我还尝试在可执行文件的兼容性选项卡中勾选“以管理员身份运行”框,但它没有任何区别。

编辑:

我使用进程监视器来查看服务中发生了什么,这是它收到错误的地方……

 Date & Time: 12/02/2014 11:44:03 Event Class: File System Operation: CreateFile Result: ACCESS DENIED Path: D:\..\executable.exe TID: 6244 Duration: 0.0000450 Desired Access: Read Data/List Directory, Execute/Traverse, Read Attributes, Synchronize Disposition: Open Options: Synchronous IO Non-Alert, Non-Directory File Attributes: n/a ShareMode: Read, Delete AllocationSize: n/a Impersonating: Domain\Worker 

 Date & Time: 12/02/2014 11:44:03 Event Class: File System Operation: CreateFile Result: ACCESS DENIED Path: D:\..\executable.exe TID: 6244 Duration: 0.0000480 Desired Access: Execute/Traverse, Synchronize Disposition: Open Options: Synchronous IO Non-Alert, Non-Directory File Attributes: n/a ShareMode: Read, Delete AllocationSize: n/a Impersonating: Domain\Worker 

一些技巧 :
如何冒充
C#中的模拟代码
模拟图书馆(Class&Com Class)
WindowsIdentity.Impersonate方法

尝试使用此示例(在某处找到):

使用系统;
使用System.Runtime.InteropServices;
使用System.Security.Principal;
使用System.Security.Permissions;

 [组件:SecurityPermissionAttribute(SecurityAction.RequestMinimum,UnmanagedCode =真)]
 [assembly:PermissionSetAttribute(SecurityAction.RequestMinimum,Name =“FullTrust”)] 
公共类ImpersonationDemo 
 {

     [StructLayout(LayoutKind.Sequential)]
     public struct SECURITY_ATTRIBUTES
     {
         public int Length;
         public IntPtr lpSecurityDescriptor;
         public bool bInheritHandle;
     }

    公共枚举SECURITY_IMPERSONATION_LEVEL
     {
         SecurityAnonymous,
         SecurityIdentification,
         SecurityImpersonation,
         SecurityDelegation
     }

     public enum TOKEN_TYPE
     {
         TokenPrimary = 1,
         TokenImpersonation
     } 

     [DllImport(“advapi32.dll”,CharSet = CharSet.Auto,SetLastError = true)]
     public static extern bool LogonUser(String lpszUsername,String lpszDomain,String lpszPassword,int dwLogonType,int dwLogonProvider,ref IntPtr phToken);

     [DllImport(“kernel32.dll”,CharSet = CharSet.Auto)]
     private unsafe static extern int FormatMessage(int dwFlags,ref IntPtr lpSource,int dwMessageId,int dwLanguageId,ref String lpBuffer,int nSize,IntPtr * Arguments);

     [DllImport(“kernel32.dll”,CharSet = CharSet.Auto)]
     public extern static bool CloseHandle(IntPtr handle);

     [DllImport(“advapi32.dll”,CharSet = CharSet.Auto,SetLastError = true)]
     public extern static bool DuplicateToken(IntPtr ExistingTokenHandle,int SECURITY_IMPERSONATION_LEVEL,ref IntPtr DuplicateTokenHandle);

     [DllImport(“advapi32.dll”,CharSet = CharSet.Auto,SetLastError = true)]
     public extern static bool DuplicateTokenEx(IntPtr hExistingToken,uint dwDesiredAccess,ref SECURITY_ATTRIBUTES lpTokenAttributes,
         SECURITY_IMPERSONATION_LEVEL ImpersonationLevel,
         TOKEN_TYPE TokenType,
         IntPtr phNewToken);

     // GetErrorMessage格式并返回错误消息
     //对应于输入的errorCode。
     public unsafe静态字符串GetErrorMessage(int errorCode)
     {
         int FORMAT_MESSAGE_ALLOCATE_BUFFER = 0x00000100;
         int FORMAT_MESSAGE_IGNORE_INSERTS = 0x00000200;
         int FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000;

         int messageSize = 255;
         String lpMsgBuf =“”;
         int dwFlags = FORMAT_MESSAGE_ALLOCATE_BUFFER |  FORMAT_MESSAGE_FROM_SYSTEM |  FORMAT_MESSAGE_IGNORE_INSERTS;

         IntPtr ptrlpSource = IntPtr.Zero;
         IntPtr prtArguments = IntPtr.Zero;

         int retVal = FormatMessage(dwFlags,ref ptrlpSource,errorCode,0,ref lpMsgBuf,messageSize,&prtArguments);
         if(0 == retVal)
         {
            抛出新的exception(“无法格式化错误代码的消息”+ errorCode +“。”);
         }

     return lpMsgBuf;
     }

     //测试工具
     //如果将此代码合并到DLL中,请务必要求FullTrust。
     [PermissionSetAttribute(SecurityAction.Demand,Name =“FullTrust”)]
     public static void Main(string [] args)
     {
         IntPtr tokenHandle = new IntPtr(0);
         IntPtr dupeTokenHandle = new IntPtr(0);
        尝试
         {
             string UserName,MachineName;

             //使用获取指定用户,计算机和密码的用户令牌
             //非托管的LogonUser方法。

             Console.Write(“输入要登录的计算机的名称:”);
             MachineName = Console.ReadLine();

             Console.Write(“输入您要模拟的{0}上的用户的登录名:”,MachineName);
             UserName = Console.ReadLine();

             Console.Write(“输入{0}的密码:”,UserName);

             const int LOGON32_PROVIDER_DEFAULT = 3;
             //此参数使LogonUser创建主令牌。
             const int LOGON32_LOGON_INTERACTIVE = 8;

             tokenHandle = IntPtr.Zero;
             dupeTokenHandle = IntPtr.Zero;

             //调用LogonUser以获取访问令牌的句柄。
             bool returnValue = LogonUser(UserName,MachineName,“mm4geata”, 
                 LOGON32_LOGON_INTERACTIVE,LOGON32_PROVIDER_DEFAULT,ref tokenHandle);

             Console.WriteLine(“LogonUser called。”);

             if(false == returnValue)
             {
                 int ret = Marshal.GetLastWin32Error();
                 Console.WriteLine(“LogonUser失败,错误代码:{0}”,ret);
                 Console.WriteLine(“\ n错误:[{0}] {1} \ n”,ret,GetErrorMessage(ret));

                返回;
             }

             Console.WriteLine(“LogonUser成功了吗?”+(returnValue?“是”:“否”));
             Console.WriteLine(“Windows NT令牌的值:”+ tokenHandle);

             //检查身份
             Console.WriteLine(“冒充之前:”+ WindowsIdentity.GetCurrent()。Name);

             // bool retVal = DuplicateToken(tokenHandle,SecurityImpersonation,ref dupeTokenHandle);

             SECURITY_ATTRIBUTES sa = new SECURITY_ATTRIBUTES();
             sa.bInheritHandle = true;
             sa.Length = Marshal.SizeOf(sa);
             sa.lpSecurityDescriptor =(IntPtr)0;

             bool retVal = DuplicateTokenEx(tokenHandle,0x10000000,ref sa,SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation,TOKEN_TYPE.TokenImpersonation,out dupeTokenHandle); 


             if(false == retVal)
             {
                 CloseHandle的(tokenHandle);
                 Console.WriteLine(“尝试复制令牌时抛出exception。”);
                返回;
             }


             //传递给以下构造函数的标记必须
             //是主要令牌,以便将其用于模拟。
             WindowsIdentity newId =新的WindowsIdentity(dupeTokenHandle);
             WindowsImpersonationContext impersonatedUser = newId.Impersonate();

             //检查身份
             Console.WriteLine(“冒充后:”+ WindowsIdentity.GetCurrent()。Name);

             //停止冒充用户
             impersonatedUser.Undo();

             //检查身份
             Console.WriteLine(“撤消后:”+ WindowsIdentity.GetCurrent()。Name);

             //释放令牌
             if(tokenHandle!= IntPtr.Zero)
             CloseHandle的(tokenHandle);
             if(dupeTokenHandle!= IntPtr.Zero)
             CloseHandle的(dupeTokenHandle);
         }
         catch(Exception ex)
         {
             Console.WriteLine(“exception发生。”+ ex.Message);
         }

     }
 }

我设法让这些代码从这个代码开始:

ProcessHelper : http : //pastie.org/private/dlkytj8rbigs8ixwtg

TokenImpersonationContext : http : //pastie.org/private/nu3pvpghoea6pwwlvjuq

该服务调用StartAsUserFromService方法,该进程调用StartAsUserFromApplication方法以启动其后继方法。

我在LogonUser调用中使用LogonType.Batch ,因为进程需要与另一个WCF服务通信并需要进行身份validation。 可以使用LogonType.NetworkLogonType.NetworkClearText ,但在Net.Tcp端口共享服务中使用Worker用户帐户导致权限问题。

这个答案很有用: 使用Process.Start()以Windows服务中的不同用户身份启动进程