Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Next revision
Previous revision
windows:hyper-v:windows-vm-template-deployment-customzation [2026/05/15 09:05] – created chriswindows:hyper-v:windows-vm-template-deployment-customzation [2026/05/19 15:02] (current) chris
Line 1: Line 1:
 ====== Virtual Machine Template (Windows) - Deployment and Customization ====== ====== Virtual Machine Template (Windows) - Deployment and Customization ======
  
 +<code powershell>
 +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
 +$Stage = 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
 + If ($Stage -in @(0, 2) -and $HeartBeat.PrimaryStatusDescription -eq 'OK') {
 + $Stage++
 + }
 + If ($Stage -eq 1 -and $HeartBeat.PrimaryStatusDescription -ne 'OK') {
 + $Stage++
 + }
 + Write-Host -NoNewLine "`rWaiting for VM to provision ($Seconds)[$Stage]"
 +} Until ($Stage -ge 3)
 +
 +
 +$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
 +}
 +
 +</code>