File: System\ServiceProcess\ServiceBase.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.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Runtime.ExceptionServices;
using System.Runtime.InteropServices;
using System.Threading;

using static Interop.Advapi32;

namespace System.ServiceProcess
{
    /// <summary>
    /// <para>Provides a base class for a service that will exist as part of a service application. <see cref='System.ServiceProcess.ServiceBase'/>
    /// must be derived when creating a new service class.</para>
    /// </summary>
    public class ServiceBase : Component
    {
        private SERVICE_STATUS _status;
        private IntPtr _statusHandle;
        private ServiceControlCallbackEx? _commandCallbackEx;
        private ServiceMainCallback? _mainCallback;
        private ManualResetEvent? _startCompletedSignal;
        private ExceptionDispatchInfo? _startFailedException;
        private int _acceptedCommands;
        private string _serviceName;
        private bool _nameFrozen;          // set to true once we've started running and ServiceName can't be changed any more.
        private bool _commandPropsFrozen;  // set to true once we've use the Can... properties.
        private bool _disposed;
        private bool _initialized;
        private object _stopLock = new object();
        private EventLog? _eventLog;

        /// <summary>
        /// Indicates the maximum size for a service name.
        /// </summary>
        public const int MaxNameLength = 80;

        /// <summary>
        /// Creates a new instance of the <see cref='System.ServiceProcess.ServiceBase()'/> class.
        /// </summary>
        public ServiceBase()
        {
            _acceptedCommands = AcceptOptions.ACCEPT_STOP;
            ServiceName = string.Empty;
            AutoLog = true;
        }

        /// <summary>
        /// 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.
        /// </summary>
        /// <param name="milliseconds"></param>
        public unsafe void RequestAdditionalTime(int milliseconds)
        {
            fixed (SERVICE_STATUS* pStatus = &_status)
            {
                if (_status.currentState != ServiceControlStatus.STATE_CONTINUE_PENDING &&
                    _status.currentState != ServiceControlStatus.STATE_START_PENDING &&
                    _status.currentState != ServiceControlStatus.STATE_STOP_PENDING &&
                    _status.currentState != ServiceControlStatus.STATE_PAUSE_PENDING)
                {
                    throw new InvalidOperationException(SR.NotInPendingState);
                }

                _status.waitHint = milliseconds;
                _status.checkPoint++;
                SetServiceStatus(_statusHandle, pStatus);
            }
        }

#if NET
        /// <summary>
        /// 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.
        /// </summary>
        /// <param name="time">The requested additional time</param>
        public void RequestAdditionalTime(TimeSpan time) => RequestAdditionalTime(ToIntMilliseconds(time));

        private static int ToIntMilliseconds(TimeSpan time)
        {
            long totalMilliseconds = (long)time.TotalMilliseconds;
            if (totalMilliseconds < -1 || totalMilliseconds > int.MaxValue)
            {
                throw new ArgumentOutOfRangeException(nameof(time));
            }
            return (int)totalMilliseconds;
        }
#endif

        /// <summary>
        /// Indicates whether to report Start, Stop, Pause, and Continue commands in the event.
        /// </summary>
        [DefaultValue(true)]
        public bool AutoLog { get; set; }

        /// <summary>
        /// The termination code for the service.  Set this to a non-zero value before
        /// stopping to indicate an error to the Service Control Manager.
        /// </summary>
        public int ExitCode
        {
            get
            {
                return _status.win32ExitCode;
            }
            set
            {
                _status.win32ExitCode = value;
            }
        }

        /// <summary>
        ///  Indicates whether the service can be handle notifications on
        ///  computer power status changes.
        /// </summary>
        [DefaultValue(false)]
        public bool CanHandlePowerEvent
        {
            get
            {
                return (_acceptedCommands & AcceptOptions.ACCEPT_POWEREVENT) != 0;
            }
            set
            {
                if (_commandPropsFrozen)
                    throw new InvalidOperationException(SR.CannotChangeProperties);

                if (value)
                {
                    _acceptedCommands |= AcceptOptions.ACCEPT_POWEREVENT;
                }
                else
                {
                    _acceptedCommands &= ~AcceptOptions.ACCEPT_POWEREVENT;
                }
            }
        }

