Press "Enter" to skip to content

Automating DR Testing & Scripting a Rubrik Recovery Plan v2.0

Joshua Stenhouse 2

One of my most popular posts over the past year has been scripting a Rubrik recovery plan using PowerShell and REST APIs. In a nutshell, it allows you to specify a boot order of VMs to recover from your Rubrik cluster with time delays, scripts, user prompts, and VM configuration options.

I recently had cause to use it in my lab after my Synology NAS iSCSI volume decided to self-combust over Christmas taking 30 VMs with it. I recovered all 30 VMs in 2 minutes using Rubrik and the recovery plan script. Pretty good huh? I thought so, but I found the script lacking a few key features when I had to use it in a real DR scenario.

I wanted to boot the VMs after attaching them to an isolated VM port group as a DR test before recovering them, but the script had no ability to do this. Neither is the script compatible with the newly released PowerShell Core. So, I’ve been busy making it even better and this post is going to give you the end result, v2.0!

Here are the new features and changes:

  • The script now comes in 2 versions Simple and Advanced
  • Simple gives you the ability to orchestrate all of the Rubrik actions with a time delay between recovering each VM
  • Simple has both a run and stop script to auto remove the VM live mounts created from the CSV list (example provided)
  • Advanced gives you more “SRM” like functionality, like changing port groups, user prompts, and running external scripts
  • Advanced comes in 3 parts, all referencing the same CSV list of VMs:
  • RubrikRecoveryPlanv2-Run.ps1 – Performs a full recovery plan, with VMs powered on or left powered off by Rubrik, against either your production or DR Rubrik cluster depending on where you want to recover the VMs.
  • RubrikRecoveryPlanv2-Configure.ps1 – Optional script to perform a DR test by configuring port groups and powering on VMs from the vCenter rather than Rubrik as part of the live mount (requires vSphere 6.5 as it uses the new REST APIs, to make it compatible with PowerShell Core).
    01/29/18 Note: The PortGroup name specified should be unique across all DCs in the vCenter, as the API call doesn’t allow you to select by vCenter.
  • RubrikRecoveryPlanv2-Stop.ps1 – Stops all the active live mounts from your recovery plan making it easier to clean up a test. This shouldn’t be used if you want to keep the recovered VMs, you should Storage vMotion them off Rubik instead.
  • Using HostSelection in the CSV set to either RANDOM or ESXi hostname enables you to automatically or manually load balance VMs across the ESXi hosts.
  • Instant recovery via the action field in the CSV has been completely removed as it was only available in production, not on replicas, and you’d have to be crazy to use it rather than a live mount IMHO.
  • Implemented numerous bug fixes around host and VM selection on a DR Rubrik appliance to prevent recovery live mount API issues.

So how do you get this new version? Simple, you can download it here along with some sample CSV lists:

PowerShell: RubrikRecoveryPlanv2.zip (updated for CDM 4.1.1)

PowerShell Core: RubrikRecoveryPlanPSCv2.zip

With PowerShell Core support you can now run this script on a Linux host on your DR site. As covered here, the only difference between the 2 scripts is removing the .net certificate policy and replacing it with  -SkipCertificateCheck on each interaction. Going forward I promise to give examples of all scripts in both formats until I feel like the majority of the market has switched to PowerShell Core.

If you’d like to create it by hand you first need to create a CSV with the following fields:

RubrikRecoveryPlanv2.0

VMName,HostSelection,DisableNetwork,RemoveNetworkDevices,RubrikPowerOn,RunScriptsinLiveMount,PreFailoverScript,PostFailoverScriptDelay,PostFailoverScript,NextVMFailoverDelay,PreFailoverUserPrompt,PostFailoverUserPrompt,vCenterPowerOn,ConfigureNIC,PortGroup,ConnectNIC

Here is an example config with no scripts, user prompts, no boot delay and the VM is going to connect to a port group called Isolated (already created on all my ESXi hosts) then powered on by the vCenter and not Rubrik as part of the live mount:

DemoApp1VM01,192.168.1.14,FALSE,FALSE,FALSE,FALSE,,0,,0,,,TRUE,TRUE,Isolated,TRUE

Next, you need to configure the variables at the top of the below script to start the recovery plan based on your CSV (all examples now on are for PowerShell, not PowerShell core):

################################################
# RubrikRecoveryPlanv2-Run.ps1 - Configure the variables for Rubrik
################################################
$RubrikCluster = "192.168.1.201"
$RecoveryPlanCSV = "C:\RubrikRecoveryPlanv2\RubrikRecoveryPlanA.csv"
$LogDirectory = "C:\RubrikRecoveryPlanv2"
# Prompting for username and password to authenicate, can set manually to remove human interaction
$Credentials = Get-Credential -Message "Enter Rubrik login credentials"
$RubrikUser = $Credentials.UserName
$Credentials.Password | ConvertFrom-SecureString
$RubrikPassword = $Credentials.GetNetworkCredential().password
# VM suffix is added to the end of each VM name as its registered in the vCenter, set to $null if you just want the VM name as is, I.E for recovery with existing VM gone
# Ensure you configure this exactly the same in the configure and stop scripts otherwise they won't find any VMs to configure or stop the live mount on
$VMSuffix = " - Live Mount"
###################################################################
# Nothing to configure below this line - Starting main function
###################################################################
# Adding certificate exception to prevent API errors
################################################
add-type @"
using System.Net;
using System.Security.Cryptography.X509Certificates;
public class TrustAllCertsPolicy : ICertificatePolicy {
public bool CheckValidationResult(
ServicePoint srvPoint, X509Certificate certificate,
WebRequest request, int certificateProblem) {
return true;
}
}
"@
[System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy
################################################
# Starting logging & importing the CSV
################################################
$Now = get-date
$Log = $LogDirectory + "\Rubrik-RecoveryPlanRunLog-" + $Now.ToString("yyyy-MM-dd") + "@" + $Now.ToString("HH-mm-ss") + ".log"
Start-Transcript -Path $Log -NoClobber
$RecoveryPlanVMs = import-csv $RecoveryPlanCSV
################################################
# Building Rubrik API string & invoking REST API
################################################
$BaseURL = "https://" + $RubrikCluster + "/api/v1/"
$RubrikSessionURL = $BaseURL + "session"
$Header = @{"Authorization" = "Basic "+[System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($RubrikUser+":"+$RubrikPassword))}
$Type = "application/json"
# Authenticating with API
Try
{
$RubrikSessionResponse = Invoke-RestMethod -Uri $RubrikSessionURL -Headers $Header -Method POST -ContentType $Type
}
Catch
{
$_.Exception.ToString()
$error[0] | Format-List -Force
}
# Extracting the token from the JSON response
$RubrikSessionHeader = @{'Authorization' = "Bearer $($RubrikSessionResponse.token)"}
###############################################
# Getting list of VMs
###############################################
$VMListURL = $baseURL+"vmware/vm?limit=5000"
Try
{
$VMListJSON = Invoke-RestMethod -Uri $VMListURL -TimeoutSec 100 -Headers $RubrikSessionHeader -ContentType $Type
$VMList = $VMListJSON.data
}
Catch
{
$_.Exception.ToString()
$error[0] | Format-List -Force
}
# For troubleshooting output the list of VMs found: $VMList | Select Name | Sort-Object Name,id | Format-Table -AutoSize
###############################################
# Getting list of Hosts
###############################################
$VMHostListURL = $BaseURL+"vmware/host"
Try
{
$VMHostListJSON = Invoke-RestMethod -Uri $VMHostListURL -TimeoutSec 100 -Headers $RubrikSessionHeader -ContentType $Type
$VMHostList = $VMHostListJSON.data
}
Catch
{
$_.Exception.ToString()
$Error[0] | Format-List -Force
}
###################################################################
# Start Per VM Actions here
###################################################################
"Starting per VM RecoveryPlan Actions with VMSuffix:$VMSuffix"
foreach ($VM in $RecoveryPlanVMs)
{
###############################################
# Setting the variables for the current VM
###############################################
$VMName = $VM.VMName
$VMHostSelection = $VM.HostSelection
$VMDisableNetwork = $VM.DisableNetwork
$VMRemoveNetworkDevices = $VM.RemoveNetworkDevices
$VMRubrikPowerOn = $VM.RubrikPowerOn
$VMRunScriptsinLiveMount = $VM.RunScriptsinLiveMount
$VMPreFailoverScript = $VM.PreFailoverScript
$VMPostFailoverScriptDelay = $VM.PostFailoverScriptDelay
$VMPostFailoverScript = $VM.PostFailoverScript
$VMNextVMFailoverDelay = $VM.NextVMFailoverDelay
$VMPreFailoverUserPrompt = $VM.PreFailoverUserPrompt
$VMPostFailoverUserPrompt = $VM.PostFailoverUserPrompt
# Setting VM live mount name
$VMLiveMountName = $VMName + $VMSuffix
# Inserting space in log for readability
"--------------------------------------------"
"Performing Action for VM:$VMName"
# Giving the user 3 seconds to see
sleep 3
###################################################################
# VM Pre-Failover User Prompt
###################################################################
if ($VMPreFailoverUserPrompt -ne "")
{
# Setting title and user prompt
$PromptTitle = "Pre-Failover Prompt"
$PromptMessage = "VM:$VMName
$VMPreFailoverUserPrompt"
# Defining options
$Continue = New-Object System.Management.Automation.Host.ChoiceDescription "&Continue", `
"Continues to run the recovery plan"
$Stop = New-Object System.Management.Automation.Host.ChoiceDescription "&Stop", `
"Stops the recovery plan altogether"
$PromptOptions = [System.Management.Automation.Host.ChoiceDescription[]]($Continue, $Stop)
# Prompting user and defining the result
$PromptResult = $host.ui.PromptForChoice($PromptTitle, $PromptMessage, $PromptOptions, 0)
switch ($PromptResult)
{
0 {"User Selected Continue Recovery Plan"}
1 {"User Selected Stop Recovery Plan"}
}
# Performing the exit action if selected
if ($PromptResult -eq 1)
{
# Stopping transcript
Stop-Transcript
# Killing PowerShell script process
kill $PID
}
}
###############################################
# Getting VM ID and VM snapshot info
###############################################
$SnapshotArray=@()
# Selecting VM ID, if multiple will cycle through each to find a snapshot (can happen if the VM name already exists in the vCenter)
$VMIDs = $VMList | Where-Object {$_.name -eq $VMName}
ForEach ($VMID in $VMIDs)
{
# Setting values
$VMIDName = $VMID.name
$VMID = $VMID.id
$VMSnapshotID = $null
$VMSnapshotDate = $null
# Building Snapshot URL
$VMSnapshotURL = $baseURL+"vmware/vm/"+$VMID+"/snapshot"
# Getting list of snapshots for the VMID
Try 
{
$VMSnapshotJSON = Invoke-RestMethod -Uri $VMSnapshotURL -TimeoutSec 100 -Headers $RubrikSessionHeader -ContentType $Type
$VMSnapshot = $VMSnapshotJSON.data
}
Catch 
{
$_.Exception.ToString()
$error[0] | Format-List -Force
}
# Building a table of all the snapshots for the VM
ForEach ($VMSnap in $VMSnapshot)
{
$SnapshotID = $VMSnap.id
$SnapshotDateOriginal = $VMSnap.date
# Converting from string to datetime format
$SnapshotDateFormat1 = $SnapshotDateOriginal.Replace("T"," ").Replace("Z"," ").TrimEnd()
$SnapshotDateFormat2 = $SnapshotDateFormat1.Substring(0,$SnapshotDateFormat1.Length-4)
# Final conversion
$SnapshotDate = ([datetime]::ParseExact($SnapshotDateFormat2,”yyyy-MM-dd HH:mm:ss”,$null))
# Adding row to table array with information gathered
$SnapshotArrayLine = new-object PSObject
$SnapshotArrayLine | Add-Member -MemberType NoteProperty -Name "VMIDName" -Value "$VMIDName"
$SnapshotArrayLine | Add-Member -MemberType NoteProperty -Name "VMID" -Value "$VMID"
$SnapshotArrayLine | Add-Member -MemberType NoteProperty -Name "SnapshotID" -Value "$SnapshotID"
$SnapshotArrayLine | Add-Member -MemberType NoteProperty -Name "SnapshotDate" -Value "$SnapshotDate"
$SnapshotArray += $SnapshotArrayLine
}
}
###############################################
# Selecting VMID and VMSnapshotID where a VMSnapshotID exists
###############################################
$VMSnapshotID = $SnapshotArray | Where-Object {$_.SnapshotID -ne ""} | Sort-Object -Descending SnapshotDate | Select -ExpandProperty SnapshotID -First 1
$VMSnapshotDate = $SnapshotArray | Where-Object {$_.SnapshotID -eq $VMSnapshotID} | Sort-Object -Descending SnapshotDate | Select -ExpandProperty SnapshotDate -First 1
$VMID = $SnapshotArray | Where-Object {$_.SnapshotID -eq $SnapshotID} | Select -ExpandProperty VMID -First 1
# Setting VMID value if not found (for logging)
IF ($VMID -eq $null)
{
$VMID = " VM and/or Snapshot Not Found In Rubrik"
}
###########################################
# Running pre-failover script if RunScriptsinTest is enabled and script configured
###########################################
if (($VMRunScriptsinLiveMount -eq "TRUE") -and ($VMPreFailoverScript -ne ""))
{
Try
{
"Running Pre-FailoverScript:$VMPreFailoverScript"
invoke-expression $VMPreFailoverScript
}
Catch
{
$_.Exception.ToString()
$error[0] | Format-List -Force
}
}
###########################################
# Selecting VM host, only selecting hosts with datastores ensures that you aren't selecting a host ID which is replicated vs usable by the Rubrik cluster
###########################################
IF ($VMHostSelection -ne "RANDOM")
{
# ESXi hostname has been specified, selecting the host
$VMHostID = $VMHostList | Where-Object {($_.name -eq $VMHostSelection) -and ($_.datastores -ne $null)} | Select -ExpandProperty id -First 1
$VMHostName = $VMHostList | Where-Object {$_.id -eq $VMHostID} | Select -ExpandProperty name -First 1
}
# Setting to RANDOM if no ESXi host found or set to RANDOM
IF (($VMHostID -eq $null) -or ($VMHostSelection -eq "RANDOM"))
{
$VMHostID = $VMHostList | Where-Object {$_.datastores -ne $null} | Get-Random | Select -ExpandProperty id
$VMHostName = $VMHostList | Where-Object {$_.id -eq $VMHostID} | Select -ExpandProperty name -First 1
}
###########################################
# Logging settings
###########################################
"Using the following values:
VMID:$VMID
HostName:$VMHostName
SnapshotID:$VMSnapshotID
SnapshotDate:$VMSnapshotDate
DisableNetwork:$VMDisableNetwork
RemoveNetworkDevices:$VMRemoveNetworkDevices
RubrikPowerOn:$VMRubrikPowerOn"
###########################################
# Creating JSON & configuring URL
###########################################
# Setting default if not specified in CSV
if ($VMDisableNetwork -eq ""){$VMDisableNetwork = "true"}
if ($VMRemoveNetworkDevices -eq ""){$VMRemoveNetworkDevices = "false"}
if ($VMRubrikPowerOn -eq ""){$VMRubrikPowerOn = "true"}
# Forcing to lower case to compensate for excel auto-correct capitalizing
$VMDisableNetwork = $VMDisableNetwork.ToLower()
$VMRemoveNetworkDevices = $VMRemoveNetworkDevices.ToLower()
$VMRubrikPowerOn = $VMRubrikPowerOn.ToLower()
$VMLMJSON =
"{
""vmName"": ""$VMLiveMountName"",
""hostId"": ""$VMHostID"",
""disableNetwork"": $VMDisableNetwork,
""removeNetworkDevices"": $VMRemoveNetworkDevices,
""powerOn"": $VMRubrikPowerOn
}"
$VMLiveMountURL = $baseURL+"vmware/vm/snapshot/"+$VMSnapshotID+"/mount"
###########################################
# POST to REST API URL with VMJSON, but only if a VMID is found
###########################################
IF ($VMSnapshotID -ne $null)
{
Try
{
"Starting LiveMount for VM:$VMLiveMountName"
$VMLiveMountPOST = Invoke-RestMethod -Method Post -Uri $VMLiveMountURL -Body $VMLMJSON -TimeoutSec 100 -Headers $RubrikSessionHeader -ContentType $Type
$VMOperationSuccess = $TRUE
}
Catch
{
$_.Exception.ToString()
$error[0] | Format-List -Force
$VMOperationSuccess = $FALSE
}
# Logging result
"OperationSuccess:$VMOperationSuccess"
}
ELSE
{
$VMOperationSuccess = $FALSE
# Logging result
"OperationSuccess:$VMOperationSuccess"
}
###########################################
# Running post-failover script if RunScriptsinTest is enabled, script configured and test started
###########################################
if (($VMRunScriptsinLiveMount -eq "TRUE") -and ($VMPostFailoverScript -ne "") -and ($VMOperationSuccess -eq $TRUE))
{
# Waiting sleep delay for post script
"Sleeping $VMPostFailoverScriptDelay seconds for VMPostFailoverScriptDelay"
sleep $VMPostFailoverScriptDelay
Try
{
"Running Post-FailoverScript:$VMPostFailoverScript"
invoke-expression $VMPostFailoverScript
}
Catch
{
$_.Exception.ToString()
$error[0] | Format-List -Force
}
}
###########################################
# Waiting for VMNextVMFailoverDelay and Post-Failover Prompt (if configured) if start test was a success
###########################################
IF ($VMOperationSuccess -eq $TRUE)
{
# Ignoring sleep delay if VM isn't set to power on, boot delay will be observed only when powering on VMs
IF ($VMRubrikPowerOn -eq "true")
{
"Sleeping $VMNextVMFailoverDelay seconds for VMNextVMFailoverDelay"
sleep $VMNextVMFailoverDelay
}
###################################################################
# VM Post-Failover User Prompt
###################################################################
if ($VMPostFailoverUserPrompt -ne "")
{
# Setting title and user prompt
$PromptTitle = "Post-Failover Prompt"
$PromptMessage = "VM:$VMName
$VMPostFailoverUserPrompt"
# Defining options
$Continue = New-Object System.Management.Automation.Host.ChoiceDescription "&Continue", `
"Continues to run the recovery plan"
$Stop = New-Object System.Management.Automation.Host.ChoiceDescription "&Stop", `
"Stops the recovery plan altogether"
$PromptOptions = [System.Management.Automation.Host.ChoiceDescription[]]($Continue, $Stop)
# Prompting user and defining the result
$PromptResult = $host.ui.PromptForChoice($PromptTitle, $PromptMessage, $PromptOptions, 0)
switch ($PromptResult)
{
0 {"User Selected Continue Recovery Plan"}
1 {"User Selected Stop Recovery Plan"}
}
# Performing the exit action if selected
if ($PromptResult -eq 1)
{
# Stopping transcript
Stop-Transcript
# Killing PowerShell script process
kill $PID
}
}
# End of "Waiting for VMPostFailoverUserPrompt and Post-Failover Prompt (if configured) if start test was a success" below
}
# End of "Waiting for VMPostFailoverUserPrompt and Post-Failover Prompt (if configured) if start test was a success" above
#
# End of per VM actions below
}
# End of per VM actions above
#
# Inserting space in log for readability
"--------------------------------------------"
"End of RecoveryPlan Script"
################################################
# Stopping logging
################################################
Stop-Transcript
###############################################
# End of script
###############################################

