Ever use the XenDesktop Setup Wizard (XDSW) to create virtual machines on Hyper-V only to find that your attached virtual hard disk (VHD) was not copied over? This happens because the XDSW removes all attached disk drives before creating the new virtual machine when interfacing with Hyper-V. This behavior surprises XenServer users who are used to the XDSW cloning the VHDs that are included in the XenServer template.

The reason XDSW behaves this way is because it does not support System Center Virtual Machine Manager (SCVMM) templates at this time. Instead the wizard uses a “sample” virtual machine as a template. Since the machine is not a template, but rather a working guest, the tool chooses to remove any existing disks to avoid future issues.

Today’s script will help you remedy that situation by automating the copy and attachment of any VHD, to a set of virtual machines. This script is particularly useful when you plan to use the “Cache on device’s hard disk” option of Provisioning Server (PVS) with a standard-mode (read-only) vDisk.

Before executing the script, you will need to copy the VHD to the SCVMM library. If you are not sure how to accomplish that task, see step two of my blog Three Steps to a PXE-Free XenDesktop and place the VHD in the VHDs folder instead of in the ISOs folder.

The script should be run from the SCVMM server that manages the virtual machines that need the VHD. I also recommend running it from the PowerShell prompt within the SCVMM Admin Console, so the VMM PowerShell libraries are automatically loaded for you.

Usage: .\ copyvhd.ps1 UNC_VHD_Path Host_Storage_Location VM_Match_Criteria Prepend Append


  • UNC_VHD_Path = The UNC File path to the SCVMM library that holds the VHD. The VHD must already exist in the SCVMM library and the script will copy the VHD from the library to the Hyper-V host.
  • Host_Storage_Location = The path where the VHD will be copied to on the Hyper-V host. This path must be local (cannot be a UNC path) to the host.
  • VM_Match_Criteria = Provides name matching criteria for selecting which VMs will be affected by the script. For example, providing a name match of “HVDesktop” will match any VM with “HVDesktop” in the name, including “HVDesktop1” or “AHVDesktop”. To match a single VM, provide the full VM name as the match criteria.
  • Prepend = Any text that should be added to the beginning of the VHD filename that will be used for the disk. The base name of the VHD will be the VM’s name. This text is optional; use a pair of double-quotes to pass a null string as the parameter.
  • Append = Any text that should be added to the end of the VHD filename that will be used for the disk. The base name of the VHD will be the VM’s name. This text is optional; use a pair of double-quotes to pass a null string as the parameter.

Example: .\copyvhd.ps1 “\\SCVMM\MSSCVMMLibrary\VHDs\writecachebase.vhd” “E:\VMs” “HVDesktop1000” “wc-” “”

The example above matches the VM with a name of HVDesktop1000 (assuming no other VMs contain those characters) and copies the writecachebase.vhd file from the SCVMM library to the E:\VMs folder on the Hyper-V host with a filename of wc-HVDesktop1000.vhd. The script then adds a disk drive at IDE Bus 0 Lun 0 to HVDesktop1000 VM settings and sets it to E:\VMs\wc-HVDesktop1000.vhd.

CopyVHD PowerShell Script
# Purpose:        Copy and Attach VHD to a VM
# Last Modified:  28 April 2010
# Author:         Chris Gilbert & Paul Wilson (no implied or expressed warranties)
# Usage:          copyvhd.ps1 [UNC_Path_to_VHD] [Host_Storage_Location][VM_Name_Match_Criteria]
#                 [Prepend_filename_text] [Append_filename_text]

# Check to verify the arguments necessary <span class="code-keyword">for</span> the script to run are provided.

<span class="code-keyword">if</span> ($args -eq $<span class="code-keyword">null</span> -or $args.Count -lt 3)
    write-output <span class="code-quote">"Usage: copyvhd UNC_VHD_Path VM_Storage_Location VMNameMatches prepend_text append_text"</span>
    write-output <span class="code-quote">"Example: .\copyvhd.ps1 "</span><span class="code-quote">"\\SCVMM\MSSCVMMLibrary\VHDs\writecachebase.vhd"</span><span class="code-quote">" "</span><span class="code-quote">"e:\vms XDesktop"</span><span class="code-quote">" "</span><span class="code-quote">""" "</span><span class="code-quote">""" "</span>
    exit 1

# Assign command-line arguments to variables <span class="code-keyword">for</span> later use

$VdiskPath = $args[0]
$ClusterStoragePath = $args[1]
$VMNameMatches = $args[2]
$PrependString = $args[3]
$AppendString = $args[4]

# Grab a handle to the local computer <span class="code-keyword">for</span> the VMM Server <span class="code-keyword">interface</span>

$VMMServer = Get-VMMServer -Computername <span class="code-quote">"localhost"</span>

# Grab a handle to the VHD specified on the command-line and exit <span class="code-keyword">if</span> not found

$BaseVdisk = Get-VirtualHardDisk -VMMServer $VMMServer | where { $_.Location -eq <span class="code-quote">"$VdiskPath"</span> }

<span class="code-keyword">if</span> ($BaseVdisk -eq $<span class="code-keyword">null</span>)
    write-output <span class="code-quote">"Unable to find vdisk: $VdiskPath"</span>
    exit 1

# Get all the VMs that match the name criteria supplied

$VMs = Get-VM | where { $_.Name -match <span class="code-quote">"$VMNameMatches"</span> }
<span class="code-keyword">if</span> ($VMs -eq $<span class="code-keyword">null</span>)
    write-output <span class="code-quote">"No VMs match the pattern: $VMNameMatches"</span>
    exit 1
<span class="code-keyword">else</span>
    $matchedString = <span class="code-quote">"{0} vms match the pattern: {1}"</span> -f $VMs.Count, $VMNameMatches
    write-output $matchedString

# The loop below does the following <span class="code-keyword">for</span> each VM matched
# 1. Looks <span class="code-keyword">for</span> Virtual Disk Drives already attached
# 2. Generates a filename to be used <span class="code-keyword">for</span> the <span class="code-keyword">new</span> VHD
# 3. Attempts to copy the template VHD from the library and attach
#    it at IDE Bus 0, LUN 0
# 4. Outputs either a <span class="code-quote">"Success"</span> or <span class="code-quote">"Disk Already Attached"</span> message

foreach ($vm in $VMS)
    $current_disks = get-VirtualDiskDrive -VM $VM
    <span class="code-keyword">if</span> ($current_disks -eq $<span class="code-keyword">null</span> -or $current_disks.count -eq 0)
        $filename = <span class="code-quote">"{0}{1}{2}.vhd"</span> -f <span class="code-quote">"$PrependString"</span>, $VM.name, <span class="code-quote">"$AppendString"</span>
        $cloningMessage = <span class="code-quote">"Attaching {0} to VM {1}"</span> -f $filename, $VM.Name
        write-output $cloningMessage
  $newvhd = New-VirtualDiskDrive -VM $VM -VirtualHardDisk $BaseVdisk -Path <span class="code-quote">"$ClusterStoragePath\$VM"</span> -Filename <span class="code-quote">"$filename"</span> -IDE -Bus 0 -LUN 0
    <span class="code-keyword">else</span>
        $diskattachedmessage = <span class="code-quote">"{0} {1}"</span> -f $VM.Name, <span class="code-quote">"has disk already attached"</span>
        write-output $diskattachedmessage

If you found this information useful and would like to be notified of future blog posts, please follow me on Twitter @pwilson98 or visit my XenDesktop on Microsoft website.