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

Windows 關機/注銷小工具:從權限提升到定時計劃

     編輯:關於C語言

需求背景

宿舍吵鬧想遠程/定時關機,遂編寫控制台小工具,核心難點:**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;
}

關鍵點深度解析

1. 為什麼需要兩個單獨的提權函數?

LookupPrivilegeValue 第二參數決定:

  • SE_DEBUG_NAME → **調試特權** → 滿足 EWX_LOGOFF(注銷)。
  • SE_SHUTDOWN_NAME → **關機特權** → 滿足 EWX_SHUTDOWN(關機/重啟)。

**實測發現**:只提升調試特權調 EWX_SHUTDOWN 會失敗;只提升關機特權調 EWX_LOGOFF 同理。**特權與操作一一綁定**,ExitWindowsEx 只是觸發器,真正的守門人是特權檢查。

2. 四步提權流程詳解

步驟API作用
1OpenProcessToken獲取當前進程訪問令牌句柄 hToken,請求 TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY 權限。
2LookupPrivilegeValue將特權名稱(字符串)轉換為本地唯一標識 LUID,寫入 TOKEN_PRIVILEGES.Privileges[0].Luid。
3設置結構體PrivilegeCount=1;Attributes=SE_PRIVILEGE_ENABLED 啟用該特權。
4AdjustTokenPrivileges將修改後的特權集合寫回令牌,生效。注意:返回值只表示 API 調用成功,**不代表特權真正啟用**,需配合 GetLastError() == ERROR_NOT_ALL_ASSIGNED 判斷。

3. 遠程關機擴展

若需遠程操作,LookupPrivilegeValue 第一參數傳目標機器名(L"\\\\PCNAME"),調用進程需在目標機擁有管理員權限,且目標機開啟「遠程注冊表」/「WMI」服務。詳細參考 MSDN InitiateSystemShutdownEx。

下一步規劃:定時功能

  1. 方案 A:任務計劃程序(推薦)
    不改代碼,用 schtasks /create /sc once /tn "AutoShutdown" /tr "shutdown_tool.exe 2" /st 23:30 系統級調度,可靠、無需常駐進程。
  2. 方案 B:程序內計時器
    SetWaitableTimer + 消息循環,或獨立線程 Sleep 計算倒計時,到點調用關機邏輯。需處理休眠/喚醒、用戶取消、權限保持等邊界情況。
  3. 方案 C:GUI 版本
    加系統托盤圖標(Shell_NotifyIcon)、倒計時顯示、熱鍵取消,體驗更佳。

避坑清單

  • ✅ **分離特權函數**:注銷/關機各用對應 LUID,切勿合用。
  • ✅ **錯誤檢查**:每個 API 檢查返回值,AdjustTokenPrivileges 後檢查 GetLastError()。
  • ✅ **清單清單**:ExitWindowsEx 第二參數 SHTDN_REASON_MAJOR_OTHER | SHTDN_REASON_MINOR_OTHER | SHTDN_REASON_FLAG_PLANNED 記錄關機原因,便於事件查看器審計。
  • ✅ **管理員權限**:程序清單加 requireAdministrator 清單(VS 專案屬性 → Linker → Manifest File → UAC Execution Level),免去手動「以管理員身份運行」。
  • ✅ **編碼統一**:項目設置 Unicode,字面量用 L"",避免 ANSI/Unicode 混用導致 LookupPrivilegeValue 失敗。

總結

Windows 關機看似簡單,實則涉及**訪問控制模型(ACL)**、**特權機制**、**令牌操作**核心系統編程概念。從「為什麼要兩個提權函數」出發,徹底理解 SE_DEBUG_NAME vs SE_SHUTDOWN_NAME 的分工,是從「調用 API」進階到「理解系統」的關鍵一步。定時功能交給系統任務計劃最省心,若要寫 GUI 版,記得把特權提升封裝成可復用的 PrivilegeEnabler RAII 類。

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