File: System\ServiceProcess\ServiceController.cs
Web Access
Project: src\src\runtime\src\libraries\System.ServiceProcess.ServiceController\src\System.ServiceProcess.ServiceController.csproj (System.ServiceProcess.ServiceController)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using Microsoft.Win32.SafeHandles;

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 const string DefaultMachineName = ".";

        // Note that ServiceType currently does not include all of SERVICE_TYPE_ALL; see see Interop.Advapi32.ServiceTypeOptions
        private const int AllServiceTypes = (int)(ServiceType.Adapter | ServiceType.FileSystemDriver | ServiceType.InteractiveProcess |
                                                  ServiceType.KernelDriver | ServiceType.RecognizerDriver | ServiceType.Win32OwnProcess |
                                                  ServiceType.Win32ShareProcess);

        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 = AllServiceTypes;
        }

        /// <summary>
        /// Creates a ServiceController object, based on service name.
        /// </summary>
        /// <param name="name">Name of the service</param>
        public ServiceController(string name)
            : this(name, DefaultMachineName)
        {
        }


        /// <summary>
        /// Creates a ServiceController object, based on machine and service name.
        /// </summary>
        /// <param name="name">Name of the service</param>
        /// <param name="machineName">Name of the machine</param>
        public ServiceController(string name, string machineName)
        {
            if (!CheckMachineName(machineName))
                throw new ArgumentException(SR.Format(SR.BadMachineName, machineName));

            ArgumentNullException.ThrowIfNull(name);
            if (name.Length == 0)
            {
                throw new ArgumentException(SR.Format(SR.InvalidParameter, nameof(name), name), nameof(name));
            }

            _machineName = machineName;
            _eitherName = name;
            _type = AllServiceTypes;
        }

        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;
        }

        /// <summary>
        /// Used by the GetServicesInGroup method.
        /// </summary>
        /// <param name="machineName">Name of the machine</param>
        /// <param name="status">Service process status</param>
        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;
        }

        /// <summary>
        /// Tells if the service referenced by this object can be paused.
        /// </summary>
        public bool CanPauseAndContinue
        {
            get
            {
                GenerateStatus();
                return (_commandsAccepted & Interop.Advapi32.AcceptOptions.ACCEPT_PAUSE_CONTINUE) != 0;
            }
        }

        /// <summary>
        /// Tells if the service is notified when system shutdown occurs.
        /// </summary>
        public bool CanShutdown
        {
            get
            {
                GenerateStatus();
                return (_commandsAccepted & Interop.Advapi32.AcceptOptions.ACCEPT_SHUTDOWN) != 0;
            }
        }

        /// <summary>
        /// Tells if the service referenced by this object can be stopped.
        /// </summary>
        public bool CanStop
        {
            get
            {
                GenerateStatus();
                return (_commandsAccepted & Interop.Advapi32.AcceptOptions.ACCEPT_STOP) != 0;
            }
        }

        /// <summary>
        /// The descriptive name shown for this service in the Service applet.
        /// </summary>
        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 = "";
            }
        }

        /// <summary>
        /// The set of services that depend on this service. These are the services that will be stopped if this service is stopped.
        /// </summary>
        public unsafe 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<ServiceController>();
                        return _dependentServices;
                    }

                    int lastError = Marshal.GetLastPInvokeError();
                    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();

                        byte* pEnumBuffer = (byte*)enumBuffer;
                        // 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)(pEnumBuffer + ((nint)i * Marshal.SizeOf<Interop.Advapi32.ENUM_SERVICE_STATUS>()));
                            Marshal.PtrToStructure(structPtr, status);
                            _dependentServices[i] = new ServiceController(_machineName, status);
                        }
                    }
                    finally
                    {
                        Marshal.FreeHGlobal(enumBuffer);
                    }
                }

                return _dependentServices;
            }
        }

        /// <summary>
        /// The name of the machine on which this service resides.
        /// </summary>
        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;
            }
        }

        /// <summary>
        /// Returns the short name of the service referenced by this object.
        /// </summary>
        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 = "";
            }
        }

        /// <summary>
        /// A set of services on which the given service object is dependent upon.
        /// </summary>
        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<ServiceController>();
                    return _servicesDependedOn;
                }

                int lastError = Marshal.GetLastPInvokeError();
                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();

                    Interop.Advapi32.QUERY_SERVICE_CONFIG config = new Interop.Advapi32.QUERY_SERVICE_CONFIG();
                    Marshal.PtrToStructure(bufPtr, config);
                    Dictionary<string, ServiceController>? 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<string, ServiceController>();
                        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('+'))
                                {
                                    // 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<ServiceController>();
                    }

                    return _servicesDependedOn;
                }
                finally
                {
                    Marshal.FreeHGlobal(bufPtr);
                }
            }
        }

        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.GetLastPInvokeError();
                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();

                    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);
                }

                return _startType;
            }
        }

        public SafeHandle ServiceHandle
        {
            get
            {
                return GetServiceHandle(Interop.Advapi32.ServiceOptions.SERVICE_ALL_ACCESS);
            }
        }

        /// <summary>
        /// Gets the status of the service referenced by this object, e.g., Running, Stopped, etc.
        /// </summary>
        /// <remarks>
        /// Please see <see cref="ServiceControllerStatus"/> for more available status.
        /// </remarks>
        public ServiceControllerStatus Status
        {
            get
            {
                GenerateStatus();
                return _status;
            }
        }

        /// <summary>
        /// Gets the type of service that this object references.
        /// </summary>
        /// <remarks>
        /// Please see <see cref="System.ServiceProcess.ServiceType"/> for available list of Service types.
        /// </remarks>
        public ServiceType ServiceType
        {
            get
            {
                GenerateStatus();
                return (ServiceType)_type;
            }
        }

        private static bool CheckMachineName(string value)
        {
            return !string.IsNullOrWhiteSpace(value) && !value.Contains('\\');
        }

        /// <summary>
        /// Closes the handle to the service manager, but does not
        /// mark the class as disposed.
        /// </summary>
        /// <remarks>
        /// Violates design guidelines by not matching Dispose() -- matches .NET Framework
        /// </remarks>
        public void Close()
        {
            if (_serviceManagerHandle != null)
            {
                _serviceManagerHandle.Dispose();
                _serviceManagerHandle = null;
            }

            _statusGenerated = false;
            _startTypeInitialized = false;
            _type = AllServiceTypes;
        }

        /// <summary>
        /// Closes the handle to the service manager, and disposes.
        /// </summary>
        protected override void Dispose(bool disposing)
        {
            Close();
            _disposed = true;
            base.Dispose(disposing);
        }

        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();

            _commandsAccepted = svcStatus.controlsAccepted;
            _status = (ServiceControllerStatus)svcStatus.currentState;
            _type = svcStatus.serviceType;
            _statusGenerated = true;
        }

        [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);

                if (result != null)
                {
                    // Now we have both
                    _name = result;
                    _displayName = userGivenName;
                    _eitherName = null;
                    return;
                }

                // Try it as a service name
                result = GetServiceDisplayName(_serviceManagerHandle, userGivenName);

                if (result == null)
                {
                    throw new InvalidOperationException(SR.Format(SR.NoService, userGivenName, _machineName), new Win32Exception(Interop.Errors.ERROR_SERVICE_DOES_NOT_EXIST));
                }

                _name = userGivenName;
                _displayName = result;
                _eitherName = null;
            }
            else if (string.IsNullOrEmpty(_displayName))
            {
                // We must have _name
                string? result = GetServiceDisplayName(_serviceManagerHandle, _name);

                if (result == null)
                {
                    throw new InvalidOperationException(SR.Format(SR.NoService, _name, _machineName), new Win32Exception(Interop.Errors.ERROR_SERVICE_DOES_NOT_EXIST));
                }

                _displayName = result;
                _eitherName = null;
            }
        }

        /// <summary>
        /// Gets service name (key name) from service display name.
        /// Returns null if service is not found.
        /// </summary>
        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;
                }

                int lastError = Marshal.GetLastPInvokeError();
                if (lastError == Interop.Errors.ERROR_SERVICE_DOES_NOT_EXIST)
                {
                    return null;
                }

                if (lastError != Interop.Errors.ERROR_INSUFFICIENT_BUFFER)
                {
                    throw new InvalidOperationException(SR.Format(SR.NoService, serviceDisplayName, _machineName), new Win32Exception(lastError));
                }

                builder.EnsureCapacity(bufLen + 1); // Does not include null
            }

            builder.Length = bufLen;
            return builder.ToString();
        }

        private unsafe string? GetServiceDisplayName(SafeServiceHandle? scmHandle, string serviceName)
        {
            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.GetLastPInvokeError();
                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();
        }

        private static SafeServiceHandle GetDataBaseHandleWithAccess(string machineName, int serviceControlManagerAccess)
        {
            var databaseHandle = new SafeServiceHandle();
            Marshal.InitHandle(databaseHandle, machineName.Equals(DefaultMachineName) || machineName.Length == 0 ?
                Interop.Advapi32.OpenSCManager(null, null, serviceControlManagerAccess) :
                Interop.Advapi32.OpenSCManager(machineName, null, serviceControlManagerAccess));

            if (databaseHandle.IsInvalid)
            {
                Exception inner = new Win32Exception();
                databaseHandle.Dispose();
                throw new InvalidOperationException(SR.Format(SR.OpenSC, machineName), inner);
            }

            return databaseHandle;
        }

        private void GetDataBaseHandleWithConnectAccess()
        {
            if (_disposed)
            {
                throw new ObjectDisposedException(GetType().Name);
            }

            // get a handle to SCM with connect access and store it in serviceManagerHandle field.
            _serviceManagerHandle ??= GetDataBaseHandleWithAccess(_machineName, Interop.Advapi32.ServiceControllerOptions.SC_MANAGER_CONNECT);
        }

        /// <summary>
        /// Gets all the device-driver services with <see cref="DefaultMachineName"/>.
        /// </summary>
        /// <returns>Set of service controllers</returns>
        public static ServiceController[] GetDevices()
        {
            return GetDevices(DefaultMachineName);
        }

        /// <summary>
        /// Gets all the device-driver services in the machine specified.
        /// </summary>
        /// <param name="machineName">Name of the machine.</param>
        /// <returns>Set of service controllers</returns>
        public static ServiceController[] GetDevices(string machineName)
        {
            return GetServicesOfType(machineName, Interop.Advapi32.ServiceTypeOptions.SERVICE_DRIVER);
        }

        /// <summary>
        /// Opens a handle for the current service. The handle must be Dispose()'d.
        /// </summary>
        /// <param name="desiredAccess">Access level to pass to OpenService()</param>
        /// <returns></returns>
        private SafeServiceHandle GetServiceHandle(int desiredAccess)
        {
            GetDataBaseHandleWithConnectAccess();

            var serviceHandle = new SafeServiceHandle();
            Marshal.InitHandle(serviceHandle, Interop.Advapi32.OpenService(_serviceManagerHandle, ServiceName, desiredAccess));
            if (serviceHandle.IsInvalid)
            {
                Exception inner = new Win32Exception();
                serviceHandle.Dispose();
                throw new InvalidOperationException(SR.Format(SR.OpenService, ServiceName, _machineName), inner);
            }

            return serviceHandle;
        }

        /// <summary>
        /// Gets the services (not including device-driver services) on the local machine.
        /// </summary>
        /// <returns>Set of service controllers</returns>
        public static ServiceController[] GetServices()
        {
            return GetServices(DefaultMachineName);
        }

        /// <summary>
        /// Gets the services (not including device-driver services) on the given machine name.
        /// </summary>
        /// <param name="machineName">Name of the machine</param>
        /// <returns></returns>
        public static ServiceController[] GetServices(string machineName)
        {
            return GetServicesOfType(machineName, Interop.Advapi32.ServiceTypeOptions.SERVICE_WIN32);
        }

        /// <summary>
        /// Helper function for ServicesDependedOn.
        /// </summary>
        /// <param name="machineName">Name of the machine.</param>
        /// <param name="group">Name of the group.</param>
        /// <returns></returns>
        private static Interop.Advapi32.ENUM_SERVICE_STATUS_PROCESS[] GetServicesInGroup(string machineName, string group)
        {
            return GetServices(machineName, Interop.Advapi32.ServiceTypeOptions.SERVICE_WIN32, group, status => status);
        }

        /// <summary>
        /// Helper function for GetDevices and GetServices.
        /// </summary>
        /// <param name="machineName">Name of the machine.</param>
        /// <param name="serviceType">Type of service.</param>
        /// <returns></returns>
        private static ServiceController[] GetServicesOfType(string machineName, int serviceType)
        {
            if (!CheckMachineName(machineName))
                throw new ArgumentException(SR.Format(SR.BadMachineName, machineName));

            return GetServices(machineName, serviceType, null, status => new ServiceController(machineName, status));
        }

        /// Helper for GetDevices, GetServices, and ServicesDependedOn
        private static unsafe T[] GetServices<T>(string machineName, int serviceType, string? group, Func<Interop.Advapi32.ENUM_SERVICE_STATUS_PROCESS, T> 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];
                byte* pMemory = (byte*)memory;
                for (int i = 0; i < servicesReturned; i++)
                {
                    IntPtr structPtr = (IntPtr)(pMemory + ((nint)i * Marshal.SizeOf<Interop.Advapi32.ENUM_SERVICE_STATUS_PROCESS>()));
                    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;
        }

        /// <summary>
        /// Suspends a service's operation.
        /// </summary>
        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();
                throw new InvalidOperationException(SR.Format(SR.PauseService, ServiceName, _machineName), inner);
            }
        }

        /// <summary>
        /// Continues a service after it has been paused.
        /// </summary>
        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();
                throw new InvalidOperationException(SR.Format(SR.ResumeService, ServiceName, _machineName), inner);
            }
        }

        /// <summary>
        /// Executes the command.
        /// </summary>
        /// <param name="command">The command</param>
        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();
                throw new InvalidOperationException(SR.Format(SR.ControlService, ServiceName, MachineName), inner);
            }
        }

        /// <summary>
        /// Refreshes all property values.
        /// </summary>
        public void Refresh()
        {
            _statusGenerated = false;
            _startTypeInitialized = false;
            _dependentServices = null;
            _servicesDependedOn = null;
        }

        /// <summary>
        /// Starts the service.
        /// </summary>
        public void Start()
        {
            Start(Array.Empty<string>());
        }

        /// <summary>
        /// Starts a service in the machine specified.
        /// </summary>
        public void Start(string[] args)
        {
            ArgumentNullException.ThrowIfNull(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();
                    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();
            }
        }

        /// <summary>
        /// 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.
        /// </summary>
        public void Stop()
        {
            Stop(stopDependentServices: true);
        }

        /// <summary>
        /// Stops the service and optionally any services that are dependent on this service.
        /// </summary>
        /// <remarks>
        /// If any other services depend on this one, you need to either pass <c>true</c> for
        /// <paramref name="stopDependentServices"/> or stop them manually before calling this method.
        /// </remarks>
        /// <param name="stopDependentServices">
        /// <c>true</c> to stop all running dependent services together with the service; <c>false</c> to stop only the service.
        /// </param>
#if NET
        public
#else
        private
#endif
            unsafe void Stop(bool stopDependentServices)
        {
            using SafeServiceHandle serviceHandle = GetServiceHandle(Interop.Advapi32.ServiceOptions.SERVICE_STOP);
            if (stopDependentServices)
            {
                // 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();
                throw new InvalidOperationException(SR.Format(SR.StopService, ServiceName, _machineName), inner);
            }
        }

        /// <summary>
        /// Waits infinitely until the service has reached the given status.
        /// </summary>
        /// <param name="desiredStatus">The status for which to wait.</param>
        public void WaitForStatus(ServiceControllerStatus desiredStatus)
        {
            WaitForStatus(desiredStatus, TimeSpan.MaxValue);
        }

        /// <summary>
        /// Waits until the service has reached the given status or until the specified time has expired.
        /// </summary>
        /// <param name="desiredStatus">The status for which to wait.</param>
        /// <param name="timeout">Wait for specific timeout</param>
        public void WaitForStatus(ServiceControllerStatus desiredStatus, TimeSpan timeout)
        {
            if (!Enum.IsDefined(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));

                Thread.Sleep(250);
                Refresh();
            }
        }
    }
}