web-dev-qa-db-ja.com

このプロセスが起動されるとすぐにクラッシュするのはなぜですか?

別のユーザーとして別のプロセス(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権限を付与し、「プロセスレベルトークンを置き換える」(ローカルセキュリティポリシー内)権限を自分に付与しようとしましたが、どちらも違いがないようです。

助けて!

環境の詳細

  • Windows Server 2012
  • .NET 4.5(言及されているすべてのアプリケーション)

さらなる詳細

当初、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が実行可能な選択であることを示唆しています仕事で。

13
nateirvin

私はマイクロソフトとの訴訟を起こすことになりました、そしてこれは私が与えられた情報です:

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の推奨事項の一部ではありませんでした。他のコードとの互換性を壊さないように、このようにしました。

16
nateirvin
  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クラスを使用する場合はそうではありません。最初にそれに取り組むようには見えません。

5
Hans Passant