If successful you’ll now have VMs live mounted on your Rubrik cluster and you’ve executed your DR plan! If you just want to test recovery then we need to run the next script below to ensure isolation. This auto-configures the port groups and powers on the VMs following the boot order in the CSV. To run it you’ll need a vSphere 6.5 vCenter as I used the new REST APIs rather than PowerCLI to ensure PowerShell Core compatibility:

################################################
# RubrikRecoveryPlanv2-Configure.ps1 - Configure the variables for vCenter
################################################
$vCenterServer = "192.168.1.10"
$RecoveryPlanCSV = "C:\RubrikRecoveryPlanv2\RubrikRecoveryPlanA.csv"
$LogDirectory = "C:\RubrikRecoveryPlanv2"
# Prompting for username and password to authenicate, can set manually to remove human interaction
$Credentials = Get-Credential -Message "Enter vCenter login credentials"
$vCenterUser = $Credentials.UserName
$Credentials.Password | ConvertFrom-SecureString
$vCenterPassword = $Credentials.GetNetworkCredential().password
# Ensure the suffix matches the suffix specified in your run script otherwise it won't find any VMs to configure!
$VMSuffix = " - Live Mount"
###################################################################
# Nothing to configure below this line - Starting main function
###################################################################
# Adding certificate exception to prevent API errors
################################################
add-type @"
using System.Net;
using System.Security.Cryptography.X509Certificates;
public class TrustAllCertsPolicy : ICertificatePolicy {
public bool CheckValidationResult(
ServicePoint srvPoint, X509Certificate certificate,
WebRequest request, int certificateProblem) {
return true;
}
}
"@
[System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy
################################################
# Starting logging & importing the CSV
################################################
$Now = get-date
$Log = $LogDirectory + "\Rubrik-RecoveryPlanConfigureLog-" + $Now.ToString("yyyy-MM-dd") + "@" + $Now.ToString("HH-mm-ss") + ".log"
Start-Transcript -Path $Log -NoClobber
$RecoveryPlanVMs = import-csv $RecoveryPlanCSV
################################################
# Building vCenter API string & invoking REST API
################################################
$BaseAuthURL = "https://" + $vCenterServer + "/rest/com/vmware/cis/"
$BaseURL = "https://" + $vCenterServer + "/rest/vcenter/"
$vCenterSessionURL = $BaseAuthURL + "session"
$Header = @{"Authorization" = "Basic "+[System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($vCenterUser+":"+$vCenterPassword))}
$Type = "application/json"
# Authenticating with API
Try
{
$vCenterSessionResponse = Invoke-RestMethod -Uri $vCenterSessionURL -Headers $Header -Method POST -ContentType $Type
}
Catch
{
$_.Exception.ToString()
$error[0] | Format-List -Force
}
# Extracting the session ID from the response
$vCenterSessionHeader = @{'vmware-api-session-id' = $vCenterSessionResponse.value}
###############################################
# Getting list of VMs
###############################################
$VMListURL = $BaseURL+"vm"
Try
{
$VMListJSON = Invoke-RestMethod -Method Get -Uri $VMListURL -TimeoutSec 100 -Headers $vCenterSessionHeader -ContentType $Type
$VMList = $VMListJSON.value
}
Catch
{
$_.Exception.ToString()
$error[0] | Format-List -Force
}
###############################################
# Getting list of Port Groups
###############################################
$PortGroupListURL = $BaseURL+"network"
Try
{
$PortGroupListJSON = Invoke-RestMethod -Method Get -Uri $PortGroupListURL -TimeoutSec 100 -Headers $vCenterSessionHeader -ContentType $Type
$PortGroupList = $PortGroupListJSON.value
}
Catch
{
$_.Exception.ToString()
$error[0] | Format-List -Force
}
###################################################################
# Start Per VM Configure Action here
###################################################################
"Starting per VM RecoveryPlan Actions with VMSuffix:$VMSuffix"
foreach ($VM in $RecoveryPlanVMs)
{
###############################################
# Setting the variables for the current VM
###############################################
$VMName = $VM.VMName
$VMDisableNetwork = $VM.DisableNetwork
$VMRubrikPowerOn = $VM.RubrikPowerOn
$VMPostFailoverScriptDelay = $VM.PostFailoverScriptDelay
$VMNextVMFailoverDelay = $VM.NextVMFailoverDelay
$VMvCenterPowerOn = $VM.vCenterPowerOn
$VMConfigureNIC = $VM.ConfigureNIC
$VMConnectNIC = $VM.ConnectNIC
$VMPortGroup = $VM.PortGroup
# Manual testing override
$VMConfigureNIC = "TRUE"
$VMvCenterPowerOn = "TRUE"
# Setting VM live mount name
$VMLiveMountName = $VMName + $VMSuffix
# Inserting space in log for readability
"--------------------------------------------"
"Configuring VM:$VMLiveMountName"
###############################################
# Getting the VM ID from the vCenter VM list
###############################################
$VMID = $VMList | Where-Object {$_.name -eq $VMLiveMountName} | Select -ExpandProperty vm -First 1
IF ($VMID -eq $null)
{
"No VMID found for VM:$VMLiveMountName"
}
ELSE
{
###############################################
# Getting NICs to configure for the VM if set to reconfigure
###############################################
IF ($VMConfigureNIC -eq "TRUE")
{
$VMNICListURL = $BaseURL+"vm/"+$VMID+"/hardware/ethernet"
Try
{
$VMNICListJSON = Invoke-RestMethod -Method Get -Uri $VMNICListURL -TimeoutSec 100 -Headers $vCenterSessionHeader -ContentType $Type
$VMNICList = $VMNICListJSON.value
}
Catch
{
$_.Exception.ToString()
$error[0] | Format-List -Force
}
$VMNICListCount = $VMNICList.count
# Inserting in log for readability
"Configuring $VMNICListCount NICs to PortGroup:$VMPortGroup"
###############################################
# Performing For Each VM NIC Action
###############################################
ForEach ($VMNIC in $VMNICList)
{
# Setting NIC ID
$VMNICID = $VMNIC.nic
# Building NIC URL
$VMNICURL = $BaseURL+"vm/"+$VMID+"/hardware/ethernet/"+$VMNICID
# Selecting Port Group ID
$VMPortGroupID = $PortGroupList | Where-Object {$_.name -eq $VMPortGroup} | Select -ExpandProperty network
# Selecting Port Group Type (needed for vCenter API)
$VMPortGroupType = $PortGroupList | Where-Object {$_.name -eq $VMPortGroup} | Select -ExpandProperty type
# Building JSON
$VMNICJSON =
"{
""spec"":{
""backing"":{
""type"": ""$VMPortGroupType"",
""network"": ""$VMPortGroupID""
}
}
}"
# Patching NIC with new settings
Try
{
Invoke-RestMethod -Method Patch -Uri $VMNICURL -TimeoutSec 100 -Headers $vCenterSessionHeader -Body $VMNICJSON -ContentType $Type
}
Catch
{
$_.Exception.ToString()
$error[0] | Format-List -Force
"Will show error if Port Group is not on the ESXi host of the VM"
}
# Waiting 3 seconds for operation to complete
sleep 3
# Connecting NIC if specified and not already connected by Rubrik (to remove error if already connected)
IF (($VMConnectNIC -eq "TRUE") -And ($VMDisableNetwork -eq "TRUE"))
{
# Building NIC URL
$VMConnectNICURL = $BaseURL+"vm/"+$VMID+"/hardware/ethernet/"+$VMNICID+"/connect"
Try
{
Invoke-RestMethod -Method POST -Uri $VMConnectNICURL -TimeoutSec 100 -Headers $vCenterSessionHeader -ContentType $Type
}
Catch
{
$_.Exception.ToString()
$error[0] | Format-List -Force
"Will error if the VM nic is already connected"
}
# Waiting 3 seconds for operation to complete
sleep 3
#
}
# End of Per NIC actions below
}
# End of Per NIC actions above
# End of IF VMConfigureNIC -eq TRUE below
}
# End of IF VMConfigureNIC -eq TRUE above
###############################################
# Powering On VM if VMvCenterPowerOn TRUE
###############################################
IF ($VMvCenterPowerOn -eq "TRUE")
{
$VMPowerOnURL = $BaseURL+"vm/"+$VMID+"/power/start"
# Output to host
"Powering On VM:$VMName"
# Performing POST
Try
{
Invoke-RestMethod -Method Post -Uri $VMPowerOnURL -TimeoutSec 100 -Headers $vCenterSessionHeader -ContentType $Type
}
Catch
{
$_.Exception.ToString()
$error[0] | Format-List -Force
}
###########################################
# Waiting for VMNextVMFailoverDelay if VMvCenterPowerOn TRUE
###########################################
"Sleeping $VMNextVMFailoverDelay seconds for VMNextVMFailoverDelay"
sleep $VMNextVMFailoverDelay
}
# Inserting space in log for readability
"End Of Operations For VM:$VMLiveMountName"
# Giving the user 3 seconds to see
sleep 3
# End of IF VMID not null below
}
# End of IF VMID not null above
#
# End of per VM actions below
}
# End of per VM actions above
#
# Inserting space in log for readability
"End of RecoveryPlan Script"
"--------------------------------------------"
################################################
# Stopping logging
################################################
Stop-Transcript
###############################################
# End of script
###############################################

