13

別のユーザーとして別のプロセス (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
};

既存の質問Process.Startでは、ネイティブ API に移行する必要があると言われていますが、a) その質問は別の状況に対応しており、b) 馬鹿げた WinForms アプリの成功は、それが仕事の実行可能な選択であることを示唆しています。

4

2 に答える 2

17

最終的に Microsoft にケースを提出したところ、以下の情報が提供されました。

資格情報が指定されている場合、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);
}

このコードを機能させるには、いくつかの前提条件があります。それを実行しているユーザーには、「プロセス レベル トークンを置き換える」および「プロセスのメモリ クォータを調整する」ためのユーザー権限が必要であり、「他のユーザー」には「バッチ ジョブとしてログオンする」ためのユーザー権限が必要です。これらの設定は、ローカル セキュリティ ポリシー(または場合によってはグループ ポリシー) の下にあります。それらを変更すると、再起動が必要になります。

UserTokenReleaseUserToken繰り返し呼び出すためStartProcess、他のユーザーを何度もログオンしないように言われたため、閉じることができるプロパティです。

そのSecureStringToString()方法はこの質問から取られました。を使用するSecureStringことは、Microsoft の推奨事項ではありませんでした。他のコードとの互換性を損なわないように、このようにしました。

于 2013-03-28T17:44:44.260 に答える
6
  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 クラスを使用する場合はそうではありません。あなたのようには見えないので、最初にそれに取り組みます。

于 2013-03-22T23:52:04.603 に答える