1 using Microsoft.Extensions.Logging;
4 using System.Threading.Tasks;
19 readonly ILogger<ProcessExecutor>
logger;
33 handle.EnableRaisingEvents =
true;
34 var tcs =
new TaskCompletionSource<int>();
35 handle.Exited += (a, b) =>
40 exitCode = handle.ExitCode;
42 catch (InvalidOperationException)
48 tcs.TrySetResult(exitCode);
62 ILogger<ProcessExecutor> logger,
63 ILoggerFactory loggerFactory)
65 this.processFeatures = processFeatures ??
throw new ArgumentNullException(nameof(processFeatures));
66 this.logger = logger ??
throw new ArgumentNullException(nameof(logger));
67 this.loggerFactory = loggerFactory ??
throw new ArgumentNullException(nameof(loggerFactory));
73 logger.LogDebug(
"Attaching to process {0}...",
id);
74 global::System.Diagnostics.Process handle;
77 handle = global::System.Diagnostics.Process.GetProcessById(
id);
81 logger.LogDebug(
"Unable to get process {0}! Exception: {1}",
id, e);
85 return CreateFromExistingHandle(handle);
91 logger.LogTrace(
"Getting current process...");
92 var handle = global::System.Diagnostics.Process.GetCurrentProcess();
93 return CreateFromExistingHandle(handle);
99 string workingDirectory,
105 if (fileName == null)
106 throw new ArgumentNullException(nameof(fileName));
107 if (workingDirectory == null)
108 throw new ArgumentNullException(nameof(workingDirectory));
109 if (arguments == null)
110 throw new ArgumentNullException(nameof(arguments));
112 if (!noShellExecute && (readOutput || readError))
113 throw new InvalidOperationException(
"Requesting output/error reading requires noShellExecute to be true!");
116 "{0}aunching process in {1}: {2} {3}",
117 noShellExecute ?
"L" :
"Shell l",
121 var handle =
new global::System.Diagnostics.Process();
124 handle.StartInfo.FileName = fileName;
125 handle.StartInfo.Arguments = arguments;
126 handle.StartInfo.WorkingDirectory = workingDirectory;
128 handle.StartInfo.UseShellExecute = !noShellExecute;
130 StringBuilder outputStringBuilder = null, errorStringBuilder = null, combinedStringBuilder = null;
132 TaskCompletionSource<object> outputReadTcs = null;
133 TaskCompletionSource<object> errorReadTcs = null;
134 if (readOutput || readError)
136 combinedStringBuilder =
new StringBuilder();
139 outputStringBuilder =
new StringBuilder();
140 handle.StartInfo.RedirectStandardOutput =
true;
141 outputReadTcs =
new TaskCompletionSource<object>();
142 handle.OutputDataReceived += (sender, e) =>
146 outputReadTcs.SetResult(null);
150 combinedStringBuilder.Append(Environment.NewLine);
151 combinedStringBuilder.Append(e.Data);
152 outputStringBuilder.Append(Environment.NewLine);
153 outputStringBuilder.Append(e.Data);
159 errorStringBuilder =
new StringBuilder();
160 handle.StartInfo.RedirectStandardError =
true;
161 errorReadTcs =
new TaskCompletionSource<object>();
162 handle.ErrorDataReceived += (sender, e) =>
166 errorReadTcs.SetResult(null);
170 combinedStringBuilder.Append(Environment.NewLine);
171 combinedStringBuilder.Append(e.Data);
172 errorStringBuilder.Append(Environment.NewLine);
173 errorStringBuilder.Append(e.Data);
178 var lifetimeTask = AttachExitHandler(handle);
182 static async Task<int> AddToLifetimeTask(Task<int> originalTask, TaskCompletionSource<object> tcs)
184 var exitCode = await originalTask.ConfigureAwait(
false);
185 await tcs.Task.ConfigureAwait(
false);
193 handle.BeginOutputReadLine();
194 lifetimeTask = AddToLifetimeTask(lifetimeTask, outputReadTcs);
197 catch (InvalidOperationException) { }
202 handle.BeginErrorReadLine();
203 lifetimeTask = AddToLifetimeTask(lifetimeTask, errorReadTcs);
206 catch (InvalidOperationException) { }
214 combinedStringBuilder,
215 loggerFactory.CreateLogger<
Process>(),
false);
227 logger.LogTrace(
"GetProcessByName: {0}...", name ??
throw new ArgumentNullException(nameof(name)));
228 var procs = global::System.Diagnostics.Process.GetProcessesByName(name);
229 global::System.Diagnostics.Process handle = null;
230 foreach (var proc
in procs)
235 logger.LogTrace(
"Disposing extra found PID: {0}", proc.Id);
242 return CreateFromExistingHandle(handle);
257 AttachExitHandler(handle),
261 loggerFactory.CreateLogger<
Process>(),
Use server authentication
static Task< int > AttachExitHandler(global::System.Diagnostics.Process handle)
Create a Task<TResult> resulting in the exit code of a given handle
readonly ILoggerFactory loggerFactory
The ILoggerFactory for the ProcessExecutor
readonly ILogger< ProcessExecutor > logger
The ILogger for the ProcessExecutor
IProcess GetProcessByName(string name)
Get a IProcess with a given name .
ProcessExecutor(IProcessFeatures processFeatures, ILogger< ProcessExecutor > logger, ILoggerFactory loggerFactory)
Construct a ProcessExecutor
IProcess CreateFromExistingHandle(global::System.Diagnostics.Process handle)
Create a IProcess given an existing handle .
IProcess LaunchProcess(string fileName, string workingDirectory, string arguments, bool readOutput, bool readError, bool noShellExecute)
Launch a IProcess
Abstraction over a global::System.Diagnostics.Process
readonly IProcessFeatures processFeatures
The IProcessFeatures for the ProcessExecutor.
Abstraction for suspending and resuming processes.
IProcess GetCurrentProcess()
Get a IProcess representing the running executable.
IProcess GetProcess(int id)
Get a IProcess by id .
For launching IProcess'