我们有一个 IIS WCF 服务,它以不同的用户身份启动另一个进程 (app.exe)。我可以完全控制这两个应用程序(目前这是一个开发环境)。 IIS 应用程序池以我的身份运行,我是一个域用户 (DOMAIN\nirvin),他也是机器上的本地管理员。第二个进程应该作为本地用户 (svc-low) 运行。我正在使用 System.Diagnostics.Process.Start(ProcessStartInfo)
启动进程。该进程成功启动 - 我知道是因为没有抛出异常,并且我获得了一个进程 ID。但是进程立即终止,我在事件日志中收到如下错误:
Faulting application name: app.exe, version: 1.0.3.0, time stamp: 0x514cd763
Faulting module name: KERNELBASE.dll, version: 6.2.9200.16451, time stamp: 0x50988aa6
Exception code: 0xc06d007e
Fault offset: 0x000000000003811c
Faulting process id: 0x10a4
Faulting application start time: 0x01ce274b3c83d62d
Faulting application path: C:\Program Files\company\app\app.exe
Faulting module path: C:\Windows\system32\KERNELBASE.dll
Report Id: 7a45cd1c-933e-11e2-93f8-005056b316dd
Faulting package full name:
Faulting package-relative application ID:
我已经在 app.exe 中进行了非常彻底的登录(现在),所以我认为它不会在 .NET 代码中抛出错误(不再)。
这是真正令人讨厌的部分:我认为我只是错误地启动了进程,所以我将我的 Process.Start()
调用复制到一个愚蠢的 WinForms 应用程序中,然后像我一样在机器上运行它,希望能修修补补,直到我得到正确的参数。因此,当然这在第一次和之后的每一次都有效:我能够始终如一地启动第二个进程并让它按预期运行。它只能从无法运行的 IIS 启动。
我已经尝试授予 svc-low 权限以“作为批处理作业登录”,并且我已尝试授予自己“替换进程级 token ”的权限(在本地安全策略中),但似乎都没有成功任何区别。
帮助!
环境细节
- window 服务器 2012
- .NET 4.5(提到的所有应用程序)
其他详细信息
起初 app.exe 是一个控制台应用程序。尝试启动使 conhost.exe 在事件日志中生成错误,因此我将 app.exe 切换为 Windows 应用程序。这把 conhost 排除在外,但给我留下了这里描述的情况。 (由 this question 引导这条路。)
我使用的 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
};
An existing question说我应该深入了解 native API,但是 a) 这个问题解决了不同的情况,b) 愚蠢的 WinForms 应用程序的成功表明 Process.Start
是这项工作的可行选择。
最佳答案
我最终向 Microsoft 提出了一个案例,这是我得到的信息:
Process.Start internally calls CreateProcessWithLogonW(CPLW) when credentials are specified. CreateProcessWithLogonW cannot be called from a Windows Service Environment (such as an IIS WCF service). It can only be called from an Interactive Process (an application launched by a user who logged on via 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);
}
要使此代码正常工作,有一些先决条件。运行它的用户必须具有“替换进程级 token ”和“调整进程的内存配额”的用户权限,而“其他用户”必须具有“作为批处理作业登录”的用户权限。这些设置可以在 Local Security Policy 下找到(或可能通过组策略)。如果更改它们,将需要重新启动。
UserToken
是一个可以通过 ReleaseUserToken
关闭的属性,因为我们将重复调用 StartProcess
并且我们被告知不要记录其他用户一次又一次。
SecureStringToString()
方法取自 this question . Microsoft 不推荐使用 SecureString
;我这样做是为了不破坏与其他代码的兼容性。
关于c# - 为什么这个进程一启动就崩溃了?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15581142/