How to provision 500 VM’s. Fast.

The Challenge

Back around March 2010, I was asked by Stephen Spellicy, who was on the vSpecialists technical marketing team, to help out on a project he was doing for EMCworld 2010. I drove up to the Nashua lab and sat down to help. Stephen told me about this new cool product coming out from EMC called VPLEX. He wanted to create 500 VM’s and he was going to show VPLEX vMotioning those systems from one datacenter to another. Oh, and Pat G. and Chad were doing to do it live on stage. Yea, no pressure.

So, the challenge to me was “Can you create a script that creates 500 VM’s?” Hell, that shouldn’t be too hard, even for a coding moron like myself so I responded with “I can write a Powershell script for that!”. And off I went…

Why Powershell?

You see, I suck at writing code. I’ll never be the guy who comes up with some seriously whacked algorithm that does amazing things with 5 bytes of code. Being a former IT guy, I think in tasks. This task was “Create 500 VM’s, each with a unique ID.”

Powershell was designed for that in mind. It takes the best of a number of IT Admin CLI’s. There’s bits of Bash, DCL and others mixed in there. It’s designed to get stuff done, with a minimum amount of hassle. Is it something I’m going to write an enterprise SaaS app in? Of course not. But for IT tasks like “Create 500 items using this template”, it’s damned near perfect.

The History of the Script

So, my first stab at this, before other requirements started coming in, was pretty simple. Ten or so lines of code. Read in from an Excel generated .CSV file the name of the VM’s and a foreach loop to create the VM’s asynchronously. But this presented a bunch of limitations. There was only one host and datastore defined. Naturally, that wasn’t going to cut it.

Version 2 took this a little further. Still the same limitations but it named the VM’s based on a count. e.g. DEMO_VM_1, DEMO_VM_2, etc… so I could dump the .CSV file and just use variables to control how many VM’s. But still, I needed to space out the VM’s being created across a number of hosts and a number of datastores. The script needed to be more adaptable.

Around this time, I started recruiting my friend Alan Renouf (@alanrenouf) to help me in this. I was rapidly reaching the need for a lifeline. Alan helped me with a few of the concepts and re-wrote some of the code to make it run better.

Version 3 took on a whole other look and feel. First, we created a unique template on each datastore. This way, when we were creating from template, we weren’t overtaxing the template datastore with IO requests. Next, we added a bunch of datastores and hosts and dumped them into arrays we could pull from.

Next, we added some code for generating randomness of the VM name so it wasn’t all done serially. We still used a standard prefix as an easy way to distinguish the VM’s from others.

Now, here’s where it got fun. I was reading a blog post from Luc Dekens (@lucd) on the Get-Task cmdlet and async tasks. I create a hash table of New-VM tasks This allows me to keep the pipeline of tasks going. As a task finishes, another task starts. It was VERY helpful.

Version 4 was mainly a cleanup and tweak. I used Luc’s code twice. Once for creating the VM’s and managing that process and second for starting the VM’s in a balanced fashion. Sure, I could have just said “Start-VM {list of VM’s}” but Luc’s code would start each one and monitor for success before starting another. This was much smoother.

The Result

The script would create 500 VM’s in a matter of minutes. It worked really well. And then Nick Weaver (@lynxbat) got a hold of it and tweaked it some more. (I’d still like a copy of that script Nick!) At EMCworld, I ran into Alan and Nick and told Alan that the code he helped with would be used for the big EMCworld Pat G/Chad demo. His response? “Crap, had I known that I would have done a better job!”

At the end of this, Stephen was happy and had christened the script “The Baby Maker” and called Alan and I “the proud parents”. I was ok with it up until the parents part. hahaha

 

So, I’m presenting my copy below. It’s quite functional and all in about 60 lines of actual code. If you need to create a BUNCH of VM’s, maybe for testing or benchmarking, this script will do it quite quickly. Use the code at your own risk. EMC and I are not responsible for you causing the end of mankind because you ran it. Oh, and batteries are not included.

