Created
March 30, 2018 12:34
-
-
Save KalinovDmitri/0dd245c021a56ecf0f5e952607cc90d4 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; } | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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