Windows 2000/XP 引入嚴格的安全模型,導致傳統關機程式在非管理員權限下失效。本文系統性解析定時關機的核心難點——SE_SHUTDOWN_NAME 權限獲取,並提供生產級的完整實作碼,涵蓋權限調整、關機啟動、錯誤處理與權限還原的完整生命週期。
Windows 安全子系統要求執行關機操作的進程必須持有 SE_SHUTDOWN_NAME 特權。此特權預設授予:
普通用戶進程需在執行時動態啟用此特權,步驟如下:
OpenProcessToken 取得進程存取權杖LookupPrivilegeValue 解析特權名稱對應的 LUIDAdjustTokenPrivileges 啟用特權AdjustTokenPrivileges 還原特權狀態(最佳實踐)BOOL ExecuteScheduledShutdown(int nTimeoutSeconds, BOOL bReboot, LPCTSTR lpszMessage)
{
TOKEN_PRIVILEGES tkp = {0};
HANDLE hToken = NULL;
BOOL fResult = FALSE;
// 1. 開啟進程權杖,請求 TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY
if (!OpenProcessToken(GetCurrentProcess(),
TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))
{
MessageBox("OpenProcessToken failed!");
return FALSE;
}
// 2. 取得 SE_SHUTDOWN_NAME 的 LUID
if (!LookupPrivilegeValue(NULL, SE_SHUTDOWN_NAME, &tkp.Privileges[0].Luid))
{
MessageBox("LookupPrivilegeValue failed!");
CloseHandle(hToken);
return FALSE;
}
// 3. 啟用關機特權
tkp.PrivilegeCount = 1;
tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
if (!AdjustTokenPrivileges(hToken, FALSE, &tkp, 0, NULL, 0))
{
MessageBox("AdjustTokenPrivileges enable failed!");
CloseHandle(hToken);
return FALSE;
}
// 4. 檢查特權調整是否真正生效
if (GetLastError() != ERROR_SUCCESS)
{
MessageBox("AdjustTokenPrivileges enable failed (Not all privileges assigned)!");
CloseHandle(hToken);
return FALSE;
}
// 5. 執行系統關機/重啟
// InitiateSystemShutdown 支援遠端關機、倒數計時對話框、自訂訊息
fResult = InitiateSystemShutdown(
NULL, // NULL=本機,遠端機器名稱格式 "\\\\ComputerName"
lpszMessage, // 顯示在倒數對話框的訊息
nTimeoutSeconds, // 倒數秒數
TRUE, // bForceAppsClosed: 強制關閉應用程式
bReboot // TRUE=重啟, FALSE=關機
);
if (!fResult)
{
MessageBox("InitiateSystemShutdown failed.");
}
// 6. 關鍵:還原特權狀態(移除 SE_PRIVILEGE_ENABLED)
tkp.Privileges[0].Attributes = 0;
AdjustTokenPrivileges(hToken, FALSE, &tkp, 0, NULL, 0);
if (GetLastError() != ERROR_SUCCESS)
{
MessageBox("AdjustTokenPrivileges disable failed.");
}
CloseHandle(hToken);
// 7. 確保關機流程啟動(InitiateSystemShutdown 為非同步)
if (fResult)
{
ExitWindowsEx(EWX_SHUTDOWN | (bReboot ? EWX_REBOOT : 0) | EWX_FORCE, 0);
}
return fResult;
}
| 特性 | InitiateSystemShutdown | ExitWindowsEx |
|---|---|---|
| 遠端關機 | 支援 | 不支援 |
| 倒數計時對話框 | 內建(可自訂訊息/秒數) | 無 |
| 取消關機 | AbortSystemShutdown | 無標準機制 |
| 同步性 | 非同步(啟動後立即返回) | 同步(阻塞至關機完成) |
| 適用場景 | 排程關機、遠端管理、使用者通知 | 即時關機、應用程式整合 |
配合 MFC 對話框實作使用者可設定的倒數計時關機:
void CShutdownDlg::OnOK()
{
CString strMsg;
GetDlgItemText(IDC_MSG_EDIT, strMsg);
int nMinutes = GetDlgItemInt(IDC_MINUTES_EDIT);
int nSeconds = nMinutes * 60;
BOOL bReboot = ((CButton*)GetDlgItem(IDC_REBOOT_CHECK))->GetCheck();
if (ExecuteScheduledShutdown(nSeconds, bReboot, strMsg))
{
// 關機已啟動,可選擇隱藏視窗或顯示倒數
ShowWindow(SW_HIDE);
}
else
{
MessageBox("關機啟動失敗,請檢查權限設定");
}
}
AbortSystemShutdown 取消既有關機 |
| 1116 (ERROR_SHUTDOWN_USERS_LOGGED_ON) | 其他用戶已登入 | 使用 EWX_FORCE 強制登出,或通知用戶儲存工作 |
Windows 定時關機的核心挑戰在於權限模型的正確操作。掌握 TOKEN_PRIVILEGES 調整流程、善用 InitiateSystemShutdown 的豐富參數、嚴謹處理錯誤碼與特權還原,才能構建可靠的跨版本關機解決方案。建議封裝為可重用的工具類,統一處理權限、日誌記錄與異常回報。