首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

C# WebForm 強制重啟伺服器:P/Invoke ExitWindowsEx 完整實作

     編輯:關於C語言

前言

Web 應用程式需提供遠端重啟伺服器功能。本文展示透過 P/Invoke 呼叫 Windows API ExitWindowsEx、AdjustTokenPrivileges 實現強制重啟,包含權限提升、關機原因代碼、完整 ASP.NET WebForm 程式碼。

核心 Windows API 與常數


using System.Runtime.InteropServices;

public class ServerRebooter
{
    // 權限常數
    const int SE_PRIVILEGE_ENABLED = 0x00000002;
    const int TOKEN_QUERY = 0x00000008;
    const int TOKEN_ADJUST_PRIVILEGES = 0x00000020;
    const string SE_SHUTDOWN_NAME = "SeShutdownPrivilege";
    
    // 關機類型
    const int EWX_LOGOFF = 0x00;
    const int EWX_SHUTDOWN = 0x01;
    const int EWX_REBOOT = 0x02;
    const int EWX_FORCE = 0x04;
    const int EWX_POWEROFF = 0x08;
    const int EWX_FORCEIFHUNG = 0x10;
    
    // LUID 結構
    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    struct LUID_AND_ATTRIBUTES
    {
        public int Count;
        public long Luid;
        public int Attributes;
    }
    
    // P/Invoke 宣告
    [DllImport("kernel32.dll", ExactSpelling = true)]
    static extern IntPtr GetCurrentProcess();
    
    [DllImport("advapi32.dll", SetLastError = true)]
    static extern bool OpenProcessToken(IntPtr h, int acc, ref IntPtr phtok);
    
    [DllImport("advapi32.dll", SetLastError = true)]
    static extern bool LookupPrivilegeValue(string host, string name, ref long pluid);
    
    [DllImport("advapi32.dll", SetLastError = true, ExactSpelling = true)]
    static extern bool AdjustTokenPrivileges(IntPtr htok, bool disall, 
        ref LUID_AND_ATTRIBUTES newst, int len, IntPtr prev, IntPtr relen);
    
    [DllImport("user32.dll", SetLastError = true, ExactSpelling = true)]
    static extern bool ExitWindowsEx(int flags, int reason);
    
    [DllImport("advapi32.dll", SetLastError = true, ExactSpelling = false)]
    static extern bool InitiateSystemShutdown(string name, string msg, 
        int timeout, bool force, bool reboot);
}

核心邏輯:提升權限並執行關機


static void DoExitWindows(int flags)
{
    LUID_AND_ATTRIBUTES tp = new();
    IntPtr hProcess = GetCurrentProcess();
    IntPtr hToken = IntPtr.Zero;
    
    // 1. 開啟處理序 Token
    if (!OpenProcessToken(hProcess, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, ref hToken))
        throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());
    
    // 2. 取得關機權限 LUID
    tp.Count = 1;
    tp.Luid = 0;
    tp.Attributes = SE_PRIVILEGE_ENABLED;
    
    if (!LookupPrivilegeValue(null, SE_SHUTDOWN_NAME, ref tp.Luid))
        throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());
    
    // 3. 調整 Token 權限:啟用 SeShutdownPrivilege
    if (!AdjustTokenPrivileges(hToken, false, ref tp, 0, IntPtr.Zero, IntPtr.Zero))
        throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());
    
    // 4. 執行關機/重啟
    if (!ExitWindowsEx(flags, 0))
        throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());
}

public static void Reboot() => DoExitWindows(EWX_REBOOT | EWX_FORCE);
public static void Shutdown() => DoExitWindows(EWX_SHUTDOWN | EWX_FORCE);
public static void Logoff() => DoExitWindows(EWX_LOGOFF);

ASP.NET WebForm 完整頁面程式碼


<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="RebootServer.aspx.cs" Inherits="WebApp.RebootServer" %>




    伺服器重啟控制台


    

伺服器電源控制








public partial class RebootServer : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e) { }
    
    protected void btnReboot_Click(object sender, EventArgs e)
    {
        try
        {
            // 方式 1: InitiateSystemShutdown (可設定訊息、倒數、強制)
            // InitiateSystemShutdown(null, "系統將於 30 秒後重啟進行維護", 30, true, true);
            
            // 方式 2: 直接呼叫 ExitWindowsEx (立即強制)
            ServerRebooter.Reboot();
            
            lblStatus.Text = "重啟指令已送出...";
        }
        catch (Exception ex)
        {
            lblStatus.Text = $"失敗: {ex.Message}";
        }
    }
    
    protected void btnShutdown_Click(object sender, EventArgs e)
    {
        try { ServerRebooter.Shutdown(); lblStatus.Text = "關機指令已送出..."; }
        catch (Exception ex) { lblStatus.Text = $"失敗: {ex.Message}"; }
    }
    
    protected void btnLogoff_Click(object sender, EventArgs e)
    {
        try { ServerRebooter.Logoff(); lblStatus.Text = "登出指令已送出..."; }
        catch (Exception ex) { lblStatus.Text = $"失敗: {ex.Message}"; }
    }
}

關鍵權限說明

權限/旗標用途
SeShutdownPrivilege允許關機/重啟本機系統的特權
TOKEN_ADJUST_PRIVILEGES調整 Token 權限的存取權
EWX_FORCE強制關閉應用程式,不詢問使用者儲存
EWX_REBOOT關機後重新開機
EWX_SHUTDOWN關機並切斷電源 (需硬體支援)

部署與安全性注意事項

  • 應用程式集區身分識別:需具備「關閉系統」本地安全性原則權限 (Local Policy → User Rights Assignment → Shut down the system)
  • 最小權限原則:專用服務帳號,僅授予 SeShutdownPrivilege
  • 防誤觸發:前端確認對話框、操作日誌記錄、IP 白名單限制
  • 替代方案:shutdown.exe /r /f /t 30 /c "維護重啟" 配合 Process.Start,無需 P/Invoke

總結

透過 P/Invoke 呼叫 AdjustTokenPrivileges 啟用 SeShutdownPrivilege,再呼叫 ExitWindowsEx 執行重啟,是 C# 實現程式化伺服器重啟的標準方式。生產環境務必嚴格管控權限與操作審計,防止惡意或誤操作導致服務中斷。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved