Prevent Windows shutdown or session ending in .NET

 
 
  • Gérald Barré

When a user logs off or shuts down their machine, Windows closes all open applications. Some applications need to run cleanup code before exiting to avoid losing state, such as an unsaved document.

To handle this, Windows sends a message to all applications:

  • WM_QUERYENDSESSION when the session is closing
  • WM_ENDSESSION when the machine is shutting down

If the application does not handle these messages, Windows forcibly closes it. However, it is possible to intercept these messages and block the machine shutdown or logoff. In that case, this window is displayed:

Windows provides three methods:

The third method controls the order in which Windows sends the WM_QUERYENDSESSION and WM_ENDSESSION messages to applications. This is useful when application A depends on application B. Application A can be notified before application B that the machine is stopping, giving it the opportunity to block the shutdown. As long as application A blocks the shutdown, application B remains running.

The priority is an integer between 0 and 0x4FF. 0x4FF is the highest priority (notified first), 0 is the lowest priority (notified last). By default, applications have priority 280.

Here is a short code example showing how to use these methods:

C#
public partial class MainForm : Form
{
    public const int WM_QUERYENDSESSION = 0x0011;
    public const int WM_ENDSESSION = 0x0016;
    public const uint SHUTDOWN_NORETRY = 0x00000001;

    [DllImport("user32.dll", SetLastError = true)]
    static extern bool ShutdownBlockReasonCreate(IntPtr hWnd, [MarshalAs(UnmanagedType.LPWStr)] string reason);
    [DllImport("user32.dll", SetLastError = true)]
    static extern bool ShutdownBlockReasonDestroy(IntPtr hWnd);
    [DllImport("kernel32.dll")]
    static extern bool SetProcessShutdownParameters(uint dwLevel, uint dwFlags);

    public MainForm()
    {
        InitializeComponent();
        // Define the priority of the application (0x3FF = The higher priority)
        SetProcessShutdownParameters(0x3FF, SHUTDOWN_NORETRY);
    }

    protected override void WndProc(ref Message m)
    {
        if (m.Msg == WM_QUERYENDSESSION || m.Msg == WM_ENDSESSION)
        {
            // Prevent windows shutdown
            ShutdownBlockReasonCreate(this.Handle, "I want to live!");
            ThreadPool.QueueUserWorkItem(o =>
            {
                // Simulate some work
                Thread.Sleep(5000);
                this.BeginInvoke((Action)(() =>
                {
                    // This method must be called on the same thread as the one that have create the Handle, so use BeginInvoke
                    ShutdownBlockReasonCreate(this.Handle, "Now I must die!");

                    // Allow Windows to shutdown
                    ShutdownBlockReasonDestroy(this.Handle);
                }));
            });

            return;
        }

        base.WndProc(ref m);
    }
}

Do you have a question or a suggestion about this post? Contact me!

Follow me:
Enjoy this blog?