When you have few Hyper-V hosts and when you have not the System Center products, you have to deploy your nodes from a USB stick or from PXE services such as WDS. These deployment methods often imply manual steps to configure the operating system. However, it is possible to automate most of the steps thanks to deployment tools (part of ADK) and some PowerShell scripts. This topic shows you how I deploy Hyper-V from USB stick with unattended file and scripts.

Requirements

To follow this topic, you need Windows Assessment and Deployment Kit (ADK). To make this topic, I have deployed the following feature:

You need also the ISO of Windows Server 2016. I have copied the install.wim (folder Sources of the ISO) on my local drive.

Prepare the USB stick

To prepare the USB stick, I use Rufus. Once you have run the tool, just select your ISO file, the USB Stick and the boot mode (Bios or UEFI). This topic is based on an UEFI configuration.

Prepare the unattended file

Once you have installed deployment tools, you can open it from start menu. Then click file | select Windows Image.

Next, browse your local drive to select install.wim.

Select the edition you want. For Hyper-V, I choose Windows Server 2016 Datacenter in Core edition.

Next create an answer file as below.

Select amd64_Microsoft-Windows-International-Core-WinPE (documentation here), and place it in 1. WindowsPE. Then specify language settings:

InputLocale : Keyboard layout

: Keyboard layout SystemLocale : system locale language

: system locale language UILanguage : User Interface language

: User Interface language UserLocale: per-user settings for currency, time, numbers and so on

Next navigate to SetupUILanguage to specify the language used during Windows Setup.

Next, select amd64_Microsoft-Windows-Setup (documentation here) and place it in 1. WindowsPE.

Navigate to DiskConfiguration and right click on it and select add disk.

To create a partition, right click on CreatePartitions and select insert New CreatePartition.

To make the below configuration, I have followed this guide of Microsoft (because I have an UEFI):

Microsoft-Windows-Setup\DiskConfiguration\Disk DiskID = 0

WillWipeDisk = true Microsoft-Windows-Setup\DiskConfiguration\Disk\ CreatePartitions\CreatePartition Order = 1

Size = 300

Type = Primary Microsoft-Windows-Setup\DiskConfiguration\Disk\ CreatePartitions\CreatePartition Order = 2

Size = 260

Type = EFI Microsoft-Windows-Setup\DiskConfiguration\Disk\ CreatePartitions\CreatePartition Order = 3

Size = 128

Type = MSR Microsoft-Windows-Setup\DiskConfiguration\Disk\ CreatePartitions\CreatePartition Extend = true

Order = 4

Type = Primary Microsoft-Windows-Setup\DiskConfiguration\Disk\ ModifyPartitions\ModifyPartition Format = NTFS

Label = WINRE

Order = 1

PartitionID = 1 Microsoft-Windows-Setup\DiskConfiguration\Disk\ ModifyPartitions\ModifyPartition Format = FAT32

Label = System

Order = 2

PartitionID = 2 Microsoft-Windows-Setup\DiskConfiguration\Disk\ ModifyPartitions\ModifyPartition Order = 3

PartitionID = 3 Microsoft-Windows-Setup\DiskConfiguration\Disk\ ModifyPartitions\ModifyPartition Format = NTFS

Label = HostOS

Letter = C

Order = 4

PartitionID = 4 Microsoft-Windows-Setup\ImageInstall\OSImage\InstallTo DiskID = 0

PartitionID = 4

In a single install.wim, there are multiple Windows images. So, we have to specify from which image Windows will be deployed. It is a little tricky but we can retrieve this information with the following dism command:

Dism /Get-ImageInfo /ImageFile:<Path/to/install.wim>

Thanks to this command, we can list images in install.wim and the index. In the below screenshot, the Windows Server 2016 Datacenter in Core edition is the index 3.

So, in InstallFrom node, I add the following metadata:

Key: /IMAGE/INDEX

Value: 3

Next navigate to ProductKey node (in UserData) and specify a product key.

