Как мне остановить установку Windows 10 от изменения настроек загрузки BIOS?

Мы настраиваем некоторые системы на PXEboot через iPXE и, в зависимости от состояния главного сервера, либо загружаемся в обычном режиме, либо обновляем образ через wimboot и MDT. Системы сначала настроены на загрузку по сети. iPXE и ​​wimboot работают под UEFI.

Он отлично работает, за исключением того, что в конце установки Windows BIOS был изменен так, чтобы указывать на новый диспетчер загрузки Windows в качестве основного загрузочного устройства. Таким образом, его невозможно снова отобразить без входа в BIOS и изменения настроек.

Я понимаю, почему порядок загрузки изменяется, поскольку процесс wimboot / MDT включает несколько перезапусков. Но я действительно хотел бы либо оставить PXE в качестве основной загрузки, либо изменить порядок загрузки, чтобы в первую очередь была сеть. (Мой PXE-сервер передаст возможность загрузки по сети, чтобы установка работала, или оставит систему в покое, когда создание образов не требуется.)

Обновление - Я вижу две возможности:

  1. Выясните, как работают окна Программа установки сообщает UEFI о необходимости загрузки с целевого установочного диска и делает то же самое при установке Windows, чтобы вернуться к загрузке PXE.
  2. Поиграйте с диспетчером загрузки Windows и BCDEdit после установки Windows, чтобы разместить параметр загрузки PXE выше загрузка с локального диска (вопрос, найденный на суперпользователе , в основном тот же вопрос, что и здесь. Обсуждаемый конечный результат не совсем то, что я действительно хочу (сначала PXE в настройках UEFI), но может дать такое же поведение (загрузка PXE всегда получает возможность действовать до запуска Windows).
4
задан 20 March 2017 в 12:16
2 ответа

Узнал следующее:

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

Как только я понял спецификацию 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;
    }
3
ответ дан 3 December 2019 в 03:28

Я придумал этот сценарий PowerShell, который мне подходит. Это не идеально, потому что просто «глупо» перемещает первую загрузочную запись, отличную от Windows, наверх. Это работает для моих целей и может быть способ сделать его умнее, которого я просто не нашел.

Это выглядит длинным, но в основном это комментарии и отформатированы для понимания. Его можно переписать в 5 или 6 строк.

https://github.com/mmseng/bcdedit-revert-uefi-gpt-boot-order

# This script looks for the first non-Windows Boot Manager entry in the UEFI/GPT boot order and moves it to the top
# For preventing newly installed Windows from hijacking the top boot order spot on my UEFI/GPT image testing VMs
# by mmseng
# https://github.com/mmseng/bcdedit-revert-uefi-gpt-boot-order

# Notes:
# - There's very little point in using this on regular production machines being deployed. Its main use is for machines being repeatedly imaged, or might be useful for lab machines.
# - AFAICT bcdedit provideds no way to pull the friendly names of the devices in the overall UEFI boot order list. Therefore, this script only moves the first entry it identifies in the list which is NOT "{bootmgr}" (a.k.a. "Windows Boot Manager"). It's up to the user to make sure the boot order will exist in a state where the desired result is achieved.
# - In my case, my test UEFI VMs initially have the boot order of 1) "EFI Network", 2) whatever else. When Windows is installed with GPT partitioning, it changes the boot order to 1) "Windows Boot Manager", 2) "EFI Network", 3) whatever else. In that state, this script can be used to change the boot order to 1) "EFI Network", 2) "Windows Boot Manager", 3) whatever else.
# - This functionality relies on the completely undocumented feature of bcdedit to modify the "{fwbootmgr}" GPT entry, which contains the overall list of UEFI boot devices.
# - AFAICT bcdedit is really only designed to edit Windows' own "{bootmgr}" entry which represents one of the "boot devices" in the overall UEFI list.
# - Here are some sources:
#   - https://www.cnet.com/forums/discussions/bugged-bcdedit-349276/
#   - https://docs.microsoft.com/en-us/windows-hardware/manufacture/desktop/bcd-system-store-settings-for-uefi
#   - https://www.boyans.net/DownloadVisualBCD.html
#   - https://serverfault.com/questions/813695/how-do-i-stop-windows-10-install-from-modifying-bios-boot-settings
#   - https://serverfault.com/questions/714337/changing-uefi-boot-order-from-windows


# Read current boot order
echo "Reading current boot order..."
$bcdOutput = cmd /c bcdedit /enum "{fwbootmgr}"
echo $bcdOutput

# Kill as many of the stupid characters as possible
echo "Removing extraneous characters from boot order output..."
$bcdOutput = $bcdOutput -replace '\s+',''
$bcdOutput = $bcdOutput -replace '`t',''
$bcdOutput = $bcdOutput -replace '`n',''
$bcdOutput = $bcdOutput -replace '`r',''
$bcdOutput = $bcdOutput.trim()
$bcdOutput = $bcdOutput.trimEnd()
$bcdOutput = $bcdOutput.trimStart()
$bcdOutput = $bcdOutput -replace ' ',''
echo $bcdOutput

# Define a reliable regex to capture the UUIDs of non-Windows Boot Manager devices in the boot order list
# This is difficult because apparently Powershell interprets regex is a fairly non-standard way (.NET regex flavor)
# https://docs.microsoft.com/en-us/dotnet/standard/base-types/regular-expressions
# Even then, .NET regex testers I used didn't match the behavior of what I got out of various Powershell commands that accept regex strings
# However this seems to work, even though I can't replicate the results in any regex testers
$regex = [regex]'^{([\-a-z0-9]+)+}'
echo "Defined regex as: $regex"

# Save matches
echo "Save strings matching regex..."
$foundMatches = $bcdOutput -match $regex

# Grab first match
# If Windows Boot Manager (a.k.a. "{bootmgr}" was the first in the list, this should be the second
# Which means it was probably the first before Windows hijacked the first spot
# Which means it was probably my "EFI Network" boot device
$secondBootEntry = $foundMatches[0]
echo "First match: $secondBootEntry"

# Move it to the first spot
echo "Running this command:"
echo "cmd /c bcdedit $bcdParams /set `"{fwbootmgr}`" displayorder $secondBootEntry /addfirst"
cmd /c bcdedit $bcdParams /set "{fwbootmgr}" displayorder $secondBootEntry /addfirst
1
ответ дан 3 December 2019 в 03:28

Теги

Похожие вопросы