Пример #1
0
 public static extern bool CreateProcessW(
     [MarshalAs(UnmanagedType.LPWStr)] string lpApplicationName,
     StringBuilder lpCommandLine,
     IntPtr lpProcessAttributes,
     IntPtr lpThreadAttributes,
     bool bInheritHandles,
     NativeHelpers.ProcessCreationFlags dwCreationFlags,
     SafeMemoryBuffer lpEnvironment,
     [MarshalAs(UnmanagedType.LPWStr)] string lpCurrentDirectory,
     NativeHelpers.STARTUPINFOEX lpStartupInfo,
     out NativeHelpers.PROCESS_INFORMATION lpProcessInformation);
Пример #2
0
        /// <summary>
        /// Parses a command line string into an argv array according to the Windows rules
        /// </summary>
        /// <param name="lpCommandLine">The command line to parse</param>
        /// <returns>An array of arguments interpreted by Windows</returns>
        public static string[] CommandLineToArgv(string lpCommandLine)
        {
            int numArgs;

            using (SafeMemoryBuffer buf = NativeMethods.CommandLineToArgvW(lpCommandLine, out numArgs))
            {
                if (buf.IsInvalid)
                {
                    throw new Win32Exception("Error parsing command line");
                }
                IntPtr[] strptrs = new IntPtr[numArgs];
                Marshal.Copy(buf.DangerousGetHandle(), strptrs, 0, numArgs);
                return(strptrs.Select(s => Marshal.PtrToStringUni(s)).ToArray());
            }
        }
Пример #3
0
        internal static Result WaitProcess(SafeFileHandle stdoutRead, SafeFileHandle stdoutWrite, SafeFileHandle stderrRead,
                                           SafeFileHandle stderrWrite, FileStream stdinStream, byte[] stdin, NativeHelpers.PROCESS_INFORMATION pi,
                                           string outputEncoding, bool waitChildren)
        {
            // Default to using UTF-8 as the output encoding, this should be a sane default for most scenarios.
            outputEncoding = String.IsNullOrEmpty(outputEncoding) ? "utf-8" : outputEncoding;
            Encoding encodingInstance = Encoding.GetEncoding(outputEncoding);

            // If we aren't waiting for child processes we don't care if the below fails
            // Logic to wait for children is from Raymond Chen
            // https://devblogs.microsoft.com/oldnewthing/20130405-00/?p=4743
            using (SafeHandle job = CreateJob(!waitChildren))
                using (SafeHandle ioPort = CreateCompletionPort(!waitChildren))
                {
                    // Need to assign the completion port to the job and then assigned the new process to that job.
                    if (waitChildren)
                    {
                        NativeHelpers.JOBOBJECT_ASSOCIATE_COMPLETION_PORT compPort = new NativeHelpers.JOBOBJECT_ASSOCIATE_COMPLETION_PORT()
                        {
                            CompletionKey  = job.DangerousGetHandle(),
                            CompletionPort = ioPort.DangerousGetHandle(),
                        };
                        int compPortSize = Marshal.SizeOf(compPort);

                        using (SafeMemoryBuffer compPortPtr = new SafeMemoryBuffer(compPortSize))
                        {
                            Marshal.StructureToPtr(compPort, compPortPtr.DangerousGetHandle(), false);

                            if (!NativeMethods.SetInformationJobObject(job,
                                                                       NativeHelpers.JobObjectInformationClass.JobObjectAssociateCompletionPortInformation,
                                                                       compPortPtr.DangerousGetHandle(), compPortSize))
                            {
                                throw new Win32Exception("Failed to set job completion port information");
                            }
                        }

                        // Server 2012/Win 8 introduced the ability to nest jobs. Older versions will fail with
                        // ERROR_ACCESS_DENIED but we can't do anything about that except not wait for children.
                        if (!NativeMethods.AssignProcessToJobObject(job, pi.hProcess))
                        {
                            throw new Win32Exception("Failed to assign new process to completion watcher job");
                        }
                    }

                    // Start the process and get the output.
                    NativeMethods.ResumeThread(pi.hThread);

                    FileStream   stdoutFS = new FileStream(stdoutRead, FileAccess.Read, 4096);
                    StreamReader stdout   = new StreamReader(stdoutFS, encodingInstance, true, 4096);
                    stdoutWrite.Close();

                    FileStream   stderrFS = new FileStream(stderrRead, FileAccess.Read, 4096);
                    StreamReader stderr   = new StreamReader(stderrFS, encodingInstance, true, 4096);
                    stderrWrite.Close();

                    if (stdin != null)
                    {
                        stdinStream.Write(stdin, 0, stdin.Length);
                    }
                    stdinStream.Close();

                    string stdoutStr, stderrStr = null;
                    GetProcessOutput(stdout, stderr, out stdoutStr, out stderrStr);
                    UInt32 rc = GetProcessExitCode(pi.hProcess);

                    if (waitChildren)
                    {
                        // If the caller wants to wait for all child processes to finish, we continue to poll the job
                        // until it receives JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO (4).
                        UInt32  completionCode = 0xFFFFFFFF;
                        UIntPtr completionKey;
                        IntPtr  overlapped;

                        while (NativeMethods.GetQueuedCompletionStatus(ioPort.DangerousGetHandle(), out completionCode,
                                                                       out completionKey, out overlapped, 0xFFFFFFFF) && completionCode != 4)
                        {
                        }
                    }

                    return(new Result
                    {
                        StandardOut = stdoutStr,
                        StandardError = stderrStr,
                        ExitCode = rc
                    });
                }
        }
