Multiple simultaneous scripted clones

Multiple simultaneous scripted clones

I've built this script for a customer that required multiple simultaneous clonings that he could easily configure to back up his production VMs. I've included a mechanism to limit the maximum number of concurrent clonings to maximise performance, so when using this script it will take some experimentation to find the 'magic number' to minimise the time used to do a full clone run.

The attached ZIP file includes three files:

- ScriptClone.cfg - The config file, one VM per line, will be run in order from top to bottom

- ScriptClone.ps1 - The actual script, includes three extra settings for you to configure

- ScriptClone.bat - A batch file to run the script, change the paths to suit your system

The script will power down the target VM if it already exists, then delete it from disk and then clone the source to the target. The power down steps are included three times with a 20 second gap because experience has shown that powerdowns don't always go smoothly. Also after the deletion of the 'old' target VM there is a wait of 2 minutes to give vCenter a chance to clean it all up and update the inventory.

Here's the source of the script:

#######################################################
# ScriptClone v1.0
# Scripted simultaneous cloning for VMWare vSphere 4
# by Mark Kathmann
# http://www.kathmann.com
#######################################################
#
# Change these three settings:
#
# myVCServer = vCenter Server hostname or IP address
# CFGFILE    = Full path name for config file
# LogFolder  = Full path name for the log files (no trailing slash)
# MaxConc    = Max number of concurrent clones 
#
#######################################################

$myVCServer = "127.0.0.1"
$CFGFILE    = "C:\ScriptClone\ScriptClone.cfg"
$LogFolder  = "C:\ScriptClone\log"
$MaxConc    = 3

#######################################################
#
# Stop editing, the real work starts here
#
#######################################################

# Connect to the vCenter Server
Connect-VIServer -Server $myVCServer -Protocol https

# Read the config file
$cfg = Get-Content $CFGFILE

# Start an empty task table
$taskTab = @{}

