Yes. I know.  This has been done before.  Quite a few times.  But I was playing around and found an interesting use of Dynamic Parameter Validation Sets.  I ran across this TechNet blog post, and it got me thinking about binding the new VM (the cloned result) to a constrained list of available virtual switches.

This is only an example of tinkering.  It is by no means intended to instruct anyone on how this should be done.  I’m open to suggestions and recommendations, etc.

A Little Background

My Hyper-V setup is running on a Windows 10 laptop with 16 GB of memory.  I have two physical network interfaces: a standard Gigabit ethernet socket, and an Intel wifi.  I bound the Wifi to my Hyper-V as switch “Hyper-V External” to isolate the physical laptop traffic on the wired connection, and Hyper-V machines on the wireless.  Anyhow, I had this chunk of code laying around to “clone” a VM using a source which has a sysprep’d  Windows Server 2012 R2 vhdx file.

I hate the way Hyper-V stores guest data files, so I override it with a per-VM folder model.  So each VM is self-contained as it relates to the files which relate to it.  My entire Hyper-V environment runs from C:\Hyper-V, rather than the default path(s).

The Process

As I mentioned, there’s a folder with a .VHDX file for a sysprep’d disk, named “template.vhdx” in a folder named “Template”.  The script creates the target folder using the destination VM name, then copies the template VHDX into the folder, renaming the disk file at the same time.  Then it creates the VM definition in Hyper-V and (optionally) configures memory, adds a second virtual disk, removes the advanced NIC and attaches a legacy NIC (yes, a bad habit, sue me), and adds a “note” or comment.

# i prefer to trim off the trailing \ from the base path...

$HVRoot = (Get-ItemProperty "HKLM:SOFTWARE\Microsoft\Windows NT\CurrentVersion\Virtualization" | 
 Select-Object -ExpandProperty "DefaultExternalDataRoot").TrimEnd("\")

# use BITS to handle the VHD/VHDX file copying...

function Replicate-DiskFile {
  param (
    [parameter(Mandatory=$True,Position=0)] [string] $Source,
    [parameter(Mandatory=$True,Position=1)] [string] $Destination
  if (!(Get-Module BitsTransfer)) {
    Import-Module BitsTransfer
  Start-BitsTransfer -Source $Source -Destination $Destination -Description "Cloning virtual disk" -DisplayName "Cloning virtual disk"

# liberal use of Write-Host for laughs, ha ha...

function Clone-TemplateVM {
  param (
    [parameter(Mandatory=$False)] [string] $HyperVPath = $HVRoot,
    [parameter(Mandatory=$False)] [string] $SourceVM = "Template",
    [parameter(Mandatory=$True)] [string] $TargetVM,
    [parameter(Mandatory=$False)] [int64] $MaxMemory = 1GB,
    [parameter(Mandatory=$False)] [int64] $SecondDiskSize = 0,
    [parameter(Mandatory=$False)] [string] $Comment = ""
  DynamicParam {
    $ParameterName = 'VirtualSwitch'
    $RuntimeParameterDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
    $AttributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute]
    $ParameterAttribute = New-Object System.Management.Automation.ParameterAttribute
    $ParameterAttribute.Mandatory = $false
    $ParameterAttribute.Position = 4

    $arrSet = Get-VMSwitch | Select-Object -ExpandProperty Name

    $ValidateSetAttribute = New-Object System.Management.Automation.ValidateSetAttribute($arrSet)

    $RuntimeParameter = New-Object System.Management.Automation.RuntimeDefinedParameter($ParameterName, [string], $AttributeCollection)
    $RuntimeParameterDictionary.Add($ParameterName, $RuntimeParameter)
    return $RuntimeParameterDictionary
  begin {
    $VirtualSwitch = $PSBoundParameters[$ParameterName]
  process {
  $Template = "$HyperVPath\Template"
  $NewGuest = "$HyperVPath\$TargetVM"

  if (!(Test-Path "$NewGuest")) {
    Write-Host "Creating target folder..."
    MD "$NewGuest"
    $NewDisk = "$TargetVM.vhdx"
    Write-Host "Copying template VHDx file..."
    Replicate-DiskFile "$Template\Template.vhdx" "$NewGuest\$NewDisk"
    Write-Host "Creating virtual machine $TargetVM..."
    New-VM $TargetVM -VHDPath "$NewGuest\$NewDisk"

    if ($MaxMemory -gt 0) {
      Write-Host "Configuring memory allocation..."
      Get-VM $TargetVM | Set-VM -MemoryMaximumBytes $MaxMemory
    if ($VirtualSwitch -ne "") {
      Write-Host "Configuring network adapter..."
      Get-VM $TargetVM | Remove-VMNetworkAdapter -Name "Network Adapter"
      Get-VM $TargetVM | Add-VMNetworkAdapter -Name "NIC1" -IsLegacy $True -SwitchName $VirtualSwitch
    if ($SecondDiskSize -gt 0) {
      Write-Host "Adding second virtual disk..."
      $DiskName = "$NewGuest\$TargetVM"+"_DISK2.vhdx"
      New-VHD –Path "$DiskName" –SizeBytes $SecondDiskSize
      Get-VM $TargetVM | Add-VMHardDiskDrive -Path "$DiskName"
    if ($Comment -ne "") {
      Write-Host "Adding comment..."
      Get-VM $TargetVM | Set-VM -Notes "$Comment"
    Write-Host "$TargetVM created."
  else {
    Write-Host "$TargetVM already exists." -ForegroundColor DarkYellow


Clone-TemplateVM -TargetVM "SCOM2" -MaxMemory 4GB -VirtualSwitch "Hyper-V Private" -SecondDisk 80GB -Comment "SCOM 2012 R2 server on"

Upon hitting -VirtualSwitch, it now displays the filtered validation set, rather than leaving me to fumble the ball at the 1 yard line.


As it runs…ps-hyperv-2

The final result…ps-hyperv-3

Final Thoughts

As I said, this is experimental.  I’m sure some of you are face-palming right now, saying “Jeez.  This guy is an f-ing idiot!”  Maybe so, but who really cares?  I’m having fun with it.  And being an idiot never stopped anyone from becoming president, so I rest my case.  Just be glad they don’t let me carry the doomsday briefcase around. 🙂

2 thoughts on “PowerShell Hyper-V Cloning

  1. Dave this looks great and I see no issues with your use of the Dynamic Parameter Validation Set. My only comment is your use of Write-Host as opposed to Write-Output or Write-Verbose which is more flexible and kind to puppies. The only reason to use Write-Host is if you want to format the output with a particular color or something fancy like that. Again, nothing big and certainly a personal preference.

    1. Yep. Write-Host is like pounding nails with a wrench. I should have used Write-Warning, Write-Error and Write-Output more for this. In the “actual” code (from which it came), I actually call a function “Write-Log” which encapsulates a little more, but I was lazy when posting this one. 🙂

Leave a Reply

Please log in using one of these methods to post your comment: Logo

You are commenting using your 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 )

Google+ photo

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

Connecting to %s