简体   繁体   English

在 C# 中重定向 CreateProcessWithTokenW 的标准输出

[英]Redirect Standard Output of CreateProcessWithTokenW in C#

I have an app that launches another process.我有一个启动另一个进程的应用程序。

The first app is launch in admin and the second in the desktop user.第一个应用程序在管理员中启动,第二个在桌面用户中启动。 Before this need, I was launching the process with basic C# handle在此之前,我使用基本的 C# 句柄启动进程

Process p = new Process();
p.StartInfo.FileName = string.Format("{0}\\program.exe", lcb.Client.InstallDirectory);
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.RedirectStandardError = true;
p.StartInfo.UseShellExecute = false;
p.StartInfo.CreateNoWindow = true;

And after I was handling the standard and error output in my main application.在我处理主应用程序中的标准和错误输出之后。

But now I launch my process like this但现在我像这样启动我的过程

var res = RunAsDesktopUser(fileName, arguments);
var process = Process.GetProcessById(res.dwProcessId);  

And inside RunAsUserDesktop I use the winapi method in advapi32.dll CreateProcessWithTokenW.在 RunAsUserDesktop 中,我使用 advapi32.dll CreateProcessWithTokenW 中的 winapi 方法。 I then get the process back like intended but when after I try to read the Standard output I got an exception saying that the process is not started or the standard output is not redirected.然后我按预期恢复进程,但是当我尝试读取标准输出后,我收到一个异常,说该进程未启动或标准输出未重定向。 So I must probably do something to redirect the standard output所以我可能必须做一些事情来重定向标准输出

This method takes some argument like this struct这个方法需要一些像这个结构的参数

private struct STARTUPINFO
{ 
    public Int32 cb;
    public string lpReserved;
    public string lpDesktop;
    public string lpTitle;
    public Int32 dwX;
    public Int32 dwY;
    public Int32 dwXSize;
    public Int32 dwYSize;
    public Int32 dwXCountChars;
    public Int32 dwYCountChars;
    public Int32 dwFillAttribute;
    public Int32 dwFlags;
    public Int16 wShowWindow;
    public Int16 cbReserved2;
    public IntPtr lpReserved2;
    public IntPtr hStdInput;
    public IntPtr hStdOutput;
    public IntPtr hStdError;
}

I kinda understand I must set the hStdOutput and hStdError IntPtr to something but I don't get what.我有点明白我必须将 hStdOutput 和 hStdError IntPtr 设置为某些东西,但我不明白。 And I can't find any documentation of this Anyone knows how to work around this?而且我找不到任何关于此的文档有人知道如何解决这个问题吗?

Create a pipe, and redirect the child process' stdhandle to the write side of the pipe (or directly to the stdhandle of the parent process).创建一个管道,并将子进程的 stdhandle 重定向到管道的写入端(或直接重定向到父进程的 stdhandle)。

