-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathNativeCommandExecutor.cs
More file actions
103 lines (90 loc) · 3.78 KB
/
Copy pathNativeCommandExecutor.cs
File metadata and controls
103 lines (90 loc) · 3.78 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
// Copyright (c) 2023-2026 ktsu-dev contributors
namespace ktsu.Essentials.CommandExecutors.Native;
using ktsu.Essentials;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
/// <summary>
/// A command executor that uses the native operating system shell to execute commands.
/// </summary>
public class NativeCommandExecutor : ICommandExecutor
{
/// <summary>
/// Executes a command asynchronously and returns the result.
/// </summary>
/// <param name="command">The command to execute.</param>
/// <param name="workingDirectory">The optional working directory for the command.</param>
/// <param name="cancellationToken">A cancellation token to cancel the operation.</param>
/// <returns>A <see cref="CommandResult"/> containing the exit code, standard output, and standard error.</returns>
public Task<CommandResult> ExecuteAsync(string command, string? workingDirectory = null, CancellationToken cancellationToken = default) =>
ExecuteAsync(command, null, workingDirectory, cancellationToken);
/// <summary>
/// Executes a command asynchronously with custom environment variables and returns the result.
/// </summary>
/// <param name="command">The command to execute.</param>
/// <param name="environmentVariables">Optional environment variables to set for the command.</param>
/// <param name="workingDirectory">The optional working directory for the command.</param>
/// <param name="cancellationToken">A cancellation token to cancel the operation.</param>
/// <returns>A <see cref="CommandResult"/> containing the exit code, standard output, and standard error.</returns>
public async Task<CommandResult> ExecuteAsync(string command, IReadOnlyDictionary<string, string>? environmentVariables, string? workingDirectory = null, CancellationToken cancellationToken = default)
{
Ensure.NotNull(command);
if (cancellationToken.IsCancellationRequested)
{
return new CommandResult(-1, string.Empty, "Operation was cancelled.");
}
try
{
bool isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
using Process process = new();
process.StartInfo = new ProcessStartInfo
{
FileName = isWindows ? "cmd.exe" : "/bin/sh",
Arguments = isWindows ? $"/c {command}" : $"-c \"{command.Replace("\"", "\\\"")}\"",
WorkingDirectory = workingDirectory ?? string.Empty,
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true,
};
if (environmentVariables is not null)
{
foreach (KeyValuePair<string, string> kvp in environmentVariables)
{
process.StartInfo.Environment[kvp.Key] = kvp.Value;
}
}
process.Start();
#if NET7_0_OR_GREATER
Task<string> stdoutTask = process.StandardOutput.ReadToEndAsync(cancellationToken);
Task<string> stderrTask = process.StandardError.ReadToEndAsync(cancellationToken);
#else
Task<string> stdoutTask = process.StandardOutput.ReadToEndAsync();
Task<string> stderrTask = process.StandardError.ReadToEndAsync();
#endif
#if NET5_0_OR_GREATER
await process.WaitForExitAsync(cancellationToken).ConfigureAwait(false);
#else
await Task.Run(process.WaitForExit, cancellationToken).ConfigureAwait(false);
#endif
string stdout = await stdoutTask.ConfigureAwait(false);
string stderr = await stderrTask.ConfigureAwait(false);
return new CommandResult(process.ExitCode, stdout, stderr);
}
catch (OperationCanceledException)
{
return new CommandResult(-1, string.Empty, "Operation was cancelled.");
}
catch (InvalidOperationException ex)
{
return new CommandResult(-1, string.Empty, ex.Message);
}
catch (System.ComponentModel.Win32Exception ex)
{
return new CommandResult(-1, string.Empty, ex.Message);
}
}
}