宿舍吵鬧想遠程/定時關機,遂編寫控制台小工具,核心難點:**Windows 2000 後關機/注銷需特權提升**。本文記錄最小可行性實現與踩坑避坑。
#include
#include
using namespace std;
// 提升「調試特權」—— 用於注銷
void EnableLogoffPrivilege() {
HANDLE hToken;
TOKEN_PRIVILEGES tkp = {1, {{0, SE_PRIVILEGE_ENABLED}}};
OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken);
LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &tkp.Privileges[0].Luid);
AdjustTokenPrivileges(hToken, FALSE, &tkp, 0, NULL, NULL);
}
// 提升「關機特權」—— 用於關機/重啟
void EnableShutdownPrivilege() {
HANDLE hToken;
TOKEN_PRIVILEGES tkp = {1, {{0, SE_PRIVILEGE_ENABLED}}};
OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken);
LookupPrivilegeValue(NULL, SE_SHUTDOWN_NAME, &tkp.Privileges[0].Luid);
AdjustTokenPrivileges(hToken, FALSE, &tkp, 0, NULL, NULL);
}
int main() {
int choice;
cout << "1 - 注銷\n2 - 關機\n選擇: ";
cin >> choice;
switch (choice) {
case 1:
EnableLogoffPrivilege();
ExitWindowsEx(EWX_LOGOFF, 0);
cout << "正在注銷...\n";
break;
case 2:
EnableShutdownPrivilege();
ExitWindowsEx(EWX_SHUTDOWN, 0);
cout << "正在關機...\n";
break;
default:
cout << "無效選項\n";
}
return 0;
}
LookupPrivilegeValue 第二參數決定:
SE_DEBUG_NAME → **調試特權** → 滿足 EWX_LOGOFF(注銷)。SE_SHUTDOWN_NAME → **關機特權** → 滿足 EWX_SHUTDOWN(關機/重啟)。**實測發現**:只提升調試特權調 EWX_SHUTDOWN 會失敗;只提升關機特權調 EWX_LOGOFF 同理。**特權與操作一一綁定**,ExitWindowsEx 只是觸發器,真正的守門人是特權檢查。
| 步驟 | API | 作用 |
|---|---|---|
| 1 | OpenProcessToken | 獲取當前進程訪問令牌句柄 hToken,請求 TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY 權限。 |
| 2 | LookupPrivilegeValue | 將特權名稱(字符串)轉換為本地唯一標識 LUID,寫入 TOKEN_PRIVILEGES.Privileges[0].Luid。 |
| 3 | 設置結構體 | PrivilegeCount=1;Attributes=SE_PRIVILEGE_ENABLED 啟用該特權。 |
| 4 | AdjustTokenPrivileges | 將修改後的特權集合寫回令牌,生效。注意:返回值只表示 API 調用成功,**不代表特權真正啟用**,需配合 GetLastError() == ERROR_NOT_ALL_ASSIGNED 判斷。 |
若需遠程操作,LookupPrivilegeValue 第一參數傳目標機器名(L"\\\\PCNAME"),調用進程需在目標機擁有管理員權限,且目標機開啟「遠程注冊表」/「WMI」服務。詳細參考 MSDN InitiateSystemShutdownEx。
schtasks /create /sc once /tn "AutoShutdown" /tr "shutdown_tool.exe 2" /st 23:30 系統級調度,可靠、無需常駐進程。SetWaitableTimer + 消息循環,或獨立線程 Sleep 計算倒計時,到點調用關機邏輯。需處理休眠/喚醒、用戶取消、權限保持等邊界情況。Shell_NotifyIcon)、倒計時顯示、熱鍵取消,體驗更佳。AdjustTokenPrivileges 後檢查 GetLastError()。ExitWindowsEx 第二參數 SHTDN_REASON_MAJOR_OTHER | SHTDN_REASON_MINOR_OTHER | SHTDN_REASON_FLAG_PLANNED 記錄關機原因,便於事件查看器審計。requireAdministrator 清單(VS 專案屬性 → Linker → Manifest File → UAC Execution Level),免去手動「以管理員身份運行」。L"",避免 ANSI/Unicode 混用導致 LookupPrivilegeValue 失敗。Windows 關機看似簡單,實則涉及**訪問控制模型(ACL)**、**特權機制**、**令牌操作**核心系統編程概念。從「為什麼要兩個提權函數」出發,徹底理解 SE_DEBUG_NAME vs SE_SHUTDOWN_NAME 的分工,是從「調用 API」進階到「理解系統」的關鍵一步。定時功能交給系統任務計劃最省心,若要寫 GUI 版,記得把特權提升封裝成可復用的 PrivilegeEnabler RAII 類。