mirror of https://github.com/winsw/winsw
Ensure child processes are cleanup up
parent
efc3e34f6d
commit
bd61d2986f
|
@ -597,7 +597,7 @@ namespace WinSW
|
|||
/// <summary>
|
||||
/// Environment variable overrides
|
||||
/// </summary>
|
||||
public override Dictionary<string, string> EnvironmentVariables => new Dictionary<string, string>(this.environmentVariables);
|
||||
public override Dictionary<string, string> EnvironmentVariables => this.environmentVariables;
|
||||
|
||||
/// <summary>
|
||||
/// List of downloads to be performed by the wrapper before starting
|
||||
|
|
|
@ -0,0 +1,111 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using log4net;
|
||||
using static WinSW.Native.ProcessApis;
|
||||
|
||||
namespace WinSW.Util
|
||||
{
|
||||
public static class ProcessExtensions
|
||||
{
|
||||
private static readonly ILog Logger = LogManager.GetLogger(typeof(ProcessExtensions));
|
||||
|
||||
public static void StopTree(this Process process, TimeSpan stopTimeout)
|
||||
{
|
||||
Stop(process, stopTimeout);
|
||||
|
||||
foreach (Process child in GetDescendants(process))
|
||||
{
|
||||
StopTree(child, stopTimeout);
|
||||
}
|
||||
}
|
||||
|
||||
internal static void StopDescendants(this Process process, TimeSpan stopTimeout)
|
||||
{
|
||||
foreach (Process child in GetDescendants(process))
|
||||
{
|
||||
StopTree(child, stopTimeout);
|
||||
}
|
||||
}
|
||||
|
||||
private static void Stop(Process process, TimeSpan stopTimeout)
|
||||
{
|
||||
Logger.Info("Stopping process " + process.Id);
|
||||
|
||||
if (process.HasExited)
|
||||
{
|
||||
Logger.Info("Process " + process.Id + " is already stopped");
|
||||
return;
|
||||
}
|
||||
|
||||
// (bool sent, bool exited)
|
||||
KeyValuePair<bool, bool> result = SignalHelper.SendCtrlCToProcess(process, stopTimeout);
|
||||
bool exited = result.Value;
|
||||
if (!exited)
|
||||
{
|
||||
bool sent = result.Key;
|
||||
if (sent)
|
||||
{
|
||||
Logger.Info("Process " + process.Id + " did not respond to Ctrl+C signal - Killing as fallback");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
process.Kill();
|
||||
}
|
||||
catch when (process.HasExited)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Propagate error if process kill fails? Currently we use the legacy behavior
|
||||
}
|
||||
|
||||
private static unsafe List<Process> GetDescendants(Process root)
|
||||
{
|
||||
DateTime startTime = root.StartTime;
|
||||
int processId = root.Id;
|
||||
|
||||
var children = new List<Process>();
|
||||
|
||||
foreach (Process process in Process.GetProcesses())
|
||||
{
|
||||
try
|
||||
{
|
||||
if (process.StartTime <= startTime)
|
||||
{
|
||||
goto Next;
|
||||
}
|
||||
|
||||
IntPtr handle = process.Handle;
|
||||
|
||||
if (NtQueryInformationProcess(
|
||||
handle,
|
||||
PROCESSINFOCLASS.ProcessBasicInformation,
|
||||
out PROCESS_BASIC_INFORMATION information,
|
||||
sizeof(PROCESS_BASIC_INFORMATION)) != 0)
|
||||
{
|
||||
goto Next;
|
||||
}
|
||||
|
||||
if ((int)information.InheritedFromUniqueProcessId == processId)
|
||||
{
|
||||
Logger.Info($"Found child process '{process.Format()}'.");
|
||||
children.Add(process);
|
||||
continue;
|
||||
}
|
||||
|
||||
Next:
|
||||
process.Dispose();
|
||||
}
|
||||
catch (Exception e) when (e is InvalidOperationException || e is Win32Exception)
|
||||
{
|
||||
process.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
return children;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,197 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using log4net;
|
||||
using static WinSW.Native.ProcessApis;
|
||||
|
||||
namespace WinSW.Util
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides helper classes for Process Management
|
||||
/// </summary>
|
||||
/// <remarks>Since WinSW 2.0</remarks>
|
||||
public class ProcessHelper
|
||||
{
|
||||
private static readonly ILog Logger = LogManager.GetLogger(typeof(ProcessHelper));
|
||||
|
||||
/// <summary>
|
||||
/// Gets all children of the specified process.
|
||||
/// </summary>
|
||||
/// <param name="processId">Process PID</param>
|
||||
/// <returns>List of child process PIDs</returns>
|
||||
private static unsafe List<Process> GetChildProcesses(DateTime startTime, int processId)
|
||||
{
|
||||
var children = new List<Process>();
|
||||
|
||||
foreach (Process process in Process.GetProcesses())
|
||||
{
|
||||
try
|
||||
{
|
||||
if (process.StartTime <= startTime)
|
||||
{
|
||||
goto Next;
|
||||
}
|
||||
|
||||
IntPtr handle = process.Handle;
|
||||
|
||||
if (NtQueryInformationProcess(
|
||||
handle,
|
||||
PROCESSINFOCLASS.ProcessBasicInformation,
|
||||
out PROCESS_BASIC_INFORMATION information,
|
||||
sizeof(PROCESS_BASIC_INFORMATION)) != 0)
|
||||
{
|
||||
goto Next;
|
||||
}
|
||||
|
||||
if ((int)information.InheritedFromUniqueProcessId == processId)
|
||||
{
|
||||
Logger.Info($"Found child process '{process.Format()}'.");
|
||||
children.Add(process);
|
||||
continue;
|
||||
}
|
||||
|
||||
Next:
|
||||
process.Dispose();
|
||||
}
|
||||
catch (Exception e) when (e is InvalidOperationException || e is Win32Exception)
|
||||
{
|
||||
process.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
return children;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops the process.
|
||||
/// If the process cannot be stopped within the stop timeout, it gets killed
|
||||
/// </summary>
|
||||
public static void StopProcess(Process process, TimeSpan stopTimeout)
|
||||
{
|
||||
Logger.Info("Stopping process " + process.Id);
|
||||
|
||||
if (process.HasExited)
|
||||
{
|
||||
Logger.Info("Process " + process.Id + " is already stopped");
|
||||
return;
|
||||
}
|
||||
|
||||
// (bool sent, bool exited)
|
||||
KeyValuePair<bool, bool> result = SignalHelper.SendCtrlCToProcess(process, stopTimeout);
|
||||
bool exited = result.Value;
|
||||
if (!exited)
|
||||
{
|
||||
bool sent = result.Key;
|
||||
if (sent)
|
||||
{
|
||||
Logger.Warn("Process " + process.Id + " did not respond to Ctrl+C signal - Killing as fallback");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
process.Kill();
|
||||
}
|
||||
catch when (process.HasExited)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Propagate error if process kill fails? Currently we use the legacy behavior
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Terminate process and its children.
|
||||
/// By default the child processes get terminated first.
|
||||
/// </summary>
|
||||
public static void StopProcessTree(Process process, TimeSpan stopTimeout)
|
||||
{
|
||||
StopProcess(process, stopTimeout);
|
||||
|
||||
foreach (Process child in GetChildProcesses(process.StartTime, process.Id))
|
||||
{
|
||||
StopProcessTree(child, stopTimeout);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts a process and asynchronosly waits for its termination.
|
||||
/// Once the process exits, the callback will be invoked.
|
||||
/// </summary>
|
||||
/// <param name="processToStart">Process object to be used</param>
|
||||
/// <param name="executable">Executable, which should be invoked</param>
|
||||
/// <param name="arguments">Arguments to be passed</param>
|
||||
/// <param name="envVars">Additional environment variables</param>
|
||||
/// <param name="workingDirectory">Working directory</param>
|
||||
/// <param name="priority">Priority</param>
|
||||
/// <param name="onExited">Completion callback. If null, the completion won't be monitored</param>
|
||||
/// <param name="logHandler">Log handler. If enabled, logs will be redirected to the process and then reported</param>
|
||||
public static void StartProcessAndCallbackForExit(
|
||||
Process processToStart,
|
||||
string? executable = null,
|
||||
string? arguments = null,
|
||||
Dictionary<string, string>? envVars = null,
|
||||
string? workingDirectory = null,
|
||||
ProcessPriorityClass? priority = null,
|
||||
Action<Process>? onExited = null,
|
||||
LogHandler? logHandler = null,
|
||||
bool hideWindow = false)
|
||||
{
|
||||
var ps = processToStart.StartInfo;
|
||||
ps.FileName = executable ?? ps.FileName;
|
||||
ps.Arguments = arguments ?? ps.Arguments;
|
||||
ps.WorkingDirectory = workingDirectory ?? ps.WorkingDirectory;
|
||||
ps.CreateNoWindow = hideWindow;
|
||||
ps.UseShellExecute = false;
|
||||
ps.RedirectStandardOutput = logHandler != null;
|
||||
ps.RedirectStandardError = logHandler != null;
|
||||
|
||||
if (envVars != null)
|
||||
{
|
||||
var newEnvironment =
|
||||
#if NETCOREAPP
|
||||
ps.Environment;
|
||||
#else
|
||||
ps.EnvironmentVariables;
|
||||
#endif
|
||||
foreach (KeyValuePair<string, string> pair in envVars)
|
||||
{
|
||||
newEnvironment[pair.Key] = pair.Value;
|
||||
}
|
||||
}
|
||||
|
||||
processToStart.Start();
|
||||
Logger.Info("Started process " + processToStart.Id);
|
||||
|
||||
if (priority != null)
|
||||
{
|
||||
processToStart.PriorityClass = priority.Value;
|
||||
}
|
||||
|
||||
// Redirect logs if required
|
||||
if (logHandler != null)
|
||||
{
|
||||
Logger.Debug("Forwarding logs of the process " + processToStart + " to " + logHandler);
|
||||
logHandler.Log(processToStart.StandardOutput, processToStart.StandardError);
|
||||
}
|
||||
|
||||
// monitor the completion of the process
|
||||
if (onExited != null)
|
||||
{
|
||||
processToStart.Exited += (sender, _) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
onExited((Process)sender!);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Error("Unhandled exception in event handler.", e);
|
||||
}
|
||||
};
|
||||
|
||||
processToStart.EnableRaisingEvents = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -270,7 +270,7 @@ namespace WinSW.Plugins.RunawayProcessKiller
|
|||
bldr.Append(proc);
|
||||
|
||||
Logger.Warn(bldr.ToString());
|
||||
ProcessHelper.StopProcessTree(proc, this.StopTimeout);
|
||||
proc.StopTree(this.StopTimeout);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -107,7 +107,7 @@ $@"<service>
|
|||
if (!proc.HasExited)
|
||||
{
|
||||
Console.Error.WriteLine("Test: Killing runaway process with ID=" + proc.Id);
|
||||
ProcessHelper.StopProcessTree(proc, TimeSpan.FromMilliseconds(100));
|
||||
proc.StopTree(TimeSpan.FromMilliseconds(100));
|
||||
if (!proc.HasExited)
|
||||
{
|
||||
// The test is failed here anyway, but we add additional diagnostics info
|
||||
|
|
|
@ -16,11 +16,9 @@ namespace WinSW
|
|||
{
|
||||
public sealed class WrapperService : ServiceBase, IEventLogger, IServiceEventLog
|
||||
{
|
||||
private readonly Process process = new Process();
|
||||
private readonly XmlServiceConfig config;
|
||||
private Dictionary<string, string>? envs;
|
||||
internal static readonly WrapperServiceEventLogProvider eventLogProvider = new WrapperServiceEventLogProvider();
|
||||
|
||||
internal WinSWExtensionManager ExtensionManager { get; }
|
||||
private static readonly TimeSpan additionalStopTimeout = new TimeSpan(TimeSpan.TicksPerSecond);
|
||||
|
||||
private static readonly ILog Log = LogManager.GetLogger(
|
||||
#if NETCOREAPP
|
||||
|
@ -28,13 +26,18 @@ namespace WinSW
|
|||
#endif
|
||||
"WinSW");
|
||||
|
||||
internal static readonly WrapperServiceEventLogProvider eventLogProvider = new WrapperServiceEventLogProvider();
|
||||
private readonly XmlServiceConfig config;
|
||||
|
||||
private Process process = null!;
|
||||
private volatile Process? poststartProcess;
|
||||
|
||||
internal WinSWExtensionManager ExtensionManager { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates to the watch dog thread that we are going to terminate the process,
|
||||
/// so don't try to kill us when the child exits.
|
||||
/// </summary>
|
||||
private bool orderlyShutdown;
|
||||
private volatile bool orderlyShutdown;
|
||||
private bool shuttingdown;
|
||||
|
||||
/// <summary>
|
||||
|
@ -243,8 +246,6 @@ namespace WinSW
|
|||
|
||||
private void DoStart()
|
||||
{
|
||||
this.envs = this.config.EnvironmentVariables;
|
||||
|
||||
this.HandleFileCopies();
|
||||
|
||||
// handle downloads
|
||||
|
@ -285,19 +286,20 @@ namespace WinSW
|
|||
throw new AggregateException(exceptions);
|
||||
}
|
||||
|
||||
try
|
||||
string? prestartExecutable = this.config.PrestartExecutable;
|
||||
if (prestartExecutable != null)
|
||||
{
|
||||
string? prestartExecutable = this.config.PrestartExecutable;
|
||||
if (prestartExecutable != null)
|
||||
try
|
||||
{
|
||||
using Process process = this.StartProcess(prestartExecutable, this.config.PrestartArguments);
|
||||
this.WaitForProcessToExit(process);
|
||||
this.LogInfo($"Pre-start process '{process.Format()}' exited with code {process.ExitCode}.");
|
||||
process.StopDescendants(additionalStopTimeout);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e);
|
||||
}
|
||||
|
||||
string startArguments = this.config.StartArguments ?? this.config.Arguments;
|
||||
|
@ -309,7 +311,7 @@ namespace WinSW
|
|||
this.ExtensionManager.FireOnWrapperStarted();
|
||||
|
||||
LogHandler executableLogHandler = this.CreateExecutableLogHandler();
|
||||
this.StartProcess(this.process, startArguments, this.config.Executable, executableLogHandler);
|
||||
this.process = this.StartProcess(this.config.Executable, startArguments, this.OnMainProcessExited, executableLogHandler);
|
||||
this.ExtensionManager.FireOnProcessStarted(this.process);
|
||||
|
||||
try
|
||||
|
@ -317,10 +319,19 @@ namespace WinSW
|
|||
string? poststartExecutable = this.config.PoststartExecutable;
|
||||
if (poststartExecutable != null)
|
||||
{
|
||||
using Process process = this.StartProcess(poststartExecutable, this.config.PoststartArguments, process =>
|
||||
using Process process = StartProcessLocked();
|
||||
this.WaitForProcessToExit(process);
|
||||
this.LogInfo($"Post-start process '{process.Format()}' exited with code {process.ExitCode}.");
|
||||
process.StopDescendants(additionalStopTimeout);
|
||||
this.poststartProcess = null;
|
||||
|
||||
Process StartProcessLocked()
|
||||
{
|
||||
this.LogInfo($"Post-start process '{process.Format()}' exited with code {process.ExitCode}.");
|
||||
});
|
||||
lock (this)
|
||||
{
|
||||
return this.poststartProcess = this.StartProcess(poststartExecutable, this.config.PoststartArguments);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
|
@ -334,61 +345,73 @@ namespace WinSW
|
|||
/// </summary>
|
||||
private void DoStop()
|
||||
{
|
||||
try
|
||||
string? prestopExecutable = this.config.PrestopExecutable;
|
||||
if (prestopExecutable != null)
|
||||
{
|
||||
string? prestopExecutable = this.config.PrestopExecutable;
|
||||
if (prestopExecutable != null)
|
||||
try
|
||||
{
|
||||
using Process process = this.StartProcess(prestopExecutable, this.config.PrestopArguments);
|
||||
this.WaitForProcessToExit(process);
|
||||
this.LogInfo($"Pre-stop process '{process.Format()}' exited with code {process.ExitCode}.");
|
||||
process.StopDescendants(additionalStopTimeout);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e);
|
||||
}
|
||||
|
||||
string? stopArguments = this.config.StopArguments;
|
||||
this.LogInfo("Stopping " + this.config.Id);
|
||||
this.orderlyShutdown = true;
|
||||
this.process.EnableRaisingEvents = false;
|
||||
|
||||
if (stopArguments is null)
|
||||
{
|
||||
Log.Debug("ProcessKill " + this.process.Id);
|
||||
ProcessHelper.StopProcessTree(this.process, this.config.StopTimeout);
|
||||
this.process.StopTree(this.config.StopTimeout);
|
||||
this.ExtensionManager.FireOnProcessTerminated(this.process);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.SignalPending();
|
||||
|
||||
Process stopProcess = new Process();
|
||||
|
||||
string stopExecutable = this.config.StopExecutable ?? this.config.Executable;
|
||||
|
||||
// TODO: Redirect logging to Log4Net once https://github.com/kohsuke/winsw/pull/213 is integrated
|
||||
this.StartProcess(stopProcess, stopArguments, stopExecutable, null);
|
||||
try
|
||||
{
|
||||
// TODO: Redirect logging to Log4Net once https://github.com/kohsuke/winsw/pull/213 is integrated
|
||||
Process stopProcess = this.StartProcess(stopExecutable, stopArguments);
|
||||
|
||||
Log.Debug("WaitForProcessToExit " + this.process.Id + "+" + stopProcess.Id);
|
||||
this.WaitForProcessToExit(this.process);
|
||||
this.WaitForProcessToExit(stopProcess);
|
||||
Log.Debug("WaitForProcessToExit " + this.process.Id + "+" + stopProcess.Id);
|
||||
this.WaitForProcessToExit(stopProcess);
|
||||
stopProcess.StopDescendants(additionalStopTimeout);
|
||||
|
||||
this.WaitForProcessToExit(this.process);
|
||||
this.process.StopDescendants(this.config.StopTimeout);
|
||||
}
|
||||
catch
|
||||
{
|
||||
this.process.StopTree(this.config.StopTimeout);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
this.poststartProcess?.StopTree(additionalStopTimeout);
|
||||
|
||||
string? poststopExecutable = this.config.PoststopExecutable;
|
||||
if (poststopExecutable != null)
|
||||
{
|
||||
string? poststopExecutable = this.config.PoststopExecutable;
|
||||
if (poststopExecutable != null)
|
||||
try
|
||||
{
|
||||
using Process process = this.StartProcess(poststopExecutable, this.config.PoststopArguments);
|
||||
this.WaitForProcessToExit(process);
|
||||
this.LogInfo($"Post-stop process '{process.Format()}' exited with code {process.ExitCode}.");
|
||||
process.StopDescendants(additionalStopTimeout);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e);
|
||||
}
|
||||
|
||||
// Stop extensions
|
||||
|
@ -447,60 +470,83 @@ namespace WinSW
|
|||
sc.SetStatus(this.ServiceHandle, ServiceControllerStatus.Stopped);
|
||||
}
|
||||
|
||||
private void StartProcess(Process processToStart, string arguments, string executable, LogHandler? logHandler)
|
||||
private void OnMainProcessExited(Process process)
|
||||
{
|
||||
// Define handler of the completed process
|
||||
void OnProcessCompleted(Process process)
|
||||
string display = process.Format();
|
||||
|
||||
if (this.orderlyShutdown)
|
||||
{
|
||||
string display = process.Format();
|
||||
this.LogInfo($"Child process '{display}' terminated with code {process.ExitCode}.");
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Warn($"Child process '{display}' finished with code {process.ExitCode}.");
|
||||
|
||||
if (this.orderlyShutdown)
|
||||
process.StopDescendants(this.config.StopTimeout);
|
||||
|
||||
lock (this)
|
||||
{
|
||||
this.LogInfo($"Child process '{display}' terminated with code {process.ExitCode}.");
|
||||
this.poststartProcess?.StopTree(new TimeSpan(TimeSpan.TicksPerMillisecond));
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Warn($"Child process '{display}' finished with code {process.ExitCode}.");
|
||||
|
||||
// if we finished orderly, report that to SCM.
|
||||
// by not reporting unclean shutdown, we let Windows SCM to decide if it wants to
|
||||
// restart the service automatically
|
||||
// if we finished orderly, report that to SCM.
|
||||
// by not reporting unclean shutdown, we let Windows SCM to decide if it wants to
|
||||
// restart the service automatically
|
||||
if (process.ExitCode == 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (process.ExitCode == 0)
|
||||
{
|
||||
this.SignalStopped();
|
||||
}
|
||||
this.SignalStopped();
|
||||
}
|
||||
finally
|
||||
catch (Exception e)
|
||||
{
|
||||
Environment.Exit(process.ExitCode);
|
||||
Log.Error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Invoke process and exit
|
||||
ProcessHelper.StartProcessAndCallbackForExit(
|
||||
processToStart: processToStart,
|
||||
executable: executable,
|
||||
arguments: arguments,
|
||||
envVars: this.envs,
|
||||
workingDirectory: this.config.WorkingDirectory,
|
||||
priority: this.config.Priority,
|
||||
onExited: OnProcessCompleted,
|
||||
logHandler: logHandler,
|
||||
hideWindow: this.config.HideWindow);
|
||||
Environment.Exit(process.ExitCode);
|
||||
}
|
||||
}
|
||||
|
||||
private Process StartProcess(string executable, string? arguments, Action<Process>? onExited = null)
|
||||
private Process StartProcess(string executable, string? arguments, Action<Process>? onExited = null, LogHandler? logHandler = null)
|
||||
{
|
||||
var info = new ProcessStartInfo(executable, arguments)
|
||||
var startInfo = new ProcessStartInfo(executable, arguments)
|
||||
{
|
||||
UseShellExecute = false,
|
||||
WorkingDirectory = this.config.WorkingDirectory,
|
||||
CreateNoWindow = this.config.HideWindow,
|
||||
RedirectStandardOutput = logHandler != null,
|
||||
RedirectStandardError = logHandler != null,
|
||||
};
|
||||
|
||||
Process process = Process.Start(info);
|
||||
Dictionary<string, string> environment = this.config.EnvironmentVariables;
|
||||
if (environment.Count > 0)
|
||||
{
|
||||
var newEnvironment =
|
||||
#if NETCOREAPP
|
||||
startInfo.Environment;
|
||||
#else
|
||||
startInfo.EnvironmentVariables;
|
||||
#endif
|
||||
foreach (KeyValuePair<string, string> pair in environment)
|
||||
{
|
||||
newEnvironment[pair.Key] = pair.Value;
|
||||
}
|
||||
}
|
||||
|
||||
Process process = Process.Start(startInfo);
|
||||
|
||||
Log.Info($"Started process {process.Format()}.");
|
||||
|
||||
if (this.config.Priority is ProcessPriorityClass priority)
|
||||
{
|
||||
process.PriorityClass = priority;
|
||||
}
|
||||
|
||||
if (logHandler != null)
|
||||
{
|
||||
logHandler.Log(process.StandardOutput, process.StandardError);
|
||||
}
|
||||
|
||||
if (onExited != null)
|
||||
{
|
||||
|
|
Loading…
Reference in New Issue