# Cycle through the lines in the config file
foreach ($line in $cfg)
{
     # Ignore lines starting with #
     if ($line.substring(0,1) -ne "#")
     {

          # Split the line's arguments
          $b = $line.split(",")

          # Count the no. of arguments, if correct (>=7) then continue
          if ($b.Length -ge 7)
          {
               # Count the active tasks
               $runningTasks = $taskTab.Count

               # If no. of running tasks = max then wait and recheck every 15 seconds
               while ($runningTasks -eq $MaxConc)
               {
                    # Read the active task statuses
                    Get-Task | % {
                         if($taskTab.ContainsKey($_.Id) -and $_.State -eq "Success")
                         {
                              $SC_Clonelog = $LogFolder+"\SCClone_"+$taskTab[http://$_.Id|http://$_.Id]+".log"
                              "{0} Cloning process finished." -f [DateTime]::Now | out-file $SC_CloneLog -append -encoding "ASCII" -force -NoClobber
                              " " | out-file $SC_CloneLog -append -encoding "ASCII" -force -NoClobber
                              $taskTab.Remove($_.Id)
                              $runningTasks--
                         }
                         elseif($taskTab.ContainsKey($_.Id) -and $_.State -eq "Error")
                         {
                              $SC_Clonelog = $LogFolder+"\SCClone_"+$taskTab[http://$_.Id|http://$_.Id]+".log"
                              "{0} Cloning process finished." -f [DateTime]::Now | out-file $SC_CloneLog -append -encoding "ASCII" -force -NoClobber
                              " " | out-file $SC_CloneLog -append -encoding "ASCII" -force -NoClobber
                              $taskTab.Remove($_.Id)
                              $runningTasks--
                         }
                    }

                    # Wait 15 seconds before trying again
                    Start-Sleep -Seconds 15
               }

               # Set the log file Location
               $SC_Clonelog = $LogFolder+"\SCClone_"+$b[0]+".log"

               # Log the start of the cloning
               "{0} Beginning cloning process..." -f [DateTime]::Now | out-file $SC_CloneLog -append -encoding "ASCII" -force -NoClobber

               # Read the command arguments
               $sourceVM       = $b[0]
               $destVmName     = $b[1]
               $vCserver       = $b[2]
               $destDatastore  = $b[3]
               $destHost       = $b[4]
               $resourcePool   = $b[5]
               $vmFolder       = $b[6]

               # Log the stopping of the target VM
               "{0} Stopping target VM..." -f [DateTime]::Now | out-file $SC_CloneLog -append -encoding "ASCII" -force -NoClobber

               # Stop the target VM (3 attempts + wait)
               Stop-VM -VM $destVmName -Confirm:$false
               sleep 20
               Stop-VM -VM $destVmName -Confirm:$false
               sleep 20
               Stop-VM -VM $destVmName -Confirm:$false
               sleep 20

               # Log the deletion of the target VM
               "{0} Deleting target VM..." -f [DateTime]::Now | out-file $SC_CloneLog -append -encoding "ASCII" -force -NoClobber

               # Delete the target VM (+ wait)
               Remove-VM -VM $destVmName -DeleteFromDisk -Confirm:$false
               sleep 120

               # Log the actual clone start
               "{0} Starting clone..." -f [DateTime]::Now | out-file $SC_CloneLog -append -encoding "ASCII" -force -NoClobber

               # Perform the cloning
               $taskTab[http://(New-VM -VMHost $destHost -Name $destVmName -ResourcePool (Get-ResourcePool $resourcePool) -Location $vmFolder -Datastore $destDatastore -VM (Get-VM $sourceVM) -RunAsync).Id|http://(New-VM -VMHost $destHost -Name $destVmName -ResourcePool (Get-ResourcePool $resourcePool) -Location $vmFolder -Datastore $destDatastore -VM (Get-VM $sourceVM) -RunAsync).Id] = $b[0]
          }
     }
}

# All lines in the config file are processed, finish up
# Count the active tasks
$runningTasks = $taskTab.Count

# If no. of running tasks > 0 then wait and recheck every 15 seconds
while ($runningTasks -gt 0)
{
     # Read the active task statuses
     Get-Task | % {
          if($taskTab.ContainsKey($_.Id) -and $_.State -eq "Success")
          {
               $SC_Clonelog = $LogFolder+"\SCClone_"+$taskTab[http://$_.Id|http://$_.Id]+".log"
               "{0} Cloning process finished." -f [DateTime]::Now | out-file $SC_CloneLog -append -encoding "ASCII" -force -NoClobber
               " " | out-file $SC_CloneLog -append -encoding "ASCII" -force -NoClobber
               $taskTab.Remove($_.Id)
               $runningTasks--
          }
          elseif($taskTab.ContainsKey($_.Id) -and $_.State -eq "Error")
          {
               $SC_Clonelog = $LogFolder+"\SCClone_"+$taskTab[http://$_.Id|http://$_.Id]+".log"
               "{0} Cloning process finished." -f [DateTime]::Now | out-file $SC_CloneLog -append -encoding "ASCII" -force -NoClobber
               " " | out-file $SC_CloneLog -append -encoding "ASCII" -force -NoClobber
               $taskTab.Remove($_.Id)
               $runningTasks--
          }
     }
     # Wait 15 seconds before trying again
     Start-Sleep -Seconds 15
}

As with all programming this code is based on larger or smaller elements of the work of many others before me, too many to mention all here, but I am of course grateful for everyone's hard work.

Attachments
Comments

I edited the .cfg file and added couple of VM names, one vm per line e.g.

test-vm01

test-vm02

and ran the .ps1 but getting this error, pl suggest :-

Exception calling "Substring" with "2" argument(s): "Index and length must

refer to a location within the string.

Parameter name: length"

At C:\Users\Vinod Sikka\Downloads\ScriptClone\ScriptClone\ScriptClone.ps1:41

char:6

+     if ($line.substring(0,1) -ne "#")

+         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    + CategoryInfo          : NotSpecified: (:) [], MethodInvocationException

    + FullyQualifiedErrorId : ArgumentOutOfRangeException

It looks like you might have an empty line in your cfg file

Version history
Revision #:
1 of 1
Last update:
‎04-17-2010 06:13 AM
Updated by: