Skip to main content

Windows Configuration

Gallium supports running Windows virtual machines with optional fully automated installation. You provide a Windows ISO through a template and, at VM creation time, can supply an Autounattend answer file and a first-run PowerShell script to automate the entire setup process. For the creation wizard field reference, see Creating a Virtual Machine.

How It Works

  1. A Windows ISO is uploaded into a template.
  2. When creating a VM from the template, the creation wizard presents two optional fields: Autounattend XML and First Run PowerShell.
  3. Both files are encrypted in your browser before being submitted. The encrypted payload is sent to the target Hypervisor, where it is decrypted only when the configuration drive is built. The files are not stored in the Gallium Console.
  4. A virtual configuration drive (USB) is assembled on the Hypervisor containing your files, VirtIO drivers, the QEMU guest agent installer, and a SetupComplete.cmd bootstrap script.
  5. A Bootstrap ISO is generated for your installation media to ensure the installation begins without intervention.
  6. The configuration drive is attached to the VM and Windows reads the Autounattend file from it during setup.
  7. After OOBE finishes, SetupComplete.cmd installs the guest agent, schedules firstrun.ps1 for the next boot, and shuts down the VM.
  8. The hypervisor detects the shutdown, detaches the configuration drive, and starts the VM. On the next boot, the scheduled task runs firstrun.ps1 and then deletes itself.

If you do not provide an Autounattend file, the VM boots into the standard Windows installer. See Manual Installation for that workflow.

Configuration Drive

Structure

The configuration drive is a virtual USB device with the volume label CONFIGDRIVE. Its contents are:

CONFIGDRIVE/
├── autounattend.xml # Your answer file (if provided)
├── firstrun.ps1 # Your first-run script (if provided)
├── SetupComplete.cmd # Post-install bootstrap script
├── drivers/
│ ├── Balloon/
│ ├── NetKVM/
│ ├── pvpanic/
│ ├── qemupciserial/
│ ├── vioinput/
│ ├── viorng/
│ ├── vioscsi/
│ ├── vioserial/
│ └── viostor/
└── guest-agent/
└── qemu-ga-x86_64.msi

Each driver folder contains subfolders for each supported Windows version and architecture — for example, drivers/viostor/2k25/amd64.

Contents

  • autounattend.xml — the Windows answer file placed at the root of the drive. Windows automatically reads this file during setup.
  • firstrun.ps1 — an optional PowerShell script placed at the root. If provided, it is automatically run on the first boot after provisioning completes (after the configuration drive has been detached).
  • SetupComplete.cmd — a bootstrap script provided by Gallium. It is staged to C:\Windows\Setup\Scripts\ by the answer file during the specialize pass. Windows runs it automatically after OOBE finishes. It installs the guest agent, schedules firstrun.ps1 for the next boot, and triggers a clean shutdown to complete provisioning.
  • drivers/ — VirtIO drivers organized by device type. The most important are viostor (storage controller, required for Windows to see the OS disk) and NetKVM (network adapter).
  • guest-agent/ — the QEMU guest agent MSI installer. The guest agent reports operating system information and IP addresses back to the Console and is required for the provisioning process to complete.
info

The configuration drive is presented as a USB-removable device, so its drive letter is not deterministic. Scripts in your answer file should locate the drive by its volume label CONFIGDRIVE rather than a hardcoded drive letter. The example answer file below demonstrates this approach.

Lifecycle

The configuration drive is automatically removed once both of the following conditions are met:

  1. The QEMU guest agent has connected to the Hypervisor.
  2. The VM shuts down.

The Gallium hypervisor holds the VM in a provisioning state until this sequence occurs. In an automated installation, SetupComplete.cmd handles both steps — it installs the guest agent and triggers a clean shutdown after OOBE finishes. The hypervisor then detaches the configuration drive and starts the VM.

If you provided a firstrun.ps1 script, it runs on this next boot — after the configuration drive has already been removed. This means your first-run script cannot depend on files from the configuration drive.

warning

Do not force-stop the VM during installation. The hypervisor uses the guest agent connection combined with the first shutdown as the signal that provisioning is complete. You can check guest agent status on the VM's Guest Agent tab in the Console. See Guest Agent for details.

Supported Windows Versions

The configuration drive includes VirtIO drivers for the following Windows versions:

Windows VersionDriver Folder Code
Windows Server 20222k22
Windows Server 20252k25
Windows 10w10
Windows 11w11

When writing an Autounattend file, use the driver folder code that matches the Windows version you are installing. For example, to install Windows Server 2025, reference the 2k25 subfolders in your driver paths.

VM Hardware Notes

Gallium Windows VMs have the following hardware characteristics that affect how you write an Autounattend file:

  • UEFI firmware — the VM uses UEFI boot (q35 machine type with SMM). Disk partitioning must use GPT, not MBR.
  • Disk 0 is the configuration drive — the SATA configuration drive always enumerates as Disk 0.
  • Disk 1 is the OS disk — the VirtIO OS disk enumerates as Disk 1. Your answer file must target Disk 1 for the Windows installation.
  • Single disk at creation — only one OS disk is supported at VM creation time. Additional disks can be added after the installation completes.
info

Windows does not have a stable method for targeting the correct installation disk when multiple VirtIO disks are present. This is why only one disk is supported during initial creation. Add additional disks from the VM's Storage tab after setup is complete.

Automated Installation

This section walks through the key parts of a Windows Autounattend answer file, section by section. A complete working example is provided at the end of the page.

A Windows answer file is organized into configuration passes. The three passes relevant to a Gallium VM are:

  1. windowsPE — runs during Windows Setup before the OS is installed. Configures drivers, locale, disk layout, and image selection.
  2. specialize — runs after the OS is installed but before first logon. Used to stage the SetupComplete.cmd bootstrap script and perform security cleanup.
  3. oobeSystem — runs during the Out-of-Box Experience. Configures user accounts and suppresses interactive prompts. After OOBE finishes, Windows automatically runs SetupComplete.cmd, which installs the guest agent and completes provisioning.

windowsPE Pass

Loading VirtIO Drivers

Windows Setup needs the VirtIO storage driver to detect the OS disk. Add a Microsoft-Windows-PnpCustomizationsWinPE component with driver paths pointing to the configuration drive. At minimum, include the viostor (storage) and NetKVM (network) drivers:

<component name="Microsoft-Windows-PnpCustomizationsWinPE" processorArchitecture="amd64"
publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"
xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State">
<DriverPaths>
<PathAndCredentials wcm:action="add" wcm:keyValue="1">
<Path>C:\drivers\viostor\2k25\amd64</Path>
</PathAndCredentials>
<PathAndCredentials wcm:action="add" wcm:keyValue="2">
<Path>C:\drivers\NetKVM\2k25\amd64</Path>
</PathAndCredentials>
</DriverPaths>
</component>

Replace 2k25 with the folder code that matches your target Windows version. You can include additional drivers (Balloon, vioserial, etc.) in the same block — see the complete example for all available drivers.

info

During the windowsPE pass, the configuration drive is mapped to C:\ by Windows Setup. This is why driver paths use C:\drivers\... even though the configuration drive will have a different letter once Windows is installed.

Locale and Input

Set the installation language and regional settings with Microsoft-Windows-International-Core-WinPE:

<component name="Microsoft-Windows-International-Core-WinPE" processorArchitecture="amd64"
publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
<SetupUILanguage>
<UILanguage>en-US</UILanguage>
</SetupUILanguage>
<InputLocale>en-US</InputLocale>
<SystemLocale>en-US</SystemLocale>
<UILanguage>en-US</UILanguage>
<UserLocale>en-US</UserLocale>
</component>

Disk Partitioning

