Web 應用程式需提供遠端重啟伺服器功能。本文展示透過 P/Invoke 呼叫 Windows API ExitWindowsEx、AdjustTokenPrivileges 實現強制重啟,包含權限提升、關機原因代碼、完整 ASP.NET WebForm 程式碼。
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);
<%@ 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 | 關機並切斷電源 (需硬體支援) |
shutdown.exe /r /f /t 30 /c "維護重啟" 配合 Process.Start,無需 P/Invoke透過 P/Invoke 呼叫 AdjustTokenPrivileges 啟用 SeShutdownPrivilege,再呼叫 ExitWindowsEx 執行重啟,是 C# 實現程式化伺服器重啟的標準方式。生產環境務必嚴格管控權限與操作審計,防止惡意或誤操作導致服務中斷。