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.