別のユーザーとして別のプロセス(app.exe)を起動するIIS WCFサービスがあります。私は両方のアプリケーションを完全に制御できます(これは今のところ開発環境です)。 IISアプリプールは、ボックスのローカル管理者でもあるドメインユーザー(DOMAIN\nirvin)として実行されます。 2番目のプロセスは、ローカルユーザー(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
レポートID:7a45cd1c-933e-11e2-93f8-005056b316dd
障害のあるパッケージのフルネーム:
障害のあるパッケージ相対アプリケーションID:
App.exe(現在)にかなり完全にログインしているので、.NETコードでエラーがスローされることはないと思います(もう)。
これが本当に厄介な部分です。プロセスを間違って起動しているだけだと思ったので、Process.Start()
呼び出しをダムのWinFormsアプリにコピーして、自分のようにマシン上で実行しました。正しいパラメータ。ですからもちろん、それは初めて、そしてそれ以来毎回機能しました。私は一貫して2番目のプロセスを起動し、意図したとおりに実行することができます。動作しないのはIISからの起動のみです。
「バッチジョブとしてログオン」にsvc-low権限を付与し、「プロセスレベルトークンを置き換える」(ローカルセキュリティポリシー内)権限を自分に付与しようとしましたが、どちらも違いがないようです。
助けて!
当初、app.exeはコンソールアプリケーションでした。起動しようとすると、conhost.exeでイベントログにエラーが生成されるため、app.exeをWindowsアプリケーションに切り替えました。それは方程式から共謀者を取り除いたが、私にここで説明された状況を残した。 (その道を この質問 によって導かれました。)
私が使用する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)を呼び出します。 CreateProcessWithLogonWはWindowsサービス環境から呼び出すことはできません(IIS WCFサービスなど))。呼び出すことしかできません。対話型プロセス(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
の使用は、Microsoftの推奨事項の一部ではありませんでした。他のコードとの互換性を壊さないように、このようにしました。
Exception code: 0xc06d007e
これは、Microsoft Visual C++、ファシリティコード0x6dに固有の例外です。エラーコードは0x007e(126)、ERROR_MOD_NOT_FOUND、「指定されたモジュールが見つかりませんでした」です。この例外は、遅延ロードされたDLLが見つからない場合に発生します。ほとんどのプログラマーは、この例外を生成するコードvc/include /delayhlp.cppをVisualStudioのインストールディレクトリに持っています。
それは、DLLに固有の、典型的な「ファイルが見つかりません」という事故です。 DLLが欠落しているものがわからない場合は、SysInternalsのProcMonユーティリティを使用できます。プログラムがDLLを検索し、爆撃する前に。
不適切に設計されたプログラムをProcess.Start()でクラッシュさせる古典的な方法は、ProcessStartInfo.WorkingDirectoryプロパティをEXEが格納されているディレクトリに設定しないことです。通常は偶然ですが、Processクラスを使用する場合はそうではありません。最初にそれに取り組むようには見えません。