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.
Contents:
OS shutdown detection: why and how to implement it
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.
Does your project require precise control of OS processes?
As an experienced company with 20+ years in system development, we are ready to help you adjust the behavior of any operating system to your needs.
Console applications
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:
BOOL WINAPI HandlerRoutine(_In_ DWORD dwCtrlType);
Next, we need to register this function as a handler for our console application with the following API function:
BOOL WINAPI SetConsoleCtrlHandler(_In_opt_ PHANDLER_ROUTINE HandlerRoutine, _In_ BOOL Add);
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 TRUE
.
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.
BOOL WINAPI HandlerRoutine(DWORD dwCtrlType)
{
switch (dwCtrlType)
{
case CTRL_SHUTDOWN_EVENT:
//Computer is shutting down
return TRUE;
case CTRL_LOGOFF_EVENT:
//Current user logs off
return TRUE;
default:
//We don't care about this event
//Default handler is used
}
return FALSE;
}
int _tmain(int argc, _TCHAR* argv[])
{
SetConsoleCtrlHandler(HandlerRoutine, TRUE); //adding HandlerRoutine to the list of handlers
getchar(); //simulates work
return 0;
}
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.
Related project
Developing a Custom Secrets Management Desktop Application for Secure Password Sharing and Storage
Explore the story of creating a cross-platform secret management solution for Windows, macOS, and Linux. Deploying this solution helped our client to improve the security score of the company by 30%.
GUI applications
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.
GUI applications receive information about target events via window messages. Thatโs why we need WM_QUERYENDSESSION and WM_POWERBROADCAST messages to enable a GUI app to detect an OS shutdown.
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:
//...
case WM_POWERBROADCAST:
{
if (wParam == PBT_APMSUSPEND)
//Computer is suspending
break;
}
case WM_QUERYENDSESSION:
{
if (lParam == 0)
//Computer is shutting down
if ((lParam & ENDSESSION_LOGOFF) == ENDSESSION_LOGOFF)
//User is logging off
break;
}
//...
The wParam
and 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:
case WM_QUERYENDSESSION:
{
if (lParam == 0)
{
//Computer is shutting down
ShutdownBlockReasonCreate(hwnd, _T("Please, don't kill me"));
}
break;
}
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:
DWORD dwLevel = 0;
DWORD dwFlags = 0;
if (GetProcessShutdownParameters(&dwLevel, &dwFlags))
SetProcessShutdownParameters(dwLevel, SHUTDOWN_NORETRY);
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:
case WM_QUERYENDSESSION:
{
if (lParam == 0)
{
//Computer is shutting down
}
if ((lParam & ENDSESSION_LOGOFF) == ENDSESSION_LOGOFF)
{
//User is logging off
}
return DefWindowProc(hwnd, msg, wParam, lParam);
}
Now, letโs take a look at WM_POWERBROADCAST. This message helps us detect suspending OS operation events with wParam ==
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.
Read also
A Comprehensive Guide to Hooking Windows APIs with Python
Discover how a programming language influences Windows API hooking, how to inject Python code into API function, and which Python libraries to use to hook APIs.
Windows services
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:
SERVICE_STATUS_HANDLE WINAPI RegisterServiceCtrlHandlerEx(
_In_ LPCTSTR lpServiceName,
_In_ LPHANDLER_FUNCTION_EX lpHandlerProc,
_In_opt_ LPVOID lpContext
);
In this code, HandlerProc parameter syntax looks like this:
DWORD WINAPI HandlerEx(
_In_ DWORD dwControl,
_In_ DWORD dwEventType,
_In_ LPVOID lpEventData,
_In_ LPVOID lpContext
);
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:
g_ServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_POWEREVENT | SERVICE_ACCEPT_SESSIONCHANGE | SERVICE_ACCEPT_SHUTDOWN;
g_ServiceStatus.dwCurrentState = SERVICE_RUNNING;
g_ServiceStatus.dwWin32ExitCode = 0;
g_ServiceStatus.dwCheckPoint = 0;
SetServiceStatus (g_StatusHandle, &g_ServiceStatus);
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 powerevent
of 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 sessionchange
.
Here is an example of our handler implementation:
DWORD WINAPI CtrlHandlerEx( DWORD CtrlCode, DWORD eventType,
LPVOID eventData, LPVOID context)
{
switch (CtrlCode)
{
case SERVICE_CONTROL_STOP :
{
//Handle service stops
return NO_ERROR;
}
case SERVICE_CONTROL_POWEREVENT:
{
if (eventType == PBT_APMQUERYSUSPEND)
{
//Computer is suspending
}
return NO_ERROR;
}
case SERVICE_CONTROL_SESSIONCHANGE:
{
switch (eventType)
{
case WTS_SESSION_LOGOFF:
//User is logging off
break;
case WTS_SESSION_LOCK:
//User locks the session
break;
}
return NO_ERROR;
}
case SERVICE_CONTROL_SHUTDOWN:
{
//Computer is shutting down
return NO_ERROR;
}
default:
{
//An event that we don't handle
}
}
return ERROR_CALL_NOT_IMPLEMENTED;
}
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.
Read also
Practical Comparison of the Most Popular API Hooking Libraries: Microsoft Detours, EasyHook, Nektra Deviare, and Mhook
Sometimes your team might need to change or augment the OS behavior for cybersecurity purposes. To ensure that, they need to master hooking API function calls with the four most popular libraries. Explore the pros and cons of using each library along with practical code examples in this comprehensive article.
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:
g_ServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_POWEREVENT | SERVICE_ACCEPT_SESSIONCHANGE | SERVICE_ACCEPT_PRESHUTDOWN;
If we catch SERVICE_CONTROL_PRESHUTDOWN, we can perform certain actions in the handler:
case SERVICE_CONTROL_PRESHUTDOWN:
{
g_logger.WriteToLog("Preshutdown is detected");
Sleep(30000); //simulating long work
return NO_ERROR;
}
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.
Conclusion
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!
Consider developing a Windows project with unusual behavior?
Delegate the most tricky tasks to our experienced system development team. Let’s discuss how we can help your bring the project to life!