using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Topshelf; using log4net; using System.Net.Sockets; using System.Net; using System.Collections.Concurrent; using System.Diagnostics; using System.IO.Pipes; namespace kmProcessServer; internal class ServiceMain : ServiceControl { ILog appLog = LogManager.GetLogger(Program.AppName); ConcurrentBag tasks = new(); ConcurrentBag ChildProcesses = new(); public bool Start(HostControl hostControl) { appLog.Info(string.Format("Service successfully started.")); tasks.Add(Task.Run(() => { StartTasks(); })); return true; } //Start public bool Stop(HostControl hostControl) { Program.isExiting = true; Task.WaitAll(tasks.ToArray()); tasks.Clear(); appLog.Debug("++ Service shutdown successful."); return true; } //Stop void StartTasks() { var rn = new Random(); try { foreach (var f0 in Directory.GetFiles(Program.AppPath + "Procs", "*.exe")) { var px = new MdlProcess() { ProgramToRun = f0, ExecutableName = Path.GetFileName(f0) }; var si = new ProcessStartInfo() { FileName = px.ProgramToRun, Verb = "OPEN", WorkingDirectory = Path.GetDirectoryName(f0), RedirectStandardError = true, UseShellExecute = false }; px.theProcess = new Process() { StartInfo = si }; px.theProcess.Start(); px.StartTime = DateTimeOffset.Now; px.IPCChannel = string.Format("kmProcess_IPCChannel.{0}", px.theProcess.Id); ChildProcesses.Add(px); appLog.DebugFormat("++ Launched Process '{0}' having PID of {1}", px.ExecutableName, px.theProcess.Id); } } catch (Exception ex) { appLog.Error(ex); } while (!Program.isExiting) { Thread.Sleep(rn.Next(900, 3000)); } // First, try to stop them all gracefully... foreach (var px in ChildProcesses) { appLog.DebugFormat("Sending request to shut down process '{0}'...", px.ExecutableName); try { using (var ch = new NamedPipeClientStream(".", px.IPCChannel, PipeDirection.Out)) { if (ch.ConnectAsync().Wait(3000)) { using (var w = new StreamWriter(ch)) { w.WriteLine("EXIT_NOW"); w.Flush(); } } } } catch (Exception ex) { appLog.Error(ex); } Thread.Sleep(100); } // Anyone still running? Use the hammer. foreach (var px in ChildProcesses) { if (px.theProcess != null) { if (!px.theProcess.HasExited) Thread.Sleep(5000); //Give it a little time to gracefully exit if (!px.theProcess.HasExited) { try { px.theProcess.Kill(); } catch { } } try { px.theProcess.Dispose(); } catch { } } } ChildProcesses.Clear(); } #region "Private Models and Such" class MdlProcess { public string ProgramToRun { get; set; } = string.Empty; public string ExecutableName { get; set; } = string.Empty; public Process? theProcess { get; set; } = null; public DateTimeOffset StartTime { get; set; } = DateTimeOffset.Now; /// /// Used for interprocess communications /// public string IPCChannel { get; set; } = string.Empty; } #endregion }