Пример #4
0
        /// <summary>
        /// Creates a process based on the CreateProcess API call.
        /// </summary>
        /// <param name="lpApplicationName">The name of the executable or batch file to execute</param>
        /// <param name="lpCommandLine">The command line to execute, typically this includes lpApplication as the first argument</param>
        /// <param name="lpCurrentDirectory">The full path to the current directory for the process, null will have the same cwd as the calling process</param>
        /// <param name="environment">A dictionary of key/value pairs to define the new process environment</param>
        /// <param name="stdin">A byte array to send over the stdin pipe</param>
        /// <param name="outputEncoding">The character encoding for decoding stdout/stderr output of the process.</param>
        /// <param name="waitChildren">Whether to wait for any children spawned by the process to finished (Server2012 +).</param>
        /// <returns>Result object that contains the command output and return code</returns>
        public static Result CreateProcess(string lpApplicationName, string lpCommandLine, string lpCurrentDirectory,
                                           IDictionary environment, byte[] stdin, string outputEncoding, bool waitChildren)
        {
            NativeHelpers.ProcessCreationFlags creationFlags = NativeHelpers.ProcessCreationFlags.CREATE_SUSPENDED |
                                                               NativeHelpers.ProcessCreationFlags.CREATE_UNICODE_ENVIRONMENT |
                                                               NativeHelpers.ProcessCreationFlags.EXTENDED_STARTUPINFO_PRESENT;
            NativeHelpers.PROCESS_INFORMATION pi = new NativeHelpers.PROCESS_INFORMATION();
            NativeHelpers.STARTUPINFOEX       si = new NativeHelpers.STARTUPINFOEX();
            si.startupInfo.dwFlags = NativeHelpers.StartupInfoFlags.USESTDHANDLES;

            SafeFileHandle stdoutRead, stdoutWrite, stderrRead, stderrWrite, stdinRead, stdinWrite;

            CreateStdioPipes(si, out stdoutRead, out stdoutWrite, out stderrRead, out stderrWrite, out stdinRead,
                             out stdinWrite);
            FileStream stdinStream = new FileStream(stdinWrite, FileAccess.Write);

            // $null from PowerShell ends up as an empty string, we need to convert back as an empty string doesn't
            // make sense for these parameters
            if (lpApplicationName == "")
            {
                lpApplicationName = null;
            }

            if (lpCurrentDirectory == "")
            {
                lpCurrentDirectory = null;
            }

            using (SafeMemoryBuffer lpEnvironment = CreateEnvironmentPointer(environment))
            {
                // Create console with utf-8 CP if no existing console is present
                bool isConsole = false;
                if (NativeMethods.GetConsoleWindow() == IntPtr.Zero)
                {
                    isConsole = NativeMethods.AllocConsole();

                    // Set console input/output codepage to UTF-8
                    NativeMethods.SetConsoleCP(65001);
                    NativeMethods.SetConsoleOutputCP(65001);
                }

                try
                {
                    StringBuilder commandLine = new StringBuilder(lpCommandLine);
                    if (!NativeMethods.CreateProcessW(lpApplicationName, commandLine, IntPtr.Zero, IntPtr.Zero,
                                                      true, creationFlags, lpEnvironment, lpCurrentDirectory, si, out pi))
                    {
                        throw new Win32Exception("CreateProcessW() failed");
                    }
                }
                finally
                {
                    if (isConsole)
                    {
                        NativeMethods.FreeConsole();
                    }
                }
            }

            return(WaitProcess(stdoutRead, stdoutWrite, stderrRead, stderrWrite, stdinStream, stdin, pi,
                               outputEncoding, waitChildren));
        }
