如何阻止 Windows 10 安裝修改 BIOS 啟動設置?
我們正在通過 iPXE 將一些系統設置為 PXEboot,並且根據主伺服器狀態,正常啟動或通過 wimboot 和 MDT 重新映像。系統配置為首先從網路引導。iPXE 和 wimboot 都在 UEFI 下執行。
它工作得很好,除了在 Windows 安裝結束時,BIOS 已被修改為指向新的 Windows 引導管理器作為主引導設備。因此,如果不進入 bios 和更改設置,它就無法再次成像。
我理解為什麼引導順序會因為 wimboot/MDT 過程涉及多次重新啟動而改變。但我真的很想將 PXE 始終作為主引導,或者在完成後將引導順序重新設置為網路優先。(我的 PXE 伺服器將傳遞網路引導機會,以允許安裝工作或在不需要映像時讓系統獨立執行。)
**更新 -**我看到兩種可能性:
- 弄清楚 windows 安裝程序如何告訴 UEFI 從目標安裝磁碟啟動,並在 windows 安裝完成後執行相同的操作以設置回 PXE 啟動。
- 安裝 Windows 後使用 Windows 引導管理器和 BCDEdit 將 PXE 引導選項置於從本地磁碟引導上方(在超級使用者處發現的問題與此處基本相同。討論的最終結果並不是我真正想要的( PXE 首先在 UEFI 設置中)但可能會產生相同的行為(PXE 啟動總是有機會在 Windows 啟動之前採取行動)。
學到了以下內容:
- 在 Linux 上,這將相當簡單,通過efibootmgr
- EasyUEFI 也可以讓我做我想做的事——命令行支持需要相當便宜的許可證;但我感覺不太好依賴像它這樣的利基工具,特別是如果有其他選擇的話。
- UEFI 機器上的 bcdedit 修改 UEFI 設置。我認為它會起作用。
- 引導順序的 UEFI 規範並不太複雜。API 實際上只是 GetVariable/SetVariable 與名為 BootOrder 的變數(按照它們將被嘗試的順序獲取/設置引導選項列表)和 Boot####(獲取/設置有關每個引導選項的資訊)。
- 我不知道如何針對 windows 上的 UEFI API 編寫 windows 應用程序(任何人?)
- Windows 提供了一個 API,其中包含 UEFI 的 GetVariable/SetVariable。
一旦我了解了引導順序和 Windows API 的 UEFI 規範,程式碼(C++,為 64 位建構,因為這就是我們所使用的全部)並不算太糟糕。這需要內置到需要管理權限並靜態連結windows執行時的exe中,然後我在安裝作業系統後在MDT中執行它,然後重新啟動。
首先,您必須聲明呼叫 API 的特權。使用一個小幫手:
struct CloseHandleHelper { void operator()(void *p) const { CloseHandle(p); } }; BOOL SetPrivilege(HANDLE process, LPCWSTR name, BOOL on) { HANDLE token; if (!OpenProcessToken(process, TOKEN_ADJUST_PRIVILEGES, &token)) return FALSE; std::unique_ptr<void, CloseHandleHelper> tokenLifetime(token); TOKEN_PRIVILEGES tp; tp.PrivilegeCount = 1; if (!LookupPrivilegeValueW(NULL, name, &tp.Privileges[0].Luid)) return FALSE; tp.Privileges[0].Attributes = on ? SE_PRIVILEGE_ENABLED : 0; return AdjustTokenPrivileges(token, FALSE, &tp, sizeof(tp), NULL, NULL); }
然後打電話
SetPrivilege(GetCurrentProcess(), SE_SYSTEM_ENVIRONMENT_NAME, TRUE));
接下來,獲取引導選項列表(uint16_t 值的串聯):
const int BUFFER_SIZE = 4096; BYTE bootOrderBuffer[BUFFER_SIZE]; DWORD bootOrderLength = 0; const TCHAR bootOrderName[] = TEXT("BootOrder"); const TCHAR globalGuid[] = TEXT("{8BE4DF61-93CA-11D2-AA0D-00E098032B8C}"); DWORD bootOrderAttributes; bootOrderLength = GetFirmwareEnvironmentVariableEx(bootOrderName, globalGuid, bootOrderBuffer, BUFFER_SIZE, &bootOrderAttributes); if (bootOrderLength == 0) { std::cout << "Failed getting BootOrder with error " << GetLastError() << std::endl; return 1; }
然後,您可以遍歷每個引導選項,為其形成 Boot#### 變數名稱,然後使用它來獲取包含有關該選項的資訊的結構。您需要查看第一個活動選項的“描述”是否等於“Windows 啟動管理器”。描述是結構中偏移量 6 處的以空字元結尾的寬字元串。
for (DWORD i = 0; i < bootOrderLength; i += 2) { std::wstringstream bootOptionNameBuilder; bootOptionNameBuilder << "Boot" << std::uppercase << std::setfill(L'0') << std::setw(4) << std::hex << *reinterpret_cast<uint16_t*>(bootOrderBuffer + i); std::wstring bootOptionName(bootOptionNameBuilder.str()); BYTE bootOptionInfoBuffer[BUFFER_SIZE]; DWORD bootOptionInfoLength = GetFirmwareEnvironmentVariableEx(bootOptionName.c_str(), globalGuid, bootOptionInfoBuffer, BUFFER_SIZE, nullptr); if (bootOptionInfoLength == 0) { std::cout << "Failed getting option info for option at offset " << i << std::endl; return 1; } uint32_t* bootOptionInfoAttributes = reinterpret_cast<uint32_t*>(bootOptionInfoBuffer); //First 4 bytes make a uint32_t comprised of flags. 0x1 means the boot option is active (not disabled) if (((*bootOptionInfoAttributes) & 0x1) != 0) { std::wstring description(reinterpret_cast<wchar_t*>(bootOptionInfoBuffer + sizeof(uint32_t) + sizeof(uint16_t))); bool isWBM = boost::algorithm::to_upper_copy<std::wstring>(description) == L"WINDOWS BOOT MANAGER"; // details - keep track of the value of i for the first WBM and non-WBM options you find, and the fact that you found them } }
現在,如果您發現活動的 WBM 和非 WBM 引導選項,並且第一個 WBM 選項位於 wbmOffset,並且第一個非 WBM 選項位於 nonWBMOffset,且 wbmOffset < nonWBMOffset,則將 BootOrder 變數中的條目交換為以下內容:
uint16_t *wbmBootOrderEntry = reinterpret_cast<uint16_t*>(bootOrderBuffer + wbmOffset); uint16_t *nonWBMBootOrderEntry = reinterpret_cast<uint16_t*>(bootOrderBuffer + nonWBMOffset); std::swap(*wbmBootOrderEntry, *nonWBMBootOrderEntry); if (SetFirmwareEnvironmentVariableEx(bootOrderName, globalGuid, bootOrderBuffer, bootOrderLength, bootOrderAttributes)) { std::cout << "Swapped WBM boot entry at offset " << wbmOffset << " with non-WBM boot entry at offset " << nonWBMOffset << std::endl; } else { std::cout << "Failed to swap WBM boot entry with non-WBM boot entry, error " << GetLastError() << std::endl; return 1; }