@greg-plamondon Sorry I’ve been rather busy and hadn’t checked the forums in a bit.
Firstly a sidenote on what @Sebastian-Roth posted last. My understanding (from lots of trial and error, testing, and reading of microsoft docs) bootmgfw.efi is the bootmgr at the firmware level for windows. It uses the bcd entries (that you can modify with bcdedit
to then load the specified efi file to boot the OS, which I think is something like bootx64.efi.
So, I have a lot of information in my brain on this so let’s see if I can simplify.
TL;DR
Perhaps the quick answer to your question is
- make a symlink on the fog server to be able to download the ipxe.efi file
ln -s /tftpboot/ipxe.efi /var/www/html/ipxe.efi
- Mount the efi partition to drive letter A in windows
mountvol A: /S
- Download the ipxe file using powershell
Invoke-WebRequest -Uri "http://fogserver/ipxe.efi" -OutFile "A:\EFI\ipxe-latest.efi";
- (untested bit, but would probably work) Set the bcd to use ipxe as a bootmanager
bcdedit /set "{bootmgr}" path "\EFI\ipxe-latest.efi"
- You can revert this back to default with
bcdedit /set "{bootmgr}" path "\efi\Microsoft\Boot\bootmgfw.efi"
- Dismount the efi partition
Mountvol A: /D
Brain dump of detailed info
I used to use rEFInd as my bootloader for my machines, but it has compatibility issues on some hardware so I found a different solution. I now use grub2win. But I still utilize Refind sometimes manually because it has a uefi shell, so when it is compatible it’s useful.
So first let me explain a wee bit on uefi shells from my understanding.
Basically, uefi has a shell, but not all computers have one built in. If you can boot to refind from a usb then you can access the shell.
The syntax for changing directories in the shell is fs#:
where # is the disk/partition number seen by uefi 0-(number of partitions -1).
You can then use ls
and cd
to navigate and you can execute .efi
boot files and load efi drivers i.e. load driverName.efi
.
Playing with the uefi shells is how I figured out that if you pop ipxe.efi on to a usb or somewhere on the local disk, you can run it from the efi shell and it boots to fog (provided your network is already configured for pxe booting to fog). Some Bios/uefi firmwares also have options for ‘booting to file’ or other custom boot options where you can achive the same idea of navigating to an efi file and booting to it.
So here’s a rundown of how I automate that process.
- I use chocolatey package manager and have a package made for installing grub2win
- I have a custom powershell module provisioning system that’s started via firstlogoncommands of my unattend.xml
- Within some of the first steps of provisioning I have it install my custom grub2win package (I have also tried to embed it in the image, but I believe sysprep/windows install changes the boot order on you, so I just make sure it runs, you could also do this with a setupcomplete.cmd system or whatever you use the run scripts after an image is completed)
So here’s what my custom package does, it looks like a lot here but it’s basically just installing a boot manager, copying some files, and editing the bcd.
Install grub2win
You can get a grub2win installer here https://sourceforge.net/projects/grub2win/
You download and extract the zip file. The setup.exe from the zip will then download the latest install files to %LocalAppData%\temp\grub2win.setup.exe.{buildNumber}\install
It will auto start a gui setup, but I leave that be and go copy the install folder from the temp folder to be a part of my package. Once I have the install folder out of the local appdata temp directory, I close the install wizard that popped up.
Configure grub
In that install folder, you’ll need to edit the file at winsource\basic.cfg
You can also use the built in gui tools to edit this after installing, but I find it easier to just make the config changes beforehand.
Here’s what my custom menu entries look like, you’ll at least need the windows and fog menu entries.
#
# Menu Entry 0 Windows 10
#
# ** Grub will boot this entry by default **
#
menuentry 'Windows 10 Hotkey=w' --hotkey=w --class windows --class icon-arrowwin {
set gfxpayload=auto
set efibootmgr=/efi/Microsoft/Boot/bootmgfw.efi
getpartition file $efibootmgr root
if [ ! -z $reviewpause ] ; then
echo GNU Grub will load the Windows EFI Boot Manager at disk address $root
g2wsleep
echo
fi
chainloader $efibootmgr
savelast 0 'Windows 10'
echo GNU Grub is now loading Windows 10
}
#
# Menu Entry 1 FOG
#
menuentry 'FOG Hotkey=f' --hotkey=f --class chainfile --class icon-fog {
set gfxpayload=auto
set chainbootmgr=/efi/ipxe-latest.efi
getpartition file $chainbootmgr root
chainloader $chainbootmgr
savelast 1 'FOG'
echo GNU Grub is now loading FOG via ipxe
}
#
# Menu Entry 2 Boot to your EFI firmware setup
#
menuentry 'Boot to your EFI firmware setup Hotkey=b' --hotkey=b --class bootfirmware --class icon-bootfirmware {
g2wutil fwsetup
}
#
# Menu Entry 3 Boot Information and Utilities
#
menuentry 'Boot Information and Utilities Hotkey=i' --hotkey=i --class bootinfo --class icon-bootinfo {
clear
set pager=0
set grub2win_chosen='0 - Windows Boot Manager'
set grub2win_lastbooted=no
export gfxmode
export grub2part
export grub2win_chosen
export grub2win_lastbooted
export grub2win_version
export grub2win_procbits
export grub2win_bootmode
export grub2win_efiboot
export grub2win_efilevel
configfile $prefix/g2bootmgr/gnugrub.bootinfo.cfg
}
#
# Menu Entry 65 Shutdown the system
#
menuentry 'Shutdown the system Hotkey=s' --hotkey=s --class shutdown --class icon-shutdown {
clear
set pager=1
echo
echo Grub is shutting down your machine
echo
sleep -i -v 2
halt
# If halt fails, display an error message
echo Grub Shutdown is not supported by your firmware
}
I put all that at the bottom, that’s my entire menuentries section.
Then at the top of the basic.cfg file (as in the very first uncommented line) I have Set default=0
to default to the windows bootloader.
Packaging and silent install
Then I make a chocolatey package (if you don’t have chocolatey you could probably achieve the same idea with a fog snapin pack). Basically you need all the files from the install folder, for a quick example we’ll pretend I copied the install files to C:\install
To install grub2win silently I would run
C:\install\winsource\grub2win.exe Autoinstall Quiet EFIAccess Shortcut=no Drive=C: RunFromEFI=yes SetAsDefault
This will extract some files to C:\grub2win
and copy neccesarry files to the EFI partition.
I also do some theme customization and add the refind efi shell tools, but I’m not going to get into that here right now.
Mount The EFI drive and copy down the ipxe.efi file
I have a powershell function called Mount-Efi that does this with some other functions that help it too with some error checking and making sure it’s not already mounted. But the most important bit is this bit
$mountLtr='A:'
$mountVol = "C:\Windows\System32\mountvol.exe";
Start-Process -FilePath $mountVol -Wait -NoNewWindow -ArgumentList @($mountLtr, '/S');
That’s all to make it pretty in powershell, you can also just run the command natively mountvol A: /S
where the /S
is telling it to mount your efi partion, and the A:
is saying to use A: as your path. This isn’t exposed to the gui file explorer, but you can then cd A:\
and you’re in the efi partion in powershell/cmd and can do whatever you want.
Getting ipxe.efi
To simplify this process and always have the latest version. I have a symlink on my fogserver to be able to grab the ipxe.efi file with a quick download.
On the fogserver I create this with ln -s /tftpboot/ipxe.efi /var/www/html/ipxe.efi
I can then download from http://fogserver/ipxe.efi. So after I’ve installed grub2win and then mounted the efi partition. I run this powershell command to download the ipxe file to efi and have it be named ipxe-latest.efi
Invoke-WebRequest -Uri "http://fogserver/ipxe.efi" -OutFile "A:\EFI\ipxe-latest.efi";
Now it is where my grub config will look for it if I choose to boot to fog.
Test that grub2win installed correct
I do a check in the efi partition that the kernel and binary files for grub2win are in the right place.
You should ssee all the files that were in the folder g2bootmgr
from the grub2win install folder at (assuming you mounted efi to A) A:\EFI\Grub2Win\g2bootmgr
particularly you want to see the gnugrub.kernel*.efi
boot file.
Edit the windows bcd
This is the series of steps I run through to edit the bcd. (I have powershell functions for each to keep it simple to run internally, but I’ll just share the basic commands to make it less confusing to read through). Essentially I have an end result of pointing the default windows bootmgr bcd entry to the grub efi boot file.
Remove boot entry added by grub2win
Grub2win adds its own entry to bcd and it does often work. But I have a handful of devices that it doesn’t work as expected on, so I remove it and edit the default windows one, which helps with devices that don’t let you change the boot order within windows.
First you need to get the bcd entry for grub2win
$searchString = "Grub2Win EFI - 64 Bit";
$grubEntry = ($bcdEntry.ToString().split([Environment]::NewLine) | Where-Object { $_ -match 'id' }).Replace("identifier","").trim();
Then you need the guid of that entry
$guid = ($grubEntry.ToString().split([Environment]::NewLine) | Where-Object { $_ -match 'id' }).Replace("identifier","").trim();
Now you can remove the entry
bcdedit /set "{fwbootmgr}" displayorder $guid /remove
# you can also view/confirm its removal before and after with
bcdedit /enum "{fwbootmgr}"
Edit where the bootmgr points
This is where we change the default bcd entry to use grub instead of the windows bootloader. Theoretically you could also use this to always boot to pxe, but I wouldn’t recommend that.
$path = "\EFI\Grub2Win\g2bootmgr\gnugrub.kernel64.efi"
bcdedit /set "{bootmgr}" path $path
The code above is just using string manipulation to get the guid out of the output of the above enum command. If doing it manually you could highlight, copy, paste.
Make sure the altered boot manager is the first boot option
I’m pretty sure this is the command windows uses during sysprep to reset the boot order to boot to windows first instead of whatever you had set in the bios for install (i.e. pxeboot)
bcdedit /set "{fwbootmgr}" displayorder "{bootmgr}" /addfirst
Remove runOnce boot options
Sometimes grub2win or windows bcd or something else during this process adds a runOnce option. Theoretically you could use the runOnce option to force the computer to boot to the network on next reboot but it’ll go back to normal after that.
However, since I’m creating a default here and want to see it work as it always will, I remove any entries if the exist.
bcdedit /deletevalue "{fwbootmgr}" bootsequence
Dismount efi partition and you’re done
Lastly you can dismount the efi partition with this command. Next time you restart you’ll have grub as a bootmgr with options for booting to fog or windows.
mountvol A: /D
Putting it all together.
Here’s what my chocolatey package looks like
- parent folder named custom-grub
<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2015/06/nuspec.xsd">
<metadata>
<id>custom-grub</id>
<version>2004.2.207.5</version>
<title>custom-grub</title>
<authors>jfullmer</authors>
<copyright>2019</copyright>
<tags>custom-grub admin</tags>
<description>
Installs the customized arrowhead grub bootloader for easy booting to fog bios setup or a uefi shell when needed
To update the config that gets installed, edit the sources\winsource\basic.cfg file
The basic theme is overridden with the customized theme complete with company logo background
the boot options are to boot to windows 10, fog via ipxe, firmware/bios setup, or the refind boot manager for when a uefi shell is needed.
In the future options for windows 10 safe mode and other boot modes should be added as a submenu.
</description>
</metadata>
<files>
<file src="tools\**" target="tools" />
</files>
</package>
$packageName = 'custom-grub'
$toolsDir = "$(Split-Path -parent $MyInvocation.MyCommand.Definition)"
$sources = "$toolsDir\sources";
$fileLocation = "$sources\winsource\grub2win.exe"
$packageArgs = @{
packageName = $packageName
softwareName = 'custom-grub*'
file = $fileLocation
fileType = 'exe'
silentArgs = "Autoinstall Quiet EFIAccess Shortcut=no Drive=C: RunFromEFI=yes SetAsDefault "
validExitCodes= @(0)
url = ""
destination = $toolsDir
}
Import-Module "$toolsDir\Functions.psm1" -force;
Set-Location "C:\"
Mount-EFI;
$EfiMnt = Get-EfiMountLetter;
Remove-OldGrubFiles -EfiMnt $EfiMnt;
Add-EfiTools -sources $sources -EfiMnt $EfiMnt
Dismount-EFI;
Install-ChocolateyInstallPackage @packageArgs
Mount-EFI;
$EfiMnt = Get-EfiMountLetter;
Test-BootMgrBinary -sources $sources -EfiMnt $EfiMnt
Add-AdlGrubTheme -sources $sources -EfiMnt $EfiMnt;
Write-Verbose "adding grub boot manager to bios boot order and fixing built-in bootmgr to use grub boot manager"
Remove-FwBootEntry;
Set-BcdBootMgrPath;
Set-FirstFwBootOption;
Remove-RunOnceBootOptions;
Dismount-EFI;
Remove-Item -Force -Recurse $sources;
return;
function Remove-OldGrubFiles {
[CmdletBinding()]
param (
$EfiMnt
)
process {
Write-Verbose "removing any current grub files for fresh install"
if ((Test-Path "C:\grub2")) {
#grub2 path already exists remove it
"Removing C:\grub2" | OUt-HOst;
Remove-Item -Force -Recurse C:\grub2;
}
if ((Test-Path "$EfiMnt\EFI\grub2win")) {
"Removing old grub2 EFI Files" | Out-host;
Remove-Item -Force -Recurse "$EfiMnt\EFI\grub2win";
}
}
}
function Add-AdlGrubTheme {
[CmdletBinding()]
param (
$sources,
$EfiMnt
)
process {
Copy-Item "$sources\themes\*" "C:\grub2\themes\" -force -recurse;
Copy-Item "$sources\themes\*" "$EfiMnt\EFI\Grub2win\themes\" -force -recurse;
}
}
function Add-EFITools {
[CmdletBinding()]
param (
$Sources,
$EfiMnt
)
process {
Write-Verbose "Adding efi binary tools and drivers"
New-Dir "$EfiMnt\EFI\Tools\";
New-Dir "$EfiMnt\EFI\boot\";
Copy-Item "$sources\refind\tools\*" "$EfiMnt\EFI\Tools\" -Force;
Copy-Item "$sources\refind\boot\*" "$EfiMnt\EFI\boot\" -Force -Recurse;
Write-Verbose "downloading latest fog ipxe file"
Invoke-WebRequest -Uri "http://arrowfog/ipxe.efi" -OutFile "$EfiMnt\EFI\ipxe-latest.efi";
}
}
function Test-BootMgrBinary {
[CmdletBinding()]
param (
$Sources,
$EfiMnt
)
process {
if (!(Test-Path "$EfiMnt\EFI\Grub2Win\g2bootmgr\gnugrub.kernel*.efi")) {
Write-Verbose "boot manager binary is missing, re-copying the grub boot binary files";
Copy-Item "$sources\g2bootmgr\*" "$EfiMnt\EFI\Grub2Win\g2bootmgr\" -force;
}
}
}
function Get-BCDEntry {
[CmdletBinding()]
param (
$searchString = "Grub2Win EFI - 64 Bit"
)
process {
return (bcdedit /enum all | select-string $searchString -Context 5,1); #get the grub bootmgr entry from all bcd options
}
}
function Get-BcdGUID {
[CmdletBinding()]
param (
$bcdEntry = (Get-BCDEntry)
)
process {
return ($bcdEntry.ToString().split([Environment]::NewLine) | ? { $_ -match 'id' }).Replace("identifier","").trim(); #get the guid of the grub bootmgr entry
}
}
function Remove-FwBootEntry {
[CmdletBinding()]
param (
$bcdEntry = (Get-BCDEntry)
)
process {
$guid = Get-BcdGUID -bcdEntry $bcdEntry;
$fwboot = bcdedit /enum "{fwbootmgr}"
if ($null -ne $guid) {
if ($fwboot -match $guid) {
return (bcdedit /set "{fwbootmgr}" displayorder $grubID /remove); #remove the grub boot manager created by the installer from boot order as it isn't universally compatible
} else {
"guid was not in {fwbootmgr}" | OUt-Host;
}
} else {
"guid not found" | OUt-Host;
return $null;
}
}
}
function Set-BcdBootMgrPath {
[CmdletBinding()]
param (
$path = "\EFI\Grub2Win\g2bootmgr\gnugrub.kernel64.efi"
)
process {
return (bcdedit /set "{bootmgr}" path $path) #set the default bootmanager to the grub efi file path
}
}
function Set-FirstFwBootOption {
[CmdletBinding()]
param (
$bootOptionName = "{bootmgr}"
)
process {
return (bcdedit /set "{fwbootmgr}" displayorder $bootOptionName /addfirst); #set the edited bootmanager to the first boot option
}
}
function Remove-RunOnceBootOptions {
[CmdletBinding()]
param (
)
process {
$fwboot = bcdedit /enum "{fwbootmgr}"
if ($fwboot -match "bootsequence") {
$result = (bcdedit /deletevalue "{fwbootmgr}" bootsequence); #remove any run once boot options
} else {
$result = "bootsequence value not present";
}
return $result;
}
}
function Dismount-Efi {
<#
.SYNOPSIS
Dismounts the EFI system partition if it is currently mounted
.DESCRIPTION
Gets the efi partition mount letter and dismounts it with the mountvol tool
#>
[CmdletBinding()]
param ()
process {
$mountLtr=(Get-EfiMountLetter)
if ($null -eq $mountLtr) {
Write-Debug "EFI Partition is not mounted";
return $null
} else {
$mountVol = "C:\Windows\System32\mountvol.exe";
return Start-Process -FilePath $mountVol -Wait -NoNewWindow -ArgumentList @($mountLtr, '/D');
}
}
}
function Get-EfiMountLetter {
<#
.SYNOPSIS
If the EFI partition is mounted this returns the current drive letter
.DESCRIPTION
Runs the mountvol.exe tool and parses out the string at the end of the output
that states if and where the EFI system partition is mounted
#>
[CmdletBinding()]
[Alias('Get-EfiMountLtr','Get-EfiMountPath')]
param (
)
process {
$mountVol = "C:\Windows\System32\mountvol.exe";
#test if mountvol already has mounted a EFI partition somewhere and dismount it
$mountVolStr = (& $mountVol)
$currentEfiMountStr = ($mountVolStr | Select-String "EFI System Partition is mounted at");
if ($null -ne $currentEfiMountStr) {
#get the substring starting at the index of the character before ':' in the drive name
$currentEfiMountLtr = $currentEfiMountStr.ToString().Substring(($currentEfiMountStr.ToString().indexOf(':'))-1,2);
if ($null -ne $currentEfiMountLtr) {
return $currentEfiMountLtr;
}
}
Write-Debug "EFI partition is not currently mounted";
return $null
}
}
-
-
- sources folder
- Contents of install folder with altered basic.cfg file from the install grub2win step. I also have customizations to the theme (like a fog icon) and have some other customizations, but the ones I described here are all you need to make it function.
Hopefully that is helpful information. Probably more information than you ever wanted.