ApriorIT
Handling the OS shutdown event using WinAPI

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

Console applications

GUI applications

Windows services

Conclusion

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
Why detect shutdown events

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.

Solutions to detect shutdown events for three types of software

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:

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;
}

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.

Related services

Operating System Management Solutions

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.

Read also:
How to Take Multi-monitor Screenshots Using WinAPI

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.

Read also:
How to Automate GUI Testing of Windows Apps with Pywinauto: Expert Advice

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:

Warning message for the user

Warning message for the user

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

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.

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.

Read also:
How to Accelerate Microservices Development: A Practical Guide to Applying Code Generation

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.

Why detect shutdown events

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!

Tell us about your project
Send us a request for proposal! We’ll get back to you with details and estimations.

Browse
By clicking Send you give consent to processing your data

Book an Exploratory Call

Do not have any specific task for us in mind but our skills seem interesting?

Get a quick Apriorit intro to better understand our team capabilities.

Contact Us

  • +1 202-780-9339
  • [email protected]
  • 3524 Silverside Road Suite 35B Wilmington, DE 19810-4929 United States
  • D-U-N-S number: 117063762