为什么这个过程一启动就会崩溃?

我们有一个IIS WCF服务,它以另一个用户的身份启动另一个进程(app.exe)。 我完全控制了两个应用程序(现在这是一个开发环境)。 IIS应用程序池以我的身份运行,域用户(DOMAIN \ nirvin),也是该框的本地管理员。 第二个进程应该作为本地用户运行(svc-low)。 我正在使用System.Diagnostics.Process.Start(ProcessStartInfo)来启动该进程。 该过程成功启动 – 我知道因为没有抛出exception,我得到了一个进程ID。 但是进程立即死亡,我在事件日志中看到如下错误:

错误应用程序名称:app.exe,版本:1.0.3.0,时间戳:0x514cd763

错误模块名称:KERNELBASE.dll,版本:6.2.9200.16451,时间戳:0x50988aa6

exception代码:0xc06d007e

故障偏移:0x000000000003811c

错误进程id:0x10a4

错误应用程序启动时间:0x01ce274b3c83d62d

错误的应用程序路径:C:\ Program Files \ company \ app \ app.exe

错误模块路径:C:\ Windows \ system32 \ KERNELBASE.dll

报告ID:7a45cd1c-933e-11e2-93f8-005056b316dd

错误包全名:

错误包相关的应用程序ID:

我已经在app.exe(现在)中进行了非常彻底的日志记录,因此我认为它不会在.NET代码中引发错误(不再)。

这是真正令人讨厌的部分:我认为我只是启动了错误的进程,所以我在一个愚蠢的WinForms应用程序中复制了我的Process.Start()调用并在机器上像我一样运行它,希望能够修改,直到我得到正确的参数。 因此,当然这是第一次和每次都有效:我能够始终如一地启动第二个流程并使其按预期运行。 它只能从IIS启动,不起作用。

我试过给svc-low权限“以批处理作业登录”,并且我已尝试授予自己“替换进程级别令牌”(在本地安全策略中)的权限,但似乎都没有任何区别。

救命!

环境细节

  • Windows Server 2012
  • .NET 4.5(提到的所有应用程序)

额外细节

起初app.exe是一个控制台应用程序。 尝试启动是让conhost.exe在事件日志中生成错误,因此我将app.exe切换为Windows应用程序。 这让conhost脱离了这个等式,但让我了解了这里描述的情况。 (通过这个问题引导了这条道路。)

我使用的ProcessStartInfo对象如下所示:

 new ProcessStartInfo { FileName = fileName, Arguments = allArguments, Domain = domainName, UserName = userName, Password = securePassword, WindowStyle = ProcessWindowStyle.Hidden, CreateNoWindow = true, UseShellExecute = false, RedirectStandardOutput = false //LoadUserProfile = true //I've done it with and without this set }; 

一个现有的问题说我应该转到原生API,但是a)这个问题解决了不同的情况; b)愚蠢的WinForms应用程序的成功表明Process.Start是一个可行的选择。

我最终向微软公开了一个案例,这是我给出的信息:

当指定凭据时,Process.Start在内部调用CreateProcessWithLogonW(CPLW)。 无法从Windows服务环境 (例如IIS WCF服务) 调用 CreateProcessWithLogonW。 它只能从Interactive Process(由通过CTRL-ALT-DELETE登录的用户启动的应用程序)调用。

(这是支持工程师的逐字逐句;强调我的)

他们建议我使用CreateProcessAsUser 。 他们给了我一些有用的示例代码,然后我根据自己的需要进行了调整,现在一切都很好!

