/// <summary> /// Attach the command to all available stream readers. /// </summary> /// <param name="logger"></param> /// <param name="commandHolder"></param> public static void LatchQuickly(ILogger <ProcessRunner> logger, CommandHolder commandHolder) { // Start the standard stream reader. new Thread(() => { Standard(logger, commandHolder); }).Start(); // Start the error stream reader. new Thread(() => { Error(logger, commandHolder); }).Start(); }
/// <summary> /// Attach the command to the error stream reader. /// </summary> /// <param name="logger"></param> /// <param name="commandHolder"></param> private static async void Error(ILogger <ProcessRunner> logger, CommandHolder commandHolder) { string line; while ((line = await commandHolder.Command.StandardError.ReadLineAsync().ConfigureAwait(false)) != null) { logger.LogError(line); } }
/// <summary> /// Stop tracking a process. /// </summary> /// <param name="commandHolder"></param> /// <returns></returns> public bool Untrack(CommandHolder commandHolder) { // Check to see if the process is already being tracked. if (_runningCommands.Contains(commandHolder)) { // The process is being tracked, remove it. _runningCommands.Remove(commandHolder); return(true); } else { // The process is not being tracked. return(false); } }
/// <summary> /// Function to attach a logging instance to the commandHolder using the classes _logger. /// </summary> private void AttachLoggerToCommandHolder(CommandHolder commandHolder, string parent) { _logger.LogDebug( "{0} Object is attaching logger to command: {1}", parent, commandHolder.Options.Executable ); var streamProcessor = GetStreamProcessor(commandHolder); // Attach command objects streamProcessor.StandardOutputProcessor = CommandLogger.StandardAction(); streamProcessor.ErrorOutputProcessor = CommandLogger.ErrorAction(); // Actually begin logging. streamProcessor.StandardOutputProcessor(_logger, commandHolder); streamProcessor.ErrorOutputProcessor(_logger, commandHolder); }
/// <summary> /// Start a new command holder based on the provided process options and caller. /// </summary> /// <param name="processOptions"></param> /// <param name="caller"></param> /// <returns></returns> public CommandHolder Run(ProcessOptions processOptions, IService caller = null) { // Convert the options into a new command holder. CommandHolder commandHolder = null; // Check the state of the service. var currentState = caller?.GetState(); var allowedStates = new[] { ServiceState.Running, ServiceState.Restarting }; if (currentState != null && !allowedStates.Any(x => x == currentState)) { _logger.LogInformation("The service state prohibited a proccess from running."); throw new InvalidOperationException($"Service state was {currentState}, invocation can NOT continue."); } // Validate that the provided directory exists. if (processOptions.WorkingDirectory != null) { if (!Directory.Exists(processOptions.WorkingDirectory)) { throw WorkingDirectoryDoesntExistException(processOptions.WorkingDirectory); } } // Check if we should run as root/admin. if (!processOptions.InvokeAsSuperuser) { // Nope - platform independent specification, go wild. commandHolder = new CommandHolder { Options = processOptions, Caller = caller, Command = new Shell( e => e.ThrowOnError(processOptions.ThrowOnError) ).Run( executable: processOptions.Executable, arguments: processOptions.Arguments, options: o => o .DisposeOnExit(processOptions.DisposeOnExit) .WorkingDirectory(processOptions.WorkingDirectory) ) }; } else { // Yes, check the operating system to know what we have to do. if (AppConfig.isWindows) { // WINDOWS ===== // runas verb, build the command holder with the runas verb. commandHolder = new CommandHolder { Options = processOptions, Caller = caller, Command = new Shell( e => e.ThrowOnError(processOptions.ThrowOnError) ).Run( executable: processOptions.Executable, arguments: processOptions.Arguments, options: o => o .StartInfo(s => s // The runas attribute will run as administrator. .Verb = "runas" ) .DisposeOnExit(processOptions.DisposeOnExit) .WorkingDirectory(processOptions.WorkingDirectory) ) }; } else if (AppConfig.isUnix) { // LINUX/MACOS ===== // Build the argument array. var arguments = new List <string>(); arguments.Add(processOptions.Executable); for (var i = 0; i != processOptions.Arguments.Length; i++) { arguments.Add(processOptions.Arguments[i] ?? ""); } var procArgStr = string.Join(" ", arguments); // Write to the console. _logger.LogDebug("Built linux specific superuser argument array: " + procArgStr); // Build the command holder with a sudo as the executable. commandHolder = new CommandHolder { Options = processOptions, Caller = caller, Command = new Shell( e => e.ThrowOnError(processOptions.ThrowOnError) ).Run( executable: GetSudoPath(), arguments: arguments, options: o => o .DisposeOnExit(processOptions.DisposeOnExit) .WorkingDirectory(processOptions.WorkingDirectory) ) }; } } // Attach Loggers if needed. if (processOptions.AttachLogToConsole) { AttachLoggerToCommandHolder(commandHolder, "Start"); } // Check if we should monitor. if (commandHolder.Options.Monitor && caller != null) { var monitoringThread = new Thread(() => Monitor(commandHolder, caller)); commandHolder.MonitoringThread = monitoringThread; monitoringThread.Start(); } // Keep track of the object. Track(commandHolder); // Return return(commandHolder); }
/// <summary> /// Reassign the command holder's command with the provided. /// </summary> /// <param name="commandHolder"></param> /// <param name="command"></param> /// <returns></returns> public CommandHolder ReassignCommand(CommandHolder commandHolder, Command command) { commandHolder.Command = command; return(commandHolder); }
/// <summary> /// Gets the stream processor for the command holder /// </summary> /// <param name="commandHolder"></param> /// <returns></returns> public StreamProcessor GetStreamProcessor(CommandHolder commandHolder) => commandHolder.Options.streamProcessor;
/// <summary> /// Get the IService caller object of the provided CommandHolder. /// </summary> /// <param name="referencedCommandHolder"></param> /// <returns></returns> public IService GetCommandCallerService(CommandHolder referencedCommandHolder) => referencedCommandHolder.Caller;
/// <summary> /// Start tracking a command. /// </summary> /// <param name="commandHolder"></param> public void Track(CommandHolder commandHolder) => _runningCommands.Add(commandHolder);
public void Monitor(CommandHolder commandHolder, IService service) { // Wait for it to be tracked. while (!_runningCommands.Contains(commandHolder)) { Thread.Sleep(100); } // While we should track the process. while (_runningCommands.Contains(commandHolder)) { // Check if we should dispose. if (commandHolder.Options.DisposeOnExit) { // Make sure the service is still running if (service.GetState() == ServiceState.Running) { // Check to see if the process has exited gracefully if (commandHolder.Command.Process.HasExited) { _logger.LogWarning("A process has exited gracefully."); // Stop tracking the command. _runningCommands.Remove(commandHolder); } } else { // Check if the command has not exited. if (!commandHolder.Command.Process.HasExited) { // tell the console _logger.LogWarning( string.Format( "The service state has changed, Process ID {0} will be killed.", commandHolder.Command.Process.Id ) ); // Gracefully close the process commandHolder.Command.Process.Close(); // Stop tracking the command. _runningCommands.Remove(commandHolder); } } } else // if process not dispose on exit { if (service.GetState() == ServiceState.Running) { if (commandHolder.Command.Process.HasExited) { // TODO: address DAEM-112 // Tell the console. _logger.LogWarning( "A process has exited unexpectedly, and will be restarted. Command output redirection will cease (DAEM-112)." ); // Restart the process. commandHolder.Command.Process.Start(); // Attach the logger if needed. if (commandHolder.Options.AttachLogToConsole) { AttachLoggerToCommandHolder(commandHolder, "Monitor"); } } } else { if (!commandHolder.Command.Process.HasExited) { _logger.LogWarning( string.Format( "The service state has changed, Process ID {0} will be killed.", commandHolder.Command.Process.Id ) ); commandHolder.Command.Process.Close(); } } } // Wait for the monitoring interval. Thread.Sleep(commandHolder.Options.MonitoringInterval * 1000); } _logger.LogDebug($"Monitor Thread {Thread.CurrentThread.ManagedThreadId}: Command ({commandHolder.Options.Executable}) is no longer tracked, terminating..."); }