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.

10 thoughts on “XenServer PowerShell – New Windows VM from ISO”

  1. Hi Dan

    Fab little article and just what I was looking for although I’ve never used PS with XS before to deploy VM’s onto XS. Is this faster at deploying a VM than from say a XenServer Template ?

    On a related notes, is there a XD/XA infrastructure deployment template or script for XenServer that would stand up, AD, Storefront, DDC’s and even the VDA like an AWS cloud transformation template ?




    1. XS template works as well. That is how I originally started. My problem is that as this is a lab env, my OS will time out after so many months, which requires me to keep recreating templates. Plus, when new versions of Win10 come out (every 6 months), I don’t want to have to create new templates.

      As for whole deployment scripts, not sure. I know Trond Eirik Haavarstein from https://xenappblog.com/ has an automation framework, but not what it does.


  2. Hey Dan, What settings did you have in your unattended file for 1803 to skip the OOBE. Image manager says the settings are deprecated and for sure thats what I’m seeing. Would love to know how you got around this, its driving me nuts.


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.