Multiple simultaneous scripted clones

Version 2

    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.