最终结果如下:

 using System; using System.ComponentModel; using System.Diagnostics; using System.Runtime.InteropServices; using System.Security; public class ProcessHelper { static ProcessHelper() { UserToken = IntPtr.Zero; } private static IntPtr UserToken { get; set; } public int StartProcess(ProcessStartInfo processStartInfo) { LogInOtherUser(processStartInfo); Native.STARTUPINFO startUpInfo = new Native.STARTUPINFO(); startUpInfo.cb = Marshal.SizeOf(startUpInfo); startUpInfo.lpDesktop = string.Empty; Native.PROCESS_INFORMATION processInfo = new Native.PROCESS_INFORMATION(); bool processStarted = Native.CreateProcessAsUser(UserToken, processStartInfo.FileName, processStartInfo.Arguments, IntPtr.Zero, IntPtr.Zero, true, 0, IntPtr.Zero, null, ref startUpInfo, out processInfo); if (!processStarted) { throw new Win32Exception(Marshal.GetLastWin32Error()); } uint processId = processInfo.dwProcessId; Native.CloseHandle(processInfo.hProcess); Native.CloseHandle(processInfo.hThread); return (int) processId; } private static void LogInOtherUser(ProcessStartInfo processStartInfo) { if (UserToken == IntPtr.Zero) { IntPtr tempUserToken = IntPtr.Zero; string password = SecureStringToString(processStartInfo.Password); bool loginResult = Native.LogonUser(processStartInfo.UserName, processStartInfo.Domain, password, Native.LOGON32_LOGON_BATCH, Native.LOGON32_PROVIDER_DEFAULT, ref tempUserToken); if (loginResult) { UserToken = tempUserToken; } else { Native.CloseHandle(tempUserToken); throw new Win32Exception(Marshal.GetLastWin32Error()); } } } private static String SecureStringToString(SecureString value) { IntPtr stringPointer = Marshal.SecureStringToBSTR(value); try { return Marshal.PtrToStringBSTR(stringPointer); } finally { Marshal.FreeBSTR(stringPointer); } } public static void ReleaseUserToken() { Native.CloseHandle(UserToken); } } internal class Native { internal const int LOGON32_LOGON_INTERACTIVE = 2; internal const int LOGON32_LOGON_BATCH = 4; internal const int LOGON32_PROVIDER_DEFAULT = 0; [StructLayout(LayoutKind.Sequential)] internal struct PROCESS_INFORMATION { public IntPtr hProcess; public IntPtr hThread; public uint dwProcessId; public uint dwThreadId; } [StructLayout(LayoutKind.Sequential)] internal struct STARTUPINFO { public int cb; [MarshalAs(UnmanagedType.LPStr)] public string lpReserved; [MarshalAs(UnmanagedType.LPStr)] public string lpDesktop; [MarshalAs(UnmanagedType.LPStr)] public string lpTitle; public uint dwX; public uint dwY; public uint dwXSize; public uint dwYSize; public uint dwXCountChars; public uint dwYCountChars; public uint dwFillAttribute; public uint dwFlags; public short wShowWindow; public short cbReserved2; public IntPtr lpReserved2; public IntPtr hStdInput; public IntPtr hStdOutput; public IntPtr hStdError; } [StructLayout(LayoutKind.Sequential)] public struct SECURITY_ATTRIBUTES { public System.UInt32 nLength; public IntPtr lpSecurityDescriptor; public bool bInheritHandle; } [DllImport("advapi32.dll", EntryPoint = "LogonUserW", SetLastError = true, CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall)] internal extern static bool LogonUser(String lpszUsername, String lpszDomain, String lpszPassword, int dwLogonType, int dwLogonProvider, ref IntPtr phToken); [DllImport("advapi32.dll", EntryPoint = "CreateProcessAsUserA", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)] internal extern static bool CreateProcessAsUser(IntPtr hToken, [MarshalAs(UnmanagedType.LPStr)] string lpApplicationName, [MarshalAs(UnmanagedType.LPStr)] string lpCommandLine, IntPtr lpProcessAttributes, IntPtr lpThreadAttributes, bool bInheritHandle, uint dwCreationFlags, IntPtr lpEnvironment, [MarshalAs(UnmanagedType.LPStr)] string lpCurrentDirectory, ref STARTUPINFO lpStartupInfo, out PROCESS_INFORMATION lpProcessInformation); [DllImport("kernel32.dll", EntryPoint = "CloseHandle", SetLastError = true, CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)] internal extern static bool CloseHandle(IntPtr handle); } 

使这段代码工作有一些先决条件。 运行它的用户必须具有“替换进程级令牌”和“调整进程的内存配额”的用户权限,而“其他用户”必须具有“以批处理作业登录”的用户权限。 可以在本地安全策略下 (或可能通过组策略)找到这些设置。 如果更改它们,则需要重新启动。

UserToken是一个可以通过ReleaseUserToken关闭的属性,因为我们将重复调用StartProcess ,并且我们被告知不要一次又一次地登录其他用户。

SecureStringToString()方法取自这个问题 。 使用SecureString不是微软推荐的一部分; 我是这样做的,以免破坏与其他代码的兼容性。

  Exception code: 0xc06d007e 

这是一个特殊于Microsoft Visual C ++,设施代码0x6d的例外。 错误代码是0x007e(126),ERROR_MOD_NOT_FOUND,“无法找到指定的模块”。 无法找到延迟加载的DLL时引发此exception。 大多数程序员都拥有在他们的机器上生成此exception的代码,即Visual Studio安装目录中的vc / include / delayhlp.cpp。

嗯,这是典型的“文件未找到”的事故,特定于DLL。 如果您不知道缺少什么DLL,那么您可以使用SysInternals的ProcMon实用程序。 你会看到程序搜索DLL而不是在炸弹之前找到它。

使用Process.Start()使设计不佳的程序崩溃的一种经典方法是不将ProcessStartInfo.WorkingDirectory属性设置为存储EXE的目录。 它通常是偶然的,但不会在您使用Process类时。 看起来不像你这样做首先解决。