Стандартный вывод из CreateProcessAsUser всегда пустой в C # - PullRequest
0 голосов
/ 28 июня 2018

Первый вызов метода запуска записи в файл echo1, но я не могу захватить стандартный вывод второй строки теста, что я делаю неправильно?

[TestMethod]
    public void Launch()
    {
        var resp = ProcessAsUserTest.ProcessAsUser.Launch("cmd.exe /c \"echo 1\" >> c:\\temp\\echo1");

        var resp1 = ProcessAsUserTest.ProcessAsUser.Launch("cmd.exe /c \"echo 1\"");

        Assert.AreEqual("1", resp1);

    }

Я был подписан на эту тему, но он не ответил: Захват стандартного вывода из CreateProcessAsUser в C #

Я получаю реализацию от https://social.msdn.microsoft.com/Forums/vstudio/en-US/0c0ca087-5e7b-4046-93cb-c7b3e48d0dfb/how-run-client-application-as-a-windows-service-in-c?forum=csharpgeneral и я отредактировал код, чтобы получить стандартный вывод, такой же, как первая ссылка без ответа.

Здесь реализация:

using Microsoft.Win32.SafeHandles;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;

namespace ProcessAsUserTest
{
    [StructLayout(LayoutKind.Sequential)]
    internal struct PROCESS_INFORMATION
    {
        public IntPtr hProcess;
        public IntPtr hThread;
        public uint dwProcessId;
        public uint dwThreadId;
    }



    [StructLayout(LayoutKind.Sequential)]
    internal struct SECURITY_ATTRIBUTES
    {
        public uint nLength;
        public IntPtr lpSecurityDescriptor;
        public bool bInheritHandle;
    }


    [StructLayout(LayoutKind.Sequential)]
    public struct STARTUPINFO
    {
        public uint cb;
        public string lpReserved;
        public string lpDesktop;
        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;

    }

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

    internal enum TOKEN_TYPE
    {
        TokenPrimary = 1,
        TokenImpersonation
    }

    [Flags]
    enum HANDLE_FLAGS
    {
        INHERIT = 1,
    }

    public class ProcessAsUser
    {

        [DllImport("advapi32.dll", SetLastError = true)]
        private static extern bool CreateProcessAsUser(
            IntPtr hToken,
            string lpApplicationName,
            string lpCommandLine,
            ref SECURITY_ATTRIBUTES lpProcessAttributes,
            ref SECURITY_ATTRIBUTES lpThreadAttributes,
            bool bInheritHandles,
            uint dwCreationFlags,
            IntPtr lpEnvironment,
            string lpCurrentDirectory,
            ref STARTUPINFO lpStartupInfo,
            out PROCESS_INFORMATION lpProcessInformation);


        [DllImport("advapi32.dll", EntryPoint = "DuplicateTokenEx", SetLastError = true)]
        private static extern bool DuplicateTokenEx(
            IntPtr hExistingToken,
            uint dwDesiredAccess,
            ref SECURITY_ATTRIBUTES lpThreadAttributes,
            Int32 ImpersonationLevel,
            Int32 dwTokenType,
            ref IntPtr phNewToken);


        [DllImport("advapi32.dll", SetLastError = true)]
        private static extern bool OpenProcessToken(
            IntPtr ProcessHandle,
            UInt32 DesiredAccess,
            ref IntPtr TokenHandle);

        [DllImport("userenv.dll", SetLastError = true)]
        private static extern bool CreateEnvironmentBlock(
                ref IntPtr lpEnvironment,
                IntPtr hToken,
                bool bInherit);


        [DllImport("userenv.dll", SetLastError = true)]
        private static extern bool DestroyEnvironmentBlock(
                IntPtr lpEnvironment);

        [DllImport("kernel32.dll", SetLastError = true)]
        private static extern bool CloseHandle(
            IntPtr hObject);

