C# 呼叫';ShutdownBlockReasonCreate';功能不阻止用户关闭系统

C# 呼叫';ShutdownBlockReasonCreate';功能不阻止用户关闭系统,c#,.net,winapi,pinvoke,shutdown,C#,.net,Winapi,Pinvoke,Shutdown,文章说, 如果应用程序必须阻止潜在的系统关闭,它可以调用 ShutdownBlockReasonCreate函数。调用方提供了一个原因 将显示给用户的字符串 并且在文档中明确指出,当用户尝试关闭时,将向用户显示带有原因字符串的对话框窗口: 指示无法关闭系统并设置原因字符串 在系统关闭启动时向用户显示 该对话框窗口的外观在讨论中得到确认: 用户可以单击“无论如何关闭”。此外,系统假设 如果用户在一定数量的时间内没有采取任何行动,“无论如何都要关机” 秒 但是,在我调用ShutdownBlockRe

文章说,

如果应用程序必须阻止潜在的系统关闭,它可以调用 ShutdownBlockReasonCreate函数。调用方提供了一个原因 将显示给用户的字符串

并且在文档中明确指出,当用户尝试关闭时,将向用户显示带有原因字符串的对话框窗口:

指示无法关闭系统并设置原因字符串 在系统关闭启动时向用户显示

该对话框窗口的外观在讨论中得到确认:

用户可以单击“无论如何关闭”。此外,系统假设 如果用户在一定数量的时间内没有采取任何行动,“无论如何都要关机” 秒

但是,在我调用ShutdownBlockReasonCreate传递当前应用程序的主窗口句柄,确保函数成功并通过调用函数检索原因字符串来双重保证之后,它不会阻止用户关闭系统,并且不会显示任何对话框窗口

为什么它对我的系统没有影响?我如何解决这个问题

我使用管理员(内置)帐户在Windows 10 x64上运行,我使用的代码来自GitHub存储库:

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);
        }
    }
}
使用这样的用法:

using (new PreventShutdownContext(this, "This app is super busy right now."))
{
  // Do something that can't be interrupted...
}
我按原样尝试了代码,使用了它的p/Invoke定义,还对代码进行了一些修改,我正在使用IntPtr结构作为窗口句柄,而不是自定义结构,并将应用程序的主窗口句柄传递给它,正如我在上面的注释中所指定的那样。

这是出于设计

这篇文章(以及你提到的主题)可能会有点误导

指示无法关闭系统,并设置在启动系统关闭时向用户显示的原因字符串

如果应用程序必须阻止潜在的系统关闭,它可以调用
ShutdownBlockReasonCreate
函数

此函数实际上只为应用程序设置消息字符串。此功能不会阻止关闭应用程序

要实现shutdown块,只需按照您引用的文章中描述的步骤进行操作。您需要对
WM_QUERYENDSESSION
消息做出反应,并返回
FALSE
(0)。 有关参考信息,请参见文档

您可能还发现了一些有趣的内容—它描述了Windows Vista引入的更改,并包含了如何实现关闭逻辑的最佳实践

顺便说一下,您的应用程序不会有特殊的“对话框窗口”。将显示标准的Windows关机UI(它因操作系统版本而异)。您的应用程序将出现在“防止关机的应用程序”列表中,其中包含您使用
ShutdownBlockReasonCreate
功能注册的消息-但仅当它为
WM\u QUERYENDSESSION
消息返回
FALSE


更新 如果上述解决方案(
WM_QUERYENDSESSION
)无法解决此问题,则可能是系统设置忽略了此机制造成的

