using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Runtime.InteropServices; using Microsoft.Extensions.Logging; namespace AyaNova.Util { public static class RunProgram { public static string Run(string cmd, string arguments, ILogger log = null, int waitForExitTimeOut = int.MaxValue) { try { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { return RunWindows(cmd, arguments, waitForExitTimeOut); } else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { return RunOSX(cmd, arguments, waitForExitTimeOut); } else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { return RunLinuxShell(cmd, arguments, waitForExitTimeOut); } } catch (Exception ex) { if (log != null) { log.LogError(ex, $"RunProgram error running command:{cmd} {arguments}"); } throw; } throw new PlatformNotSupportedException(); } /// /// Run a program directly, no shell: each argument is passed as-is (no quoting or escaping needed, nothing is interpreted by a shell) /// and extra environment variables are set for this process only (e.g. PGPASSWORD so a password never appears on a command line) /// Returns empty string on success (exit code 0) or the error text otherwise, same convention as the backup job expects /// public static string RunNoShell(string fileName, IEnumerable arguments, IDictionary environment = null, ILogger log = null, int waitForExitTimeOut = int.MaxValue) { try { using (var process = new Process()) { process.StartInfo = new ProcessStartInfo { FileName = fileName, RedirectStandardOutput = true, RedirectStandardError = true, UseShellExecute = false, CreateNoWindow = true, }; foreach (var a in arguments) process.StartInfo.ArgumentList.Add(a); if (environment != null) foreach (var e in environment) process.StartInfo.Environment[e.Key] = e.Value; process.Start(); //read both streams at once, reading one to the end first can deadlock if the other fills its buffer var stdOut = process.StandardOutput.ReadToEndAsync(); var stdErr = process.StandardError.ReadToEndAsync(); if (!process.WaitForExit(waitForExitTimeOut)) { process.Kill(true); return $"ERROR: TIMED OUT {waitForExitTimeOut}ms BEFORE COMPLETION running {fileName}"; } process.WaitForExit();//ensures output streams are fully read after the timed wait above if (process.ExitCode != 0) return $"ERROR: {Path.GetFileName(fileName)} exit code {process.ExitCode}: {stdErr.Result}{stdOut.Result}".Trim(); return string.Empty; } } catch (Exception ex) { //arguments deliberately not logged, they can contain connection details if (log != null) { log.LogError(ex, $"RunProgram error running command:{fileName}"); } throw; } } private static string RunWindows(string cmd, string arguments, int waitForExitTimeOut = int.MaxValue) { //RunProgram.Run("cmd.exe",FullRunCommand, log); // var FullRunCommand=$"/C {BackupUtilityCommand} {Arguments}"; //for Windows need to pass to cmd.exe because often have command line piping etc var args = $"/C {cmd} {arguments}"; using (var process = new Process()) { process.StartInfo = new ProcessStartInfo { FileName = "cmd.exe", Arguments = args, RedirectStandardOutput = true, RedirectStandardError = true, UseShellExecute = false, CreateNoWindow = true, }; process.Start(); string result = $"{process.StandardOutput.ReadToEnd()}{process.StandardError.ReadToEnd()} "; if (!process.WaitForExit(waitForExitTimeOut)) { result += $"\nERROR: TIMED OUT {waitForExitTimeOut}ms BEFORE COMPLETION"; } return result; } } private static string RunLinuxShell(string cmd, string arguments, int waitForExitTimeOut = int.MaxValue) { var escapedArgs = $"{cmd} {arguments}".Replace("\"", "\\\""); using (var process = new Process()) { process.StartInfo = new ProcessStartInfo { FileName = "/bin/sh", Arguments = $"-c \"{escapedArgs}\"", RedirectStandardOutput = true, UseShellExecute = false, CreateNoWindow = true, }; process.Start(); string result = process.StandardOutput.ReadToEnd(); if (!process.WaitForExit(waitForExitTimeOut)) { result += $"\nERROR: TIMED OUT {waitForExitTimeOut}ms BEFORE COMPLETION"; } return result; } } private static string RunOSX(string cmd, string arguments, int waitForExitTimeOut = int.MaxValue) { using (var process = new Process()) { process.StartInfo = new ProcessStartInfo { FileName = cmd, Arguments = arguments, RedirectStandardOutput = true, UseShellExecute = false, CreateNoWindow = true, }; process.Start(); string result = process.StandardOutput.ReadToEnd(); if (!process.WaitForExit(waitForExitTimeOut)) { result += $"\nERROR: TIMED OUT {waitForExitTimeOut}ms BEFORE COMPLETION"; } return result; } } } }