2020年8月26日 星期三

[C#] 攔截並阻擋系統關機程序

 取自:

https://notes.andywu.tw/2018/c-sharp-%E6%94%94%E6%88%AA%E4%B8%A6%E9%98%BB%E6%93%8B%E7%B3%BB%E7%B5%B1%E9%97%9C%E6%A9%9F%E7%A8%8B%E5%BA%8F/

[C#] 攔截並阻擋系統關機程序

前言

會研究這個主題是因為最近在用系上電腦教室挖礦XDD (噓~


但是大約下午沒課後一小時就會有專門的人來一台台關機 (凸 - -" 我才剛開好的說


幸運的是負責關機的人都是按一下電源鍵然後關螢幕就換下一台去了


所以我就想說如果可以攔截開機訊號,阻斷關機繼續進行,我的挖礦程式就不會被關閉了 (太邪惡了lol...

 


以下是效果圖,explorer.exe是我寫的阻止關機程式(故意取這樣的名字,才不會讓人起疑


它會卡著系統不讓系統關閉所有程式

原理解說

在程式實作之前,我們必須先了解一下windows的關機順序


系統在接收到使用者發出的關機訊號後(不管是從開始選單的還是按一下電源鍵),會逐一對所有應用程式發出WM_QUERYENDSESSION訊息,來詢問應用程式在現階段是否可以被關閉


至於WM_QUERYENDSESSION是什麼,以下是MSDN對WM_QUERYENDSESSION的解釋

The WM_QUERYENDSESSION message is sent when the user chooses to end the session or when an application calls one of the system shutdown functions. If any application returns zero, the session is not ended. The system stops sending WM_QUERYENDSESSION messages as soon as one application returns zero.


After processing this message, the system sends the WM_ENDSESSION message with the wParam parameter set to the results of the WM_QUERYENDSESSION message.


翻成中文的意思就是說:


如果有一個應用程式對WM_QUERYENDSESSION回傳0,則系統就不會繼續發送WM_QUERYENDSESSION,關機的程序也就因為那個應用程式而宣告暫停(就會卡在上面那張圖)


p.s. 如果這時按Force Quit,則關閉應用程式就會變成系統主動去kill,完全不會理會應用程式的回應


當送完WM_QUERYENDSESSION,那麼系統就會再接著發送WM_ENDSESSION訊息,來「通知」應用程式是否會關機


注意,WM_ENDSESSION在這邊只有通知用喔,應用程式在這個階段已經沒有決定權了


淺略了解windows關機前的程序後,我們要做的就是在WM_QUERYENDSESSION時給它回傳0,讓系統因為我們而暫時停止關機程序

程式實作

首先先開一個新的專案,類型是視窗應用程式(Windows Forms App)


Layout要怎麼設計隨便,甚至我把它Opacity設0%,隱藏起來


接下來切換到該Form的程式碼頁

 


在創建的class新增下列程式碼,override掉原本的WndProc方法

protected override void WndProc(ref Message aMessage)

{

    const int WM_QUERYENDSESSION = 0x0011;

    const int WM_ENDSESSION = 0x0016;

 

    if (aMessage.Msg == WM_QUERYENDSESSION || aMessage.Msg == WM_ENDSESSION)

        return;

 

    base.WndProc(ref aMessage);

}



這個方法是用來讓系統或硬體跟應用程式溝通的,原本在被override前裡面也已經寫好遇到WM_QUERYENDSESSION及WM_ENDSESSION等等的訊息時要執行的動作


我們現在override後讓他遇到WM_QUERYENDSESSION跟WM_ENDSESSION訊息時直接return,啥都不要做;如果是其他則執行原本被override前的WndProc方法


如果有做到這一步其實就已經算完成了


編譯並執行程式後測試看看,按下登出、關機、重新開機,會發現系統的關機程序卡在像上圖一樣的地方。

 


如果還想要進一步更改提示訊息,我們需要使用ShutdownBlockReasonCreate這個方法


這個方法的用途就跟它名字一樣,應該不用多解釋吧:))


把以下程式碼新增在這個Form的建構子裡面,也就是InitializeComponent();下面,讓Form一初始化完就執行

ShutdownBlockReasonCreate(this.Handle, "這邊填要顯示的提示");


這時Visual Studio應該會報錯,說不認得這個方法


於是我們要告訴程式這個方法的實作已經在user32.dll完成了,去user32.dll裡面找

[DllImport("user32.dll")]

public extern static bool ShutdownBlockReasonCreate(IntPtr hWnd, [MarshalAs(UnmanagedType.LPWStr)] string pwszReason);

新增完後記得把動態載入dll所依賴的System.Runtime.InteropServices給補上

using System.Runtime.InteropServices;

到這邊算是第二階段的完成了。

如果想要把該Form從工作管理員隱藏,就把下列程式碼也加進去

protected override CreateParams CreateParams

{

    get

    {

        var cp = base.CreateParams;

        cp.ExStyle |= 0x80;

        return cp;

    }

}

最後附上完整程式碼

using System;

using System.Windows.Forms;

using System.Runtime.InteropServices;

 

namespace NoShutdown

{

    public partial class NoShutdown : Form

    {

        [DllImport("user32.dll")]

        public extern static bool ShutdownBlockReasonCreate(IntPtr hWnd, [MarshalAs(UnmanagedType.LPWStr)] string pwszReason);

         

        public NoShutdown()

        {

            InitializeComponent();

            ShutdownBlockReasonCreate(this.Handle, "這邊填要顯示的提示");

        }

         

        protected override void WndProc(ref Message aMessage)

        {

            const int WM_QUERYENDSESSION = 0x0011;

            const int WM_ENDSESSION = 0x0016;

 

            if (aMessage.Msg == WM_QUERYENDSESSION || aMessage.Msg == WM_ENDSESSION)

                return;

 

            base.WndProc(ref aMessage);

        }

 

        protected override CreateParams CreateParams //Hide from Task Manager

        {

            get

            {

                var cp = base.CreateParams;

                cp.ExStyle |= 0x80;

                return cp;

            }

        }

    }

}

小提醒:如果曾經使用過一些工具去優化關機速度的可能就不會被阻擋在那個畫面。因為那些優化為了減少關機時間,不管WM_QUERYENDSESSION回傳什麼會直接kill掉。


沒有留言:

張貼留言