Пример #5
0
        /// <summary>
        /// Wrapper around the Win32 CreateProcess API for low level use. This just spawns the new process and does not
        /// wait until it is complete before returning.
        /// </summary>
        /// <param name="applicationName">The name of the executable or batch file to execute</param>
        /// <param name="commandLine">The command line to execute, typically this includes applicationName as the first argument</param>
        /// <param name="processAttributes">SecurityAttributes to assign to the new process, set to null to use the defaults</param>
        /// <param name="threadAttributes">SecurityAttributes to assign to the new thread, set to null to use the defaults</param>
        /// <param name="inheritHandles">Any inheritable handles in the calling process is inherited in the new process</param>
        /// <param name="creationFlags">Custom creation flags to use when creating the new process</param>
        /// <param name="environment">A dictionary of key/value pairs to define the new process environment</param>
        /// <param name="currentDirectory">The full path to the current directory for the process, null will have the same cwd as the calling process</param>
        /// <param name="startupInfo">Custom StartupInformation to use when creating the new process</param>
        /// <returns>ProcessInformation containing a handle to the process and main thread as well as the pid/tid.</returns>
        public static ProcessInformation NativeCreateProcess(string applicationName, string commandLine,
                                                             SecurityAttributes processAttributes, SecurityAttributes threadAttributes, bool inheritHandles,
                                                             ProcessCreationFlags creationFlags, IDictionary environment, string currentDirectory, StartupInfo startupInfo)
        {
            // We always have the extended version present.
            creationFlags |= ProcessCreationFlags.ExtendedStartupInfoPresent;

            // $null from PowerShell ends up as an empty string, we need to convert back as an empty string doesn't
            // make sense for these parameters
            if (String.IsNullOrWhiteSpace(applicationName))
            {
                applicationName = null;
            }

            if (String.IsNullOrWhiteSpace(currentDirectory))
            {
                currentDirectory = null;
            }

            NativeHelpers.STARTUPINFOEX si = new NativeHelpers.STARTUPINFOEX();
            if (!String.IsNullOrWhiteSpace(startupInfo.Desktop))
            {
                si.startupInfo.lpDesktop = startupInfo.Desktop;
            }

            if (!String.IsNullOrWhiteSpace(startupInfo.Title))
            {
                si.startupInfo.lpTitle = startupInfo.Title;
            }

            bool useStdHandles = false;

            if (startupInfo.StandardInput != null)
            {
                si.startupInfo.hStdInput = startupInfo.StandardInput;
                useStdHandles            = true;
            }

            if (startupInfo.StandardOutput != null)
            {
                si.startupInfo.hStdOutput = startupInfo.StandardOutput;
                useStdHandles             = true;
            }

            if (startupInfo.StandardError != null)
            {
                si.startupInfo.hStdError = startupInfo.StandardError;
                useStdHandles            = true;
            }

            if (useStdHandles)
            {
                si.startupInfo.dwFlags |= NativeHelpers.StartupInfoFlags.USESTDHANDLES;
            }

            if (startupInfo.WindowStyle != null)
            {
                switch (startupInfo.WindowStyle)
                {
                case ProcessWindowStyle.Normal:
                    si.startupInfo.wShowWindow = 1;      // SW_SHOWNORMAL
                    break;

                case ProcessWindowStyle.Hidden:
                    si.startupInfo.wShowWindow = 0;      // SW_HIDE
                    break;

                case ProcessWindowStyle.Minimized:
                    si.startupInfo.wShowWindow = 6;      // SW_MINIMIZE
                    break;

                case ProcessWindowStyle.Maximized:
                    si.startupInfo.wShowWindow = 3;      // SW_MAXIMIZE
                    break;
                }
                si.startupInfo.dwFlags |= NativeHelpers.StartupInfoFlags.STARTF_USESHOWWINDOW;
            }

            NativeHelpers.PROCESS_INFORMATION pi = new NativeHelpers.PROCESS_INFORMATION();
            using (SafeMemoryBuffer lpProcessAttr = CreateSecurityAttributes(processAttributes))
                using (SafeMemoryBuffer lpThreadAttributes = CreateSecurityAttributes(threadAttributes))
                    using (SafeMemoryBuffer lpEnvironment = CreateEnvironmentPointer(environment))
                    {
                        StringBuilder commandLineBuff = new StringBuilder(commandLine);
                        if (!NativeMethods.CreateProcessW(applicationName, commandLineBuff, lpProcessAttr, lpThreadAttributes,
                                                          inheritHandles, creationFlags, lpEnvironment, currentDirectory, si, out pi))
                        {
                            throw new Win32Exception("CreateProcessW() failed");
                        }
                    }

            return(new ProcessInformation
            {
                Process = new SafeNativeHandle(pi.hProcess),
                Thread = new SafeNativeHandle(pi.hThread),
                ProcessId = pi.dwProcessId,
                ThreadId = pi.dwThreadId,
            });
        }