ktsu.RunCommand
1.5.0
Prefix Reserved
See the version list below for details.
dotnet add package ktsu.RunCommand --version 1.5.0
NuGet\Install-Package ktsu.RunCommand -Version 1.5.0
<PackageReference Include="ktsu.RunCommand" Version="1.5.0" />
<PackageVersion Include="ktsu.RunCommand" Version="1.5.0" />
<PackageReference Include="ktsu.RunCommand" />
paket add ktsu.RunCommand --version 1.5.0
#r "nuget: ktsu.RunCommand, 1.5.0"
#:package ktsu.RunCommand@1.5.0
#addin nuget:?package=ktsu.RunCommand&version=1.5.0
#tool nuget:?package=ktsu.RunCommand&version=1.5.0
ktsu.RunCommand
A library that provides an easy way to execute a shell command and handle the output via delegates. It supports both synchronous and asynchronous execution with customizable output handling.
Installation
To install RunCommand, you can use the .NET CLI:
dotnet add package ktsu.RunCommand
Or you can use the NuGet Package Manager in Visual Studio to search for and install the ktsu.RunCommand package.
Usage
Basic Execution
The simplest way to execute a command is to use the Execute method, passing the executable and its arguments separately. All methods return the process exit code:
using ktsu.RunCommand;
class Program
{
static void Main()
{
int exitCode = RunCommand.Execute("dotnet", ["--version"]);
if (exitCode == 0)
{
Console.WriteLine("Command executed successfully!");
}
else
{
Console.WriteLine($"Command failed with exit code: {exitCode}");
}
}
}
Deprecated: single command strings
The overloads taking one command string are obsolete. They separate the executable from its arguments by splitting on the first space, which cannot represent an executable path that itself contains a space — on Windows that includes anything under C:\Program Files\:
// Obsolete, and broken: splits into "C:\Program" plus "Files\Git\bin\git.exe --version"
await RunCommand.ExecuteAsync(@"C:\Program Files\Git\bin\git.exe --version");
// Correct
await RunCommand.ExecuteAsync(@"C:\Program Files\Git\bin\git.exe", ["--version"]);
Quoting does not rescue it, because the split happens before any quote handling. The string form is inherently ambiguous — no parse handles every combination of spaces and quotes without adopting a shell's full grammar — so rather than grow a half-grammar that moves the surprise elsewhere, these overloads are deprecated in favour of the argument-list ones, which have no such ambiguity because the executable is passed separately.
Migration is mechanical: split the string yourself at the boundaries you meant.
| Obsolete | Replacement |
|---|---|
Execute(command) |
Execute(fileName, arguments) |
Execute(command, outputHandler) |
Execute(fileName, arguments, outputHandler) |
Execute(command, elevation) |
Execute(fileName, arguments, outputHandler, options) |
ExecuteAsync(command) |
ExecuteAsync(fileName, arguments) |
ExecuteAsync(command, outputHandler) |
ExecuteAsync(fileName, arguments, outputHandler) |
ExecuteAsync(command, cancellationToken) |
ExecuteAsync(fileName, arguments, outputHandler, cancellationToken) |
ExecuteAsync(command, outputHandler, elevation, cancellationToken) |
ExecuteAsync(fileName, arguments, outputHandler, options, cancellationToken) |
Custom Output Handling
To handle the output of the command, you can provide delegates to the OutputHandler class:
using ktsu.RunCommand;
class Program
{
static void Main()
{
int exitCode = RunCommand.Execute(
fileName: "dotnet",
arguments: ["--version"],
outputHandler: new(
onStandardOutput: Console.Write,
onStandardError: Console.Write
)
);
Console.WriteLine($"Process exited with code: {exitCode}");
}
}
NOTE: When using the default OutputHandler, the delegates will receive undelimited chunks of output. This gives you the flexibility to receive exactly the output the command produces, including whitespace and non-printable characters, and handle it as you see fit.
Line-by-Line Output Handling
If you prefer to handle the output line by line, you can use the LineOutputHandler class:
using ktsu.RunCommand;
class Program
{
static void Main()
{
int exitCode = RunCommand.Execute(
fileName: "dotnet",
arguments: ["--version"],
outputHandler: new LineOutputHandler(
onStandardOutput: line => Console.WriteLine($"Output: {line}"),
onStandardError: line => Console.WriteLine($"Error: {line}")
)
);
Console.WriteLine($"Process exited with code: {exitCode}");
}
}
Asynchronous Execution
All of the above examples can be executed asynchronously by using the ExecuteAsync method:
using ktsu.RunCommand;
class Program
{
static async Task Main()
{
int exitCode = await RunCommand.ExecuteAsync("dotnet", ["--version"]);
if (exitCode == 0)
{
Console.WriteLine("Command executed successfully!");
}
else
{
Console.WriteLine($"Command failed with exit code: {exitCode}");
}
}
}
Elevation (Windows)
If you need to run a command with elevated privileges, pass Elevation.Elevated. On Windows this launches the process with the runas verb, which triggers a UAC prompt:
using ktsu.RunCommand;
class Program
{
static void Main()
{
int exitCode = RunCommand.Execute(
fileName: "powershell",
arguments: ["-Command", "Get-Service"],
outputHandler: new(),
options: new() { Elevation = Elevation.Elevated });
Console.WriteLine($"Process exited with code: {exitCode}");
}
}
NOTE: Output redirection is incompatible with
runas, so anOutputHandlerpassed alongsideElevation.Elevatedwill not be invoked. You still get the process exit code.
On non-Windows platforms Elevation.Elevated is a no-op — prefix your command with sudo yourself if you need elevation there.
Process Options
CommandOptions shapes the process a command runs in. Pass it alongside an executable and its arguments:
using ktsu.RunCommand;
using ktsu.Semantics.Paths;
class Program
{
static async Task Main()
{
int exitCode = await RunCommand.ExecuteAsync(
fileName: "git",
arguments: ["status", "--short"],
outputHandler: new LineOutputHandler(onStandardOutput: Console.WriteLine),
options: new()
{
WorkingDirectory = AbsoluteDirectoryPath.Create(@"C:\repos\my project"),
EnvironmentVariables = new Dictionary<string, string?>
{
["GIT_TERMINAL_PROMPT"] = "0",
["LC_ALL"] = "C",
},
});
Console.WriteLine($"Process exited with code: {exitCode}");
}
}
Without a WorkingDirectory the process inherits the current directory of the calling process, which is what commands did before this option existed.
EnvironmentVariables is an overlay on the inherited environment, not a replacement: a name you do not list keeps whatever the calling process had. A null value removes a variable, which is how you unset something the parent had set:
EnvironmentVariables = new Dictionary<string, string?>
{
["GIT_DIR"] = null,
}
Environment variables are the only control surface some tools expose, so this covers behaviour with no command-line equivalent — GIT_TERMINAL_PROMPT=0 to make an authenticating git fetch fail rather than block forever on a prompt no terminal will answer, GIT_ASKPASS/SSH_ASKPASS to supply credentials without putting them on a command line where any process listing can read them, and LC_ALL=C to force stable, machine-parseable output rather than whatever the host locale produces.
NOTE:
EnvironmentVariablescannot be combined withElevation.Elevatedon Windows. Elevation requiresUseShellExecute, which offers nowhere to pass an environment, so the call throwsArgumentExceptionrather than silently dropping the variables.
The type is AbsoluteDirectoryPath rather than a string on purpose. A relative directory would have to be resolved against the caller's current directory — the process-global state this option exists to avoid depending on, since it is shared by every thread and races with concurrent calls.
CommandOptions.Elevation carries the privilege level, so a single options object replaces the separate Elevation argument.
Encoding
By default, the library uses the UTF-8 encoding for the input and output streams. If you need to use a different encoding, you can specify it in the OutputHandler or LineOutputHandler constructor:
using System.Text;
using ktsu.RunCommand;
class Program
{
static void Main()
{
int exitCode = RunCommand.Execute(
fileName: "dotnet",
arguments: ["--version"],
outputHandler: new(
onStandardOutput: Console.Write,
onStandardError: Console.Write,
encoding: Encoding.ASCII
)
);
}
}
API Reference
RunCommand Class
Passing the executable and its arguments separately:
Execute(string fileName, IEnumerable<string> arguments): Executes a command synchronously and returns the process exit code.Execute(string fileName, IEnumerable<string> arguments, OutputHandler outputHandler): Executes a command synchronously with custom output handling.ExecuteAsync(string fileName, IEnumerable<string> arguments): The asynchronous equivalent.ExecuteAsync(string fileName, IEnumerable<string> arguments, OutputHandler outputHandler): The asynchronous equivalent with custom output handling.ExecuteAsync(string fileName, IEnumerable<string> arguments, OutputHandler outputHandler, CancellationToken cancellationToken): As above, terminating the process and its children if the token is signalled.
Obsolete — see Deprecated: single command strings:
Execute(string command),Execute(string command, OutputHandler outputHandler),Execute(string command, Elevation elevation),Execute(string command, OutputHandler outputHandler, Elevation elevation)ExecuteAsync(string command),ExecuteAsync(string command, OutputHandler outputHandler),ExecuteAsync(string command, Elevation elevation),ExecuteAsync(string command, OutputHandler outputHandler, Elevation elevation),ExecuteAsync(string command, CancellationToken cancellationToken),ExecuteAsync(string command, OutputHandler outputHandler, CancellationToken cancellationToken),ExecuteAsync(string command, OutputHandler outputHandler, Elevation elevation, CancellationToken cancellationToken)Execute(string fileName, IEnumerable<string> arguments, OutputHandler outputHandler, CommandOptions options): Executes a command synchronously with the given process options, passing arguments individually so no manual quoting is required.ExecuteAsync(string fileName, IEnumerable<string> arguments, OutputHandler outputHandler, CommandOptions options): The asynchronous equivalent.ExecuteAsync(string fileName, IEnumerable<string> arguments, OutputHandler outputHandler, CommandOptions options, CancellationToken cancellationToken): As above, terminating the process and its children if the token is signalled.
CommandOptions Record
WorkingDirectory: AnAbsoluteDirectoryPathnaming the directory the process starts in, ornullto inherit the caller's current directory.EnvironmentVariables: AnIReadOnlyDictionary<string, string?>applied over the inherited environment, ornullto inherit it unchanged. Anullvalue removes a variable.Elevation: The privilege level under which to run the command. Defaults toElevation.Default.
Elevation Enum
Elevation.Default: Run with the current process's privileges (output is captured).Elevation.Elevated: On Windows, launch via therunasverb (UAC prompt); output is not captured. No-op on non-Windows.OutputHandler Class
Processes output in raw chunks:
OutputHandler(onStandardOutput, onStandardError): Constructor with handlers for output and error streams.
LineOutputHandler Class
Processes output line by line:
LineOutputHandler(onStandardOutput, onStandardError): Constructor with handlers for output and error streams.
NOTE: The
OutputHandlerclasses receive undelimited chunks of output directly from the process stream. TheLineOutputHandlerbuffers this output and splits it by newline characters, invoking the delegates for each complete line.
License
This project is licensed under the MIT License. See the LICENSE file for details.
Contributing
Contributions are welcome! Please open an issue or submit a pull request for any improvements or bug fixes.
Acknowledgements
Thanks to the .NET community and ktsu.dev contributors for their support.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 is compatible. net5.0-windows was computed. net6.0 is compatible. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 is compatible. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 is compatible. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- ktsu.Semantics.Paths (>= 3.0.1)
- ktsu.Semantics.Strings (>= 3.0.1)
- System.Memory (>= 4.6.3)
- System.Threading.Tasks.Extensions (>= 4.6.3)
-
.NETStandard 2.1
- ktsu.Semantics.Paths (>= 3.0.1)
- ktsu.Semantics.Strings (>= 3.0.1)
-
net10.0
- ktsu.Semantics.Paths (>= 3.0.1)
- ktsu.Semantics.Strings (>= 3.0.1)
-
net5.0
- ktsu.Semantics.Paths (>= 3.0.1)
- ktsu.Semantics.Strings (>= 3.0.1)
-
net6.0
- ktsu.Semantics.Paths (>= 3.0.1)
- ktsu.Semantics.Strings (>= 3.0.1)
-
net7.0
- ktsu.Semantics.Paths (>= 3.0.1)
- ktsu.Semantics.Strings (>= 3.0.1)
-
net8.0
- ktsu.Semantics.Paths (>= 3.0.1)
- ktsu.Semantics.Strings (>= 3.0.1)
-
net9.0
- ktsu.Semantics.Paths (>= 3.0.1)
- ktsu.Semantics.Strings (>= 3.0.1)
NuGet packages (3)
Showing the top 3 NuGet packages that depend on ktsu.RunCommand:
| Package | Downloads |
|---|---|
|
ktsu.GitIntegration
A .NET library that wraps the git command-line binary behind a fluent, strongly-typed interface for reading repository state — status, log, diff, branches, remotes, and revision resolution — and for mutating it — init, clone, staging, committing, branch creation and deletion, checkout, remote management, and remote sync via fetch, pull, and push — executed via ktsu.RunCommand with reproducible, copy-pasteable failures, locale-safe parsing, and a machine-readable per-reference account of every fetch and push, and that also unifies access to hosted Git providers behind a pluggable GitProvider abstraction with a GitHub implementation built on Octokit and credential resolution through ktsu.CredentialCache. Includes a set of semantic string types that replace stringly-typed Git identifiers — branch names, commit SHAs, ref names, remote names, author names and emails, repository names, and web URIs — with validated, compile-time-safe wrappers. |
|
|
ktsu.SvnToGit.Core
A guided .NET command-line tool that migrates a Subversion repository to Git by wrapping git svn in an interactive Spectre.Console front-end. Walks through cloning with standard layout, optional authors-file mapping and empty-directory preservation, converting remote git-svn branches into local Git branches, and aggressive garbage collection, with validation and progress reporting at every step. Ships the migration logic as a reusable library alongside the console app. |
|
|
ktsu.KtsuTools.Core
KtsuTools is a unified developer tools suite that consolidates multiple ktsu-dev utilities into a single CLI application with consistent UX powered by Spectre.Console. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.5.6 | 0 | 8/26/2026 |
| 1.5.5 | 49 | 8/25/2026 |
| 1.5.4 | 94 | 8/24/2026 |
| 1.5.3 | 129 | 8/21/2026 |
| 1.5.2 | 162 | 8/20/2026 |
| 1.5.1 | 169 | 8/19/2026 |
| 1.5.0 | 330 | 8/19/2026 |
| 1.4.29 | 105 | 8/19/2026 |
| 1.4.28 | 177 | 8/18/2026 |
| 1.4.27 | 91 | 8/18/2026 |
| 1.4.26 | 538 | 8/11/2026 |
| 1.4.25 | 167 | 8/6/2026 |
| 1.4.24 | 89 | 8/6/2026 |
| 1.4.23 | 119 | 8/5/2026 |
| 1.4.22 | 146 | 7/28/2026 |
| 1.4.21 | 149 | 7/21/2026 |
| 1.4.20 | 158 | 7/15/2026 |
| 1.4.19 | 145 | 7/14/2026 |
| 1.4.18 | 139 | 7/13/2026 |
| 1.4.17 | 161 | 7/8/2026 |
## v1.5.0 (minor)
Changes since v1.4.0:
- [minor] Obsolete the command-string overloads ([@matt-edmondson](https://github.com/matt-edmondson))
- [minor] Add environment variables to CommandOptions ([@matt-edmondson](https://github.com/matt-edmondson))
- [minor] Add CommandOptions with a working directory for the spawned process ([@matt-edmondson](https://github.com/matt-edmondson))
- chore: store icon.png in LFS as .gitattributes declares ([@matt-edmondson](https://github.com/matt-edmondson))
- [patch] Throw on cancellation instead of returning a killed process's exit code ([@matt-edmondson](https://github.com/matt-edmondson))
- docs: scope build badge to the default branch ([@matt-edmondson](https://github.com/matt-edmondson))
- docs: correct README, DESCRIPTION and TAGS metadata ([@matt-edmondson](https://github.com/matt-edmondson))
- Stop Update SDKs failing when there is nothing to update ([@matt-edmondson](https://github.com/matt-edmondson))
- Fix build against ktsu.Sdk 2.27.0 ([@matt-edmondson](https://github.com/matt-edmondson))
- Sync .editorconfig ([@KtsuTools](https://github.com/KtsuTools))
- Sync global.json ([@KtsuTools](https://github.com/KtsuTools))
- chore: update ktsu.Sdk to 2.21.1 [patch] ([@matt-edmondson](https://github.com/matt-edmondson))
- chore: remove unused SourceLink package versions ([@matt-edmondson](https://github.com/matt-edmondson))
- chore: remove SourceLink references and fix test attributes ([@matt-edmondson](https://github.com/matt-edmondson))