diff --git a/src/libraries/System.ServiceProcess.ServiceController/src/System/ServiceProcess/ServiceBase.cs b/src/libraries/System.ServiceProcess.ServiceController/src/System/ServiceProcess/ServiceBase.cs index 314b13ff475a0..c8dcaa55b8936 100644 --- a/src/libraries/System.ServiceProcess.ServiceController/src/System/ServiceProcess/ServiceBase.cs +++ b/src/libraries/System.ServiceProcess.ServiceController/src/System/ServiceProcess/ServiceBase.cs @@ -1,26 +1,22 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; using System.ComponentModel; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; -using System.IO; -using System.Reflection; using System.Runtime.ExceptionServices; using System.Runtime.InteropServices; -using System.Security; using System.Threading; using static Interop.Advapi32; namespace System.ServiceProcess { - /// + /// /// Provides a base class for a service that will exist as part of a service application. /// must be derived when creating a new service class. - /// + /// public class ServiceBase : Component { private SERVICE_STATUS _status; @@ -37,28 +33,27 @@ public class ServiceBase : Component private bool _initialized; private EventLog? _eventLog; - /// - /// - /// Indicates the maximum size for a service name. - /// - /// + /// + /// Indicates the maximum size for a service name. + /// public const int MaxNameLength = 80; - /// - /// Creates a new instance of the class. - /// + /// + /// Creates a new instance of the class. + /// public ServiceBase() { _acceptedCommands = AcceptOptions.ACCEPT_STOP; - ServiceName = ""; + ServiceName = string.Empty; AutoLog = true; } - /// + /// /// When this method is called from OnStart, OnStop, OnPause or OnContinue, /// the specified wait hint is passed to the /// Service Control Manager to avoid having the service marked as not responding. - /// + /// + /// public unsafe void RequestAdditionalTime(int milliseconds) { fixed (SERVICE_STATUS* pStatus = &_status) @@ -77,16 +72,16 @@ public unsafe void RequestAdditionalTime(int milliseconds) } } - /// - /// Indicates whether to report Start, Stop, Pause, and Continue commands in the event - /// + /// + /// Indicates whether to report Start, Stop, Pause, and Continue commands in the event. + /// [DefaultValue(true)] public bool AutoLog { get; set; } - /// + /// /// The termination code for the service. Set this to a non-zero value before /// stopping to indicate an error to the Service Control Manager. - /// + /// public int ExitCode { get @@ -99,10 +94,10 @@ public int ExitCode } } - /// + /// /// Indicates whether the service can be handle notifications on /// computer power status changes. - /// + /// [DefaultValue(false)] public bool CanHandlePowerEvent { @@ -126,9 +121,9 @@ public bool CanHandlePowerEvent } } - /// + /// /// Indicates whether the service can handle Terminal Server session change events. - /// + /// [DefaultValue(false)] public bool CanHandleSessionChangeEvent { @@ -152,10 +147,9 @@ public bool CanHandleSessionChangeEvent } } - /// - /// Indicates whether the service can be paused - /// and resumed. - /// + /// + /// Indicates whether the service can be paused and resumed. + /// [DefaultValue(false)] public bool CanPauseAndContinue { @@ -179,10 +173,9 @@ public bool CanPauseAndContinue } } - /// - /// Indicates whether the service should be notified when - /// the system is shutting down. - /// + /// + /// Indicates whether the service should be notified when the system is shutting down. + /// [DefaultValue(false)] public bool CanShutdown { @@ -206,10 +199,9 @@ public bool CanShutdown } } - /// - /// Indicates whether the service can be - /// stopped once it has started. - /// + /// + /// Indicates whether the service can be stopped once it has started. + /// [DefaultValue(true)] public bool CanStop { @@ -233,9 +225,9 @@ public bool CanStop } } - /// + /// /// can be used to write notification of service command calls, such as Start and Stop, to the Application event log. This property is read-only. - /// + /// [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public virtual EventLog EventLog { @@ -262,9 +254,9 @@ protected IntPtr ServiceHandle } } - /// - /// Indicates the short name used to identify the service to the system. - /// + /// + /// Indicates the short name used to identify the service to the system. + /// public string ServiceName { get @@ -304,12 +296,12 @@ internal static bool ValidServiceName(string serviceName) return true; } - /// + /// /// Disposes of the resources (other than memory ) used by /// the . /// This is called from when all /// services in the process have entered the SERVICE_STOPPED state. - /// + /// protected override void Dispose(bool disposing) { _nameFrozen = false; @@ -318,60 +310,60 @@ protected override void Dispose(bool disposing) base.Dispose(disposing); } - /// + /// /// When implemented in a /// derived class, /// executes when a Continue command is sent to the service /// by the /// Service Control Manager. Specifies the actions to take when a /// service resumes normal functioning after being paused. - /// + /// protected virtual void OnContinue() { } - /// + /// /// When implemented in a /// derived class, executes when a Pause command is sent /// to /// the service by the Service Control Manager. Specifies the /// actions to take when a service pauses. - /// + /// protected virtual void OnPause() { } - /// + /// /// /// When implemented in a derived class, executes when the computer's /// power status has changed. /// - /// + /// protected virtual bool OnPowerEvent(PowerBroadcastStatus powerStatus) { return true; } - /// + /// /// When implemented in a derived class, /// executes when a Terminal Server session change event is received. - /// + /// protected virtual void OnSessionChange(SessionChangeDescription changeDescription) { } - /// + /// /// When implemented in a derived class, /// executes when the system is shutting down. /// Specifies what should /// happen just prior /// to the system shutting down. - /// + /// protected virtual void OnShutdown() { } - /// + /// /// When implemented in a /// derived class, executes when a Start command is sent /// to the service by the Service @@ -385,18 +377,18 @@ protected virtual void OnShutdown() /// OnStart never be called if you use the SCM to start the service? What about /// services that start automatically at boot-up? /// - /// + /// protected virtual void OnStart(string[] args) { } - /// + /// /// When implemented in a /// derived class, executes when a Stop command is sent to the /// service by the Service Control Manager. Specifies the actions to take when a /// service stops /// running. - /// + /// protected virtual void OnStop() { } @@ -563,7 +555,7 @@ private unsafe void DeferredShutdown() } } - /// + /// /// When implemented in a derived class, /// executes when a custom command is passed to /// the service. Specifies the actions to take when @@ -581,16 +573,16 @@ private unsafe void DeferredShutdown() /// second paragraph below--what, if any, contact does the SCM have with a /// custom command? /// - /// + /// protected virtual void OnCustomCommand(int command) { } - /// + /// /// Provides the main entry point for an executable that /// contains multiple associated services. Loads the specified services into memory so they can be /// started. - /// + /// public static unsafe void Run(ServiceBase[] services) { if (services == null || services.Length == 0) @@ -621,13 +613,10 @@ public static unsafe void Run(ServiceBase[] services) foreach (ServiceBase service in services) { - if (service._startFailedException != null) - { - // Propagate exceptions throw during OnStart. - // Note that this same exception is also thrown from ServiceMainCallback - // (so SCM can see it as well). - service._startFailedException.Throw(); - } + // Propagate exceptions throw during OnStart. + // Note that this same exception is also thrown from ServiceMainCallback + // (so SCM can see it as well). + service._startFailedException?.Throw(); } string errorMessage = string.Empty; @@ -660,12 +649,12 @@ public static unsafe void Run(ServiceBase[] services) } } - /// + /// /// Provides the main /// entry point for an executable that contains a single /// service. Loads the service into memory so it can be /// started. - /// + /// public static void Run(ServiceBase service) { if (service == null) @@ -703,7 +692,7 @@ private void Initialize(bool multipleServices) _status.checkPoint = 0; _status.waitHint = 0; - _mainCallback = this.ServiceMainCallback; + _mainCallback = ServiceMainCallback; _commandCallbackEx = this.ServiceCommandCallbackEx; _initialized = true; @@ -752,12 +741,12 @@ private int ServiceCommandCallbackEx(int command, int eventType, IntPtr eventDat return 0; } - /// + /// /// Command Handler callback is called by NT . /// Need to take specific action in response to each /// command message. There is usually no need to override this method. /// Instead, override OnStart, OnStop, OnCustomCommand, etc. - /// + /// /// private unsafe void ServiceCommandCallback(int command) { @@ -861,11 +850,11 @@ private void ServiceQueuedMainCallback(object state) _startCompletedSignal!.Set(); } - /// + /// /// ServiceMain callback is called by NT . /// It is expected that we register the command handler, /// and start the service at this point. - /// + /// /// [EditorBrowsable(EditorBrowsableState.Never)] public unsafe void ServiceMainCallback(int argCount, IntPtr argPointer) diff --git a/src/libraries/System.ServiceProcess.ServiceController/src/System/ServiceProcess/ServiceController.cs b/src/libraries/System.ServiceProcess.ServiceController/src/System/ServiceProcess/ServiceController.cs index 72eebc88a9ca5..ba58f6174f374 100644 --- a/src/libraries/System.ServiceProcess.ServiceController/src/System/ServiceProcess/ServiceController.cs +++ b/src/libraries/System.ServiceProcess.ServiceController/src/System/ServiceProcess/ServiceController.cs @@ -2,888 +2,971 @@ // The .NET Foundation licenses this file to you under the MIT license. using Microsoft.Win32.SafeHandles; -using System; using System.Collections.Generic; using System.ComponentModel; -using System.Diagnostics; using System.Diagnostics.CodeAnalysis; -using System.Globalization; using System.Runtime.InteropServices; -using System.Security; using System.Text; using System.Threading; namespace System.ServiceProcess { - /// This class represents an NT service. It allows you to connect to a running or stopped service - /// and manipulate it or get information about it. - [Designer("System.ServiceProcess.Design.ServiceControllerDesigner, System.Design, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")] - public class ServiceController : Component - { - private string _machineName; // Never null - private readonly ManualResetEvent _waitForStatusSignal = new ManualResetEvent(false); - private const string DefaultMachineName = "."; - - private string? _name; - private string? _eitherName; - private string? _displayName; - - private int _commandsAccepted; - private bool _statusGenerated; - private bool _startTypeInitialized; - private int _type; - private bool _disposed; - private SafeServiceHandle? _serviceManagerHandle; - private ServiceControllerStatus _status; - private ServiceController[]? _dependentServices; - private ServiceController[]? _servicesDependedOn; - private ServiceStartMode _startType; - - public ServiceController() - { - _machineName = DefaultMachineName; - _type = Interop.Advapi32.ServiceTypeOptions.SERVICE_TYPE_ALL; - } - - /// Creates a ServiceController object, based on service name. - public ServiceController(string name) - : this(name, DefaultMachineName) - { - } - - /// Creates a ServiceController object, based on machine and service name. - public ServiceController(string name, string machineName) - { - if (!CheckMachineName(machineName)) - throw new ArgumentException(SR.Format(SR.BadMachineName, machineName)); - - if (string.IsNullOrEmpty(name)) - throw new ArgumentException(SR.Format(SR.InvalidParameter, nameof(name), name)); - - _machineName = machineName; - _eitherName = name; - _type = Interop.Advapi32.ServiceTypeOptions.SERVICE_TYPE_ALL; - } + /// This class represents an NT service. It allows you to connect to a running or stopped service + /// and manipulate it or get information about it. + [Designer("System.ServiceProcess.Design.ServiceControllerDesigner, System.Design, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")] + public class ServiceController : Component + { + private string _machineName; // Never null + private readonly ManualResetEvent _waitForStatusSignal = new ManualResetEvent(false); + private const string DefaultMachineName = "."; + + private string? _name; + private string? _eitherName; + private string? _displayName; + + private int _commandsAccepted; + private bool _statusGenerated; + private bool _startTypeInitialized; + private int _type; + private bool _disposed; + private SafeServiceHandle? _serviceManagerHandle; + private ServiceControllerStatus _status; + private ServiceController[]? _dependentServices; + private ServiceController[]? _servicesDependedOn; + private ServiceStartMode _startType; + + public ServiceController() + { + _machineName = DefaultMachineName; + _type = Interop.Advapi32.ServiceTypeOptions.SERVICE_TYPE_ALL; + } + /// + /// Creates a ServiceController object, based on service name. + /// + /// Name of the service + public ServiceController(string name) + : this(name, DefaultMachineName) + { + } - /// Used by the GetServices and GetDevices methods. Avoids duplicating work by the static - /// methods and our own GenerateInfo(). - private ServiceController(string machineName, Interop.Advapi32.ENUM_SERVICE_STATUS status) - { - if (!CheckMachineName(machineName)) - throw new ArgumentException(SR.Format(SR.BadMachineName, machineName)); - - _machineName = machineName; - _name = status.serviceName; - _displayName = status.displayName; - _commandsAccepted = status.controlsAccepted; - _status = (ServiceControllerStatus)status.currentState; - _type = status.serviceType; - _statusGenerated = true; - } - /// Used by the GetServicesInGroup method. - private ServiceController(string machineName, Interop.Advapi32.ENUM_SERVICE_STATUS_PROCESS status) - { - if (!CheckMachineName(machineName)) - throw new ArgumentException(SR.Format(SR.BadMachineName, machineName)); - - _machineName = machineName; - _name = status.serviceName; - _displayName = status.displayName; - _commandsAccepted = status.controlsAccepted; - _status = (ServiceControllerStatus)status.currentState; - _type = status.serviceType; - _statusGenerated = true; - } + /// + /// Creates a ServiceController object, based on machine and service name. + /// + /// Name of the service + /// Name of the machine + public ServiceController(string name, string machineName) + { + if (!CheckMachineName(machineName)) + throw new ArgumentException(SR.Format(SR.BadMachineName, machineName)); - /// Tells if the service referenced by this object can be paused. - public bool CanPauseAndContinue - { - get - { - GenerateStatus(); - return (_commandsAccepted & Interop.Advapi32.AcceptOptions.ACCEPT_PAUSE_CONTINUE) != 0; - } - } + if (string.IsNullOrEmpty(name)) + throw new ArgumentException(SR.Format(SR.InvalidParameter, nameof(name), name)); + _machineName = machineName; + _eitherName = name; + _type = Interop.Advapi32.ServiceTypeOptions.SERVICE_TYPE_ALL; + } - /// Tells if the service is notified when system shutdown occurs. - public bool CanShutdown - { - get - { - GenerateStatus(); - return (_commandsAccepted & Interop.Advapi32.AcceptOptions.ACCEPT_SHUTDOWN) != 0; - } - } + private ServiceController(string machineName, Interop.Advapi32.ENUM_SERVICE_STATUS status) + { + if (!CheckMachineName(machineName)) + throw new ArgumentException(SR.Format(SR.BadMachineName, machineName)); + + _machineName = machineName; + _name = status.serviceName; + _displayName = status.displayName; + _commandsAccepted = status.controlsAccepted; + _status = (ServiceControllerStatus)status.currentState; + _type = status.serviceType; + _statusGenerated = true; + } - /// Tells if the service referenced by this object can be stopped. - public bool CanStop - { - get - { - GenerateStatus(); - return (_commandsAccepted & Interop.Advapi32.AcceptOptions.ACCEPT_STOP) != 0; - } - } + /// + /// Used by the GetServicesInGroup method. + /// + /// Name of the machine + /// Service process status + private ServiceController(string machineName, Interop.Advapi32.ENUM_SERVICE_STATUS_PROCESS status) + { + if (!CheckMachineName(machineName)) + throw new ArgumentException(SR.Format(SR.BadMachineName, machineName)); + + _machineName = machineName; + _name = status.serviceName; + _displayName = status.displayName; + _commandsAccepted = status.controlsAccepted; + _status = (ServiceControllerStatus)status.currentState; + _type = status.serviceType; + _statusGenerated = true; + } - /// The descriptive name shown for this service in the Service applet. - public string DisplayName - { - get - { - if (string.IsNullOrEmpty(_displayName)) - GenerateNames(); - return _displayName; - } - set - { - if (value == null) - throw new ArgumentNullException(nameof(value)); + /// + /// Tells if the service referenced by this object can be paused. + /// + public bool CanPauseAndContinue + { + get + { + GenerateStatus(); + return (_commandsAccepted & Interop.Advapi32.AcceptOptions.ACCEPT_PAUSE_CONTINUE) != 0; + } + } - if (string.Equals(value, _displayName, StringComparison.OrdinalIgnoreCase)) - { - // they're just changing the casing. No need to close. - _displayName = value; - return; - } + /// + /// Tells if the service is notified when system shutdown occurs. + /// + public bool CanShutdown + { + get + { + GenerateStatus(); + return (_commandsAccepted & Interop.Advapi32.AcceptOptions.ACCEPT_SHUTDOWN) != 0; + } + } - Close(); - _displayName = value; - _name = ""; - } - } + /// + /// Tells if the service referenced by this object can be stopped. + /// + public bool CanStop + { + get + { + GenerateStatus(); + return (_commandsAccepted & Interop.Advapi32.AcceptOptions.ACCEPT_STOP) != 0; + } + } - /// The set of services that depend on this service. These are the services that will be stopped if - /// this service is stopped. - public ServiceController[] DependentServices - { - get - { - if (_dependentServices == null) - { - using var serviceHandle = GetServiceHandle(Interop.Advapi32.ServiceOptions.SERVICE_ENUMERATE_DEPENDENTS); - // figure out how big a buffer we need to get the info - int bytesNeeded = 0; - int numEnumerated = 0; - bool result = Interop.Advapi32.EnumDependentServices(serviceHandle, Interop.Advapi32.ServiceState.SERVICE_STATE_ALL, IntPtr.Zero, 0, - ref bytesNeeded, ref numEnumerated); - if (result) - { - _dependentServices = Array.Empty(); - return _dependentServices; - } - - int lastError = Marshal.GetLastWin32Error(); - if (lastError != Interop.Errors.ERROR_MORE_DATA) - throw new Win32Exception(lastError); - - // allocate the buffer - IntPtr enumBuffer = Marshal.AllocHGlobal((IntPtr)bytesNeeded); - - try - { - // get all the info - result = Interop.Advapi32.EnumDependentServices(serviceHandle, Interop.Advapi32.ServiceState.SERVICE_STATE_ALL, enumBuffer, bytesNeeded, - ref bytesNeeded, ref numEnumerated); - if (!result) - throw new Win32Exception(); - - // for each of the entries in the buffer, create a new ServiceController object. - _dependentServices = new ServiceController[numEnumerated]; - for (int i = 0; i < numEnumerated; i++) - { - Interop.Advapi32.ENUM_SERVICE_STATUS status = new Interop.Advapi32.ENUM_SERVICE_STATUS(); - IntPtr structPtr = (IntPtr)((long)enumBuffer + (i * Marshal.SizeOf())); - Marshal.PtrToStructure(structPtr, status); - _dependentServices[i] = new ServiceController(_machineName, status); - } - } - finally - { - Marshal.FreeHGlobal(enumBuffer); - } - } + /// + /// The descriptive name shown for this service in the Service applet. + /// + public string DisplayName + { + get + { + if (string.IsNullOrEmpty(_displayName)) + GenerateNames(); + return _displayName; + } + set + { + if (value == null) + throw new ArgumentNullException(nameof(value)); + + if (string.Equals(value, _displayName, StringComparison.OrdinalIgnoreCase)) + { + // they're just changing the casing. No need to close. + _displayName = value; + return; + } + + Close(); + _displayName = value; + _name = ""; + } + } - return _dependentServices; - } - } + /// + /// The set of services that depend on this service. These are the services that will be stopped if this service is stopped. + /// + public ServiceController[] DependentServices + { + get + { + if (_dependentServices == null) + { + using var serviceHandle = GetServiceHandle(Interop.Advapi32.ServiceOptions.SERVICE_ENUMERATE_DEPENDENTS); + // figure out how big a buffer we need to get the info + int bytesNeeded = 0; + int numEnumerated = 0; + bool result = Interop.Advapi32.EnumDependentServices(serviceHandle, Interop.Advapi32.ServiceState.SERVICE_STATE_ALL, IntPtr.Zero, 0, + ref bytesNeeded, ref numEnumerated); + if (result) + { + _dependentServices = Array.Empty(); + return _dependentServices; + } + + int lastError = Marshal.GetLastWin32Error(); + if (lastError != Interop.Errors.ERROR_MORE_DATA) + throw new Win32Exception(lastError); + + // allocate the buffer + IntPtr enumBuffer = Marshal.AllocHGlobal((IntPtr)bytesNeeded); + + try + { + // get all the info + result = Interop.Advapi32.EnumDependentServices(serviceHandle, Interop.Advapi32.ServiceState.SERVICE_STATE_ALL, enumBuffer, bytesNeeded, + ref bytesNeeded, ref numEnumerated); + if (!result) + throw new Win32Exception(); - /// The name of the machine on which this service resides. - public string MachineName - { - get - { - return _machineName; - } - set + // for each of the entries in the buffer, create a new ServiceController object. + _dependentServices = new ServiceController[numEnumerated]; + for (int i = 0; i < numEnumerated; i++) { - if (!CheckMachineName(value)) - throw new ArgumentException(SR.Format(SR.BadMachineName, value)); - - if (string.Equals(_machineName, value, StringComparison.OrdinalIgnoreCase)) - { - // no need to close, because the most they're changing is the - // casing. - _machineName = value; - return; - } - - Close(); - _machineName = value; + Interop.Advapi32.ENUM_SERVICE_STATUS status = new Interop.Advapi32.ENUM_SERVICE_STATUS(); + IntPtr structPtr = (IntPtr)((long)enumBuffer + (i * Marshal.SizeOf())); + Marshal.PtrToStructure(structPtr, status); + _dependentServices[i] = new ServiceController(_machineName, status); } + } + finally + { + Marshal.FreeHGlobal(enumBuffer); + } } - /// Returns the short name of the service referenced by this object. - public string ServiceName - { - get - { - if (string.IsNullOrEmpty(_name)) - GenerateNames(); - return _name; - } - set - { - if (value == null) - throw new ArgumentNullException(nameof(value)); - - if (string.Equals(value, _name, StringComparison.OrdinalIgnoreCase)) - { - // they might be changing the casing, but the service we refer to - // is the same. No need to close. - _name = value; - return; - } - - if (!ServiceBase.ValidServiceName(value)) - throw new ArgumentException(SR.Format(SR.ServiceName, value, ServiceBase.MaxNameLength.ToString())); - - Close(); - _name = value; - _displayName = ""; - } - } - - public unsafe ServiceController[] ServicesDependedOn - { - get - { - if (_servicesDependedOn != null) - return _servicesDependedOn; + return _dependentServices; + } + } - using var serviceHandle = GetServiceHandle(Interop.Advapi32.ServiceOptions.SERVICE_QUERY_CONFIG); - bool success = Interop.Advapi32.QueryServiceConfig(serviceHandle, IntPtr.Zero, 0, out int bytesNeeded); - if (success) - { - _servicesDependedOn = Array.Empty(); - return _servicesDependedOn; - } + /// + /// The name of the machine on which this service resides. + /// + public string MachineName + { + get + { + return _machineName; + } + set + { + if (!CheckMachineName(value)) + throw new ArgumentException(SR.Format(SR.BadMachineName, value)); + + if (string.Equals(_machineName, value, StringComparison.OrdinalIgnoreCase)) + { + // no need to close, because the most they're changing is the + // casing. + _machineName = value; + return; + } + + Close(); + _machineName = value; + } + } - int lastError = Marshal.GetLastWin32Error(); - if (lastError != Interop.Errors.ERROR_INSUFFICIENT_BUFFER) - throw new Win32Exception(lastError); + /// + /// Returns the short name of the service referenced by this object. + /// + public string ServiceName + { + get + { + if (string.IsNullOrEmpty(_name)) + GenerateNames(); + return _name; + } + set + { + if (value == null) + throw new ArgumentNullException(nameof(value)); + + if (string.Equals(value, _name, StringComparison.OrdinalIgnoreCase)) + { + // they might be changing the casing, but the service we refer to + // is the same. No need to close. + _name = value; + return; + } + + if (!ServiceBase.ValidServiceName(value)) + throw new ArgumentException(SR.Format(SR.ServiceName, value, ServiceBase.MaxNameLength.ToString())); + + Close(); + _name = value; + _displayName = ""; + } + } - // get the info - IntPtr bufPtr = Marshal.AllocHGlobal((IntPtr)bytesNeeded); - try + /// + /// A set of services on which the given service object is depend upon. + /// + public unsafe ServiceController[] ServicesDependedOn + { + get + { + if (_servicesDependedOn != null) + return _servicesDependedOn; + + using var serviceHandle = GetServiceHandle(Interop.Advapi32.ServiceOptions.SERVICE_QUERY_CONFIG); + bool success = Interop.Advapi32.QueryServiceConfig(serviceHandle, IntPtr.Zero, 0, out int bytesNeeded); + if (success) + { + _servicesDependedOn = Array.Empty(); + return _servicesDependedOn; + } + + int lastError = Marshal.GetLastWin32Error(); + if (lastError != Interop.Errors.ERROR_INSUFFICIENT_BUFFER) + throw new Win32Exception(lastError); + + // get the info + IntPtr bufPtr = Marshal.AllocHGlobal((IntPtr)bytesNeeded); + try + { + success = Interop.Advapi32.QueryServiceConfig(serviceHandle, bufPtr, bytesNeeded, out bytesNeeded); + if (!success) + throw new Win32Exception(Marshal.GetLastWin32Error()); + + Interop.Advapi32.QUERY_SERVICE_CONFIG config = new Interop.Advapi32.QUERY_SERVICE_CONFIG(); + Marshal.PtrToStructure(bufPtr, config); + Dictionary? dependencyHash = null; + + char* dependencyChar = config.lpDependencies; + if (dependencyChar != null) + { + // lpDependencies points to the start of multiple null-terminated strings. The list is + // double-null terminated. + int length = 0; + dependencyHash = new Dictionary(); + while (*(dependencyChar + length) != '\0') + { + length++; + if (*(dependencyChar + length) == '\0') + { + string dependencyNameStr = new string(dependencyChar, 0, length); + dependencyChar = dependencyChar + length + 1; + length = 0; + if (dependencyNameStr.StartsWith("+", StringComparison.Ordinal)) { - success = Interop.Advapi32.QueryServiceConfig(serviceHandle, bufPtr, bytesNeeded, out bytesNeeded); - if (!success) - throw new Win32Exception(Marshal.GetLastWin32Error()); - - Interop.Advapi32.QUERY_SERVICE_CONFIG config = new Interop.Advapi32.QUERY_SERVICE_CONFIG(); - Marshal.PtrToStructure(bufPtr, config); - Dictionary? dependencyHash = null; - - char* dependencyChar = config.lpDependencies; - if (dependencyChar != null) - { - // lpDependencies points to the start of multiple null-terminated strings. The list is - // double-null terminated. - int length = 0; - dependencyHash = new Dictionary(); - while (*(dependencyChar + length) != '\0') - { - length++; - if (*(dependencyChar + length) == '\0') - { - string dependencyNameStr = new string(dependencyChar, 0, length); - dependencyChar = dependencyChar + length + 1; - length = 0; - if (dependencyNameStr.StartsWith("+", StringComparison.Ordinal)) - { - // this entry is actually a service load group - Interop.Advapi32.ENUM_SERVICE_STATUS_PROCESS[] loadGroup = GetServicesInGroup(_machineName, dependencyNameStr.Substring(1)); - foreach (Interop.Advapi32.ENUM_SERVICE_STATUS_PROCESS groupMember in loadGroup) - { - if (!dependencyHash.ContainsKey(groupMember.serviceName!)) - dependencyHash.Add(groupMember.serviceName!, new ServiceController(MachineName, groupMember)); - } - } - else - { - if (!dependencyHash.ContainsKey(dependencyNameStr)) - dependencyHash.Add(dependencyNameStr, new ServiceController(dependencyNameStr, MachineName)); - } - } - } - } - - if (dependencyHash != null) - { - _servicesDependedOn = new ServiceController[dependencyHash.Count]; - dependencyHash.Values.CopyTo(_servicesDependedOn, 0); - } - else - { - _servicesDependedOn = Array.Empty(); - } - - return _servicesDependedOn; + // this entry is actually a service load group + Interop.Advapi32.ENUM_SERVICE_STATUS_PROCESS[] loadGroup = GetServicesInGroup(_machineName, dependencyNameStr.Substring(1)); + foreach (Interop.Advapi32.ENUM_SERVICE_STATUS_PROCESS groupMember in loadGroup) + { + if (!dependencyHash.ContainsKey(groupMember.serviceName!)) + dependencyHash.Add(groupMember.serviceName!, new ServiceController(MachineName, groupMember)); + } } - finally + else { - Marshal.FreeHGlobal(bufPtr); + if (!dependencyHash.ContainsKey(dependencyNameStr)) + dependencyHash.Add(dependencyNameStr, new ServiceController(dependencyNameStr, MachineName)); } + } } - } - - public ServiceStartMode StartType - { - get - { - if (_startTypeInitialized) - return _startType; - - using var serviceHandle = GetServiceHandle(Interop.Advapi32.ServiceOptions.SERVICE_QUERY_CONFIG); - bool success = Interop.Advapi32.QueryServiceConfig(serviceHandle, IntPtr.Zero, 0, out int bytesNeeded); - - int lastError = Marshal.GetLastWin32Error(); - if (lastError != Interop.Errors.ERROR_INSUFFICIENT_BUFFER) - throw new Win32Exception(lastError); + } - // get the info - IntPtr bufPtr = Marshal.AllocHGlobal((IntPtr)bytesNeeded); - try - { - success = Interop.Advapi32.QueryServiceConfig(serviceHandle, bufPtr, bytesNeeded, out bytesNeeded); - if (!success) - throw new Win32Exception(Marshal.GetLastWin32Error()); - - Interop.Advapi32.QUERY_SERVICE_CONFIG config = new Interop.Advapi32.QUERY_SERVICE_CONFIG(); - Marshal.PtrToStructure(bufPtr, config); - - _startType = (ServiceStartMode)config.dwStartType; - _startTypeInitialized = true; - } - finally - { - Marshal.FreeHGlobal(bufPtr); - } + if (dependencyHash != null) + { + _servicesDependedOn = new ServiceController[dependencyHash.Count]; + dependencyHash.Values.CopyTo(_servicesDependedOn, 0); + } + else + { + _servicesDependedOn = Array.Empty(); + } - return _startType; - } + return _servicesDependedOn; } - - public SafeHandle ServiceHandle + finally { - get - { - return GetServiceHandle(Interop.Advapi32.ServiceOptions.SERVICE_ALL_ACCESS); - } + Marshal.FreeHGlobal(bufPtr); } + } + } - /// Gets the status of the service referenced by this object, e.g., Running, Stopped, etc. - public ServiceControllerStatus Status - { - get - { - GenerateStatus(); - return _status; - } - } + public ServiceStartMode StartType + { + get + { + if (_startTypeInitialized) + return _startType; - /// Gets the type of service that this object references. - public ServiceType ServiceType - { - get - { - GenerateStatus(); - return (ServiceType)_type; - } - } + using var serviceHandle = GetServiceHandle(Interop.Advapi32.ServiceOptions.SERVICE_QUERY_CONFIG); + bool success = Interop.Advapi32.QueryServiceConfig(serviceHandle, IntPtr.Zero, 0, out int bytesNeeded); - private static bool CheckMachineName(string value) - { - // string.Contains(char) is .NetCore2.1+ specific - return !string.IsNullOrWhiteSpace(value) && value.IndexOf('\\') == -1; - } + int lastError = Marshal.GetLastWin32Error(); + if (lastError != Interop.Errors.ERROR_INSUFFICIENT_BUFFER) + throw new Win32Exception(lastError); - /// - /// Closes the handle to the service manager, but does not - /// mark the class as disposed. - /// - /// - /// Violates design guidelines by not matching Dispose() -- matches .NET Framework - /// - public void Close() + // get the info + IntPtr bufPtr = Marshal.AllocHGlobal((IntPtr)bytesNeeded); + try { - if (_serviceManagerHandle != null) - { - _serviceManagerHandle.Dispose(); - _serviceManagerHandle = null; - } + success = Interop.Advapi32.QueryServiceConfig(serviceHandle, bufPtr, bytesNeeded, out bytesNeeded); + if (!success) + throw new Win32Exception(Marshal.GetLastWin32Error()); - _statusGenerated = false; - _startTypeInitialized = false; - _type = Interop.Advapi32.ServiceTypeOptions.SERVICE_TYPE_ALL; - } + Interop.Advapi32.QUERY_SERVICE_CONFIG config = new Interop.Advapi32.QUERY_SERVICE_CONFIG(); + Marshal.PtrToStructure(bufPtr, config); - /// - /// Closes the handle to the service manager, and disposes. - /// - protected override void Dispose(bool disposing) - { - Close(); - _disposed = true; - base.Dispose(disposing); + _startType = (ServiceStartMode)config.dwStartType; + _startTypeInitialized = true; } - - private unsafe void GenerateStatus() + finally { - if (!_statusGenerated) - { - using var serviceHandle = GetServiceHandle(Interop.Advapi32.ServiceOptions.SERVICE_QUERY_STATUS); - Interop.Advapi32.SERVICE_STATUS svcStatus = default; - bool success = Interop.Advapi32.QueryServiceStatus(serviceHandle, &svcStatus); - if (!success) - throw new Win32Exception(Marshal.GetLastWin32Error()); - - _commandsAccepted = svcStatus.controlsAccepted; - _status = (ServiceControllerStatus)svcStatus.currentState; - _type = svcStatus.serviceType; - _statusGenerated = true; - } + Marshal.FreeHGlobal(bufPtr); } - [MemberNotNull(nameof(_name))] - [MemberNotNull(nameof(_displayName))] - private void GenerateNames() - { - GetDataBaseHandleWithConnectAccess(); - - if (string.IsNullOrEmpty(_name)) - { - // Figure out the _name based on the information we have. - // We must either have _displayName or the constructor parameter _eitherName. - string? userGivenName = string.IsNullOrEmpty(_eitherName) ? _displayName : _eitherName; - - if (string.IsNullOrEmpty(userGivenName)) - throw new InvalidOperationException(SR.Format(SR.ServiceName, userGivenName, ServiceBase.MaxNameLength.ToString())); - - // Try it as a display name - string? result = GetServiceKeyName(_serviceManagerHandle, userGivenName); + return _startType; + } + } - if (result != null) - { - // Now we have both - _name = result; - _displayName = userGivenName; - _eitherName = null; - return; - } + public SafeHandle ServiceHandle + { + get + { + return GetServiceHandle(Interop.Advapi32.ServiceOptions.SERVICE_ALL_ACCESS); + } + } - // Try it as a service name - result = GetServiceDisplayName(_serviceManagerHandle, userGivenName); + /// + /// Gets the status of the service referenced by this object, e.g., Running, Stopped, etc. + /// + /// + /// Please see for more available status. + /// + public ServiceControllerStatus Status + { + get + { + GenerateStatus(); + return _status; + } + } - if (result == null) - { - throw new InvalidOperationException(SR.Format(SR.NoService, userGivenName, _machineName), new Win32Exception(Interop.Errors.ERROR_SERVICE_DOES_NOT_EXIST)); - } + /// + /// Gets the type of service that this object references. + /// + /// + /// Please see for available list of Service types. + /// + public ServiceType ServiceType + { + get + { + GenerateStatus(); + return (ServiceType)_type; + } + } - _name = userGivenName; - _displayName = result; - _eitherName = null; - } - else if (string.IsNullOrEmpty(_displayName)) - { - // We must have _name - string? result = GetServiceDisplayName(_serviceManagerHandle, _name); + private static bool CheckMachineName(string value) + { + // string.Contains(char) is .NetCore2.1+ specific + return !string.IsNullOrWhiteSpace(value) && value.IndexOf('\\') == -1; + } - if (result == null) - { - throw new InvalidOperationException(SR.Format(SR.NoService, _name, _machineName), new Win32Exception(Interop.Errors.ERROR_SERVICE_DOES_NOT_EXIST)); - } + /// + /// Closes the handle to the service manager, but does not + /// mark the class as disposed. + /// + /// + /// Violates design guidelines by not matching Dispose() -- matches .NET Framework + /// + public void Close() + { + if (_serviceManagerHandle != null) + { + _serviceManagerHandle.Dispose(); + _serviceManagerHandle = null; + } + + _statusGenerated = false; + _startTypeInitialized = false; + _type = Interop.Advapi32.ServiceTypeOptions.SERVICE_TYPE_ALL; + } - _displayName = result; - _eitherName = null; - } - } + /// + /// Closes the handle to the service manager, and disposes. + /// + protected override void Dispose(bool disposing) + { + Close(); + _disposed = true; + base.Dispose(disposing); + } - /// - /// Gets service name (key name) from service display name. - /// Returns null if service is not found. - /// - private unsafe string? GetServiceKeyName(SafeServiceHandle? SCMHandle, string serviceDisplayName) - { - var builder = new ValueStringBuilder(stackalloc char[256]); - int bufLen; - while (true) - { - bufLen = builder.Capacity; - fixed (char* c = builder) - { - if (Interop.Advapi32.GetServiceKeyName(SCMHandle, serviceDisplayName, c, ref bufLen)) - break; - } + private unsafe void GenerateStatus() + { + if (_statusGenerated) + { + return; + } + + using var serviceHandle = GetServiceHandle(Interop.Advapi32.ServiceOptions.SERVICE_QUERY_STATUS); + Interop.Advapi32.SERVICE_STATUS svcStatus = default; + bool success = Interop.Advapi32.QueryServiceStatus(serviceHandle, &svcStatus); + if (!success) + throw new Win32Exception(Marshal.GetLastWin32Error()); + + _commandsAccepted = svcStatus.controlsAccepted; + _status = (ServiceControllerStatus)svcStatus.currentState; + _type = svcStatus.serviceType; + _statusGenerated = true; + } - int lastError = Marshal.GetLastWin32Error(); - if (lastError == Interop.Errors.ERROR_SERVICE_DOES_NOT_EXIST) - { - return null; - } + [MemberNotNull(nameof(_name))] + [MemberNotNull(nameof(_displayName))] + private void GenerateNames() + { + GetDataBaseHandleWithConnectAccess(); - if (lastError != Interop.Errors.ERROR_INSUFFICIENT_BUFFER) - { - throw new InvalidOperationException(SR.Format(SR.NoService, serviceDisplayName, _machineName), new Win32Exception(lastError)); - } + if (string.IsNullOrEmpty(_name)) + { + // Figure out the _name based on the information we have. + // We must either have _displayName or the constructor parameter _eitherName. + string? userGivenName = string.IsNullOrEmpty(_eitherName) ? _displayName : _eitherName; - builder.EnsureCapacity(bufLen + 1); // Does not include null - } + if (string.IsNullOrEmpty(userGivenName)) + throw new InvalidOperationException(SR.Format(SR.ServiceName, userGivenName, ServiceBase.MaxNameLength.ToString())); - builder.Length = bufLen; - return builder.ToString(); - } + // Try it as a display name + string? result = GetServiceKeyName(_serviceManagerHandle, userGivenName); - private unsafe string? GetServiceDisplayName(SafeServiceHandle? scmHandle, string serviceName) + if (result != null) { - var builder = new ValueStringBuilder(4096); - int bufLen; - while (true) - { - bufLen = builder.Capacity; - fixed (char* c = builder) - { - if (Interop.Advapi32.GetServiceDisplayName(scmHandle, serviceName, c, ref bufLen)) - break; - } - - int lastError = Marshal.GetLastWin32Error(); - if (lastError == Interop.Errors.ERROR_SERVICE_DOES_NOT_EXIST) - { - return null; - } - else if (lastError != Interop.Errors.ERROR_INSUFFICIENT_BUFFER) - { - throw new InvalidOperationException(SR.Format(SR.NoService, serviceName, _machineName), new Win32Exception(lastError)); - } - - builder.EnsureCapacity(bufLen + 1); // Does not include null - } - - builder.Length = bufLen; - return builder.ToString(); + // Now we have both + _name = result; + _displayName = userGivenName; + _eitherName = null; + return; } - private static SafeServiceHandle GetDataBaseHandleWithAccess(string machineName, int serviceControlManagerAccess) - { - SafeServiceHandle? databaseHandle; - if (machineName.Equals(DefaultMachineName) || machineName.Length == 0) - { - databaseHandle = new SafeServiceHandle(Interop.Advapi32.OpenSCManager(null, null, serviceControlManagerAccess)); - } - else - { - databaseHandle = new SafeServiceHandle(Interop.Advapi32.OpenSCManager(machineName, null, serviceControlManagerAccess)); - } + // Try it as a service name + result = GetServiceDisplayName(_serviceManagerHandle, userGivenName); - if (databaseHandle.IsInvalid) - { - Exception inner = new Win32Exception(Marshal.GetLastWin32Error()); - throw new InvalidOperationException(SR.Format(SR.OpenSC, machineName), inner); - } - - return databaseHandle; + if (result == null) + { + throw new InvalidOperationException(SR.Format(SR.NoService, userGivenName, _machineName), new Win32Exception(Interop.Errors.ERROR_SERVICE_DOES_NOT_EXIST)); } - private void GetDataBaseHandleWithConnectAccess() - { - if (_disposed) - { - throw new ObjectDisposedException(GetType().Name); - } + _name = userGivenName; + _displayName = result; + _eitherName = null; + } + else if (string.IsNullOrEmpty(_displayName)) + { + // We must have _name + string? result = GetServiceDisplayName(_serviceManagerHandle, _name); - // get a handle to SCM with connect access and store it in serviceManagerHandle field. - if (_serviceManagerHandle == null) - { - _serviceManagerHandle = GetDataBaseHandleWithAccess(_machineName, Interop.Advapi32.ServiceControllerOptions.SC_MANAGER_CONNECT); - } + if (result == null) + { + throw new InvalidOperationException(SR.Format(SR.NoService, _name, _machineName), new Win32Exception(Interop.Errors.ERROR_SERVICE_DOES_NOT_EXIST)); } - public static ServiceController[] GetDevices() + _displayName = result; + _eitherName = null; + } + } + + /// + /// Gets service name (key name) from service display name. + /// Returns null if service is not found. + /// + private unsafe string? GetServiceKeyName(SafeServiceHandle? SCMHandle, string serviceDisplayName) + { + var builder = new ValueStringBuilder(stackalloc char[256]); + int bufLen; + while (true) + { + bufLen = builder.Capacity; + fixed (char* c = builder) { - return GetDevices(DefaultMachineName); + if (Interop.Advapi32.GetServiceKeyName(SCMHandle, serviceDisplayName, c, ref bufLen)) + break; } - /// Gets all the device-driver services in the machine specified. - public static ServiceController[] GetDevices(string machineName) + int lastError = Marshal.GetLastWin32Error(); + if (lastError == Interop.Errors.ERROR_SERVICE_DOES_NOT_EXIST) { - return GetServicesOfType(machineName, Interop.Advapi32.ServiceTypeOptions.SERVICE_TYPE_DRIVER); + return null; } - /// Opens a handle for the current service. The handle must be Dispose()'d. - private SafeServiceHandle GetServiceHandle(int desiredAccess) + if (lastError != Interop.Errors.ERROR_INSUFFICIENT_BUFFER) { - GetDataBaseHandleWithConnectAccess(); + throw new InvalidOperationException(SR.Format(SR.NoService, serviceDisplayName, _machineName), new Win32Exception(lastError)); + } - var serviceHandle = new SafeServiceHandle(Interop.Advapi32.OpenService(_serviceManagerHandle, ServiceName, desiredAccess)); - if (serviceHandle.IsInvalid) - { - Exception inner = new Win32Exception(Marshal.GetLastWin32Error()); - throw new InvalidOperationException(SR.Format(SR.OpenService, ServiceName, _machineName), inner); - } + builder.EnsureCapacity(bufLen + 1); // Does not include null + } - return serviceHandle; - } + builder.Length = bufLen; + return builder.ToString(); + } - /// Gets the services (not including device-driver services) on the local machine. - public static ServiceController[] GetServices() + private unsafe string? GetServiceDisplayName(SafeServiceHandle? scmHandle, string serviceName) + { + var builder = new ValueStringBuilder(4096); + int bufLen; + while (true) + { + bufLen = builder.Capacity; + fixed (char* c = builder) { - return GetServices(DefaultMachineName); + if (Interop.Advapi32.GetServiceDisplayName(scmHandle, serviceName, c, ref bufLen)) + break; } - /// Gets the services (not including device-driver services) on the machine specified. - public static ServiceController[] GetServices(string machineName) + int lastError = Marshal.GetLastWin32Error(); + if (lastError == Interop.Errors.ERROR_SERVICE_DOES_NOT_EXIST) { - return GetServicesOfType(machineName, Interop.Advapi32.ServiceTypeOptions.SERVICE_TYPE_WIN32); + return null; } - - /// Helper function for ServicesDependedOn. - private static Interop.Advapi32.ENUM_SERVICE_STATUS_PROCESS[] GetServicesInGroup(string machineName, string group) + else if (lastError != Interop.Errors.ERROR_INSUFFICIENT_BUFFER) { - return GetServices(machineName, Interop.Advapi32.ServiceTypeOptions.SERVICE_TYPE_WIN32, group, status => status); + throw new InvalidOperationException(SR.Format(SR.NoService, serviceName, _machineName), new Win32Exception(lastError)); } - /// Helper function for GetDevices and GetServices. - private static ServiceController[] GetServicesOfType(string machineName, int serviceType) - { - if (!CheckMachineName(machineName)) - throw new ArgumentException(SR.Format(SR.BadMachineName, machineName)); + builder.EnsureCapacity(bufLen + 1); // Does not include null + } - return GetServices(machineName, serviceType, null, status => new ServiceController(machineName, status)); - } + builder.Length = bufLen; + return builder.ToString(); + } - /// Helper for GetDevices, GetServices, and ServicesDependedOn - private static T[] GetServices(string machineName, int serviceType, string? group, Func selector) - { - int resumeHandle = 0; - - T[] services; - - using SafeServiceHandle databaseHandle = GetDataBaseHandleWithAccess(machineName, Interop.Advapi32.ServiceControllerOptions.SC_MANAGER_ENUMERATE_SERVICE); - Interop.Advapi32.EnumServicesStatusEx( - databaseHandle, - Interop.Advapi32.ServiceControllerOptions.SC_ENUM_PROCESS_INFO, - serviceType, - Interop.Advapi32.StatusOptions.STATUS_ALL, - IntPtr.Zero, - 0, - out int bytesNeeded, - out int servicesReturned, - ref resumeHandle, - group); - - IntPtr memory = Marshal.AllocHGlobal((IntPtr)bytesNeeded); - try - { - // - // Get the set of services - // - Interop.Advapi32.EnumServicesStatusEx( - databaseHandle, - Interop.Advapi32.ServiceControllerOptions.SC_ENUM_PROCESS_INFO, - serviceType, - Interop.Advapi32.StatusOptions.STATUS_ALL, - memory, - bytesNeeded, - out bytesNeeded, - out servicesReturned, - ref resumeHandle, - group); - - // - // Go through the block of memory it returned to us and select the results - // - services = new T[servicesReturned]; - for (int i = 0; i < servicesReturned; i++) - { - IntPtr structPtr = (IntPtr)((long)memory + (i * Marshal.SizeOf())); - Interop.Advapi32.ENUM_SERVICE_STATUS_PROCESS status = new Interop.Advapi32.ENUM_SERVICE_STATUS_PROCESS(); - Marshal.PtrToStructure(structPtr, status); - services[i] = selector(status); - } - } - finally - { - Marshal.FreeHGlobal(memory); - } + private static SafeServiceHandle GetDataBaseHandleWithAccess(string machineName, int serviceControlManagerAccess) + { + SafeServiceHandle? databaseHandle; + if (machineName.Equals(DefaultMachineName) || machineName.Length == 0) + { + databaseHandle = new SafeServiceHandle(Interop.Advapi32.OpenSCManager(null, null, serviceControlManagerAccess)); + } + else + { + databaseHandle = new SafeServiceHandle(Interop.Advapi32.OpenSCManager(machineName, null, serviceControlManagerAccess)); + } + + if (databaseHandle.IsInvalid) + { + Exception inner = new Win32Exception(Marshal.GetLastWin32Error()); + throw new InvalidOperationException(SR.Format(SR.OpenSC, machineName), inner); + } + + return databaseHandle; + } - return services; - } + private void GetDataBaseHandleWithConnectAccess() + { + if (_disposed) + { + throw new ObjectDisposedException(GetType().Name); + } + + // get a handle to SCM with connect access and store it in serviceManagerHandle field. + if (_serviceManagerHandle == null) + { + _serviceManagerHandle = GetDataBaseHandleWithAccess(_machineName, Interop.Advapi32.ServiceControllerOptions.SC_MANAGER_CONNECT); + } + } - /// Suspends a service's operation. - public unsafe void Pause() - { - using var serviceHandle = GetServiceHandle(Interop.Advapi32.ServiceOptions.SERVICE_PAUSE_CONTINUE); - Interop.Advapi32.SERVICE_STATUS status = default; - bool result = Interop.Advapi32.ControlService(serviceHandle, Interop.Advapi32.ControlOptions.CONTROL_PAUSE, &status); + /// + /// Gets all the device-driver services with . + /// + /// Set of service controllers + public static ServiceController[] GetDevices() + { + return GetDevices(DefaultMachineName); + } - if (!result) - { - Exception inner = new Win32Exception(Marshal.GetLastWin32Error()); - throw new InvalidOperationException(SR.Format(SR.PauseService, ServiceName, _machineName), inner); - } - } + /// + /// Gets all the device-driver services in the machine specified. + /// + /// Name of the machine. + /// Set of service controllers + public static ServiceController[] GetDevices(string machineName) + { + return GetServicesOfType(machineName, Interop.Advapi32.ServiceTypeOptions.SERVICE_TYPE_DRIVER); + } - /// Continues a service after it has been paused. - public unsafe void Continue() - { - using var serviceHandle = GetServiceHandle(Interop.Advapi32.ServiceOptions.SERVICE_PAUSE_CONTINUE); - Interop.Advapi32.SERVICE_STATUS status = default; - bool result = Interop.Advapi32.ControlService(serviceHandle, Interop.Advapi32.ControlOptions.CONTROL_CONTINUE, &status); - if (!result) - { - Exception inner = new Win32Exception(Marshal.GetLastWin32Error()); - throw new InvalidOperationException(SR.Format(SR.ResumeService, ServiceName, _machineName), inner); - } - } + /// + /// Opens a handle for the current service. The handle must be Dispose()'d. + /// + /// Access level to pass to OpenService() + /// + private SafeServiceHandle GetServiceHandle(int desiredAccess) + { + GetDataBaseHandleWithConnectAccess(); - public unsafe void ExecuteCommand(int command) - { - using var serviceHandle = GetServiceHandle(Interop.Advapi32.ServiceOptions.SERVICE_USER_DEFINED_CONTROL); - Interop.Advapi32.SERVICE_STATUS status = default; - bool result = Interop.Advapi32.ControlService(serviceHandle, command, &status); - if (!result) - { - Exception inner = new Win32Exception(Marshal.GetLastWin32Error()); - throw new InvalidOperationException(SR.Format(SR.ControlService, ServiceName, MachineName), inner); - } - } + var serviceHandle = new SafeServiceHandle(Interop.Advapi32.OpenService(_serviceManagerHandle, ServiceName, desiredAccess)); + if (serviceHandle.IsInvalid) + { + Exception inner = new Win32Exception(Marshal.GetLastWin32Error()); + throw new InvalidOperationException(SR.Format(SR.OpenService, ServiceName, _machineName), inner); + } - /// Refreshes all property values. - public void Refresh() - { - _statusGenerated = false; - _startTypeInitialized = false; - _dependentServices = null; - _servicesDependedOn = null; - } + return serviceHandle; + } - /// Starts the service. - public void Start() - { - Start(Array.Empty()); - } + /// + /// Gets the services (not including device-driver services) on the local machine. + /// + /// Set of service controllers + public static ServiceController[] GetServices() + { + return GetServices(DefaultMachineName); + } - /// Starts a service in the machine specified. - public void Start(string[] args) - { - if (args == null) - throw new ArgumentNullException(nameof(args)); + /// + /// Gets the services (not including device-driver services) on the given machine name. + /// /// + /// Name of the machine + /// + public static ServiceController[] GetServices(string machineName) + { + return GetServicesOfType(machineName, Interop.Advapi32.ServiceTypeOptions.SERVICE_TYPE_WIN32); + } - using SafeServiceHandle serviceHandle = GetServiceHandle(Interop.Advapi32.ServiceOptions.SERVICE_START); - IntPtr[] argPtrs = new IntPtr[args.Length]; - int i = 0; - try - { - for (i = 0; i < args.Length; i++) - { - if (args[i] == null) - throw new ArgumentNullException($"{nameof(args)}[{i}]", SR.ArgsCantBeNull); + /// + /// Helper function for ServicesDependedOn. + /// + /// Name of the machine. + /// Name of the group. + /// + private static Interop.Advapi32.ENUM_SERVICE_STATUS_PROCESS[] GetServicesInGroup(string machineName, string group) + { + return GetServices(machineName, Interop.Advapi32.ServiceTypeOptions.SERVICE_TYPE_WIN32, group, status => status); + } - argPtrs[i] = Marshal.StringToHGlobalUni(args[i]); - } - } - catch - { - for (int j = 0; j < i; j++) - Marshal.FreeHGlobal(argPtrs[i]); - throw; - } + /// + /// Helper function for GetDevices and GetServices. + /// + /// Name of the machine. + /// Type of service. + /// + private static ServiceController[] GetServicesOfType(string machineName, int serviceType) + { + if (!CheckMachineName(machineName)) + throw new ArgumentException(SR.Format(SR.BadMachineName, machineName)); - GCHandle argPtrsHandle = default; - try - { - argPtrsHandle = GCHandle.Alloc(argPtrs, GCHandleType.Pinned); - bool result = Interop.Advapi32.StartService(serviceHandle, args.Length, argPtrsHandle.AddrOfPinnedObject()); - if (!result) - { - Exception inner = new Win32Exception(Marshal.GetLastWin32Error()); - throw new InvalidOperationException(SR.Format(SR.CannotStart, ServiceName, _machineName), inner); - } - } - finally - { - for (i = 0; i < args.Length; i++) - Marshal.FreeHGlobal(argPtrs[i]); - if (argPtrsHandle.IsAllocated) - argPtrsHandle.Free(); - } - } + return GetServices(machineName, serviceType, null, status => new ServiceController(machineName, status)); + } - /// Stops the service. If any other services depend on this one for operation, - /// they will be stopped first. The DependentServices property lists this set - /// of services. - public unsafe void Stop() - { - using SafeServiceHandle serviceHandle = GetServiceHandle(Interop.Advapi32.ServiceOptions.SERVICE_STOP); - // Before stopping this service, stop all the dependent services that are running. - // (It's OK not to cache the result of getting the DependentServices property because it caches on its own.) - for (int i = 0; i < DependentServices.Length; i++) - { - ServiceController currentDependent = DependentServices[i]; - currentDependent.Refresh(); - if (currentDependent.Status != ServiceControllerStatus.Stopped) - { - currentDependent.Stop(); - currentDependent.WaitForStatus(ServiceControllerStatus.Stopped, new TimeSpan(0, 0, 30)); - } - } + /// Helper for GetDevices, GetServices, and ServicesDependedOn + private static T[] GetServices(string machineName, int serviceType, string? group, Func selector) + { + int resumeHandle = 0; + + T[] services; + + using SafeServiceHandle databaseHandle = GetDataBaseHandleWithAccess(machineName, Interop.Advapi32.ServiceControllerOptions.SC_MANAGER_ENUMERATE_SERVICE); + Interop.Advapi32.EnumServicesStatusEx( + databaseHandle, + Interop.Advapi32.ServiceControllerOptions.SC_ENUM_PROCESS_INFO, + serviceType, + Interop.Advapi32.StatusOptions.STATUS_ALL, + IntPtr.Zero, + 0, + out int bytesNeeded, + out int servicesReturned, + ref resumeHandle, + group); + + IntPtr memory = Marshal.AllocHGlobal((IntPtr)bytesNeeded); + try + { + // + // Get the set of services + // + Interop.Advapi32.EnumServicesStatusEx( + databaseHandle, + Interop.Advapi32.ServiceControllerOptions.SC_ENUM_PROCESS_INFO, + serviceType, + Interop.Advapi32.StatusOptions.STATUS_ALL, + memory, + bytesNeeded, + out bytesNeeded, + out servicesReturned, + ref resumeHandle, + group); + + // + // Go through the block of memory it returned to us and select the results + // + services = new T[servicesReturned]; + for (int i = 0; i < servicesReturned; i++) + { + IntPtr structPtr = (IntPtr)((long)memory + (i * Marshal.SizeOf())); + Interop.Advapi32.ENUM_SERVICE_STATUS_PROCESS status = new Interop.Advapi32.ENUM_SERVICE_STATUS_PROCESS(); + Marshal.PtrToStructure(structPtr, status); + services[i] = selector(status); + } + } + finally + { + Marshal.FreeHGlobal(memory); + } + + return services; + } - Interop.Advapi32.SERVICE_STATUS status = default; - bool result = Interop.Advapi32.ControlService(serviceHandle, Interop.Advapi32.ControlOptions.CONTROL_STOP, &status); - if (!result) - { - Exception inner = new Win32Exception(Marshal.GetLastWin32Error()); - throw new InvalidOperationException(SR.Format(SR.StopService, ServiceName, _machineName), inner); - } - } + /// + /// Suspends a service's operation. + /// + public unsafe void Pause() + { + using var serviceHandle = GetServiceHandle(Interop.Advapi32.ServiceOptions.SERVICE_PAUSE_CONTINUE); + Interop.Advapi32.SERVICE_STATUS status = default; + bool result = Interop.Advapi32.ControlService(serviceHandle, Interop.Advapi32.ControlOptions.CONTROL_PAUSE, &status); + + if (!result) + { + Exception inner = new Win32Exception(Marshal.GetLastWin32Error()); + throw new InvalidOperationException(SR.Format(SR.PauseService, ServiceName, _machineName), inner); + } + } - /// Waits infinitely until the service has reached the given status. - public void WaitForStatus(ServiceControllerStatus desiredStatus) - { - WaitForStatus(desiredStatus, TimeSpan.MaxValue); - } + /// + /// Continues a service after it has been paused. + /// + public unsafe void Continue() + { + using var serviceHandle = GetServiceHandle(Interop.Advapi32.ServiceOptions.SERVICE_PAUSE_CONTINUE); + Interop.Advapi32.SERVICE_STATUS status = default; + bool result = Interop.Advapi32.ControlService(serviceHandle, Interop.Advapi32.ControlOptions.CONTROL_CONTINUE, &status); + if (!result) + { + Exception inner = new Win32Exception(Marshal.GetLastWin32Error()); + throw new InvalidOperationException(SR.Format(SR.ResumeService, ServiceName, _machineName), inner); + } + } - /// Waits until the service has reached the given status or until the specified time - /// has expired - public void WaitForStatus(ServiceControllerStatus desiredStatus, TimeSpan timeout) - { - if (!Enum.IsDefined(typeof(ServiceControllerStatus), desiredStatus)) - throw new ArgumentException(SR.Format(SR.InvalidEnumArgument, nameof(desiredStatus), (int)desiredStatus, typeof(ServiceControllerStatus))); + /// + /// Executes the command. + /// + /// The command + public unsafe void ExecuteCommand(int command) + { + using var serviceHandle = GetServiceHandle(Interop.Advapi32.ServiceOptions.SERVICE_USER_DEFINED_CONTROL); + Interop.Advapi32.SERVICE_STATUS status = default; + bool result = Interop.Advapi32.ControlService(serviceHandle, command, &status); + if (!result) + { + Exception inner = new Win32Exception(Marshal.GetLastWin32Error()); + throw new InvalidOperationException(SR.Format(SR.ControlService, ServiceName, MachineName), inner); + } + } - DateTime start = DateTime.UtcNow; - Refresh(); - while (Status != desiredStatus) - { - if (DateTime.UtcNow - start > timeout) - throw new System.ServiceProcess.TimeoutException(SR.Format(SR.Timeout, ServiceName)); + /// + /// Refreshes all property values. + /// + public void Refresh() + { + _statusGenerated = false; + _startTypeInitialized = false; + _dependentServices = null; + _servicesDependedOn = null; + } - _waitForStatusSignal.WaitOne(250); - Refresh(); - } - } + /// + /// Starts the service. + /// + public void Start() + { + Start(Array.Empty()); + } + + /// + /// Starts a service in the machine specified. + /// + public void Start(string[] args) + { + if (args == null) + throw new ArgumentNullException(nameof(args)); + + using SafeServiceHandle serviceHandle = GetServiceHandle(Interop.Advapi32.ServiceOptions.SERVICE_START); + IntPtr[] argPtrs = new IntPtr[args.Length]; + int i = 0; + try + { + for (i = 0; i < args.Length; i++) + { + if (args[i] == null) + throw new ArgumentNullException($"{nameof(args)}[{i}]", SR.ArgsCantBeNull); + + argPtrs[i] = Marshal.StringToHGlobalUni(args[i]); + } + } + catch + { + for (int j = 0; j < i; j++) + Marshal.FreeHGlobal(argPtrs[i]); + throw; + } + + GCHandle argPtrsHandle = default; + try + { + argPtrsHandle = GCHandle.Alloc(argPtrs, GCHandleType.Pinned); + bool result = Interop.Advapi32.StartService(serviceHandle, args.Length, argPtrsHandle.AddrOfPinnedObject()); + if (!result) + { + Exception inner = new Win32Exception(Marshal.GetLastWin32Error()); + throw new InvalidOperationException(SR.Format(SR.CannotStart, ServiceName, _machineName), inner); + } + } + finally + { + for (i = 0; i < args.Length; i++) + Marshal.FreeHGlobal(argPtrs[i]); + if (argPtrsHandle.IsAllocated) + argPtrsHandle.Free(); + } + } + + /// + /// Stops the service. If any other services depend on this one for operation, + /// they will be stopped first. The DependentServices property lists this set + /// of services. + /// + public unsafe void Stop() + { + using SafeServiceHandle serviceHandle = GetServiceHandle(Interop.Advapi32.ServiceOptions.SERVICE_STOP); + // Before stopping this service, stop all the dependent services that are running. + // (It's OK not to cache the result of getting the DependentServices property because it caches on its own.) + for (int i = 0; i < DependentServices.Length; i++) + { + ServiceController currentDependent = DependentServices[i]; + currentDependent.Refresh(); + if (currentDependent.Status != ServiceControllerStatus.Stopped) + { + currentDependent.Stop(); + currentDependent.WaitForStatus(ServiceControllerStatus.Stopped, new TimeSpan(0, 0, 30)); + } + } + + Interop.Advapi32.SERVICE_STATUS status = default; + bool result = Interop.Advapi32.ControlService(serviceHandle, Interop.Advapi32.ControlOptions.CONTROL_STOP, &status); + if (!result) + { + Exception inner = new Win32Exception(Marshal.GetLastWin32Error()); + throw new InvalidOperationException(SR.Format(SR.StopService, ServiceName, _machineName), inner); + } + } + + /// + /// Waits infinitely until the service has reached the given status. + /// + /// The status for which to wait. + public void WaitForStatus(ServiceControllerStatus desiredStatus) + { + WaitForStatus(desiredStatus, TimeSpan.MaxValue); + } + + /// + /// Waits until the service has reached the given status or until the specified time has expired. + /// + /// The status for which to wait. + /// Wait for specific timeout + public void WaitForStatus(ServiceControllerStatus desiredStatus, TimeSpan timeout) + { + if (!Enum.IsDefined(typeof(ServiceControllerStatus), desiredStatus)) + throw new ArgumentException(SR.Format(SR.InvalidEnumArgument, nameof(desiredStatus), (int)desiredStatus, typeof(ServiceControllerStatus))); + + DateTime start = DateTime.UtcNow; + Refresh(); + while (Status != desiredStatus) + { + if (DateTime.UtcNow - start > timeout) + throw new System.ServiceProcess.TimeoutException(SR.Format(SR.Timeout, ServiceName)); + + _waitForStatusSignal.WaitOne(250); + Refresh(); + } } + } } diff --git a/src/libraries/System.ServiceProcess.ServiceController/src/System/ServiceProcess/SessionChangeDescription.cs b/src/libraries/System.ServiceProcess.ServiceController/src/System/ServiceProcess/SessionChangeDescription.cs index b46be025400ba..449b2cc1939e4 100644 --- a/src/libraries/System.ServiceProcess.ServiceController/src/System/ServiceProcess/SessionChangeDescription.cs +++ b/src/libraries/System.ServiceProcess.ServiceController/src/System/ServiceProcess/SessionChangeDescription.cs @@ -5,18 +5,15 @@ namespace System.ServiceProcess { public readonly struct SessionChangeDescription { - private readonly SessionChangeReason _reason; - private readonly int _id; - internal SessionChangeDescription(SessionChangeReason reason, int id) { - _reason = reason; - _id = id; + Reason = reason; + SessionId = id; } - public SessionChangeReason Reason => _reason; + public SessionChangeReason Reason { get; } - public int SessionId => _id; + public int SessionId { get; } public override bool Equals(object? obj) { @@ -30,12 +27,12 @@ public override bool Equals(object? obj) public override int GetHashCode() { - return (int)_reason ^ _id; + return (int)Reason ^ SessionId; } public bool Equals(SessionChangeDescription changeDescription) { - return (_reason == changeDescription._reason) && (_id == changeDescription._id); + return (Reason == changeDescription.Reason) && (SessionId == changeDescription.SessionId); } public static bool operator ==(SessionChangeDescription a, SessionChangeDescription b) diff --git a/src/libraries/System.ServiceProcess.ServiceController/src/System/ServiceProcess/SessionChangeReason.cs b/src/libraries/System.ServiceProcess.ServiceController/src/System/ServiceProcess/SessionChangeReason.cs index e22a167f127de..cb16e6c1c417d 100644 --- a/src/libraries/System.ServiceProcess.ServiceController/src/System/ServiceProcess/SessionChangeReason.cs +++ b/src/libraries/System.ServiceProcess.ServiceController/src/System/ServiceProcess/SessionChangeReason.cs @@ -3,43 +3,54 @@ namespace System.ServiceProcess { + /// + /// Enum containing various session change reason + /// public enum SessionChangeReason { - /// - /// A session was connected to the console session. - /// - ConsoleConnect = Interop.Advapi32.SessionStateChange.WTS_CONSOLE_CONNECT, - /// - /// A session was disconnected from the console session. - /// - ConsoleDisconnect = Interop.Advapi32.SessionStateChange.WTS_CONSOLE_DISCONNECT, - /// - /// A session was connected to the remote session. - /// - RemoteConnect = Interop.Advapi32.SessionStateChange.WTS_REMOTE_CONNECT, - /// - /// A session was disconnected from the remote session. - /// - RemoteDisconnect = Interop.Advapi32.SessionStateChange.WTS_REMOTE_DISCONNECT, - /// - /// A user has logged on to the session. - /// - SessionLogon = Interop.Advapi32.SessionStateChange.WTS_SESSION_LOGON, - /// - /// A user has logged off the session. - /// - SessionLogoff = Interop.Advapi32.SessionStateChange.WTS_SESSION_LOGOFF, - /// - /// A session has been locked. - /// - SessionLock = Interop.Advapi32.SessionStateChange.WTS_SESSION_LOCK, - /// - /// A session has been unlocked. - /// - SessionUnlock = Interop.Advapi32.SessionStateChange.WTS_SESSION_UNLOCK, - /// - /// A session has changed its remote controlled status. - /// - SessionRemoteControl = Interop.Advapi32.SessionStateChange.WTS_SESSION_REMOTE_CONTROL + /// + /// A session was connected to the console session. + /// + ConsoleConnect = Interop.Advapi32.SessionStateChange.WTS_CONSOLE_CONNECT, + + /// + ///A session was disconnected from the console session. + /// + ConsoleDisconnect = Interop.Advapi32.SessionStateChange.WTS_CONSOLE_DISCONNECT, + + /// + /// A session was connected to the remote session. + /// + RemoteConnect = Interop.Advapi32.SessionStateChange.WTS_REMOTE_CONNECT, + + /// + /// A session was disconnected from the remote session. + /// + RemoteDisconnect = Interop.Advapi32.SessionStateChange.WTS_REMOTE_DISCONNECT, + + /// + /// A user has logged on to the session. + /// + SessionLogon = Interop.Advapi32.SessionStateChange.WTS_SESSION_LOGON, + + /// + /// A user has logged off the session. + /// + SessionLogoff = Interop.Advapi32.SessionStateChange.WTS_SESSION_LOGOFF, + + /// + /// A session has been locked. + /// + SessionLock = Interop.Advapi32.SessionStateChange.WTS_SESSION_LOCK, + + /// + /// A session has been unlocked. + /// + SessionUnlock = Interop.Advapi32.SessionStateChange.WTS_SESSION_UNLOCK, + + /// + /// A session has changed its remote controlled status. + /// + SessionRemoteControl = Interop.Advapi32.SessionStateChange.WTS_SESSION_REMOTE_CONTROL } } diff --git a/src/libraries/System.ServiceProcess.ServiceController/src/System/ServiceProcess/TimeoutException.cs b/src/libraries/System.ServiceProcess.ServiceController/src/System/ServiceProcess/TimeoutException.cs index 7cc5ce4e4e160..7648e50a27dc6 100644 --- a/src/libraries/System.ServiceProcess.ServiceController/src/System/ServiceProcess/TimeoutException.cs +++ b/src/libraries/System.ServiceProcess.ServiceController/src/System/ServiceProcess/TimeoutException.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; using System.Runtime.Serialization; namespace System.ServiceProcess @@ -12,7 +11,7 @@ public class TimeoutException : SystemException { private const int ServiceControllerTimeout = unchecked((int)0x80131906); - public TimeoutException() : base() + public TimeoutException() { HResult = ServiceControllerTimeout; }