Skip to content

Hyper-V Virtual Mashine automation

Need a virtual test machine and don`t want to do the work every time? In this post, I'll walk you through a script that will create the test VM for you in about two and a half minutes. You'll barely have to lift a finger.

Sounds good? Then lets do this!

Prerequisites

  • Windows 11 or Windows 10 (pro or enterprise)
  • Hyper-V installed
  • Windows 11 ISO (pro or enterprise), with at least the may 2024 updates
  • Elevated powershell access

Creating the script

The prep

  1. First we get our prerequisites in straight and download the Windows 11 ISO.

  2. Second we need to enable the Hyper-V Windows Feature.

powershell
Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V -All

The Script

  1. Now, to start with the script, we first need a few infos from the user.

    • The Name you want for the VM.
    • The Folder where the VM should be created.
    • The name of the Windows 11 ISO without the .iso extension. The script will assume the ISO is in the same folder as the VM will be created.
powershell
Param(
  [Parameter(Mandatory=$True,Position=1)]
  [string]$VMName,
  [Parameter(Mandatory=$True,Position=2)]
  [string]$Folder,
  [Parameter(Mandatory=$True,Position=3)]
  [string]$WindowsISO
)
  1. Next a few of our own parameters are added.
powershell
$vhdpath = '$Folder\$VMName\$VMName.vhdx'
$vhdsize = 50GB
$iso = '$Folder\$WindowsISO.iso'
$imagePath = '$Folder\$VMName.wim'
  1. Now the Windows 11 iso will be mounted and the install.wim image file will be extracted from it.
    This image file contains all the windows install files we need.
powershell
# extract .wim file from windows .iso
$DiskImage = Mount-DiskImage -ImagePath $iso -StorageType ISO -PassThru

push-Location '$((Get-Volume -DiskImage $DiskImage).driveletter):'

# read files with the usual filesystem commands
Copy-Item '.\sources\install.wim' -Destination $Folder

#Pop-Location
Dismount-DiskImage -DevicePath $DiskImage.DevicePath
  1. The next step is to create the Virtual Disk Image used for the VM.
    For that we create a .vdhx file (don`t be mislead by the VHD in the cmdlet.), mount it and prepare it for the Windows installation. This means creating the .vhdx, initializing it and creating and formating the needed partitions.
powershell
# Create a new VHDX file
New-VHD -Path $vhdPath -SizeBytes $vhdsize -Dynamic

# Mount the VHDX file
Mount-VHD -Path $vhdPath

# Get the disk number of the mounted VHDX
$disk = Get-Disk | Where-Object IsOffline -eq $false | Sort-Object Number | Select-Object -Last 1

# Initialize the disk
Initialize-Disk -Number $disk.Number

# Creates the four needed Windows partitions and formats them to Microsofts best practice
$system_part = New-Partition -DiskNumber $disk.Number -size 100MB -AssignDriveLetter -GptType '{c12a7328-f81f-11d2-ba4b-00a0c93ec93b}'
Format-Volume -DriveLetter $system_part.DriveLetter -NewFileSystemLabel 'System' -FileSystem Fat32 -Confirm:$false -Force

$ms_part = New-Partition -DiskNumber $disk.Number -size 16MB -AssignDriveLetter -GptType '{e3c9e316-0b5c-4db8-817d-f92df00215ae}'

$recovery_part = New-Partition -DiskNumber $disk.Number -size 500MB -AssignDriveLetter -GptType '{de94bba4-06d1-4d40-a16a-bfd50179d6ac}'
Format-Volume -DriveLetter $recovery_part.DriveLetter -NewFileSystemLabel 'Recovery' -FileSystem NTFS -Confirm:$false -Force

$windows_part = New-Partition -DiskNumber $disk.Number -UseMaximumSize -AssignDriveLetter -GptType '{ebd0a0a2-b9e5-4433-87c0-68b6b72699c7}'
Format-Volume -DriveLetter $windows_part.DriveLetter -NewFileSystemLabel 'Windows' -FileSystem NTFS -Confirm:$false -Force
  1. Now that we got a mounted prepared disk, we can apply the Windows image to it.
powershell
dism /Apply-Image /ImageFile:'$Folder\install.wim' /Index:1 /ApplyDir:'$($windows_part.DriveLetter):\'
  1. And following this, we need to copy the boot files to the System partition.
    Because this is a batch process we need to start it via a process and that necessitated the short sleep afterwards.