正如@ElektroStudios在他们的研究中发现的那样:

  • 如果用户设置了
    AutoEndTasks
    注册表值(可在
    HKCU\Control Panel\Desktop
    注册表项中找到),则关机不会显示任何UI以允许用户取消关机。因此,在这种情况下创建“取消关机原因”是没有用的,因为在任何情况下,应用程序都将被迫立即关闭(以继续关机)。如需参考,请阅读
  • 为了使这项工作按预期进行,
    AutoEndTasks
    注册表值必须为0(零);否则,任何试图阻止关机的应用程序都将被终止,并且在关机时不会显示任何UI
  • AutoEndTasks
    值可以添加到
    HKEY_用户\默认\控制面板\桌面
    键,该键覆盖在
    HKCU
    配置单元和
    HKU\{SID}
    中定义的值。这意味着,如果
    AutoEndTasks
    HKCU
    中为
    false
    (0),但在
    HKU\中为
    true
    (1)。默认值为
    ,则应用程序将不会阻止系统关闭,并且不会显示关闭UI。如果
    AutoEndTasks
    HKU\.DEFAULT
    中为
    false
    ,但在
    HKCU
    中为true,则应用程序将阻止系统关闭,并显示关机界面
  • 关于这一点的另一个优点是,
    AutoEndTasks
    值无需重新启动/注销系统即可生效。因此,一旦在正确的键(例如
    HKEY_USERS\.DEFAULT\Control Panel\Desktop
    )中将其设置为
    false
    ,应用程序将阻止系统关闭,我们可以在使用完该功能后将该值恢复到以前的状态

如果有人感兴趣,我将在VB.NET中共享我的自定义实现。目前,这段代码缺少Windows API定义和我用来读取/写入AutoEndTasks注册表值(下面代码中名为
TweakingUtil.AutoEndTasks
的成员)的方法,但您可以了解我认为最重要的一点

Imports Microsoft.Win32
Imports System.Security

