web-dev-qa-db-ja.com

Windows 10インストールがBIOSブート設定を変更しないようにするにはどうすればよいですか?

一部のシステムをiPXE経由でPXEbootに設定し、マスターサーバーの状態に応じて、通常どおりに起動するか、wimbootとMDT経由で再イメージ化します。システムは、最初にネットワークから起動するように構成されています。 iPXEとwimbootはどちらもUEFIで実行されています。

Windowsのインストールの最後にBIOSが変更され、新しいWindowsブートマネージャーをプライマリブートデバイスとしてポイントするように変更されています。そのため、BIOSを入力して設定を変更しないと、再度画像化することはできません。

Wimboot/MDTプロセスに複数の再起動が含まれるため、起動順序が変更される理由を理解しています。しかし、私は本当にPXEを最初から最後までブートしておくか、ブート順序を操作して、ネットワークが最初に終了するようにしたいと思っています。 (私のPXEサーバーは、ネットワークブートの機会を利用して、インストールが機能するようにするか、イメージングが不要なときにシステムをそのままにしておきます。)

更新-2つの可能性があります。

  1. Windowsインストーラーが宛先インストールディスクからブートするようにUEFIに指示する方法を理解し、WindowsインストールがPXEブートに戻るように設定されたときに同じことを行います。
  2. Windowsのインストール後にWindowsブートマネージャーとBCDEditを試して、ローカルディスクからのブートより上にPXEブートオプションを配置します(質問は superuser にあります)これは基本的にここと同じ質問です。 t私が本当に欲しいもの(UEFI設定で最初にPXEを使用)でも同じ動作が得られる可能性があります(PXEブートは常にWindowsが起動する前に動作する機会を得ます)。
4
aggieNick02

次のことを学びました。

  1. Linuxでは、これは efibootmgr を介してかなり簡単です。
  2. EasyUEFIを使用すると、自分がやりたいことができるようになります。コマンドラインのサポートにはかなり安価なライセンスが必要です。しかし、特に他のオプションがある場合、私はそのようなニッチなツールに依存して気分が良くありません。
  3. EFIマシンのbcdeditはUEFI設定を変更します 。うまくいくと思います。
  4. EFI仕様 ブート順序はそれほど複雑ではありません。 APIは、実際には、BootOrder(ブートオプションのリストを試行される順序で取得/設定する)およびBoot ####(各ブートオプションに関する情報を取得/設定する)という名前の変数を持つGetVariable/SetVariableです。
  5. WindowsでUEFI APIに対してWindowsアプリを作成する方法がわかりません(誰か?)
  6. WindowsはAPIを提供します 特に、UEFIのGetVariable/SetVariableをラップします。

ブート順序とWindows APIのUEFI仕様を理解したら、コード(C++、64ビット用にビルドされているため、使用しているすべてのもの)はそれほど悪くありませんでした。これは、管理者権限を必要とし、Windowsランタイムを静的にリンクするexeに組み込む必要があり、OSのインストール後、再起動前に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にあるnullで終了するワイド文字列です。

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
aggieNick02

私はこの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
mmseng