#------------------------------------------------------------------------------
# 1st draft written by Mike Foley, @mikefoley, Virtualization Evangelist, RSA, The Security Division of EMC
# Contributions from Alan Renouf, @alanrenouf, EMC vSpecialist and PowerCLI guru
# Many thanks to those who's code we used, especially Luc Dekens, @LucD
#------------------------------------------------------------------------------
# All information, statements, and descriptions herein are offered AS IS
# only and are provided for informational purposes only. EMC Corporation
# does not guarantee the performance of, or offer a warranty of any kind,
# whether express, implied, or statutory, regarding the information herein
# or the compatibility of the information herein with EMC Corporation software
# or hardware products or other products.
#------------------------------------------------------------------------------
# 06-Apr-2010, Mike Foley, A simple Powershell/PowerCLI script to create a bunch of VM's. Written from lots of examples
# 20-Apr-2010, Alan Renouf, applied a different method of working out the numbers and re-wrote some of the code
# 21-Apr-2010, Alan Renouf, Fixed datastores which are used for deployment as per email from Mike.
#------------------------------------------------------------------------------
# You'll need to have the PowerCLI cmdlets installed. Get these from VMware.
# http://www.vmware.com/go/powercli

Add-PSSnapin -Name "VMware.VimAutomation.Core" -ErrorAction SilentlyContinue
#
#Things you can change
#------------------------------
$vchost = "10.5.103.185"
$num_vms_total = 4
$vm_prefix = "DEMO_VM_"
$Template_prefix = "tiny-ds-"
#-------------------------------
#Don't change below
$Template_Name = $vm_prefix + "*"

Connect-VIserver $vchost -user administrator -password emcworld

Write-Host "Collecting Template lists"
$Template_List = Get-Template $Template_Name | Select Name, @{N="Datastore";E={Get-VIObjectByVIView (Get-View ($_).DatastoreIdList)}} | Sort Name, Datastore

Write-Host "Collecting Datastore lists"
$Datastore_list = $Template_List | Select -Expand Datastore

Write-Host "Collecting Host lists"
$hosts = Get-VMHost | Sort Name

Write-Host "Collecting number of VMs per host"
$HostVMs = $hosts | Select Name, @{N="NumVM";E={@(($_ | Get-Vm ($vm_prefix + "*"))).Count}} | Sort NumVM, Name 

Write-Host "Collecting number of VMs per datastore"
$DSVMs = $Datastore_list | Select name, @{N="NumVM";E={@($_ | Get-VM ($vm_prefix + "*")).Count}} | Sort NumVM, Name

#Setup for Get-Random in the loop below
$min = [char]'a'
$max = [char]'z'
$alpha = [char[]]($min..$max)
$ofs = ''
$taskTab = @{}
$vm_count = 0

While ($vm_count -lt $num_vms_total){
    Write-Host "Finding host with least amount of $vm_prefix VMs on it"
    $LeastHost = $HostVMs | Sort NumVM | Select -First 1
    Write-Host "Finding datastore with least amount of $vm_prefix VMs on it"
    $LeastDatastore = $DSVMs | Sort NumVM | Select -First 1
    $ran_text = [string]( $alpha | Get-Random -Count 5 )
    $VM_Name = $vm_prefix + $ran_text + $vm_count
    $Template = ($Template_List | Where {$_.Datastore.Name -eq $LeastDatastore.Name}).Name
    Write "Create a virtual machine called $VM_Name on $($LeastHost.Name) using a template called $Template onto a datastore called $($LeastDatastore.Name)"
    $taskTab[(New-VM -Name $VM_Name -Template $template -VMHost (Get-VMhost $LeastHost.Name) -Datastore (Get-Datastore $LeastDatastore.Name) -DiskStorageFormat Thin -RunAsync ).Id] = $VM_Name
    $DSVMs | Where { $_.Name -eq $LeastDatastore.Name } | Foreach { $_.NumVM++ }
    $HostVMs | Where { $_.Name -eq $LeastHost.Name } | Foreach { $_.NumVM++ }
    $vm_count ++
}

Write-Host "-----------------------"
Write-Host "VM Deployment completed"
Write-Host "-----------------------"

