For executing a Process I've created an separate class which binds the possibilities together, for example reading output, start as Admin, catch Exceptions and also start all this asynchronous.
Are there any suggestions to improve it further?
public class ExternalProcess
{
public string FileName { get; set; }
public string Arguments { get; set; } = "";
public string WorkingDirectory { get; set; } = "";
public int Timeout_milliseconds { get; set; } = -1;
public bool ReadOutput { get; set; }
public bool ShowWindow { get; set; }
public bool StartAsAdministrator { get; set; }
public string Output { get; private set; } = "";
public string OutputError { get; private set; } = "";
public int ExitCode { get; private set; }
public bool WasKilled { get; private set; }
public bool WasSuccessful { get; private set; }
public event EventHandler OutputChanged;
public event EventHandler OutputErrorChanged;
public bool Start(CancellationToken cancellationToken = default(CancellationToken))
{
var task = StartAsync(cancellationToken);
task.Wait();
return task.Result;
}
public async Task<bool> StartAsync(CancellationToken cancellationToken = default(CancellationToken))
{
if (String.IsNullOrWhiteSpace(FileName))
{
throw new ArgumentNullException(nameof(FileName));
}
//if (!File.Exists(FileName)) // removed because also commands could be executed (for example: ping)
if (!ReadOutput)
{
Output = OutputError = $"Enable {nameof(ReadOutput)} to get Output ";
}
if (StartAsAdministrator)
{
ReadOutput = false; // Verb="runas" only possible with ShellExecute=true.
Output = OutputError = "Output couldn't be read when started as Administrator ";
}
var useShellExecute = !ReadOutput; // true when started as admin, false for reading output
if (!StartAsAdministrator && !ShowWindow)
useShellExecute = false; // false for hiding the window
using (var p = new Process
{
EnableRaisingEvents = true,
StartInfo = new ProcessStartInfo()
{
FileName = FileName,
Arguments = Arguments,
UseShellExecute = useShellExecute,
RedirectStandardOutput = ReadOutput,
RedirectStandardError = ReadOutput,
RedirectStandardInput = ReadOutput,
CreateNoWindow = !ShowWindow,
}
})
{
if (StartAsAdministrator)
{
p.StartInfo.Verb = "runas";
}
if (!String.IsNullOrWhiteSpace(WorkingDirectory))
{
p.StartInfo.WorkingDirectory = WorkingDirectory;
}
if (ReadOutput)
{
p.OutputDataReceived += (sender, args) =>
{
if (!String.IsNullOrEmpty(args?.Data))
{
Output += args.Data + Environment.NewLine;
OutputChanged?.Invoke(this, EventArgs.Empty);
}
};
p.ErrorDataReceived += (sender, args) =>
{
if (!String.IsNullOrEmpty(args?.Data))
{
OutputError += args.Data + Environment.NewLine;
OutputErrorChanged?.Invoke(this, EventArgs.Empty);
}
};
}
try
{
p.Start();
}
catch (System.ComponentModel.Win32Exception ex)
{
if (ex.NativeErrorCode == 1223)
{
OutputError += "AdminRights request Canceled by User!! " + ex;
ExitCode = -1;
WasSuccessful = false;
return WasSuccessful;
}
else
{
OutputError += "Win32Exception thrown: " + ex;
ExitCode = -1;
WasSuccessful = false;
throw;
}
}
catch (Exception ex)
{
OutputError += "Exception thrown: " + ex;
ExitCode = -1;
WasSuccessful = false;
throw;
}
if (ReadOutput)
{
// var writer = p.StandardInput; writer.WriteLine("");
p.BeginOutputReadLine();
p.BeginErrorReadLine();
}
CancellationTokenSource timeoutCancellationToken = null;
if (Timeout_milliseconds > 0)
{
timeoutCancellationToken = new CancellationTokenSource(Timeout_milliseconds);
}
while (!p.HasExited && !cancellationToken.IsCancellationRequested && !(timeoutCancellationToken?.IsCancellationRequested ?? false))
{
await Task.Delay(10).ConfigureAwait(false); // configureAwait is that the task doesn't continue after the first await is done (prevent deadlock)
}
if (!p.HasExited)
{
WasKilled = true;
OutputError += " Process was cancelled!";
try
{
p.CloseMainWindow();
int waitForKill = 30;
do
{
Thread.Sleep(10);
waitForKill--;
} while (!p.HasExited && waitForKill > 0);
if (!p.HasExited)
{
p.Kill();
}
}
catch { }
}
ExitCode = p.ExitCode;
p.Close();
if (ExitCode == -1073741510)
{
OutputError += $"Process exited by user, exitcode: {ExitCode}!";
}
WasSuccessful = !WasKilled && ExitCode == 0;
return WasSuccessful;
}
}
}
Process
in parallel is very tricky, see Occasionally not getting output from processes running in parallel - if you are going to do that, then it's much better to let eachProcess
run in its own, newAppDomain
that makes them completely independent. \$\endgroup\$