''' ----------------------------------------------------------------------------------------------------
''' <summary>
''' Provides a mechanism to prevent any system shutdown/restart/log-off request during the life-cycle of a instance of this class.
''' <para></para>
''' Applications should use this class as they begin an operation that cannot be interrupted, such as burning a CD or DVD.
''' <para></para>
''' This class is to be used in either a <see langword="Using"/> statement or for the life-cycle of the current application.
''' </summary>
''' ----------------------------------------------------------------------------------------------------
''' <remarks>
''' Original source-code: <see href="https://github.com/dahall/Vanara/blob/master/WIndows.Forms/Contexts/PreventShutdownContext.cs"/>
''' </remarks>
''' ----------------------------------------------------------------------------------------------------
''' <example> This is a code example.
''' <code lang="vb">
''' Using psc As New PreventShutdownContext("Critical operation is in progress...")
'''     ' Do something that can't be interrupted... 
''' End Using
''' </code>
''' </example>
''' ----------------------------------------------------------------------------------------------------
''' <example> This is a code example.
''' <code lang="vb">
''' Public NotInheritable Class Form1 : Inherits Form
''' 
'''     Private psc As PreventShutdownContext
''' 
'''     Private Sub AllowShutdown()
'''         If (Me.psc IsNot Nothing) Then
'''             Me.psc.Dispose()
'''             Me.psc = Nothing
'''         End If
'''     End Sub
''' 
'''     Private Sub DisallowShutdown()
'''         If (Me.psc Is Nothing) Then
'''             Me.psc = New PreventShutdownContext("Application defined reason goes here.")
'''         End If
'''     End Sub
''' 
'''     Protected Overrides Sub OnShown(ByVal e As EventArgs)
'''         Me.DisallowShutdown()
'''         MyBase.OnShown(e)
'''     End Sub
''' 
''' End Class
''' </code>
''' </example>
''' ----------------------------------------------------------------------------------------------------
''' <example> This is a code example using the <see langword="using"/> statement.
''' <code lang="cs">
''' using (new PreventShutdownContext("Critical operation is in progress...")) {
'''    // Do something that can't be interrupted...
''' }
''' </code>
''' </example>
''' ----------------------------------------------------------------------------------------------------
''' <code lang="cs">
''' public partial class Form1 : Form {
''' 
'''    private PreventShutdownContext disallowShutdown;
''' 
'''    private void AllowShutdown() {
'''        if (this.psc != null) {
'''            this.psc.Dispose();
'''            this.psc = null;
'''        }
'''    }
'''
'''    private void DisallowShutdown() {
'''        if (this.psc == null) {
'''            this.psc = new PreventShutdownContext("Application defined reason goes here.");
'''        }
'''    }
'''
'''    protected override void OnShown(EventArgs e) {
'''        this.DisallowShutdown();
'''        base.OnShown(e);
'''    }
'''    
''' }
''' </code>
''' ----------------------------------------------------------------------------------------------------
Public NotInheritable Class PreventShutdownContext : Implements IDisposable

#Region " Private Fields "

    ''' <summary>
    ''' Holds the main window handle for the current application.
    ''' </summary>
    Private ReadOnly hRef As HandleRef

    ''' <summary>
    ''' Flag to determine whether the shutdown reason is created.
    ''' </summary>
    Private isReasonCreated As Boolean

    ''' <summary>
    ''' Holds the previous value of "HKEY_USERS\.DEFAULT\Control Panel\Desktop" "AutoEndTasks" registry value.
    ''' <para></para>
    ''' This registry value is restored when calling <see cref="PreventShutdownContext.Dispose()"/>
    ''' </summary>
    Private ReadOnly previousAutoEndTasksValue As Boolean

#End Region

#Region " Constructors "

    ''' <summary>
    ''' Initializes a new instance of the <see cref="PreventShutdownContext"/> class.
    ''' </summary>
    ''' <param name="reason">
    ''' The reason for which the current application must prevent system shutdown. 
    ''' <para></para>
    ''' 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>
    ''' 
    ''' <param name="throwOnError">
    ''' If <see langword="True"/>, an exception will be thrown if 
    ''' the application does not meet the requirements to prevent a system shutdown.
    ''' <para></para>
    ''' Default value is <see langword="True"/>.
    ''' </param>
    ''' <exception cref="InvalidOperationException">
    ''' Applications without a user interface can't prevent a system shutdown.
    ''' </exception>
    ''' 
    ''' <exception cref="InvalidOperationException">
    ''' The main window of the current application is not yet created or is not visible.
    ''' </exception>
    ''' 
    ''' <exception cref="InvalidOperationException">
    ''' Only the thread that created the main window of the current application can call this to prevent a system shutdown.
    ''' </exception>
    ''' 
    ''' <exception cref="SecurityException">
    ''' The user does not have the permissions required to create or modify 'AutoEndTasks' registry value. 
    ''' Therefore, the application can't prevent a system shutdown.
    ''' </exception>
    <DebuggerStepThrough>
    Public Sub New(ByVal reason As String, Optional ByVal throwOnError As Boolean = True)

        If Not Environment.UserInteractive Then
            If (throwOnError) Then
                Throw New InvalidOperationException(
                    "Applications without a user interface can't prevent a system shutdown.")
            End If
        End If

        Dim pr As Process = Process.GetCurrentProcess()
        Me.hRef = New HandleRef(pr, pr.MainWindowHandle)
        If (Me.hRef.Handle = IntPtr.Zero) AndAlso (throwOnError) Then
            Throw New InvalidOperationException(
                "The main window of the current application is not yet created or is not visible.")
        End If

        Dim currentThreadId As UInteger = NativeMethods.GetCurrentThreadId()
        Dim mainThreadId As Integer = NativeMethods.GetWindowThreadProcessId(Me.hRef.Handle, Nothing)
        If (currentThreadId <> mainThreadId) AndAlso (throwOnError) Then
            Throw New InvalidOperationException(
                "Only the thread that created the main window of the current application can call this to prevent a system shutdown.")
        End If

        Me.previousAutoEndTasksValue = TweakingUtil.AutoEndTasks
        If (Me.previousAutoEndTasksValue) Then
            Try
                TweakingUtil.AutoEndTasks = False
            Catch ex As SecurityException
                If (throwOnError) Then
                    Throw New SecurityException(
                            "The user does not have the permissions required to create or modify 'AutoEndTasks' registry value. " &
                            "Therefore, the application can't prevent a system shutdown.", ex)
                End If
            Catch ex As Exception
                If (throwOnError) Then
                    Throw
                End If
            End Try
        End If

        AddHandler SystemEvents.SessionEnding, AddressOf Me.SessionEnding
        Me.Reason = reason

    End Sub

#End Region

#Region " Properties "

    ''' <summary>
    ''' Gets or sets the reason for which the current application must prevent system shutdown. 
    ''' <para></para>
    ''' 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 for which the current application must prevent system shutdown.
    ''' </value>
    Public Property Reason As String
        Get
            Return Me.reason_
        End Get
        <DebuggerStepThrough>
        Set(ByVal value As String)
            If value.Equals(Me.reason_, StringComparison.Ordinal) Then
                Exit Property
            End If

            Me.SetReason(value)
            Me.reason_ = value
        End Set
    End Property
    ''' <summary>
    ''' ( backing field of <see cref="PreventShutdownContext.Reason"/> property )
    ''' <para></para>
    ''' The reason for which the application must prevent system shutdown.
    ''' </summary>
    Private reason_ As String

#End Region

#Region " Event-Handlers "

    ''' <summary>
    ''' Handles the <see cref="Microsoft.Win32.SystemEvents.SessionEnding"/> event.
    ''' </summary>
    ''' <param name="sender">
    ''' The source of the event.
    ''' </param>
    ''' 
    ''' <param name="e">
    ''' The <see cref="SessionEndingEventArgs"/> instance containing the event data.
    ''' </param>
    Private Sub SessionEnding(ByVal sender As Object, e As SessionEndingEventArgs)

        ' By setting "e.Cancel" property to True, 
        ' the application will respond 0 (zero) to "WM_QUERYENDSESSION" message in order to prevent a system shutdown. 
        '
        ' For more info: 
        ' https://docs.microsoft.com/en-us/windows/desktop/shutdown/wm-queryendsession
        ' https://docs.microsoft.com/en-us/windows/desktop/Shutdown/shutdown-changes-for-windows-vista

        e.Cancel = True

    End Sub

#End Region

#Region " Private Methods "

    ''' <summary>
    ''' Sets the reason for which the current application must prevent system shutdown.
    ''' </summary>
    ''' <param name="reason">
    ''' The reason for which the current application must prevent system shutdown.
    ''' <para></para>
    ''' 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>
    ''' <exception cref="Win32Exception">
    ''' </exception>
    <DebuggerStepThrough>
    Private Sub SetReason(ByVal reason As String)
        Dim succeed As Boolean
        Dim win32Err As Integer

        If (Me.isReasonCreated) Then
            succeed = NativeMethods.ShutdownBlockReasonDestroy(Me.hRef.Handle)
            win32Err = Marshal.GetLastWin32Error()
            If Not succeed Then
                Throw New Win32Exception(win32Err)
            End If
        End If

        succeed = NativeMethods.ShutdownBlockReasonCreate(Me.hRef.Handle, reason)
        win32Err = Marshal.GetLastWin32Error()
        If Not succeed Then
            Throw New Win32Exception(win32Err)
        End If
        Me.isReasonCreated = True
    End Sub

#End Region

#Region " IDisposable Implementation "

    ''' <summary>
    ''' Flag to detect redundant calls when disposing.
    ''' </summary>
    Private isDisposed As Boolean

    ''' <summary>
    ''' Releases all the resources used by this instance.
    ''' </summary>
    <DebuggerStepThrough>
    Public Sub Dispose() Implements IDisposable.Dispose
        Me.Dispose(isDisposing:=True)
    End Sub

    ''' <summary>
    ''' Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
    ''' Releases unmanaged and, optionally, managed resources.
    ''' </summary>
    ''' <param name="isDisposing">
    ''' <see langword="True"/>  to release both managed and unmanaged resources; 
    ''' <see langword="False"/> to release only unmanaged resources.
    ''' </param>
    <DebuggerStepThrough>
    Private Sub Dispose(ByVal isDisposing As Boolean)
        If (Not Me.isDisposed) AndAlso (isDisposing) Then
            RemoveHandler SystemEvents.SessionEnding, AddressOf Me.SessionEnding
            NativeMethods.ShutdownBlockReasonDestroy(Me.hRef.Handle)
            Me.isReasonCreated = False
            Try
                TweakingUtil.AutoEndTasks = Me.previousAutoEndTasksValue
            Catch
            End Try
        End If

        Me.isDisposed = True
    End Sub

#End Region

End Class
导入Microsoft.Win32
导入系统。安全
''' ----------------------------------------------------------------------------------------------------
''' 
''提供了一种机制,以防止在此类实例的生命周期内出现任何系统关闭/重新启动/注销请求。
''' 
''应用程序在开始无法中断的操作(如刻录CD或DVD)时应使用此类。
''' 
''该类将用于语句或cur的生命周期
Imports Microsoft.Win32
Imports System.Security

''' ----------------------------------------------------------------------------------------------------
''' <summary>
''' Provides a mechanism to prevent any system shutdown/restart/log-off request during the life-cycle of a instance of this class.
''' <para></para>
''' Applications should use this class as they begin an operation that cannot be interrupted, such as burning a CD or DVD.
''' <para></para>
''' This class is to be used in either a <see langword="Using"/> statement or for the life-cycle of the current application.
''' </summary>
''' ----------------------------------------------------------------------------------------------------
''' <remarks>
''' Original source-code: <see href="https://github.com/dahall/Vanara/blob/master/WIndows.Forms/Contexts/PreventShutdownContext.cs"/>
''' </remarks>
''' ----------------------------------------------------------------------------------------------------
''' <example> This is a code example.
''' <code lang="vb">
''' Using psc As New PreventShutdownContext("Critical operation is in progress...")
'''     ' Do something that can't be interrupted... 
''' End Using
''' </code>
''' </example>
''' ----------------------------------------------------------------------------------------------------
''' <example> This is a code example.
''' <code lang="vb">
''' Public NotInheritable Class Form1 : Inherits Form
''' 
'''     Private psc As PreventShutdownContext
''' 
'''     Private Sub AllowShutdown()
'''         If (Me.psc IsNot Nothing) Then
'''             Me.psc.Dispose()
'''             Me.psc = Nothing
'''         End If
'''     End Sub
''' 
'''     Private Sub DisallowShutdown()
'''         If (Me.psc Is Nothing) Then
'''             Me.psc = New PreventShutdownContext("Application defined reason goes here.")
'''         End If
'''     End Sub
''' 
'''     Protected Overrides Sub OnShown(ByVal e As EventArgs)
'''         Me.DisallowShutdown()
'''         MyBase.OnShown(e)
'''     End Sub
''' 
''' End Class
''' </code>
''' </example>
''' ----------------------------------------------------------------------------------------------------
''' <example> This is a code example using the <see langword="using"/> statement.
''' <code lang="cs">
''' using (new PreventShutdownContext("Critical operation is in progress...")) {
'''    // Do something that can't be interrupted...
''' }
''' </code>
''' </example>
''' ----------------------------------------------------------------------------------------------------
''' <code lang="cs">
''' public partial class Form1 : Form {
''' 
'''    private PreventShutdownContext disallowShutdown;
''' 
'''    private void AllowShutdown() {
'''        if (this.psc != null) {
'''            this.psc.Dispose();
'''            this.psc = null;
'''        }
'''    }
'''
'''    private void DisallowShutdown() {
'''        if (this.psc == null) {
'''            this.psc = new PreventShutdownContext("Application defined reason goes here.");
'''        }
'''    }
'''
'''    protected override void OnShown(EventArgs e) {
'''        this.DisallowShutdown();
'''        base.OnShown(e);
'''    }
'''    
''' }
''' </code>
''' ----------------------------------------------------------------------------------------------------
Public NotInheritable Class PreventShutdownContext : Implements IDisposable

