PowerShell - New-VMFromTemplate
Create a VM from a sysprepped image with 1 line of code.
New-VMFromTemplate -VMName dc -TemplatePath 'C:\vm\template\core.vhdx'
Name State CPUUsage(%) MemoryAssigned(M) Uptime Status Version
---- ----- ----------- ----------------- ------ ------ -------
dc Off 0 0 00:00:00 Operating normally 8.3
Source code at the end of the post. Jump ahead and grab the code, or come along of the journey.
The Journey
I made a post on how to create a VM template previously (Build VM Template), but I didn’t provide a function that could streamline the process.
The problem is ensuring the default behaviour is as optimized as possible. It should also be safe to run so that you don't overwrite files, or do more copying of the large template file then is necessary.
My Defaults
VM location
I don't like using the default Hyper-V VM location.
I want all my VMs in the same location,
and tucked away in folders named after the VM's name.
Example: C:\vm\DC1\
VHD location
With my VM files in a standard location,
I will store the VHD / VHDX files in the same folder structure.
I want the main VHD file to be named the same as the VM's name.
Example: C:\vm\DC1\DC1.vhdx
VM Settings
I have pretty consistent VM settings for servers that I like to use.
Settings like Processor Count, Memory, and VM Generation
are generally the same across the board,
but there will be circumstances where I want to modify these defaults.
Putting these settings as parameters should work nicely.
Break down the problem
Time to break down the problem into its basic components.
Problem 1: Order
We will need to know the VM location before we can run New-VM.
The VM folder will also need to exist before we can copy the template file.
Most settings can be applied when using New-VM,
but some settings need to be applied after the VM is created.
Solution:
Ensure the VM folder structure exists before creating VM,
and before copying the template.
# Example
$vmName='dc'
$vmPath="C:\vm\$vmName"
if(!(Test-Path -Path $vmPath)){
New-Item -Path $vmPath -ItemType Container
}
Problem 2: Templates
The template is the most important piece to this puzzle.
We should validate that the template exists,
and that it is a valid template file (at least a VHD or VHDX file).
Solution:
Running Get-VHD on the template path
will indicate if it is a valid file type.
$templatePath='C:\vm\template\core.vhdx'
$templateVHD=Get-VHD -Path $templatePath
Problem 3: Copying Template
The template file name will not be the same as
what we want it to be.
We also need to copy it to the VM path location.
Solution:
Ensure the VM folder structure exists first.
We should also ensure everything that could go wrong has been checked.
We do not want to copy the whole file,
only to have some error stop the process short.
Once validation is complete,
we can copy the file and rename it according to the VM name.
Problem 4: Settings
We will need to specify the VM Generation when running New-VM.
This setting cannot be modified after it is created.
Most other settings can be specified when running New-VM.
Solution:
Specify all settings for the VM in the New-VM cmdlet.
When the VM is created, use Set-VM for setting ProcessorCount.
All these settings should have default values,
and be parameters for maximum flexibility.
Source Code
Function New-VMFromTemplate {
[CmdletBinding()]
Param(
[Parameter(Position=0,Mandatory=$true,ValueFromPipeline=$true)]
[String]$VMName,
[Parameter(Position=1,Mandatory=$true)]
[ValidateScript({Test-Path -Path $_ -IsValid})]
[String]$TemplatePath,
[Parameter(Position=2)]
[int64]$MemoryStartupBytes=1GB,
[Parameter(Position=3)]
[int16]$ProcessorCount=2,
[Parameter(Position=4)]
[String]$SwitchName,
[Parameter(Position=5)]
[ValidateRange(1,2)]
[int16]$Generation=2
)
Begin{
Try{
Write-Verbose "Validating template..."
$templatePathExist=Test-Path -Path $TemplatePath
if(!$templatePathExist){
Write-Error -Message "Cannot find template path [$TemplatePath]" `
-Category ObjectNotFound `
-ErrorAction Stop
}
$templateVhd=Get-VHD -Path $TemplatePath -ErrorAction Stop
if($PSBoundParameters.ContainsKey('SwitchName')){
$vmSwitch=Get-VMSwitch -Name $SwitchName -ErrorAction SilentlyContinue
if(!$vmSwitch){
Write-Error -Message "VM Switch with name [$SwitchName] not found" `
-Category ObjectNotFound `
-ErrorAction Stop
}
}
}Catch{
Write-Error -Exception $_.Exception `
-Category $_.CategoryInfo.Category
break
}
}
Process{
Try{
Write-Verbose "Validating VM info..."
# Change this as desired
$vmDefaultPath="C:\vm\$VMName"
$newVHDPath="$vmDefaultPath\$($VMName).$($templateVhd.VhdFormat)"
$vmVHDExists=Test-Path -Path $newVHDPath
if($vmVHDExists){
Write-Error -Message "VM VHD already exists [$newVHDPath]" `
-Category ResourceExists `
-ErrorAction Stop
}
$vmExist=Get-VM -VMName $VMName -ErrorAction SilentlyContinue
if($vmExist){
Write-Error -Message "VM already exists [$VMName]" `
-Category ResourceExists `
-ErrorAction Stop
}
### Copy template VHD file to target VM Path
$vmPathExist=Test-Path -Path $vmDefaultPath
if(!$vmPathExist){
New-Item -Path $vmDefaultPath -ItemType Container -ErrorAction Stop |
Out-Null
}
Write-Verbose "Copying template to new VHD path"
Copy-Item -Path $TemplatePath -Destination $newVHDPath -ErrorAction Stop
Set-ItemProperty -Path $newVHDPath `
-Name isReadOnly `
-Value $false `
-ErrorAction Stop
### Create new VM
$newVMParameters=@{
'VMName'=$VMName;
'Path'=$vmDefaultPath;
'VHDPath'=$newVHDPath;
'Generation'=$Generation;
'MemoryStartupBytes'=$MemoryStartUpBytes;
'ErrorAction'='Stop';
}
if($PSBoundParameters.ContainsKey('SwitchName')){
$newVMParameters.Add('SwitchName',$SwitchName)
}
Write-Verbose "Creating VM [$VMName]"
$newVM=New-VM @newVMParameters
if($newVM.ProcessorCount -ne $ProcessorCount){
$newVM=Set-VM -VMName $VMName `
-ProcessorCount $ProcessorCount `
-PassThru `
-ErrorAction Stop
}
Write-Output $newVM
}Catch{
Write-Error -Exception $_.Exception `
-Category $_.CategoryInfo.Category
}
}
End{}
}
Example 1
New-VMFromTemplate -VMName dc `
-TemplatePath C:\vm\template\Gen1Core.vhdx `
-Generation 1 `
-SwitchName 'External vSwitch'
Create a new VM called dc, using the C:\vm\template\Gen1Core.vhdx template. This command assumes that the template is a Generation 1 template.
Example 2
'dc1','dc2' | New-VMFromTemplate -TemplatePath 'C:\vm\template\core.vhdx' `
-ProcessorCount 1 `
-Memory 1.5GB
Create 2 VMs, dc1 and dc2, using the C:\vm\template\core.vhdx template. Assign 1.5GB for the MemoryStartUpBytes on each VM, and ensure only 1 Virtual CPU is assigned. No VM Network Adapter will be provided when the VMs are made.
Example 3
New-VMFromTemplate -VMName CM1
-TemplatePath 'C:\vm\template\gui.vhdx' `
-Memory 3GB `
-SwitchName 'Internal0'
Create a VM using the C:\vm\template\gui.vhdx template. Assign 3GB of memory and assign the 'Internal0' VM Network Adapter.
Thanks for reading
PS> exit