VMware PowerCLI Automate Nightly Clone

Here is a script written in PowerShell for VMware’s PowerCLI module.  Basically I needed a quick and dirty backup solution to get some important VMs stored on a different datastore regularly.  It also goes back and cleans up old clones.  It isn’t perfect.  I’d like to write some more safe guards to check the date of each backup and if it didn’t see a current one to skip deletion for a given clone.  Will come back and revisit that, but it is pretty functional.  I am using it today with vCenter 6.5 and PowerCLI 6.5…

I won’t go into the details of how to setup a scheduled task to kick this off or how to install PowerCLI.  Let me know if you have questions: adam@tylerlife.us..

Files needed:

CloneJob.ps1 – The script
VMLinst.txt – basic text file with the names of each VM.
vCenterPWD – Hashed password for vCenter long.  You will only need this if AD integration is not configured.  You’d also have to modify the script to not call this password.  Easy to spot if you’ve ever used the “connect-viserver command.”

If you do not have vCenter domain joined or you are running this from a system that is not a member of the domain, you can generate the password hash file by running the following commands from and elevated PowerCLI prompt.

$cred=Get-Credential
$cred.Password | ConvertFrom-Securestring
$cred.Password | ConvertFrom-Securestring | Set-Content C:\scripts\vCenterPWD

This should prompt you for a username and password.  Enter “administrator@vsphere.local” for example, then convert it to “SecureString” and write it to a file your clone script can reference.

You have your password now in a file.  My script code uses the filename “vCenterPWD” as long as it is in the same directory..

Note regarding this script embedded into this post. I’ve been toying with a new WordPress syntax highligher and had to remove all my double quotes and replace them with singles. Just an FYI in case you experience trouble running it. A quick replace all should do it. I plan to post the full script in Zip format to this site later.

Below are some highlighted areas you are going to want to review and modify for your environment.

<#
Clone script written by Adam Tyler 20180223
Script will read 'VMList.txt' in local directory
check source datastore and VM required space against\
destination.

Will also purge old backups, but only if they are over
1 day old and a current backup is present.


#>



<#This is a cool if statement.  Basically checks to see if 
the PowerCLI modules are loaded, if not it will load them.  
Handy if running manually when testing from a PowerShell window.
#>

If ( ! (Get-module VMware.VimAutomation.core )) {

get-module -name VMware* -ListAvailable | Import-Module

}

Set-Location $PSScriptRoot
push-location ./

#####################Create log file
$datetime = (Get-Date).tostring('dd-MM-yyyy-HH-mm-ss')
$Logfile = ($datetime + '.log')

#####################Log write function
Function LogWrite
{
   Param ([string]$logstring)

   Add-content $Logfile -value $logstring
}
#####################Log write function

#####################Begin log
LogWrite '---------------------------->'
LogWrite 'Begin CloneJob.ps1'
$datetime = Get-Date
LogWrite '$datetime'
LogWrite '---------------------------->'


$username = 'administrator@vsphere.local'
$pwd = Get-Content c:\Scripts\vCenterPWD | ConvertTo-SecureString
$cred = New-Object System.Management.Automation.PsCredential $username, $pwd

LogWrite 'Connect to VIServer'
Connect-VIServer vcenter.domain.local -credential $cred


