Skip to content

Instantly share code, notes, and snippets.

@KalinovDmitri
Created March 30, 2018 12:34
Show Gist options
  • Save KalinovDmitri/0dd245c021a56ecf0f5e952607cc90d4 to your computer and use it in GitHub Desktop.
Save KalinovDmitri/0dd245c021a56ecf0f5e952607cc90d4 to your computer and use it in GitHub Desktop.
using System;
using System.Collections.Generic;
using System.IO;
using System.Diagnostics;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace CodeSamples.Common.Diagnostics
{
public class AsyncProcessRunner
{
public AsyncProcessRunner() { }
public Task<ProcessExecutionResult> RunAsync(ProcessStartInfo startInfo, params string[] commandLine)
{
if (startInfo == null)
{
throw new ArgumentNullException(nameof(startInfo));
}
if (commandLine == null)
{
throw new ArgumentNullException(nameof(commandLine));
}
if (!startInfo.RedirectStandardInput && commandLine.Length > 0)
{
throw new ArgumentOutOfRangeException(nameof(commandLine), "Unable to transmit command line to process if RedirectStandardInput is false.");
}
var completionSource = new TaskCompletionSource<ProcessExecutionResult>();
var state = new ProcessRunningState(completionSource, startInfo, commandLine);
Task.Factory.StartNew(RunProcessAndWaitForExit, state);
return completionSource.Task;
}
private static void RunProcessAndWaitForExit(object state)
{
((ProcessRunningState)state).RunAndWaitForExit();
}
}
internal class ProcessRunningState
{
private readonly TaskCompletionSource<ProcessExecutionResult> _completionSource;
private readonly ProcessStartInfo _startInfo;
private readonly string[] _commandLine;
private Process _process;
private List<string> _output;
private List<string> _errorOutput;
internal ProcessRunningState(TaskCompletionSource<ProcessExecutionResult> completionSource, ProcessStartInfo startInfo, string[] commandLine)
{
_completionSource = completionSource;
_startInfo = startInfo;
_commandLine = commandLine;
_output = new List<string>();
_errorOutput = new List<string>();
}
internal void RunAndWaitForExit()
{
try
{
var process = new Process
{
StartInfo = _startInfo,
EnableRaisingEvents = true
};
_process = process;
process.Exited += OnProcessExited;
if (_startInfo.RedirectStandardError) process.ErrorDataReceived += OnErrorDataReceived;
if (_startInfo.RedirectStandardOutput) process.OutputDataReceived += OnOutputDataReceived;
bool started = _process.Start();
if (!started)
{
_completionSource.TrySetException(new InvalidOperationException($"Could not start process '{_startInfo.FileName}'."));
}
if (_startInfo.RedirectStandardOutput) _process.BeginOutputReadLine();
if (_startInfo.RedirectStandardError) _process.BeginErrorReadLine();
if (_startInfo.RedirectStandardInput)
foreach (var command in _commandLine)
_process.StandardInput.WriteLine(command);
_process.WaitForExit();
}
catch (Exception exc)
{
_completionSource.TrySetException(exc);
}
finally
{
_process?.Dispose();
}
}
private void OnErrorDataReceived(object sender, DataReceivedEventArgs args)
{
_errorOutput.Add(args.Data);
}
private void OnOutputDataReceived(object sender, DataReceivedEventArgs args)
{
_output.Add(args.Data);
}
private void OnProcessExited(object sender, EventArgs args)
{
_completionSource.TrySetResult(new ProcessExecutionResult
{
ExitCode = _process.ExitCode,
StartTime = _process.StartTime,
ExitTime = _process.ExitTime,
ErrorOutput = _errorOutput.AsReadOnly(),
Output = _output.AsReadOnly()
});
}
}
public class ProcessExecutionResult
{
public int ExitCode { get; internal set; }
public DateTime StartTime { get; internal set; }
public DateTime ExitTime { get; internal set; }
public TimeSpan ExecutionTime => ExitTime - StartTime;
public IReadOnlyList<string> Output { get; internal set; }
public IReadOnlyList<string> ErrorOutput { get; internal set; }
}
}
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace CodeSamples.Common.Diagnostics.Tests
{
[TestClass]
public class AsyncProcessRunnerTests
{
#region Test methods
[TestMethod]
public async Task IsAsyncProcessRunningExecutesSuccessfully()
{
var runner = new AsyncProcessRunner();
var startInfo = new ProcessStartInfo("cmd")
{
UseShellExecute = false,
CreateNoWindow = true,
RedirectStandardInput = true,
RedirectStandardOutput = true,
RedirectStandardError = true,
WorkingDirectory = Environment.CurrentDirectory
};
var executionResult = await runner.RunAsync(startInfo, "powershell -command \"Get-ChildItem\"", "exit").ConfigureAwait(false);
Assert.IsNotNull(executionResult);
Assert.IsTrue(executionResult.ExitCode == 0);
Console.WriteLine("Execution time: {0}", executionResult.ExecutionTime);
Console.WriteLine(string.Join("\r\n", executionResult.Output));
}
[TestMethod]
public async Task IsPingWithOutputExecutesSuccessfully()
{
var runner = new AsyncProcessRunner();
var startInfo = new ProcessStartInfo("ping", "-n 4 google.ru")
{
UseShellExecute = false,
CreateNoWindow = true,
RedirectStandardInput = true,
RedirectStandardOutput = true,
RedirectStandardError = true,
WorkingDirectory = Environment.CurrentDirectory
};
var executionResult = await runner.RunAsync(startInfo).ConfigureAwait(false);
Assert.IsNotNull(executionResult);
Assert.IsTrue(executionResult.ExitCode == 0);
Console.WriteLine("Execution time: {0}", executionResult.ExecutionTime);
Console.WriteLine(string.Join("\r\n", executionResult.Output));
}
[TestMethod]
public async Task IsPingWithoutOutputExecutesSuccessfully()
{
var runner = new AsyncProcessRunner();
var startInfo = new ProcessStartInfo("ping", "-n 4 google.ru")
{
UseShellExecute = false,
CreateNoWindow = true,
RedirectStandardInput = false,
RedirectStandardOutput = false,
RedirectStandardError = false,
WorkingDirectory = Environment.CurrentDirectory
};
var executionResult = await runner.RunAsync(startInfo).ConfigureAwait(false);
Assert.IsNotNull(executionResult);
Assert.IsTrue(executionResult.ExitCode == 0);
Console.WriteLine("Execution time: {0}", executionResult.ExecutionTime);
Console.WriteLine(string.Join("\r\n", executionResult.Output));
}
#endregion
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment