I do most of my work on a MacBook Pro but occasionally I do need a Windows or Linux environment to test platform specific things and so under my desk is a reasonable powerful desktop which dual boots between Windows and Linux. I have it all set up for remote connections and a while ago I figured out a command to tell the Linux bootloader to boot to Windows on the next boot (but only the next boot). So by default it booted to Linux but when I wanted I could switch to Windows without having to connect a monitor to it.
This was annoying though. I’d say my most common need is Windows but I couldn’t figure out how to have that as the default and switch to Linux when needed. Also when installing Windows updates it would frequently reboot, which meant Linux starting and I would have to manually log in to switch it again. Sometimes this would repeat a couple of times. Also there seemed to be some bug in the BIOS where often when switching off from the command line the desktop would just switch back on again a few seconds later. It’s tolerable enough to be useful but annoying enough that I finally sat down and figured out how to improve things.
First off I wanted to be able to turn it on remotely. I’ve started going into a co-working space
once a week and may need access. Wake-on-lan is the obvious answer, and I already have a
Home Assistant instance which you can configure to do that. It
also supports running a command to turn off the device so after some painful shenanigans on the
Windows side I set up shell access to both operating systems from the home assistant server so it
could send the appropriate commands to shut down. On the Linux side this is a simple sudo poweroff
(you have to add the necessary to the sudo config to allow this without a password). On Windows it
is the slightly more complex shutdown /f /t 0 /s
which I stuck in a turnoff.bat
file in the home
directory of the home assistant user.
Hmm but these are two separate commands. I could write a command with the same name for each but it would actually be useful to know which OS was running from Home Assistant. So, I changed things so that each operating system used a different MAC address to connect to the network. This turns out to be quite straightforward. On Linux something like this works:
~$ sudo nmcli connection modify "Wired connection 1" 802-3-ethernet <mac address>
On Windows it is a case of going into the network adapters properties in Device Manager and modifying the “Locally Administered Address”. Ok, now booting with different MAC addresses, different IP addresses and with some tinkering with my DHCP server different hostnames. So now there are three Wake-on-lan entries in Home Assistant, one for the real MAC address to turn it on when it is off and one for each OS to detect which is on and be able to send an off command. Already quite an improvement! Home Assistant lets you control a switch’s visibility on the dashboard based on the status of other devices so I added three switches and made only the one that was appropriate show up.
But the switch UI also supports a long press, so it was pretty easy to then make that tell Linux to reboot to Windows. Nice! But still a little annoying.
Ok the whole thing where it turns itself back on. I’ve long suspected that the BIOS is set to turn
back on after a power outage and maybe that was malfunctioning. I spent some time trying to figure
out whether I could change that setting via a UEFI variable without needing to hook up a monitor to
it. I couldn’t. But in the process of doing that I discovered that I could control the UEFI boot
order. Previously I was changing the Linux bootloader and couldn’t find a way to do that from
Windows (understandably!). But both the Windows bootloader and Linux bootloader appear in UEFI and
it turns out I could change that boot order from both operating systems! For Linux this is
efibootmgr -o 0011,0014,000E,000D
(you’ll have to look up the values with efibootmgr
, hopefully
the IDs don’t change!). Windows was a bit more complex. You can use bcdedit /enum {fwbootmgr}
to
list the UEFI boot entries, but seemed to be no way to guess which GUID corresponded to Linux. In
the end I just figured that since I previously set the order to “Windows then Linux” then Linux
must be the one under {bootmgr}
. So then a script to change the order to boot Linux first looks
like bcdedit /set {fwbootmgr} displayorder {8dddf3a7-9031-11ed-b2a4-806e6f6e6963} {bootmgr}
. It
appears that on startup the BIOS adds all the other devices to the boot order anyway so no need to
worry about those.
And it all worked! I could remotely turn on, turn off, switch OS and the chosen OS was “sticky”! I even took the time to hook up a monitor and disable the setting to restore power after an outage and sure enough it stopped turning itself on.
But wait. Now Wake-on-lan wasn’t working. It definitely was earlier. Does that power on setting somehow mean the network device gets powered down entirely? That seems silly. I was just about to go back to begrudgingly turn on the setting when on a whim I tried sending the Wake-on-lan command to the spoofed MAC addresses I was using. And it turned on. Somehow now I had to send the Wake-on-lan command to the MAC address for whichever operating system was last running. That’s a little surprising but also an easy fix. I just configured the button in Home Assistant to send the Wake-on-lan command to all three MAC addresses.
Took a while, but I managed to solve all the annoyances. Maybe some of this is useful to others, it will certainly be useful to me for the next time I have to set this up after the desktop dies or something!