#Region " Private Fields "

    ''' <summary>
    ''' Holds the main window handle for the current application.
    ''' </summary>
    Private ReadOnly hRef As HandleRef

    ''' <summary>
    ''' Flag to determine whether the shutdown reason is created.
    ''' </summary>
    Private isReasonCreated As Boolean

    ''' <summary>
    ''' Holds the previous value of "HKEY_USERS\.DEFAULT\Control Panel\Desktop" "AutoEndTasks" registry value.
    ''' <para></para>
    ''' This registry value is restored when calling <see cref="PreventShutdownContext.Dispose()"/>
    ''' </summary>
    Private ReadOnly previousAutoEndTasksValue As Boolean

#End Region

#Region " Constructors "

    ''' <summary>
    ''' Initializes a new instance of the <see cref="PreventShutdownContext"/> class.
    ''' </summary>
    ''' <param name="reason">
    ''' The reason for which the current application must prevent system shutdown. 
    ''' <para></para>
    ''' 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>
    ''' 
    ''' <param name="throwOnError">
    ''' If <see langword="True"/>, an exception will be thrown if 
    ''' the application does not meet the requirements to prevent a system shutdown.
    ''' <para></para>
    ''' Default value is <see langword="True"/>.
    ''' </param>
    ''' <exception cref="InvalidOperationException">
    ''' Applications without a user interface can't prevent a system shutdown.
    ''' </exception>
    ''' 
    ''' <exception cref="InvalidOperationException">
    ''' The main window of the current application is not yet created or is not visible.
    ''' </exception>
    ''' 
    ''' <exception cref="InvalidOperationException">
    ''' Only the thread that created the main window of the current application can call this to prevent a system shutdown.
    ''' </exception>
    ''' 
    ''' <exception cref="SecurityException">
    ''' The user does not have the permissions required to create or modify 'AutoEndTasks' registry value. 
    ''' Therefore, the application can't prevent a system shutdown.
    ''' </exception>
    <DebuggerStepThrough>
    Public Sub New(ByVal reason As String, Optional ByVal throwOnError As Boolean = True)

        If Not Environment.UserInteractive Then
            If (throwOnError) Then
                Throw New InvalidOperationException(
                    "Applications without a user interface can't prevent a system shutdown.")
            End If
        End If

        Dim pr As Process = Process.GetCurrentProcess()
        Me.hRef = New HandleRef(pr, pr.MainWindowHandle)
        If (Me.hRef.Handle = IntPtr.Zero) AndAlso (throwOnError) Then
            Throw New InvalidOperationException(
                "The main window of the current application is not yet created or is not visible.")
        End If

        Dim currentThreadId As UInteger = NativeMethods.GetCurrentThreadId()
        Dim mainThreadId As Integer = NativeMethods.GetWindowThreadProcessId(Me.hRef.Handle, Nothing)
        If (currentThreadId <> mainThreadId) AndAlso (throwOnError) Then
            Throw New InvalidOperationException(
                "Only the thread that created the main window of the current application can call this to prevent a system shutdown.")
        End If

        Me.previousAutoEndTasksValue = TweakingUtil.AutoEndTasks
        If (Me.previousAutoEndTasksValue) Then
            Try
                TweakingUtil.AutoEndTasks = False
            Catch ex As SecurityException
                If (throwOnError) Then
                    Throw New SecurityException(
                            "The user does not have the permissions required to create or modify 'AutoEndTasks' registry value. " &
                            "Therefore, the application can't prevent a system shutdown.", ex)
                End If
            Catch ex As Exception
                If (throwOnError) Then
                    Throw
                End If
            End Try
        End If

        AddHandler SystemEvents.SessionEnding, AddressOf Me.SessionEnding
        Me.Reason = reason

    End Sub

#End Region

#Region " Properties "

    ''' <summary>
    ''' Gets or sets the reason for which the current application must prevent system shutdown. 
    ''' <para></para>
    ''' 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 for which the current application must prevent system shutdown.
    ''' </value>
    Public Property Reason As String
        Get
            Return Me.reason_
        End Get
        <DebuggerStepThrough>
        Set(ByVal value As String)
            If value.Equals(Me.reason_, StringComparison.Ordinal) Then
                Exit Property
            End If

            Me.SetReason(value)
            Me.reason_ = value
        End Set
    End Property
    ''' <summary>
    ''' ( backing field of <see cref="PreventShutdownContext.Reason"/> property )
    ''' <para></para>
    ''' The reason for which the application must prevent system shutdown.
    ''' </summary>
    Private reason_ As String

#End Region

#Region " Event-Handlers "

    ''' <summary>
    ''' Handles the <see cref="Microsoft.Win32.SystemEvents.SessionEnding"/> event.
    ''' </summary>
    ''' <param name="sender">
    ''' The source of the event.
    ''' </param>
    ''' 
    ''' <param name="e">
    ''' The <see cref="SessionEndingEventArgs"/> instance containing the event data.
    ''' </param>
    Private Sub SessionEnding(ByVal sender As Object, e As SessionEndingEventArgs)

        ' By setting "e.Cancel" property to True, 
        ' the application will respond 0 (zero) to "WM_QUERYENDSESSION" message in order to prevent a system shutdown. 
        '
        ' For more info: 
        ' https://docs.microsoft.com/en-us/windows/desktop/shutdown/wm-queryendsession
        ' https://docs.microsoft.com/en-us/windows/desktop/Shutdown/shutdown-changes-for-windows-vista

        e.Cancel = True

    End Sub

#End Region

#Region " Private Methods "

    ''' <summary>
    ''' Sets the reason for which the current application must prevent system shutdown.
    ''' </summary>
    ''' <param name="reason">
    ''' The reason for which the current application must prevent system shutdown.
    ''' <para></para>
    ''' 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>
    ''' <exception cref="Win32Exception">
    ''' </exception>
    <DebuggerStepThrough>
    Private Sub SetReason(ByVal reason As String)
        Dim succeed As Boolean
        Dim win32Err As Integer

        If (Me.isReasonCreated) Then
            succeed = NativeMethods.ShutdownBlockReasonDestroy(Me.hRef.Handle)
            win32Err = Marshal.GetLastWin32Error()
            If Not succeed Then
                Throw New Win32Exception(win32Err)
            End If
        End If

        succeed = NativeMethods.ShutdownBlockReasonCreate(Me.hRef.Handle, reason)
        win32Err = Marshal.GetLastWin32Error()
        If Not succeed Then
            Throw New Win32Exception(win32Err)
        End If
        Me.isReasonCreated = True
    End Sub

