VMs by datastore. ⢠VMs by CPU and RAM allocation. The examples given have been developed to meet the requirements of
VERSION 4.0 MAY 2018
Automating Zerto Virtual Replication with PowerShell & REST APIs Whitepaper
Table of Contents 1
2
3
4
5
6
INTRODUCTION .................................................................................................................................................... 4 1.1
Use Cases .....................................................................................................................................................................4
1.2 1.3
REST APIs .....................................................................................................................................................................4 Legal Disclaimer ...........................................................................................................................................................4
BASICS & BEST PRACTICES .................................................................................................................................... 5 2.1
Requirements ..............................................................................................................................................................5
2.2
Using Variables & Arrays .............................................................................................................................................5
2.3 2.4
Encrypting Passwords ..................................................................................................................................................6 Scripting Best Practices................................................................................................................................................6
2.5
Transcripts ...................................................................................................................................................................6
2.6
Loading Modules .........................................................................................................................................................7
2.7
Bypassing Certificate Warnings ...................................................................................................................................7
2.8
Establishing API Sessions .............................................................................................................................................8
2.9
Full Start of Script Example..........................................................................................................................................9
QUERYING & REPORTING ................................................................................................................................... 11 3.1
Use Cases .................................................................................................................................................................. 11
3.1
Listing Unprotected VMs .......................................................................................................................................... 11
3.2
Using Unprotected VM IDs ....................................................................................................................................... 11
3.3 3.4
Listing Protected VMs & VPGs .................................................................................................................................. 12 Long Term RPO & Storage Reporting to CSV ............................................................................................................ 13
3.5
Resource Reports...................................................................................................................................................... 15
3.6
Resource Report Use Cases ...................................................................................................................................... 17
3.7
VPG, VM, VDISK, VNIC & Re-IP Settings Report ....................................................................................................... 18
DAILY EMAIL REPORTS........................................................................................................................................ 27 4.1
Use Cases .................................................................................................................................................................. 27
4.2
Design Methodology ................................................................................................................................................ 27
4.3
Daily Email Report .................................................................................................................................................... 27
AUTOMATING DEPLOYMENT ............................................................................................................................. 67 5.1
Use Cases .................................................................................................................................................................. 67
5.2
Bulk Automated VRA Deployment ........................................................................................................................... 67
5.3
Bulk Automated VPG Creation – ZVM Only.............................................................................................................. 70
5.4
Bulk Automated VPG Creation – ZVM & ZCM .......................................................................................................... 76
5.5
Bulk Automated VPG Creation with Boot Groups & Re-IP – ZVM Only ................................................................... 82
AUTOMATING VM PROTECTION ........................................................................................................................ 89 6.1
Use Cases .................................................................................................................................................................. 89
6.2
Automating VM Protection by vSphere Folder - ZVM Only ..................................................................................... 89
6.3
Automating VM Protection by vSphere Folder - ZVM & ZCM .................................................................................. 97
6.4
Automating VM Protection with vRealize Orchestrator ........................................................................................ 104
6.5
Adding VMs to VPGs ............................................................................................................................................... 105
AUTOMATING ZVR WITH POWERSHELL & REST API WHITEPAPER
2 OF 134
7
8
9
BULK EDIT OPERATIONS ................................................................................................................................... 107 7.1
Bulk VPG Name Changing ....................................................................................................................................... 107
7.2
Bulk Editing VM NIC Settings Including Re-IP & Port Groups ................................................................................. 111
SCHEDULING OFFSITE CLONES ......................................................................................................................... 119 8.1
Use Cases ................................................................................................................................................................ 119
8.2
Design Methodology .............................................................................................................................................. 119
8.3
Scheduled Offsite Clone ......................................................................................................................................... 120
TROUBLESHOOTING ......................................................................................................................................... 134
AUTOMATING ZVR WITH POWERSHELL & REST API WHITEPAPER
3 OF 134
1
INTRODUCTION
1.1 Use Cases This document gives an overview of how to utilize Zerto Virtual Replication REST APIs with PowerShell to automate your virtual infrastructure. In turn, this enables the reduction of manual processes and the realization of the full benefits of software-defined replication and recovery. Highlights of key use cases covered within this document include: • • • • • • •
Automating VM protection Automating VM protection with vRealize Orchestrator Bulk VRA deployment Bulk VPG configuration Scheduling Offsite Clones Daily email reports Bulk Re-IP addressing
Also included is how to obtain reporting on: • • • • • • •
Long term RPO statistics VMs by top journal and recovery storage usage Protected and unprotected VMs VMs by target Hosts for recovery balancing VMs by average bandwidth utilization VMs by &toTimeString=" + $EndDateTime + "&startIndex=0&count=500" $ResourceReport = Invoke-RestMethod -Uri $ResourceReportURL -TimeoutSec 100 -Headers $zertoSessionHeader -ContentType $TypeJSON $ResourceReportAPIArray = $ResourceReport.ArrayOfVmResourcesInfo.VmResourcesInfo # Building table from Array by VpgName
AUTOMATING ZVR WITH POWERSHELL & REST API WHITEPAPER
16 OF 134
$ResourceReportAPITable = $ResourceReport | Sort-Object -Property VpgName | format-table -Property Timestamp,VpgName,VmName,TargetVraName $ResourceReportAPITable
3.6 Resource Report Use Cases Once you have successfully queried the resource report API many different use cases can be fulfilled. Note: when copying and pasting the below examples delete the line break inserted after “-Property” for the script to function correctly: # Protected VMs by most RecoveryJournalUsedStorageInGB usage $ResourceReportAPITable = $ResourceReport | Sort-Object -Property RecoveryJournalUsedStorageInGB -Descending | format-table -Property Timestamp,VmName,VpgName, RecoveryJournalUsedStorageInGB,RecoveryVolumesUsedStorageInGB $ResourceReportAPITable # Protected VMs by most RecoveryVolumesStorageInGB usage $ResourceReportAPITable = $ResourceReport | Sort-Object -Property RecoveryVolumesUsedStorageInGB -Descending | format-table -Property Timestamp,VmName,VpgName,RecoveryVolumesProvisionedStorageInGB,RecoveryVolumesUsedStorageInGB $ResourceReportAPITable # Protected VMs by CPU and RAM size $ResourceReportAPITable = $ResourceReport | Sort-Object -Property NumberOfvCpu,MemoryInMB -Descending | format-table -Property Timestamp,VpgName,VmName,NumberOfvCpu,MemoryInMB,VmHardwareVersion $ResourceReportAPITable # Protected VMs by target host, then by CPU and RAM size, useful for balancing recovery $ResourceReportAPITable = $ResourceReport | Sort-Object -Property TargetHost,NumberOfvCpu,MemoryInMB -Descending | format-table -Property Timestamp,VpgName,VmName,TargetHost,NumberOfvCpu,MemoryInMB,VmHardwareVersion $ResourceReportAPITable # VMs replicating to a VRA with number of volumes per VM, useful for balancing replication $ResourceReportAPITable = $ResourceReport | Where-Object {$_.TargetVraName -eq "Z-VRA-192.168.0.14"} | format-table -Property Timestamp,VmName,VpgName,TargetVraName,NumberOfVolumes $ResourceReportAPITable # Protected VMs with a specific VM hardware version $ResourceReportAPITable = $ResourceReport | Where-Object {$_.VmHardwareVersion -eq "vmx-08"} | format-table -Property Timestamp,VmName,VpgName,VmHardwareVersion $ResourceReportAPITable # Protected VMs by highest average bandwidth usage (derived from time between samples) $ResourceReportAPITable = $ResourceReport | Sort-Object -Property BandwidthInBytes -Descending | format-table -Property Timestamp,VmName,VpgName,BandwidthInBytes $ResourceReportAPITable # Protected VMs to a specific > .tg {border-collapse:collapse;border-spacing:0;border-color:#aaa;} .tg td{font-family:Arial, sans-serif;font-size:10px;padding:10px 5px;border-style:solid;border-width:0px;overflow:hidden;word-break:normal;border-color:#aaa;color:#333;background-color:#ffffff;border-top-width:1px;border-bottom-width:1px;} .tg th{font-family:Arial, sans-serif;font-size:10px;font-weight:bold;padding:10px 5px;border-style:solid;border-width:0px;overflow:hidden;word-break:normal;border-color:#aaa;color:$TableFont ;background-color:$TableBorder;border-top-width:1px;border-bottom-width:1px;} .tg .tg-foxd{background-color:$TableBackground;vertical-align:top;text-align:left} .tg .tg-yw4l{vertical-align:top} .caption {font-family:Arial, sans-serif;font-size:11px;font-weight:bold;color:$TableFont;}
"@ ################################################ # Creating CSV Save Function ################################################ Function Save-CSV{ Param($Array,$CSVFileName,$CSVDirectory) # Saving file to directory specified then returning file name to use for email $Timestamp = get-date $Now = $TimeStamp.ToString("yyyy-MM-dd HH-mm-ss ") $CSVName = $Now + $CSVFileName $CSVFile = $CSVDirectory + $CSVName + ".csv" $Array | Export-CSV -NoTypeInformation $CSVFile $CSVFile }
AUTOMATING ZVR WITH POWERSHELL & REST API WHITEPAPER
28 OF 134
################################################ # Creating Time function ################################################ Function Get-Time{ $Timestamp = get-date $Now = $TimeStamp.ToString("yyyy-MM-dd HH-mm-ss ") $Now } ################################################ # Creating Email Function ################################################ Function Email-ZVRReport{ Param($EmailTo,$Subject,$Body,$Attachment,$SMTPProfile) # Getting SMTP Profile Settings $EmailFrom = $SMTPProfile[0] $SMTPServer = $SMTPProfile[1] $SMTPPort = $SMTPProfile[2] $SMTPUser = $SMTPProfile[3] $SMTPPassword = $SMTPProfile[4] $SMTPSSLEnabled = $SMTPProfile[5] # Building SMTP settings based on settings $emailsetting = New-Object System.Net.Mail.MailMessage $emailsetting.to.add($EmailTo) $emailsetting.from = $EmailFrom $emailsetting.IsBodyHTML = "TRUE" $emailsetting.subject = $Subject $emailsetting.body = $Body # Adding attachments if ($Attachment -ne $null) { # Performing for each to support multiple attachments foreach ($_ in $Attachment) { $emailattachmentsetting = new-object System.Net.Mail.Attachment $_ $emailsetting.attachments.add($emailattachmentsetting) # invoke-expression $AttachmentCommand # End of for each attachment below } # End of for each attachment above } # Creating SMTP object $smtp = New-Object System.Net.Mail.SmtpClient($SMTPServer, $SMTPPort); # Enabling SSL if set if ($SMTPSSLEnabled -eq "TRUE") { $smtp.EnableSSL = "TRUE" } # Setting credentials $smtp.Credentials = New-Object System.Net.NetworkCredential($SMTPUser, $SMTPPassword); # Sending the Email Try { $smtp.send($emailsetting) } Catch [system.exception] { # Trying email again $smtp.send($emailsetting) } # End of email function } ################################################ # Creating Report arrays ################################################ $ProtectedVPGArray = @() $ProtectedVMArray = @()
AUTOMATING ZVR WITH POWERSHELL & REST API WHITEPAPER
29 OF 134
$TargetVRAArray = @() $UnprotectedVMArray = @() $Target&toTimeString=" + $NowDateTime + "&startIndex=0&count=500" $ResourceReport = Invoke-RestMethod -Uri $ResourceReportURL -TimeoutSec 100 -Headers $TargetZVMSessionHeader -ContentType $TypeJSON ################################################ # Creating ProtectedVPGArray ################################################ # Getting VPGs $ProtectedVPGsURL = $SourceZVMBaseURL+"vpgs" $ProtectedVPGsCMD = Invoke-RestMethod -Uri $ProtectedVPGsURL -TimeoutSec 100 -Headers $SourceZVMSessionHeader_JSON -ContentType $TypeJSON foreach ($VPG in $ProtectedVPGsCMD) { $VPGName = $VPG.VpgName $VPGIdentifier = $VPG.VpgIdentifier $VMCount = $VPG.VmsCount $PriorityNumber = $VPG.Priority $RPO = $VPG.ActualRPO $StatusNumber = $VPG.Status $SizeInGb = $VPG.UsedStorageInMB / 1024 $SizeInGb = [math]::Round($SizeInGb,2) # Converting priority $VPGPriority = $VMPriorityArray | Where-Object {$_.Number -eq $PriorityNumber} | select -ExpandProperty Name # Converting VM status $VPGStatus = $VMStatusArray | Where-Object {$_.Number -eq $StatusNumber} | select -ExpandProperty Name $VPGStatusDescription = $VMStatusArray | Where-Object {$_.Number -eq $StatusNumber} | select -ExpandProperty Description # Getting VPG Journal size $VPGResourceReport = $ResourceReport | Where-Object {$_.VpgName -eq $VPGName} # Calculating total Journal usage $VPGJournalUsage = $VPGResourceReport.RecoveryJournalUsedStorageInGB $VPGTotalJournalUsage = 0 foreach ($_ in $VPGJournalUsage) { $VPGTotalJournalUsage += $_
AUTOMATING ZVR WITH POWERSHELL & REST API WHITEPAPER
41 OF 134
} $VPGTotalJournalUsage = [math]::Round($VPGTotalJournalUsage,2) # Getting Alerts for the VPG for past 24 hours $Tomorrow = (get-date).AddDays(1) $Yesterday = (get-date).AddDays(-1) # Building URL $VPGAlertsURL = $SourceZVMBaseURL+"alerts?"+"startDate=$Yesterday&endDate=$Tomorrow&vpgIdentifier={$VPGIdentifier}&isDismissed=false" # Getting events $VPGAlertsCMD = Invoke-RestMethod -Uri $VPGAlertsURL -TimeoutSec 100 -Headers $SourceZVMSessionHeader_JSON -ContentType $TypeJSON $VPGLastAlert = $VPGAlertsCMD | select * -First 1 # Getting description of last alert $VPGLastAlertIdentifier = $VPGLastAlert.HelpIdentifier $VPGLastAlertDescription = $EventStatusArray | Where-Object {$_.Identifier -eq $VPGLastAlertIdentifier} | select -expandproperty Description # Calculating RPO violations in last 24 hours $VPGRPOAlerts = $VPGAlertsCMD | Where-Object {$_.HelpIdentifier -eq "VPG0009" -or $_.HelpIdentifier -eq "VPG0009"} | Measure-Object | select -ExpandProperty Count # Adding to array $ProtectedVPGArrayLine = new-object PSObject $ProtectedVPGArrayLine | Add-Member -MemberType NoteProperty -Name "SourcePOD" -Value $SourcePOD $ProtectedVPGArrayLine | Add-Member -MemberType NoteProperty -Name "TargetPOD" -Value $TargetPOD $ProtectedVPGArrayLine | Add-Member -MemberType NoteProperty -Name "VPGName" -Value $VPGName $ProtectedVPGArrayLine | Add-Member -MemberType NoteProperty -Name "VMCount" -Value $VMCount $ProtectedVPGArrayLine | Add-Member -MemberType NoteProperty -Name "Priority" -Value $VPGPriority $ProtectedVPGArrayLine | Add-Member -MemberType NoteProperty -Name "RPO" -Value $RPO $ProtectedVPGArrayLine | Add-Member -MemberType NoteProperty -Name "RPOAlerts" -Value $VPGRPOAlerts $ProtectedVPGArrayLine | Add-Member -MemberType NoteProperty -Name "Status" -Value $VPGStatus $ProtectedVPGArrayLine | Add-Member -MemberType NoteProperty -Name "SizeInGb" -Value $SizeInGb $ProtectedVPGArrayLine | Add-Member -MemberType NoteProperty -Name "JournalSizeInGb" -Value $VPGTotalJournalUsage $ProtectedVPGArrayLine | Add-Member -MemberType NoteProperty -Name "AlertDescription" -Value $VPGLastAlertDescription $ProtectedVPGArray += $ProtectedVPGArrayLine } # Getting VMs $ProtectedVMsURL = $SourceZVMBaseURL+"vms" $ProtectedVMsCMD = Invoke-RestMethod -Uri $ProtectedVMsURL -TimeoutSec 100 -Headers $SourceZVMSessionHeader_JSON -ContentType $TypeJSON # Adding to array $ProtectedVMs = $ProtectedVMsCMD | Sort-Object VpgName foreach ($VM in $ProtectedVMs) { $VPGName = $VM.VpgName $VMName = $VM.VmName $StatusNumber = $VM.Status $PriorityNumber = $VM.Priority $RPO = $VM.ActualRPO $SizeInGb = $VM.UsedStorageInMB / 1024 $SizeInGb = [math]::Round($SizeInGb,2) $VMDisks = $VM.Volumes.Count # Converting priority $VMPriority = $VMPriorityArray | Where-Object {$_.Number -eq $PriorityNumber} | select -ExpandProperty Name # Converting VM status $VMStatus = $VMStatusArray | Where-Object {$_.Number -eq $StatusNumber} | select -ExpandProperty Name $VMStatusDescription = $VMStatusArray | Where-Object {$_.Number -eq $StatusNumber} | select -ExpandProperty Description # Gettong VM Journal size $VMResourceReport = $ResourceReport | Where-Object {$_.VmName -eq $VMName} | select -First 1 $VMSourceCluster = $VMResourceReport.SourceCluster $VMTargetCluster = $VMResourceReport.TargetCluster # Calculating total Journal usage $VMJournalUsage = $VMResourceReport.RecoveryJournalUsedStorageInGB $VMTotalJournalUsage = 0 foreach ($_ in $VMJournalUsage) { $VMTotalJournalUsage += $_ } $VMTotalJournalUsage = [math]::Round($VMTotalJournalUsage,2) # Creating array line $ProtectedVMArrayLine = new-object PSObject $ProtectedVMArrayLine | Add-Member -MemberType NoteProperty -Name "SourcePOD" -Value "$SourcePOD" $ProtectedVMArrayLine | Add-Member -MemberType NoteProperty -Name "SourceCluster" -Value "$VMSourceCluster"
AUTOMATING ZVR WITH POWERSHELL & REST API WHITEPAPER
42 OF 134
$ProtectedVMArrayLine | Add-Member -MemberType NoteProperty -Name "TargetPOD" -Value "$TargetPOD" $ProtectedVMArrayLine | Add-Member -MemberType NoteProperty -Name "TargetCluster" -Value "$VMTargetCluster" $ProtectedVMArrayLine | Add-Member -MemberType NoteProperty -Name "VPGName" -Value "$VPGName" $ProtectedVMArrayLine | Add-Member -MemberType NoteProperty -Name "VMName" -Value "$VMName" $ProtectedVMArrayLine | Add-Member -MemberType NoteProperty -Name "Priority" -Value "$VMPriority" $ProtectedVMArrayLine | Add-Member -MemberType NoteProperty -Name "RPO" -Value "$RPO" $ProtectedVMArrayLine | Add-Member -MemberType NoteProperty -Name "Status" -Value "$VMStatus" $ProtectedVMArrayLine | Add-Member -MemberType NoteProperty -Name "Disks" -Value "$VMDisks" $ProtectedVMArrayLine | Add-Member -MemberType NoteProperty -Name "SizeInGb" -Value "$SizeInGb" $ProtectedVMArrayLine | Add-Member -MemberType NoteProperty -Name "JournalSizeInGb" -Value "$VMTotalJournalUsage" $ProtectedVMArray += $ProtectedVMArrayLine } ################################################ # Creating TargetVRAArray ################################################ $TargetZVMHostsURL = $TargetZVMBaseURL+"virtualizationsites/"+$TargetLocalSiteIdentifier+"/hosts" $TargetZVMHostsCMD = Invoke-RestMethod -Uri $TargetZVMHostsURL -TimeoutSec 100 -Headers $TargetZVMSessionHeader -ContentType $TypeJSON $TargetZVMVRAsURL = $TargetZVMBaseURL+"vras" $TargetZVMVRAsCMD = Invoke-RestMethod -Uri $TargetZVMVRAsURL -TimeoutSec 100 -Headers $TargetZVMSessionHeader -ContentType $TypeJSON $TargetZVMVRAs = $TargetZVMVRAsCMD | Select-Object VraName,HostIdentifier,VraGroup,RecoveryCounters -Unique # For each VRA foreach ($TargetVRA in $TargetZVMVRAs) { $VRAName = $TargetVRA.VraName $VRACluster = get-vm $VRAName | Get-Cluster | select -expandproperty Name $VRAHostIdentifier = $TargetVRA.HostIdentifier $VRAVMs = $TargetVRA.RecoveryCounters.Vms $VRAHostIdentifier = $TargetVRA.HostIdentifier $VRAVolumes = $TargetVRA.RecoveryCounters.Volumes $VRAVpgs = $TargetVRA.RecoveryCounters.Vpgs $VRAGroup = $TargetVRA.VraGroup # Getting hostname $VRAHostname = $TargetZVMHostsCMD | Where-Object {$_.HostIdentifier -eq $VRAHostIdentifier} | select -ExpandProperty VirtualizationHostName # Getting over commit >
$TableCaption SourcePOD | TargetPOD | VPGName | VMCount | Priority | RPO | RPOAlerts | Status | SizeInGB | JournalSizeInGB | AlertDescription |
"@ # Building HTML table $ProtectedVPGArrayHTMLTable = $null foreach ($_ in $Array) { # Setting values $SourcePOD = $_.SourcePOD $TargetPOD = $_.TargetPOD $VPGName = $_.VPGName $VMCount = $_.VMCount $Priority = $_.Priority $RPO = $_.RPO $RPOAlerts = $_.RPOAlerts $Status = $_.Status $SizeInGb = $_.SizeInGb $JournalSizeInGb = $_.JournalSizeInGb $AlertDescription = $_.AlertDescription # Building HTML table row $ProtectedVPGArrayHTMLTableRow = "
$SourcePOD | $TargetPOD | $VPGName | $VMCount | $Priority | $RPO | $RPOAlerts | $Status |
AUTOMATING ZVR WITH POWERSHELL & REST API WHITEPAPER
53 OF 134
$SizeInGb | $JournalSizeInGb | $AlertDescription |
" # Adding rows to table $ProtectedVPGArrayHTMLTable += $ProtectedVPGArrayHTMLTableRow } # Compiling End of HTML email $ProtectedVPGArrayHTMLTableEnd = @"
"@ # Compiling Final HTML $ProtectedVPGArrayHTMLTable = $ProtectedVPGArrayHTMLTableStart + $ProtectedVPGArrayHTMLTable + $ProtectedVPGArrayHTMLTableEnd $ProtectedVPGArrayHTMLTable } ################################################ # Function for building HTML table for ProtectedVMArray ################################################ Function Create-ProtectedVMArrayTable { Param($Array,$TableCaption) $ProtectedVMArrayHTMLTableStart = @"
$TableCaption SourcePOD | SourceCluster | TargetPOD | TargetCluster | VPGName | VMName | Priority | Status | RPO | Disks | SizeInGB | JournalSizeInGB |
"@ # Building HTML table $ProtectedVMArrayHTMLTable = $null foreach ($_ in $Array) { # Setting values $SourcePOD = $_.SourcePOD $SourceCluster = $_.SourceCluster $TargetPOD = $_.TargetPOD $TargetCluster = $_.TargetCluster $VPGName = $_.VPGName $VMName = $_.VMName $Priority = $_.Priority $Status = $_.Status $RPO = $_.RPO $Disks = $_.Disks $SizeInGb = $_.SizeInGb $JournalSizeInGb = $_.JournalSizeInGb # Building HTML table row $ProtectedVMArrayHTMLTableRow = " $SourcePOD | $SourceCluster | $TargetPOD | $TargetCluster | $VPGName | $VMName |
AUTOMATING ZVR WITH POWERSHELL & REST API WHITEPAPER
54 OF 134
$Priority | $Status | $RPO | $Disks | $SizeInGb | $JournalSizeInGb |
" # Adding rows to table $ProtectedVMArrayHTMLTable += $ProtectedVMArrayHTMLTableRow } # Compiling End of HTML email $ProtectedVMArrayHTMLTableEnd = @"
"@ # Compiling Final HTML $ProtectedVMArrayHTMLTable = $ProtectedVMArrayHTMLTableStart + $ProtectedVMArrayHTMLTable + $ProtectedVMArrayHTMLTableEnd $ProtectedVMArrayHTMLTable # End of ProtectedVMArrayTable function } ################################################ # Function for building HTML table for TargetVRAArray ################################################ Function Create-TargetVRAArrayTable { Param($Array,$TableCaption) $TargetVRAArrayHTMLTableStart = @"
$TableCaption TargetPOD | VRACluster | RecoveryVRAName | ESXiHostname | VRAVPGs | VRAVMs | VRAVolumes | VRAVolumesTB | VRAJournalsTB | VMNumbervCPU | VMCpuUsedGhz | VMMemoryGB | VMActiveMemoryGB |
"@ # Building HTML table $TargetVRAArrayHTMLTable = $null foreach ($_ in $Array) { # Setting values $TargetPOD = $_.TargetPOD $TargetCluster = $_.VRACluster $VRAName = $_.VRAName $ESXiHostname = $_.ESXiHostname $VRAVPGs = $_.VRAVPGs $VRAVMs = $_.VRAVMs $VRAVolumes = $_.VRAVolumes $VRARecoveryVolumesInTB = $_.VRARecoveryVolumesInTB $VRARecoveryJournalsInTB = $_.VRARecoveryJournalsInTB $VMNumberOfvCPU = $_.VMNumberOfvCPU $VMCpuUsedInGhz = $_.VMCpuUsedInGhz $VMMemoryInGB = $_.VMMemoryInGB $VMActiveMemoryInGB = $_.VMActiveMemoryInGB # Building HTML table row $TargetVRAArrayHTMLTableRow = "
AUTOMATING ZVR WITH POWERSHELL & REST API WHITEPAPER
55 OF 134
$TargetPOD | $TargetCluster | $VRAName | $ESXiHostname | $VRAVPGs | $VRAVMs | $VRAVolumes | $VRARecoveryVolumesInTB | $VRARecoveryJournalsInTB | $VMNumberOfvCPU | $VMCpuUsedInGhz | $VMMemoryInGB | $VMActiveMemoryInGB |
" # Adding rows to table $TargetVRAArrayHTMLTable += $TargetVRAArrayHTMLTableRow } # Compiling End of HTML email $TargetVRAArrayHTMLTableEnd = @"
"@ # Compiling Final HTML $TargetVRAArrayHTMLTable = $TargetVRAArrayHTMLTableStart + $TargetVRAArrayHTMLTable + $TargetVRAArrayHTMLTableEnd $TargetVRAArrayHTMLTable # End of TargetVRAArrayTable function } ################################################ # Function for building HTML table for UnprotectedVMArrayTable ################################################ Function Create-UnprotectedVMArrayTable { Param($Array,$TableCaption) $UnprotectedVMArrayHTMLTableStart = @"
$TableCaption SourcePOD | VMFolder | VMName | VMCluster | NumCPU | MemoryGB | NICS | HardDisks | UsedSpaceGB |
"@ # Building HTML table $UnprotectedVMArrayHTMLTable = $null foreach ($_ in $Array) { # Setting values $SourcePOD = $_.SourcePOD $VMFolder = $_.VMFolder $VMName = $_.VMName $VMCluster = $_.VMCluster $NumCPU = $_.NumCPU $MemoryGB = $_.MemoryGB $NICS = $_.NICS $HardDisks = $_.HardDisks $UsedSpaceGB = $_.UsedSpaceGB # Building HTML table row $UnprotectedVMArrayHTMLTableRow = " $SourcePOD |
AUTOMATING ZVR WITH POWERSHELL & REST API WHITEPAPER
56 OF 134
$VMFolder | $VMName | $VMCluster | $NumCPU | $MemoryGB | $NICS | $HardDisks | $UsedSpaceGB |
" # Adding rows to table $UnprotectedVMArrayHTMLTable += $UnprotectedVMArrayHTMLTableRow } # Compiling End of HTML email $UnprotectedVMArrayHTMLTableEnd = @"
"@ # Compiling Final HTML $UnprotectedVMArrayHTMLTable = $UnprotectedVMArrayHTMLTableStart + $UnprotectedVMArrayHTMLTable + $UnprotectedVMArrayHTMLTableEnd $UnprotectedVMArrayHTMLTable # End of TargetVRAArrayTable function } ################################################ # Function for building HTML table for Target>
$TableCaption PODName | >>UsedByZVR | CapacityGB | FreeSpaceGB | FreePercent |
"@ # Building HTML table $Targettg-yw4l"">$PODName
$tg-yw4l"">$tg-yw4l"">$UsedByZVR | $CapacityGB | $FreeSpaceGB | $FreePercent | " # Adding rows to table
AUTOMATING ZVR WITH POWERSHELL & REST API WHITEPAPER
57 OF 134
$Target>
$TableCaption PODName | VPGName | VMCount | Priortiy | ProtectedSiteName | RecoverySiteName | RpoInSeconds | TestIntervalInMinutes | UseWanCompression | BootGroupCount | BootGroupNames | BootGroupDelays | JournalHistoryInHours | Journal>Journal>JournalHardLimitInMB | JournalHardLimitInPercent | JournalWarningThresholdInMB | JournalWarningThresholdInPercent | FailoverNetworkName | FailoverTestNetworkName | Default>DefaultFolderName | DefaultHostClusterName | DefaultHostName |
"@ # Building HTML table $VPGArrayHTMLTable = $null foreach ($_ in $Array) { # Setting values $PODName = $_.SourcePOD $VPGName = $_.VPGName $VPGidentifier = $_.VPGidentifier $VPGOrganization = $_.VPGOrganization $VPGVMCount = $_.VPGVMCount $VPGPriortiy = $_.VPGPriortiy $VPGProtectedSiteName = $_.VPGProtectedSiteName $VPGProtectedSiteIdentifier = $_.VPGProtectedSiteIdentifier $VPGRecoverySiteName = $_.VPGRecoverySiteName $VPGRecoverySiteIdentifier = $_.VPGRecoverySiteIdentifier $VPGRpoInSeconds = $_.VPGRpoInSeconds $VPGServiceProfileIdentifier = $_.VPGServiceProfileIdentifier $VPGTestIntervalInMinutes = $_.VPGTestIntervalInMinutes
AUTOMATING ZVR WITH POWERSHELL & REST API WHITEPAPER
58 OF 134
$VPGUseWanCompression = $_.VPGUseWanCompression $VPGZorgIdentifier = $_.VPGZorgIdentifier $VPGBootGroupCount = $_.VPGBootGroupCount $VPGBootGroupNames = $_.VPGBootGroupNames $VPGBootGroupDelays = $_.VPGBootGroupDelays $VPGBootGroupIdentifiers = $_.VPGBootGroupIdentifiers $VPGJournalHistoryInHours = $_.VPGJournalHistoryInHours $VPGJournaltg-yw4l"">$PODName
$VPGName | $VPGVMCount | $VPGPriortiy | $VPGProtectedSiteName | $VPGRecoverySiteName | $VPGRpoInSeconds | $VPGTestIntervalInMinutes | $VPGUseWanCompression | $VPGBootGroupCount | $VPGBootGroupNames | $VPGBootGroupDelays | $VPGJournalHistoryInHours | $VPGJournaltg-yw4l"">$VPGJournaltg-yw4l"">$VPGJournalHardLimitInMB | $VPGJournalHardLimitInPercent | $VPGJournalWarningThresholdInMB | $VPGJournalWarningThresholdInPercent | $VPGFailoverNetworkName | $VPGFailoverTestNetworkName | $VPGDefaulttg-yw4l"">$VPGDefaultFolderName | $VPGDefaultHostClusterName | $VPGDefaultHostName | " # Adding rows to table $VPGArrayHTMLTable += $VPGArrayHTMLTableRow } # Compiling End of HTML email $VPGArrayHTMLTableEnd = @"
AUTOMATING ZVR WITH POWERSHELL & REST API WHITEPAPER
59 OF 134
"@ # Compiling Final HTML $VPGArrayHTMLTable = $VPGArrayHTMLTableStart + $VPGArrayHTMLTable + $VPGArrayHTMLTableEnd $VPGArrayHTMLTable # End of TargetVRAArrayTable function } ################################################ # Function for building HTML table for VMArray ################################################ Function Create-VMArrayTable { Param($Array,$TableCaption) $VMArrayHTMLTableStart = @"
$TableCaption PODName | VPGName | VMName | NICCount | VolumeCount | ProvisionedStorageInMB | UsedStorageInMB | BootGroupName | BootGroupDelay | Journal>Journal>JournalHardLimitInMB | JournalHardLimitInPercent | >>FolderName | HostClusterName | HostName |
"@ # Building HTML table $VMArrayHTMLTable = $null foreach ($_ in $Array) { # Setting values $PODName = $_.SourcePOD $VPGName = $_.VPGName $VPGidentifier = $_.VPGidentifier $VMName = $_.VMName $VMIdentifier = $_.VMIdentifier $VMNICCount = $_.VMNICCount $VMVolumeCount = $_.VMVolumeCount $VMProvisionedStorageInMB = $_.VMProvisionedStorageInMB $VMUsedStorageInMB = $_.VMUsedStorageInMB $VMBootGroupName = $_.VMBootGroupName $VMBootGroupDelay = $_.VMBootGroupDelay $VMBootGroupIdentifier = $_.VMBootGroupIdentifier $VMJournaltg-yw4l"">$PODName $VPGName | $VMName | $VMNICCount | $VMVolumeCount | $VMProvisionedStorageInMB | $VMUsedStorageInMB | $VMBootGroupName | $VMBootGroupDelay | $VMJournaltg-yw4l"">$VMJournaltg-yw4l"">$VMJournalHardLimitInMB | $VMJournalHardLimitInPercent | $VMtg-yw4l"">$VMtg-yw4l"">$VMFolderName | $VMHostClusterName | $VMHostName | " # Adding rows to table $VMArrayHTMLTable += $VMArrayHTMLTableRow } # Compiling End of HTML email $VMArrayHTMLTableEnd = @"
"@ # Compiling Final HTML $VMArrayHTMLTable = $VMArrayHTMLTableStart + $VMArrayHTMLTable + $VMArrayHTMLTableEnd $VMArrayHTMLTable # End of TargetVRAArrayTable function } ################################################ # Function for building HTML table for VMVolumeArray ################################################ Function Create-VMVolumeArrayTable { Param($Array,$TableCaption) $VMVolumeArrayHTMLTableStart = @"
$TableCaption PODName | VPGName | VMName | VolumeID | VolumeIsSWAP | VolumeIsThin | Volume>Volumetg-yw4l"">$PODName | $VPGName | $VMName | $VMVolumeID | $VMVolumeIsSWAP | $VMVolumeIsThin | $VMVolumetg-yw4l"">$VMVolume> $TableCaption |
PODName | VPGName | VMName | VMNICIdentifier | FailoverNetworkName | FailoverDNSSuffix | FailoverShouldReplaceMacAddress | FailoverGateway | FailoverDHCP | FailoverPrimaryDns | FailoverSecondaryDns | FailoverStaticIp | FailoverSubnetMask | FailoverTestNetworkName | FailoverTestDNSSuffix | FailoverTestShouldReplaceMacAddress | FailoverTestGateway | FailoverTestDHCP | FailoverTestPrimaryDns |
AUTOMATING ZVR WITH POWERSHELL & REST API WHITEPAPER
62 OF 134
FailoverTestSecondaryDns | FailoverTestStaticIp | FailoverTestSubnetMask |
"@ # Building HTML table $VMNICArrayHTMLTable = $null foreach ($_ in $Array) { # Setting values $PODName = $_.SourcePOD $VPGName = $_.VPGName $VPGidentifier = $_.VPGidentifier $VMName = $_.VMName $VMNICIdentifier = $_.VMIdentifier $VMNICIdentifier = $_.VMNICIdentifier $VMNICFailoverNetworkName = $_.VMNICFailoverNetworkName $VMNICFailoverNetworkIdentifier = $_.VMNICFailoverNetworkIdentifier $VMNICFailoverDNSSuffix = $_.VMNICFailoverDNSSuffix $VMNICFailoverShouldReplaceMacAddress = $_.VMNICFailoverShouldReplaceMacAddress $VMNICFailoverGateway = $_.VMNICFailoverGateway $VMNICFailoverDHCP = $_.VMNICFailoverDHCP $VMNICFailoverPrimaryDns = $_.VMNICFailoverPrimaryDns $VMNICFailoverSecondaryDns = $_.VMNICFailoverSecondaryDns $VMNICFailoverStaticIp = $_.VMNICFailoverStaticIp $VMNICFailoverSubnetMask = $_.VMNICFailoverSubnetMask $VMNICFailoverTestNetworkName = $_.VMNICFailoverTestNetworkName $VMNICFailoverTestNetworkIdentifier = $_.VMNICFailoverTestNetworkIdentifier $VMNICFailoverTestDNSSuffix = $_.VMNICFailoverTestDNSSuffix $VMNICFailoverTestShouldReplaceMacAddress = $_.VMNICFailoverTestShouldReplaceMacAddress $VMNICFailoverTestGateway = $_.VMNICFailoverTestGateway $VMNICFailoverTestDHCP = $_.VMNICFailoverTestDHCP $VMNICFailoverTestPrimaryDns = $_.VMNICFailoverTestPrimaryDns $VMNICFailoverTestSecondaryDns = $_.VMNICFailoverTestSecondaryDns $VMNICFailoverTestStaticIp = $_.VMNICFailoverTestStaticIp $VMNICFailoverTestSubnetMask = $_.VMNICFailoverTestSubnetMask # Building HTML table row $VMNICArrayHTMLTableRow = " $PODName | $VPGName | $VMName | $VMNICIdentifier | $VMNICFailoverNetworkName | $VMNICFailoverDNSSuffix | $VMNICFailoverShouldReplaceMacAddress | $VMNICFailoverGateway | $VMNICFailoverDHCP | $VMNICFailoverPrimaryDns | $VMNICFailoverSecondaryDns | $VMNICFailoverStaticIp | $VMNICFailoverSubnetMask | $VMNICFailoverTestNetworkName | $VMNICFailoverTestDNSSuffix | $VMNICFailoverTestShouldReplaceMacAddress | $VMNICFailoverTestGateway | $VMNICFailoverTestDHCP | $VMNICFailoverTestPrimaryDns | $VMNICFailoverTestSecondaryDns | $VMNICFailoverTestStaticIp | $VMNICFailoverTestSubnetMask |
" # Adding rows to table $VMNICArrayHTMLTable += $VMNICArrayHTMLTableRow }
AUTOMATING ZVR WITH POWERSHELL & REST API WHITEPAPER
63 OF 134
# Compiling End of HTML email $VMNICArrayHTMLTableEnd = @"
"@ # Compiling Final HTML $VMNICArrayHTMLTable = $VMNICArrayHTMLTableStart + $VMNICArrayHTMLTable + $VMNICArrayHTMLTableEnd $VMNICArrayHTMLTable # End of TargetVRAArrayTable function } ################################################ # Function for building HTML table for PODSummaryArray ################################################ Function Create-PODSummaryArrayTable { Param($Array,$TableCaption) $PODSummaryArrayHTMLTableStart = @"
$TableCaption PODName | VMs | Protected | UnProtected | VPGs | MeetingSLA | NotMeetingSLA | AverageRPO | HighPriority | MediumPriority | LowPriority | ProtectedSizeTB | JournalSizeTB |
"@ # Building HTML table $PODSummaryArrayHTMLTable = $null foreach ($_ in $Array) { # Setting values $PODName = $_.PODName $VMs = $_.VMs $VMsUnProtected = $_.VMsUnProtected $VMsProtected = $_.VMsProtected $VPGs = $_.VPGs $MeetingSLA = $_.MeetingSLA $NotMeetingSLA = $_.NotMeetingSLA $AverageRPO = $_.AverageRPO $HighPriority = $_.HighPriority $MediumPriority = $_.MediumPriority $LowPriority = $_.LowPriority $ProtectedSizeTB = $_.ProtectedSizeTB $JournalSizeTB = $_.JournalSizeTB # Building HTML table row $PODSummaryArrayHTMLTableRow = " $PODName | $VMs | $VMsProtected | $VMsUnProtected | $VPGs | $MeetingSLA | $NotMeetingSLA | $AverageRPO | $HighPriority | $MediumPriority | $LowPriority |
AUTOMATING ZVR WITH POWERSHELL & REST API WHITEPAPER
64 OF 134
$ProtectedSizeTB | $JournalSizeTB |
" # Adding rows to table $PODSummaryArrayHTMLTable += $PODSummaryArrayHTMLTableRow } # Compiling End of HTML email $PODSummaryArrayHTMLTableEnd = @"
"@ # Compiling Final HTML $PODSummaryArrayHTMLTable = $PODSummaryArrayHTMLTableStart + $PODSummaryArrayHTMLTable + $PODSummaryArrayHTMLTableEnd $PODSummaryArrayHTMLTable # End of TargetVRAArrayTable function } ######################################################################################################################## # Customize reports below ######################################################################################################################## ######################################################################### # Building & Sending Report - POD Summary Report ######################################################################### # Setting Email subject $Subject = "Zerto POD Summary Report" # Creating Tables for Email Body # Table1 $PODSummaryArraySorted = $PODSummaryArray | Sort-Object PODName $PODSummaryArrayHTML = Create-PODSummaryArrayTable -Array $PODSummaryArraySorted -TableCaption "POD Summary" # Table2 $VPGAlerts = $ProtectedVPGArray | Where-Object {$_.Status -ne "MeetingSLA" -or $_.RPOAlerts -ge "1"} | Sort-Object SourcePOD,VPGName if ($VPGAlertArraySorted -ne $null) { $VPGAlertArrayHTML = Create-ProtectedVPGArrayTable -Array $VPGAlerts -TableCaption "All VPG Violations by POD and VPGName" } else { $VPGAlertArrayHTML = $null } # Table2 $Target&toTimeString=" + $NowDateTime + "&startIndex=0&count=500"
$ResourceReport = Invoke-RestMethod -Uri $ResourceReportURL -TimeoutSec 100 -Headers $zertoSessionHeader -ContentType $TypeJSON # Importing CSV for VPGs $OffsiteCloneVPGs = import-csv $OffsiteCloneVPGsCSV $OffsiteCloneVPGsByName = $OffsiteCloneVPGs | Select-Object VPGName -ExpandProperty VPGName # Building list of VRAs that have VPGs to clone $TargetSiteVPGs = $ResourceReport | Sort-Object VPGName -Unique | Select VPGName,TargetVraName ################################################ # Building array of VRAs that have VPGs that require offsite clone ################################################ $TargetVRAArray = @() foreach ($VPG in $TargetSiteVPGs) { if ($OffsiteCloneVPGsByName -ccontains $VPG.VpgName) { $TargetVRAArrayLine = new-object PSObject $TargetVRAArrayLine | Add-Member -MemberType NoteProperty -Name "VPGName" -Value $VPG.VpgName $TargetVRAArrayLine | Add-Member -MemberType NoteProperty -Name "VRAName" -Value $VPG.TargetVraName $TargetVRAArray += $TargetVRAArrayLine
AUTOMATING ZVR WITH POWERSHELL & REST API WHITEPAPER
121 OF 134
} } $TargetSiteVRAs = $TargetVRAArray | Select-Object VRAName -Unique ################################################################################################ # Performing for each VRA that has an offsite clone to run action ################################################################################################ foreach ($VRA in $TargetSiteVRAs) { $VRAName = $VRA.VRAName # Getting the VPGs to clone for the current VPG $VRAVPGsToClone = $null $VRAVPGsToClone = $TargetVRAArray | where-object {$_.VRAName -eq "$VRAName"} | Select-Object VPGName -ExpandProperty VPGName -Unique write-host "$VRAName VPGs to clone: $VRAVPGsToClone" $Now = get-date $JobTime = $Now.ToString("yyy-MM-dd_HH-mm-ss") ################################################ # Script block within per VRA ################################################ $VRAScriptBlock = { param ( $VRAName, $VRAVPGs ToClone, $vmLis t, $OffsiteCloneVPGs, $vCenterServer, $vCenterUser, $vCenterPassword, $ZVMIP, $ZVMPSPort, $ZMVPSUser, $ZVMPSPasswd, $OffsiteCloneVPGLog, $OffsiteCl oneVMLog, $Offsi teCloneLog http-equiv="content-type">
CloneStart | CloneEnd | CloneTimeTaken | VpgName | JobSuccessful | CheckpointTimeStamp | VSSCheckpointRequired | VSSCheckpoint | TotalSizeinTB | TotalSizeinGB | AverageThroughputMbSec | VMCount | VRAName | CloneDatastore |
"@ # Creating HTML Row $VPGHTMLRow = " $VPGTimeSuffix | $VPGCloneEndTime | $VPGCloneTimeTaken | $VPGName | $OffsiteCloneJobSuccess | $VPGTimeSuffix | $VPGFailIfNoVSS | $VPGVSSCPUsed |
AUTOMATING ZVR WITH POWERSHELL & REST API WHITEPAPER
131 OF 134
$OffsiteCloneSizeinTB | $OffsiteCloneSizeinGB | $OffsiteCloneAverageThroughputMbSec | $VMCount | $VRAName | $VPGCloneDatastore |
" $VPGHTMLTable += $VPGHTMLRow # Compiling End of HTML email $VPGHTMLEnd = @"
"@ # Compiling Final HTML $VPGHTML = $VPGHTMLMain + $VPGHTMLTable + $VPGHTMLEnd # Sending per VPG email $EmailSubject = "OffsiteCloneVPG:$VPGName JobSuccess:$OffsiteCloneJobSuccess" # Nothing to configure below # Building SMTP settings beased on settings $emailsetting = New-Object System.Net.Mail.MailMessage $emailsetting.to.add($EmailTo) $emailsetting.from = $EmailFrom $emailsetting.IsBodyHTML = "TRUE" $emailsetting.subject = $EmailSubject $emailsetting.body = $VPGHTML # Creating SMTP object $smtp = New-Object System.Net.Mail.SmtpClient($SMTPServer, $SMTPPort); # Enabling SSL if set if ($SMTPSSLEnabled -eq "TRUE") { $smtp.EnableSSL = "TRUE" } # Setting credentials $smtp.Credentials = New-Object System.Net.NetworkCredential($SMTPUser, $SMTPPassword); # Sending the Email Try { $smtp.send($emailsetting) } Catch [system.exception] { } Finally { } # Resetting HTML email row table $VPGHTMLTable = $null # End of for each VPG below } # End of for each VPG above # ################################################ # Disconnecting from vCenter ################################################ disconnect-viserver $vCenterServer -Force -Confirm:$false ################################################ # Stopping logging ################################################ stop-transcript # # End of script block below } # End of script block above
AUTOMATING ZVR WITH POWERSHELL & REST API WHITEPAPER
132 OF 134
################################################ # Starting Job to execute script block for each VRA ################################################ # Size significantly reduced to stop line break issues when copying and pasting Start-Job $VRAScriptBlock -Name "$JobTime $VRAName OffsiteClone" -ArgumentList $VRAName, $VRAVPGsToClone, $vmList, $OffsiteCloneVPGs, $vCenterServer, $vCenterUser, $vCenterPassword, $ZVMIP, $ZVMPSPort, $ZMVPSUser, $ZVMPSPasswd, $OffsiteCloneVPGLog, $OffsiteCloneVMLog, $OffsiteClone Log DataDir, $RemoveVMsFromInventory, $vCenterTimeZoneMatch, $EmailTo, $EmailFrom, $SMTPServer, $SMTPPort, $SMTPUser, $SMTPPassword, $SMTPSSLEnabled
# End of for each VRA below } # End of for each VRA above
AUTOMATING ZVR WITH POWERSHELL & REST API WHITEPAPER
133 OF 134
9
TROUBLESHOOTING
One of the most powerful tools at your disposal when working with PowerShell and APIs is to encapsulate your Invoke-WebRequest commands inside a Try/Catch statement to get more information on the error being returned. This can be very useful for troubleshooting everything from complex VPG creation to initial authentication with the API. Following is an example of a try statement to aid with troubleshooting: Try { $xZertoSessionResponse = Invoke-WebRequest -Uri $xZertoSessionURL -Headers $headers -Method POST -Body $sessionBody -ContentType $TypeJSON
} Catch { Write-Host $_.Exception.ToString() $error[0] | Format-List -Force }
A graphical editing tool such as PowerShell ISE is recommended for not only the first run of your powershell scripts, but to aid with real-time troubleshooting and should be used whenever an error is encountered:
By utilizing the transcipting method as described in section 2.5 it is possible to see any exceptions caught during the script runtime. This can be used in combination with the Zerto Virtual Manager logs to correlate the error if it was generated by the REST API. These logs can be found be browsing to the below file on the ZVM: c:\Program Files\Zerto\Zerto Virtual Replication\logs\logfile.csv
AUTOMATING ZVR WITH POWERSHELL & REST API WHITEPAPER
134 OF 134