        /// <summary>
        /// Indicates whether the service can handle Terminal Server session change events.
        /// </summary>
        [DefaultValue(false)]
        public bool CanHandleSessionChangeEvent
        {
            get
            {
                return (_acceptedCommands & AcceptOptions.ACCEPT_SESSIONCHANGE) != 0;
            }
            set
            {
                if (_commandPropsFrozen)
                    throw new InvalidOperationException(SR.CannotChangeProperties);

                if (value)
                {
                    _acceptedCommands |= AcceptOptions.ACCEPT_SESSIONCHANGE;
                }
                else
                {
                    _acceptedCommands &= ~AcceptOptions.ACCEPT_SESSIONCHANGE;
                }
            }
        }

        /// <summary>
        ///   Indicates whether the service can be paused and resumed.
        /// </summary>
        [DefaultValue(false)]
        public bool CanPauseAndContinue
        {
            get
            {
                return (_acceptedCommands & AcceptOptions.ACCEPT_PAUSE_CONTINUE) != 0;
            }
            set
            {
                if (_commandPropsFrozen)
                    throw new InvalidOperationException(SR.CannotChangeProperties);

                if (value)
                {
                    _acceptedCommands |= AcceptOptions.ACCEPT_PAUSE_CONTINUE;
                }
                else
                {
                    _acceptedCommands &= ~AcceptOptions.ACCEPT_PAUSE_CONTINUE;
                }
            }
        }

        /// <summary>
        /// Indicates whether the service should be notified when the system is shutting down.
        /// </summary>
        [DefaultValue(false)]
        public bool CanShutdown
        {
            get
            {
                return (_acceptedCommands & AcceptOptions.ACCEPT_SHUTDOWN) != 0;
            }
            set
            {
                if (_commandPropsFrozen)
                    throw new InvalidOperationException(SR.CannotChangeProperties);

                if (value)
                {
                    _acceptedCommands |= AcceptOptions.ACCEPT_SHUTDOWN;
                }
                else
                {
                    _acceptedCommands &= ~AcceptOptions.ACCEPT_SHUTDOWN;
                }
            }
        }

        /// <summary>
        /// Indicates whether the service can be stopped once it has started.
        /// </summary>
        [DefaultValue(true)]
        public bool CanStop
        {
            get
            {
                return (_acceptedCommands & AcceptOptions.ACCEPT_STOP) != 0;
            }
            set
            {
                if (_commandPropsFrozen)
                    throw new InvalidOperationException(SR.CannotChangeProperties);

                if (value)
                {
                    _acceptedCommands |= AcceptOptions.ACCEPT_STOP;
                }
                else
                {
                    _acceptedCommands &= ~AcceptOptions.ACCEPT_STOP;
                }
            }
        }

        /// <summary>
        /// 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.
        /// </summary>
        [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public virtual EventLog EventLog =>
            _eventLog ??= new EventLog("Application")
            {
                Source = ServiceName
            };

        [EditorBrowsable(EditorBrowsableState.Advanced)]
        protected IntPtr ServiceHandle
        {
            get
            {
                return _statusHandle;
            }
        }

        /// <summary>
        /// Indicates the short name used to identify the service to the system.
        /// </summary>
        public string ServiceName
        {
            get
            {
                return _serviceName;
            }
            [MemberNotNull(nameof(_serviceName))]
            set
            {
                if (_nameFrozen)
                    throw new InvalidOperationException(SR.CannotChangeName);

                // For component properties, "" is a special case.
                if (value != "" && !ValidServiceName(value))
                    throw new ArgumentException(SR.Format(SR.ServiceName, value, ServiceBase.MaxNameLength.ToString(CultureInfo.CurrentCulture)));

                _serviceName = value;
            }
        }

        internal static bool ValidServiceName(string serviceName) =>
            !string.IsNullOrEmpty(serviceName) &&
            serviceName.Length <= ServiceBase.MaxNameLength && // not too long
            serviceName.AsSpan().IndexOfAny('\\', '/') < 0; // no slashes or backslash allowed

        /// <summary>
        ///    <para>Disposes of the resources (other than memory ) used by
        ///       the <see cref='System.ServiceProcess.ServiceBase'/>.</para>
        ///    This is called from <see cref="Run(ServiceBase[])"/> when all
        ///    services in the process have entered the SERVICE_STOPPED state.
        /// </summary>
        protected override void Dispose(bool disposing)
        {
            _nameFrozen = false;
            _commandPropsFrozen = false;
            _disposed = true;
            base.Dispose(disposing);
        }

        /// <summary>
        ///    <para> 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.</para>
        /// </summary>
        protected virtual void OnContinue()
        {
        }

        /// <summary>
        ///    <para> 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.</para>
        /// </summary>
        protected virtual void OnPause()
        {
        }

        /// <summary>
        ///    <para>
        ///         When implemented in a derived class, executes when the computer's
        ///         power status has changed.
        ///    </para>
        /// </summary>
        protected virtual bool OnPowerEvent(PowerBroadcastStatus powerStatus)
        {
            return true;
        }

        /// <summary>
        ///    <para>When implemented in a derived class,
        ///       executes when a Terminal Server session change event is received.</para>
        /// </summary>
        protected virtual void OnSessionChange(SessionChangeDescription changeDescription)
        {
        }

        /// <summary>
        ///    <para>When implemented in a derived class,
        ///       executes when the system is shutting down.
        ///       Specifies what should
        ///       happen just prior
        ///       to the system shutting down.</para>
        /// </summary>
        protected virtual void OnShutdown()
        {
        }

        /// <summary>
        ///    <para> When implemented in a
        ///       derived class, executes when a Start command is sent
        ///       to the service by the Service
        ///       Control Manager. Specifies the actions to take when the service starts.</para>
        ///    <note type="rnotes">
        ///       Tech review note:
        ///       except that the SCM does not allow passing arguments, so this overload will
        ///       never be called by the SCM in the current version. Question: Is this true even
        ///       when the string array is empty? What should we say, then. Can
        ///       a ServiceBase derived class only be called programmatically? Will
        ///       OnStart never be called if you use the SCM to start the service? What about
        ///       services that start automatically at boot-up?
        ///    </note>
        /// </summary>
        protected virtual void OnStart(string[] args)
        {
        }

        /// <summary>
        ///    <para> 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.</para>
        /// </summary>
        protected virtual void OnStop()
        {
        }

        private unsafe void DeferredContinue()
        {
            fixed (SERVICE_STATUS* pStatus = &_status)
            {
                try
                {
                    OnContinue();
                    WriteLogEntry(SR.ContinueSuccessful);
                    _status.currentState = ServiceControlStatus.STATE_RUNNING;
                }
                catch (Exception e)
                {
                    _status.currentState = ServiceControlStatus.STATE_PAUSED;
                    WriteLogEntry(SR.Format(SR.ContinueFailed, e), EventLogEntryType.Error);

                    // We re-throw the exception so that the advapi32 code can report
                    // ERROR_EXCEPTION_IN_SERVICE as it would for native services.
                    throw;
                }
                finally
                {
                    SetServiceStatus(_statusHandle, pStatus);
                }
            }
        }

        private void DeferredCustomCommand(int command)
        {
            try
            {
                OnCustomCommand(command);
                WriteLogEntry(SR.CommandSuccessful);
            }
            catch (Exception e)
            {
                WriteLogEntry(SR.Format(SR.CommandFailed, e), EventLogEntryType.Error);

                // We should re-throw the exception so that the advapi32 code can report
                // ERROR_EXCEPTION_IN_SERVICE as it would for native services.
                throw;
            }
        }

        private unsafe void DeferredPause()
        {
            fixed (SERVICE_STATUS* pStatus = &_status)
            {
                try
                {
                    OnPause();
                    WriteLogEntry(SR.PauseSuccessful);
                    _status.currentState = ServiceControlStatus.STATE_PAUSED;
                }
                catch (Exception e)
                {
                    _status.currentState = ServiceControlStatus.STATE_RUNNING;
                    WriteLogEntry(SR.Format(SR.PauseFailed, e), EventLogEntryType.Error);

                    // We re-throw the exception so that the advapi32 code can report
                    // ERROR_EXCEPTION_IN_SERVICE as it would for native services.
                    throw;
                }
                finally
                {
                    SetServiceStatus(_statusHandle, pStatus);
                }
            }
        }

        private void DeferredPowerEvent(int eventType)
        {
            // Note: The eventData pointer might point to an invalid location
            // This might happen because, between the time the eventData ptr was
            // captured and the time this deferred code runs, the ptr might have
            // already been freed.
            try
            {
                OnPowerEvent((PowerBroadcastStatus)eventType);

                WriteLogEntry(SR.PowerEventOK);
            }
            catch (Exception e)
            {
                WriteLogEntry(SR.Format(SR.PowerEventFailed, e), EventLogEntryType.Error);

                // We rethrow the exception so that advapi32 code can report
                // ERROR_EXCEPTION_IN_SERVICE as it would for native services.
                throw;
            }
        }

        private void DeferredSessionChange(int eventType, int sessionId)
        {
            try
            {
                OnSessionChange(new SessionChangeDescription((SessionChangeReason)eventType, sessionId));
            }
            catch (Exception e)
            {
                WriteLogEntry(SR.Format(SR.SessionChangeFailed, e), EventLogEntryType.Error);

                // We rethrow the exception so that advapi32 code can report
                // ERROR_EXCEPTION_IN_SERVICE as it would for native services.
                throw;
            }
        }

        // We mustn't call OnStop directly from the command callback, as this will
        // tie up the command thread for the duration of the OnStop, which can be lengthy.
        // This is a problem when multiple services are hosted in a single process.
        private unsafe void DeferredStop()
        {
            lock (_stopLock)
            {
                // never call SetServiceStatus again after STATE_STOPPED is set.
                if (_status.currentState != ServiceControlStatus.STATE_STOPPED)
                {
                    fixed (SERVICE_STATUS* pStatus = &_status)
                    {
                        int previousState = _status.currentState;

                        _status.checkPoint = 0;
                        _status.waitHint = 0;
                        _status.currentState = ServiceControlStatus.STATE_STOP_PENDING;
                        SetServiceStatus(_statusHandle, pStatus);
                        try
                        {
                            OnStop();
                            WriteLogEntry(SR.StopSuccessful);
                            _status.currentState = ServiceControlStatus.STATE_STOPPED;
                            SetServiceStatus(_statusHandle, pStatus);
                        }
                        catch (Exception e)
                        {
                            _status.currentState = previousState;
                            SetServiceStatus(_statusHandle, pStatus);
                            WriteLogEntry(SR.Format(SR.StopFailed, e), EventLogEntryType.Error);
                            throw;
                        }
                    }
                }
            }
        }

        private unsafe void DeferredShutdown()
        {
            try
            {
                OnShutdown();
                WriteLogEntry(SR.ShutdownOK);

                lock (_stopLock)
                {
                    if (_status.currentState == ServiceControlStatus.STATE_PAUSED || _status.currentState == ServiceControlStatus.STATE_RUNNING)
                    {
                        fixed (SERVICE_STATUS* pStatus = &_status)
                        {
                            _status.checkPoint = 0;
                            _status.waitHint = 0;
                            _status.currentState = ServiceControlStatus.STATE_STOPPED;
                            SetServiceStatus(_statusHandle, pStatus);
                        }
                    }
                }
            }
            catch (Exception e)
            {
                WriteLogEntry(SR.Format(SR.ShutdownFailed, e), EventLogEntryType.Error);
                throw;
            }
        }

        /// <summary>
        /// <para>When implemented in a derived class, <see cref='System.ServiceProcess.ServiceBase.OnCustomCommand'/>
        /// executes when a custom command is passed to the service. Specifies the actions to take when
        /// a command with the specified parameter value occurs.</para>
        /// </summary>
        protected virtual void OnCustomCommand(int command)
        {
        }

        /// <summary>
        ///    <para>Provides the main entry point for an executable that
        ///       contains multiple associated services. Loads the specified services into memory so they can be
        ///       started.</para>
        /// </summary>
        public static unsafe void Run(ServiceBase[] services)
        {
            if (services == null || services.Length == 0)
                throw new ArgumentException(SR.NoServices);

            IntPtr entriesPointer = Marshal.AllocHGlobal(checked((services.Length + 1) * sizeof(SERVICE_TABLE_ENTRY)));
            Span<SERVICE_TABLE_ENTRY> entries = new Span<SERVICE_TABLE_ENTRY>((void*)entriesPointer, services.Length + 1);
            entries.Clear();
            try
            {
                bool multipleServices = services.Length > 1;

                // The members of the last entry in the table must have NULL values to designate the end of the table.
                // Leave the last element in the entries span to be zeroed out.
                for (int index = 0; index < services.Length; ++index)
                {
                    ServiceBase service = services[index];
                    service.Initialize(multipleServices);
                    // This method allocates on unmanaged heap; Make sure that the contents are freed after use.
                    entries[index] = service.GetEntry();
                }

                // While the service is running, this function will never return. It will return when the service
                // is stopped.
                // After it returns, SCM might terminate the process at any time
                // (so subsequent code is not guaranteed to run).
                bool res = StartServiceCtrlDispatcher(entriesPointer);

                foreach (ServiceBase service in services)
                {
                    // 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;

                if (!res)
                {
                    errorMessage = new Win32Exception().Message;
                    Console.WriteLine(SR.CantStartFromCommandLine);
                }

                foreach (ServiceBase service in services)
                {
                    service.Dispose();
                    if (!res)
                    {
                        service.WriteLogEntry(SR.Format(SR.StartFailed, errorMessage), EventLogEntryType.Error);
                    }
                }
            }
            finally
            {
                // Free the pointer to the name of the service on the unmanaged heap.
                for (int i = 0; i < entries.Length; i++)
                {
                    Marshal.FreeHGlobal(entries[i].name);
                }

                // Free the unmanaged array containing the entries.
                Marshal.FreeHGlobal(entriesPointer);
            }
        }

        /// <summary>
        ///    <para>Provides the main
        ///       entry point for an executable that contains a single
        ///       service. Loads the service into memory so it can be
        ///       started.</para>
        /// </summary>
        public static void Run(ServiceBase service)
        {
            if (service == null)
                throw new ArgumentException(SR.NoServices);

            Run(new ServiceBase[] { service });
        }

        public void Stop()
        {
            DeferredStop();
        }

        private void Initialize(bool multipleServices)
        {
            if (!_initialized)
            {
                //Cannot register the service with NT service manager if the object has been disposed, since finalization has been suppressed.
                if (_disposed)
                    throw new ObjectDisposedException(GetType().Name);

                if (!multipleServices)
                {
                    _status.serviceType = ServiceTypeOptions.SERVICE_WIN32_OWN_PROCESS;
                }
                else
                {
                    _status.serviceType = ServiceTypeOptions.SERVICE_WIN32_SHARE_PROCESS;
                }

                _status.currentState = ServiceControlStatus.STATE_START_PENDING;
                _status.controlsAccepted = 0;
                _status.win32ExitCode = 0;
                _status.serviceSpecificExitCode = 0;
                _status.checkPoint = 0;
                _status.waitHint = 0;

                _mainCallback = ServiceMainCallback;
                _commandCallbackEx = this.ServiceCommandCallbackEx;

                _initialized = true;
            }
        }

        // Make sure that the name field is freed after use. We allocate a new string to avoid holding one central handle,
        // which may lead to dangling pointer if Dispose is called in other thread.
        private SERVICE_TABLE_ENTRY GetEntry()
        {
            _nameFrozen = true;
            return new SERVICE_TABLE_ENTRY()
            {
                callback = Marshal.GetFunctionPointerForDelegate(_mainCallback!),
                name = Marshal.StringToHGlobalUni(_serviceName)
            };
        }

        private int ServiceCommandCallbackEx(int command, int eventType, IntPtr eventData, IntPtr eventContext)
        {
            switch (command)
            {
                case ControlOptions.CONTROL_POWEREVENT:
                    {
                        ThreadPool.QueueUserWorkItem(_ => DeferredPowerEvent(eventType));
                        break;
                    }

                case ControlOptions.CONTROL_SESSIONCHANGE:
                    {
                        // The eventData pointer can be released between now and when the DeferredDelegate gets called.
                        // So we capture the session id at this point
                        WTSSESSION_NOTIFICATION sessionNotification = new WTSSESSION_NOTIFICATION();
                        Marshal.PtrToStructure(eventData, sessionNotification);
                        ThreadPool.QueueUserWorkItem(_ => DeferredSessionChange(eventType, sessionNotification.sessionId));
                        break;
                    }

                default:
                    {
                        ServiceCommandCallback(command);
                        break;
                    }
            }

            return 0;
        }

        /// <summary>
        ///     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.
        /// </summary>
        /// <internalonly/>
        private unsafe void ServiceCommandCallback(int command)
        {
            fixed (SERVICE_STATUS* pStatus = &_status)
            {
                if (command == ControlOptions.CONTROL_INTERROGATE)
                    SetServiceStatus(_statusHandle, pStatus);
                else if (_status.currentState != ServiceControlStatus.STATE_CONTINUE_PENDING &&
                    _status.currentState != ServiceControlStatus.STATE_START_PENDING &&
                    _status.currentState != ServiceControlStatus.STATE_STOP_PENDING &&
                    _status.currentState != ServiceControlStatus.STATE_PAUSE_PENDING)
                {
                    switch (command)
                    {
                        case ControlOptions.CONTROL_CONTINUE:
                            if (_status.currentState == ServiceControlStatus.STATE_PAUSED)
                            {
                                _status.currentState = ServiceControlStatus.STATE_CONTINUE_PENDING;
                                SetServiceStatus(_statusHandle, pStatus);

                                ThreadPool.QueueUserWorkItem(_ => DeferredContinue());
                            }

                            break;

                        case ControlOptions.CONTROL_PAUSE:
                            if (_status.currentState == ServiceControlStatus.STATE_RUNNING)
                            {
                                _status.currentState = ServiceControlStatus.STATE_PAUSE_PENDING;
                                SetServiceStatus(_statusHandle, pStatus);

                                ThreadPool.QueueUserWorkItem(_ => DeferredPause());
                            }

                            break;

                        case ControlOptions.CONTROL_STOP:
                            int previousState = _status.currentState;
                            //
                            // Can't perform all of the service shutdown logic from within the command callback.
                            // This is because there is a single ScDispatcherLoop for the entire process.  Instead, we queue up an
                            // asynchronous call to "DeferredStop", and return immediately.  This is crucial for the multiple service
                            // per process scenario, such as the new managed service host model.
                            //
                            if (_status.currentState == ServiceControlStatus.STATE_PAUSED || _status.currentState == ServiceControlStatus.STATE_RUNNING)
                            {
                                _status.currentState = ServiceControlStatus.STATE_STOP_PENDING;
                                SetServiceStatus(_statusHandle, pStatus);
                                // Set our copy of the state back to the previous so that the deferred stop routine
                                // can also save the previous state.
                                _status.currentState = previousState;

                                ThreadPool.QueueUserWorkItem(_ => DeferredStop());
                            }

                            break;

                        case ControlOptions.CONTROL_SHUTDOWN:
                            //
                            // Same goes for shutdown -- this needs to be very responsive, so we can't have one service tying up the
                            // dispatcher loop.
                            //
                            ThreadPool.QueueUserWorkItem(_ => DeferredShutdown());
                            break;

                        default:
                            ThreadPool.QueueUserWorkItem(_ => DeferredCustomCommand(command));
                            break;
                    }
                }
            }
        }

        // Need to execute the start method on a thread pool thread.
        // Most applications will start asynchronous operations in the
        // OnStart method. If such a method is executed in MainCallback
        // thread, the async operations might get canceled immediately.
        private void ServiceQueuedMainCallback(object state)
        {
            string[] args = (string[])state;

            try
            {
                OnStart(args);
                WriteLogEntry(SR.StartSuccessful);
                _status.checkPoint = 0;
                _status.waitHint = 0;
                _status.currentState = ServiceControlStatus.STATE_RUNNING;
            }
            catch (Exception e)
            {
                WriteLogEntry(SR.Format(SR.StartFailed, e), EventLogEntryType.Error);
                _status.currentState = ServiceControlStatus.STATE_STOPPED;

                // We capture the exception so that it can be propagated
                // from ServiceBase.Run.
                // We also use the presence of this exception to inform SCM
                // that the service failed to start successfully.
                _startFailedException = ExceptionDispatchInfo.Capture(e);
            }
            _startCompletedSignal!.Set();
        }

        /// <summary>
        ///     ServiceMain callback is called by NT .
        ///     It is expected that we register the command handler,
        ///     and start the service at this point.
        /// </summary>
        /// <internalonly/>
        [EditorBrowsable(EditorBrowsableState.Never)]
        public unsafe void ServiceMainCallback(int argCount, IntPtr argPointer)
        {
            fixed (SERVICE_STATUS* pStatus = &_status)
            {
                string[]? args = null;

                if (argCount > 0)
                {
                    char** argsAsPtr = (char**)argPointer;

                    // The first arg is always the service name. We don't want to pass that in,
                    // but we can use it to set the service name on ourselves if we don't already know it.
                    if (string.IsNullOrEmpty(_serviceName))
                    {
                        _serviceName = Marshal.PtrToStringUni((IntPtr)(*argsAsPtr))!;
                    }

                    args = new string[argCount - 1];

                    for (int index = 0; index < args.Length; ++index)
                    {
                        // we increment the pointer first so we skip over the first argument.
                        argsAsPtr++;
                        args[index] = Marshal.PtrToStringUni((IntPtr)(*argsAsPtr))!;
                    }
                }

                // If we are being hosted, then Run will not have been called, since the EXE's Main entrypoint is not called.
                if (!_initialized)
                {
                    Initialize(true);
                }

                _statusHandle = RegisterServiceCtrlHandlerEx(ServiceName, _commandCallbackEx, (IntPtr)0);

                _nameFrozen = true;
                if (_statusHandle == (IntPtr)0)
                {
                    string errorMessage = new Win32Exception().Message;
                    WriteLogEntry(SR.Format(SR.StartFailed, errorMessage), EventLogEntryType.Error);
                }

                _status.controlsAccepted = _acceptedCommands;
                _commandPropsFrozen = true;
                if ((_status.controlsAccepted & AcceptOptions.ACCEPT_STOP) != 0)
                {
                    _status.controlsAccepted |= AcceptOptions.ACCEPT_SHUTDOWN;
                }

                _status.currentState = ServiceControlStatus.STATE_START_PENDING;

                bool statusOK = SetServiceStatus(_statusHandle, pStatus);

                if (!statusOK)
                {
                    return;
                }

                // Need to execute the start method on a thread pool thread.
                // Most applications will start asynchronous operations in the
                // OnStart method. If such a method is executed in the current
                // thread, the async operations might get canceled immediately
                // since NT will terminate this thread right after this function
                // finishes.
                _startCompletedSignal = new ManualResetEvent(false);
                _startFailedException = null;
                ThreadPool.QueueUserWorkItem(new WaitCallback(this.ServiceQueuedMainCallback!), args);
                _startCompletedSignal.WaitOne();

                if (_startFailedException != null)
                {
                    // Inform SCM that the service could not be started successfully.
                    // (Unless the service has already provided another failure exit code)
                    if (_status.win32ExitCode == 0)
                    {
                        _status.win32ExitCode = ServiceControlStatus.ERROR_EXCEPTION_IN_SERVICE;
                    }
                }

                statusOK = SetServiceStatus(_statusHandle, pStatus);
                if (!statusOK)
                {
                    string errorMessage = new Win32Exception().Message;
                    WriteLogEntry(SR.Format(SR.StartFailed, errorMessage), EventLogEntryType.Error);
                    lock (_stopLock)
                    {
                        if (_status.currentState != ServiceControlStatus.STATE_STOPPED)
                        {
                            _status.currentState = ServiceControlStatus.STATE_STOPPED;
                            SetServiceStatus(_statusHandle, pStatus);
                        }
                    }
                }
            }
        }

        private void WriteLogEntry(string message, EventLogEntryType type = EventLogEntryType.Information)
        {
            // EventLog failures shouldn't affect the service operation
            try
            {
                if (AutoLog)
                {
                    EventLog.WriteEntry(message, type);
                }
            }
            catch
            {
                // Do nothing.  Not having the event log is bad, but not starting the service as a result is worse.
            }
        }
    }
}