C# Process
last modified October 18, 2026
In this article, we explore how to work with processes in C# using the
System.Diagnostics namespace.
The Process class provides access to local processes, allowing
developers to start, stop, monitor, and interact with system processes.
Whether it's launching an external application, capturing process output, or
managing running tasks, the Process class offers a comprehensive
set of functionalities.
To configure process execution, C# provides the ProcessStartInfo
class, which specifies various settings such as:
- FileName: Defines the executable or command to run.
- Arguments and ArgumentList: Pass command-line arguments to the process.
- UseShellExecute: Determines whether to use the operating system shell to start the process.
- RedirectStandardInput/Output/Error: Enable writing to and reading from the standard streams of the process.
- WorkingDirectory: Sets the directory in which the process starts.
- CreateNoWindow: Specifies whether to start the process without a new window.
The examples run on Linux and macOS; on Windows, some of the commands, such as
sleep, are not available and must be replaced.
C# Process simple example
In the first example, we start a command and wait until it finishes.
using System.Diagnostics;
using var process = Process.Start("dotnet", "--version");
process.WaitForExit();
Console.WriteLine($"exit code: {process.ExitCode}");
The example runs the dotnet --version command.
using var process = Process.Start("dotnet", "--version");
The Start method starts a process with the given program and
arguments. The program is looked up in the directories of the
PATH environment variable. The returned Process
object is disposable.
process.WaitForExit();
Console.WriteLine($"exit code: {process.ExitCode}");
The WaitForExit method waits until the process ends. After that,
the ExitCode property contains the value the process returned; by
convention, zero means success. The output of the child process goes directly
to our console.
$ dotnet run 10.0.203 exit code: 0
C# Process capture output
The StandardOutput property gets a stream used to read the textual
output of the process. To use it, the output must be redirected.
using System.Diagnostics;
var psi = new ProcessStartInfo("dotnet")
{
RedirectStandardOutput = true,
UseShellExecute = false
};
psi.ArgumentList.Add("--list-sdks");
using var process = Process.Start(psi)!;
string output = await process.StandardOutput.ReadToEndAsync();
await process.WaitForExitAsync();
string[] lines = output.Split('\n', StringSplitOptions.RemoveEmptyEntries);
Console.WriteLine($"exit code: {process.ExitCode}");
Console.WriteLine($"{lines.Length} SDK(s) installed:");
foreach (var line in lines)
{
Console.WriteLine($" {line.Split(' ')[0]}");
}
We list the installed .NET SDKs and process the output of the command.
var psi = new ProcessStartInfo("dotnet")
{
RedirectStandardOutput = true,
UseShellExecute = false
};
Redirection requires UseShellExecute to be false,
which is the default in .NET (Core); in .NET Framework, the default was
true. In this context, the shell refers to the operating system
shell that opens documents and URLs, not to command shells such as bash.
psi.ArgumentList.Add("--list-sdks");
The ArgumentList collection takes each argument separately and
quotes it correctly, so arguments with spaces or quotes are passed safely. It is
preferred over building the Arguments string by hand.
string output = await process.StandardOutput.ReadToEndAsync(); await process.WaitForExitAsync();
We read the whole output and wait for the process to exit. The output must be read before waiting; if the process fills the pipe buffer while nobody reads it, it blocks and never exits.
$ dotnet run exit code: 0 1 SDK(s) installed: 10.0.203
C# Process standard error
A process writes error messages to the standard error stream. When we redirect both streams, we must read them concurrently; reading one stream to its end while the process is blocked writing to the other one causes a deadlock.
using System.Diagnostics;
var result = await RunAsync("git", "--version");
Print(result);
result = await RunAsync("git", "no-such-command");
Print(result);
static async Task<(int ExitCode, string Output, string Error)> RunAsync(string file, params string[] args)
{
var psi = new ProcessStartInfo(file)
{
RedirectStandardOutput = true,
RedirectStandardError = true
};
foreach (var arg in args)
{
psi.ArgumentList.Add(arg);
}
using var process = Process.Start(psi)!;
Task<string> outTask = process.StandardOutput.ReadToEndAsync();
Task<string> errTask = process.StandardError.ReadToEndAsync();
await process.WaitForExitAsync();
return (process.ExitCode, await outTask, await errTask);
}
static void Print((int ExitCode, string Output, string Error) r)
{
Console.WriteLine($"exit code: {r.ExitCode}");
Console.WriteLine($"stdout: {r.Output.Trim()}");
Console.WriteLine($"stderr: {r.Error.Trim()}");
Console.WriteLine();
}
The RunAsync helper runs a command and returns the exit code
together with the output and the error output.
Task<string> outTask = process.StandardOutput.ReadToEndAsync(); Task<string> errTask = process.StandardError.ReadToEndAsync(); await process.WaitForExitAsync(); return (process.ExitCode, await outTask, await errTask);
We start reading both streams before we wait, so that they are read at the same time.
$ dotnet run exit code: 0 stdout: git version 2.34.1 stderr: exit code: 1 stdout: stderr: git: 'no-such-command' is not a git command. See 'git --help'.
The second command fails; the exit code is 1 and the message is in the error output.
C# Process standard input
With RedirectStandardInput, we can write data to the process.
using System.Diagnostics;
var psi = new ProcessStartInfo("sort")
{
RedirectStandardInput = true,
RedirectStandardOutput = true
};
using var process = Process.Start(psi)!;
string[] words = ["sky", "cloud", "falcon", "owl", "crane", "atom"];
foreach (var word in words)
{
await process.StandardInput.WriteLineAsync(word);
}
process.StandardInput.Close();
string sorted = await process.StandardOutput.ReadToEndAsync();
await process.WaitForExitAsync();
Console.Write(sorted);
We send words to the sort command and read them back sorted.
process.StandardInput.Close();
The sort command reads its input until the end of the stream
before it prints anything. Closing the input stream signals the end.
$ dotnet run atom cloud crane falcon owl sky
C# Process timeout and kill
The next example waits for a process with a timeout and kills it if it runs too long.
using System.Diagnostics;
using var process = Process.Start("sleep", "30");
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(2));
var sw = Stopwatch.StartNew();
try
{
await process.WaitForExitAsync(cts.Token);
}
catch (OperationCanceledException)
{
Console.WriteLine($"timed out after {sw.Elapsed.TotalSeconds:F1} s, killing process {process.Id}");
process.Kill(entireProcessTree: true);
await process.WaitForExitAsync();
}
Console.WriteLine($"has exited: {process.HasExited}, exit code: {process.ExitCode}");
We start the sleep 30 command and give it two seconds.
await process.WaitForExitAsync(cts.Token);
When the token is cancelled, WaitForExitAsync throws an
OperationCanceledException. Cancelling the wait does not stop the
process itself.
process.Kill(entireProcessTree: true); await process.WaitForExitAsync();
The Kill method terminates the process; with
entireProcessTree, it also terminates the processes started by it.
Kill does not wait, so we wait for the exit afterwards.
$ dotnet run timed out after 2.0 s, killing process 4708 has exited: True, exit code: 137
On Linux, a process killed with the SIGKILL signal reports the
exit code 137 (128 + 9).
C# open file or URL
With UseShellExecute set to true, the operating
system opens a document or a URL with its default application, as if the user
double-clicked it.
Process.Start(new ProcessStartInfo("https://zetcode.com") { UseShellExecute = true });
Process.Start(new ProcessStartInfo("report.pdf") { UseShellExecute = true });
The first line opens the URL in the default browser, the second opens the PDF
file in the default viewer. On Linux, this requires a desktop environment with
the xdg-open command.
C# Process.GetProcessesByName
The Process.GetProcessesByName method returns the running
processes that have the specified name.
using System.Diagnostics;
Process[] processes = Process.GetProcessesByName("dotnet");
Console.WriteLine($"{processes.Length} dotnet process(es)");
foreach (var process in processes)
{
using (process)
{
Console.WriteLine($"Id: {process.Id}, memory: {process.WorkingSet64 / 1048576} MB");
}
}
We find the dotnet processes and print their ids and memory
usage. The name is given without the .exe extension.
foreach (var process in processes)
{
using (process)
{
...
}
}
Each returned Process object holds operating system resources, so
we dispose it.
$ dotnet run 1 dotnet process(es) Id: 16961, memory: 91 MB
C# Process.GetProcesses
The Process.GetProcesses method returns all processes running on
the local computer.
using System.Diagnostics;
Process[] processes = Process.GetProcesses();
var top = processes
.OrderByDescending(p => p.WorkingSet64)
.Take(5);
Console.WriteLine($"{processes.Length} processes, top 5 by memory:");
foreach (var p in top)
{
string name = p.ProcessName.Length > 20 ? p.ProcessName[..20] : p.ProcessName;
Console.WriteLine($"{name,-20} {p.Id,8} {p.WorkingSet64 / 1048576,8} MB");
}
foreach (var p in processes)
{
p.Dispose();
}
We print the five processes that use the most memory. The
WorkingSet64 property gives the amount of physical memory used by
the process.
string name = p.ProcessName.Length > 20 ? p.ProcessName[..20] : p.ProcessName;
On Linux, some programs change their process name to a long command line, so we shorten the names.
$ dotnet run 325 processes, top 5 by memory: code 4147 518 MB Code --standard-sche 3098 480 MB kilo 3479 449 MB code 3441 377 MB code 3051 313 MB
C# current process
The Process.GetCurrentProcess method returns the process of our
own program.
using System.Diagnostics;
using var current = Process.GetCurrentProcess();
Console.WriteLine($"Name: {current.ProcessName}");
Console.WriteLine($"Id: {current.Id}");
Console.WriteLine($"Started: {current.StartTime:HH:mm:ss}");
Console.WriteLine($"Threads: {current.Threads.Count}");
Console.WriteLine($"Memory: {current.WorkingSet64 / 1048576} MB");
Console.WriteLine($"Main module: {Path.GetFileName(current.MainModule?.FileName)}");
We print information about the running program. For the process id alone,
Environment.ProcessId is simpler and faster.
$ dotnet run Name: ProcessEx Id: 4830 Started: 08:58:25 Threads: 7 Memory: 25 MB Main module: ProcessEx
Source
Process class - language reference
In this article we have worked with processes in C#.
Author
List all C# tutorials.