Because the VM uses UEFI, the OS disk must be partitioned with a GPT layout. The following creates three partitions on Disk 1 (the VirtIO OS disk):

  1. EFI System Partition — 100 MB, FAT32
  2. Microsoft Reserved Partition — 16 MB, no format
  3. Windows OS Partition — remaining space, NTFS, assigned drive letter C:
<DiskConfiguration>
<WillShowUI>OnError</WillShowUI>
<Disk wcm:action="add">
<DiskID>1</DiskID>
<WillWipeDisk>true</WillWipeDisk>
<CreatePartitions>
<CreatePartition wcm:action="add">
<Order>1</Order>
<Type>EFI</Type>
<Size>100</Size>
</CreatePartition>
<CreatePartition wcm:action="add">
<Order>2</Order>
<Type>MSR</Type>
<Size>16</Size>
</CreatePartition>
<CreatePartition wcm:action="add">
<Order>3</Order>
<Type>Primary</Type>
<Extend>true</Extend>
</CreatePartition>
</CreatePartitions>
<ModifyPartitions>
<ModifyPartition wcm:action="add">
<Order>1</Order>
<PartitionID>1</PartitionID>
<Label>EFI</Label>
<Format>FAT32</Format>
</ModifyPartition>
<ModifyPartition wcm:action="add">
<Order>2</Order>
<PartitionID>3</PartitionID>
<Label>Windows</Label>
<Format>NTFS</Format>
<Letter>C</Letter>
</ModifyPartition>
</ModifyPartitions>
</Disk>
</DiskConfiguration>
warning

Do not target Disk 0 — that is the configuration drive. The OS disk is always Disk 1.

Image Selection

Specify which Windows edition to install using the image index. The InstallTo element must point to Disk 1, Partition 3 (the Windows OS partition created above):

<ImageInstall>
<OSImage>
<InstallTo>
<DiskID>1</DiskID>
<PartitionID>3</PartitionID>
</InstallTo>
<WillShowUI>OnError</WillShowUI>
<InstallToAvailablePartition>false</InstallToAvailablePartition>
<InstallFrom>
<MetaData wcm:action="add">
<Key>/IMAGE/INDEX</Key>
<Value>2</Value>
</MetaData>
</InstallFrom>
</OSImage>
</ImageInstall>

Common image index values for Windows Server ISOs:

IndexEdition
1Standard (Core)
2Standard (Desktop Experience)
3Datacenter (Core)
4Datacenter (Desktop Experience)

Product Key

The <ProductKey> element in <UserData> controls license activation. It also determines which editions the installer offers — if a key is specified, Setup only shows editions that match that key's edition ID.

For evaluation ISOs, omit the key or leave the element empty. Setup will accept all editions in the ISO:

<ProductKey>
<WillShowUI>Never</WillShowUI>
</ProductKey>

For retail or volume-licensed ISOs, add your key inside the element:

<ProductKey>
<Key>XXXXX-XXXXX-XXXXX-XXXXX-XXXXX</Key>
<WillShowUI>Never</WillShowUI>
</ProductKey>

specialize Pass

The specialize pass runs after the OS image is written to disk but before the first logon. In a Gallium VM, this pass stages the SetupComplete.cmd bootstrap script (and optionally firstrun.ps1) from the configuration drive into the OS, and deletes the answer file from the configuration drive for security.

info

The QEMU guest agent cannot be installed during the specialize pass because the VSS COM+ component is not yet registered at that stage and the guest agent MSI requires it. Guest agent installation is deferred to SetupComplete.cmd, which runs after OOBE when the OS is fully booted.

Staging SetupComplete.cmd

Windows automatically looks for a script at C:\Windows\Setup\Scripts\SetupComplete.cmd after OOBE finishes and runs it in SYSTEM context before the logon screen appears. Copy it — along with firstrun.ps1 if present — from the configuration drive during specialize so it is in place when needed:

<RunSynchronousCommand wcm:action="add">
<Order>1</Order>
<Path>powershell -c "$d=(gcim Win32_Volume -F \"Label='CONFIGDRIVE'\").DriveLetter;md C:\Windows\Setup\Scripts -F;cp $d\SetupComplete.cmd,$d\firstrun.ps1 C:\Windows\Setup\Scripts"</Path>
<Description>Stage SetupComplete.cmd and firstrun.ps1</Description>
<WillReboot>Never</WillReboot>
</RunSynchronousCommand>

SetupComplete.cmd is provided by Gallium on the configuration drive. It handles the remaining provisioning steps after OOBE:

  1. Installs the QEMU guest agent from the configuration drive.
  2. If firstrun.ps1 is present, registers a one-shot scheduled task that runs it on the next boot — after the configuration drive has been detached.
  3. Waits for OOBE finalisation to complete, then triggers a clean shutdown so the hypervisor can detach the configuration drive and mark provisioning as complete.

See Guest Agent for more on what the guest agent provides.

Security Cleanup

After Windows Setup has cached and processed the answer file, delete the original from the configuration drive to remove any plaintext credentials:

<RunSynchronousCommand wcm:action="add">
<Order>2</Order>
<Path>powershell -c "$d=(gcim Win32_Volume -F \"Label='CONFIGDRIVE'\").DriveLetter;ri \"$d\autounattend.xml\" -fo -ea 0"</Path>
<Description>Remove autounattend.xml from config drive</Description>
<WillReboot>Never</WillReboot>
</RunSynchronousCommand>

oobeSystem Pass

The oobeSystem pass runs during the Out-of-Box Experience on first boot. In a Gallium VM, this pass only needs to set the Administrator password and suppress interactive prompts. Guest agent installation, first-run script execution, and shutdown are all handled automatically by SetupComplete.cmd after OOBE finishes.

Administrator Password

Set the built-in Administrator password:

<UserAccounts>
<AdministratorPassword>
<Value>YourPasswordHere</Value>
<PlainText>true</PlainText>
</AdministratorPassword>
</UserAccounts>
info

The Autounattend file is encrypted in your browser and decrypted only on the Hypervisor when the configuration drive is built. The security cleanup step in the specialize pass deletes the file from the configuration drive after Windows has processed it.

OOBE Suppression

Suppress all interactive Out-of-Box Experience prompts so setup completes without user input:

<OOBE>
<HideEULAPage>true</HideEULAPage>
<ProtectYourPC>1</ProtectYourPC>
<NetworkLocation>Work</NetworkLocation>
<HideWirelessSetupInOOBE>true</HideWirelessSetupInOOBE>
<SkipUserOOBE>true</SkipUserOOBE>
<SkipMachineOOBE>true</SkipMachineOOBE>
</OOBE>

Once OOBE finishes, Windows automatically runs C:\Windows\Setup\Scripts\SetupComplete.cmd (staged during the specialize pass). This script installs the guest agent, schedules firstrun.ps1 for the next boot, and shuts down the VM to complete provisioning.

Complete Example

The following is a complete, working Autounattend answer file for Windows Server 2025 Standard (Desktop Experience). It automates the full installation: loads VirtIO drivers, partitions the disk, stages SetupComplete.cmd to handle guest agent installation and shutdown, and suppresses all interactive prompts.

Adapt the driver folder codes, image index, locale settings, and credentials to match your environment.

