Files
raven/server/AyaNova/util/RunProgram.cs

184 lines
7.0 KiB
C#

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();
}
/// <summary>
/// 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
/// </summary>
public static string RunNoShell(string fileName, IEnumerable<string> arguments, IDictionary<string, string> 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;
}
}
}
}