Deployment

如何阻止 Windows 10 安裝修改 BIOS 啟動設置?

  • March 28, 2019

我們正在通過 iPXE 將一些系統設置為 PXEboot,並且根據主伺服器狀態,正常啟動或通過 wimboot 和 MDT 重新映像。系統配置為首先從網路引導。iPXE 和 wimboot 都在 UEFI 下執行。

它工作得很好,除了在 Windows 安裝結束時,BIOS 已被修改為指向新的 Windows 引導管理器作為主引導設備。因此,如果不進入 bios 和更改設置,它就無法再次成像。

我理解為什麼引導順序會因為 wimboot/MDT 過程涉及多次重新啟動而改變。但我真的很想將 PXE 始終作為主引導,或者在完成後將引導順序重新設置為網路優先。(我的 PXE 伺服器將傳遞網路引導機會,以允許安裝工作或在不需要映像時讓系統獨立執行。)

**更新 -**我看到兩種可能性:

  1. 弄清楚 windows 安裝程序如何告訴 UEFI 從目標安裝磁碟啟動,並在 windows 安裝完成後執行相同的操作以設置回 PXE 啟動。
  2. 安裝 Windows 後使用 Windows 引導管理器和 BCDEdit 將 PXE 引導選項置於從本地磁碟引導上方(在超級使用者處發現的問題與此處基本相同。討論的最終結果並不是我真正想要的( PXE 首先在 UEFI 設置中)但可能會產生相同的行為(PXE 啟動總是有機會在 Windows 啟動之前採取行動)。

學到了以下內容:

  1. 在 Linux 上,這將相當簡單,通過efibootmgr
  2. EasyUEFI 也可以讓我做我想做的事——命令行支持需要相當便宜的許可證;但我感覺不太好依賴像它這樣的利基工具,特別是如果有其他選擇的話。
  3. UEFI 機器上的 bcdedit 修改 UEFI 設置。我認為它會起作用。
  4. 引導順序的 UEFI 規範並不太複雜。API 實際上只是 GetVariable/SetVariable 與名為 BootOrder 的變數(按照它們將被嘗試的順序獲取/設置引導選項列表)和 Boot####(獲取/設置有關每個引導選項的資訊)。
  5. 我不知道如何針對 windows 上的 UEFI API 編寫 windows 應用程序(任何人?)
  6. 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&lt;uint16_t*&gt;(bootOrderBuffer + wbmOffset);
   uint16_t *nonWBMBootOrderEntry = reinterpret_cast&lt;uint16_t*&gt;(bootOrderBuffer + nonWBMOffset);
   std::swap(*wbmBootOrderEntry, *nonWBMBootOrderEntry);
   if (SetFirmwareEnvironmentVariableEx(bootOrderName, globalGuid, bootOrderBuffer, bootOrderLength, bootOrderAttributes))
   {
       std::cout &lt;&lt; "Swapped WBM boot entry at offset " &lt;&lt; wbmOffset &lt;&lt; " with non-WBM boot entry at offset " &lt;&lt; nonWBMOffset &lt;&lt; std::endl;
   }
   else
   {
       std::cout &lt;&lt; "Failed to swap WBM boot entry with non-WBM boot entry, error " &lt;&lt; GetLastError() &lt;&lt; std::endl;
       return 1;
   }

引用自:https://serverfault.com/questions/813695