XenServer PowerShell – New Windows VM from ISO

I tend to build many Windows VMs on XenServer and want a way to automate as much as possible.

I want to avoid using Sysprep.  I’m getting a Windows 10 ISO from Microsoft every 6 months (semi-annual release).  If I go with Sysprep, I have to manually create the VM, sysprep it, turn it into a template and finally create my VM.  Plus, how many times can I sysprep before I run into license issues and sysprep count?

Seems like a lot of waste and trouble.

Instead, I want to automatically build a VM from a newly downloaded ISO file. The challenge is I need to have an unattend.xml file within the ISO. Let’s see how to accomplish that.

Before I begin, I need the following items:

  • Unattend.xml for Windows 2016
  • Unattend.xml for Windows 10
  • ETFSBoot.com (from Windows ISO .\boot\etfsboot.com)
  • OSCDIMG.exe (from Windows Assessment and Deployment Kit C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Deployment Tools\amd64\Oscdimg)
  • XenServer template for Windows 2016 (Blank template with CPU, RAM, Network and Storage allocated)
  • XenServer template for Windows 10 (Blank template CPU, RAM, Network and Storage allocated)
  • Windows 2016 ISO expanded to a folder
  • Windows 10 ISO expanded to a folder
  • XenServer PowerShell installed

The first part are all of my parameters

# Parameters

# Name of virtual machines.  Can be more than one, separated by comma
$VMNames = @("Win10-718", "Win10-717")

# PowerShell script to use after OS install completes. Need 1 entry per VMName
$DeploymentScript = @("\\dc1\Automation\VMBuild\InstallVSITarget.ps1", "\\dc1\Automation\VMBuild\InstallVSITarget.ps1")

# Folder to expanded ISO file.
$ImagePath = @("\\media\iso\Win10-1803-Image","\\media\iso\Win10-1803-Image")

# XenServer template with CPU, Memory, network and disk allocated. The disk is empty
$SourceTemplateName = "XS Template - Windows 2016(Blank)"

# Path to the log file
$Logfile = "\\dc1\Automation\VMBuild\VMs"

# Tools used to build an ISO file
$BootFile = "\\dc1\Automation\Software\Oscdimg\etfsboot.com"
$ISOTool = "\\dc1\Automation\Software\Oscdimg\oscdimg.exe"

# Active Directory OU to place the VM during the build
$OULocation = "OU=AutoBuildOU,DC=snpp,DC=local"

# XenServer master, user and password
$XenServerHost = "XS2.snpp.local"
$UserName = "root"
$Password = "Chuck Norris"

# Path to the unattended XML file for the OS
$UnattendTemplate = "\\dc1\Automation\VMBuild\AutoUnattendWin2016.xml"

# Unattend.xml variables
$AutoLogonPW = "Chuck Norris"
$AutoLogonAcct = "AutoLogon"
$DomainJoinAcct = "AutoLogon"
$DomainJoinPW = "AutoLogon"
$Domain = "SNPP"
$FQDN = "SNPP.local"
$LocalAdminPW = "Chuck Norris"
$WinProductKey = "xxxxx-xxxxx-xxxxx-xxxxx-xxxxx"

I have 2 functions that helps with my log file

The first simply gets a data/timestamp for each log entry

