diff --git a/RunCommand/Elevation.cs b/RunCommand/Elevation.cs
new file mode 100644
index 0000000..4af2f8b
--- /dev/null
+++ b/RunCommand/Elevation.cs
@@ -0,0 +1,24 @@
+// Copyright (c) ktsu.dev
+// All rights reserved.
+// Licensed under the MIT license.
+
+namespace ktsu.RunCommand;
+
+///
+/// Specifies the privilege level under which a command should be executed.
+///
+public enum Elevation
+{
+ ///
+ /// Run the command with the current process's privileges. Standard output and standard error are captured.
+ ///
+ Default,
+
+ ///
+ /// Run the command with elevated privileges. On Windows, launches the process with the
+ /// runas verb, prompting the user for UAC consent; output cannot be captured in this mode
+ /// because elevation requires UseShellExecute. On non-Windows platforms this value has no
+ /// effect; prefix the command with sudo there instead.
+ ///
+ Elevated,
+}
diff --git a/RunCommand/RunCommand.cs b/RunCommand/RunCommand.cs
index f2ba1f1..b7bc41f 100644
--- a/RunCommand/RunCommand.cs
+++ b/RunCommand/RunCommand.cs
@@ -29,6 +29,29 @@ public static int Execute(string command) =>
public static int Execute(string command, OutputHandler outputHandler) =>
ExecuteAsync(command, outputHandler).Result;
+ ///
+ /// Executes a shell command synchronously at the specified elevation level.
+ ///
+ /// The command to execute.
+ /// The privilege level under which to run the command.
+ /// The exit code of the executed process.
+ public static int Execute(string command, Elevation elevation) =>
+ ExecuteAsync(command, elevation).Result;
+
+ ///
+ /// Executes a shell command synchronously with an output handler at the specified elevation level.
+ ///
+ /// The command to execute.
+ ///
+ /// The handler for processing command output. Not invoked when is
+ /// on Windows because elevation requires UseShellExecute,
+ /// which is incompatible with output redirection.
+ ///
+ /// The privilege level under which to run the command.
+ /// The exit code of the executed process.
+ public static int Execute(string command, OutputHandler outputHandler, Elevation elevation) =>
+ ExecuteAsync(command, outputHandler, elevation).Result;
+
///
/// Executes a shell command asynchronously
///
@@ -44,6 +67,29 @@ public static async Task ExecuteAsync(string command)
/// The handler for processing command output.
/// A task representing the asynchronous operation with the process exit code.
public static async Task ExecuteAsync(string command, OutputHandler outputHandler)
+ => await ExecuteAsync(command, outputHandler, Elevation.Default).ConfigureAwait(false);
+
+ ///
+ /// Executes a shell command asynchronously at the specified elevation level.
+ ///
+ /// The command to execute.
+ /// The privilege level under which to run the command.
+ /// A task representing the asynchronous operation with the process exit code.
+ public static async Task ExecuteAsync(string command, Elevation elevation)
+ => await ExecuteAsync(command, new(), elevation).ConfigureAwait(false);
+
+ ///
+ /// Executes a shell command asynchronously with an output handler at the specified elevation level.
+ ///
+ /// The command to execute.
+ ///
+ /// The handler for processing command output. Not invoked when is
+ /// on Windows because elevation requires UseShellExecute,
+ /// which is incompatible with output redirection.
+ ///
+ /// The privilege level under which to run the command.
+ /// A task representing the asynchronous operation with the process exit code.
+ public static async Task ExecuteAsync(string command, OutputHandler outputHandler, Elevation elevation)
{
Ensure.NotNull(command);
Ensure.NotNull(outputHandler);
@@ -53,35 +99,50 @@ public static async Task ExecuteAsync(string command, OutputHandler outputH
string filename = commandParts[0];
string arguments = commandParts.Length > 1 ? commandParts[1] : string.Empty;
- using Process process = new()
+ bool isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
+ bool useElevation = elevation == Elevation.Elevated && isWindows;
+
+ ProcessStartInfo startInfo = new()
{
- StartInfo = new ProcessStartInfo
- {
- FileName = filename,
- Arguments = arguments,
- RedirectStandardOutput = true,
- RedirectStandardError = true,
- StandardOutputEncoding = outputHandler.Encoding,
- StandardErrorEncoding = outputHandler.Encoding,
- UseShellExecute = false,
- CreateNoWindow = true,
- }
+ FileName = filename,
+ Arguments = arguments,
+ CreateNoWindow = true,
};
- bool isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
- if (isWindows)
+ if (useElevation)
{
- process.StartInfo.LoadUserProfile = true;
+ startInfo.UseShellExecute = true;
+ startInfo.Verb = "runas";
}
+ else
+ {
+ startInfo.RedirectStandardOutput = true;
+ startInfo.RedirectStandardError = true;
+ startInfo.StandardOutputEncoding = outputHandler.Encoding;
+ startInfo.StandardErrorEncoding = outputHandler.Encoding;
+ startInfo.UseShellExecute = false;
- process.Start();
+ if (isWindows)
+ {
+ startInfo.LoadUserProfile = true;
+ }
+ }
- AsyncProcessStreamReader outputReader = new(process, outputHandler);
+ using Process process = new() { StartInfo = startInfo };
- Task outputTask = outputReader.Start();
- Task processTask = process.WaitForExitAsync();
+ process.Start();
- await Task.WhenAll(outputTask, processTask).ConfigureAwait(false);
+ if (useElevation)
+ {
+ await process.WaitForExitAsync().ConfigureAwait(false);
+ }
+ else
+ {
+ AsyncProcessStreamReader outputReader = new(process, outputHandler);
+ Task outputTask = outputReader.Start();
+ Task processTask = process.WaitForExitAsync();
+ await Task.WhenAll(outputTask, processTask).ConfigureAwait(false);
+ }
return process.ExitCode;
}