LogWrite ''
LogWrite '#############Begin begin backup portion of script'
LogWrite ''
LogWrite 'Read VMList to VMs variable'
$VMs = get-content VMList.txt
foreach ($VM in $VMs) {

LogWrite 'Find current datastore for $VM'
#find current datastore and select name, write to variable.
$CurrentDS = Get-Datastore -RelatedObject $VM | select name
$CDS1 = $currentDS.name
LogWrite '$VM Datastore identified $CDS1'

#find appropriate destination datastore.

<#
In my case I had 3 different storage devices.  You may have more.
The important thing is that the $CPFlag variable gets populated
with a different datastore than the VM is on currently.  You can add
"if" statements as needed.

Basically the $CDS1 variable is searched for a string of your choice. 
The common name of a storage device with multiple datastores for example.
if it matches your string, set destination to string of your choice.
#>

LogWrite 'Find appropriate destination datastore'
if($CDS1 -match 'Partofdatastorename01') { $CPFlag = 'Fulldatastorename02' }
if($CDS1 -match 'Partofdatastorename02') { $CPFlag = 'Fulldatastorename01' }
if($CDS1 -match 'Partofdatastorename03') { $CPFlag = 'Fulldatastorename01' }
LogWrite '$VM will be moved to $CPFlag'

#Get VM used space
LogWrite 'Find $VM used space'
$VMSpace = get-vm $VM | select UsedSpaceGb
$VMSpace2 = $VMSpace | where-object {$_ -match '\d{1,}\.\d{2}'} | foreach {$Matches[0]}
LogWrite '$VM requires $VMSpace2 Gb'

#Get destination datastore free space
LogWrite 'Check destination datastore free space'
$DestSpace = get-datastore $CPFlag  | select FreeSpaceGB
$DestSpace2 = $DestSpace | where-object {$_ -match '\d{1,}\.\d{2}'} | foreach {$Matches[0]}
LogWrite '$CPFlag has $DestSpace2 Gb available'

#Math to check available free space
LogWrite ''
$DSAvail = $DestSpace2 - $VMSpace2
LogWrite 'Space after move: $DSAvail'
LogWrite ''

#if statement if free psace check is good, proceed with clone.
LogWrite 'Check if free space on destination datastore is less than 50 Gb after move'
	if($DSAvail -lt '50') {
	echo 'Less Than True!'
	LogWrite '#############'
	LogWrite 'Free space check failed, SKIP clone'
	LogWrite '#############'
	}Else{
	LogWrite '#############'
	LogWrite 'Free space check pass, proceed with clone'
	$vmdatestamp = (Get-Date).tostring('yyyyMMdd-HHmmss')
	LogWrite 'Start clone $VM to $VM-$vmdatestamp'
	
	new-vm -Name $VM-$vmdatestamp -VM $VM -Datastore $CPFlag -vmhost <HOSTIPHERE> -Location 'Clone Tasks'#-DiskStorageFormat thin
	
	LogWrite 'Check Time after clone $VM'
	$datetime = Get-Date
	LogWrite '$datetime'
	LogWrite '#############'	
	}



}

LogWrite ''
LogWrite '#################################Begin cleanup/purge portion of script'
LogWrite ''


#Check for backups from today.  If you don't find any, let's not remove anything.
$vmcheck = get-vm | select Name
$vmcheck2 = $vmcheck | where-object {$_ -match '\d{8}'} | foreach {$Matches[0]}
$CompareDate1 = (Get-Date).tostring('yyyyMMdd')
$vmcheck3 = $vmcheck | where-object {$_ -match $CompareDate1} | foreach {$Matches[0]}
if($vmcheck3 -ne $null) {
	$flag = '1'
	
	}else{
	$flag = '0'
	
}



if($flag -eq '0'){
LogWrite 'Unable to find current backup'
LogWrite 'DO NOT REMOVE BACKUPS'
LogWrite ''

}Else{
LogWrite 'Current backup check passed'

LogWrite 'Get list of VMs for delete candidate'
$vmlist = get-vm | select Name
	LogWrite 'Match for cloned VMs'
	LogWrite ''
	foreach($vmls in $vmlist) { 
	
		#First confirm that I am working with a cloned VM by looking for 8 characters, then '-', then another 6 chars.
		#The naming convention defined above when clones are created.
		if($vmls -match '\d{8}-\d{6}') {
		LogWrite 'match! $vmls'
		LogWrite 'Prep variable for compare'	

		#Grab matched string and doctor it to only look at 8 digit date.
		$var1 = $vmls.name
		$var2 = $var1 -split '-' | Select-String '\d{8}'
		$var3 = $var2 -replace '(?S)'

		#Grab todays date in the same 8 digit format then subtract to compare variance.  How old is the clone?
		$CompareDate = (Get-Date).tostring('yyyyMMdd')
		$var4 = $CompareDate - $var3

			#If clone is older than 1 day, delete it.  Turn this knob based on your requirements.
			if($var4 -gt '1') { 
			LogWrite 'Outdated clone identified!'
			LogWrite ''
			LogWrite 'Check VM running state'
			#refresh vm variable.  I don't know why you have to do this, but the below running state if statement doesn't work unless you do.
			$vmls = Get-VM -Name $vmls.Name
				if($vmls.PowerState -match 'PoweredOff') {
				LogWrite 'VM not running!'			
				remove-vm -VM $vmls.name -DeletePermanently:$true -Confirm:$false
				LogWrite 'Deleted: $vmls'
				LogWrite ''	
				
				}else{				
				LogWrite 'VM running! $vmls.Name'
				LogWrite ''
				echo 'VM running! $vmls.Name'			
				}
		
			}else{
			LogWrite 'No old clone found: $vmls.Name'
			LogWrite ''
			echo 'No old clone found: $vmls.Name'
		
			}
				
		}else{ 
		LogWrite 'No Match found: $vmls.Name'
		LogWrite ''
		echo 'No Match found: $vmls.Name'
		}		
	}
}


#####################End log
LogWrite '---------------------------->'
LogWrite 'End CloneJob.ps1'
$datetime = Get-Date
LogWrite '$datetime'
LogWrite '---------------------------->'

LogWrite 'Disconnect vCenter Server'
Disconnect-VIServer vcenter.domain.local -Confirm:$false

exit

Leave a Reply