<?xml version="1.0" encoding="utf-8"?>
<unattend xmlns="urn:schemas-microsoft-com:unattend">
<settings pass="windowsPE">
<!-- Load VirtIO drivers so Windows Setup can see the OS disk and network -->
<component name="Microsoft-Windows-PnpCustomizationsWinPE" processorArchitecture="amd64"
publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"
xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State">
<DriverPaths>
<PathAndCredentials wcm:action="add" wcm:keyValue="1">
<Path>C:\drivers\viostor\2k25\amd64</Path>
</PathAndCredentials>
<PathAndCredentials wcm:action="add" wcm:keyValue="3">
<Path>C:\drivers\NetKVM\2k25\amd64</Path>
</PathAndCredentials>
<PathAndCredentials wcm:action="add" wcm:keyValue="5">
<Path>C:\drivers\Balloon\2k25\amd64</Path>
</PathAndCredentials>
<PathAndCredentials wcm:action="add" wcm:keyValue="7">
<Path>C:\drivers\pvpanic\2k25\amd64</Path>
</PathAndCredentials>
<PathAndCredentials wcm:action="add" wcm:keyValue="9">
<Path>C:\drivers\qemupciserial\2k25\amd64</Path>
</PathAndCredentials>
<PathAndCredentials wcm:action="add" wcm:keyValue="11">
<Path>C:\drivers\vioinput\2k25\amd64</Path>
</PathAndCredentials>
<PathAndCredentials wcm:action="add" wcm:keyValue="13">
<Path>C:\drivers\viorng\2k25\amd64</Path>
</PathAndCredentials>
<PathAndCredentials wcm:action="add" wcm:keyValue="15">
<Path>C:\drivers\vioscsi\2k25\amd64</Path>
</PathAndCredentials>
<PathAndCredentials wcm:action="add" wcm:keyValue="17">
<Path>C:\drivers\vioserial\2k25\amd64</Path>
</PathAndCredentials>
</DriverPaths>
</component>

<!-- Locale and keyboard layout -->
<component name="Microsoft-Windows-International-Core-WinPE" processorArchitecture="amd64"
publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
<SetupUILanguage>
<UILanguage>en-US</UILanguage>
</SetupUILanguage>
<InputLocale>en-US</InputLocale>
<SystemLocale>en-US</SystemLocale>
<UILanguage>en-US</UILanguage>
<UserLocale>en-US</UserLocale>
</component>

<!-- Disk partitioning, image selection, and user data -->
<component name="Microsoft-Windows-Setup" processorArchitecture="amd64"
publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"
xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State">
<DiskConfiguration>
<WillShowUI>OnError</WillShowUI>
<Disk wcm:action="add">
<!--
Disk 0 = SATA CONFIGDRIVE (do not wipe)
Disk 1 = VirtIO OS disk
GPT layout required for UEFI boot
-->
<DiskID>1</DiskID>
<WillWipeDisk>true</WillWipeDisk>
<CreatePartitions>
<CreatePartition wcm:action="add">
<Order>1</Order>
<Type>EFI</Type>
<Size>100</Size>
</CreatePartition>
<CreatePartition wcm:action="add">
<Order>2</Order>
<Type>MSR</Type>
<Size>16</Size>
</CreatePartition>
<CreatePartition wcm:action="add">
<Order>3</Order>
<Type>Primary</Type>
<Extend>true</Extend>
</CreatePartition>
</CreatePartitions>
<ModifyPartitions>
<ModifyPartition wcm:action="add">
<Order>1</Order>
<PartitionID>1</PartitionID>
<Label>EFI</Label>
<Format>FAT32</Format>
</ModifyPartition>
<ModifyPartition wcm:action="add">
<Order>2</Order>
<PartitionID>3</PartitionID>
<Label>Windows</Label>
<Format>NTFS</Format>
<Letter>C</Letter>
</ModifyPartition>
</ModifyPartitions>
</Disk>
</DiskConfiguration>
<UserData>
<AcceptEula>true</AcceptEula>
<FullName>Administrator</FullName>
<Organization>Administrators</Organization>
<ProductKey>
<!-- Omit the key for evaluation ISOs -->
<WillShowUI>Never</WillShowUI>
</ProductKey>
</UserData>
<ImageInstall>
<OSImage>
<InstallTo>
<DiskID>1</DiskID>
<PartitionID>3</PartitionID>
</InstallTo>
<WillShowUI>OnError</WillShowUI>
<InstallToAvailablePartition>false</InstallToAvailablePartition>
<InstallFrom>
<!--
Image index values for Windows Server ISOs:
1 = Standard Core
2 = Standard Desktop Experience
3 = Datacenter Core
4 = Datacenter Desktop Experience
-->
<MetaData wcm:action="add">
<Key>/IMAGE/INDEX</Key>
<Value>2</Value>
</MetaData>
</InstallFrom>
</OSImage>
</ImageInstall>
</component>
</settings>

