Part 1: PowerShell Pre-Reqs, Functions inside Remote Sessions
Part 2: Create a Sysprepped Template & Create a Group of VMs With 1 Click
Part 3: Set a Static IP & Join Machines to the Domain
Part 4: Remotely Install XenDesktop Software
Part 5: Remotely Add StoreFront & Delivery Controllers

If you are beginning with a XenDesktop deployment on Hyper-V and need a Windows Image to be used as a template, this script will help automate its creation. Without System Center Virtual Machine Manager, Hyper-V does not support creating a template to spin up additional virtual machines. The goal of this blog post is to create a Sysprepped Windows 2012 R2 image that will be used as the template with a single click. The Sysprepped image will be replicated by copying and renaming the file to create the machines needed to build out a XenDesktop environment. On a side note, be sure to check out this great Hyper-V Planning Guide blog series from Consulting.

Starting Point: The Pre-Requistes

To begin using the code below, a few items must first be in place:

  • A physical server with the Hyper-V role installed and configured.
  • The scripts below assumes a virtual switch on Hyper-V named “InternalNetwork” is available.
  • An Active Directory deployment with DHCP/DNS must be in-place.
  • The script is meant to be executed locally from a Hyper-V server.
  • Besides that, no other resources such as System Center Virtual Machine Manager or SQL Server are assumed to be already built.

High Level PowerShell Overview

The following steps will are detailed below:

  1. Create a Windows 2012 R2 Image from an ISO. Use an autounatended.xml file to allow the installation to happen without any user intervention.
  2. Once Windows is installed, the machine will receive a temporary IP address from DHCP. This will allow remote access for PowerShell.
  3. The newly created Windows VM will then be Sysprepped using an unattend.xml file and then shutdown.
  4. Once the VM is shutdown, it will be replicated by coping and renaming the file based on the VMs that need to be created.

Define VMs to be Created

To define what VMs should be created, a multi-dimensional array of the VMs will be passed to the script. Each VM in the array is separated by parentheses and includes a set of values. Below are what each value in the array correlate to:

  • LogicalName: The name the VM will be given in Hyper-V. The value will be used to reference the VM in later parts of the script.
  • HostName: The Hostname that will be used inside Windows.
  • Static IP: The static IP address that will be given to the VM.
  • VM Location: The Hyper-V server hostname where the VM is located.
  • vCPUs: The number of Virtual CPUs the VM should be given.
  • Memory: The amount of memory that should be given to the VM.

$VMs =@ (‘DeliveryController1′,’XDC-01′,’′,’server1′,’2′,’4’),(‘DeliveryController2′,’XDC-02′,’′,’server1′,’2′,’4’),(‘SQLServer1′,’XDSQL-01′,’′,’server1′,’2′,’4’)

Copy Function

This function was created since the native “copy-item” command in PowerShell does not support the Credential parameter. This means that the account running the script will be used when copying files to a remote machine. To get around this limitation, the “net use” command is used to authenticate with the remote machine. This allows the copy-item to use the credentials specified to authenticate to the network share on the remote host.

$username = "administrator"
$password = "mypassword"