In UserData node, set AcceptEula to true and provide FullName and Organization name.

Next add amd64_Microsoft-Windows-Shell-Setup (documentation here) to specialize and oobeSystem section.

In Specialize section, specify the timezone and the computer name. You can find here the allowed timezone format.

Move to oobeSystem section. In UserAccounts | AdministratorPassword node, specify an Administrator password as below

In the above screenshot, I configure an Auto Logon that will run 5 times. I make this configuration because later, I’ll run a PowerShell script with some reboots. We will see that in the next section.

In FirstLogonCommands, I add a SynchronousCommand. I specify the script that will customize my operating system. This cmd script calls a PowerShell script. We will see that in the next section.

Next add amd64_Microsoft-Windows-TerminalServices-LocalSessionManager (documentation here). I set fDenyTSConnections to false to enable the RDP connection.

Once you have finished to edit the answer file, you can save it on USB stick. You must name it autounattend.xml.

Post deployment scripts

In the USB Stick, I create a folder called Deploy. In this folder I have also created a folder called Binaries and Agent. In binaries, I have copied the drivers (as Dell, Mellanox and so on). In Deploy folder I have the following script:

ConfigureOS.cmd

Autodeploy.ps1

NodeConfiguration.xml

ConfigureOS.cmd

This script creates c:\temp\Deploy folder and copy the content of Deploy from USB stick to c:\temp\Deploy. Then the script AutoDeploy.ps1 is run.

mkdir c:\temp\Deploy xcopy D:\Deploy\* C:\temp\Deploy /H /F /E PowerShell -file c:\temp\deploy\AutoDeploy.ps1

NodeConfiguration.xml

This XML file contains network configurations (IP Address, netmask, Active Directory and so on). This XML is called in AutoDeploy.ps1.

<?xml version="1.0" encoding="UTF-8" /> <NodeConfiguration> <Network vSwitchName="SW-1G"> <Management name="MGMT-22" ipaddr="10.138.22.12" netmask="24" gateway="10.138.22.1" dns="10.139.16.15,10.138.23.15" type="Untagged" vlanid=""/> <Cluster name="Cluster-100" ipaddr="192.168.100.12" netmask="24" type="Access" vlanid="100"/> </Network> <ActiveDirectory name="homecloud.net" /> </NodeConfiguration>

AutoDeploy.ps1

The AutoDeploy.ps1 script install drivers, features, agent, configure networks, MPIO and join Active Directory. A scheduled task is created to run the script after each reboot. The script knows where it is after each reboot thanks to step file. The script reads the step file and regarding the value, it runs a part of the script.

