When a user shuts down a computer or ends a session, there’s always a risk that an active application won’t save the latest changes or won’t end operations properly. This may result in the loss of users’ data and your spending extra resources to restore it.
To avoid this scenario, you can teach your application or service to detect operating system (OS) shutdown and user log-off events. This will allow your application to stop its work correctly or ask the user to delay the shutdown to avoid data loss. In this article, we show you how to handle OS shutdown events for console apps, graphical user interface (GUI) applications, and services with the help of the Windows API.
This article will be useful for development teams that work on Windows software and are looking for ways to make it save or record data before the operating system shuts down.
Applications need to detect OS shutdowns to complete short but very important actions:
- Save all unsaved data
- Notify network components that the computer will be shut off
- Log the shutdown in order to analyze it later
The high-level process of detecting a system shutdown is the same for all types of Windows applications and services. To detect a shutdown, we create a callback function and register it in the system. When a certain Windows event occurs, the system calls the callback function, transferring information about the event via input parameters. The callback function then analyzes the input parameter data, decides which event has occurred, and executes code accordingly.
There’s no one way to make all types of applications detect an OS shutdown and user log-off. The reason is that different types of applications differ in their syntax and functionality. That’s why in this article we show you three separate tutorials — for console applications, GUI applications, and Windows services. Note that the methods we describe will help you only to detect shutdown and log-off events. To detect OS sleep, you’ll need to implement other mechanisms.
Let’s suppose we have a console application with default handlers that doesn’t detect shutdown and log-off events. In order for the application to do so, we’ll create our own handler function:
Next, we need to register this function as a handler for our console application with the following API function:
Here’s what it will look like in the application code:
If the computer shuts down or the user logs off, the HandlerRoutine function should return TRUE. If this function returns
FALSE, the OS will use the next handler from the list for the console application. Windows will repeat this process until a handler returns
Note that the system launches HandlerRoutine in a separate thread. Therefore, you may need to take extra measures to synchronize your resources across multiple threads.
Note: If a console application loads gdi32.dll or user32.dll libraries or calls functions used in these dlls without working with them directly, Windows considers it a GUI application. In this situation, Microsoft Developer Network (MSDN) recommends using a detector via windows messages. For example, you can create a fake window with zero size. We’ll show you how to implement such a detector in the next section.
MSDN also warns that when processing CTRL_CLOSE_EVENT, CTRL_LOGOFF_EVENT, and CTRL_SHUTDOWN_EVENT signals, console functions and C runtime functions that call them may not work correctly. This happens when the OS calls internal console cleanup routines before executing the process signal handler.
To investigate this topic deeper, feel free to study MSDN’s description of the SetConsoleCtrlHandler function and handling of WinAPI events for console applications. In most cases, after implementing HandlerRoutine, your console application will be able to detect an OS shutdown.
For a GUI application, only shutdown and restart are considered suspending operations. Entering sleep mode isn’t a very important event because it doesn’t change anything in a GUI app’s work. In some cases, we might need to detect sleep mode in order to message other system components that the PC is going to sleep. However, most GUI apps will only need mechanisms to detect OS shutdown and restart. Let’s see how these mechanisms work.
Windows sends the WM_QUERYENDSESSION window message when the user initiates a user session closing process. Shutting a computer down and restarting it also cause a user session to end. Thus, messages about these events are delivered via the same window message.
We use the WM_POWERBROADCAST message to get information about suspending an operation in the system. Here’s how we handle this message in a GUI application:
lParam parameters in WM_POWERBROADCAST contain identifiers of various system events, including shutdown. For the WM_QUERYENDSESSION window message, the
IParam value of 0 indicates a restart or shutdown, while other values indicate other events.
Note that we process shutdown and log-off events separately, since they are not necessarily connected.
What can we do after we receive WM_QUERYENDSESSION?
If we don’t do anything, Windows shows a warning message saying These applications are preventing shutdown and the user can either cancel the shutdown or forcibly continue it, regardless of the waiting applications. In such cases, our application can behave in one of two ways:
- Close it to let the system shut down immediately
- Show a warning message explaining to users why they shouldn’t reboot right now
The last option is possible for Windows Vista and later versions of Windows. Here’s how it looks:
For the user, the message will look like this:
If we don’t want our application to be shown in the list of apps that block shutdown (for example, if we need to hide it from users), we can implement the SetProcessShutdownParameters function. In this function, pay special attention to the
dwLevel parameter. If we change its value to
SHUTDOWN_NORETRY, our GUI application won’t block the shutdown:
This code wouldn’t work if something other than the default handler called WM_QUERYENDSESSION, however. That’s why we need to modify our window message handler in the following way:
Now, let’s take a look at WM_POWERBROADCAST. This message helps us detect suspending OS operation events with
PBT_APMSUSPEND. Also, from the WM_POWERBROADCAST window message, we can get different variants of the resume event and power setting change (for example, when the user changes the power plan).
That’s how you can use WM_QUERYENDSESSION and WM_POWERBROADCAST to detect OS shutdown in a GUI application. Now, let’s take a look at the same process for a Windows service.
The mechanism of detecting necessary events for a Windows service is identical to the method for console applications, but with slightly different syntax and many more features. Let’s take a look at this mechanism using a generic Windows service.
As with console applications, we need to create a callback function and register it as a service control requests handler.
We can do this using two functions:
In our example, we’ll use the second function, as it allows us to process more controls than the first. Here’s how we implement RegisterServiceCtrlHandlerEx in our service:
In this code, HandlerProc parameter syntax looks like this:
For more information on controls managed by the HandlerProc parameter, explore the official Microsoft documentation for the LPHANDLER_FUNCTION_EX callback function.
We also need to specify the controls we want to process at the start of the service:
If we don’t do this, our handler will not be called for the SERVICE_ACCEPT_STOP and SERVICE_ACCEPT_POWEREVENT controls.
Besides the control code, the handler receives another parameter:
eventType. For some controls, this parameter equals 0 and doesn’t mean anything. But for the SERVICE_CONTROL_POWEREVENT and SERVICE_CONTROL_SESSIONCHANGE controls, for example, the
eventType parameter can be used to define which
sessionchange group event has occurred. The
eventData parameter for these events points to a structure with additional data on this event: the POWERBROADCAST_SETTING structure points to
powerevent and WTSSESSION_NOTIFICATION points to
Here is an example of our handler implementation:
At the main thread, we handle the control by executing the CtrlHandlerEx function. There’s a time limit of approximately 20 seconds for our Windows service to process the SERVICE_CONTROL_SHUTDOWN control. After this, the system will shut down whether or not the service has finished its shutdown preparations. If the service has completed its preparations, it will return
NO_ERROR and close to tell the system not to wait 20 seconds.
If the service performs some long and important activity that doesn’t allow the OS to close the process quickly, we can use another method to handle shutdowns. Instead of the SERVICE_CONTROL_SHUTDOWN control, we can subscribe to SERVICE_CONTROL_PRESHUTDOWN. Note that Windows XP and Windows Server 2003 don’t support this control.
In other Windows OSs, if the service gets the preshutdown notification, the system suspends the shutdown process and doesn’t resume it until either the preshutdown time runs out or the preshutdown control handler has completed its task.
The timeout is defined in the SERVICE_PRESHUTDOWN_INFO structure and is set to 180,000 milliseconds (three minutes) by default. We can set our own value for SERVICE_PRESHUTDOWN_INFO using the ChangeServiceConfig2 function.
Note: Since the service has received SERVICE_CONTROL_PRESHUTDOWN, it no longer can receive SERVICE_CONTROL_SHUTDOWN, and therefore it makes sense to subscribe only to one of the two events.
You can use SERVICE_CONTROL_SHUTDOWN the same way you can use all other structures. Don’t forget to add it to the list of accepted controls:
If we catch SERVICE_CONTROL_PRESHUTDOWN, we can perform certain actions in the handler:
In this case, the message warning about system shutdown will be displayed to the user for 30 seconds.
Since the choice between shutdown and preshutdown influences the user experience, we recommend using it only as a last resort – if there’s a possibility of losing data or if the restoration of the service’s active state and corresponding data takes a long time after a system restart.
In all other cases, it’s best to use SERVICE_CONTROL_SHUTDOWN in the way we described above. It helps a Windows service detect a shutdown and finish its work without disrupting a user’s session.
The ability to detect shutdowns and user log-offs can be crucial for Windows applications that need to save data or end critical processes before closing. In this article, we showed you how to implement this ability into console and GUI applications as well as a Windows service.
Need more expertise on the subject? We have developers with expert knowledge on OS management that are ready to help you improve your solution. Contact us and let’s start discussing your project!