Search code examples
c#.netwinapipinvokeshutdown

Calling 'ShutdownBlockReasonCreate' function does not prevent the user from shutting down the system


This article says:

If an application must block a potential system shutdown, it can call the ShutdownBlockReasonCreate function. The caller provides a reason string that will be displayed to the user.

And in the ShutdownBlockReasonCreate documentation it clearly indicates that a dialog window with the reason string will be displayed to the user when attempting to shutdown:

Indicates that the system cannot be shut down and sets a reason string to be displayed to the user if system shutdown is initiated

And the appearance of that dialog window is confirmed in this discussion:

The user could click "Shut down anyway". Besides, the system assumes "Shut down anyway" if the user takes no action within some number of seconds.

However, after I call ShutdownBlockReasonCreate passing the main window handle of the current application, assuring that the function succeed and double assuring it by calling ShutdownBlockReasonQuery function to retrieve the reason string, it does not prevent the user from shutting down the system and no dialog window is shown.

Why it has no effect on my system?, and how do I solve this issue?.

I'm running on Windows 10 x64 with an Administrator (built-in) account, and the code that I'm using is from this GitHub repository:

using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using Vanara.PInvoke;
using static Vanara.PInvoke.User32;

namespace Vanara.Windows.Forms.Forms
{
    /// <summary>Used to define a set of operations within which any shutdown request will be met with a reason why this application is blocking it.</summary>
    /// <remarks>This is to be used in either a 'using' statement or for the life of the application.
    /// <para>To use for the life of the form, define a class field:
    public class PreventShutdownContext : IDisposable
    {
        private HandleRef href;

        /// <summary>Initializes a new instance of the <see cref="PreventShutdownContext"/> class.</summary>
        /// <param name="window">The <see cref="Form"/> or <see cref="Control"/> that contains a valid window handle.</param>
        /// <param name="reason">The reason the application must block system shutdown. Because users are typically in a hurry when shutting down the system, they may spend only a few seconds looking at the shutdown reasons that are displayed by the system. Therefore, it is important that your reason strings are short and clear.</param>
        public PreventShutdownContext(Control window, string reason)
        {
            href = new HandleRef(window, window.Handle);
            Reason = reason;
        }

        /// <summary>The reason the application must block system shutdown. Because users are typically in a hurry when shutting down the system, they may spend only a few seconds looking at the shutdown reasons that are displayed by the system. Therefore, it is important that your reason strings are short and clear.</summary>
        /// <value>The reason string.</value>
        public string Reason
        {
            get
            {
                if (!ShutdownBlockReasonQuery(href.Handle, out var reason))
                    Win32Error.ThrowLastError();
                return reason;
            }
            set
            {
                if (value == null) value = string.Empty;
                if (ShutdownBlockReasonQuery(href.Handle, out var _))
                    ShutdownBlockReasonDestroy(href.Handle);
                if (!ShutdownBlockReasonCreate(href.Handle, value))
                    Win32Error.ThrowLastError();
            }
        }

        /// <summary>Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.</summary>
        public void Dispose()
        {
            ShutdownBlockReasonDestroy(href.Handle);
        }
    }
}

...

[DllImport(Lib.User32, SetLastError = true, ExactSpelling = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool ShutdownBlockReasonCreate(HWND hWnd, [MarshalAs(UnmanagedType.LPWStr)] string reason);

[DllImport(Lib.User32, SetLastError = true, ExactSpelling = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool ShutdownBlockReasonQuery(HWND hWnd, [MarshalAs(UnmanagedType.LPWStr)] StringBuilder pwszBuff, ref uint pcchBuff);

[DllImport(Lib.User32, SetLastError = true, ExactSpelling = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool ShutdownBlockReasonDestroy(HWND hWnd);

With an usage like this:

using (new PreventShutdownContext(this, "This app is super busy right now."))
{
  // Do something that can't be interrupted...
}

I tried the code as is, with its P/Invoke definitions, and also a little modification of the code on which I'm using IntPtr structure for window handles instead of that custom HWND structure, and passing to it the main window handle of the application as I specified in a commentary above.


Solution

  • This is by design.

    The documentation (as well as the topic you referenced) can be slightly misleading.

    Indicates that the system cannot be shut down and sets a reason string to be displayed to the user if system shutdown is initiated.

    If an application must block a potential system shutdown, it can call the ShutdownBlockReasonCreate function.

    This function actually only sets the message string for your application. This function doesn't prevent your application from being closed.

    To implement the shutdown block, just follow the steps described in the article you referenced. You need to react to the WM_QUERYENDSESSION message and return FALSE (0). For reference, see also the WM_QUERYENDSESSION documentation.

    You might also find interesting this topic - it describes the changes introduced with Windows Vista and contains the best practices how to implement the shutdown logic.

    By the way, there will be no special "dialog window" about your application. The standard Windows shutdown UI will be shown (it differs depending on the OS version). Your application will appear in the "Applications that prevent the shutdown" list with the message you register using the ShutdownBlockReasonCreate function - but only if it returns FALSE for the WM_QUERYENDSESSION message.


    Update

    If the above solution (WM_QUERYENDSESSION) doesn't solve the issue, it might be caused by a system setting that just ignores this mechanism.

    As @ElektroStudios discovered in their research:

    • If the user has the AutoEndTasks registry value set (found in HKCU\Control Panel\Desktop registry key), then the shutdown does not show any UI to let the user cancel shutdown. So it's useless to create a "cancel shutdown reason" in these circumstances, because the app will be forced to close instantly in any case (to continue the shutdown). For reference, read this MS Docs topic.
    • In order to make this thing work as expected, the AutoEndTasks registry value must be 0 (zero); otherwise, any app that attempts to prevent shutdown will be terminated and no UI will be shown at shutdown.
    • The AutoEndTasks value can be added to the HKEY_USERS\.DEFAULT\Control Panel\Desktop key that overrides the value defined in the HKCU hive and HKU\{SID}. This means, if AutoEndTasks is false (0) in HKCU but is true (1) in HKU\.DEFAULT, then the app will not prevent the system from shutting down and no shutdown UI will be shown. If AutoEndTasks is false in HKU\.DEFAULT but is true in HKCU, then the app will prevent the system from shutting down and the shutdown UI will be shown.
    • Also a good point about this is that the AutoEndTasks value does not require to restart/log-off the system to take effect. So, once it is set to false in the proper key (e.g. HKEY_USERS\.DEFAULT\Control Panel\Desktop), the app will prevent the system from shutdown, and we can revert this value to its previous state when we finish to use the functionality.