VMware PowerCLI Automate Nightly Clone Version 2

  • Home
  • VMware PowerCLI Automate Nightly Clone Version 2

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, but this version 2 of the script added some fixes for the log file, resolved log file syntax issues, rotates the log file, and adds an email feature.  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
VMList.txt – basic text file with the names of each VM.
vCenterPWD – Hashed password for vCenter logon.  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.  Just a tip that this seems to be profile specific.  In other words, if you are using a service account of somekind to run the scheduled clone task, you want to create this password while logged in as that service account.

$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 20190808
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
3 days old and a current backup is present.


**Update 20191002, add variable $null commands to resolve compare date problem.

#>
 
 
 
<#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 = 'clone@vsphere.local'
$pwd = Get-Content vCenterPWD | ConvertTo-SecureString
$cred = New-Object System.Management.Automation.PsCredential $username, $pwd
 
LogWrite 'Connect to VIServer'
Connect-VIServer server.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'
$CPFlag = $null
if($CDS1 -match 'SourceDataStore') { $CPFlag = 'DestinationDataStore' }
#if($CDS1 -match 'Partofdatastorename02') { $CPFlag = 'Fulldatastorename01' }
#if($CDS1 -match 'Partofdatastorename03') { $CPFlag = 'Fulldatastorename01' }

if($CPFlag -eq $null) {
LogWrite ('No suitable destination datastore found, exit job for: '+($VM))

#command used to skip the rest of processing for this object.  Return to next in foreach.
continue

}

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 space 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 vmhost.domain.local -Location 'Regular Clone'#-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 -Location 'Regular Clone' | 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 -Location 'Regular Clone' | select Name
    LogWrite 'Match for cloned VMs'
    LogWrite ''
    foreach($vmls in $vmlist) { 
		#clear vars to resolve bad compare date issue 20191002
		$CompareDate = $null
        $var1 = $null
        $var2 = $null
        $var3 = $null
		$var4 = $null
		
        #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.name))
        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
		LogWrite ('Compare Date: '+($var4))
 
            #If clone is older than 1 day, delete it.  Turn this knob based on your requirements.
            if($var4 -gt '3') {
            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))
        }       
    }
}

LogWrite 'Clear log files older than 3 days'
forfiles -p "C:\Scripts\RegularClone" -s -m *.log /D -3 /C "cmd /c del @path"

LogWrite 'Send Email'

#####################End log
LogWrite '---------------------------->'
LogWrite 'End CloneJob.ps1'
$datetime = Get-Date
LogWrite ($datetime)
LogWrite '---------------------------->'
 
LogWrite 'Disconnect vCenter Server'
Disconnect-VIServer vmhost.domain.local -Confirm:$false

#Begin Email with attached log'


$pwd2 = Get-Content RelayCred | ConvertTo-SecureString
$cred2 = New-Object System.Management.Automation.PsCredential $username, $pwd2

# Send email
$emailSmtpServer = "mailserver.domain.com"
$emailSmtpServerPort = "25"
$emailSmtpUser = "domain\relayaccount"
$emailSmtpPass = $pwd2
 
$emailMessage = New-Object System.Net.Mail.MailMessage
$emailMessage.From = "DoNotReply@domain.com"
$emailMessage.To.Add( "myemail@domain.com")
$emailMessage.Subject = "Regular Clone"
$emailMessage.IsBodyHtml = $true
$emailMessage.Body = @"
<p>Regular Clone Log - VCENTER SERVER</strong>.<br>
 <br>
 See attached log file.<br>
By Adam Tyler</p>
"@
 
$SMTPClient = New-Object System.Net.Mail.SmtpClient( $emailSmtpServer , $emailSmtpServerPort )
$SMTPClient.EnableSsl = $true
$SMTPClient.Credentials = New-Object System.Net.NetworkCredential( $emailSmtpUser , $emailSmtpPass );

$attachment = (($PSScriptRoot)+'\'+($Logfile))
$emailMessage.Attachments.Add( $attachment )
 
$SMTPClient.Send( $emailMessage )
 
EXIT

Leave a Reply