powershell
Start-Process -NoNewWindow -FilePath '$($windows_part.DriveLetter):\Windows\System32\bcdboot' -ArgumentList '$($windows_part.DriveLetter):\Windows /s $($system_part.DriveLetter):'
start-sleep -seconds 10
  1. Before we can create the VM next, we have bit of cleanup to do.
    This means dismounting the still mounted .vhdx, so the vm can use it and remove the install.wim image, because we don`t need it anymore and it takes up about 5 GB to 6 GB.
powershell
dismount-vhd -path $vhdpath
remove-item -path '$Folder\install.wim' -force
  1. Now the VM can finally be created and for that, we first need to provide a few parameters. For the Test mashine 8 GB of RAM is used, Generation 2 so vTPM and Autopilot can be used and the VM uses the Default Switch. If you need something different, you can just change the parameters.
powershell
$VM = @{
     Name = $VMName
     MemoryStartupBytes = 8589934592
     Generation = 2
     BootDevice = 'VHD'
     Path = '$Folder\$VMName'
     SwitchName = (Get-VMSwitch).Name
     VHDPath = $vhdpath
     GuestStateIsolationType = 'TrustedLaunch'
 }

New-VM @VM
  1. Now we need to set the KeyProtector and Enable TPM on VM so Autopilot is possible.
powershell
Set-VMKeyProtector -VMName $VMName -NewLocalKeyProtector
Enable-VMTPM -VMName $VMName
  1. And then start and connects to VM. And just like this you got a fresh Windows 11 VM that doesn`t need extra steps to generalized.
powershell
Start-VM -Name $VMName
vmconnect.exe localhost $vmname

The whole Script together

Just run it, and your good to go.

TIP

If you allready got Hyper-V activated or using the script multiple times, just remove the 'Installation Hyper-V Windows Feature' part.

powershell
# Installation Hyper-V Windows Feature
Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V -All

# Parameter Promt
Param(
  [Parameter(Mandatory=$True,Position=1)]
  [string]$VMName,
  [Parameter(Mandatory=$True,Position=2)]
  [string]$Folder,
  [Parameter(Mandatory=$True,Position=3)]
  [string]$WindowsISO
)

# Parameter
$vhdpath = '$Folder\$VMName\$VMName.vhdx'
$vhdsize = 50GB
$iso = '$Folder\$WindowsISO.iso'
$imagePath = '$Folder\$VMName.wim'

# extract .wim file from windows .iso
$DiskImage = Mount-DiskImage -ImagePath $iso -StorageType ISO -PassThru

push-Location '$((Get-Volume -DiskImage $DiskImage).driveletter):'

# read files with the usual filesystem commands
Copy-Item '.\sources\install.wim' -Destination $Folder

#Pop-Location
Dismount-DiskImage -DevicePath $DiskImage.DevicePath

# Create a new VHDX file
New-VHD -Path $vhdPath -SizeBytes $vhdsize -Dynamic

# Mount the VHDX file
Mount-VHD -Path $vhdPath

# Get the disk number of the mounted VHDX
$disk = Get-Disk | Where-Object IsOffline -eq $false | Sort-Object Number | Select-Object -Last 1

# Initialize the disk
Initialize-Disk -Number $disk.Number

# Creates the four needed Windows partitions and formats them to Microsofts best practice
$system_part = New-Partition -DiskNumber $disk.Number -size 100MB -AssignDriveLetter -GptType '{c12a7328-f81f-11d2-ba4b-00a0c93ec93b}'
Format-Volume -DriveLetter $system_part.DriveLetter -NewFileSystemLabel 'System' -FileSystem Fat32 -Confirm:$false -Force

$ms_part = New-Partition -DiskNumber $disk.Number -size 16MB -AssignDriveLetter -GptType '{e3c9e316-0b5c-4db8-817d-f92df00215ae}'

$recovery_part = New-Partition -DiskNumber $disk.Number -size 500MB -AssignDriveLetter -GptType '{de94bba4-06d1-4d40-a16a-bfd50179d6ac}'
Format-Volume -DriveLetter $recovery_part.DriveLetter -NewFileSystemLabel 'Recovery' -FileSystem NTFS -Confirm:$false -Force

$windows_part = New-Partition -DiskNumber $disk.Number -UseMaximumSize -AssignDriveLetter -GptType '{ebd0a0a2-b9e5-4433-87c0-68b6b72699c7}'
Format-Volume -DriveLetter $windows_part.DriveLetter -NewFileSystemLabel 'Windows' -FileSystem NTFS -Confirm:$false -Force

# apply Image to VM Disk
dism /Apply-Image /ImageFile:'$Folder\install.wim' /Index:1 /ApplyDir:'$($windows_part.DriveLetter):\'

# Copies the System Boot files to the System partition
Start-Process -NoNewWindow -FilePath '$($windows_part.DriveLetter):\Windows\System32\bcdboot' -ArgumentList '$($windows_part.DriveLetter):\Windows /s $($system_part.DriveLetter):'

# Ingages a small delay to ensure the boot files are copied to the system partition because of the external process thats started
start-sleep -seconds 10

# Dismounts the VHDX file
dismount-vhd -path $vhdpath

# Removes the copied Windows install.wim Image
remove-item -path '$Folder\install.wim' -force

# VM creation
$VM = @{
     Name = $VMName
     MemoryStartupBytes = 8589934592
     Generation = 2
     BootDevice = 'VHD'
     Path = '$Folder\$VMName'
     SwitchName = (Get-VMSwitch).Name
     VHDPath = $vhdpath
     GuestStateIsolationType = 'TrustedLaunch'
 }

New-VM @VM

# Set KeyProtector and Enable TPM on VM so Autopilot is possible
Set-VMKeyProtector -VMName $VMName -NewLocalKeyProtector
Enable-VMTPM -VMName $VMName

# Start and connects to VM
Start-VM -Name $VMName
vmconnect.exe localhost $vmname