web-dev-qa-db-ja.com

Process.Start()を使用して、Windowsサービス内から別のユーザーとしてプロセスを開始する

Windowsサービスから指定されたユーザーアカウントで任意の.NET exeを定期的に実行したいのですが。

これまでのところ、対象のプロセスが何か、いつ実行するかを決定するロジックを使用してWindowsサービスを実行しています。ターゲットプロセスは次の方法で開始されます。

  1. Windowsサービスは、「管理者」資格情報を使用して開始されます。
  2. 時間が来ると、開始するプロセスの詳細を示す引数(ファイル名、ユーザー名、ドメイン、パスワード)を使用して中間.NETプロセスが実行されます。
  3. このプロセスは、新しいSystem.Diagnostics.Processを作成し、渡された引数で満たされたProcessStartInfoオブジェクトを関連付けてから、プロセスオブジェクトでStart()を呼び出します。

初回これが発生しますターゲットプロセスは正常に実行され、正常に終了します。ただし、以降のたびに、ターゲットプロセスが開始されるとすぐに、「アプリケーションは正しく初期化できませんでした(0xc0000142)」というエラーがスローされます。 Windowsサービスを再起動すると、プロセスがもう一度(最初の実行に対して)正常に実行されます。

当然、目標は毎回ターゲットプロセスを正常に実行することです。

上記のステップ2について:別のユーザーとしてプロセスを実行するには、.NETがwin32関数CreateProcessWithLogonWを呼び出します。この関数には、指定されたユーザーをログインさせるためのウィンドウハンドルが必要です。Windowsサービスはインタラクティブモードで実行されていないため、ウィンドウハンドルはありません。この中間プロセスは、ターゲットプロセスに渡すことができるウィンドウハンドルを持っているため、問題を解決します。

PsexecやWindowsタスクプランナーの使用に関する提案はありません。私は自分の人生の多くを受け入れました。これには、上記の方法で問題を解決することが含まれます。

35
Matt Jacobsen

次のシナリオでは、実装が機能しているようです(Works On My Machine(TM))。

バッチファイル、.NETコンソールアセンブリ、.NET Windowsフォームアプリケーション。

方法は次のとおりです。

管理者ユーザーとしてWindowsサービスを実行しています。次のポリシーを管理者ユーザーに追加します。

  • サービスとしてログオン
  • オペレーティングシステムの一部として機能する
  • プロセスのメモリクォータを調整する
  • プロセスレベルトークンを置き換える

これらのポリシーは、コントロールパネル/管理ツール/ローカルセキュリティポリシー/ユーザー権利の割り当てを開いて追加できます。いったん設定されると、ポリシーは次のログインまで有効になりません。 Administratorの代わりに別のユーザーを使用できます。これにより、状況が少し安全になる可能性があります。

現在、私のWindowsサービスには、他のユーザーとしてジョブを開始するために必要な権限があります。ジョブを開始する必要がある場合、サービスは別のアセンブリ(「スターター」.NETコンソールアセンブリ)を実行します。これにより、プロセスが開始されます。

Windowsサービスにある次のコードは、「スターター」コンソールアセンブリを実行します。

Process proc = null;
System.Diagnostics.ProcessStartInfo info;
string domain = string.IsNullOrEmpty(row.Domain) ? "." : row.Domain;
info = new ProcessStartInfo("Starter.exe");
info.Arguments = cmd + " " + domain + " " + username + " " + password + " " + args;
info.WorkingDirectory = Path.GetDirectoryName(cmd);
info.UseShellExecute = false;
info.RedirectStandardError = true;
info.RedirectStandardOutput = true;
proc = System.Diagnostics.Process.Start(info);

次に、コンソールアセンブリは相互運用呼び出しを介してターゲットプロセスを開始します。

class Program
{
    #region Interop

    [StructLayout(LayoutKind.Sequential)]
    public struct LUID
    {
        public UInt32 LowPart;
        public Int32 HighPart;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct LUID_AND_ATTRIBUTES
    {
        public LUID Luid;
        public UInt32 Attributes;
    }

