Virtual Machines with Windows#
Windows are not provided as a downloadable image, and we must build and provide our own
qcow2
images for running virtual machines with Windows in qemu.
Prerequisites#
On the host machine, install the necessary packages:
sudo apt install \
netcat-openbsd \
qemu-system \
qemu-utils \
libvirt-daemon-system \
libvirt-clients \
virtinst \
bridge-utils \
ovmf \
libguestfs-tools \
dnsmasq-base \
swtpm \
swtpm-tools
And download a Windows 11 Disk Image to the /var/lib/libvirt/images
directory.
On your development machine, install virt-manager and create a connection to the host machine through the interface.
Creating a qcow2
Image#
Install the Virtual Machine#
Create a new virtual machine on the host:
Select the host and click the “New VM” button. On Mac, you might want to resize the popup that appears to properly see the options.
Select “Local install media (ISO image or CDROM)” and click forward.
Click “Browse” and fine the Windows 11 iso, you have downloaded. Select “Windows 11” as operating system and click forward.
Don’t touch the memory and CPU settings, but set the disk image to be of size 64 GiB to ensure the image doesn’t get unnecessarily large.
On the final page, click “Customize configuration before install” and click finish.
For Mac, you want to remove spice-stuff:
Remove “spice”-related hardware: USB drivers and “spice drive”.
Change the display to VNC instead of spice.
Start installation in top left corner.
Go through the Windows installation screen:
Just select English US.
Accept that you want to install and delete everything.
There is no product key.
Choose Windows 11 Pro.
Go next until installation starts and wait for it to be done.
Windows configuration:
Keyboard: USA and US layout, but add your preferred keyboard layout as secondary.
Skip naming device.
Select “Setup for work and school”.
When prompted to login, click “Sign in options” and select “Domain join instead”.
Just select
root
for username, password, answers to all security questions.
Turn off all the experiences.
Wait for download and install (takes a while).
Provisioning of the Virtual Machine#
Look and feel (optional):
Uninstall everything unnecessary (OneDrive, Outlook, Paint, Teams etc.).
I usually also personalise it by changing the layout of the progress bar and start menu.
xNVMe provisioning:
Download windows-2022.ps1 from xNVMe GitHub repository.
Open a Terminal as Administrator.
Run:
powershell.exe -ExecutionPolicy Bypass -File C:\Users\root\Downloads\windows-2022.ps1
This might fail because of
PATH
not being updates, so open a new tab in Terminal and run again.
C:\Users\root\Downloads\windows-2022.ps1
OpenSSH:
Open a Terminal as Administrator and check whether OpenSSH Server is available.
Get-WindowsCapability -Online | Where-Object Name -like 'OpenSSH*'
# Expected output:
Name : OpenSSH.Server~~~~0.0.1.0
State : NotPresent
If the output is empty, you need to install the capability first:
Download OpenSSH-Win64.zip.
Unzip the contents to
C:/Program Files/OpenSSH
.Open a Terminal as Administrator and run the following commands (takes a while).
cd "C:\Program Files\OpenSSH"
powershell.exe -ExecutionPolicy Bypass -File install-sshd.ps1
Add the OpenSSH Server capability and start it as a service.
Add-WindowsCapability -Online -Name OpenSSH.Server
Set-Service -Name sshd -StartupType Automatic
Start-Service sshd
New-NetFirewallRule -Name sshd -DisplayName 'OpenSSH Server (sshd)' `
-Enabled True -Direction Inbound -Protocol TCP -Action Allow -LocalPort 22
Set the default shell to msys2:
New-ItemProperty -Path "HKLM:\SOFTWARE\OpenSSH" -Name DefaultShell `
-Value "C:\tools\msys64\usr\bin\bash.exe" -PropertyType String -Force
Change the SSH config (
C:\ProgramData\ssh\sshd_config
) to with the following keys. You might need to save to documents folder, remove file ending and then replace the original file.
PermitRootLogin yes
PasswordAuthentication yes
Restart the sshd service.
Restart-Service sshd
VirtIO drivers:
Find the latest stable VirtIO driver as a disk image, probably called something like
virtio-win.iso
.Open the downloaded ISO, and run the
virtio-win-guest-tools.exe
installer.Click next through it all to install it.
Enable WMIC:
Open a Terminal as Administrator and run the following command (takes a while).
Add-WindowsCapability -Online -Name WMIC
Check#
Test whether the SSH server has been configured correctly:
Inside the Windows VM, run
ipconfig
to find the IPv4 Address.On the host machine, run
ssh root@<ipv4>
with passwordroot
to see that you can ssh into the machine and it starts a msys2 shell.Close down the VM through the GUI and close virt-manager.
You can also follow the guide below to test that you can start a QEMU guest manually with the qcow2 image.
Upload the qcow image to the Hetzner storage box.
scp /var/lib/libvirt/images/<qcow image> <storage-box url>:~/win11.qcow2
Starting QEMU with qcow2 image#
Assuming you want to run qemu as user ghr
with home directory /home/ghr
. This
might be necessary to setup as root
.
Setup home directory#
Create subdirectories:
mkdir -p /home/ghr/system_imaging
cd /home/ghr/system_imaging
mkdir -p disk fd iso
Get the qcow2 image:
If created on this machine, copy from libvirt directory:
cp /var/lib/libvirt/images/<qcow image> /home/ghr/system_imaging/disk
If not, copy from Hetzner storage box:
scp <storage-box url>:~/win11.qcow2 /home/ghr/system_imaging/disk
Get drivers:
Copy the Windows BIOS drivers to the fd directory
cp /usr/share/OVMF/OVMF_CODE_4M.ms.fd /home/ghr/system_imaging/fd/win11_VARS.fd
Download the VirtIO driver to the iso directory
cd /home/ghr/system_imaging/iso && wget https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/archive-virtio/virtio-win-0.1.271-1/virtio-win.iso
Change ownership:
chown -R ghr:ghr /home/ghr/system_imaging
Check#
Login as user ghr
.
su - ghr
Test with QEMU:
Copy the original image to somewhere safe.
mkdir -p /home/ghr/guests/tmp
rm -rf /home/ghr/guests/tmp/*
cp /var/lib/libvirt/images/<qcow2 image> /home/ghr/guests/tmp/boot.img
Start a swtpm socket
swtpm socket --tpm2 --ctrl type=unixio,path=/home/ghr/guests/tmp/swtpm.sock --tpmstate dir=/home/ghr/guests/tmp --flags not-need-init --daemon
Start the qemu guest:
/opt/qemu/bin/qemu-system-x86_64 \
-cpu host \
-smp 6 \
-m 32G \
-accel kvm \
-pidfile /home/ghr/guests/tmp/guest.pid \
-monitor unix:/home/ghr/guests/tmp/monitor.sock,server,nowait \
-display none \
-serial file:/home/ghr/guests/tmp/serial.output \
-daemonize \
-netdev user,id=n1,ipv6=off,hostfwd=tcp:127.0.0.1:4200-:22 \
-device virtio-net-pci,netdev=n1 \
-drive if=pflash,format=raw,readonly=on,file=/usr/share/OVMF/OVMF_CODE_4M.ms.fd \
-drive if=pflash,format=raw,file=/home/ghr/system_imaging/fd/win11_VARS.fd \
-blockdev driver=qcow2,node-name=boot,file.driver=file,file.filename=/home/ghr/guests/tmp/boot.img \
-device ide-hd,drive=boot \
-drive file=/home/ghr/system_imaging/iso/virtio-win.iso,media=cdrom \
-M q35,kernel_irqchip=split \
-chardev socket,id=chrtpm,path=/home/ghr/guests/tmp/swtpm.sock \
-tpmdev emulator,id=tpm0,chardev=chrtpm \
-device tpm-tis,tpmdev=tpm0 \
-device pcie-root-port,id=rp_nvme,chassis=2,slot=2 \
-device vfio-pci,host=0000:02:00.0,bus=rp_nvme
Wait for it to start, and maybe give it 10 seconds to boot all the way
Then try to login
ssh -p 4200 root@localhost
Hopefully it works!
Remember to close down the qemu guest.
kill -9 $(cat /home/ghr/guests/tmp/guest.pid)