diff --git a/src/WinSW.Core/Native/Service.cs b/src/WinSW.Core/Native/Service.cs index 47915cf..e889c10 100644 --- a/src/WinSW.Core/Native/Service.cs +++ b/src/WinSW.Core/Native/Service.cs @@ -139,6 +139,24 @@ namespace WinSW.Native internal Service(IntPtr handle) => this.handle = handle; + internal unsafe int ProcessId + { + get + { + if (!QueryServiceStatusEx( + this.handle, + ServiceStatusType.ProcessInfo, + out SERVICE_STATUS_PROCESS status, + sizeof(SERVICE_STATUS_PROCESS), + out _)) + { + Throw.Command.Win32Exception("Failed to query service status."); + } + + return status.CurrentState == ServiceControllerStatus.Running ? status.ProcessId : -1; + } + } + /// internal ServiceControllerStatus Status { diff --git a/src/WinSW.Core/Native/ServiceApis.cs b/src/WinSW.Core/Native/ServiceApis.cs index 0f7877a..ae59139 100644 --- a/src/WinSW.Core/Native/ServiceApis.cs +++ b/src/WinSW.Core/Native/ServiceApis.cs @@ -65,6 +65,14 @@ namespace WinSW.Native [DllImport(Libraries.Advapi32, SetLastError = true)] internal static extern bool QueryServiceStatus(IntPtr serviceHandle, out SERVICE_STATUS serviceStatus); + [DllImport(Libraries.Advapi32, SetLastError = true)] + internal static extern bool QueryServiceStatusEx( + IntPtr serviceHandle, + ServiceStatusType infoLevel, + out SERVICE_STATUS_PROCESS buffer, + int bufferSize, + out int bytesNeeded); + [DllImport(Libraries.Advapi32, SetLastError = true)] internal static extern bool SetServiceObjectSecurity(IntPtr serviceHandle, SecurityInfos securityInformation, byte[] securityDescriptor); @@ -145,6 +153,12 @@ namespace WinSW.Native MODIFY_BOOT_CONFIG, } + // SC_STATUS_ + internal enum ServiceStatusType + { + ProcessInfo = 0, + } + internal struct SERVICE_DELAYED_AUTO_START_INFO { public bool DelayedAutostart; @@ -182,5 +196,18 @@ namespace WinSW.Native public int CheckPoint; public int WaitHint; } + + internal struct SERVICE_STATUS_PROCESS + { + public ServiceType ServiceType; + public ServiceControllerStatus CurrentState; + public int ControlsAccepted; + public int Win32ExitCode; + public int ServiceSpecificExitCode; + public int CheckPoint; + public int WaitHint; + public int ProcessId; + public int ServiceFlags; + } } } diff --git a/src/WinSW.Core/Util/ProcessExtensions.cs b/src/WinSW.Core/Util/ProcessExtensions.cs index f76a369..f89bdc8 100644 --- a/src/WinSW.Core/Util/ProcessExtensions.cs +++ b/src/WinSW.Core/Util/ProcessExtensions.cs @@ -15,20 +15,72 @@ namespace WinSW.Util { Stop(process, stopTimeout); - foreach (Process child in GetDescendants(process)) + foreach (Process child in GetChildren(process)) { - StopTree(child, stopTimeout); + using (child) + { + StopTree(child, stopTimeout); + } } } internal static void StopDescendants(this Process process, TimeSpan stopTimeout) { - foreach (Process child in GetDescendants(process)) + foreach (Process child in GetChildren(process)) { - StopTree(child, stopTimeout); + using (child) + { + StopTree(child, stopTimeout); + } } } + internal static unsafe List GetChildren(this Process process) + { + DateTime startTime = process.StartTime; + int processId = process.Id; + + var children = new List(); + + foreach (Process other in Process.GetProcesses()) + { + try + { + if (other.StartTime <= startTime) + { + goto Next; + } + + IntPtr handle = other.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 '{other.Format()}'."); + children.Add(other); + continue; + } + + Next: + other.Dispose(); + } + catch (Exception e) when (e is InvalidOperationException || e is Win32Exception) + { + other.Dispose(); + } + } + + return children; + } + private static void Stop(Process process, TimeSpan stopTimeout) { Logger.Info("Stopping process " + process.Id); @@ -61,51 +113,5 @@ namespace WinSW.Util // TODO: Propagate error if process kill fails? Currently we use the legacy behavior } - - private static unsafe List GetDescendants(Process root) - { - DateTime startTime = root.StartTime; - int processId = root.Id; - - var children = new List(); - - 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; - } } } diff --git a/src/WinSW/Program.cs b/src/WinSW/Program.cs index 93e188d..fd13eb7 100644 --- a/src/WinSW/Program.cs +++ b/src/WinSW/Program.cs @@ -26,6 +26,7 @@ using log4net.Core; using log4net.Layout; using WinSW.Logging; using WinSW.Native; +using WinSW.Util; using Process = System.Diagnostics.Process; using TimeoutException = System.ServiceProcess.TimeoutException; @@ -238,6 +239,22 @@ namespace WinSW root.Add(refresh); } + { + var dev = new Command("dev"); + + dev.Add(config); + dev.Add(noElevate); + + root.Add(dev); + + var ps = new Command("ps") + { + Handler = CommandHandler.Create(DevPs), + }; + + dev.Add(ps); + } + return new CommandLineBuilder(root) // see UseDefaults @@ -774,6 +791,58 @@ namespace WinSW } } + void DevPs(string? pathToConfig, bool noElevate) + { + XmlServiceConfig config = XmlServiceConfig.Create(pathToConfig); + + if (!elevated) + { + Elevate(noElevate); + return; + } + + using ServiceManager scm = ServiceManager.Open(); + using Service sc = scm.OpenService(config.Id); + + int processId = sc.ProcessId; + if (processId >= 0) + { + const string Vertical = " \u2502 "; + const string Corner = " \u2514\u2500"; + const string Cross = " \u251c\u2500"; + const string Space = " "; + + using Process process = Process.GetProcessById(processId); + Draw(process, string.Empty, true); + + static void Draw(Process process, string indentation, bool isLastChild) + { + Console.Write(indentation); + + if (isLastChild) + { + Console.Write(Corner); + indentation += Space; + } + else + { + Console.Write(Cross); + indentation += Vertical; + } + + Console.WriteLine(process.Format()); + + List children = process.GetChildren(); + int count = children.Count; + for (int i = 0; i < count; i++) + { + using Process child = children[i]; + Draw(child, indentation, i == count - 1); + } + } + } + } + // [DoesNotReturn] static void Elevate(bool noElevate) {