You’ve now live mounted your VMs, connected them to an Isolated port group, powered them on and performed a successful DR test, the final step is to stop the test by unmounting your live mounts:

################################################
# RubrikRecoveryPlanv2-Stop.ps1 - Configure the variables below for Rubrik
################################################
$RubrikCluster = "192.168.1.201"
$RecoveryPlanCSV = "C:\RubrikRecoveryPlanv2\RubrikRecoveryPlanA.csv"
$LogDirectory = "C:\RubrikRecoveryPlanv2"
# Prompting for username and password to authenicate, can set manually to remove human interaction
$Credentials = Get-Credential -Message "Enter Rubrik login credentials"
$RubrikUser = $Credentials.UserName
$Credentials.Password | ConvertFrom-SecureString
$RubrikPassword = $Credentials.GetNetworkCredential().password
# Ensure the suffix matches the suffix specified in your run script otherwise it won't find any VMs to unmount!
$VMSuffix = " - Live Mount"
###################################################################
# Nothing to configure below this line - Starting main function
###################################################################
# Adding certificate exception to prevent API errors
################################################
add-type @"
using System.Net;
using System.Security.Cryptography.X509Certificates;
public class TrustAllCertsPolicy : ICertificatePolicy {
public bool CheckValidationResult(
ServicePoint srvPoint, X509Certificate certificate,
WebRequest request, int certificateProblem) {
return true;
}
}
"@
[System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy
################################################
# Starting logging & importing the CSV
################################################
$Now = get-date
$Log = $LogDirectory + "\Rubrik-RecoveryPlanStopLog-" + $Now.ToString("yyyy-MM-dd") + "@" + $Now.ToString("HH-mm-ss") + ".log"
Start-Transcript -Path $Log -NoClobber
$RecoveryPlanVMs = import-csv $RecoveryPlanCSV
################################################
# Building Rubrik API string & invoking REST API
################################################
$BaseURL = "https://" + $RubrikCluster + "/api/v1/"
$RubrikSessionURL = $BaseURL + "session"
$Header = @{"Authorization" = "Basic "+[System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($RubrikUser+":"+$RubrikPassword))}
$Type = "application/json"
# Authenticating with API
Try
{
$RubrikSessionResponse = Invoke-RestMethod -Uri $RubrikSessionURL -Headers $Header -Method POST -ContentType $Type
}
Catch
{
$_.Exception.ToString()
$error[0] | Format-List -Force
}
# Extracting the token from the JSON response
$RubrikSessionHeader = @{'Authorization' = "Bearer $($RubrikSessionResponse.token)"}
###############################################
# Getting list of VMs
###############################################
$VMListURL = $baseURL+"vmware/vm?limit=5000"
Try
{
$VMListJSON = Invoke-RestMethod -Uri $VMListURL -TimeoutSec 100 -Headers $RubrikSessionHeader -ContentType $Type
$VMList = $VMListJSON.data
}
Catch
{
$_.Exception.ToString()
$error[0] | Format-List -Force
}
###############################################
# Getting list of VM Live Mounts - For unmounting GET
###############################################
$VMActiveLiveMountsURL = $baseURL+"vmware/vm/snapshot/mount"
Try
{
$VMActiveLiveMountsJSON = Invoke-RestMethod -Uri $VMActiveLiveMountsURL -TimeoutSec 100 -Headers $RubrikSessionHeader -ContentType $Type
$VMActiveLiveMounts = $VMActiveLiveMountsJSON.data
}
Catch
{
$_.Exception.ToString()
$error[0] | Format-List -Force
}
###################################################################
# Start Per VM UnMount Action here
###################################################################
"Starting per VM RecoveryPlan Actions with VMSuffix:$VMSuffix"
foreach ($VM in $RecoveryPlanVMs)
{
###############################################
# Setting the variables for the current VM
###############################################
$VMName = $VM.VMName
# Setting VM live mount name
$VMLiveMountName = $VMName + $VMSuffix
# Selecting VM ID from live mount name
$VMID = $VMList | Where-Object {$_.name -eq $VMLiveMountName} | Select -ExpandProperty id -First 1
# Getting VM Live Mount ID
$VMLiveMountID = $VMActiveLiveMounts | Where-Object {$_.mountedVmId -eq $VMID} | Select -ExpandProperty id -First 1
# In case of a duplicate VM name in the vCenter the wrong VMID might be selected
# If $VMLiveMountID is $null checking against the 2nd VMID found
IF ($VMLiveMountID -eq $null)
{
$VMID = $VMList | Where-Object {$_.name -eq $VMLiveMountName} | Select -ExpandProperty id -First 1
$VMLiveMountID = $VMActiveLiveMounts | Where-Object {$_.vmId -eq $VMID} | Select -ExpandProperty id -First 1
}
###########################################
# Setting URL running DELETE to REST API
###########################################
$VMUnMountURL = $baseURL+"vmware/vm/snapshot/mount/"+$VMLiveMountID
###########################################
# POST to REST API URL with VMJSON
###########################################
# Only trying unmount if VMID found
IF ($VMLiveMountID -ne $null)
{
# Inserting space in log for readability
"--------------------------------------------"
"Performing UnMount for VM:$VMName"
# Giving the user 3 seconds to see
sleep 3
# DELETE request
Try
{
Invoke-RestMethod -Method DELETE -Uri $VMUnMountURL -TimeoutSec 100 -Headers $RubrikSessionHeader -ContentType $Type
$VMUnMountSuccess = $TRUE
}
Catch
{
$VMUnMountSuccess = $FALSE
$_.Exception.ToString()
$error[0] | Format-List -Force
}
}
# End of per VM actions below
}
# End of per VM actions above
#
# Inserting space in log for readability
"UnMountSuccess:$VMUnMountSuccess"
"--------------------------------------------"
"End of RecoveryPlan Script"
################################################
# Stopping logging
################################################
Stop-Transcript
###############################################
# End of script
###############################################

Hope you found this useful. Happy scripting,

Joshua

  1. SRoetman SRoetman

    I had to drop the “Sort-Object -Descending SnapshotDate” part from the Run script because it was putting the only backup from 2017 at the top of the sorting list:

    12/19/2017 07:14:54
    08/26/2018 08:07:31
    08/25/2018 09:08:26
    08/24/2018 09:03:26
    08/23/2018 08:06:08
    08/22/2018 08:41:13
    08/21/2018 09:09:48
    08/20/2018 09:30:40
    08/19/2018 09:56:08
    08/18/2018 10:21:26
    08/17/2018 10:46:53
    08/16/2018 10:56:36
    08/15/2018 11:21:56
    08/14/2018 11:47:03
    08/13/2018 12:01:29
    08/12/2018 12:26:33
    08/11/2018 12:52:02
    08/10/2018 13:17:24
    08/09/2018 13:31:57
    08/08/2018 13:57:26
    08/07/2018 14:22:00
    08/06/2018 14:47:03
    08/05/2018 15:12:31
    08/04/2018 15:37:52
    08/03/2018 16:03:05
    08/02/2018 16:28:35
    08/01/2018 08:14:26
    07/31/2018 08:03:31
    07/30/2018 08:24:07
    07/29/2018 08:18:47
    07/28/2018 08:11:25
    07/17/2018 08:48:22
    06/17/2018 08:54:06
    05/18/2018 12:47:23
    04/18/2018 08:44:37
    03/19/2018 12:25:16
    02/17/2018 12:07:57
    01/18/2018 07:34:04

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: