How to Create a Hyper-V VM from an ISO using PowerShell

Table of contents

Today I have another article that continues my fascination with Hyper-V, PowerShell and spinning up new virtual machines with minimal intervention.

During a recent conference, fellow PowerShell MVP Aleksandar Nikolic did a presentation on creating a Hyper-V test lab. One of his demonstrations used a tool I had forgotten about and sparked an idea for a PowerShell script. My script will create a new Hyper-V virtual machine based on an ISO file for a Windows Server. Most of Microsoft’s installation media can be downloaded as ISO images from MSDN or TechNet. Or you might download an evaluation installation ISO of a new operating system so that you can test it out, such as Windows Server 2012. This is a great reason to use a Hyper-V virtual machine.

My script relies on a PowerShell script which you can freely download from Microsoft called Convert-WindowsImage.ps1. The script was written by Microsoft and is designed to create a bootable VHD or VHDX file from an ISO installation image. You can download the script from http://gallery.technet.microsoft.com/scriptcenter/Convert-WindowsImageps1-0fe23a8f#content. It will require PowerShell 3.0. I’m not going to go into the details of the script except to explain that it will extract a server version from the ISO file, create a VHD or VHDX file based on the installation media and insert an unattend.xml file. With a little planning you could create a system for cranking out new virtual machines based on a variety of unattend.xml files. You could even generate or modify the XML files on the fly. But that is beyond what I want to demonstrate in this article.

First, let me show you my script.

#requires -version 3.0

<#
  Create a Hyper-V virtual machine from an 
  ISO file and template
  This script requires the Convert-WindowsImage.ps1 script which you can
  download from Microsoft:
  http://gallery.technet.microsoft.com/scriptcenter/Convert-WindowsImageps1-0fe23a8f#content
#>

[cmdletbinding(SupportsShouldProcess)]
Param(
[Parameter (Position = 0,Mandatory,
HelpMessage = "Enter the name of the new virtual machine")]
[ValidateNotNullorEmpty()]
[string]$Name,

[ValidateScript({Test-Path $_ })]
[string]$ISOPath = 'F:9200.16384.WIN8_RTM.120725-1247_X64FRE_SERVER_EVAL_EN-US-HRM_SSS_X64FREE_EN-US_DV5.ISO',
[ValidateNotNullorEmpty()]
[string]$Edition = "ServerDatacenterEvalCore",

[ValidateScript({$_ -ge 10GB})]
[int64]$Size = 20GB,

[ValidateScript({Test-Path $_ })]
[string]$Unattend = "F:VHDunattend.xml",

[ValidateNotNullorEmpty()]
[string]$VHDPath = "F:VHDWin2012DatacenterEvalCore.vhdx",

[ValidateScript({$_ -ge 256MB})]
[int64]$Memory = 1GB,

[ValidateNotNullorEmpty()]
[string]$Switch = "Work Network",

[ValidateScript({$_ -ge 1})]
[int]$ProcessorCount = 2
)

#region Convert ISO to VHD
Write-Verbose "Converting ISO to VHD(X)"

#parse the format from the VHDPath parameter value
[regex]$rx = ".VHD$|.VHDX$"
#regex pattern is case-sensitive
if ($vhdpath.ToUpper() -match $rx) {
    #get the match and strip off the period
    $Format = $rx.Match($vhdpath.toUpper()).Value.Replace(".","")
}
else {
    Throw "The extension for VHDPath is invalid. It must be .VHD or .VHDX"
    Return
}

#define a hashtable of parameters and values for the Convert-WindowsImage

$convertParams = @{
SourcePath = $ISOPath
SizeBytes = $size
UnattendPath = $Unattend 
Edition = $Edition 
VHDFormat = $Format
VHDPath = $VHDPath
ErrorAction = 'Stop'
}

Write-Verbose ($convertParams | Out-String)

#define a variable with information to be displayed in WhatIf messages
$Should = "VM $Name from $ISOPath to $VHDPath"

#region -Whatif processing
If ($pscmdlet.ShouldProcess($Should)) {
    Try {
        #call the convert script splatting the parameter hashtable
        c:scriptsConvert-WindowsImage.ps1 @convertParams
    }
    Catch {
        Write-Warning "Failed to convert $ISOPath to $VHDPath"
        Write-Warning $_.Exception.Message 
    }
} #should process
#endregion

#endregion

#region Creating the virtual machine
Write-Verbose "Creating virtual machine $Name"
Write-Verbose "VHDPath = $VHDPath"
Write-Verbose "MemoryStartup = $Memory"
Write-Verbose "Switch = $Switch"
Write-Verbose "ProcessorCount = $ProcessorCount"

New-VM -Name $Name -VHDPath $VHDPath -MemoryStartupBytes $Memory -SwitchName $Switch |
Set-VM -DynamicMemory -ProcessorCount $ProcessorCount -Passthru

Write-Verbose "New VM from ISO complete"
#endregion

The script takes values for the virtual machine name and the path to the ISO file. I’ve set a default value based on an ISO file that I use. Because Windows Server installation files can contain multiple images, you need to specify the edition you wish to convert. You also need to specify the name and path for the VHD or VHDX file, including the extension. The script needs the extension so it knows what kind of disk file to create. Specify the path to an unattend.xml file which will be inserted into the new VHD. The Microsoft script will automatically mount the new VHD, copy the unattend file and automatically dismount. Finally, you will also need to specify some basic settings for the new virtual machine such as the amount of memory, the number of processors and the Hyper-V switch.

[optin-monster-shortcode id=”lwzgfpb294pntqna”]

My script first calls the Convert-WindowsImage.ps1 script to create the VHD or VHDX file.

Try {
  #call the convert script splatting the parameter hashtable
  c:scriptsConvert-WindowsImage.ps1 @convertParams
}

This might take up to 10 minutes depending on the size of the disk you want to create and where you are creating it. When I create a 15GB VHDX on an SSD drive it only took a few minutes. By the way, the disk files are automatically configured to be dynamically expanding. My script also supports –WhatIf and –Verbose so you can test out and verify your values.

Once I have a disk file, creating the virtual machine is very easy using New-VM with parameters gathered from the script.

New-VM -Name $Name -VHDPath $VHDPath -MemoryStartupBytes $Memory -SwitchName $Switch | Set-VM -DynamicMemory -ProcessorCount $ProcessorCount -Passthru

In Figure 1 you can see my script in action.

Figure 1

The new VM is piped to Set-VM to configure. You could add as many additional configuration changes as you want from PowerShell. The end result is a working virtual machine with as much of the OS configured as I want using the unattend.xml file. If it helps, unattend file that I use.

My script certainly works as a stand-alone tool, although you will most likely want to adjust some of the default parameter values. I would suggest you use the script as the basis for building your own provisioning system. There are so many variables in how someone might want to spin up and provision a new virtual machine that I haven’t tried to build a perfect solution. Instead I’ve written articles on the blog that explore the process from different angles with the assumption that you will be able to pull something together.

For PowerShell help, including using the Hyper-V cmdlets, I encourage you to use the forum at http://powershell.org.

 

Share this post