3

I have a lab full of computers using UEFI that I want to always attempt to PXE boot before all other boot options. However, after automatically imaging a PC with Windows 8.1/Windows 10, the UEFI boot order gets changed (unsurprisingly) by Windows to Windows Boot Manager.

How can I programmatically change the boot order so that PXE booting (IPv4) always gets reset back to the default using BCDEDIT (or some other Windows-based tool)? Does BCDEDIT have a well-known GUID or similar for PXE booting?

3 Answers 3

1

While @nex84's comments about the BCD being at a higher level than the BIOS boot menu is correct, it's not strictly so. On UEFI machines, the BCD entries actually coalesce both the firmware's native "boot manager" and the Windows boot manager.

You can enumerate all entries by using bcdedit /enum all and this will include the PXE boot option -- assuming, of course, that it already exists in your "BIOS". You can then manipulate the boot order with the usual bcdedit /displayorder commands.

You may also wish to use EasyBCD for a freeware GUI option. By default, the latest version of EasyBCD hides UEFI-level entries from the display, but if you enable "Expert Mode" in the options, they will become available. (Disclosure: I'm with NeoSmart Technologies, authors of EasyBCD)

Please be very, very careful with bcdedit when playing with UEFI boot variables. I have personally experimented with devices that were permanently trashed because they present their firmware configuration app (aka BIOS setup) only as a function of this boot menu, incorrectly configuring it could be permanent (unless you have an EEPROM programmer on hand to reflash the firmware, and you happen to be very handy with surfacemount soldering).

1

I found myself with the same problem and asked it over at serverfault. It took a good day of google-stumbling before I pieced together enough to solve it. Here it goes:

  1. On Linux, this would be fairly straightforward, via efibootmgr
  2. EasyUEFI would let me do what I want too - command line support requires a fairly cheap license; but I don't feel great depending on a niche tool like it, especially if there are other options.
  3. bcdedit on a UEFI machine modifies UEFI settings. I think it would work.
  4. The UEFI spec for boot order isn't too complicated. The API is really just GetVariable/SetVariable with variables named BootOrder (to get/set the list of boot options in the order they'll be tried) and Boot#### (to get/set info about each boot option).
  5. I have no idea how I'd write a windows app against the UEFI API on windows (anyone?)
  6. Windows provides an API that, among other things, wraps UEFI's GetVariable/SetVariable.

Once I understood the UEFI spec for boot order and the windows API, the code (C++, built for 64-bit as that is all we are using) wasn't too bad. This needs to be built into an exe that requires administrative privileges and statically links the windows runtime, and then I run it in MDT after the OS is installed before restart.

First, you have to claim a privilege to call the API. Use a little helper:

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);
}

then call

SetPrivilege(GetCurrentProcess(), SE_SYSTEM_ENVIRONMENT_NAME, TRUE));

Next, get the list of boot options (a concatenation of uint16_t values):

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;
}

You then can iterate over each boot option, form the Boot#### variable name for it, and then use that to get a struct with info about the option. You'll want to see if the first active option has "Description" equal to "Windows Boot Manager". Description is a null-terminated wide character string at offset 6 in the struct.

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
    }
}

Now if you found active WBM and non-WBM boot options and the first WBM option is at wbmOffset, and the first non-WBM option is at nonWBMOffset, with wbmOffset < nonWBMOffset, swap the entries in the BootOrder variable with the following:

    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;
    }
0

The PXE boot is setuped in the BIOS boot order.

The BCD bootloader (for Windows) is launched after the BIOS steps, so it can't have an impact on it.

To boot on PXE, you have to set it prior to the device that hosts the BCD bootloader in the BIOS boot order.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .