Используйте BCDEDIT для настройки загрузки PXE в качестве опции загрузки по умолчанию

У меня есть лаборатория, полная компьютеров, использующих UEFI, и я хочу всегда пытаться выполнить PXE-загрузку перед всеми остальными вариантами загрузки. Однако после автоматического создания образа ПК с Windows 8.1/Windows 10 порядок загрузки UEFI (неудивительно) изменяется Windows на диспетчер загрузки Windows.

Как программно изменить порядок загрузки, чтобы загрузка PXE (IPv4) всегда возвращалась к исходному состоянию с помощью BCDEDIT (или другого инструмента на базе Windows)? Имеет ли BCDEDIT хорошо известный GUID или аналогичный для загрузки PXE?

3 ответа

Хотя комментарии @nex84 о том, что BCD находится на более высоком уровне, чем загрузочное меню BIOS, верны, это не совсем так. На машинах с UEFI записи BCD фактически объединяют как собственный "менеджер загрузки" встроенного ПО, так и менеджер загрузки Windows.

Вы можете перечислить все записи, используя bcdedit /enum all и это будет включать в себя опцию загрузки PXE - при условии, конечно, что она уже существует в вашем "BIOS". Затем вы можете управлять порядком загрузки с обычным bcdedit /displayorder команды.

Вы также можете использовать EasyBCD для бесплатного графического интерфейса пользователя. По умолчанию последняя версия EasyBCD скрывает записи уровня UEFI от дисплея, но если вы включите "Экспертный режим" в опциях, они станут доступны. (Раскрытие: я с NeoSmart Technologies, авторы EasyBCD)

Пожалуйста, будьте очень, очень осторожны с bcdedit при игре с переменными загрузки UEFI. Я лично экспериментировал с устройствами, которые постоянно удалялись из-за того, что они представляют свое приложение для конфигурирования прошивки (так называемая настройка BIOS) только в качестве функции этого загрузочного меню, неправильная настройка его может быть постоянной (если у вас нет под рукой программиста EEPROM для перепрошивки прошивки). и вам очень пригодится пайка на поверхности).

Я обнаружил, что столкнулся с той же проблемой и спросил об этом при сбое сервера. Прошел хороший день гугл-спотыкания, прежде чем я собрал достаточно частей, чтобы решить эту проблему. Здесь это идет:

  1. В Linux это было бы довольно просто, через efibootmgr
  2. EasyUEFI позволил бы мне делать то, что я тоже хочу - поддержка командной строки требует довольно дешевой лицензии; но я не чувствую себя прекрасно в зависимости от нишевого инструмента, как это, особенно если есть другие варианты.
  3. bcdedit на машине UEFI изменяет настройки UEFI. Я думаю, что это будет работать.
  4. Спецификация UEFI для порядка загрузки не слишком сложна. API - это просто GetVariable/SetVariable с переменными с именем BootOrder (для получения / установки списка параметров загрузки в порядке, в котором они будут опробованы) и Boot#### (для получения / установки информации о каждом параметре загрузки).
  5. Я понятия не имею, как я написал бы приложение Windows против API UEFI на окнах (любой?)
  6. Windows предоставляет API, который, помимо прочего, оборачивает UEFI GetVariable/SetVariable.

Как только я понял спецификацию UEFI для порядка загрузки и Windows API, код (C++, построенный для 64-битной системы, поскольку это все, что мы используем) был не так уж плох. Это должно быть встроено в исполняемый файл, который требует административных привилегий и статически связывает среду выполнения Windows, а затем я запускаю его в 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

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

Загрузка PXE настраивается в порядке загрузки BIOS.

Загрузчик BCD (для Windows) запускается после шагов BIOS, поэтому он не может повлиять на него.

Для загрузки на PXE его необходимо установить до устройства, на котором размещен загрузчик BCD, в порядке загрузки BIOS.

Другие вопросы по тегам