Write-Host "-----------------------"
Write-Host "Starting VM's"
Write-Host "-----------------------"


#
## Start each VM that is completed
$runningTasks = $taskTab.Count
while($runningTasks -gt 0){
    Get-Task | % {
        if($taskTab.ContainsKey($_.Id) -and $_.State -eq "Success"){
            $xx = Get-VM $taskTab[$_.Id] 
           Write-Host "Starting VM " $xx.Name
            Get-VM $taskTab[$_.Id] | Start-VM 
            $taskTab.Remove($_.Id)
            $runningTasks--
        }
        elseif($taskTab.ContainsKey($_.Id) -and $_.State -eq "Error"){
            $taskTab.Remove($_.Id)
            $runningTasks--
        }
    }
    Start-Sleep -Seconds 15
}

#
#Disconnect-VIserver -server $vchost -confirm:$false
#
#
#
#foreach($Name in $newVmList){
#    $taskTab[(New-VM -VM (Get-VM $modelVm) -Name $Name -VMHost (Get-VMHost -Name $esxName) -RunAsync).Id] = $Name
#}

Cleaning up

Ok, you’ve just created 1000 VM’s and you’re thinking “Dammit Foley! How do I get rid of all of these VM’s quickly before my boss finds out!” Easy Chief, I’ve got you covered. Below is the Remove-VM’s script that uses Luc’s code, again, to troll thru the VM’s, create a hash table and delete them.

 

#
# 21-Apr-2010, Mike Foley, A simple Powershell/PowerCLI script to delete a bunch of VM's. Written from lots of examples
#------------------------------------------------------------------------------
# 1st draft written by Mike Foley, @mikefoley, Virtualization Evangelist, RSA, The Security Division of EMC
# Contributions from Alan Renouf, @alanrenouf, EMC vSpecialist and PowerCLI guru
# Many thanks to those who's code we used, especially Luc Dekens, @LucD
#------------------------------------------------------------------------------
# All information, statements, and descriptions herein are offered AS IS
# only and are provided for informational purposes only. EMC Corporation
# does not guarantee the performance of, or offer a warranty of any kind,
# whether express, implied, or statutory, regarding the information herein
# or the compatibility of the information herein with EMC Corporation software
# or hardware products or other products.
#------------------------------------------------------------------------------
#
# You'll need to have the PowerCLI cmdlets installed. Get these from VMware.
# http://www.vmware.com/go/powercli
#
#Things to change
$vchost = "10.5.103.185"
$vm_prefix = "DEMO_VM_"
$VM_Name = $vm_prefix + "*"
#
#
Add-PSSnapin -Name "VMware.VimAutomation.Core" -ErrorAction SilentlyContinue
#
#
Connect-VIserver $vchost -user administrator -password emcworld
#
$list = Get-VM $VM_Name

foreach ($vm in $list){
    If ($vm.powerstate -eq "PoweredOff") {
    Write-Host "Removing PoweredOff VM " $vm
    Remove-VM -DeleteFromDisk -vm $vm -RunAsync -confirm:$false }
    ElseIf ($vm.powerstate -eq "PoweredOn"){
    Write-Host "Creating Hash Table and Stopping VM " $vm
    $taskTab[(Stop-VM  -vm $vm -RunAsync -confirm:$false).Id] = $vm}
    }



# Start each VM that is completed
$runningTasks = $taskTab.Count
while($runningTasks -gt 0){
    Get-Task | % {
        if($taskTab.ContainsKey($_.Id) -and $_.State -eq "Success"){
            $xx = Get-VM $taskTab[$_.Id] 
            Write-Host "Removing VM from Hash Table " $xx.Name
            Get-VM $taskTab[$_.Id] | Remove-VM -DeleteFromDisk -RunAsync -confirm:$false 
            $taskTab.Remove($_.Id)
            $runningTasks--
        }
        elseif($taskTab.ContainsKey($_.Id) -and $_.State -eq "Error"){
            $taskTab.Remove($_.Id)
            $runningTasks--
        }
    }
    Start-Sleep -Seconds 15
}

 

Enjoy!

mike