#End Region

#Region " IDisposable Implementation "

    ''' <summary>
    ''' Flag to detect redundant calls when disposing.
    ''' </summary>
    Private isDisposed As Boolean

    ''' <summary>
    ''' Releases all the resources used by this instance.
    ''' </summary>
    <DebuggerStepThrough>
    Public Sub Dispose() Implements IDisposable.Dispose
        Me.Dispose(isDisposing:=True)
    End Sub

    ''' <summary>
    ''' Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
    ''' Releases unmanaged and, optionally, managed resources.
    ''' </summary>
    ''' <param name="isDisposing">
    ''' <see langword="True"/>  to release both managed and unmanaged resources; 
    ''' <see langword="False"/> to release only unmanaged resources.
    ''' </param>
    <DebuggerStepThrough>
    Private Sub Dispose(ByVal isDisposing As Boolean)
        If (Not Me.isDisposed) AndAlso (isDisposing) Then
            RemoveHandler SystemEvents.SessionEnding, AddressOf Me.SessionEnding
            NativeMethods.ShutdownBlockReasonDestroy(Me.hRef.Handle)
            Me.isReasonCreated = False
            Try
                TweakingUtil.AutoEndTasks = Me.previousAutoEndTasksValue
            Catch
            End Try
        End If

        Me.isDisposed = True
    End Sub

#End Region

End Class