Do you know how windows terminates all the applications when user logs off? I did not think too much about this, and assumed that it is a normal process - after all the WM_QUERYENDSESSION and WM_ENDSESSION processing, the application main window closes, posts WM_QUIT and the application quits in a regular way.

But a recent bug reported for one of my GUI applications caused me to look deeper. The application settings, normally saved at application's exit, were not saves if user logged off, and this made me look closer at what happens at logoff.

This was a managed application with related code in Main() function that looked like this:

static void Main()
{
    LoadPreferences();
    Application.Run(new MainForm());
    SavePreferences();
}

This works great when the application is closed normally. But if user logs off, the application closes, but the preferences are not saved - the code following Application.Run is never called at all.

What happens? Does Application.Run throw any exception that causes the following code to be bypassed? This was ruled out rather quickly by debugger. I then assumed something in Windows Forms calls ExitProcess in response to WM_ENDSESSION, or maybe default message handler does this - but that was proved wrong too. A repro with unmanaged ATL code showed it is not related to managed libraries at all.

Finally my colleguae debugged this issue a little deeper, and found that CSRSS is blatantly terminating the process after it processed the WM_ENDSESSION event. Here are the outlines of the whole sequence (for non-console applications).

The user selects Start/Log off and then selects “OK” in the confirmation dialog. This calls ExitWindowsEx(EWX_LOGOFF); if during the OK button click, the CTRL key was down, it also adds the EWX_FORCE flag which makes it ignore the result of the WM_QUERYENDSESSION message (as if the applications always returned TRUE). ExitWindowsEx(EWX_LOGOFF) causes roughly the following activity to occur in the logon session’s instance of CSRSS:

For each process in the session
{
    For each UI thread in the process
    {
        For each top-level window in the thread
        {
            Send WM_QUERYENDSESSION and get back the result
            Wait with a short timeout and show an “End program” dialog if it takes too long; if the user says “kill”, call TerminateProcess on the process and continue the “for process” loop
            If EWX_FORCE was not specified and WM_QUERYENDSESSION returned FALSE, break (and continue "UI thread loop");
        }
        // Note that even if one window in one thread returned FALSE to WM_QUERYENDSESSION, windows in the other threads in the same process are still sent WM_QUERYENDSESSION
        bool bDoShutdown = (all threads agreed (i.e., all windows returned TRUE to WM_QUERYENDSESSION)) or EWX_FORCE
        For each UI thread in the process
        {
            For each top-level window in the thread
            {
                Send WM_ENDSESSION(bDoShutdown);
                If (bDoShutdown) wait for return (wait with a short timeout and show an “End program” dialog if it takes too long; whatever the user says, consider that the return from WM_QUERYENDSESSION) 
            }
        }
        If (bDoShutdown) TerminateProcess on the process
}
I have omitted some details, in particular related to no-UI and console processes, but the general picture should be clear. Probably the Windows guys decided that graceful cleanup is not needed when user logs off, and all the application are closed.

Since the application is forecefully terminated, the application should not rely on being able to execute any code after the message loop. Even classes like SafeHandle are not finalized. All really important cleanup and termination code should be executed when the main form closes, or by providing a handler for the WM_ENDSESSION message . Another option (although I did not try it) is to catch WM_ENDSESSION message and terminate the message loop, then exit the application gracefully - although this goes contrary to Windows design of killing applications fast to ensure quick log off.

Update: Raymond Chen described the reasons for this behavior:
http://blogs.msdn.com/oldnewthing/archive/2008/04/21/8413175.aspx