Rapid Provisioning Bulk Virtual Machines using SCVMM and Hyper-V; Creating Full Clones for XenDesktop or Microsoft VDI

Share Button

Over the last several years I’ve written a number of PowerShell scripts, ranging in complexity from one-liners to very complex multi-function modular packages for distribution (See the Citrix Chained Reboot Scripts for XenApp and XenDesktop for an example of one of my more complex scripts).  As I’ve discovered, scripting in PowerShell is an adaptive process, easily allowing you to take bits and pieces from a previous script to feed into the next. Well, that’s just what I’ve done with this SCVMM/Hyper-V script to create Full VM Clones for Citrix XenDesktop or Microsoft VDI. See below for a demo of the finished product:

In many cases, you may not want to use chained base image / linked cloning technologies. For example, if you’re planning to provide disaster recovery capabilities for your users full thick clones, this becomes increasingly difficult (or impossible) when you’re talking about linking differencing disks back to a master or base disk. This is also one of the challenges with the RingCube acquisition (Personal vDisk) and can become a barrier to adoption. Another scenario is when you’re using specific vendor’s storage optimization, like Atlantis ILIO or Nutanix and deduplication occurs either in-line or as a back-end process. In these scenarios, it makes more sense to have full clone persistent desktops for the operational simplicity. Unfortunately both Citrix XenDesktop and Microsoft VDI lack the ability to create full clones for persistent, both of which employ a vhdx chaining mechanism for Hyper-V.

Without further introduction, let’s actually dive into how to create the rapid provisioning script. Since this script will be very environment specific, I’ll be walking readers through how to create this, instead of providing a pre-packaged script. To start, you need a SCVMM and Hyper-V environment with a Windows virtual machine captured as a template. If you need assistance on how to get to this point, there are a lot of guides on the web, including one by my colleague Greg Shields on Tech Target: Using Virtual Machine Manager for rapid Hyper-V deployment.

One of my favorite features of the latest release of System Center is that (for the most part) wizards provided in the GUI have a “View Script” button in the corner so you can reuse the PowerShell functions for future deployments! We’ll use these script as a starting point. In my example above, I’m using local storage (Atlantis ILIO) and have all the domain join process setup so when the machines power on, they are already joined to the domain with the Citrix VDA installed, ready for production. To get started, in SCVMM right click your template, select Create Virtual Machine, enter all the details about the VM deployment, and at the end, click View Script:

image

This will output a script similar to the following:

image

Go ahead and save this script somewhere useful. In order to modularize this script and make it repeatable to create VMs in bulk, we’ll need to change all the unique identifiers such as JobGroups, computername, temporary template, and hardware profile.  These will be generated once for each virtual machine.

At the beginning of the script, drop the following code to create the GUID variables:

$jobgroup = [guid]::NewGuid()
$jobgroup2 = [guid]::NewGuid()
$profile = [guid]::NewGuid()
$temptemplate = [guid]::NewGuid()

There are actually two unique GUIDs for the JobGroups, so we’ll need to replace the first GUID with $jobgroup, and the second with $jobgroup2. In my example, the first JobGroup was “a728072c-64f6-468c-a73f-489c145d9580”.  A simple find and replace will replace this unique GUID with the variable $jobgroup:

image

We’ll go ahead and do the same for jobgroup2, profile, and temptemplate. The first line that has the GUID for JobGroup2 starts with “New-SCVMTemplate”.  Find this line and you’ll find the second JobGroup GUID (“d7091731-5bd4-4b49-b9f1-b2e9f1854410” in my example):

image

A simple find replace on this GUID and we’ll be all set:

image

Next, we’ll do the same for $profile (first line starts with “New-SCHardwareProfile”). Be sure to include the double quotes when you replace this with the $profile variable:

image

Next, let’s do the same for $temptemplate (first line starts with “New-SCVMTemplate”). Be sure to include the double quotes when you replace this with the $temptemplate variable:

image

Finally, we’ll replace the $computername with a variable that we’ll increment as we deploy the VMs in bulk (first line starts with “New-SCVMTemplate”). Be sure to include the double quotes with you replace this with the $computername variable:

