A .NET library that provides functionality for creating child processes. Easier, less error-prone, more flexible than System.Diagnostics.Process at creating and interacting with child processes.
This library can be obtained via NuGet.
See the Wiki for the goals and the roadmap.
- Concentrates on creating a child process and obtaining its output.
- Cannot query status of a process.
- Cannot create a resident process.
- More destinations of redirection:
- NUL
- File (optionally appended)
- Pipe
- Handle
- Less error-prone default values for redirection:
- stdin to NUL
- stdout to the current stdout
- stderr to the current stderr
- Pipes are asynchronous; asynchronous reads and writes will be handled by IO completion ports.
- Ensures termination of child processes
- .NET Core 3.1 or later
RIDs:
win10-x86(not tested)win10-x64(1809 or later; tested on 1809)win10-arm(not tested)win10-arm64(not tested)linux-x64(tested on Ubuntu 18.04)linux-arm(not tested)linux-arm64(not tested)linux-musl-arm64(not tested)linux-musl-x64(tested on Alpine 3.20)osx-x64(macOS 10.15 Catalina or later; tested on 12)osx-arm64(macOS 11.0 Big Sur or later; not tested)
NOTE: On glibc-based Linux, the system must have glibc 2.27 or later and libstdc++ 3.4.25 or later.
- On Windows prior to Windows 11 24H2, each
ChildProcess.Startexecution leaks one process handle toconhost.exe(except when the child process is successfully attached to the current console usingChildProcessFlags.AttachToCurrentConsole).- This is due to a Kernel32 issue.
- On Windows 10 1809 (including Windows Server 2019),
SignalTerminationjust forcibly kills the process tree (the same operation asKill).- This is due to a Windows pseudoconsole bug where
ClosePseudoConsoledoes not terminate applications attached to the pseudoconsole.
- This is due to a Windows pseudoconsole bug where
- On macOS prior to 11.0,
ExitCodefor processes killed by a signal will always be-1.- This is due to a
waitidbug where it returns0insiginfo_t.si_statusfor such processes.
- This is due to a
- When completely rewriting environment variables with
ChildProcessCreationContextorChildProcessFlags.DisableEnvironmentVariableInheritance, it is recommended that you include basic environment variables such asSystemRoot, etc.
- More than 2^63 processes cannot be created.
This library assumes that the underlying runtime has the following characteristics:
- Windows
- The inner value of a
SafeFileHandleis a file handle. - The inner value of a
SafeWaitHandleis a handle thatWaitForSingleObjectcan wait for. - The inner value of a
SafeProcessHandleis a process handle.
- The inner value of a
- *nix
- The inner value of a
SafeFileHandleis a file descriptor. - The inner value of a
SafeProcessHandleis a process id. Socket.Handlereturns a socket file descriptor.
- The inner value of a
Open ChildProcessExample.sln or see ChildProcess.Example for more examples.
You can read the output of a child, optionally combining stdout and stderr.
varsi=newChildProcessStartInfo("cmd","/C","echo","foo"){StdOutputRedirection=OutputRedirection.OutputPipe,// Works like 2>&1StdErrorRedirection=OutputRedirection.OutputPipe,// DisableArgumentQuoting: See ChildProcessExamplesWindows.cs for detailsFlags=ChildProcessFlags.DisableArgumentQuoting,};using(varp=ChildProcess.Start(si)){using(varsr=newStreamReader(p.StandardOutput)){// "foo"Console.Write(awaitsr.ReadToEndAsync());}awaitp.WaitForExitAsync();// ExitCode: 0Console.WriteLine("ExitCode: {0}",p.ExitCode);}You can redirect the output of a child into a file without ever reading the output.
vartempFile=Path.GetTempFileName();varsi=newChildProcessStartInfo("cmd","/C","set"){ExtraEnvironmentVariables=newDictionary<string,string>{{"A","A"}},StdOutputRedirection=OutputRedirection.File,StdErrorRedirection=OutputRedirection.File,StdOutputFile=tempFile,StdErrorFile=tempFile,// DisableArgumentQuoting: See ChildProcessExamplesWindows.cs for detailsFlags=ChildProcessFlags.UseCustomCodePage|ChildProcessFlags.DisableArgumentQuoting,CodePage=Encoding.Default.CodePage,// UTF-8 on .NET Core};using(varp=ChildProcess.Start(si)){awaitp.WaitForExitAsync();}// A=A// ALLUSERSPROFILE=C:\ProgramData// ...Console.WriteLine(File.ReadAllText(tempFile));File.Delete(tempFile);You can pipe the output of a child into another child without ever reading the output.
// Create an anonymous pipe.usingvarinPipe=newAnonymousPipeServerStream(PipeDirection.In);varsi1=newChildProcessStartInfo("cmd","/C","set"){// Connect the output to writer side of the pipe.StdOutputRedirection=OutputRedirection.Handle,StdErrorRedirection=OutputRedirection.Handle,StdOutputHandle=inPipe.ClientSafePipeHandle,StdErrorHandle=inPipe.ClientSafePipeHandle,// DisableArgumentQuoting: See ChildProcessExamplesWindows.cs for detailsFlags=ChildProcessFlags.UseCustomCodePage|ChildProcessFlags.DisableArgumentQuoting,CodePage=Encoding.Default.CodePage,// UTF-8 on .NET Core};varsi2=newChildProcessStartInfo("findstr","Windows"){// Connect the input to the reader side of the pipe.StdInputRedirection=InputRedirection.Handle,StdInputHandle=inPipe.SafePipeHandle,StdOutputRedirection=OutputRedirection.OutputPipe,StdErrorRedirection=OutputRedirection.OutputPipe,Flags=ChildProcessFlags.UseCustomCodePage,CodePage=Encoding.Default.CodePage,// UTF-8 on .NET Core};usingvarp1=ChildProcess.Start(si1);usingvarp2=ChildProcess.Start(si2);// Close our copy of the pipe handles. (Otherwise p2 will get stuck while reading from the pipe.)inPipe.DisposeLocalCopyOfClientHandle();inPipe.Close();using(varsr=newStreamReader(p2.StandardOutput)){// ...// OS=Windows_NT// ...Console.Write(awaitsr.ReadToEndAsync());}awaitp1.WaitForExitAsync();awaitp2.WaitForExitAsync();