The Windows NT 6 shutdown process

You've come to this page because you've asked a question similar to the following:

What is the Windows NT version 6 ("Windows Vista") shutdown process ?

This is the Frequently Given Answer to such questions.

The Windows NT 6 shutdown process is largely based around the ExitWindowsEx() Win32 API call. Only at the very end of the process is the kernel requested to perform the actual task of shutting the machine down.

The rôle of ExitWindowsEx()

Unlike many Win32 API calls since Windows NT 4, ExitWindowsEx() is still implemented as a remote procedure call to the CSRSS ("Client-Server Runtime Subsystem") process for the session. This makes the flow of control in the shutdown procedure less obvious than it could be, because many actions are performed by CSRSS acting upon a process' behalf (and hence impersonating the caller's user credentials) rather than directly by the process itself.

ExitWindowsEx() has two behaviours. Either it sends a window message to a window owned by the WINLOGON process for the current session, or it broadcasts end-session messages to all top-level windows of a session. Both behaviours are implemented by CSRSS, but that is an artifact of the client-server design of Win32 on Windows NT, rather than an necessity in its own right. The shutdown process could equally well be regarded as if the behaviour of ExitWindowsEx() were performed directly by the actual process that invoked it.

How users and applications initiate shutdown

Shutdown is initiated in one of three ways: an ordinary application process calls InitiateSystemShutdownEx(), an application process calls ExitWindowsEx() with parameters requesting a system shutdown, or the interactive user in session 1 triggers the Secure Attention Sequence. Both of the former two actually end up simulating the third.