<settings pass="specialize">
<!-- Computer name and time zone -->
<component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64"
publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
<ComputerName>*</ComputerName>
<TimeZone>UTC</TimeZone>
</component>

<!-- Stage SetupComplete.cmd and clean up credentials -->
<component name="Microsoft-Windows-Deployment" processorArchitecture="amd64"
publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"
xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State">
<RunSynchronous>
<!-- Stage SetupComplete.cmd and firstrun.ps1 into C:\Windows\Setup\Scripts -->
<RunSynchronousCommand wcm:action="add">
<Order>1</Order>
<Path>powershell -c "$d=(gcim Win32_Volume -F \"Label='CONFIGDRIVE'\").DriveLetter;md C:\Windows\Setup\Scripts -F;cp $d\SetupComplete.cmd,$d\firstrun.ps1 C:\Windows\Setup\Scripts"</Path>
<Description>Stage SetupComplete.cmd and firstrun.ps1</Description>
<WillReboot>Never</WillReboot>
</RunSynchronousCommand>
<!-- Delete the answer file from the config drive to remove plaintext credentials -->
<RunSynchronousCommand wcm:action="add">
<Order>2</Order>
<Path>powershell -c "$d=(gcim Win32_Volume -F \"Label='CONFIGDRIVE'\").DriveLetter;ri \"$d\autounattend.xml\" -fo -ea 0"</Path>
<Description>Remove autounattend.xml from config drive</Description>
<WillReboot>Never</WillReboot>
</RunSynchronousCommand>
</RunSynchronous>
</component>
</settings>

<settings pass="oobeSystem">
<component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64"
publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"
xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State">
<!-- Suppress all OOBE prompts -->
<OOBE>
<HideEULAPage>true</HideEULAPage>
<ProtectYourPC>1</ProtectYourPC>
<NetworkLocation>Work</NetworkLocation>
<HideWirelessSetupInOOBE>true</HideWirelessSetupInOOBE>
<SkipUserOOBE>true</SkipUserOOBE>
<SkipMachineOOBE>true</SkipMachineOOBE>
</OOBE>
<TimeZone>UTC</TimeZone>
<!-- Set the Administrator password -->
<UserAccounts>
<AdministratorPassword>
<Value>YourPasswordHere</Value>
<PlainText>true</PlainText>
</AdministratorPassword>
</UserAccounts>
</component>
</settings>
</unattend>
tip

This example targets Windows Server 2025 Standard (Desktop Experience) using the 2k25 driver paths and image index 2. Adjust the driver folder codes and image index to match your Windows version and edition.

Manual Installation

If you do not provide an Autounattend file, the VM boots into the standard Windows installer. You will need to load the VirtIO drivers manually and install the guest agent yourself.

  1. Start the VM — it will boot from the Windows ISO attached to the template.
  2. Load the storage driver — at the disk selection screen, click Load driver and browse to the configuration drive. Navigate to drivers > viostor > {version} > amd64 and select the driver. The VirtIO OS disk will become visible.
  3. Select the OS disk — choose the VirtIO disk (Disk 1) and continue with the installation.
  4. Install network and other drivers — after Windows is installed, open the configuration drive in File Explorer (look for the volume labeled CONFIGDRIVE) and install additional drivers from the drivers folder. At minimum, install the NetKVM driver for network connectivity.
  5. Install the guest agent — run guest-agent\qemu-ga-x86_64.msi from the configuration drive.
  6. Shut down the VM — manually remove the Windows ISO, Boostrap ISO, and Configuration drive from the Virtual Machine.