It seems like every time I need to work with UEFI, it turns into a multi-day affair involving reading spec documents and source code examples. And this time is no exception. The task sounded simple enough: If you’ve got a UEFI-based machine that is currently running Windows, how can you force it to reboot into a Linux environment? But of course Windows doesn’t really want to make this easy, so it’s more work than it sounds.
It’s useful to start with some background reading, for those that haven’t read my previous UEFI articles, since this builds on top of those:
- Geeking out with UEFI. This is where it all started. The important piece to understand here: UEFI variables.
- Working with UEFI variables in PowerShell. This mentioned an “extra credit” activity, using the Boot* UEFI variables to build a boot entry editor in PowerShell. No time like the present…
- UEFI Secure Boot: Who controls what can run? This introduces the UEFIv2 PowerShell module.
- UEFI Secure Boot: Yes, again. This was the last time I updated the UEFIv2 PowerShell module.
So what’s the UEFI boot manager? Here’s how the UEFI 2.9 spec describes it:
“The boot manager is a component in firmware conforming to this specification that determines which
drivers and applications should be explicitly loaded and when. Once compliant firmware is initialized, it
passes control to the boot manager. The boot manager is then responsible for determining what to load
and any interactions with the user that may be required to make such a decision.”
Sigh. Perfectly clear, right? Basically, it’s the thing that loads Windows or Linux, and also the thing that can present a boot menu (usually when you press a certain key combination when the device is powering up) for you to choose from available options, including OSes, PXE, HTTP boot, CD/media boot, etc. It may look something like this:
Behind the scenes, this is just a representation of a bunch of UEFI variables. Variables with names that start with Boot and then a four hex digit number (e.g. Boot0000, Boot0001, etc.) define each of the boot entries, and then another variable named BootOrder defines the order that those entries are normally considered (where the first one to successfully boot something wins). Here’s what those variables look like on my Lenovo workstation:
First, we can see the Boot* variables of interest, including six different boot entries. We can also see the BootOrder content, which indicates that the Boot* variables should be tried in this order:
And as you might surmise from the hex dump of the Boot0005 entry, there’s a bunch of stuff in that entry, some easily readable and some not. So what’s the format of those entries? Well, there’s a good chuck of the UEFI spec that explains that, 70 pages to be exact, with data structures, record types, samples, descriptions, etc. Suffice to say, it’s exceedingly complicated because there are so many supported options. Want to boot from a PC Card? Fibre Channel? FireWire? USB? iSCSI? Infiniband? Hard drive? URL? All of those are represented differently. So if you wanted to support all of those, you would need a lot of code. There is a fair amount of code in UEFI itself that you could use in a custom C/C++ program (e.g. like the “efibootmgr” utility that you can find on Linux systems) to decipher all of this, but since I wanted to use PowerShell, I had to do a little more decoding work. And there are lots of things that I don’t care about, so I didn’t try to decode everything, just those entries that I could actually see on the PCs that I have handy.
- Get-UEFIBootEntry, to list one or all of the boot entries currently present, in the order that the firmware will try to boot them.
- Add-UEFIBootEntry, to add a hard drive entry to boot a specific file (e.g. shimx64.efi for Linux).
- Remove-UEFIBootEntry, to remove an existing boot entry.
Let’s start with looking at the current boot entries from my Lenovo workstation:
Great, three entries, with the first two being PXE options and the third being the standard Windows Boot Manager. But I noticed when looking at the BootOrder that there were more than three entries. The “Hidden” column provides some clues: There are some hidden entries in the list that the firmware will try to boot, but that it won’t show to the end user in the boot menu. So let’s display those too:
I think the last two are hidden because there are no USB or CD devices ready to boot, but the “Lenovo Cloud” item is a little more interesting — but to see what any of these items are all about, we need more detail. And that means deciphering the Boot* entries much more completely. To see that detail, you can add the “-FilePaths” switch to see what the cmdlet has decoded:
Let’s dig into one of those in more detail, just to see what’s going on:
So this “Windows Boot Manager” entry specifies to boot from a hard drive with a particular hard drive signature (the GUID starting with “36adb17e”) by loading the file at path \EFI\MICROSOFT\BOOT\BOOTMGFW.EFI. That makes sense.
While we’re poking around, let’s look at that “Lenovo Cloud” entry too:
That’s a little more involved, specifying to boot from a particular PCI-based PnP device (the Ethernet adapter) with MAC address E04F43E60F48, using IPv4 (but not with a preconfigured IP address), loading the boot file from the Lenovo web site.
OK, so back to why I started on this little crusade: I want to stage the files need to boot Linux on this machine (using a RAMdisk, similar in concept to Windows PE) and add a boot entry to make that the first OS to try to boot. Switching over to a VM, I can add a new boot entry using a simple command:
Add-UEFIBootEntry -Name "Linux" -FilePath "\EFI\BOOT\BOOTX64.EFI"
(Of course prior to doing this I replaced that BOOTX64.EFI file with the Linux shimx64.efi file, and placed the other Linux boot files where they needed to be on the same FAT32 volume that the Windows boot files are on.). Once that new boot entry has been added, the computer can be rebooted into the new OS. Here’s what that whole process looks like (typos and all):
There are lots of boot entry types that the UEFIv2 module can’t handle, but it does at least take care of the most useful. Feel free to try it out (use a non-production system first, just in case you break it — I did that a number of times while testing, but I used a VM so I could just revert back to a checkpoint), and let me know if you run into any issues or “opportunities for improvement.”
Categories: Geeking Out