我们有一个IIS WCF服务,它以另一个用户的身份启动另一个进程(app.exe)。我完全控制了两个应用程序(现在这是一个开发环境)。 IIS应用程序池以我的身份运行,域用户(DOMAIN \ nirvin),也是该框的本地管理员。第二个进程应该作为本地用户运行(svc-low)。我正在使用System.Diagnostics.Process.Start(ProcessStartInfo)
来启动该过程。该过程成功启动 - 我知道因为没有抛出异常,我得到了一个进程ID。但是该过程立即死亡,我在事件日志中看到如下错误:
错误应用程序名称:app.exe,版本:1.0.3.0,时间戳:0x514cd763
错误模块名称:KERNELBASE.dll,版本:6.2.9200.16451,时间戳:0x50988aa6
异常代码:0xc06d007e
故障偏移:0x000000000003811c
错误进程ID:0x10a4
错误申请开始时间:0x01ce274b3c83d62d
错误应用程序路径:C:\ Program Files \ company \ app \ app.exe
错误模块路径:C:\ Windows \ system32 \ KERNELBASE.dll
报告编号:7a45cd1c-933e-11e2-93f8-005056b316dd
错误包全名:
错误包相关的应用程序ID:
我已经在app.exe(现在)中进行了非常彻底的日志记录,因此我认为它不会在.NET代码中引发错误(不再)。
这是真正令人讨厌的部分:我认为我刚刚启动了错误的进程,所以我在一个愚蠢的WinForms应用程序中复制了我的Process.Start()
调用,然后像我一样在机器上运行它,希望能够修补它直到我得到参数正确。因此,当然这是第一次和每次都有效:我能够始终如一地启动第二个流程并使其按预期运行。它只从IIS启动不起作用。
我尝试过“以批处理作业登录”的svc-low权限,并且我已尝试授予自己“替换进程级别令牌”(在本地安全策略中)的权限,但似乎都没有任何差异。
帮助!
首先,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说我应该转到原生API,但是a)该问题解决了不同的情况; b)愚蠢的WinForms应用程序的成功表明Process.Start
是一个可行的选择工作
答案 0 :(得分:14)
我最终向微软开了一个案例,这是我给出的信息:
当指定凭据时,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);
}
使这段代码工作有一些先决条件。运行它的用户必须具有“替换进程级令牌”和“调整进程的内存配额”的用户权限,而“其他用户”必须具有“以批处理作业登录”的用户权限。可以在Local Security Policy下(或可能通过组策略)找到这些设置。如果您更改它们,则需要重新启动。
UserToken
是可以通过ReleaseUserToken
关闭的属性,因为我们会反复调用StartProcess
,并且我们被告知不要一次又一次地登录其他用户。
SecureStringToString()
方法取自this question。使用SecureString
不是微软推荐的一部分;我是这样做的,以免破坏与其他代码的兼容性。
答案 1 :(得分:5)
Exception code: 0xc06d007e
这是Microsoft Visual C ++特有的例外,设施代码为0x6d。错误代码为0x007e(126),ERROR_MOD_NOT_FOUND,“无法找到指定的模块”。无法找到延迟加载的DLL时引发此异常。大多数程序员都拥有在他们的机器上生成此异常的代码,即Visual Studio安装目录中的vc / include / delayhlp.cpp。
嗯,这是典型的“找不到文件”的错误,特定于DLL。如果您不知道缺少什么DLL,那么您可以使用SysInternals的ProcMon实用程序。你会看到程序搜索DLL而不是在炸弹之前找到它。
使用Process.Start()使设计不佳的程序崩溃的一种经典方法是不将ProcessStartInfo.WorkingDirectory属性设置为存储EXE的目录。它通常是偶然的,但在使用Process类时不会。看起来不像你这样做首先解决。