using System.Diagnostics; using System.Runtime.CompilerServices; internal static class Shellify { [Flags] private enum ShellSplitState { None = 0, InSingleQuote = 1 << 0, InDoubleQuote = 1 << 1, Escape = 1 << 2 } /// /// Splits a shell-escaped string into arguments, handling quotes and escapes. /// public static List ShellSplit(string input) { var args = new List(); if (string.IsNullOrEmpty(input)) return args; var current = new System.Text.StringBuilder(); var state = ShellSplitState.None; foreach (char c in input) { if (state.HasFlag(ShellSplitState.Escape)) { current.Append(c); state &= ~ShellSplitState.Escape; } else if (c == '\\') { state |= ShellSplitState.Escape; } else if (c == '\'' && !state.HasFlag(ShellSplitState.InDoubleQuote)) { state ^= ShellSplitState.InSingleQuote; } else if (c == '"' && !state.HasFlag(ShellSplitState.InSingleQuote)) { state ^= ShellSplitState.InDoubleQuote; } else if (char.IsWhiteSpace(c) && !state.HasFlag(ShellSplitState.InSingleQuote) && !state.HasFlag(ShellSplitState.InDoubleQuote)) { if (current.Length > 0) { args.Add(current.ToString()); current.Clear(); } } else { current.Append(c); } } if (current.Length > 0) args.Add(current.ToString()); return args; } public static TaskAwaiter GetAwaiter(this string command) { return RunProcessAsync(command).GetAwaiter(); } private static async Task RunProcessAsync(string command) { var strings = ShellSplit(command); var psi = new ProcessStartInfo { FileName = strings.First(), ArgumentList = { strings.Skip(1) }, RedirectStandardOutput = true, RedirectStandardError = true, UseShellExecute = false, CreateNoWindow = true, }; using var process = Process.Start(psi); string output = await process.StandardOutput.ReadToEndAsync(); string error = await process.StandardError.ReadToEndAsync(); await process.WaitForExitAsync(); if (process.ExitCode != 0) throw new System.Exception($"Process exited with code {process.ExitCode}: {error}"); return output; } }