        [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern IntPtr CreateNamedPipe(string name, int openMode, int pipeMode, int maxInstances, int outBufSize, int inBufSize, int timeout, IntPtr lpPipeAttributes);

        [DllImport("kernel32.dll", CharSet = CharSet.Auto)]
        private static extern IntPtr CreateFile(string lpFileName, int dwDesiredAccess, int dwShareMode, IntPtr lpSecurityAttributes, int dwCreationDisposition, int dwFlagsAndAttributes, HandleRef hTemplateFile);

        [DllImport("kernel32.dll", CharSet = CharSet.Ansi, SetLastError = true)]
        private static extern IntPtr GetStdHandle(int whichHandle);

        [DllImport("kernel32.dll", SetLastError = true)]
        private static extern bool GetExitCodeProcess(IntPtr process, ref UInt32 exitCode);

        [DllImport("kernel32.dll", SetLastError = true)]
        private static extern bool SetHandleInformation(IntPtr hObject, HANDLE_FLAGS dwMask, HANDLE_FLAGS dwFlags);

        [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        public static extern int GetConsoleOutputCP();

        [DllImport("kernel32.dll")]
        static extern int WaitForSingleObject(IntPtr hHandle, int dwMilliseconds);



        private const short SW_SHOW = 5;
        private const short SW_HIDE = 0;
        private const uint TOKEN_QUERY = 0x0008;
        private const uint TOKEN_DUPLICATE = 0x0002;
        private const uint TOKEN_ASSIGN_PRIMARY = 0x0001;
        private const int GENERIC_ALL_ACCESS = 0x10000000;
        private const int STARTF_USESHOWWINDOW = 0x00000001;
        private const int STARTF_FORCEONFEEDBACK = 0x00000040;
        private const uint CREATE_UNICODE_ENVIRONMENT = 0x00000400;


        private const int STD_INPUT_HANDLE = -10;

        private static readonly IntPtr INVALID_HANDLE_VALUE = (IntPtr)(-1);
        private static readonly HandleRef NullHandleRef = new HandleRef(null, IntPtr.Zero);

        private static string LaunchProcessAsUser(string cmdLine, IntPtr token, IntPtr envBlock)
        {
            bool result = false;

            PROCESS_INFORMATION pi = new PROCESS_INFORMATION();
            SECURITY_ATTRIBUTES saProcess = new SECURITY_ATTRIBUTES();
            SECURITY_ATTRIBUTES saThread = new SECURITY_ATTRIBUTES();
            saProcess.nLength = (uint)Marshal.SizeOf(saProcess);
            saThread.nLength = (uint)Marshal.SizeOf(saThread);

            IntPtr stdoutReadHandle = IntPtr.Zero;
            SafeFileHandle safeHandle = null;
            IntPtr stdoutWriteHandle = IntPtr.Zero;
            IntPtr stdinHandle = GetStdHandle(STD_INPUT_HANDLE);
            try
            {
                CreatePipe(out stdoutReadHandle, out stdoutWriteHandle, false);
                SetHandleInformation(stdoutReadHandle, HANDLE_FLAGS.INHERIT, 0);

                STARTUPINFO si = new STARTUPINFO();
                si.cb = (uint)Marshal.SizeOf(si);
                //if this member is NULL, the new process inherits the desktop
                //and window station of its parent process. If this member is
                //an empty string, the process does not inherit the desktop and
                //window station of its parent process; instead, the system
                //determines if a new desktop and window station need to be created.
                //If the impersonated user already has a desktop, the system uses the
                //existing desktop.
                si.lpDesktop = @"WinSta0\Default"; //Modify as needed
                si.dwFlags = STARTF_USESHOWWINDOW | STARTF_FORCEONFEEDBACK;
                si.wShowWindow = SW_SHOW;
                si.hStdInput = stdinHandle;
                si.hStdOutput = stdoutWriteHandle;
                si.hStdOutput = stdoutWriteHandle;
                //Set other si properties as required.

                result = CreateProcessAsUser(
                    token,
                    null,
                    cmdLine,
                    ref saProcess,
                    ref saThread,
                    false,
                    CREATE_UNICODE_ENVIRONMENT,
                    envBlock,
                    null,
                    ref si,
                    out pi);

                if (result == false)
                {
                    int error = Marshal.GetLastWin32Error();
                    string message = String.Format("CreateProcessAsUser Error: {0}", error);
                    Debug.WriteLine(message);
                }

                var ret = WaitForSingleObject(pi.hProcess, 100000);
                //Console.Write("WaitForSingleObject returned " + ret);
                //ret==258 (0x102) - not signalled, ret==0 ok!

                CloseHandle(pi.hProcess);
                CloseHandle(pi.hThread);
                CloseHandle(stdoutWriteHandle);
                CloseHandle(stdinHandle);


                safeHandle = new SafeFileHandle(stdoutReadHandle, true);
                string outputData;
                var encoding = Encoding.GetEncoding(GetConsoleOutputCP());
                using (var fs = new FileStream(safeHandle, FileAccess.Read, 0x1000, true))
                using (var reader = new StreamReader(fs, encoding))
                {
                    outputData = reader.ReadToEnd();

                }

                return outputData;
            }
            finally
            {

                if (!safeHandle.IsClosed)
                {
                    safeHandle.Close();
                }

            }
        }

        private static IntPtr GetPrimaryToken(int processId)
        {
            IntPtr token = IntPtr.Zero;
            IntPtr primaryToken = IntPtr.Zero;
            bool retVal = false;
            Process p = null;

            try
            {
                p = Process.GetProcessById(processId);
            }

            catch (ArgumentException ex)
            {
                string details = String.Format("ProcessID {0} Not Available, More: {1}", processId, ex.Message);
                throw new ArgumentException(details);
            }


            //Gets impersonation token
            retVal = OpenProcessToken(p.Handle, TOKEN_DUPLICATE, ref token);
            if (retVal == true)
            {

                SECURITY_ATTRIBUTES sa = new SECURITY_ATTRIBUTES();
                sa.nLength = (uint)Marshal.SizeOf(sa);

                //Convert the impersonation token into Primary token
                retVal = DuplicateTokenEx(
                    token,
                    TOKEN_ASSIGN_PRIMARY | TOKEN_DUPLICATE | TOKEN_QUERY,
                    ref sa,
                    (int)SECURITY_IMPERSONATION_LEVEL.SecurityIdentification,
                    (int)TOKEN_TYPE.TokenPrimary,
                    ref primaryToken);

                //Close the Token that was previously opened.
                CloseHandle(token);
                if (retVal == false)
                {
                    string message = String.Format("DuplicateTokenEx Error: {0}", Marshal.GetLastWin32Error());
                    throw new Exception(message);
                }

            }

            else
            {

                string message = String.Format("OpenProcessToken Error: {0}", Marshal.GetLastWin32Error());
                Debug.WriteLine(message);

            }

            //We'll Close this token after it is used.
            return primaryToken;

        }

        private static IntPtr GetEnvironmentBlock(IntPtr token)
        {

            IntPtr envBlock = IntPtr.Zero;
            bool retVal = CreateEnvironmentBlock(ref envBlock, token, false);
            if (retVal == false)
            {

                //Environment Block, things like common paths to My Documents etc.
                //Will not be created if "false"
                //It should not adversley affect CreateProcessAsUser.

                string message = String.Format("CreateEnvironmentBlock Error: {0}", Marshal.GetLastWin32Error());
                Debug.WriteLine(message);

            }
            return envBlock;
        }

        public static string Launch(string appCmdLine /*,int processId*/)
        {

            string ret = "";
            //Either specify the processID explicitly
            //Or try to get it from a process owned by the user.
            //In this case assuming there is only one explorer.exe

            Process[] ps = Process.GetProcessesByName("explorer");
            int processId = -1;//=processId
            if (ps.Length > 0)
            {
                processId = ps[0].Id;
            }

            if (processId > 1)
            {
                IntPtr token = GetPrimaryToken(processId);

                if (token != IntPtr.Zero)
                {

                    IntPtr envBlock = GetEnvironmentBlock(token);
                    ret = LaunchProcessAsUser(appCmdLine, token, envBlock);
                    if (envBlock != IntPtr.Zero)
                        DestroyEnvironmentBlock(envBlock);

                    CloseHandle(token);
                }

            }
            return ret;
        }

        private static void CreatePipe(out IntPtr parentHandle, out IntPtr childHandle, bool parentInputs)
        {
            string pipename = @"\\.\pipe\" + Guid.NewGuid().ToString();

            parentHandle = CreateNamedPipe(pipename, 0x40000003, 0, 0xff, 0x1000, 0x1000, 0, IntPtr.Zero);
            if (parentHandle == INVALID_HANDLE_VALUE)
            {
                throw new Exception("Invalid Handle Exception.");
            }

            int childAcc = 0x40000000;
            if (parentInputs)
            {
                childAcc = -2147483648;
            }
            childHandle = CreateFile(pipename, childAcc, 3, IntPtr.Zero, 3, 0x40000080, NullHandleRef);
            if (childHandle == INVALID_HANDLE_VALUE)
            {
                throw new Exception("Invalid Handle Exception.");
            }
        }

    }
}

ПРИМЕЧАНИЕ: Это только пример кода. В реальном коде я хочу сделать снимок рабочего стола из Window Service, используя другой процесс, в контексте explorer.exe, потому что служба windows не может получить доступ непосредственно к рабочему столу, и для этого нужен другой контекст.

Та же тема: https://social.msdn.microsoft.com/Forums/vstudio/en-US/19ff0bb0-924f-4f9c-9b71-ff4ee4ccaf50/standard-output-from-createprocessasuser-always-empty-in-c?forum=csharpgeneral#19ff0bb0-924f-4f9c-9b71-ff4ee4ccaf50

Если вам нужна дополнительная информация, пожалуйста, скажите мне, спасибо!

...