Skip to content

Instantly share code, notes, and snippets.

@jewijaya
Created July 24, 2020 01:03
Show Gist options
  • Save jewijaya/300e23774ca9338b081ee6ecda7aebae to your computer and use it in GitHub Desktop.
Save jewijaya/300e23774ca9338b081ee6ecda7aebae to your computer and use it in GitHub Desktop.
Another Process runner using RX
//ref: https://stackoverflow.com/a/60355879/10522960
public static void ExecuteScriptRx(string path, int processTimeOutMilliseconds, out string logs, out bool success, params string[] args)
{
StringBuilder output = new StringBuilder();
StringBuilder error = new StringBuilder();
using (var process = new Process())
using (var disposables = new CompositeDisposable())
{
process.StartInfo = new ProcessStartInfo
{
WindowStyle = ProcessWindowStyle.Hidden,
FileName = "powershell.exe",
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
Arguments = $"-ExecutionPolicy Bypass -File \"{path}\"",
WorkingDirectory = Path.GetDirectoryName(path)
};
if (args.Length > 0)
{
var arguments = string.Join(" ", args.Select(x => $"\"{x}\""));
process.StartInfo.Arguments += $" {arguments}";
}
output.AppendLine($"args:'{process.StartInfo.Arguments}'");
// Raise the Process.Exited event when the process terminates.
process.EnableRaisingEvents = true;
// Subscribe to OutputData
Observable.FromEventPattern<DataReceivedEventArgs>(process, nameof(Process.OutputDataReceived))
.Subscribe(
eventPattern => output.AppendLine(eventPattern.EventArgs.Data),
exception => error.AppendLine(exception.Message)
).DisposeWith(disposables);
// Subscribe to ErrorData
Observable.FromEventPattern<DataReceivedEventArgs>(process, nameof(Process.ErrorDataReceived))
.Subscribe(
eventPattern => error.AppendLine(eventPattern.EventArgs.Data),
exception => error.AppendLine(exception.Message)
).DisposeWith(disposables);
var processExited =
// Observable will tick when the process has gracefully exited.
Observable.FromEventPattern<EventArgs>(process, nameof(Process.Exited))
// First two lines to tick true when the process has gracefully exited and false when it has timed out.
.Select(_ => true)
.Timeout(TimeSpan.FromMilliseconds(processTimeOutMilliseconds), Observable.Return(false))
// Force termination when the process timed out
.Do(exitedSuccessfully => { if (!exitedSuccessfully) { try { process.Kill(); } catch {} } } );
// Subscribe to the Process.Exited event.
processExited
.Subscribe()
.DisposeWith(disposables);
// Start process(ing)
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
// Wait for the process to terminate (gracefully or forced)
processExited.Take(1).Wait();
logs = output + Environment.NewLine + error;
success = process.ExitCode == 0;
}
}
/// <summary>
/// Extension methods associated with the IDisposable interface.
/// </summary>
public static class DisposableExtensions
{
/// <summary>
/// Ensures the provided disposable is disposed with the specified <see cref="CompositeDisposable"/>.
/// </summary>
public static T DisposeWith<T>(this T item, CompositeDisposable compositeDisposable)
where T : IDisposable
{
if (compositeDisposable == null)
{
throw new ArgumentNullException(nameof(compositeDisposable));
}
compositeDisposable.Add(item);
return item;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment