Logo
blank Skip to main content

Handling OS Shutdown Events with WinAPI

C++
API

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.

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

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:

C
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:

C
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.

C
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%.

Project details
Developing a Custom Secrets Management Desktop Application for Secure Password Sharing and Storage

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:

C
//...
    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:

C
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:

C
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:

C
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.

Learn more

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:

C
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:

C
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:

C
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:

C
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.

Learn more
Comparison of API hooking libraries

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:

C
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:

C
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!

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!

Have a question?

Ask our expert!

Tell us about
your project

...And our team will:

  • Process your request within 1-2 business days.
  • Get back to you with an offer based on your project's scope and requirements.
  • Set a call to discuss your future project in detail and finalize the offer.
  • Sign a contract with you to start working on your project.

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.