RapidClone.psm1
$bootVmScriptBlock = { sleep $args[0] try{ if($args[3]){ $vcenter = Connect-VIServer -Server $args[2] -User $args[3] -Password $args[4] }else{ $vcenter = Connect-VIServer -Server $args[2] } }catch{ Write-Error "Failed to connect to vcenter {0}" -f $args[2] } try{ Start-VM -RunAsync -VM $args[1] }catch{ Write-Error "Failed to start vm {0}" -f $args[1] }finally{ try{ Disconnect-VIServer $vcenter -Force -Confirm:$false }catch{} } } function GetTag($name,$category){ # ----------------------------- # Get/Set vm tag # ----------------------------- if($name){ try{ $cat = Get-TagCategory -Name $category }catch{ $cat = New-TagCategory -Name $category -EntityType VM -Description "Used for rapid cloning" } try{ $tag = Get-Tag -Name $name -Category $category }catch{ $tag = New-Tag -Name $name -Category $category } return $tag }else{ return $null } } function WriteWithColor($text,$state,$color){ # ----------------------------- # fancy output with color and state # ----------------------------- Write-Host "[" -NoNewline Write-Host $state -NoNewline -ForegroundColor $color Write-Host ("] {0}" -f $text) } function ProcessVmTasks($tasks,$description,[switch]$loop,[switch]$startvm,$startjobs,[switch]$ignoreTags,[switch]$nooutput,$outputhash){ # ----------------------------- # a vm task processor # ----------------------------- while(($tasks.Count -ne 0) -or ($startvm -and ($startjobs.Count -ne 0))){ # define a list of tasks to be remove in case of completed $todelete = @() # loop all vm tasks names foreach($t in $tasks.Keys){ try{ # get vm task $task = get-task -id $tasks[$t].id -ErrorAction stop # if success if($task.State -eq "Success"){ if(-not $nooutput){ $outputhash[$t].state = "Success" } # do we need to start the vm ? if(-not $startvm){ # if not, we are done, set as success WriteWithColor -text ("{0} {1}" -f $description,$t) -state "Success" -color green }else{ # start the vm in the background using a start-job # we add a random delay to not stress the vm environment $delay = random -Minimum 0 -Maximum $RandomBootDelaySeconds if($credentials){ $startjobs[$t] = start-job -Name "Starting $t" -ScriptBlock $bootVmScriptBlock -ArgumentList @($delay,$t,$Vcenter,$credentials.UserName,$credentials.GetNetworkCredential().Password) }else{ $startjobs[$t] = start-job -Name "Starting $t" -ScriptBlock $bootVmScriptBlock -ArgumentList @($delay,$t,$Vcenter,"","") } # set as cloned WriteWithColor -text ("{0} {1} [starting with delay of $delay]" -f $description,$t) -state "Cloned" -color yellow } # if creating clones, we might register tags if($tagshash[$t] -and -not $ignoreTags){ $vm = get-vm $t $out = New-TagAssignment -Tag $tagshash[$t] -Entity $vm } # mark to remove from task list, cloning is done $todelete+=$t }elseif($task.State -ne "Running" -and $task.State -ne "Queued"){ # something went wrong here, it's not success nor running WriteWithColor -text $t -state $task.State -color red if(-not $nooutput){ $outputhash[$t].state = $task.State } # mark to remove $todelete+=$t } } catch{$todelete+=$t} } # process remove tasks $todelete | %{$tasks.Remove($_)} # checking start jobs status $todelete = @() # we loop all started jobs foreach($j in $startjobs.Keys){ try{ # get the job status $job = get-job -id $startjobs[$j].id -ErrorAction stop # if completed if($job.State -eq "Completed"){ # get the actual result $result = $job | receive-job if($result.State -eq "Running"){ WriteWithColor -text $j -state "Started" -color green }else{ WriteWithColor -text $j -state $result.State -color red } # mark to delete from jobs $todelete+=$j }elseif($job.State -ne "Running" -and $job.State -ne "Queued"){ # something went wrong, nor completed, nor running WriteWithColor -text $j -state $job.State -color red $todelete+=$j } } catch{ WriteWithColor -text $_.Exception.Message -state "Warning" -color yellow $todelete+=$j } } # remove jobs $todelete | %{$startjobs.Remove($_)} # do we go in a loop or not ? if($loop){ sleep 1 }else{ break } } } function CheckPlugin($hostname,$hostId){ # ----------------------------- # checks if the nas plugin is installed # ----------------------------- $list = @() if($hostId){ $vmHost = Get-VMHost -Id $hostId }else{ $vmHost = Get-VMHost -Name $hostname } if($vmHost){ $vmHostName = $vmhost.Name $esxcli = $vmHost | Get-EsxCli -V2 $list = $esxcli.software.vib.list.Invoke() $plugin = $list| ?{$_.Name -match "NetappNasPlugin"} if($plugin){ WriteWithColor -text ("[{0}] {1} version {2}" -f $vmHostName,$plugin.Name,$plugin.Version) -state "Installed" -color green }else{ Throw "[$vmHostName] The NetAppNasPlugin is not installed" } } } function GetResourcePoolHosts($resourcepool){ try{ $rp = Get-ResourcePool -Name $resourcepool $cluster = Get-Cluster -Id $rp.ExtensionData.Owner return $cluster.ExtensionData.Host }catch{ Throw "ResourcePool [$resourcepool] is not in a vmware cluster" } } function getNewCloneNames($nametemplate,$count,$fillGaps){ $nametemplateNrPlaceholder = "" if($nametemplate -match "(#+)"){ $nametemplateNrPlaceholder = $Matches[1] WriteWithColor -text "Name template [$NameTemplate]" -state "Validated" -color green }else{ throw "No proper name template [$NameTemplate], it must contain 1 or more hashtags (#) as number placeholders to make the clones unique." } $vmSearchPattern = $nametemplate -replace $nametemplateNrPlaceholder,"*" $vmNumberRegex = $nametemplate -replace $nametemplateNrPlaceholder,"([0-9]+)" $currentVms = @(get-vm $vmSearchPattern) | %{$_.Name} $freeNumbers = @() $takenNumbers = @() foreach($vm in $currentVms){ if($vm -match $vmNumberRegex){ $takenNumbers+=[int]($Matches[1]) } } $freeNumbers = @(1..10000) | ?{$_ -notin $takenNumbers} $lastNumber = 0 if($takenNumbers.Count -gt 0){ $lastNumber = ($takenNumbers | Sort-Object)[-1] } if($fillGaps){ $newNumbers = $freeNumbers | Sort-Object | select -first $count }else{ $newNumbers = ($lastNumber+1)..($lastNumber+$count) } return $newNumbers | %{ $nametemplate -replace $nametemplateNrPlaceholder,([string]($_)).PadLeft($nametemplateNrPlaceholder.Length,"0") } } function vmExists($name,[switch]$passthru){ $check = $null try{ $check = get-vm -Name $name }catch{ } if(-not $passthru){ return [bool]$check }else{ return $check } } function vmHostExists($name,[switch]$passthru){ $check = $null try{ $check = Get-VMHost -Name $name }catch{ } if(-not $passthru){ return [bool]$check }else{ return $check } } function resourcePoolExists($name,[switch]$passthru){ $check = $null try{ $check = Get-ResourcePool -Name $name }catch{ } if(-not $passthru){ return [bool]$check }else{ return $check } } function datastoreExists($name,[switch]$passthru){ $check = $null try{ $check = Get-Datastore -Name $name }catch{ } if(-not $passthru){ return [bool]$check }else{ return $check } } function osCustomizationSpecExists($name,[switch]$passthru){ $check = $null try{ $check = Get-OSCustomizationSpec -Name $name }catch{ } if(-not $passthru){ return [bool]$check }else{ return $check } } function parseVm($vm,[switch]$ignoreErrors,[switch]$silent,[switch]$noTemplateCheck){ # ----------------------------- # checks a vm and returns a hash with common info # ----------------------------- $return = @{} $templatevm = $null if(-not $noTemplateCheck){ try{ # is template ? $templatevm = get-template -Name $vm $return["vmhost"] = (Get-VMHost -id ($templatevm.HostId)).Name $return["istemplate"]=$true if(-not $silent){ WriteWithColor -text "template [$vm]" -state "Exists" -color green } }catch{} } if(-not $templatevm){ try{ # is normal vm $templatevm = get-vm -name $vm if(-not $silent){ WriteWithColor -text "Source vm [$vm]" -state "Exists" -color green } $return["vmhost"] = $templatevm.VMHost.Name $return["istemplate"] = $false }catch{ if(-not $ignoreErrors){ Throw "VM or Template [$vm] not found" } } } if($templatevm){ # if found $return["name"] = $vm $return["tags"] = @{} $return["datastore"] = "" $return["resourcepool"] = "" try{ $currentDatastores = @($templatevm.ExtensionData.Storage.PerDatastoreUsage) if(($currentDatastores.Count -gt 1) -and (-not $ignoreErrors)){ Throw "The source vm has more than 1 datastore, rapid cloning not possible" } $return["datastore"] = (Get-Datastore -id ($currentDatastores[0].Datastore)).Name }catch{ Throw "No source datastore present" } if($templatevm.ResourcePool.Name -ne "Resources"){ $return["resourcepool"] = $templatevm.ResourcePool.Name } $tags = (Get-TagAssignment -Entity $templatevm).Tag $tagshash[$vm] = $tags foreach($tag in $tags){ $return["tags"][$tag.Category.Name] = $tag.Name } } return $return } function stopVms($vms,[switch]$silent){ $stoptasks = @{} foreach($v in $vms){ try{ $vm = get-vm $v if($vm.PowerState -eq "PoweredOn"){ $stoptasks["$vm"] = Stop-VM -VM $vm -RunAsync -Confirm:$false }else{ WriteWithColor -text "Stopping clone $vm" -state $vm.PowerState -color green } ProcessVmTasks -tasks $stoptasks -description "Stopping clone" -ignoreTags -nooutput }catch{ if(-not $silent){ WriteWithColor -text "Stopping clone $v" -state "Unknown vm" -color red } } } ProcessVmTasks -tasks $stoptasks -description "Stopping clone" -loop -ignoreTags -nooutput } function removeVms($vms,[switch]$silent){ $removetasks = @{} foreach($v in $vms){ try{ $vm = get-vm $v $removetasks["$vm"] = Remove-VM -VM $vm -DeletePermanently -RunAsync -Confirm:$false ProcessVmTasks -tasks $removetasks -description "Removing clone" -ignoreTags -nooutput }catch{ if(-not $silent){ WriteWithColor -text "Removing clone $v" -state "Unknown vm" -color red } } } ProcessVmTasks -tasks $removetasks -description "Removing clone" -loop -ignoreTags -nooutput } function initTempTemplates($datastores,$sourcevmhash){ # ----------------------------- # creates datastore-local templates for rapid cloning # ----------------------------- $ttemplates = @() $datastores = @($datastores) $cDatastore = $sourcevmhash.datastore $cHost = $sourcevmhash.vmhost $cIstemplate = $sourcevmhash.istemplate try{ foreach($datastore in $datastores){ if($datastore -eq $cDatastore){ WriteWithColor -text "[$datastore] is the source datastore, rapid cloning" -state "Info" -color yellow }else{ WriteWithColor -text "[$datastore] is not the source datastore, a slow copy is needed first" -state "Info" -color yellow } $copyName = "{0}__{1}__{2}" -f $datastore,$sourcevmhash.name,(get-date -Format "yyyyMMdd") $ttemplates+=$copyName if((vmExists -name $copyName) -and $ReuseExistingTemporaryTemplates){ WriteWithColor -text "Re-using existing temporary template [$copyName]" -state "Success" -color green }else{ removeVms -vms @($copyName) -silent if($cIstemplate){ $task = New-Vm -VMHost $cHost -Datastore $datastore -Template $sourcevmhash.name -Name $copyName -RunAsync }else{ $task = New-Vm -VMHost $cHost -Datastore $datastore -VM $sourcevmhash.name -Name $copyName -RunAsync } $out = $task | Wait-Task WriteWithColor -text "Temporary template [$copyName]" -state "Success" -color green } } }catch{ WriteWithColor -text $_.Exception.Message -state "Error" -color red WriteWithColor -text $_.Exception.InnerException.Message -state "Error" -color red if($RemoveTemporaryTemplates){ removeVms -vms @($ttemplates) -silent } Throw "Something went wrong in preparing templates" } return $ttemplates } function cloneVm($name,$vmhost,$resourcepool,$oscustomizationspec,$sourcevm,$datastore,$istemplate){ $expression = "New-VM -Name $name" if($istemplate){ $expression += " -Template" }else{ $expression += " -VM" } $expression += " $sourcevm -Datastore $datastore -RunAsync" if($resourcepool){ $expression += " -ResourcePool $resourcepool" }else{ $expression += " -VMHost $vmhost" } if($OSCustomizationSpec){ $expression += " -OsCustomizationSpec $oscustomizationspec" } Write-Verbose "Invoking : $expression" return Invoke-Expression -Command $expression } function connectVcenter($Vcenter,$Credentials){ if($Credentials){ $VcenterServer = Connect-VIServer $Vcenter -Credential $Credentials }else{ $VcenterServer = Connect-VIServer $Vcenter } } <# .Synopsis Rapidly clones multiple vm's by leveraging VAAI and the NetApp Nas Plugin. .DESCRIPTION Using NetApps Nas Plugin and the vmware VAAI technology, vmware can leverage NetApp file cloning technology and thus rapidly (in seconds) clone vm's. This cmdlet allows you to easily clone a source vm or a source template. When rapid cloning is not possible (for example template not on then same datastore), this cmdlet will optimize the cloning process by distributing the source template to each datastore and then use rapid cloning withing the same datastore. .EXAMPLE New-RapidClone -Vcenter jabba.slash.local -Credentials $credentials -SourceVm centostemplate -NameTemplate "test_##" -Count 3 -StartVM -ResourcePool RP1 -NoNetappNasPluginCheck [Connected] Vcenter Connection [jabba.slash.local] [Exists] Source vm [centostemplate] [Exists] ResourcePool is provided [RP1] [Validated] Name template [test_##] [Info] Inheriting datastore from source [r2d2-ds01] [Info] [r2d2-ds01] is the source datastore, rapid cloning [Success] Temporary template [r2d2-ds01__centostemplate__20200518] [Cloned] Adding clone test_01 [starting with delay of 11] [Cloned] Adding clone test_02 [starting with delay of 3] [Cloned] Adding clone test_03 [starting with delay of 50] [Started] test_02 [Started] test_01 [Started] test_03 [Success] Removing clone r2d2-ds01__centostemplate__20200518 [Disconnected] Vcenter Connection [jabba.slash.local] .OUTPUTS The output is a list objects with the clone information. Pipe it for example to convertto-csv and out-file to save the results in a CSV-file. #> function New-RapidClone{ [cmdletbinding()] param( # Vcenter server [Parameter(Mandatory=$true)] [string]$Vcenter, # PSCredentials, if non given, we assume the current user has direct access. [PSCredential]$Credentials, # The source vm name or template name [Parameter(Mandatory=$true)] [string]$SourceVm, # The naming convention template. Add hashtags (#) where you want auto numbering. # The script is smart enough to check whether [Parameter(Mandatory=$true)] [string]$NameTemplate, # Number of clones to create [Parameter(Mandatory=$true)] [int]$Count, # Indicates if the clones need to be booted. # Booting happens in the background and a random delay is added to not boot all clones at once [switch]$StartVM=$false, # This will not re-create temorary templates, but re-use existing ones if they exist # When running tests, they might gain time, as you slow copied templates can be re-used. # Use in combination with switch RemoveTemporaryTemplates, by setting it to $false. [switch]$ReuseExistingTemporaryTemplates, # Remove temporary template after execution # When running tests, you may turn these off to avoid re-copying templates # Use the ReuseExistingTemporaryTemplates flag to re-use them [switch]$RemoveTemporaryTemplates=$true, # The maximum random boot delay in seconds. If you create more clones, you could make this higher to spread the boot processes. [int]$RandomBootDelaySeconds=60, # The script will automatically search for the last number and continue from there. # This switch will start from 1 instead and start filling gaps. [switch]$FillGaps, # The script does a Netapp Nas Plugin check. With this flag you can omit this check # If you pass a resourcepool, we assume the resourcepool is on a vmware cluster and we will check every host in the cluster [switch]$NoNetappNasPluginCheck, # A list of datastore over which the script should spread the clones. # If non are given. The datastore where the source vm lives is used. [string[]]$Datastores, # The destination VMHost # You can also choose for a resource pool # If both are missing, the VMHost of the source VM is used. [string]$VMHost, # The destination ResourcePool # You can also choose for a single VMHost # If both are missing, the VMHost of the source VM is used. [string]$ResourcePool, # OSCustomizationSpec [string]$OSCustomizationSpec ) Begin{ $tagshash = @{} $ErrorActionPreference = "stop" # ---------------------------------- # Connecting to vcenter # ---------------------------------- try{ ConnectVcenter -Vcenter $Vcenter -Credentials $Credentials WriteWithColor -text "Vcenter Connection [$Vcenter]" -state "Connected" -color green }catch{ Throw "Failed to connect to vcenter $Vcenter" } } Process{ try{ $currentHost = "" $currentDatastore = "" $sourceVmIsTemplate=$false # ---------------------------------- # Checking source vm # ---------------------------------- $vmhash = parseVm -vm $SourceVm $currentHost = $vmhash.vmhost $currentDatastore = $vmhash.datastore $sourceVmIsTemplate = $vmhash.istemplate # ---------------------------------- # check vm host # ---------------------------------- if($VMHost){ if(vmHostExists -name $VMHost){ WriteWithColor -text "VMHost is provided [$VMHost]" -state "Exists" -color green }else{ Throw "The provided VMHost [$VMHost] is not found." } } # ---------------------------------- # check resource pool # ---------------------------------- if($ResourcePool){ if(resourcePoolExists -name $ResourcePool){ WriteWithColor -text "ResourcePool is provided [$ResourcePool]" -state "Exists" -color green }else{ Throw "The provided ResourcePool [$ResourcePool] is not found." } } # ---------------------------------- # fall back to template resources # ---------------------------------- if(-not $VMHost -and -not $ResourcePool){ WriteWithColor -text "Inheriting destination from source [$currentHost]" -state "Info" -color cyan $VMHost = $currentHost } # ---------------------------------- # check if Netapp Nas Plugin installed # ---------------------------------- if(-not $NoNetappNasPluginCheck -and $VMHost){ CheckPlugin -hostname $VMHost } if(-not $NoNetappNasPluginCheck -and $ResourcePool){ $rpHosts = GetResourcePoolHosts -resourcepool $ResourcePool $rpHosts | %{CheckPlugin -hostid $_} } # ---------------------------------- # check OSCustomizationSpec # ---------------------------------- if($OSCustomizationSpec){ if(osCustomizationSpecExists -name $OSCustomizationSpec){ WriteWithColor -text "OSCustomizationSpec is provided [$OSCustomizationSpec]" -state "Exists" -color green }else{ Throw "The provided OSCustomizationSpec [$OSCustomizationSpec] is not found." } } # ---------------------------------- # Getting new free auto increment names # ---------------------------------- $cloneNames = getNewCloneNames -nametemplate $NameTemplate -count $Count -fillGaps $FillGaps # ---------------------------------- # searching and checking datastores # ---------------------------------- if($Datastores){ foreach($datastore in $Datastores){ if(datastoreExists -name $datastore){ WriteWithColor -text "Datastore [$datastore]" -state "Exists" -color green }else{ Throw "Datastore [$datastore] not found" } } }else{ WriteWithColor -text "Inheriting datastore from source [$currentDatastore]" -state "Info" -color cyan $Datastores = @($currentDatastore) } ##################################### # AT THIS POINT EVERYTHING IS CHECKED # NOW THE REAL COPYING STARTS # FIRST WE WILL MAKE SURE THAT EVERY # DATASTORE HAS A COPY ##################################### $tempTemplates = @() $tempTemplates+=initTempTemplates -datastores $Datastores -sourcevmhash $vmhash # ---------------------------------- # Start cloning # ---------------------------------- $clonetasks = @{} $startjobs = @{} $outputhash = @{} # Getting tags $tags = @() $tags += GetTag -name $SourceVm -category ParentTemplate $tags += GetTag -name $NameTemplate -category NameTemplate $tags += GetTag -name $OSCustomizationSpec -category OsCustomizationSpec $tags = $tags | ?{$_} # remove empty tags $i=0 $roundrobin=0 # create all clones foreach($clonename in $cloneNames){ $i++ $pct = [int]($i/$count*100) Write-Progress -Activity "Cloning" -Status "Adding $clonename" -PercentComplete $pct # round robin get template & datastore $thisdatastore = $Datastores[$roundrobin] $thistemplate = $tempTemplates |?{([string]$_).StartsWith("{0}__" -f $thisdatastore)} $roundrobin++ if($roundrobin -ge $Datastores.Count){ $roundrobin=0 } # register the tags $tagshash[$clonename] = $tags # set outputhash $outputhash[$clonename]=@{} $outputhash[$clonename]["vm"] = $clonename $outputhash[$clonename]["vmhost"] = $VMHost $outputhash[$clonename]["datastore"] = $thisdatastore $outputhash[$clonename]["parenttemplate"] = $SourceVm $outputhash[$clonename]["nametemplate"] = $NameTemplate $outputhash[$clonename]["oscustomizationspec"] = $OSCustomizationSpec $outputhash[$clonename]["resourcepool"] = $ResourcePool # invoke the clone $clonetasks["$clonename"] = cloneVm -name $clonename -vmhost $VMHost -resourcepool $ResourcePool -oscustomizationspec $OSCustomizationSpec -sourcevm $thistemplate -datastore $thisdatastore # process completed tasks ProcessVmTasks -tasks $clonetasks -description "Adding clone" -startjobs $startjobs -startvm:$StartVM -outputhash $outputhash } # process final tasks Write-Progress -Activity "Adding clone task" -Status "Done" -Completed ProcessVmTasks -tasks $clonetasks -description "Adding clone" -startjobs $startjobs -startvm:$StartVM -loop -outputhash $outputhash }catch{ WriteWithColor -text $_.Exception.Message -state "Error" -color red if($_.Exception.InnerException.Message){ WriteWithColor -text $_.Exception.InnerException.Message -state "Error" -color red } } } End{ try{ # remove templates if required if($RemoveTemporaryTemplates){ removeVms -vms @($tempTemplates) -silent } # disconnect $out = Disconnect-VIServer -Server $vcenter -Confirm:$false WriteWithColor -text "Vcenter Connection [$Vcenter]" -state "Disconnected" -color green }catch{} # send to output stream for further piping foreach($h in $outputhash.Keys){ New-Object -TypeName PSObject -Property $outputhash[$h] } } } <# .Synopsis Rapidly re-clones multiple vm-clones by leveraging VAAI and the NetApp Nas Plugin. .DESCRIPTION Using NetApps Nas Plugin and the vmware VAAI technology, vmware can leverage NetApp file cloning technology and thus rapidly (in seconds) clone vm's. This cmdlet allows you to easily re-clone previously clones vm's. Using tags on the vm's, we remember the original template and re-deploy it with the same name. When rapid cloning is not possible (for example template not on then same datastore), this cmdlet will optimize the cloning process by distributing the source template to each datastore and then use rapid cloning withing the same datastore. .EXAMPLE Update-RapidClone -Vcenter jabba.slash.local -Credentials $credentials -StartVM -Names (get-vm test*).Name [Connected] Vcenter Connection [jabba.slash.local] [Success] Stopping clone test_01 [Success] Stopping clone test_02 [Success] Stopping clone test_03 [Success] Removing clone test_01 [Success] Removing clone test_02 [Success] Removing clone test_03 [Exists] Source vm [centostemplate] [Info] [r2d2-ds01] is the source datastore, rapid cloning [Success] Temporary template [r2d2-ds01__centostemplate__20200518] [Cloned] Adding clone test_01 [starting with delay of 12] [Cloned] Adding clone test_02 [starting with delay of 34] [Cloned] Adding clone test_03 [starting with delay of 15] [Started] test_01 [Started] test_03 [Started] test_02 [Success] Removing clone r2d2-ds01__centostemplate__20200518 [Disconnected] Vcenter Connection [jabba.slash.local] .OUTPUTS The output is a list objects with the clone information. Pipe it for example to convertto-csv and out-file to save the results in a CSV-file. #> function Update-RapidClone{ [cmdletbinding()] param( # Vcenter server [Parameter(Mandatory=$true)] $Vcenter, # PSCredentials, if non given, we assume the current user has direct access. [PSCredential]$Credentials, # Indicates if the clones need to be booted. # Booting happens in the background and a random delay is added to not boot all clones at once [switch]$StartVM=$false, # This will not re-create temorary templates, but re-use existing ones if they exist # When running tests, they might gain time, as you slow copied templates can be re-used. # Use in combination with switch RemoveTemporaryTemplates, by setting it to $false. [switch]$ReuseExistingTemporaryTemplates, # Remove temporary template after execution # When running tests, you may turn these off to avoid re-copying templates # Use the ReuseExistingTemporaryTemplates flag to re-use them [switch]$RemoveTemporaryTemplates=$true, # The maximum random boot delay in seconds. If you create more clones, you could make this higher to spread the boot processes. [int]$RandomBootDelaySeconds=60, # An array of vm names to be re-deployed [Parameter(Mandatory=$true)] [string[]]$Names ) Begin{ $tagshash = @{} $ErrorActionPreference = "stop" # ---------------------------------- # Connecting to vcenter # ---------------------------------- try{ ConnectVcenter -Vcenter $Vcenter -Credentials $Credentials WriteWithColor -text "Vcenter Connection [$Vcenter]" -state "Connected" -color green }catch{ Throw "Failed to connect to vcenter $Vcenter" } } Process{ try{ $startjobs = @{} $clonetasks = @{} $templateshash = @{} # we analyze all passed vm's $vmhashlist = $Names | %{parseVm -vm $_ -ignoreErrors -silent -noTemplateCheck} $tempTemplates = @() $sourcevmhashes = @{} $outputhash = @{} # remove the old clones stopVms -vms $Names removeVms -vms $Names # find all template-datastore combinations foreach($vmhash in $vmhashlist){ $parenttemplate = $vmhash.tags.ParentTemplate $datastore = $vmhash.datastore if(-not $templateshash.ContainsKey($parenttemplate)){ $templateshash[$parenttemplate] = @{} $templateshash[$parenttemplate]["data"] = parseVm -vm $parenttemplate $templateshash[$parenttemplate]["datastores"] = @() } if($datastore -notin $templateshash[$parenttemplate]["datastores"]){ $templateshash[$parenttemplate]["datastores"] += $datastore } } # prep all templates for rapid cloning foreach($k in $templateshash.Keys){ $tempTemplates+=initTempTemplates -datastores $templateshash[$k].datastores -sourcevmhash $templateshash[$k].data } # we loop the list $i = 0 foreach($vmhash in $vmhashlist){ $vmname = $vmhash.name $vmhost = $vmhash.vmhost $datastore = $vmhash.datastore $parenttemplate = $vmhash.tags.ParentTemplate $nametemplate = $vmhash.tags.NameTemplate $oscustomizationspec = $vmhash.tags.OsCustomizationSpec $resourcepool = $vmhash.resourcepool # set output data $outputhash[$vmname] = @{} $outputhash[$vmname]["vm"] = $vmname $outputhash[$vmname]["vmhost"] = $vmhost $outputhash[$vmname]["datastore"] = $datastore $outputhash[$vmname]["parenttemplate"] = $parenttemplate $outputhash[$vmname]["nametemplate"] = $nametemplate $outputhash[$vmname]["oscustomizationspec"] = $oscustomizationspec $outputhash[$vmname]["resourcepool"] = $resourcepool # progress bar $i++ $pct = [int]($i/$vmhashlist.Keys.Count*100) Write-Progress -Activity "Cloning" -Status "Adding $clonename" -PercentComplete $pct # quick check if the vm is compliant to this script, we expect certain tags if(-not $parenttemplate){ WriteWithColor -text "$vmname is not tagged with ParentTemplate" -state "Warning" -color yellow continue } # redeploy the clones $clonetasks[$vmname] = clonevm -name $vmname -vmhost $vmhost -resourcepool $resourcepool -oscustomizationspec $oscustomizationspec -sourcevm $parenttemplate -datastore $datastore # check if tasks have completed ProcessVmTasks -tasks $clonetasks -description "Adding clone" -startjobs $startjobs -startvm:$StartVM -outputhash $outputhash } # finalize tasks Write-Progress -Activity "Adding clone task" -Status "Done" -Completed ProcessVmTasks -tasks $clonetasks -description "Adding clone" -startjobs $startjobs -startvm:$StartVM -loop -outputhash $outputhash }catch{ WriteWithColor -text $_.Exception.Message -state "Error" -color red if($_.Exception.InnerException.Message){ WriteWithColor -text $_.Exception.InnerException.Message -state "Error" -color red } } } End{ try{ # remove templates if required if($RemoveTemporaryTemplates){ removeVms -vms @($tempTemplates) -silent } # disconnect $out = Disconnect-VIServer -Server $vcenter -Confirm:$false WriteWithColor -text "Vcenter Connection [$Vcenter]" -state "Disconnected" -color green }catch{} # send to output stream for further piping foreach($h in $outputhash.Keys){ New-Object -TypeName PSObject -Property $outputhash[$h] } } } <# .Synopsis Rapidly removes multiple vm's. .DESCRIPTION This cmdlet is dangerous. It rapidly stops and remove vm's in bulk. .EXAMPLE Remove-RapidClone -Vcenter jabba.slash.local -Credentials $credentials -Names (get-vm test*).name -Force [Connected] Vcenter Connection [jabba.slash.local] [Success] Stopping clone test_01 [Success] Stopping clone test_02 [Success] Stopping clone test_03 [Success] Removing clone test_01 [Success] Removing clone test_02 [Success] Removing clone test_03 [Disconnected] Vcenter Connection [jabba.slash.local] #> function Remove-RapidClone{ [cmdletbinding(SupportsShouldProcess = $true,ConfirmImpact='High')] param( # Vcenter server [Parameter(Mandatory=$true)] $Vcenter, # PSCredentials, if non given, we assume the current user has direct access. [PSCredential]$Credentials, # An array of vm names to be removed [Parameter(Mandatory=$true)] [string[]]$Names, # Same as -confirm:$false [switch]$Force ) Begin{ ################################### # START CODE ################################### $ErrorActionPreference = "stop" # ---------------------------------- # Connecting to vcenter # ---------------------------------- try{ ConnectVcenter -Vcenter $Vcenter -Credentials $Credentials WriteWithColor -text "Vcenter Connection [$Vcenter]" -state "Connected" -color green }catch{ Throw "Failed to connect to vcenter $Vcenter" } } Process{ try{ ##################################### # REMOVE ACTION ##################################### if ($Force -or $PSCmdlet.ShouldProcess($Names,"Remove vm's")) { stopVms -vms $Names removeVms -vms $Names } }catch{ WriteWithColor -text $_.Exception.Message -state "Error" -color red if($_.Exception.InnerException.Message){ WriteWithColor -text $_.Exception.InnerException.Message -state "Error" -color red } } } End{ try{ # disconnect $out = Disconnect-VIServer -Server $vcenter -Confirm:$false WriteWithColor -text "Vcenter Connection [$Vcenter]" -state "Disconnected" -color green }catch{} } } |