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

Windows 定時關機權威指南:權限提升與 InitiateSystemShutdown 完整實作

     編輯:關於VC++

Windows 2000/XP 引入嚴格的安全模型,導致傳統關機程式在非管理員權限下失效。本文系統性解析定時關機的核心難點——SE_SHUTDOWN_NAME 權限獲取,並提供生產級的完整實作碼,涵蓋權限調整、關機啟動、錯誤處理與權限還原的完整生命週期。

權限模型核心概念

Windows 安全子系統要求執行關機操作的進程必須持有 SE_SHUTDOWN_NAME 特權。此特權預設授予:

  • Administrators 群組
  • Backup Operators 群組

普通用戶進程需在執行時動態啟用此特權,步驟如下:

  1. OpenProcessToken 取得進程存取權杖
  2. LookupPrivilegeValue 解析特權名稱對應的 LUID
  3. AdjustTokenPrivileges 啟用特權
  4. 執行關機 API
  5. 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 vs ExitWindowsEx 差異分析

特性InitiateSystemShutdownExitWindowsEx
遠端關機支援不支援
倒數計時對話框內建(可自訂訊息/秒數)無
取消關機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("關機啟動失敗,請檢查權限設定");
    }
}

常見失敗原因與對策

| 錯誤碼 | 含義 | 解決方案 | |--------|------|----------| | 1300 (ERROR_NOT_ALL_ASSIGNED) | 特權未授予當前用戶 | 本地安全原則 → 用戶權利指派 → 關閉系統 → 加入用戶/群組 | | 5 (ERROR_ACCESS_DENIED) | 存取被拒 | 以 Administrator 身分執行,或檢查 UAC 虛擬化影響 | | 1115 (ERROR_SHUTDOWN_IN_PROGRESS) | 已有關機進行中 | 呼叫 AbortSystemShutdown 取消既有關機 | | 1116 (ERROR_SHUTDOWN_USERS_LOGGED_ON) | 其他用戶已登入 | 使用 EWX_FORCE 強制登出,或通知用戶儲存工作 |

總結

Windows 定時關機的核心挑戰在於權限模型的正確操作。掌握 TOKEN_PRIVILEGES 調整流程、善用 InitiateSystemShutdown 的豐富參數、嚴謹處理錯誤碼與特權還原,才能構建可靠的跨版本關機解決方案。建議封裝為可重用的工具類,統一處理權限、日誌記錄與異常回報。

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