web-dev-qa-db-ja.com

BCDEDITを使用して、PXEブートをデフォルトのブートオプションとして構成します

UEFIを使用するコンピューターでいっぱいのラボがあり、他のすべてのブートオプションの前に常にPXEブートを試みたいと思っています。ただし、Windows 8.1/Windows 10でPCを自動的にイメージングした後、UEFIの起動順序は(当然のことながら)WindowsによってWindows BootManagerに変更されます。

BCDEDIT(または他のWindowsベースのツール)を使用してPXEブート(IPv4)が常にデフォルトにリセットされるように、プログラムでブート順序を変更するにはどうすればよいですか? BCDEDITには、PXEブート用のよく知られたGUIDまたは同様のものがありますか?

2
jessicah

私は同じ問題を抱えていることに気づき、 serverfault で質問しました。私がそれを解決するのに十分につなぎ合わせる前に、グーグルつまずきの良い日を取りました。ここに行きます:

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

ブート順序とWindowsAPIの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 ####変数名を作成し、それを使用してオプションに関する情報を含む構造体を取得できます。最初のアクティブなオプションの「説明」が「WindowsBootManager」と等しいかどうかを確認する必要があります。説明は、構造体のオフセット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
aggieNick02

BCDがBIOSブートメニューよりも高いレベルにあるという@ nex84のコメントは正しいですが、厳密にはそうではありません。 UEFIマシンでは、BCDエントリは実際にはファームウェアのネイティブの「ブートマネージャー」とWindowsブートマネージャーの両方を合体させます。

bcdedit /enum allを使用してすべてのエントリを列挙できます。これには、PXEブートオプションが含まれます。もちろん、「BIOS」にすでに存在していることを前提としています。その後、通常のbcdedit /displayorderコマンドを使用して起動順序を操作できます。

フリーウェアのGUIオプションには EasyBCD を使用することもできます。デフォルトでは、EasyBCDの最新バージョンはUEFIレベルのエントリを表示から非表示にしますが、オプションで「エキスパートモード」を有効にすると、それらが使用可能になります。 (開示:EasyBCDの作者であるNeoSmart Technologiesと一緒です)

UEFIブート変数で遊ぶときは、bcdeditに非常に注意してください。ファームウェア構成アプリ(別名BIOSセットアップ)がこのブートメニューの機能としてのみ表示されるために永続的にゴミ箱に捨てられたデバイスを個人的に実験しましたが、誤って構成すると永続的になる可能性があります(ファームウェアを再フラッシュするEEPROMプログラマーが手元にない場合) 、そしてあなたはたまたま表面実装はんだ付けで非常に便利です)。

1

PXEブートは、BIOSブート順序でセットアップされます。

BCDブートローダー(Windows用)はBIOSの手順の後に起動されるため、影響を与えることはありません。

PXEで起動するには、BIOSの起動順序でBCDブートローダーをホストするデバイスの前にPXEを設定する必要があります。

0
nex84