# Variables $DeployPkg = "C:\temp\Deploy" $ScriptDir = Split-Path $script:MyInvocation.MyCommand.Path $StepFile = $($DeployPkg + "\step.cfg") $DellPkg = $DeployPkg + "\Binaries\Dell\suu.cmd" $XMLPath = $DeployPkg + "\NodeConfiguration.xml" if ((Test-Path $XMLPath) -like $False){ Write-Host "Can't find the XML file located to $XMLPath. Exiting" -ForegroundColor Red Exit } # Get XML content $XML = Get-Content $XMLPath Write-Host "The AutoDeploy script is located in $ScriptDir" -ForegroundColor Green Write-Host "The step file is $StepFile" -ForegroundColor Green #### INITIALIZATION OF STEP FILE #### if ((Test-Path $StepFile) -like $False){ Write-Host "The step file doesn't exist. Creating it with 0 value" -ForegroundColor Green Set-Content -LiteralPath $StepFile -Value 0 } # Get Step $Step = Get-Content $StepFile Write-Host "Current Step: 0. Deploying Dell Drivers and firmware" -ForegroundColor Green #### DELL DRIVERS INSTALLATION R630 + FIRMWARE #### if ($Step -like 0){ # Set a schedule task to run this script on each OS start $action = New-ScheduledTaskAction -Execute 'Powershell.exe' -Argument '-command "& C:\temp\Deploy\Autodeploy.ps1"' $trigger = New-ScheduledTaskTrigger -Atlogon Register-ScheduledTask -Action $action -Trigger $trigger -TaskName "AutoDeploy" -Description "Server Deployment" # Test if dell drivers are found if ((Test-Path $DellPkg) -like $False){ Write-Host "Can't find Dell package. Please update the variable DellPkg. Exiting." -ForegroundColor Red Exit } #Change the Power schema to high performance Write-Host "Change power schema to high performance" -ForegroundColor Green POWERCFG.EXE /S SCHEME_MIN #Change the value of step file Set-Content -LiteralPath $StepFile -Value 1 # Driver installation cmd /c "$DellPkg -e" Restart-Computer } #### ROLE AND FEATURE INSTALLATION #### if ($Step -like 1){ Write-Host "Installing the current Windows Roles and Features:" -ForegroundColor Green Write-Host " - Hyper-V + PowerShell cmdlets" -ForegroundColor Blue Write-Host " - Failover Clustering + PowerShell cmdlets" -ForegroundColor Blue Write-Host " - MPIO" -ForegroundColor Blue Write-Host " - Active Directory PowerSHell cmdlets" -ForegroundColor Blue Install-WindowsFeature Hyper-V, Failover-Clustering, MultiPath-IO, RSAT-CLustering-Powershell, Hyper-V-PowerShell, RSAT-AD-PowerShell Set-Content -LiteralPath $StepFile -Value 2 Restart-Computer -Force } #### NETWORK CONFIGURATION, MPIO AND AD #### if ($Step -like 2){ $SwitchName = $XML.NodeConfiguration.Network.vSwitchName $MGMTNicName = $XML.NodeConfiguration.Network.Management.Name $MGMTNicIP = $XML.NodeConfiguration.Network.Management.ipaddr $MGMTNicMask = $XML.NodeConfiguration.Network.Management.netmask $MGMTNicGW = $XML.NodeConfiguration.Network.Management.gateway $MGMTNicDNS = $XML.NodeConfiguration.Network.Management.dns $MGMTNicvType = $XML.NodeConfiguration.Network.Management.type $MGMTNicVlan = $XML.NodeConfiguration.Network.Management.vlanid $ClusNicName = $XML.NodeConfiguration.Network.Cluster.Name $ClusNicIP = $XML.NodeConfiguration.Network.Cluster.ipaddr $ClusNicMask = $XML.NodeConfiguration.Network.Cluster.netmask $ClusNicvType = $XML.NodeConfiguration.Network.Cluster.type $ClusNicVlan = $XML.NodeConfiguration.Network.Cluster.vlanid # Creating vSwitch (SET) Write-Host "Creating Switch Embedded Teaming (name: $SwitchName) vSwitch with all physical NIC" -ForegroundColor Green New-VMSwitch -Name $SwitchName -NetAdapterName NIC1, NIC2, NIC3, NIC4 -EnableEmbeddedTeaming $True -AllowManagementOS $False > $Null # Creating vNIC Management Write-Host "Creating Management NIC (NIC name: $MGMTNicName)" -ForegroundColor Green Add-VMNetworkAdapter -SwitchName $SwitchName -ManagementOS -Name $MGMTNicName if ($MGMTNicvType -like "Untagged"){ Write-Host "Configure vNIC $MGMTNicName to untagged" -ForegroundColor Green Set-VMNetworkAdapterVLAN -ManagementOS -VMNetworkAdapterName $MGMTNicName -Untagged } Elseif ($ClusNicvType -like "Access"){ Write-Host "Configure vNIC $MGMTNicName to tagged (VID: $MGMTNicVlan)" -ForegroundColor Green Set-VMNetworkAdapterVLAN -ManagementOS -VMNetworkAdapterName $MGMTNicName -Access -VlanId $MGMTNicVlan } # Creating vNIC Cluster (Hearbeat + Live-Migration) Write-Host "Creating Cluster NIC (NIC name: $ClusNicName)" -ForegroundColor Green Add-VMNetworkAdapter -SwitchName $SwitchName -ManagementOS -Name $ClusNicName Write-Host "Tagging the $ClusNicName NIC" -ForegroundColor Green if ($ClusNicvType -like "Untagged"){ Write-Host "Configure vNIC $ClusNicName to untagged" -ForegroundColor Green Set-VMNetworkAdapterVLAN -ManagementOS -VMNetworkAdapterName $ClusNicName -Untagged } Elseif ($ClusNicvType -like "Access"){ Write-Host "Configure vNIC $ClusNicName to tagged (VID: $ClusNicVlan)" -ForegroundColor Green Set-VMNetworkAdapterVLAN -ManagementOS -VMNetworkAdapterName $ClusNicName -Access -VlanId $ClusNicVlan } # Disable VMQ because 1GB NIC Write-Host "Disabling VMQ on all NICs" -ForegroundColor Green Disable-NetAdapterVMQ -Name * # Enable Jumbo Frame on all NICs Write-Host "Enabling JumboFrame on all vNICs" -ForegroundColor Green Get-NetAdapterAdvancedProperty -Name * -RegistryKeyword "*jumbopacket" | Set-NetAdapterAdvancedProperty -RegistryValue 9014 # Set IP addresses Write-Host "Set IP Address on vNICs $MGMTNicName ($MGMTNicIP/$MGMTNicMask, GW: $MGMTNicGW)" -ForegroundColor Green New-NetIPAddress -InterfaceAlias "vEthernet ($MGMTNicName)" -IPAddress $MGMTNicIP -PrefixLength $MGMTNicMask -Type Unicast -DefaultGateway $MGMTNicGW | Out-Null Write-Host "Set DNS on $MGMTNicName (DNS: $MGMTNicDNS)" -ForegroundColor Green Set-DnsClientServerAddress -InterfaceAlias "vEthernet ($MGMTNicName)" -ServerAddresses $MGMTNicDNS | Out-Null Write-Host "Set IP Address on vNICs $ClusNicName ($ClusNicIP/$ClusNicMask)" -ForegroundColor Green New-NetIPAddress -InterfaceAlias "vEthernet ($ClusNicName)" -IPAddress $ClusNicIP -PrefixLength $ClusNicMask -Type Unicast | Out-Null #Disable DNS registration of Storage and Cluster network adapter Write-Host "Disabling register in DNS for $ClusNicName" -ForegroundColor Green Set-DNSClient -InterfaceAlias *$ClusNicName* -RegisterThisConnectionsAddress $False #### CONFIGURE MPIO #### Write-Host "Activate SAS claim for MPIO" -ForegroundColor Green Enable-MSDSMAutomaticClaim -BusType SAS #### ADD TO DOMAIN #### Write-Host "Add computer to the domain $($XML.NodeConfiguration.ActiveDirectory.name)" -ForegroundColor Green New-MSDSMSupportedHW -allApplicable $Credential = Get-Credential -Message "Credential for $($XML.NodeConfiguration.ActiveDirectory.name)" Add-Computer -DomainName $($XML.NodeConfiguration.ActiveDirectory.name) -Credential $Credential Set-Content -LiteralPath $StepFile -Value 3 Restart-Computer } #### INSTALLATION AGENT #### if ($Step -like 3) { # Add here the Agent installation ################################ Set-Content -LiteralPath $StepFile -Value 4 Restart-Computer } #### REMOVE AUTOLOGON , CHANGE ADMIN PWD #### if ($Step -like 4){ Write-Host "Delete automatic run script at startup" -ForegroundColor Green Unregister-ScheduledTask AutoDeploy Restart-Computer }

Conclusion

This topic shows you an example of how we can automate the Hyper-V host’s deployment with free tools. If you have not System Center, you can use this method. With automation, you can get a consistent deployment more easily than manual deployment.