function copy_remote($source, $dest) {
    net use $dest $password /USER:$username
    Copy-Item -Path $source -Destination $dest –Recurse -Force
    net use * /delete /y

VM State Function

This function will be used to verify if a VM on Hyper-V is powered On or Off. In this case, it will be used to verify that the sysprepped VM is powered off before it is copied.

function vm_state($a, [ref]$vmstate) {
    $vmstate.value =  (Get-VM –name $a).State

Function To Determine VM IP

Since using PowerShell Remoting will be used to perform the Sysprep operation, the DHCP IP address of the Windows must be know. The function below will query the Hyper-V server and loop until it returns a valid IPv4 address.

function network_online($a, $b, [ref]$ipv4) {

  # Loop until VM has a real IP address
    $VMIP = (Get-VMNetworkAdapter -VMName $b -Computer $a).IpAddresses
    write-host "." -NoNewline -ForegroundColor "Red"
    Start-Sleep -s 2
  } until (($VMIP -ne $null) -AND ($VMIP[0] -notmatch "169."))

  $ipv4.value = $VMIP[0]

Convert Memory Size Function

In the $VMs array discussed earlier, one of the values assigned is the memory size in GB that we want to give each VM . To convert this value so that it is readable to the PowerShell code that creates the VM, this PowerShell function found here will be used. It allows the value to be easily converted to the memory size that will be used.

function Convert-Memsize ( [string] $memsize ) {
    $memsize = $memsize.ToUpper()
    $result = 0
    switch ($memsize.ToUpper().Substring($memsize.Length-2))
        "KB" { $result = [Int64]::Parse($memsize.Replace("KB",""))*1024 }
        "MB" { $result = [Int64]::Parse($memsize.Replace("MB",""))*1024*1024 }
        "GB" { $result = [Int64]::Parse($memsize.Replace("GB",""))*1024*1024*1024 }
        "TB" { $result = [Int64]::Parse($memsize.Replace("TB",""))*1024*1024*1024*1024 }
        default { return $memsize }
    return [int64] $result

Create the Initial VM

The function below creates a VM named WindowsMaster which will be used as the template for all the VMs in the XenDesktop build. Using an autounattended.xml file such as this one placed on a virtual floppy file, Windows will be allowed to complete it’s installation without any under intervention. This file sets items such as the partition size and local administrator password.

function initial_vm() {
# Variables
$SRV1 = "WindowsMaster"        # Name of VM
$SRAM = 4GB                                # RAM assigned
$SRV1VHD = 60GB                                # Size of Hard-Drive
$VMLOC = "C:\VMs"                    # Location of the VM and VHDX files
$NetworkSwitch1 = "InternalNetwork"    # Name of the Network Switch
$WSISO = "C:\Deploy\ISOs\windows2012_r2.iso"            # Windows Server 20012 R2 ISO
$WSVFD = "C:\Deploy\unattend\unattend.vfd"    # Windows Server 20012 R2 Virtual Floppy Disk with autounattend.xml file

# Create VM Folder and Network Switch
MD $VMLOC -ErrorAction SilentlyContinue
$TestSwitch = Get-VMSwitch -Name $NetworkSwitch1 -ErrorAction SilentlyContinue; if ($TestSwitch.Count -EQ 0){New-VMSwitch -Name $NetworkSwitch1 -SwitchType External}

# Create Virtual Machines
New-VM -Name $SRV1 -Path $VMLOC -MemoryStartupBytes $SRAM -NewVHDPath $VMLOC\$SRV1.vhdx -NewVHDSizeBytes $SRV1VHD -SwitchName $NetworkSwitch1

# Configure Virtual Machines
Set-VMDvdDrive -VMName $SRV1 -Path $WSISO
Set-VMFloppyDiskDrive -VMName $SRV1 -Path $WSVFD
Start-VM $SRV1
Write-Host "VM Installation Beginning ..."

Sysprep the VM

Once the WindowsMaster VM installation has completed, the VM must be Sysprepped before it is copied to be used by the other machines that will makeup the XenDesktop environment. This function begins by waiting until the WindowsMaster VM receives an IP address from DHCP and then returns the value to the $IPv4 variable. Using that IP address, the function copies a Sysprep unattend.xml file to the local and then performs the Sysprep operation. The unattend.xml file include information such as giving the machine a random hostname. Once the Sysyprep operation is completed, the VM will be shutdown.

function sysprep() {

   #Set Variable
   Set-Variable -Name ipv4

    Sleep 5
    #Call Function to Determine if VM is Online and then determine it's IP Address
   Write-host "Waiting for VM to Finish Installation and Shutdown ..."

    # Return the VM's IP Address
    network_online server1 WindowsMaster ([ref]$ipv4)
    write-host "VMIP: $IPv4 ..."

    Sleep 40
    # Copy Unattended File
    copy_remote c:\Deploy\unattend\unattend.xml -Destination \\$ipv4\c$\unattend.xml
    Write-Host Starting Sysprep Process...

    # Execute Sysprep on Template VM
    Invoke-Command -ComputerName $ipv4 -credential $cred -ScriptBlock {

    # Install .NET 3.5 (Needed later by SQL Server)
    dism /online /enable-feature /featurename:NetFX3 /all /Source:d:\sources\sxs /LimitAccess

    Start-Process -FilePath "c:\windows\system32\sysprep\sysprep.exe" -Wait -ArgumentList "/oobe /generalize /shutdown /unattend:c:\unattend.xml"

Create VMs Functions

Now that we have Sysprepped Windows machine, we can simply copy the VHDX file and rename it to be used for the machines that will be part of the XenDesktop deployment. The following function begins with waiting for the WindowsMaster VM to be shutdown and then proceeds to loop through the $VMs array and creates a copy of the WindowsMaster.vhdx file for each VM in the array. After the copy is created, a new VM on Hyper-V is created using the Sysyprepped Windows image which has been copied. Finally, the vCPUs for each VM are specified and the VM is set to start.

 function create_vms() {

  Set-Variable -Name vmstate
  # Call function to determine VMs state
  vm_state WindowsMaster ([ref]$vmstate)
  write-host state: $vmstate

  # Loop to Wait for WindowsMaster VM to Shutdown
    do {
    write-host "." -NoNewline -ForegroundColor "Red"
    vm_state WindowsMaster ([ref]$vmstate)
    Start-Sleep -s 2
  } while ($vmstate -ne "Off")

  #Return the VM state
  write-host $vmstate
  Sleep 5

   $NetworkSwitch1 = "InternalNetwork"  # Name of the Network Switch

  # Copy Syspreped VM
  foreach ($z in $VMs)

    "Creating VHD for $($z[0]) VM ...`n"
    Copy-Item -Path "c:\VMs\WindowsMaster.vhdx" -Destination "\\$($z[3])\c$\VMs\$($z[0]).vhdx"


  # Create Virtual Machines
  foreach ($z in $VMs)
    $memory = Convert-Memsize -memsize "$($z[5])GB"

    "Creating $($z[0]) VM ..."
    New-VM -Name $z[0] -ComputerName $z[3] -MemoryStartupBytes $memory -VHDPath "c:\VMs\$($z[0]).vhdx" -SwitchName $NetworkSwitch1

  # Set vCPUs
  foreach ($z in $VMs)
    SET-VMProcessor –VMName $z[0] –count $z[4] -ComputerName $z[3]

  # Start the VMs
  foreach ($z in $VMs)
    Start-VM $z[0] -ComputerName $z[3]
    "Starting $($z[0]) VM ..."
    Sleep 80

  Sleep 200

Executing the Functions

Now that all our functions are created, we can set them to run sequentially by calling the individual functions at the end of the script:

# Create Initial Template VM

# Create the Sysprep Master Template

# Create the Virtual Machines

Depending on what VMs are entered in the array, the VMs will appear in Hyper-V as shown below. Of course the NetScaler VMs are not created from the sysyprepped Windows machine. I’ll leave it to you to figure out how to spin up the NetScaler machine with the same type of method.

Roger LaMarca – Senior Consultant
Worldwide Consulting
Virtual Desktop Handbook
Project Accelerator