function Get-TimeStamp {
    return "[{0:MM/dd/yy} {0:HH:mm:ss}]" -f (Get-Date)

The second is the log entry. It posts to the screen and to a text file (one for each VM being created)

Function Write-ScreenLog {
    Param ($textstring)

    write-host "$(get-timestamp) : $textstring" -foregroundcolor yellow
    Add-Content -Value "$(Get-timestamp): $textstring" -Path $Logfile\$VMName.txt

Now, we create a VM, 1 loop for each defined VMName


foreach ($VMName in $VMNames) {
    #Prepare Log file. If file does not exist, create. Else, if file exists from previous build, delete and create new file
    If((Test-Path $Logfile\$VMName.txt) -eq $false) {
        New-Item $Logfile\$VMName.txt -type file -force
    else {
        Remove-item $Logfile\$VMName.txt
        New-Item $Logfile\$VMName.txt -type file -force

    Write-ScreenLog "#############################"
    Write-ScreenLog "Creating new VM from ISO file"
    Write-ScreenLog "#############################"

    # Connect to XenServer Pool Master
    Write-ScreenLog "Connecting to XenServer host: $XenServerHost"
    $session = Connect-XenServer -Server $XenServerHost -UserName $UserName -Password $Password -NoWarnCertificates -SetDefaultSession -PassThru

    # Create XenServer VM from blank template
    Write-ScreenLog "Scheduling creation of vm '$VMName' from template '$SourceTemplateName'"
    # Get the source template
    $objSourceTemplate = Get-XenVM -Name $SourceTemplateName
    # Make a copy of the blank template
    Invoke-XenVM -NewName $VMName -VM $objSourceTemplate -XenAction Copy
    # Provision the copy into a VM
    Invoke-XenVM -XenAction Provision -Name $VMName

    # Copy template unattend.xml file to the expanded ISO path
    $ISOPath = $ImagePath[$i]
    Copy-Item $UnattendTemplate -Destination $ISOPath"\AutoUnattend.xml"

    #Customize unattend.xml
    $DefaultXML=Get-Content $ISOPath"\AutoUnattend.xml"
    $DefaultXML | Foreach-Object {
        $_ -replace '1DomainJoinAcct', $DomainJoinAcct `
        -replace '1DomainJoinPW', $DomainJoinPW `
        -replace '1AutoLogonAcct', $AutoLogonAcct `
        -replace '1AutoLogonPW', $AutoLogonPW `
        -replace '1OULocation', $OULocation `
        -replace '1ComputerName', $VMName `
        -replace '1Domain', $Domain `
        -replace '1FQDN', $FQDN `
        -replace '1LocalAdminPW', $LocalAdminPW `
        -replace '1ProductKey', $WinProductKey `
        -replace '1DeploymentScript', $DeploymentScript[$i]
    } | Set-Content $ISOPath"\AutoUnattend.xml"
    Write-ScreenLog "Deployment Script is $DeploymentScript[$i]"

    # Create Custom ISO file VM. This turns the folder that contains the ISO and unattend into a new ISO file
    Write-ScreenLog "Creating bootable ISO called \\media\iso\$VMName.ISO"
    $ISOArg = "-b$bootfile -u2 -h -m -l$VMName $ISOPath \\media\iso\$VMName.iso"
    Start-Process -FilePath $ISOTool -ArgumentList $ISOArg -PassThru -wait

    # Mount Customized OS ISO to VM
    Write-ScreenLog "Mounting customized ISO to $VMName"
    # Get the VM, select the virtual disks that are CD, attach the ISO
    get-xenvm -Name $VMName | select -ExpandProperty VBDs | Get-XenVBD | where {$_.type -eq "CD"} | Invoke-XenVBD -XenAction Insert -VDI (Get-XenVDI -Name $VMName".iso" | Select -ExpandProperty opaque_ref)

    #Start VM
    $VM = Get-XenVM -Name $VMName
    Write-ScreenLog "Scheduling power on of VM '$VMName'"
    Invoke-XenVM -VM $VM -XenAction Start -Async
    $i = $i + 1

Write-ScreenLog "VM creation complete"

A few important things to point out.

I need to customize the Unattend.xml. I start with a template for Windows 10 and another one for Windows 2016. A few entries within the .XML file are variables used to customize the file from the PowerShell script. These include:

  • 1DomainJoinPW
  • 1AutoLogonAcct
  • 1AutoLogonPW
  • 1OULocation
  • 1ComputerName
  • 1DeploymentScript
  • 1Domain
  • 1FQDN
  • 1LocalAdminPW
  • 1ProductKey

The second part is to incorporate this customized Unattend.xml into an bootable ISO file.  Using ETFSBoot and OSCDIMG, I take my folder containing the expanded ISO and unattend.xml and create a customized, bootable ISO file, which I later attach to the VM. The ISO has the same name as the VM because we might have multiple VMs building simultaneously.

When Windows 10 1803 released, I was able to grab the ISO and immediately create a custom VM.


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )


Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.