Local VM creation with KVM
Local VM creation with KVM on Linux
Creating ZFS pool and base directories
This assumes a pair of SSDs are being used for the VMs data volume.
Here we’ll create the initial ZFS pool and dataset:
# List the block devices:
lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
<snip>
nvme4n1 259:5 0 931.5G 0 disk
nvme5n1 259:7 0 931.5G 0 disk
# Create the ZFS pool:
sudo zpool create \
-f \
-o ashift=12 \
-o autotrim=on \
-o compatibility=openzfs-2.1-linux \
-O compression=lz4 \
-O acltype=posixacl \
-O xattr=sa \
-O relatime=off \
-m none \
-O encryption=aes-256-gcm \
-O keylocation=prompt \
-O keyformat=passphrase \
vms_data \
/dev/nvme4n1 /dev/nvme5n1
# Create the mountpoint for the dataset:
sudo zfs create -o mountpoint=/home/patrickmslattery/VMs vms_data/VMs
# Show our volumes and datasets:
zfs list
NAME USED AVAIL REFER MOUNTPOINT
vms_data 996K 899G 192K none
vms_data/VMs 192K 899G 192K /home/patrickmslattery/VMs
zroot 252G 205G 192K none
zroot/ROOT 14.8G 205G 192K none
# Get ownership of the mount:
sudo chown --changes --recursive patrickmslattery: /home/patrickmslattery/VMs
And next we need a few directories created inside that dataset:
mkdir -p ~/VMs/base_images/Linux/{Fedora,Amazon}
mkdir -p ~/VMs/ISOs/Windows/{2012-R2,2016,2019,2022,2025,VirtIO-Win/{2020,latest}}
Obtaining VHD/ISOs/QCOWs
We next need to obtain the VHD/VHDX, ISO or QCOW files we’ll need for each edition of Windows Server/Linux. Note that some Windows Server editions may not have VHD files available online.
Generally we can download all of the Windows Server install files from the Windows Evaluation Center site from Microsoft. You’ll sometimes have to fill out an evaluation form to get access to the download files.
The included license on these Windows installations is valid for 180 days. After that time you’ll need to recreate the VM from scratch. While there are almost certainly ways available to bypass this 180 day license restriction, there is simply no point in doing so for our needs, we WANT to be able to automate the build, and we are not using these VMs for production in any case.
We’ll be storing all of the downloaded Windows Server VHDs and ISOs under the appropriate ~/VMs/VMs/ISOs/Windows/{edition} folders.
Windows Server 2008 R2
Microsoft no longer provide downloads for the X64 version of Windows 2008 R2, archive.org has copies of the TechNet ISOs
Windows_Server_2008_R2_x64.iso
SHA1 hash: a548d6743129f2a02c907d2758773a1f6bb1bcd7
Windows Server 2012 R2
https://www.microsoft.com/en-us/evalcenter/evaluate-windows-server-2012-r2
Windows Server 2016
ISO only
https://www.microsoft.com/en-us/evalcenter/evaluate-windows-server-2016
Windows Server 2019
https://www.microsoft.com/en-us/evalcenter/evaluate-windows-server-2019
Windows Server 2022
https://www.microsoft.com/en-us/evalcenter/evaluate-windows-server-2022
Windows Server 2025
https://www.microsoft.com/en-us/evalcenter/evaluate-windows-server-2025
Windows 11 - 25H2
https://www.microsoft.com/en-us/evalcenter/download-windows-11-enterprise
curl \
--location https://software-static.download.prss.microsoft.com/dbazure/888969d5-f34g-4e03-ac9d-1f9786c66749/26200.6584.250915-1905.25h2_ge_release_svc_refresh_CLIENT_CONSUMER_x64FRE_en-us.iso \
--output ~/VMs/ISOs/Windows/11/26200.6584.250915-1905.25h2_ge_release_svc_refresh_CLIENT_CONSUMER_x64FRE_en-us.iso
echo "D141F6030FED50F75E2B03E1EB2E53646C4B21E5386047CB860AF5223F102A32 26200.6584.250915-1905.25h2_ge_release_svc_refresh_CLIENT_CONSUMER_x64FRE_en-us.iso" >> ~/VMs/ISOs/Windows/11/manifest.sha256
File hashes: https://go.microsoft.com/fwlink/?linkid=2334901
VirtIO drivers
Windows Server builds do not include drivers for KVM so we need to install the necessary drivers to optimize the VMs.
We can download the latest stable drivers ISO from: https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/stable-virtio/virtio-win.iso
Save this ISO in the ~/VMs/ISOs/Windows/VirtIO-Win/latest folder. You should refresh this ISO from time to time, there is not a regular release cadence for this ISO but there is generally at one or more release per quarter.
For older Windows Server editions, such as Windows Server 2012 R2, we need to download an older version of the ISO as the latest version of the ISO has dropped support for Windows Server 2012 R2, this one works well for it though:
Save this ISO in the ~/VMs/ISOs/Windows/VirtIO-Win/2020 folder.
Fedora 44
https://dl.fedoraproject.org/pub/fedora/linux/releases/44/
Workstation
https://dl.fedoraproject.org/pub/fedora/linux/releases/43/Workstation/x86_64/iso/
Server
Qcow2: https://dl.fedoraproject.org/pub/fedora/linux/releases/43/Server/x86_64/images/
ISO: https://dl.fedoraproject.org/pub/fedora/linux/releases/43/Server/x86_64/iso/
Cloud
https://dl.fedoraproject.org/pub/fedora/linux/releases/43/Cloud/x86_64/images/
Amazon Linux 2
The AWS Linux 2 images are regularly updated, the latest images can be found at: https://cdn.amazonlinux.com/os-images/latest/
Pull the latest KVM image and validate the image via the provided SHA256 hash:
# Create directories
mkdir -p ~/VMs/ISOs/Linux/AWS/{2,2023}
# Set an appropriate folder icon:
gio set -t string ~/VMs/ISOs/Linux/AWS metadata::custom-icon file://"${HOME}"/Pictures/icons/folder-aws.svg
# Download
curl --location \
https://cdn.amazonlinux.com/os-images/2.0.20260202.2/kvm/SHA256SUMS \
--output ~/VMs/ISOs/Linux/AWS/2/SHA256SUMS
curl --location \
https://cdn.amazonlinux.com/os-images/2.0.20260202.2/kvm/amzn2-kvm-2.0.20260202.2-x86_64.xfs.gpt.qcow2 \
--output ~/VMs/ISOs/Linux/AWS/2/amzn2-kvm-2.0.20260202.2-x86_64.xfs.gpt.qcow2
# Validate hash
cd ~/VMs/ISOs/Linux/AWS/2
sha256sum -c ~/VMs/ISOs/Linux/AWS/2/SHA256SUMS
Expected result:
amzn2-kvm-2.0.20260202.2-x86_64.xfs.gpt.qcow2: OK
Amazon Linux 2023
Note that the release ID constantly changes, it’s pretty easy to update an older release to a newer release and have it exactly the same config as the new release. https://cdn.amazonlinux.com/al2023/os-images/2023.9.20251208.0/kvm/
Convert VHD/VHDX files to a format (qcow2) that is usable with KVM
KVM can’t use the VHD/VHDX files that Microsoft provides for base OS images, therefore we need to convert these images to a format (qcow2) that KVM understands.
Windows Server 2012 R2
qemu-img convert \
-f vpc \
-O qcow2 \
~/VMs/ISOs/Windows/2012-R2/9600.16415.amd64fre.winblue_refresh.130928-2229_server_serverdatacentereval_en-us.vhd \
~/VMs/base_images/win-serv-2012R2-c-base.qcow2
Windows Server 2016
There is no official VHD available for Windows Server 2016 as of mid 2026. We have to install the OS via the ISO image.
Windows Server 2019
qemu-img convert \
-f vpc \
-O qcow2 \
~/VMs/ISOs/Windows/2019/17763.737.amd64fre.rs5_release_svc_refresh.190906-2324_server_serverdatacentereval_en-us_1.vhd \
~/VMs/base_images/win-serv-2019-c-base.qcow2
Windows Server 2022
qemu-img convert \
-f vpc \
-O qcow2 \
~/ISOs/Windows/2022/20348.169.amd64fre.fe_release_svc_refresh.210806-2348_server_serverdatacentereval_en-us.vhd \
~/VMs/base_images/win-serv-2022-c.qcow2
Windows Server 2025
Note that this is using the newer VHDX format and therefore uses a slightly different command switch.
qemu-img convert \
-f vhdx \
-O qcow2 \
~/ISOs/Windows/2025/26100.1742.amd64fre.ge_release_svc_refresh.240906-0331_server_serverdatacentereval_en-us.vhdx \
~/VMs/base_images/windows-server-2025-base.qcow2
Windows 11
There is no official VHD/VHDX currently available for Windows 11 that I can find. We have to install the OS via the ISO image.
os-variant
To obtain the correct os-variant string for virt-install, run the following command:
osinfo-query --fields=name,short-id,version os
You should get something like the following:
Short ID | Name | Version
----------------------+----------------------------------------------------+---------
win2k12 | Microsoft Windows Server 2012 | 6.3
win2k12r2 | Microsoft Windows Server 2012 R2 | 6.3
win2k16 | Microsoft Windows Server 2016 | 10.0
win2k19 | Microsoft Windows Server 2019 | 10.0
win2k22 | Microsoft Windows Server 2022 | 10.0
win2k25 | Microsoft Windows Server 2025 | 10.0
win2k3 | Microsoft Windows Server 2003 | 5.2
win2k3r2 | Microsoft Windows Server 2003 R2 | 5.2
win2k8 | Microsoft Windows Server 2008 | 6.0
win2k8r2 | Microsoft Windows Server 2008 R2 | 6.1
Use the Short ID like so:
--os-variant "win2k25"
Set background colors for Windows desktops
Clear the default wallpaper and set a consistent background color per OS version
function SetBackgroundColor {
param (
[Parameter(Mandatory=$true)]
[string]$RGBColor
)
Write-Host "Set desktop to: $RGBColor"
Write-Host "You must restart the system to update the desktop"
# Clear the wallpaper:
reg add "HKEY_CURRENT_USER\Control Panel\Desktop" /v Wallpaper /t REG_SZ /d " " /f
reg add "HKEY_USERS\.DEFAULT\Control Panel\Desktop" /v Wallpaper /t REG_SZ /d " " /f
# Set background color:
reg add "HKEY_CURRENT_USER\Control Panel\Colors" /v Background /t REG_SZ /d $RGBColor /f
reg add "HKEY_USERS\.DEFAULT\Control Panel\Colors" /v Background /t REG_SZ /d $RGBColor /f
RUNDLL32.EXE user32.dll,UpdatePerUserSystemParameters
}
# Call the function (will prompt for missing mandatory parameters if not provided)
# Create-NewFile -Path "C:\Temp" -FileName "MyDocument.txt"
$WindowsProductName = (Get-ComputerInfo | Select-Object).WindowsProductName
if ($windowsProductName -like "Windows Server 2025*") {
Write-Host "This is Windows Server 2025."
# Green
SetBackgroundColor -RGBColor "60 220 20"
} elseif ($windowsProductName -like "Windows 10*") {
Write-Host "This is Windows 10 or maybe Windows 11."
# Purple
SetBackgroundColor -RGBColor "107 105 214"
} elseif ($windowsProductName -like "Windows 11*") {
Write-Host "This is Windows 11."
# Purple
SetBackgroundColor -RGBColor "107 105 214"
} elseif ($windowsProductName -like "Windows Server 2022*") {
Write-Host "This is Windows Server 2022."
# Royal Blue
SetBackgroundColor -RGBColor "60 20 220"
} elseif ($windowsProductName -like "Windows Server 2019*") {
Write-Host "This is Windows Server 2019."
# Orange
SetBackgroundColor -RGBColor "255 190 0"
} elseif ($windowsProductName -like "Windows Server 2016*") {
Write-Host "This is Windows Server 2016."
# Pink
SetBackgroundColor -RGBColor "255 180 210"
} elseif ($windowsProductName -like "Windows Server 2012*") {
Write-Host "This is Windows Server 2012 R2."
# Crimson
SetBackgroundColor -RGBColor "220 20 60"
} else {
Write-Host "This is an unknown Windows operating system: $windowsProductName"
# Add commands for other Windows versions here
}
| Background colors by Windows version | |
|---|---|
| Windows 11 | Purple |
| Windows Server 2025 | Green |
| Windows Server 2022 | Blue |
| Windows Server 2019 | Orange |
| Windows Server 2016 | Pink |
| Windows Server 2012 R2 | Red |
Links
(What is KVM?)[https://www.redhat.com/en/topics/virtualization/what-is-KVM] (KVM - ArchWiki)[https://wiki.archlinux.org/title/KVM] (virt-manager - ArchWiki)[https://wiki.archlinux.org/title/Virt-manager] (virt-install - ArchWiki)[https://man.archlinux.org/man/virt-install.1] (Install PowerShell 7 on Red Hat Enterprise Linux)[https://learn.microsoft.com/en-us/powershell/scripting/install/install-rhel?view=powershell-7.5] (All cloud config examples)[https://docs.cloud-init.io/en/latest/reference/examples.html] (cloud-init: What is the execution order of cloud-config directives?)[https://stackoverflow.com/questions/34095839/cloud-init-what-is-the-execution-order-of-cloud-config-directives]