What is the proper way for a Windows service to fail?
Best practice in native code is to call SetServiceStatus with a non-zero exit code to indicate 1) it's stopped and 2) something went wrong.
In managed code, you could achieve the same effect by obtaining the SCM handle through the ServiceBase.ServiceHandle Property and P/Invoke-ing the Win32 API.
I don't see why the SCM would treat this any differently than setting the ServiceBase.ExitCode
property non-zero and then calling ServiceBase.Stop
, actually. P/Invoke is a bit more direct perhaps, if the service is in panic mode.
As noted in the comments (also see https://serverfault.com/questions/72318/set-up-recovery-actions-to-take-place-when-a-service-fails) if a process calls SetServiceStatus(SERVICE_STOPPED)
with a non-zero exit code, the Recovery Actions for the serice will only be done if the option "Enable Actions For Stops With Errors" (sc.exe failureflag
) is ticked. -> System Event ID 7024
If a service process exits (Env.Exit()
) or crashs without consulting the SCM, then the Recovery Actions will always be run. -> System Event ID 7031
I've found that Environment.Exit(1) works fine for me. I generally place it in a method that catches unhandled exceptions and log the problem before I stop it. It completely destroys the service, but the SCM also knows that it is shutdown. You can set the SCM to restart your service automatically when it goes down x amount of times. I find this is far more useful than writing your own restart/shutdown code.
I don't know if there is a (non-P/Invoke) equivalent for this, but the WinAPI way seems to be to call SetServiceStatus
with a value of SERVICE_STOPPED
and then wait for the SCM to shut you down. As a positive side-effect, it logs the failure of your service into the event log.
Here are some quotes from the relevant part of the documentation:
If a service calls SetServiceStatus with the dwCurrentState member set to SERVICE_STOPPED and the dwWin32ExitCode member set to a nonzero value, the following entry is written into the System event log:
[...] <ServiceName> terminated with the following error: <ExitCode> [...]
The following are best practices when calling this function:
[...]
- If the status is SERVICE_STOPPED, perform all necessary cleanup and call SetServiceStatus one time only. This function makes an LRPC call to the SCM. The first call to the function in the SERVICE_STOPPED state closes the RPC context handle and any subsequent calls can cause the process to crash.
- Do not attempt to perform any additional work after calling SetServiceStatus with SERVICE_STOPPED, because the service process can be terminated at any time.
PS: In my opinion, if network resources are unavailable, the service should not stop but continue running, waiting for the resources to become available. Temporary network outages can happen, and they should not require manual intervention from the system administrator once the network is back up.
After some testing I found the following works in cases where you calling Stop might cause other issues:
ExitCode = 1;
Environment.Exit(1);
Just calling Environment.Exit doesn't make the SCM do fault handling, but first setting the ServiceBase ExitCode does.