The following method redirects the stdhandle of the subprocess through the pipe.以下方法通过管道重定向子进程的 stdhandle。 (BTW: You can declare type of HANDLE as IntPtr ) (顺便说一句:您可以将HANDLE类型声明为IntPtr

using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;

namespace ConsoleApp2
{
    class Program
    {
        private static IntPtr out_read;
        private static IntPtr out_write;
        private static IntPtr err_read;
        private static IntPtr err_write;
        private static int CREATE_NO_WINDOW = 0x08000000;
        private static int STARTF_USESTDHANDLES = 0x00000100;
        private static int BUFSIZE = 4096;
        private static int HANDLE_FLAG_INHERIT = 0x00000001;
        private static void Main(string[] args)
        {
            SECURITY_ATTRIBUTES saAttr = new SECURITY_ATTRIBUTES();
            saAttr.nLength = Marshal.SizeOf(typeof(SECURITY_ATTRIBUTES));
            saAttr.bInheritHandle = 0x1;
            saAttr.lpSecurityDescriptor = IntPtr.Zero;

            CreatePipe(ref out_read,ref out_write,ref saAttr, 0);
            CreatePipe(ref err_read, ref err_write,ref saAttr, 0);

            SetHandleInformation(out_read, HANDLE_FLAG_INHERIT, 0);
            SetHandleInformation(err_read, HANDLE_FLAG_INHERIT, 0);
            PROCESS_INFORMATION res;
            RunAsDesktopUser("C:\\test.exe",out res);
            byte[] buf = new byte[BUFSIZE];
            int dwRead = 0;
            while (true)
            {
                bool bSuccess = ReadFile(out_read, buf, BUFSIZE, ref dwRead, IntPtr.Zero);
                if (!bSuccess || dwRead == 0)
                    break;
                Console.WriteLine(System.Text.Encoding.Default.GetString(buf));
            }
            CloseHandle(out_read);
            CloseHandle(err_read);
            CloseHandle(out_write);
            CloseHandle(err_write);
        }

        private static void RunAsDesktopUser(string fileName, out PROCESS_INFORMATION pi)
        {
            var si = new STARTUPINFO();
            pi = new PROCESS_INFORMATION();
            if (string.IsNullOrWhiteSpace(fileName))
                throw new ArgumentException("Value cannot be null or whitespace.", nameof(fileName));

            // To start process as shell user you will need to carry out these steps:
            // 1. Enable the SeIncreaseQuotaPrivilege in your current token
            // 2. Get an HWND representing the desktop shell (GetShellWindow)
            // 3. Get the Process ID(PID) of the process associated with that window(GetWindowThreadProcessId)
            // 4. Open that process(OpenProcess)
            // 5. Get the access token from that process (OpenProcessToken)
            // 6. Make a primary token with that token(DuplicateTokenEx)
            // 7. Start the new process with that primary token(CreateProcessWithTokenW)

            var hProcessToken = IntPtr.Zero;
            // Enable SeIncreaseQuotaPrivilege in this process.  (This won't work if current process is not elevated.)
            try
            {
                var process = GetCurrentProcess();
                if (!OpenProcessToken(process, 0x0020, ref hProcessToken))
                    return;

                var tkp = new TOKEN_PRIVILEGES
                {
                    PrivilegeCount = 2,
                    Privileges = new LUID_AND_ATTRIBUTES[2]
                };

                if (!LookupPrivilegeValue(null, "SeIncreaseQuotaPrivilege", ref tkp.Privileges[0].Luid))
                    return;
                if (!LookupPrivilegeValue(null, "SeImpersonatePrivilege", ref tkp.Privileges[1].Luid))
                    return;

                tkp.Privileges[0].Attributes = 0x00000002;
                tkp.Privileges[1].Attributes = 0x00000002;

                if (!AdjustTokenPrivileges(hProcessToken, false, ref tkp, 0, IntPtr.Zero, IntPtr.Zero))
                    return;
            }
            finally
            {
                CloseHandle(hProcessToken);
            }

            // Get an HWND representing the desktop shell.
            // CAVEATS:  This will fail if the shell is not running (crashed or terminated), or the default shell has been
            // replaced with a custom shell.  This also won't return what you probably want if Explorer has been terminated and
            // restarted elevated.
            var hwnd = GetShellWindow();
            if (hwnd == IntPtr.Zero)
                return;

            var hShellProcess = IntPtr.Zero;
            var hShellProcessToken = IntPtr.Zero;
            var hPrimaryToken = IntPtr.Zero;
            try
            {
                // Get the PID of the desktop shell process.
                uint dwPID;
                if (GetWindowThreadProcessId(hwnd, out dwPID) == 0)
                    return;

                // Open the desktop shell process in order to query it (get the token)
                hShellProcess = OpenProcess(ProcessAccessFlags.QueryInformation, false, dwPID);
                if (hShellProcess == IntPtr.Zero)
                    return;

                // Get the process token of the desktop shell.
                if (!OpenProcessToken(hShellProcess, 0x0002, ref hShellProcessToken))
                    return;

                var dwTokenRights = 395U;

                // Duplicate the shell's process token to get a primary token.
                // Based on experimentation, this is the minimal set of rights required for CreateProcessWithTokenW (contrary to current documentation).
                if (!DuplicateTokenEx(hShellProcessToken, dwTokenRights, IntPtr.Zero, SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, TOKEN_TYPE.TokenPrimary, out hPrimaryToken))
                    return;

                // Start the target process with the new token.
                si.cb = Marshal.SizeOf(typeof(STARTUPINFO));
                si.hStdOutput = out_write;
                si.hStdError = err_write;
                si.dwFlags |= STARTF_USESTDHANDLES;
                if (!CreateProcessWithTokenW(hPrimaryToken, 0, fileName, "", CREATE_NO_WINDOW, IntPtr.Zero, Path.GetDirectoryName(fileName), ref si, out pi))
                    return;
            }
            finally
            {
                CloseHandle(hShellProcessToken);
                CloseHandle(hPrimaryToken);
                CloseHandle(hShellProcess);
            }
            return;
        }

        #region Interop


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

        [StructLayout(LayoutKind.Sequential, Pack = 4)]
        private struct LUID_AND_ATTRIBUTES
        {
            public LUID Luid;
            public UInt32 Attributes;
        }

        [StructLayout(LayoutKind.Sequential)]
        private struct LUID
        {
            public uint LowPart;
            public int HighPart;
        }

        [Flags]
        private enum ProcessAccessFlags : uint
        {
            All = 0x001F0FFF,
            Terminate = 0x00000001,
            CreateThread = 0x00000002,
            VirtualMemoryOperation = 0x00000008,
            VirtualMemoryRead = 0x00000010,
            VirtualMemoryWrite = 0x00000020,
            DuplicateHandle = 0x00000040,
            CreateProcess = 0x000000080,
            SetQuota = 0x00000100,
            SetInformation = 0x00000200,
            QueryInformation = 0x00000400,
            QueryLimitedInformation = 0x00001000,
            Synchronize = 0x00100000
        }

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

        private enum TOKEN_TYPE
        {
            TokenPrimary = 1,
            TokenImpersonation
        }

        [StructLayout(LayoutKind.Sequential)]
        private struct PROCESS_INFORMATION
        {
            public IntPtr hProcess;
            public IntPtr hThread;
            public int dwProcessId;
            public int dwThreadId;
        }
        private struct SECURITY_ATTRIBUTES
        {
            public Int32 nLength;
            public IntPtr lpSecurityDescriptor;
            public int bInheritHandle;
        }
        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
        private struct STARTUPINFO
        {
            public Int32 cb;
            public string lpReserved;
            public string lpDesktop;
            public string lpTitle;
            public Int32 dwX;
            public Int32 dwY;
            public Int32 dwXSize;
            public Int32 dwYSize;
            public Int32 dwXCountChars;
            public Int32 dwYCountChars;
            public Int32 dwFillAttribute;
            public Int32 dwFlags;
            public Int16 wShowWindow;
            public Int16 cbReserved2;
            public IntPtr lpReserved2;
            public IntPtr hStdInput;
            public IntPtr hStdOutput;
            public IntPtr hStdError;
        }

        [DllImport("kernel32.dll", ExactSpelling = true)]
        private static extern IntPtr GetCurrentProcess();

        [DllImport("advapi32.dll", ExactSpelling = true, SetLastError = true)]
        private static extern bool OpenProcessToken(IntPtr h, int acc, ref IntPtr phtok);

        [DllImport("advapi32.dll", SetLastError = true)]
        private static extern bool LookupPrivilegeValue(string host, string name, ref LUID pluid);

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

        [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool CloseHandle(IntPtr hObject);


        [DllImport("user32.dll")]
        private static extern IntPtr GetShellWindow();

        [DllImport("user32.dll", SetLastError = true)]
        private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);

        [DllImport("kernel32.dll", SetLastError = true)]
        private static extern IntPtr OpenProcess(ProcessAccessFlags processAccess, bool bInheritHandle, uint processId);
        [DllImport("kernel32.dll", SetLastError = true)]
        private static extern IntPtr CreatePipe(ref IntPtr hReadPipe, ref IntPtr hWritePipe, ref SECURITY_ATTRIBUTES lpPipeAttributes,Int32 nSize);
        [DllImport("kernel32.dll", SetLastError = true)]
        private static extern bool ReadFile(IntPtr hFile, byte[] lpBuffer, int nNumberOfBytesToRead, ref int lpNumberOfBytesRead, IntPtr lpOverlapped/*IntPtr.Zero*/);
        [DllImport("kernel32.dll", SetLastError = true)]
        private static extern bool SetHandleInformation(IntPtr hObject, int dwMask, int dwFlags);


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

        [DllImport("advapi32", SetLastError = true, CharSet = CharSet.Unicode)]
        private static extern bool CreateProcessWithTokenW(IntPtr hToken, int dwLogonFlags, string lpApplicationName, string lpCommandLine, int dwCreationFlags, IntPtr lpEnvironment, string lpCurrentDirectory, [In] ref STARTUPINFO lpStartupInfo, out PROCESS_INFORMATION lpProcessInformation);
        #endregion
    }
}

The 3 Streams (most people do not know about Error) are part of the process class, not StartInfo. 3 个流(大多数人不知道 Error)是进程类的一部分,而不是 StartInfo。 Those bools merely define if they can be be "taken out" of their normal place.这些布尔值仅定义了它们是否可以从其正常位置“取出”。 Otherwise the getter will throw a Exception.否则 getter 将抛出异常。

Here is the example code for one of them:以下是其中之一的示例代码:

using (Process myProcess = new Process())
{
    ProcessStartInfo myProcessStartInfo = new ProcessStartInfo("net ", "use " + args[0]);

    myProcessStartInfo.UseShellExecute = false;
    myProcessStartInfo.RedirectStandardError = true;
    myProcess.StartInfo = myProcessStartInfo;
    myProcess.Start();

    StreamReader myStreamReader = myProcess.StandardError;
    // Read the standard error of net.exe and write it on to console.
    Console.WriteLine(myStreamReader.ReadLine());
}

This pattern (Property protected by a bool switch) is a somewhat rare pattern.这种模式(受布尔开关保护的属性)是一种很少见的模式。 I saw it a few times with rather old code (BackgroundWorker comes to mind), but it seems to have fallen out of favor.我用相当旧的代码看到了几次(想到了BackgroundWorker),但它似乎已经失宠了。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 CreateProcessWithTokenW-C#中的用法示例 - CreateProcessWithTokenW - Example of usage in C# 从 CreateProcessWithTokenW 获取 C# 中的进程对象 - Get a Process objet in C# from CreateProcessWithTokenW 将Python标准输入/输出重定向到C#表单应用程序 - Redirect Python standard input/output to C# forms application C#:重定向已经运行的进程的标准输出 - C#: Redirect Standard Output of a Process that is Already Running C#和CreateProcessWithTokenW-使用其他用户令牌启动进程 - C# and CreateProcessWithTokenW - Start process with a different users token 如何在C#、. net中仅异步重定向标准错误流而不是进程的标准输出流 - How to asynchronously redirect ONLY standard error stream and not standard output stream of a process in C#, .net C#标准输出readtoEnd() - c# standard output readtoEnd() C#运行没有新窗口的批处理脚本,并将所有(标准/错误)输出重定向到RichTextBox - C# run a batch script without a new window and redirect all (standard/error) output to a RichTextBox 将静默SQL Server安装的输出重定向到C#中的标准错误? - Redirect output from silent SQL Server installation to Standard Error in C#? 如何在Windows窗体应用程序中的c#中将标准输出重定向到psexec进程中的文件 - How to redirect standard output to a file from psexec process in c# within a Windows Forms Application
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM