2

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

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

3 ответа3

1

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

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

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

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

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

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

0

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

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

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

Всё ещё ищете ответ? Посмотрите другие вопросы с метками .