image

By default it will use the same host that you selected in the wizard for each deployment. If you are deploying to local storage, take a look at the line that starts with “Set-SCVMConfiguration” as it will likely contain the path to be used for the deployments. In my case, since I’m using an ILIO iSCSI volume, I want all these VMs directed to the “I:\” drive:

Set-SCVMConfiguration -VMConfiguration $virtualMachineConfiguration -VMLocation “I:\” -PinVMLocation $true

Also, I’ve found that sometimes when doing find and replace, my variables still end up with double quotes around them. Just do a cursory search of the script to make sure none of your variables are in quotes as this will break the script. If any of the five variables are in double quotes, remove the quotes. See:

image

Next, we’ll go ahead and walk through the magic of creating these VMs in bulk.  First thing you need to do is add a right bracket “ } ” at the very end of the script. This is because we will be using this entire script in a while loop:

image

Next, you can simply copy/paste the following at the top of the script (above the $jobgroup, $jobgroup2, $temptemplate, and $profile variables). We’ll use this as a starting point for creating the admin dialog and loop:

ipmo ‘virtualmachinemanager\virtualmachinemanager.psd1’
[void]
[System.Reflection.Assembly]::LoadWithPartialName(‘Microsoft.VisualBasic’)
$namingprefix = [Microsoft.VisualBasic.Interaction]::InputBox(“Provide the Virtual Machine Name Prefix:”, “Virtual Machine Name Prefix”, “HVXD71W7-6”)
[int]$numberofvms = [Microsoft.VisualBasic.Interaction]::InputBox(“How Many Virtual Machines to Create?”, “How Many Virtual Machines to Create?”, “10”)
[int]$maxjobs = “5”
get-vm | sort-object Name | where {$_.Name -like $namingprefix + “*”} | Select-Object -last 1 | foreach-object {$lastvm = $_.Name.Substring($_.Name.Length – 2,2)}
if ( $lastvm -ne $null ){ $i = [int]$lastvm} else { $i = 0}
$j = $i + $numberofvms
for ($i += 1; $i -le $j; $i++) {
$running = @(get-job | ? {$_.Status -eq “Running”})
while ($running.Count -ge $maxjobs) {$running = @(get-job | ? {$_.Status -eq “Running”});write-host Sleeping;start-sleep -s 15}
$computername = $namingprefix + “{0:d2}” -f $i
write-host (get-date -uformat %I:%M:%S) “- Creating virtual machine ” $computername -ForegroundColor Green

Unfortunately the blockquote below puts line breaks in the wrong places due to word wrap. Click here to download as a text file:  SCVMM_Rapid_Provisioning_Windows7_Full_Clones_Snipit.txt

Let’s walk through each line and I’ll outline what we’re doing…

ipmo ‘virtualmachinemanager\virtualmachinemanager.psd1’

Since we want to be able to call this script from a PS1 script, this imports the Virtual Machine Manager PowerShell module.

[void]
[System.Reflection.Assembly]::LoadWithPartialName(‘Microsoft.VisualBasic’)

Load the VB assembly so we can create a dialog.

$namingprefix = [Microsoft.VisualBasic.Interaction]::InputBox(“Provide the Virtual Machine Name Prefix:”, “Virtual Machine Name Prefix”, “HVXD71W7-6”)

Present a VB dialog to the administrator to provide the VM Name Prefix. Looks something like this:

image

[int]$numberofvms = [Microsoft.VisualBasic.Interaction]::InputBox(“How Many Virtual Machines to Create?”, “How Many Virtual Machines to Create?”, “10”)

Present a VB dialog to the administrator to provide the number (integer) of VMs to create. Looks something like this:

image

[int]$maxjobs = “5”

Specify the maximum number of SCVMM jobs for throttling. If your hosts and array can handle more than five simultaneous provisioning actions, you can increase the number of jobs here.

get-vm | sort-object Name | where {$_.Name -like $namingprefix + “*”} | Select-Object -last 1 | foreach-object {$lastvm = $_.Name.Substring($_.Name.Length – 2,2)}

if ( $lastvm -ne $null ){ $i = [int]$lastvm} else { $i = 0}

Determine “Where we left off” and start the provisioning process from there. For example, if your prefix was HVXD71W7-6 and you already had VMs 01-10, it would start at 11 for the next VM to create. If you want to create three digits for the increment (more than 100 VMs for the prefix), change the number 2 (two places) to 3.

$j = $i + $numberofvms
for ($i += 1; $i -le $j; $i++) {

Create the upper boundary ($j) and for loop.

$running = @(get-job | ? {$_.Status -eq “Running”})
while ($running.Count -ge $maxjobs) {$running = @(get-job | ? {$_.Status -eq “Running”});write-host Sleeping;start-sleep -s 15}

Throttle the jobs to create $maxjobs (i.e. 5) VMs at a time, based on total running jobs on SCVMM. Sleep for 15 seconds and check again.

$computername = $namingprefix + “{0:d2}” -f $i
write-host (get-date -uformat %I:%M:%S) “- Creating virtual machine ” $computername -ForegroundColor Green

Create the $computername variable based on the namingprefix and current job number (for example HVXD71W7-601).  If you want to create three digits for the increment (more than 100 VMs for the prefix), change the d2 to d3.

That’s it!  If you want to compare your final script against mine, you can find my final here:

Click to Download SCVMM_Rapid_Provisioning_Windows7_Full_Clones.txt

As always, if you have any questions, comments, or just want to leave feedback, please do so below.  Thanks for reading!

@youngtech

Share Button
  1. JassuJassu02-21-2015

    Hi,

    Need a similar script but for Xendesktop VDI’s. We have Hyper V environment (2008) where we have Xendesktop controllers and VDI’s are deployed on a shared storage. i need a script which can Deploy Xendesktop VDI to SCVMM but provides an option to select the shared storage i.e. which LUN this VDI will reside. Can you please help in this?

  2. Graham ConstantineGraham Constantine02-15-2015

    Hi That worked with some site-specific tweaking, thanks.

  3. GustavoGustavo10-10-2014

    Hello Dane, I found your script a I have to tell you, that’s great script, thanks, and I’d like to ask you how to modify this script to create a “5VM” from a template and from a csv file that contains the server names.
    I just did modify the script but when I excecute it, the script does not finish and create an infinite VM in VMM, I send the code, for review if you can.

    **************************************script modified****************************************

    [int]$numberofvms = [Microsoft.VisualBasic.Interaction]::InputBox(“How Many Virtual Machines to Create?”, “How Many Virtual Machines to Create?”, “10”)
    [int]$maxjobs = “5”

    Import-CSV “C:\tempo\server_names.csv” | % {
    $computername = $_.server

    get-vm | sort-object Name | where {$_.Name -like $computername} | Select-Object -last 1 | foreach-object {$lastvm = ($_.Name)}
    if ( $lastvm -ne $null ){ $i = [int]$lastvm} else { $i = 0}
    $j = $i + $numberofvms
    for ($i += 1; $i -le $j; $i++) {

    $running = @(get-job | ? {$_.Status -eq “Running”})
    while ($running.Count -ge $maxjobs) {$running = @(get-job | ? {$_.Status -eq “Running”});write-host Sleeping;start-sleep -s 15}

    $computername = $_.server -f $i
    write-host (get-date -uformat %I:%M:%S) “- Creando virtual machine ” $computername -ForegroundColor Green

    $jobgroup = [guid]::NewGuid()
    $profile = [guid]::NewGuid()
    $temptemplate = [guid]::NewGuid()
    $jobgroup2 = [guid]::NewGuid()

    New-SCVirtualScsiAdapter -VMMServer localhost -JobGroup $jobgroup -AdapterID 7 -ShareVirtualScsiAdapter $false -ScsiControllerType DefaultTypeNoType

    New-SCVirtualDVDDrive -VMMServer localhost -JobGroup $jobgroup -Bus 1 -LUN 0

    New-SCVirtualNetworkAdapter -VMMServer localhost -JobGroup $jobgroup -Synthetic

    Set-SCVirtualCOMPort -NoAttach -VMMServer localhost -GuestPort 1 -JobGroup $jobgroup

    Set-SCVirtualCOMPort -NoAttach -VMMServer localhost -GuestPort 2 -JobGroup $jobgroup

    Set-SCVirtualFloppyDrive -RunAsynchronously -VMMServer localhost -NoMedia -JobGroup $jobgroup

    $CPUType = Get-SCCPUType -VMMServer localhost | where {$_.Name -eq “3.60 GHz Xeon (2 MB L2 cache)”}

    New-SCHardwareProfile -VMMServer localhost -CPUType $CPUType -Name $profile -Description “Profile used to create a VM/Template” -CPUCount 4 -MemoryMB 2048 -DynamicMemoryEnabled $false -MemoryWeight 5000 -VirtualVideoAdapterEnabled $false -CPUExpectedUtilizationPercent 20 -DiskIops 0 -CPUMaximumPercent 100 -CPUReserve 0 -NumaIsolationRequired $false -NetworkUtilizationMbps 0 -CPURelativeWeight 100 -HighlyAvailable $true -DRProtectionRequired $false -NumLock $false -BootOrder “PxeBoot”, “CD”, “IdeHardDrive”, “Floppy” -CPULimitFunctionality $false -CPULimitForMigration $false -Generation 1 -JobGroup $jobgroup

    $Template = Get-SCVMTemplate -VMMServer localhost | where {$_.Name -eq “P0PSWVTEMP-2012-R2”}
    $HardwareProfile = Get-SCHardwareProfile -VMMServer localhost | where {$_.Name -eq $profile}
    $OperatingSystem = Get-SCOperatingSystem -VMMServer localhost | where {$_.Name -eq “64-bit edition of Windows Server 2012 Standard”}

    New-SCVMTemplate -Name $temptemplate -Template $Template -HardwareProfile $HardwareProfile -JobGroup $jobgroup2 -ComputerName $computername -TimeZone 41 -LocalAdministratorCredential $null -AnswerFile $null -OperatingSystem $OperatingSystem

    $template = Get-SCVMTemplate -All | where { $_.Name -eq $temptemplate }
    $virtualMachineConfiguration = New-SCVMConfiguration -VMTemplate $template -Name $computername
    Write-Output $virtualMachineConfiguration
    $vmHost = Get-SCVMHost -ComputerName P0PSWFINF20
    Set-SCVMConfiguration -VMConfiguration $virtualMachineConfiguration -VMHost $vmHost
    Update-SCVMConfiguration -VMConfiguration $virtualMachineConfiguration
    Set-SCVMConfiguration -VMConfiguration $virtualMachineConfiguration -VMLocation “C:\ClusterStorage\P0PSWFCLU11-CSV04\” -PinVMLocation $true

    $AllNICConfigurations = Get-SCVirtualNetworkAdapterConfiguration -VMConfiguration $virtualMachineConfiguration

    Update-SCVMConfiguration -VMConfiguration $virtualMachineConfiguration
    New-SCVirtualMachine -Name $computername -VMConfiguration $virtualMachineConfiguration -Description “” -BlockDynamicOptimization $false -StartVM -JobGroup $jobgroup2 -ReturnImmediately -StartAction “NeverAutoTurnOnVM” -StopAction “SaveVM”

    }
    }
    *****************************************end********************************************************
    if you need the script in a text file, please write me to sirjig@gmai.com and I’ll send it

    thanks

  4. Alex VerboonAlex Verboon06-23-2014

    Hey there, many thanks for this, has been of great help.
    Alex

  5. George FeiGeorge Fei06-14-2014

    Thanks for the sharing! I’ve tested the method and it partially works for me.

    It appears that VMM do more works than the exported script. Basically, VMM automatically delete the temp hardware profiles when the scripts leave the temp profiles in library. Meanwhile, the script is not able to specify how the virtual network adapters should be connected (especially with VLAN). Thus, I’m not able to create a bulk of domain joined workstations by using this script.

    • Dane YoungDane Young07-27-2014

      George,

      Thanks for the feedback! I’ve found this myself as well. Obviously I need to refresh the blog post with some additional bits that will make this script ready for prime time.

      Take care
      –youngtech

Leave a Reply