Virtual Machine Template (Windows) - Deployment and Customization

This is an old revision of the document!


Virtual Machine Template (Windows) - Deployment and Customization

Param(
    [Parameter(Mandatory=$True)]
    [string]$VMName

)

$AdministratorPassword = "ChangeMe123!"

# Copy VHDX
$NewDiskPath = "D:\Hyper-V\Virtual Hard Disks\$($VMName.ToLower())-os.vhdx"
Copy-Item -Path "C:\Hyper-V\Virtual Hard Disk Template\win2025-template.vhdx" -Destination $NewDiskPath

# New VM
New-VM -Name $VMName.toUpper() -Generation 2 -MemoryStartupBytes 4GB -Path "C:\Hyper-V\Virtual Machines" -SwitchName "ConvergedSwitch"
Set-VMProcessor -VMName $VMName -Count 4
Set-VMFirmware -VMName $VMName -EnableSecureBoot On

# Attach Disk
Add-VMHardDiskDrive -VMName $VMName -Path $NewDiskPath -ControllerType SCSI
Set-VMFirmware -VMName $VMName -FirstBootDevice (Get-VMHardDiskDrive -VMName $VMName)



# Unattend
$Unattend = @"
<?xml version="1.0" encoding="utf-8"?>
<unattend xmlns="urn:schemas-microsoft-com:unattend">
  <settings pass="specialize">
    <component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64"
      publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
      <ComputerName>$($VMName.ToUpper())</ComputerName>
    </component>
    <component name="Microsoft-Windows-ServerManager-SvrMgrNc" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
      <DoNotOpenServerManagerAtLogon>true</DoNotOpenServerManagerAtLogon>
    </component>
    <component name="Microsoft-Windows-DNS-Client" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
      <UseDomainNameDevolution>false</UseDomainNameDevolution>
      <DNSDomain>local</DNSDomain>
    </component>
  </settings>
  <settings pass="oobeSystem">
    <component name="Microsoft-Windows-International-Core" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
      <!-- German keyboard (QWERTZ) -->
      <!-- 0407 = German (Germany) language ID -->
      <!-- 00000407 = German keyboard layout -->
      <InputLocale>0407:00000407</InputLocale>
      <!-- Everything else US English -->
      <SystemLocale>en-US</SystemLocale>
      <UILanguage>en-US</UILanguage>
      <UILanguageFallback>en-US</UILanguageFallback>
      <UserLocale>en-US</UserLocale>
    </component>
    <component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"> 
      <OOBE>
        <HideEULAPage>true</HideEULAPage>
        <HideOnlineAccountScreens>true</HideOnlineAccountScreens>
        <HideWirelessSetupInOOBE>true</HideWirelessSetupInOOBE>
        <NetworkLocation>Work</NetworkLocation>
        <ProtectYourPC>3</ProtectYourPC>
      </OOBE>
      <UserAccounts>
        <AdministratorPassword>
          <Value>$AdministratorPassword</Value>
          <PlainText>true</PlainText>
        </AdministratorPassword>
      </UserAccounts>
      <TimeZone>W. Europe Standard Time</TimeZone>
    </component>
  </settings>
</unattend>
"@


$Disk = Mount-VHD -Path $NewDiskPath -Passthru
$Partition = Get-Partition -DiskNumber $Disk.Number -PartitionNumber 3
If (-not $Partition.DriveLetter) {
	Set-Partition -DiskNumber $Disk.Number -PartitionNumber 3 -NewDriveLetter X	
}
$Partition = Get-Partition -DiskNumber $Disk.Number -PartitionNumber 3


$Unattend | Set-Content -Path X:\Windows\Panther\Unattend.xml -Encoding UTF8
$Unattend | Set-Content -Path C:\Users\Administrator\Desktop\LastUnattend.xml -Encoding UTF8

Dismount-VHD -Path $NewDiskPath 

# Start VM
Start-VM -Name $VMName 

# Wait VM provisioned
$Seconds = 0
Do {
	$Seconds++
	Start-Sleep -Seconds 1
	$HeartBeat = Get-VMIntegrationService -VMName $VMName -Name "Heartbeat"  # https://learn.microsoft.com/en-us/powershell/module/hyper-v/get-vmintegrationservice?view=windowsserver2025-ps
	Write-Host -NoNewLine "`rWaiting for VM to provision ($Seconds)"
} Until (($HeartBeat.PrimaryStatusDescription -eq 'OK'))


$Credential = New-Object System.Management.Automation.PSCredential ("Administrator", (ConvertTo-SecureString "$AdministratorPassword" -AsPlainText -Force) )


Invoke-Command -VMName $VMName -Credential $Credential -ScriptBlock {
	Get-NetAdapter | Rename-NetAdapter -NewName "Access_10.58.5.19_b24"
	Get-NetAdapter | New-NetIPAddress -IPAddress 10.58.5.19 -PrefixLength 24
	Get-Netadapter | Set-DnsClientServerAddress -ServerAddresses 10.4.21.181,10.4.3.50,10.4.3.51
}

windows/hyper-v/windows-vm-template-deployment-customzation.1778836177.txt.gz · Last modified: 2026/05/15 09:09