When an ordinary application process calls ExitWindowsEx() to request a shutdown, a logoff, a power-down, a halt, or a reboot, the first behaviour of ExitWindowsEx() is invoked. ExitWindowsEx() (i.e. CSRSS acting upon the calling process' behalf) simply sends a window message to a window owned by the WINLOGON process for the current session to simulate the interactive user choosing the equivalent action from the WINLOGON user interface. So in the case of ExitWindowsEx() being called to initiate a shutdown, a window message is sent to the WINLOGON process for that session, requesting a shutdown, and WINLOGON behaves from that point just as if shutdown had been requested via its user interface.

This is why, incidentally, ExitWindowsEx() only has an effect if the process invoking it is being run in session 1. (Microsoft says "by the interactive user", which can be misleading. This is what it actually means.) ExitWindowsEx() sends a window message to the WINLOGON process for the session in which the invoking process is running. But only the WINLOGON process in session 1 does anything in response to the message, because it is the only session talking to a "local" user; and, indeed, other sessions do not necessarily have WINLOGON processes at all. (Session 0 has WININIT, not WINLOGON.) Therefore, only when a process is runnng in session 1 does ExitWindowsEx() have any actual effect.

The behaviour of InitiateSystemShutdownEx() is similar, albeit slightly more complicated by the fact that shutdowns triggered in this way can be deferred. When an ordinary application process calls InitiateSystemShutdownEx() it connects to the \InitShutdown named pipe and using that pipe performs an RPC to schedule a shutdown. The server for that named pipe is WINLOGON, which handles all of the details of deferring the shutdown, and actually triggering the shutdown at the scheduled time. WINLOGON also allows already scheduled deferred shutdown requests to be cancelled. AbortSystemShutdown() performs an RPC via the same named pipe to request that a previously scheduled shutdown be cancelled.

The function in WINLOGON that handles the "initiate shutdown" request via the named pipe is BaseInitiateShutdownEx(). This checks the client's privilege level, checks the validity of the request, checks to ensure that a shutdown is not already scheduled and that a shutdown has not already begun, checks to see whether WINLOGON is not in the "logged-on and locked" state, writes a record to the audit log, and (if everything succeeds to this point — flags in the request allowing some of the aforementioned checks to be overridden) starts a thread to actually perform the shutdown at the scheduled time.

The thread runs the not-quite-appropriately-named LogoffThreadProc() function. This function waits until the specified time, displaying a "countdown" dialogue box in the interim, and then calls ExitWindowsEx(). This is a normal call to ExitWindowsEx(), which sends a message to WINLOGON to simulate shutdown being requested via its user interface.

WINLOGON has various internal flags and variables that control this deferred shutdown mechanism. The ShutdownInProgress flag records the fact that a shutdown is already scheduled (i.e. the thread has already been invoked). The ShutdownTime variable records the time that the thread will wait until before actually initiating the shutdown. The ShutdownHasBegun records the fact that a shutdown has begun (i.e. the thread has called ExitWindowsEx()). The AbortShutdown flag is set if an "abort shutdown" request comes in via the named pipe or if abort is chosen from the "countdown" dialogue box.

WINLOGON's response to a shutdown request

When WINLOGON receives an interactive (or simulated) shutdown request, it first logs the current user off. (If ExitWindowsEx() had been called requesting a logout, the procedure would end here.) Logging the user off involves sending end-session messages to all of the message queues in the session, sending logoff events to all of the consoles in the session, switching from the user desktop to the login desktop, and effecting an internal change of state to the "no-one logged on" state.

To send end-session messages and logoff events, WINLOGON itself calls ExitWindowsEx(), impersonating the currently logged on user and using secret undocumented parameters to the call that request the second behaviour of ExitWindowsEx(), namely that that end-session messages be broadcast to all of the top-level windows in the current session. ExitWindowsEx() (i.e. CSRSS acting upon WINLOGON's behalf) enumerates all of the top-level windows in the the session, first sending the WM_QUERYENDSESSION window message and then sending the WM_ENDSESSION window message to them. If they are console windows, this in turn causes CSRSS (which is the owner of all console windows) to send the CTRL_LOGOFF_EVENT event to the associated console.

Microsoft's documentation talks about sending messages to threads, implying that all threads in the session that have message queues are enumerated. In fact, ExitWindowsEx() takes the easy option and enumerates top-level windows instead, which is a lot simpler to do. Thus if a single-threaded application has two top-level windows, it will receive the end-session window messages twice, once per window. Similarly, even if a thread has a message loop, it won't receive end-session messages unless the program has created a top-level window. These are both contrary to what Microsoft's documentation implies.

It is CSRSS that implements the timeouts, waiting for a response, and that displays the "end task" dialogue boxes for unresponsive processes.

The shutdown process can potentially become stuck forever at this point, if the user does not close the dialogue boxes. CSRSS knows that it is in the middle of processing an ExitWindowsEx() broadcast, and so responds with a failure result code if any process in the session (including WINLOGON) calls ExitWindowsEx() again. The current broadcast sequence must be cancelled before CSRSS allows another one to be triggered. (Note that if ExitWindowsEx() were not implemented as a client-server Win32 API call, this behaviour would be a lot harder to duplicate, since there would be no central arbitrator to prevent two separate processes from executing ExitWindowsEx() simultaneously.)

When all of the end session messages and events have been sent, CSRSS completes the remote procedure call and WINLOGON's call to ExitWindowsEx() returns. WINLOGON then effects an internal change of state from the "logged-on" state to the "no-one logged on" state. This involves updating the user's profile, deleting network connections, playing the "log off" and "system exit" sounds, killing all COM processes (which has a grace period of 15 minutes), saving and unloading the user's profile, and deleting RAS connections. Finally WINLOGON informs the LogonIU of the state change. (On prior versions of Windows NT, WINLOGON would notify the GINA of an internal change of state by calling the GINA's WlxLogoff() function.)

After logging the current user off, WINLOGON then attempts to stop all of the processes running in session 0.

To stop all of the processes running in session 0, WINLOGON calls ExitWindowsEx() again, again using secret undocumented parameters to the call to request an end-session broadcast, but this time under the aegis of the "local system" user account. ExitWindowsEx() (i.e. CSRSS acting upon WINLOGON's behalf) performs the same enumeration for session 0 as it did for session 1, sending the same window messages. One difference, however, is that instead of sending a logoff event to any consoles, it sends a CTRL_SHUTDOWN_EVENT instead.

It is here that the Service Controller (SCM) is first informed that the system is shutting down. It, in turn, attempts to stop all running service processes. (Its console event handler, its ScShutdownNotificationRoutine() function, receives the CTRL_SHUTDOWN_EVENT event and in turn calls the SCM's ScShutdownAllServices() function.) Because it does so, CSRSS implements a special case for it. Since it knows which process is the SCM (because the SCM registers itself with CSRSS at startup) it knows to implement a different, usually longer, timeout when sending end-session messages to the SCM.

When all of the shutdown messages and events have been sent, CSRSS completes the remote procedure call and WINLOGON's call to ExitWindowsEx() returns. WINLOGON then effects an internal change of state from the "no-one logged-on" state to the "shutdown" state. WINLOGON informs the LogonIU of the state change. (On prior versions of Windows NT, WINLOGON would notify the GINA of an internal change of state by calling the GINA's WlxShutdown() function.)

Finally, WINLOGON tells the Session Manager Subsystem (SMSS) to shut the system down. It does this by calling sending a shutdown request to the SMSS's LPC port.

SMSS's response to a shutdown request to its LPC port

The SMSS is the last stage in the shutdown process. It informs all of the subsystem processes (e.g. CSRSS, PSXSS, OS2SS, and so forth), in all of the sessions that it has started, that the system is shutting down, giving them the opportunity to terminate all of their client process and shut themselves down. It then calls the kernel's NtShutdownSystem() system call.

Shutdown processing in the NtShutdownSystem() system call

Inside the kernel, all drivers are shut down, the in-memory copies of the registry hives are flushed to disc, the disc cache is flushed, the paging file is cleared, and eventually the NtSetSystemPowerState() function is called. This causes all plug-and-play devices to be shut down and the system to be either halted, rebooted, or powered off.


© Copyright 2006–2006 Jonathan de Boyne Pollard. "Moral" rights asserted.
Permission is hereby granted to copy and to distribute this web page in its original, unmodified form as long as its last modification datestamp is preserved.