使用 BCDEDIT 将 PXE 启动配置为默认启动选项

使用 BCDEDIT 将 PXE 启动配置为默认启动选项

我的实验室里满是使用 UEFI 的计算机,我希望始终在所有其他启动选项之前尝试 PXE 启动。然而,在自动镜像一台装有 Windows 8.1/Windows 10 的 PC 后,UEFI 启动顺序(不出所料)被 Windows 更改为 Windows 启动管理器。

我如何以编程方式更改启动顺序,以便始终使用 BCDEDIT(或其他基于 Windows 的工具)将 PXE 启动(IPv4)重置为默认值?BCDEDIT 是否具有用于 PXE 启动的知名 GUID 或类似功能?

答案1

虽然 @nex84 关于 BCD 位于 BIOS 启动菜单更高级别的评论是正确的,但严格来说并非如此。在 UEFI 机器上,BCD 条目实际上合并了固件的本机“启动管理器”和 Windows 启动管理器。

您可以使用枚举所有条目bcdedit /enum all,这将包括 PXE 启动选项 - 当然,假设它已经存在于您的“BIOS”中。然后,您可以使用常用bcdedit /displayorder命令来操作启动顺序。

您可能还希望使用易BCD以获得免费软件 GUI 选项。默认情况下,最新版本的 EasyBCD 会隐藏 UEFI 级条目,但如果您在选项中启用“专家模式”,它们将变为可用。(披露:我与 EasyBCD 的作者 NeoSmart Technologies 合作)

在使用 UEFI 启动变量时,请非常小心地使用 bcdedit。我曾亲自试验过一些设备,这些设备被永久破坏,因为它们仅将其固件配置应用程序(又称 BIOS 设置)作为此启动菜单的功能显示,错误配置它可能会造成永久性损坏(除非您手头有 EEPROM 编程器来刷新固件,并且您恰好非常擅长表面贴装焊接)。

答案2

我遇到了同样的问题,于是在服务器故障。我花了一整天的时间在 Google 上搜索,才找到足够的信息来解决这个问题。如下所示:

  1. 在 Linux 上,这相当简单,通过启动管理器
  2. EasyUEFI 也能让我做我想做的事——命令行支持需要相当便宜的许可证;但依赖像这样的小众工具我感觉不太好,特别是如果有其他选择的话。
  3. UEFI 计算机上的 bcdedit 修改 UEFI 设置。我认为它会起作用。
  4. UEFI 规范启动顺序并不太复杂。API 实际上只是 GetVariable/SetVariable,其中包含名为 BootOrder(用于按尝试顺序获取/设置启动选项列表)和 Boot####(用于获取/设置有关每个启动选项的信息)的变量。
  5. 我不知道如何针对 Windows 上的 UEFI API 编写 Windows 应用程序(有人知道吗?)
  6. Windows 提供了一个 API除其他外,它包装了 UEFI 的 GetVariable/SetVariable。

一旦我理解了 UEFI 的启动顺序规范和 Windows API,代码(C++,为 64 位构建,因为我们只使用 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#### 变量名,然后使用它来获取包含有关该选项的信息的结构。您需要查看第一个活动选项的“Description”是否等于“Windows Boot Manager”。Description 是结构中偏移量为 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;
    }

答案3

PXE 启动在 BIOS 启动顺序中设置。

BCD 引导加载程序(适用于 Windows)在 BIOS 步骤之后启动,因此不会对其产生影响。

要在 PXE 上启动,您必须在 BIOS 启动顺序中将其设置在托管 BCD 引导加载程序的设备之前。

相关内容