Unattended installation of Windows Server 2016 virtual machines is explained below
Windows Server 2016 installation can be fully automated by using an unattend.xml file and using PowerShell code. This article demonstrates a typical unattend.xml file that can be armed with user preferences through PowerShell and perform the zero-touch installation. The only prerequisite is a Gen 2 syspreped version of Windows Server 2016 in either Core or Desktop Experience installation. The unattend.xml file information and the PowerShell code will be displayed in this article and you can copy and use it in your own environment. This installation method truly allows for a light out installation even from a single Core installation of Windows Server and a simple PowerShell console.
The installation steps are pretty straightforward:
The sample unattend.xml file is displayed below. For highlighting purposes, the actual data that will be replaced by the PowerShell code has the number 1 added in the text. You can however add more customization options and customize the PowerShell code to replace those values.
#Copy everything after this line and save it as unattend.xml#
<?
xml
version
=
"1.0"
encoding
"utf-8"
?>
<
unattend
xmlns
"urn:schemas-microsoft-com:unattend"
>
settings
pass
"windowsPE"
component
name
"Microsoft-Windows-International-Core-WinPE"
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"
SetupUILanguage
UILanguage
>en-US</
</
InputLocale
>0409:00000409</
SystemLocale
UILanguageFallback
UserLocale
"Microsoft-Windows-Setup"
UserData
AcceptEula
>true</
FullName
>1AdminAccount</
Organization
>1Organization</
EnableFirewall
EnableNetwork
"offlineServicing"
"Microsoft-Windows-LUA-Settings"
EnableLUA
"generalize"
"Microsoft-Windows-Security-SPP"
SkipRearm
>1</
"specialize"
"Microsoft-Windows-International-Core"
"Microsoft-Windows-Security-SPP-UX"
SkipAutoActivation
"Microsoft-Windows-SQMApi"
CEIPEnabled
>0</
"Microsoft-Windows-Shell-Setup"
ComputerName
>1Name</
ProductKey
>1ProductID</
"Microsoft-Windows-TCPIP"
Interfaces
Interface
wcm:action
"add"
Identifier
>1MacAddressDomain</
Ipv4Settings
DhcpEnabled
>false</
Metric
>10</
RouterDiscoveryEnabled
UnicastIpAddresses
IpAddress
wcm:keyValue
"1"
>1IPDomain/24</
Routes
Route
NextHopAddress
>1DefaultGW</
Prefix
>0.0.0.0/0</
"Microsoft-Windows-DNS-Client"
DNSServerSearchOrder
>1DNSServer</
>1MACAddressDomain</
EnableAdapterDomainNameRegistration
DisableDynamicUpdate
DNSDomain
>1DNSDomain</
UseDomainNameDevolution
"oobeSystem"
OOBE
HideEULAPage
HideOEMRegistrationScreen
HideOnlineAccountScreens
HideWirelessSetupInOOBE
NetworkLocation
>Work</
ProtectYourPC
SkipUserOOBE
SkipMachineOOBE
UserAccounts
LocalAccounts
LocalAccount
Password
Value
>1AdminPassword</
PlainText
>True</
Description
></
DisplayName
Group
>Administrators</
Name
RegisteredOrganization
RegisteredOwner
DisableAutoDaylightTimeSet
TimeZone
>GTB Standard Time</
VisualEffects
SystemDefaultBackgroundColor
>2</
"Microsoft-Windows-ehome-reg-inf"
"x86"
"NonSxS"
RestartEnabled
cpi:offlineImage
cpi:source
"wim:c:/server2016/sources/install.wim#Windows Server 2016 SERVERDATACENTERCORE"
xmlns:cpi
"urn:schemas-microsoft-com:cpi"
/>
#Copy everything above this line and save it as unattend.xml#
The PowerShell code is pretty easy to understand and comments are added in every line for you to fully understand what is going on.
#Arm the Variables, a bunch of them
#Cpu Cores in the VM
$CpuCount=
2
#Ram Size
$RAMCount=
1
GB
#VMName , will also become the Computer Name
$Name=
"Test"
#IP Address
$IPDomain=
"192.168.0.1"
#Defa
ult Gateway to be used
$DefaultGW=
"192.168.0.254"
#DNS Server
$DNSServer=
#DNS Domain Name
$DNSDomain=
"test.com"
#Hyper V Switch Name
$SwitchNameDomain=
"HyperV"
#Set the VM Domain access NIC name
$NetworkAdapterName=
"Public"
#User name and Password
$AdminAccount=
"Administrator"
$AdminPassword=
"P@ssw0rd"
#Org info
$Organization=
"Test Organization"
#This ProductID is actually the AVMA key provided by MS
$ProductID=
"TMJ3Y-NTRTM-FJYXT-T22BY-CWG3J"
#Where's the VM Default location? You can also specify it manually
$Path= Get-VMHost |select VirtualMachinePath -ExpandProperty VirtualMachinePath
#Where should I store the VM VHD?, you actually have nothing to do here unless you want a custom name on the VHD
$VHDPath=$Path + $Name +
"\" + $Name + "
.vhdx"
#Where are the folders with prereq software ?
$StartupFolder=
"C:\ISO"
$TemplateLocation=
"C:\ISO\Template2016.vhdx"
$UnattendLocation=
"C:\ISO\unattend.xml"
#Part
Complete-------------------------------------------------------------------------------#
Initialize---------------------------------------------------------------------------------#
#Start the Party!
#Let
's see if there are any VM'
s with the same name if you actually find any simply inform the user
$VMS=Get-VM
Foreach($VM in $VMS)
{
if ($Name -match $VM.Name)
write-host -ForegroundColor Red
"Found VM With the same name!!!!!"
$Found=$True
}
#Create the VM
New-VM -Name $Name -Path $Path -MemoryStartupBytes $RAMCount -Generation
-NoVHD
#Remove any
auto
generated adapters and add new ones with correct names for Consistent Device Naming
Get-VMNetworkAdapter -VMName $Name |Remove-VMNetworkAdapter
Add-VMNetworkAdapter -VMName $Name -SwitchName $SwitchNameDomain -Name $NetworkAdapterName -DeviceNaming On
#Start and stop VM to get mac address, then arm the new MAC address on the NIC itself
start-vm $Name
sleep
5
stop-vm $Name -Force
$MACAddress=get-VMNetworkAdapter -VMName $Name -Name $NetworkAdapterName|select MacAddress -ExpandProperty MacAddress
$MACAddress=($MACAddress -replace
'(..)'
,
'$1-'
).trim(
'-'
)
get-VMNetworkAdapter -VMName $Name -Name $NetworkAdapterName|Set-VMNetworkAdapter -StaticMacAddress $MACAddress
#Copy the template and add the disk on the VM. Also configure CPU and start - stop settings
Copy-item $TemplateLocation -Destination $VHDPath
Set-VM -Name $Name -ProcessorCount $CpuCount -AutomaticStartAction Start -AutomaticStopAction ShutDown -AutomaticStartDelay
Add-VMHardDiskDrive -VMName $Name -ControllerType SCSI -Path $VHDPath
#Set first boot device to the disk we attached
$Drive=Get-VMHardDiskDrive -VMName $Name | where {$_.Path -eq
"$VHDPath"
Get-VMFirmware -VMName $Name | Set-VMFirmware -FirstBootDevice $Drive
#Prepare the unattend.xml file to send out, simply copy to a new file and replace values
Copy-Item $UnattendLocation $StartupFolder\
"unattend"
$Name
".xml"
$DefaultXML=$StartupFolder+
"\unattend"
+$Name+
$NewXML=$StartupFolder +
"\unattend$Name.xml"
$DefaultXML=Get-Content $DefaultXML
$DefaultXML | Foreach-Object {
$_ -replace
'1AdminAccount'
, $AdminAccount `
-replace
'1Organization'
, $Organization `
'1Name'
, $Name `
'1ProductID'
, $ProductID`
'1MacAddressDomain'
,$MACAddress `
'1DefaultGW'
, $DefaultGW `
'1DNSServer'
, $DNSServer `
'1DNSDomain'
, $DNSDomain `
'1AdminPassword'
, $AdminPassword `
'1IPDomain'
, $IPDomain `
} | Set-Content $NewXML
#Mount the new virtual machine VHD
mount-vhd -Path $VHDPath
#Find the drive letter of the mounted VHD
$VolumeDriveLetter=GET-DISKIMAGE $VHDPath | GET-DISK | GET-PARTITION |get-volume |?{$_.FileSystemLabel -ne
"Recovery"
}|select DriveLetter -ExpandProperty DriveLetter
#Construct the drive letter of the mounted VHD Drive
$DriveLetter=
"$VolumeDriveLetter"
+
":"
#Copy the unattend.xml to the drive
Copy-Item $NewXML $DriveLetter\unattend.xml
#Dismount the VHD
Dismount-Vhd -Path $VHDPath
#Fire up the VM
Start-VM $Name
Complete---------------------------------------------------------------------#