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);
/// <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()); } }
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 }); } }
/// <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)); }
/// <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, }); }