For automatic operation of the Hyper-V processing module (pmhyperv), virtual machine (VM) templates must comply with a unified standard. This article describes the directory structure, the format of the template.json file, and the preparation of VHDX disks for Linux and Windows for automatic VM creation via the Hyper-V processing module. The instructions will help create templates that will be deployed without administrator intervention on the platform.
To prepare a template:
- Create a directory with a defined structure.
- Prepare the template description — the template.json file.
- Prepare a custom configuration file for the bootstrap mechanism (bootstrap config).
- Prepare the guest OS.
- Publish the template.
Directory structure
Each template is located in a separate subdirectory of the templates directory:
D:\Templates\
├── ubuntu-24.04\
│ ├── disk.vhdx
│ ├── user-data.yaml.tpl
│ └── template.json
└── windows-server-2022\
├── disk.vhdx
├── unattend.xml.tpl
└── template.jsonThe subdirectory name must match the value of
intname
in the file template.json. The Hyper-V processing module module:
- Stores the value of
intnamein BILLmanager. - When creating a VM, creates the directory <templates_dir>\<intname>.
Format of template.json file
Required fields
Additional fields for Linux, generation 2
Additional template fields used when publishing to BILLmanager
Custom bootstrap configuration file
The module does not automatically configure the guest OS: DHCP, DNS, account, SSH, OOBE, partition, and file system parameters are not generated. OS configuration is performed using a bootstrap config — a file with configuration instructions. For Linux, this is a YAML file whose first line must be #cloud-config. For Windows, this is an XML file. See examples below.
The relative path to the bootstrap config is specified in the bootstrap_file field of the template.json file. The path is specified relative to the template directory. Absolute paths and directory traversal using .. are not supported.
To pass VM information, use tokens — records in the format __BILLMGR_<parameter>__. Before building the ISO image, the module will substitute the current value. Supported tokens:
Using unknown tokens in the format
__BILLMGR_*__
is prohibited.
Minimal template examples
Linux
D:\Templates\ubuntu-test\
├── disk.vhdx
├── template.json
└── user-data.yaml.tpl{
"intname": "ubuntu-test",
"name": "Ubuntu Test",
"os_family": "linux",
"generation": 2,
"vhdx_file": "disk.vhdx",
"bootstrap_file": "user-data.yaml.tpl"
}
If the image does not support Secure Boot, add the field "secure_boot": "off".
Minimal user-data.yaml.tpl file
The file sets the root user password and notifies the module of bootstrap completion:
The example does not modify the partition table or file system. The Linux image must:
- contain network interface configuration for DHCP operation before VM deployment;
- support NoCloud — a cloud-init data source that allows passing configuration locally without a network service;
- run hv_kvp_daemon.
SSH server is required only for client access and is not used by the module.
Windows
D:\Templates\windows-test\
├── disk.vhdx
├── template.json
└── unattend.xml.tpl{
"intname": "windows-test",
"name": "Windows Test",
"os_family": "windows",
"generation": 2,
"vhdx_file": "disk.vhdx",
"bootstrap_file": "unattend.xml.tpl"
}The system writes the marker as SYSTEM during the
specialize
phase. For more details, see Microsoft documentation https://learn.microsoft.com/en-us/windows-hardware/manufacture/desktop/specialize?view=windows-11. The configuration does not use parameters
AutoLogon
(for more details see https://learn.microsoft.com/en-us/windows-hardware/customize/desktop/unattend/microsoft-windows-shell-setup-autologon) and
FirstLogonCommands
(for more details see https://learn.microsoft.com/en-us/windows-hardware/customize/desktop/unattend/microsoft-windows-shell-setup-firstlogoncommands). The configuration does not depend on the first interactive login, Windows language, or built-in administrator name. The oobeSystem phase sets the generated password for the built-in Administrator account.
The Windows image must:
- use DHCP;
- contain an active Hyper-V Data Exchange service;
- apply the unattend.xml file from the attached bootable DVD disk during startup.
Before publishing, execute the command:
sysprep.exe /generalize /oobe /shutdown /mode:vmGeneral requirements for VHDX disk
Prepare the disk according to the following requirements:
- use VHDX format. If your template is in VHD format, convert it to VHDX. After conversion, the file must have the .vhdx extension, and the command
Get-VHDmust returnVhdFormat = VHDX;Simply renaming the file from.vhdto.vhdxis not sufficient. Use specialized software (for example, the Hyper-V Manager graphical snap-in or the Convert-VHD utility) - completely shut down the source virtual machine and detach the VHDX disk;
- use a VHDX disk without checkpoints or differencing chains;
- ensure the image does not contain user data, static addresses, or old bootstrap disks;
- specify in the template.json file the generation that matches the system boot method:
- BIOS for generation 1;
- UEFI for generation 2;
- use a VHDX disk from a VM of the same generation specified in the metadata;
- grant the WinRM service account read access to the VHDX disk files template.json and bootstrap_file;
- remove the "read-only" attribute, as the module executes the
Resize-VHDcommand after copying.
Disk size
When creating a VM, the module copies the VHDX disk and sets the exact virtual size according to the tariff's disc resource. Set the template disk size no larger than the minimum value of the disc resource for all tariffs using this template.
Check the disk size using the command:
Get-VHD 'D:\Templates\ubuntu-24.04\disk.vhdx' |
Select-Object Path, VhdFormat, VhdType, Size, MinimumSize, FileSizeThe MinimumSize field shows the theoretical minimum to which Hyper-V can shrink the disk; however, the processing module does not support reducing disk size. If the template disk size exceeds the target size specified by the tariff's disc resource, VM creation will fail. It is recommended to prepare a small base disk and allow only expansion.
The module changes only the virtual size of the VHDX on the Hyper-V host. It does not modify the partition table or file system inside the guest operating system (Linux or Windows). If you need to expand the guest partition, use the bootstrap config or OS administration tools.
Linux template
Guest OS requirements
Required:
cloud-initwith NoCloud data source;hv_kvp_daemonfor:- Data Exchange integration services;
- passing the DHCP address and readiness marker to the node;
- default DHCP configuration;
- obtaining an IP address via DHCP.
You can perform installation from:
- official cloud image;
- ISO image.
Perform disk preparation on a temporary VM in Hyper-V. After completing configuration and cleanup, delete this VM, keeping only the VHDX disk file for the template.
OS installation
Option 1: official cloud image
If you have no experience configuring Linux templates, it is recommended to start with the official cloud image (cloud image) of the distribution. If the image is provided in qcow2 or raw format, convert it on a Linux VM using the utility qemu-img :
qemu-img info ubuntu-24.04-server-cloudimg-amd64.img
qemu-img convert -p -f qcow2 -O vhdx -o subformat=dynamic \
ubuntu-24.04-server-cloudimg-amd64.img disk.vhdxIn the qemu-img convert command, specify the actual format of the source file from the output of the qemu-img info command. For example, qcow2. After conversion, copy the VHDX disk to the Hyper-V node.
Create a temporary VM of the same generation that will be specified in the template.json file:
- for Linux generation 2, use the secure boot template
MicrosoftUEFICertificateAuthority; - if the image does not support secure boot, disable Secure Boot. In the template.json file, specify
"secure_boot": "off".
Option 2: installation from ISO image
Perform the following steps:
- Create a temporary VM with a VHDX disk of at least 20 GB.
- Install the OS without a static IP address.
- Install cloud-init and Hyper-V integration services.
- Verify NoCloud data source operation.
- Clear the VM identifier and cloud-init state.
- Shut down the VM and transfer the VHDX disk to the template directory.
OS configuration
Debian or Ubuntu
sudo apt-get update
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y \
cloud-init hyperv-daemonsIn Ubuntu, the data exchange service (KVP daemon) may be included in the linux-cloud-tools-virtual package. If after installing hyperv-daemons the hv-kvp-daemon service does not start, install the linux-cloud-tools-virtual and linux-cloud-tools-$(uname -r) packages:
sudo apt-get install -y linux-cloud-tools-virtual linux-cloud-tools-$(uname -r)Verify the actual service name:
systemctl list-unit-files | grep -Ei 'hv.*kvp|kvp.*daemon'
pgrep -a hv_kvp_daemonThe required result is working cloud-init and hv_kvp_daemon.
RHEL (AlmaLinux)
sudo dnf install -y cloud-init hyperv-daemonsRequired result — working cloud-init and hv_kvp_daemon.
Verifying cloud-init
cloud-init --version
cloud-init status --long
pgrep -a hv_kvp_daemonEnsure that the file /etc/cloud/cloud-init.disabled does not exist and that the NoCloud data source is not prohibited in the datasource_list setting. S
Cleanup before publishing
Execute inside the temporary VM:
sudo cloud-init clean --logs --machine-id
sudo rm -rf /var/lib/cloud/seed/nocloud /var/lib/cloud/seed/nocloud-net
sudo rm -f /etc/ssh/ssh_host_*
sudo sync
sudo poweroffStandard cloud-init will create new SSH host keys on first boot. If the distribution has been modified, verify this before publishing.
After shutdown:
- Delete the temporary VM from Hyper-V without deleting the VHDX disk.
- Move the disk to the template directory.
Content of Linux bootstrap disk
The seed.iso disk with label cidata is a temporary ISO image that the processing module automatically creates when deploying a VM. The seed.iso disk contains the files:
user-data— custom cloud-init configuration file after substituting supported tokens;meta-data— unique instance identifier (instance-id) andlocal-hostname;
The custom user-data file must write BILLmanager.Provisioning=vm_<item> to KVP pool 1. The module waits until the VM transitions to the Running state and the readiness marker appears in the KVP pool. The wait time is up to 360 seconds. The module does not connect to the guest operating system over the network.
After successful verification, the seed.iso disk and the original bootstrap directory are deleted from the Hyper-V node.
Windows template
Supported guest systems
Windows Server 2016 and newer are recommended. The guest system must support:
- running the system preparation process (
Sysprep) and the Out-of-Box Experience wizard (OOBE); - built-in local Administrator account;
- obtaining a network address via DHCP;
- applying the unattend.xml file from an attached DVD disk.
Preparing a temporary VM
- Create a temporary VM of the required generation with a dynamic VHDX disk.
- Install Windows OS and all updates.
- Configure the network adapter to obtain an address via DHCP.
- Do not join the machine to a domain.
- Activate the built-in Administrator account. You can find the account by its relative identifier (RID)
-500:
$administrator = Get-LocalUser | Where-Object { $_.SID.Value -match '-500$' } Enable-LocalUser -InputObject $administrator - Delete temporary data, auxiliary services (
agents), and unique credentials. - Configure automatic system partition expansion. For more details, see section Expanding C: drive on first boot.
- Execute the
Sysprepcommand and wait for the VM to shut down completely. For more details, see section System preparation (Sysprep).
The temporary machine password is not used for created services. The unattend.xml file sets a new random password. Use the value $administrator.Name in the custom unattend.xml file.
Expanding C: drive on first boot
The pmhyperv module increases the VHDX disk size but does not invoke the Resize-Partition command inside the guest Windows. Add a task to the template for automatic partition expansion on startup, or leave this action to the user's discretion.
- Create inside the temporary VM the file C:\ProgramData\BILLmanager\Expand-SystemVolume.ps1:
$ErrorActionPreference = 'Stop' $partition = Get-Partition -DriveLetter C $supported = Get-PartitionSupportedSize -DriveLetter C if ($supported.SizeMax -gt $partition.Size) { Resize-Partition -DriveLetter C -Size $supported.SizeMax } Unregister-ScheduledTask -TaskName 'BILLmanager Expand System Volume' ` -Confirm:$false -ErrorAction SilentlyContinue - Register the task to run as the system account:
$action = New-ScheduledTaskAction -Execute 'powershell.exe' -Argument ( '-NoProfile -ExecutionPolicy Bypass ' + '-File "C:\ProgramData\BILLmanager\Expand-SystemVolume.ps1"' ) $trigger = New-ScheduledTaskTrigger -AtStartup Register-ScheduledTask -TaskName 'BILLmanager Expand System Volume' ` -Action $action -Trigger $trigger -User 'SYSTEM' -RunLevel Highest -Force - Test the task on a copy of the VM: after increasing the VHDX disk size, the
Get-Partition -DriveLetter Ccommand should show the new size.
System preparation (Sysprep)
Run from PowerShell or command prompt:
& "$env:WINDIR\System32\Sysprep\Sysprep.exe" `
/generalize /oobe /shutdown /mode:vmAfter executing the command, the VM will shut down. Do not start the temporary VM after shutdown: the next startup must occur on the created instance with the attached unattend.iso disk.
After shutdown, delete the temporary machine from Hyper-V without deleting the VHDX disk and move the disk to the template directory.
Content of Windows bootstrap disk
The unattend.iso disk is a temporary ISO image that the pmhyperv module automatically creates during deployment. The unattend.iso disk contains the custom unattend.xml file after token substitution. The module does not add network, DNS, or OOBE(Out-of-Box Experience) settings. The custom configuration file must ensure:
- DHCP;
- password installation;
- writing
BILLmanager.Provisioning=vm_<item>to the guest KVP (Key-Value Pair) branch.
The module expects the VM Running state and the readiness marker via VMbus, after which it deletes unattend.iso and the original directory.
Template publishing
- Ensure the source VM is shut down.
- Verify the VHDX disk using the
Get-VHDcommand. The output must display actual disk data: path, VHDX format, type, size. If the command returns an error or empty result, the disk is corrupted or does not exist. - Create a directory whose name matches the
intnamevalue. - Copy the VHDX disk and add the template.json file.
- Verify JSON correctness and path conformity in the metadata.
- Start synchronization of processing module settings in BILLmanager.
- Select the appeared template (
ostempl) in a test tariff plan. - Create a test service in an isolated network to verify operability.
- Wait for the VM to transition to the
Runningstate and for the readiness marker to appear in the KVP pool. - Verify operation inside the test VM:
Get-NetRoute -DestinationPrefix '0.0.0.0/0' | Select-Object InterfaceAlias, NextHop, RouteMetric
Template update
To update a template:
- Prepare a new directory with a temporary name.
- Fully prepare and verify the VHDX disk.
Do not modify the VHDX disk while it is being copied to create a new VM. Modifying the source file during this operation corrupts the copy and causes VM creation errors. - Shut down the source VM.
- Replace the template directory during a maintenance window.
- Resynchronize processing module settings.
- Create a test service to verify operability.
Changes to the template VHDX disk affect only new VMs. Previously created VMs use their own disk.vhdx file in the VM directory and do not depend on template updates.
Related topics:
En
Es