I have been wanting to create a way to provision a Windows qcow2 file that I could use as an ephemeral Virtual Machine in my KVM-based home lab. I also wanted to be able to customize these images and create new images with as much automation as possible.
- KVM/libvirt installed on your workstation
Binaries:
- mkisofs
- virt-viewer
- qemu-img
If you're using NixOS and already have libvirt set up, you should be able to get everything else needed with the following command:
nix shell nixpkgs#cdrkit nixpkgs#virt-viewer nixpkgs#qemu
You can download the official Windows ISO from the following website:
Download these and places them in this folder named windows.iso
and virtio-win.iso
respectively.
VirtIO stopped supporting Windows 7 at v0.1.173-4, so if you're trying to use Windows 7, make sure to download v0.1.173-4. Unfortunately, you will likely recieve a message stating "VdService" failed to start. This is the Spice Agent service, and the only way I have found to get the install to complete is to disable Spice Agent when installing everything else.
Windows 8+ appear to all be able to use the latest version.
All versions can be found here.
The key to an automated Windows installation lies in the autounattend.xml
file.
You can use the generator at schneegans.de to easily create one of these files.
If you create an iso an mount it in the VM alongside the installation media, then Windows will automatically use it.
This can be done with the following commands:
ls autounattend
autounattend.xml
mkisofs -o autounattend.iso -J -r autounattend
I have included an autounattend.xml
that probably has most of the configurations you'll want.
You can use it as a jumping off point here.
The installation can be kicked off with the following command:
sudo virt-install --name windows \
--vcpus 2 \
--memory 4096 \
--network network=default \
--graphics=vnc \
--console pty,target_type=serial \
--cdrom ./windows.iso \
--disk path=./autounattend.iso,device=cdrom \
--disk path=./virtio-win.iso,device=cdrom \
--disk path=./windows.qcow2,size=128,format=qcow2 \
--os-variant win11
Upon executing that command, virt-viewer should appear and (unfortunately) you will need to press a key to boot the iso. From there, however, you should just be able to walk off for about 30 minutes and come back to a Windows desktop.
At the end of the installation, you can run the following command to kill the Virtual Machine while leaving the qcow2 intact:
sudo virsh destroy windows
sudo virsh undefine --nvram windows
I'm still trying to figure out a better way to do this, but I figured that in the meantime, I would take notes on how to do it manually.
I added instructions above for mounting this cdrom. If you already did that, skip to continue here.
One of the biggest things I want to have working is the QEMU Guest Agent. First, download the Stable virtio-win ISO. Then execute the following commands:
$ sudo virsh domblklist windows
Target Source
-------------------------------------------------------------
sda ./windows.iso
sdb ./autounattend.iso
sdc ./windows.qcow2
$ sudo virsh attach-disk windows ./virtio.iso sdb --type cdrom
The ISOs will be swapped out.
continue here
Open the virtio-win disc and run through virtio-win-guest-tools.exe
and virtio-win-gt-x64.msi
.
For some reason, the installation VM doesn't want to acknowledge that QEMU Guest Agent is working, but if you create a new VM based on this qcow2, it's fine.
I am using this image with Terraform, and IPv6 tends to get assigned before IPv4, which causes issues because I need to wait for IPv4, so I'm disabling it with the following PowerShell command:
iex(iwr "https://raw.githubusercontent.com/bamhm182/WindowsVmCreator/wip-initial/configure.ps1" -UseBasicParsing)
# Install programs with Ninite
# Install Visual Studio Community 2022
# Install System Updates
# Unless I'm mistaken, I actually don't want this because it sets the box up as a new box and I just want something with known credentials that I can boot into immediately.
# C:\Windows\System32\Sysprep\sysprep.exe
# OOBE
# Generalize: No
# Shutdown
At this point, you might notice that the qcow2 says it's 128GB. It isn't taking up all of this space, so we can make that file smaller with the following command:
qemu-img convert -O qcow2 ./windows.qcow2 ./windows.shrunk.qcow2
It takes considerably longer, but you can squeeze a little more space out with -c
:
qemu-img convert -c -O qcow2 ./windows.qcow2 ./windows.shrunk.qcow2
The base install was around 9.9GB
uncompressed, and 5.1GB
compressed.