35

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

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

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

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

当然のことながら、目標はターゲット プロセスが毎回正常に実行されるようにすることです。

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

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

4

8 に答える 8

24

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

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

方法は次のとおりです。

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

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

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

現在、私の 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 からも使用できるように、このすべてのジョブをアセンブリにまとめたいと思います。誰かがここに何か提案があれば、感謝します。

于 2009-02-19T08:47:40.200 に答える
1

psexecもタスクプランナーも提案しません。しかし、あなたはSudowinを見たことがありますか?

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

また、オープンソースであり、関連するサービスから何度もプロセスを実行する方法を確認できます。

于 2008-12-12T11:03:52.467 に答える
1

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

于 2008-12-12T11:50:04.423 に答える
1

最初の呼び出しの後に失敗する理由は、"既定の" セキュリティ記述子 (それが何であれ) を使用しているためである可能性が非常に高いです。

msdnから:

lpProcessAttributes [入力、オプション]

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

CreateProcessWithLogonW がこの既定のセキュリティ記述子を作成していると思います (いずれにしても、指定していません)。

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

于 2009-01-06T09:25:05.667 に答える
0

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

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

于 2009-01-13T14:56:11.883 に答える
0

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

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

于 2008-12-12T12:14:09.977 に答える
0

msdn ( http://msdn.microsoft.com/en-us/library/ms682431(VS.85).aspx )でこのコメントを読みました。

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

それはまさに「ユーザーエクスペリエンスの悪さ」です。誰も期待していません。

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

于 2008-12-12T13:48:33.437 に答える