Standard output from CreateProcessAsUser Always Empty in C#

0

The first Call to Launch Method Write Into file echo1, but I can't capture the standard output of the second line of the test, what I am doing wrong?

[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);

    }

I was follow this thread but It has not answer: Capture standard output from CreateProcessAsUser in C#

I get the implementation from 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 and I've edited the code to get standard output same as the first link without answer.

Here the implementation:

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.");
            }
        }

    }
}

NOTE: It is a sample code only, In real code I want to take a screenshot of the desktop from Window Service using another process, In the context of explorer.exe, because windows service cannot access to desktop directly and need another context to do it.

Same thread: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

If you need more information please tell me, thanks!

c#
redirectstandardoutput
createprocessasuser
asked on Stack Overflow Jun 28, 2018 by Juan Pablo • edited Jun 29, 2018 by Juan Pablo

0 Answers

Nobody has answered this question yet.


User contributions licensed under CC BY-SA 3.0