    public struct TOKEN_PRIVILEGES
    {
        public UInt32 PrivilegeCount;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)]
        public LUID_AND_ATTRIBUTES[] Privileges;
    }

    enum TOKEN_INFORMATION_CLASS
    {
        TokenUser = 1,
        TokenGroups,
        TokenPrivileges,
        TokenOwner,
        TokenPrimaryGroup,
        TokenDefaultDacl,
        TokenSource,
        TokenType,
        TokenImpersonationLevel,
        TokenStatistics,
        TokenRestrictedSids,
        TokenSessionId,
        TokenGroupsAndPrivileges,
        TokenSessionReference,
        TokenSandBoxInert,
        TokenAuditPolicy,
        TokenOrigin,
        TokenElevationType,
        TokenLinkedToken,
        TokenElevation,
        TokenHasRestrictions,
        TokenAccessInformation,
        TokenVirtualizationAllowed,
        TokenVirtualizationEnabled,
        TokenIntegrityLevel,
        TokenUIAccess,
        TokenMandatoryPolicy,
        TokenLogonSid,
        MaxTokenInfoClass
    }

    [Flags]
    enum CreationFlags : uint
    {
        CREATE_BREAKAWAY_FROM_JOB = 0x01000000,
        CREATE_DEFAULT_ERROR_MODE = 0x04000000,
        CREATE_NEW_CONSOLE = 0x00000010,
        CREATE_NEW_PROCESS_GROUP = 0x00000200,
        CREATE_NO_WINDOW = 0x08000000,
        CREATE_PROTECTED_PROCESS = 0x00040000,
        CREATE_PRESERVE_CODE_AUTHZ_LEVEL = 0x02000000,
        CREATE_SEPARATE_WOW_VDM = 0x00001000,
        CREATE_SUSPENDED = 0x00000004,
        CREATE_UNICODE_ENVIRONMENT = 0x00000400,
        DEBUG_ONLY_THIS_PROCESS = 0x00000002,
        DEBUG_PROCESS = 0x00000001,
        DETACHED_PROCESS = 0x00000008,
        EXTENDED_STARTUPINFO_PRESENT = 0x00080000
    }

    public enum TOKEN_TYPE
    {
        TokenPrimary = 1,
        TokenImpersonation
    }

    public enum SECURITY_IMPERSONATION_LEVEL
    {
        SecurityAnonymous,
        SecurityIdentification,
        SecurityImpersonation,
        SecurityDelegation
    }

    [Flags]
    enum LogonFlags
    {
        LOGON_NETCREDENTIALS_ONLY = 2,
        LOGON_WITH_PROFILE = 1
    }

    enum LOGON_TYPE
    {
        LOGON32_LOGON_INTERACTIVE = 2,
        LOGON32_LOGON_NETWORK,
        LOGON32_LOGON_BATCH,
        LOGON32_LOGON_SERVICE,
        LOGON32_LOGON_UNLOCK = 7,
        LOGON32_LOGON_NETWORK_CLEARTEXT,
        LOGON32_LOGON_NEW_CREDENTIALS
    }

    enum LOGON_PROVIDER
    {
        LOGON32_PROVIDER_DEFAULT,
        LOGON32_PROVIDER_WINNT35,
        LOGON32_PROVIDER_WINNT40,
        LOGON32_PROVIDER_WINNT50
    }

    #region _SECURITY_ATTRIBUTES
    //typedef struct _SECURITY_ATTRIBUTES {  
    //    DWORD nLength;  
    //    LPVOID lpSecurityDescriptor;  
    //    BOOL bInheritHandle;
    //} SECURITY_ATTRIBUTES,  *PSECURITY_ATTRIBUTES,  *LPSECURITY_ATTRIBUTES;
    #endregion
    struct SECURITY_ATTRIBUTES
    {
        public uint Length;
        public IntPtr SecurityDescriptor;
        public bool InheritHandle;
    }

    [Flags] enum SECURITY_INFORMATION : uint
    {
        OWNER_SECURITY_INFORMATION        = 0x00000001,
        GROUP_SECURITY_INFORMATION        = 0x00000002,
        DACL_SECURITY_INFORMATION         = 0x00000004,
        SACL_SECURITY_INFORMATION         = 0x00000008,
        UNPROTECTED_SACL_SECURITY_INFORMATION = 0x10000000,
        UNPROTECTED_DACL_SECURITY_INFORMATION = 0x20000000,
        PROTECTED_SACL_SECURITY_INFORMATION   = 0x40000000,
        PROTECTED_DACL_SECURITY_INFORMATION   = 0x80000000
    }

    #region _SECURITY_DESCRIPTOR
    //typedef struct _SECURITY_DESCRIPTOR {
    //  UCHAR  Revision;
    //  UCHAR  Sbz1;
    //  SECURITY_DESCRIPTOR_CONTROL  Control;
    //  PSID  Owner;
    //  PSID  Group;
    //  PACL  Sacl;
    //  PACL  Dacl;
    //} SECURITY_DESCRIPTOR, *PISECURITY_DESCRIPTOR;
    #endregion
    [StructLayoutAttribute(LayoutKind.Sequential)]
    struct SECURITY_DESCRIPTOR
    {
        public byte revision;
        public byte size;
        public short control; // public SECURITY_DESCRIPTOR_CONTROL control;
        public IntPtr owner;
        public IntPtr group;
        public IntPtr sacl;
        public IntPtr dacl;
    }

    #region _STARTUPINFO
    //typedef struct _STARTUPINFO {  
    //    DWORD cb;  
    //    LPTSTR lpReserved;  
    //    LPTSTR lpDesktop;  
    //    LPTSTR lpTitle;  
    //    DWORD dwX;  
    //    DWORD dwY;  
    //    DWORD dwXSize;  
    //    DWORD dwYSize;  
    //    DWORD dwXCountChars;  
    //    DWORD dwYCountChars;  
    //    DWORD dwFillAttribute;  
    //    DWORD dwFlags;  
    //    Word wShowWindow;  
    //    Word cbReserved2;  
    //    LPBYTE lpReserved2;  
    //    HANDLE hStdInput;  
    //    HANDLE hStdOutput;  
    //    HANDLE hStdError; 
    //} STARTUPINFO,  *LPSTARTUPINFO;
    #endregion
    struct STARTUPINFO
    {
        public uint cb;
        [MarshalAs(UnmanagedType.LPTStr)]
        public string Reserved;
        [MarshalAs(UnmanagedType.LPTStr)]
        public string Desktop;
        [MarshalAs(UnmanagedType.LPTStr)]
        public string Title;
        public uint X;
        public uint Y;
        public uint XSize;
        public uint YSize;
        public uint XCountChars;
        public uint YCountChars;
        public uint FillAttribute;
        public uint Flags;
        public ushort ShowWindow;
        public ushort Reserverd2;
        public byte bReserverd2;
        public IntPtr StdInput;
        public IntPtr StdOutput;
        public IntPtr StdError;
    }

    #region _PROCESS_INFORMATION
    //typedef struct _PROCESS_INFORMATION {  
    //  HANDLE hProcess;  
    //  HANDLE hThread;  
    //  DWORD dwProcessId;  
    //  DWORD dwThreadId; } 
    //  PROCESS_INFORMATION,  *LPPROCESS_INFORMATION;
    #endregion
    [StructLayout(LayoutKind.Sequential)]
    struct PROCESS_INFORMATION
    {
        public IntPtr Process;
        public IntPtr Thread;
        public uint ProcessId;
        public uint ThreadId;
    }

    [DllImport("advapi32.dll", SetLastError = true)]
    static extern bool InitializeSecurityDescriptor(IntPtr pSecurityDescriptor, uint dwRevision);
    const uint SECURITY_DESCRIPTOR_REVISION = 1;

    [DllImport("advapi32.dll", SetLastError = true)]
    static extern bool SetSecurityDescriptorDacl(ref SECURITY_DESCRIPTOR sd, bool daclPresent, IntPtr dacl, bool daclDefaulted);

    [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    extern static bool DuplicateTokenEx(
        IntPtr hExistingToken,
        uint dwDesiredAccess,
        ref SECURITY_ATTRIBUTES lpTokenAttributes,
        SECURITY_IMPERSONATION_LEVEL ImpersonationLevel,
        TOKEN_TYPE TokenType,
        out IntPtr phNewToken);

    [DllImport("advapi32.dll", SetLastError = true)]
    public static extern bool LogonUser(
        string lpszUsername,
        string lpszDomain,
        string lpszPassword,
        int dwLogonType,
        int dwLogonProvider,
        out IntPtr phToken
        );

    #region GetTokenInformation
    //BOOL WINAPI GetTokenInformation(
    //  __in       HANDLE TokenHandle,
    //  __in       TOKEN_INFORMATION_CLASS TokenInformationClass,
    //  __out_opt  LPVOID TokenInformation,
    //  __in       DWORD TokenInformationLength,
    //  __out      PDWORD ReturnLength
    //);
    #endregion
    [DllImport("advapi32.dll", SetLastError = true)]
    static extern bool GetTokenInformation(
        IntPtr TokenHandle,
        TOKEN_INFORMATION_CLASS TokenInformationClass,
        IntPtr TokenInformation,
        int TokenInformationLength,
        out int ReturnLength
        );


    #region CreateProcessAsUser
    //        BOOL WINAPI CreateProcessAsUser(
    //  __in_opt     HANDLE hToken,
    //  __in_opt     LPCTSTR lpApplicationName,
    //  __inout_opt  LPTSTR lpCommandLine,
    //  __in_opt     LPSECURITY_ATTRIBUTES lpProcessAttributes,
    //  __in_opt     LPSECURITY_ATTRIBUTES lpThreadAttributes,
    //  __in         BOOL bInheritHandles,
    //  __in         DWORD dwCreationFlags,
    //  __in_opt     LPVOID lpEnvironment,
    //  __in_opt     LPCTSTR lpCurrentDirectory,
    //  __in         LPSTARTUPINFO lpStartupInfo,
    //  __out        LPPROCESS_INFORMATION lpProcessInformation);
    #endregion
    [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    static extern bool CreateProcessAsUser(
        IntPtr Token, 
        [MarshalAs(UnmanagedType.LPTStr)] string ApplicationName,
        [MarshalAs(UnmanagedType.LPTStr)] string CommandLine,
        ref SECURITY_ATTRIBUTES ProcessAttributes, 
        ref SECURITY_ATTRIBUTES ThreadAttributes, 
        bool InheritHandles,
        uint CreationFlags, 
        IntPtr Environment, 
        [MarshalAs(UnmanagedType.LPTStr)] string CurrentDirectory, 
        ref STARTUPINFO StartupInfo, 
        out PROCESS_INFORMATION ProcessInformation);

    #region CloseHandle
    //BOOL WINAPI CloseHandle(
    //      __in          HANDLE hObject
    //        );
    #endregion
    [DllImport("Kernel32.dll")]
    extern static int CloseHandle(IntPtr handle);

    [DllImport("advapi32.dll", ExactSpelling = true, SetLastError = true)]
    internal static extern bool AdjustTokenPrivileges(IntPtr htok, bool disall, ref TokPriv1Luid newst, int len, IntPtr prev, IntPtr relen);

    [DllImport("advapi32.dll", SetLastError = true)]
    internal static extern bool LookupPrivilegeValue(string Host, string name, ref long pluid);

    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    internal struct TokPriv1Luid
    {
        public int Count;
        public long Luid;
        public int Attr;
    }

    //static internal const int TOKEN_QUERY = 0x00000008;
    internal const int SE_PRIVILEGE_ENABLED = 0x00000002;
    //static internal const int TOKEN_ADJUST_PRIVILEGES = 0x00000020;

    internal const int TOKEN_QUERY = 0x00000008;
    internal const int TOKEN_DUPLICATE = 0x0002;
    internal const int TOKEN_ASSIGN_PRIMARY = 0x0001;

    #endregion

    [STAThread]
    static void Main(string[] args)
    {
        string username, domain, password, applicationName;
        username = args[2];
        domain = args[1];
        password = args[3];
        applicationName = @args[0];

        IntPtr token = IntPtr.Zero;
        IntPtr primaryToken = IntPtr.Zero;
        try
        {
            bool result = false;

            result = LogonUser(username, domain, password, (int)LOGON_TYPE.LOGON32_LOGON_NETWORK, (int)LOGON_PROVIDER.LOGON32_PROVIDER_DEFAULT, out token);
            if (!result)
            {
                int winError = Marshal.GetLastWin32Error();
            }

            string commandLine = null;

            #region security attributes
            SECURITY_ATTRIBUTES processAttributes = new SECURITY_ATTRIBUTES();

            SECURITY_DESCRIPTOR sd = new SECURITY_DESCRIPTOR();
            IntPtr ptr = Marshal.AllocCoTaskMem(Marshal.SizeOf(sd));
            Marshal.StructureToPtr(sd, ptr, false);
            InitializeSecurityDescriptor(ptr, SECURITY_DESCRIPTOR_REVISION);
            sd = (SECURITY_DESCRIPTOR)Marshal.PtrToStructure(ptr, typeof(SECURITY_DESCRIPTOR));

            result = SetSecurityDescriptorDacl(ref sd, true, IntPtr.Zero, false);
            if (!result)
            {
                int winError = Marshal.GetLastWin32Error();
            }

            primaryToken = new IntPtr();
            result = DuplicateTokenEx(token, 0, ref processAttributes, SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, TOKEN_TYPE.TokenPrimary, out primaryToken);
            if (!result)
            {
                int winError = Marshal.GetLastWin32Error();
            }
            processAttributes.SecurityDescriptor = ptr;
            processAttributes.Length = (uint)Marshal.SizeOf(sd);
            processAttributes.InheritHandle = true;
            #endregion

            SECURITY_ATTRIBUTES threadAttributes = new SECURITY_ATTRIBUTES();
            threadAttributes.SecurityDescriptor = IntPtr.Zero;
            threadAttributes.Length = 0;
            threadAttributes.InheritHandle = false;

            bool inheritHandles = true;
            //CreationFlags creationFlags = CreationFlags.CREATE_DEFAULT_ERROR_MODE;
            IntPtr environment = IntPtr.Zero;
            string currentDirectory = currdir;

            STARTUPINFO startupInfo = new STARTUPINFO();
            startupInfo.Desktop = "";

            PROCESS_INFORMATION processInformation;

            result = CreateProcessAsUser(primaryToken, applicationName, commandLine, ref processAttributes, ref threadAttributes, inheritHandles, 16, environment, currentDirectory, ref startupInfo, out processInformation);
            if (!result)
            {
                int winError = Marshal.GetLastWin32Error();
                File.AppendAllText(logfile, DateTime.Now.ToLongTimeString() + " " + winError + Environment.NewLine);
            }
        }
        catch
        {
            int winError = Marshal.GetLastWin32Error();
            File.AppendAllText(logfile, DateTime.Now.ToLongTimeString() + " " + winError + Environment.NewLine);
        }
        finally
        {
            if (token != IntPtr.Zero)
            {
                int x = CloseHandle(token);
                if (x == 0)
                    throw new Win32Exception(Marshal.GetLastWin32Error());
                x = CloseHandle(primaryToken);
                if (x == 0)
                    throw new Win32Exception(Marshal.GetLastWin32Error());
            }
        }
    }

基本的な手順は次のとおりです。

  1. ユーザーをログオン
  2. 指定されたトークンをプライマリトークンに変換します
  3. このトークンを使用して、プロセスを実行します
  4. 終了したらハンドルを閉じます。

これは私のマシンからの新しい開発コードであり、実稼働環境で使用する準備がほぼ整っていません。ここのコードはまだバグがあります-初心者のために:ハンドルが正しいポイントで閉じられているかどうかはわかりませんが、上記で定義されている必要のない相互運用関数がいくつかあります。別のスタータープロセスも本当に私を困らせます。理想的には、このジョブだけでなく、APIやこのサービスで使用するために、アセンブリにラップされます。誰かがここに何か提案があれば、喜ばれるでしょう。

23
Matt Jacobsen

単なる推測-開始情報でLoadUserProfile = trueを使用していますか? CreateProcessWithLogonWは、特に指示しない限り、デフォルトでユーザーレジストリHiveをロードしません。

1
liggett78

呼び出しが最初に失敗した理由は、「デフォルト」のセキュリティ記述子(それが何であれ)を使用しているためと考えられます。

から msdn

lpProcessAttributes [in、オプション]

新しいプロセスオブジェクトのセキュリティ記述子を指定し、子プロセスがプロセスに返されたハンドルを継承できるかどうかを決定するSECURITY_ATTRIBUTES構造体へのポインター。 lpProcessAttributesがNULLまたはlpSecurityDescriptorがNULLの場合、プロセスはデフォルトのセキュリティ記述子を取得し、ハンドルを継承できません。デフォルトのセキュリティ記述子は、hTokenパラメータで参照されるユーザーのものです。このセキュリティ記述子は、呼び出し元のアクセスを許可しない場合があります。この場合、実行後にプロセスを再び開くことはできません。プロセスハンドルは有効であり、引き続き完全なアクセス権があります。

CreateProcessWithLogonWがこのデフォルトのセキュリティ記述子を作成していると思います(いずれの場合も、指定していません)。

相互運用を開始する時間...

1
Matt Jacobsen

Psexecもタスクプランナーもお勧めしません。しかし、 Sudowin を見たことはありますか?

プロセスを実行する前にパスワードを要求することを除いて、それはほぼ正確にあなたが望むことを行います。

また、すべてがオープンソースであるため、関連するサービスからプロセスが何度も実行される様子を確認できます。

1
Vinko Vrsalovic

私はこのコメントをmsdnで読んだところです( http://msdn.Microsoft.com/en-us/library/ms682431(VS.85).aspx ):

この関数でユーザーアプリケーションを呼び出さないでください。 ChristianWimmer |
編集|履歴を表示してくださいお待ちくださいドキュメントの編集や類似の機能(Wordなど)を提供するユーザーモードアプリケーションを呼び出すと、保存されていないデータはすべて失われます。これは、通常のシャットダウンシーケンスがCreateProcessWithLogonWで起動されたプロセスには適用されないためです。このようにして、起動されたアプリケーションは、WM_QUERYENDSESSION、WM_ENDSESSION、および最も重要なWM_QUITメッセージを取得しません。そのため、データの保存やデータのクリーンアップは求められません。警告なしで終了します。この関数はユーザーフレンドリーではないため、注意して使用する必要があります。

それは単に「悪いユーザー体験」です。誰もそれを期待していません。

これは私が観察したことを説明することができます:初めて動作します。それ以降は失敗します。これは、何かが内部で適切にクリーンアップされていないという私の信念を強めます

0
Matt Jacobsen

CreateProcessWithLogonWを使用するためにウィンドウハンドルは必要ありません。情報の出所がわかりません。

アプリケーションの初期化に失敗したエラーには多くの原因がありますが、ほとんどの場合、セキュリティまたは枯渇したユーザーリソースに関連しています。何を実行しているのか、何を実行しているのかについての詳細情報がないと、これを診断するのは非常に困難です。ユーザーは、ウィンドウステーションとそれが起動されているデスクトップにアクセスする権限を持っています。初期化時にロードする必要のあるdllファイルに対する正しい権限を持っていますか。

0
Stephen Martin

Windowsサービス内で「runas」動詞を使用してPhantomJSバイナリを起動しようとしたときに、同様の問題が発生しました。次の手順で問題を解決しました:

  • ユーザーになりすます
  • 開始プロセス(UIなし)
  • なりすまし

偽装には Impersonator-Class を使用できます。また、アプリケーションがWindows UIにアクセスしようとしないように、ProcessStartInfoで次のプロパティを設定することも重要です。

var processStartInfo = new ProcessStartInfo()
{
    FileName = $@"{assemblyFolder}\PhantomJS\phantomjs.exe",
    Arguments = $"--webdriver={port}",
    RedirectStandardOutput = true,
    RedirectStandardError = true,
    RedirectStandardInput = true,
    UseShellExecute = false,
    CreateNoWindow = true,
    ErrorDialog = false,
    WindowStyle = ProcessWindowStyle.Hidden
};
0
Harry Berry

「Windowsサービスは「管理者」の資格情報を使用して開始されます」

実際の「管理者」アカウント、または「管理者」グループのユーザーを意味しますか?管理者としてサービスを開始すると、この問題が解決しました。

0
Fiona