Simplify Windows VM installation on KVM/QEMU with virt-install

This post is for you if you:

  • Need to quickly spin up a Windows virtual machine on a Linux server or workstation
  • Want to have performance optimized hardware settings for Windows VM
  • Don't want to click through a graphical interface such as virt-manager or Gnome Boxes every time

Well, I have the solution for you. From time to time I need a Windows VM for various purposes. Manually installing Windows on Linux KVM/QEMU is error-prune and time-consuming. To scratch my own itch, I have found and documented the way to reliably spin up Windows 10 and 11 VMs on any Linux machines.

Prerequisite

You will need to prepare the following things before you can start:

  • Windows 10 or 11 ISO image (nowadays you can download directly from Microsoft)
  • Virtualisation stack (sudo apt install qemu-kvm libvirt-daemon-system or sudo dnf install @virtualization)
  • virt-install command-line utility (provided by package virtinst on Debian/Ubuntu; virt-install on RHEL/Fedora)

virt-install command

The one-liner command for Windows 10 or 11. Adjust anything you see fit.

virt-install \
  --connect qemu:///session \
  --name win11-test \
  --boot uefi \
  --vcpus 4 \
  --cpu qemu64,-vmx \
  --memory 8192 \
  --memballoon driver.iommu=on \
  --osinfo win11 \
  --network bridge=virbr0,model=virtio,driver.iommu=on \
  --graphics spice \
  --noautoconsole \
  --cdrom Win11_22H2_English_x64v2.iso \
  --disk /home/ewon/.local/share/libvirt/images/win11-test.qcow2,size=50,target.bus=scsi,cache=writeback \
  --controller type=scsi,model=virtio-scsi,driver.iommu=on

Explanations:

  • To use KVM/QEMU system session (opposed to user session), specify --connect qemu:///system
  • The --boot uefi may not work reliably on some distros. For example, Fedora 37 (as I tested; Fedora 38 seems to be fine) would default to non-4M version of the OVMF file, resulting in non-working UEFI, hence no Windows 11 support. You may need to manually specify OVMF 4M file path using the following flags instead:
--machine q35 \
--boot loader=/usr/share/edk2/ovmf-4m/OVMF_CODE.fd,loader.readonly=yes,loader.type=pflash,nvram.template=/usr/share/edk2/ovmf-4m/OVMF_VARS.fd,loader_secure=yes \
# For Fedora, the OVMF 4M code is under /usr/share/edk2/ovmf-4m/OVMF_CODE.fd
# For Debian, the OVMF 4M code is under /usr/share/OVMF/OVMF_CODE_4M.fd
  • The --cpu flag for AMD is qemu64,-vmx; qemu64 enables Windows 11 support
  • The --osinfo flag can be either win10 or win11
  • Memory, network and disk controller all support IOMMU driver. Enable them for best performance
  • --graphics spice implies both --video=qxl and --channel=spicevmc; use it for best performance
  • --disk specify the path of qcow2 image file; use scsi (VirtIO) and writeback for best performance
  • --controller this is a rather important flag and often got overlooked. It has to be specified in order for scsi type disk (see --disk) to show up in Windows

Installation process

Follow the steps:

  • Paste the virt-install one-liner and hit enter. Ideally you would get the following output:
[ewon@ThinkPad]$ virt-install \
  --connect qemu:///session \
  --name win11-test \
  --boot uefi \
  --vcpus 4 \
  --cpu qemu64,-vmx \
  --memory 8192 \
  --memballoon driver.iommu=on \
  --osinfo win11 \
  --network bridge=virbr0,model=virtio,driver.iommu=on \
  --graphics spice \
  --noautoconsole \
  --cdrom Win11_22H2_English_x64v2.iso \
  --disk /home/ewon/.local/share/libvirt/images/win11-test.qcow2,size=50,target.bus=scsi,cache=writeback \
  --controller type=scsi,model=virtio-scsi,driver.iommu=on

Starting install...
Allocating 'win11-test.qcow2'                                                                     |    0 B  00:00:00 ...
Creating domain...                                                                               |    0 B  00:00:00

Domain is still running. Installation may be in progress.
You can reconnect to the console to complete the installation process.
  • We also need VirtIO drivers ISO attached to the VM during the installation. Since virt-install does not support loading multiple CDROM, we have to add it by using virt-manager (see below step) or directly editing the XML file (see how-to).
  • Shutdown the VM, edit the VM to include the second CDROM. Don't forget to enable SATA CDROM 1 (Windows ISO) as a boot device.
  • Start the VM and attach to graphical console. Windows Installer should appear.
  • If you manually specified --machine q35 and --boot loader= instead of --boot uefi, press Esc during boot, turn on Secure boot. While you are in UEFI settings, you can also adjust the screen resolution.
  • Follow Windows Installer, load drivers (vioscsi, NetKVM, Balloon) from virtio-win CD Drive and continue the installation process.

Post-installation and bugs

After Windows is installed, you want to install the VirtIO Guest Tools by running "virtio-win-gt-x64.msi" on the CDROM. It enables quality-of-life improvements such as dynamic resolution and two-way clipboard share. After that, you can remove the two CDROMs from the VM instance.

If the screen resolution still looks off, make sure the GPU is detected by Windows: check Windows Updates "receive updates for other Microsoft products..." then install graphics driver.

A downside of enabling UEFI firmware is that internal snapshot is not possible, see StackExchange for workarounds. Personally I don't bother to snapshot Windows VM anyway, since they are ephemeral. If I were to solve it, I would utilize filesystem snapshot (btrfs or ZFS).

I've possibly encountered other bugs/annoyances in the past that I didn't document. Since installing Windows VM is a somewhat "popular" practice for Linux users, I think most problems have been found and fixed, or at least worked around. Fire up your search engine if you couldn't solve something on your own.