3

I wrote a C# windows service running under the local system account. I need to do something when the system shuts down, and hooking to the pre-shutdown event works fine.

I am accepting the preshutdown command by setting the right flag:

FieldInfo acceptedCommandsFieldInfo = typeof(ServiceBase).GetField("acceptedCommands", BindingFlags.Instance | BindingFlags.NonPublic);
                if (acceptedCommandsFieldInfo == null)
                {
                    throw new Exception("acceptedCommands field not found");
                }
                int value = (int)acceptedCommandsFieldInfo.GetValue(service);
                acceptedCommandsFieldInfo.SetValue(service, value | SERVICE_ACCEPT_PRESHUTDOWN);

And performing my last steps when i receive the preshutdown command.

This works, i am able to perform my logic every time, i know that the preshutdown event gives you 20 seconds. My problem is the following: my code finishes in less than 1 second (don't need to do much, i just need to make sure i'm doing that on shutting down), however,the system is still waiting for my service to finish for 20 seconds.

Can see that by activating the "verbose" mode of windows 10, which can be enabled by adding the "verbosestatus" registry entry with a value of "1" (DWORD value) at this path: HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System

I have been searching for 2 days and i can't seem to find a way to tell the system that my code has finished and it shouldn't wait for the remaining ~19 seconds for it, and just shut down.

Sadly, this is time consuming to test as well, since you need to shut down your machine or VM to catch this preshutdown flow.

Did anybody encounter this problem? There should be some kind of cancelling event i assume, i just can't seem to find it.

  • Posted the question from a new user, if you need any additional answer let me know. – Darksody Mar 20 '20 at 07:04
  • Do you start any threads? Marking them with `myThread.IsBackground = true` on creation may help. If this does not solve your issue, you need to take a dump – Oguz Ozgul Mar 20 '20 at 07:04
  • I am not starting any threads specifically, but i am using the TPL, so there probably are a couple of running Tasks. The tasks do use the threadpool, i'll search for a way to make them run in background though. – Darksody Mar 20 '20 at 07:06
  • Update: i have commented all of my code, so basically i'm just putting the preshutdown flag, and on the preshutdown event i do nothing (i write a log that successfully finishes). No other threads or tasks involved, i commented my OnStart method and everything. Same result. Just by putting the preshutdown flag there makes windows wait 20 seconds for my service. – Darksody Mar 20 '20 at 07:58
  • Update 2: I have created a simple windows service running under local system that does nothing but write to a file on preshutdown (also commented the code so it literally does nothing and i get the same response. I have uploaded the sample code here: https://github.com/darksody/PreshutdownService – Darksody Mar 20 '20 at 09:26

1 Answers1

0

NOTE: I had to decompile your exe from the GITHub link because you didn't include the cs files.

This is your code...

    /// You are calling this in your constructor
    private void RegisterPreShutdownEvent()
    {
        FieldInfo field = typeof(ServiceBase).GetField("acceptedCommands", BindingFlags.Instance | BindingFlags.NonPublic);
        if (field == null)
        {
            throw new Exception("acceptedCommands field not found");
        }
        int value = (int)field.GetValue(this);
        field.SetValue(this, value | 256);
    }

    /// And processing the result here
    protected override void OnCustomCommand(int command)
    {
        base.OnCustomCommand(command);
        if (command == 15)
        {
            this.OnPreShutdown();
        }
    }

I don't think that your solution for implementation of PRESHUTDOWN is sufficient. You are tying into the event and getting notified of PreShutdown, but you aren't doing anything to change your service's status when complete. I took a closer look at the implementation of OnCustomCommand that you are utilizing to trigger shutdown using a decompiled copy of ServiceBase.

private void DeferredCustomCommand(int command)
    {
        try
        {
            this.OnCustomCommand(command);
            this.WriteEventLogEntry("Service command was processed successfully.");
        }
        catch (Exception exception)
        {
            this.WriteEventLogEntry(string.Format("Failed to process service command. {0}", exception), EventLogEntryType.Error);
            throw;
        }
    }

Where a normal shutdown, handled by DeferredShutdown() looks like this. Notice that it raises the OnShutdown event, but then also checks and sets the service state.

    private unsafe void DeferredShutdown()
    {
        try
        {
            this.OnShutdown();
            this.WriteEventLogEntry("Service has been successfully shut down.");
            if ((this.status.currentState == SERVICE_PAUSED) || (this.status.currentState == SERVICE_RUNNING))
            {
                fixed (NativeMethods.SERVICE_STATUS* service_statusRef = &this.status)
                {
                    this.status.checkPoint = 0;
                    this.status.waitHint = 0;
                    this.status.currentState = SERVICE_STOPPED;
                    NativeMethods.SetServiceStatus(this.statusHandle, service_statusRef);
                    if (this.isServiceHosted)
                    {
                        try
                        {
                            AppDomain.Unload(AppDomain.CurrentDomain);
                        }
                        catch (CannotUnloadAppDomainException exception)
                        {
                            this.WriteEventLogEntry(string.Format("Failed to unload app domain {0}.  The following exception occurred: {1}.", AppDomain.CurrentDomain.FriendlyName, exception.Message), EventLogEntryType.Error);
                        }
                    }
                }
            }
        }
        catch (Exception exception2)
        {
            this.WriteEventLogEntry(string.Format("Failed to shut down service. The error that occurred was: {0}.", exception2), EventLogEntryType.Error);
            throw;
        }
    }

I don't have a precise answer of how to fix this. I did find this: https://www.atomia.com/2011/11/16/how-to-process-the-preshutdown-event-in-a-managed-windows-service/ which looks like it might work. There difference here is that they are forcing a call to the DeferredShutdown() which will raise your service class' OnShutdown() event and properly update the service state when complete. Here is the code from their solution:

    internal virtual int MyServiceControlCallbackEx(int control, int eventType, IntPtr eventData, IntPtr eventContext)
    {
        var baseCallback = typeof(ServiceBase).GetMethod("ServiceCommandCallbackEx", BindingFlags.Instance | BindingFlags.NonPublic);
        switch (control)
        {
            case 0x0000000F: //pre shutdown
                             // now pretend shutdown was called
                return (int)baseCallback.Invoke(this, new object[] { 0x00000005, eventType, eventData, eventContext });
            default:
                // call base
                return (int)baseCallback.Invoke(this, new object[] { control, eventType, eventData, eventContext });
        }
    }

    internal virtual void MyInitialize(bool multipleServices)
    {
        // call base
        var init = typeof(ServiceBase).GetMethod("Initialize", BindingFlags.Instance | BindingFlags.NonPublic);
        init.Invoke(this, new object[] { multipleServices });

        // register our handler
        var targetDelegate =
        typeof(ServiceBase).Assembly.GetType("System.ServiceProcess.NativeMethods+ServiceControlCallbackEx");

        var commandCallbackEx = typeof(ServiceBase).GetField("commandCallbackEx", BindingFlags.Instance | BindingFlags.NonPublic);
        commandCallbackEx.SetValue(this, Delegate.CreateDelegate(targetDelegate, this, "MyServiceControlCallbackEx"));

        // accept preshutdown command
        var acceptedCommands = typeof(ServiceBase).GetField("acceptedCommands", BindingFlags.Instance | BindingFlags.NonPublic);
        int accCom = (int)acceptedCommands.GetValue(this);
        acceptedCommands.SetValue(this, accCom | 0x00000100);
    }

To use this you would add these methods into your service class and call MyInitialize from your constructor and then override OnShutdown() with your code that you want to execute. I think that they also recommend that you set the CanShutdown property to false in your service class.

CactusPCJack
  • 459
  • 5
  • 18