Site Recovery Manager configuration export with SRM API 5.0


During a recent implementation of VMware Site Recovery Manager 5.1.1 for one of my clients I faced a problem how I could make a text dump of current SRM settings mainly for a project documentation but also just in case the SRM environment had to be restored from scratch.

I did a short research on that and found Site Recovery Manager Developer’s Guide which presents SRM API 5. Unfortunately SRM API seems to be rather poor and there are some built-in restrictions on what can be extracted from SRM. What’s even worse it won’t get any better in the future as VMware engineers Lee Dilworth and Ken Werneburg clearly stated during the VMworld 2013 session BCO5129 – Protection for All that a richer SRM API could let PowerShell gurus develop a free PowerCLI-based recovery system and according to VMware it would become a competition to SRM, which is a commercial product.

Regardless of the above-mentioned limitations I used SRM API to create the following PowerCLI cmdlet:

Function Get-SrmConfig {
<#
.SYNOPSIS
	Returns the SRM configuration settings
.DESCRIPTION
	The function will return a SRM configuration for
	a given SRM instance. Only some settings are
	exported as SRM API 5 is limited. The function
	also allows writing the exported setings to CSV
	files in a specified folder.
	Always connect to vCenter Server and SRM Server
	available in the protected site.
.NOTES
	Authors: Patrick Wolowicz
.PARAMETER SrmServerAddr
	The SRM Server address.
.PARAMETER User
	The username with SRM administrative priviledges
.PARAMETER Password
	The username's password
.PARAMETER CsvExportFolder
	(Optional) The path to store SRM configuration files.
	The default is $null.
.EXAMPLE
	PS> Get-SrmConfig -SrmServerAddr 192.168.10.50 -User Administrator@local.test -Password xxx
.EXAMPLE
	PS> Get-SrmConfig -SrmServerAddr 192.168.10.50 -User Administrator@local.test -Password xxx -CsvExportFolder .
#>

Param(
	[string]$SrmServerAddr,
	[string]$User,
	[string]$Password,
	[string]$CsvExportFolder = $null
)

[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true}

$web01 = New-WebServiceProxy("http://" + $srmServerAddr + ":9085/srm.wsdl") -Namespace SRM01

$srm01 = New-Object SRM01.Srmbinding
$srm01.url = "https://" + $srmServerAddr + ":9007"
$srm01.CookieContainer = New-Object System.Net.CookieContainer

$mof01 = New-Object SRM01.ManagedObjectReference
$mof01.type = "SrmServiceInstance"
$mof01.value = $mof01.type

$srmApi01 = ($srm01.RetrieveContent($mof01)).srmApi
$protection01 = ($srm01.RetrieveContent($mof01)).protection
$recovery01 = ($srm01.RetrieveContent($mof01)).recovery

Try {
	$srm01.SRMLogin($srmApi01, $User, $Password)
}
Catch [Exception] {
	Write-Host -BackgroundColor Red "Unable to connect to SRM $srmServerAddr"
	Write-Host -BackgroundColor Red $_.Exception.Message
	Return
}
Write-Host -ForegroundColor Yellow "Connected to SRM $srmServerAddr"

##### Recovery plans
$rpTable = New-Object System.Data.DataTable "Recovery Plans"
$rpCol01 = New-Object system.Data.DataColumn Name,([string])
$rpCol02 = New-Object system.Data.DataColumn State,([string])
$rpCol03 = New-Object system.Data.DataColumn ProtectionGroupNames,([string])
$rpTable.Columns.Add($rpCol01)
$rpTable.Columns.Add($rpCol02)
$rpTable.Columns.Add($rpCol03)

$recPlans = $srm01.ListPlans($recovery01)
ForEach ($recPlan in $recPlans){
	$recPlanInfo = $srm01.RecoveryPlanGetInfo($recPlan)

	$pGrps = @()
	ForEach($protGroup in $recPlanInfo.protectionGroups) {
		$pGrps += $srm01.GetInfo($protGroup).name
	}

	$row = $rpTable.NewRow()
	$row.Name = $recPlanInfo.name
	$row.State = $recPlanInfo.state
	$row.ProtectionGroupNames = [string]::Join(", ", $pGrps)
	$rpTable.Rows.Add($row)
}

Write-Host "Exporting recovery plans..."
Write-Host "Row count:" $rpTable.Rows.Count
$rpTable | Format-Table -AutoSize
if(-Not [string]::IsNullOrEmpty($CsvExportFolder)) { $rpTable | Export-Csv "$CsvExportFolder\RecoveryPlans.csv" }

##### Protection groups
$pgTable = New-Object System.Data.DataTable "Protection Groups"
$pgCol01 = New-Object system.Data.DataColumn Name,([string])
$pgCol02 = New-Object system.Data.DataColumn Type,([string])
$pgCol03 = New-Object system.Data.DataColumn State,([string])
$pgCol04 = New-Object system.Data.DataColumn PeerState,([string])
$pgTable.Columns.Add($pgCol01)
$pgTable.Columns.Add($pgCol02)
$pgTable.Columns.Add($pgCol03)
$pgTable.Columns.Add($pgCol04)

##### Protected Datastores
$pdsTable = New-Object System.Data.DataTable "Protected Datastores"
$pdsCol01 = New-Object system.Data.DataColumn Id,([string])
$pdsCol02 = New-Object system.Data.DataColumn Name,([string])
$pdsCol03 = New-Object system.Data.DataColumn Uid,([string])
$pdsCol04 = New-Object system.Data.DataColumn CapacityMB,([Int64])
$pdsCol05 = New-Object system.Data.DataColumn Path,([string])
$pdsCol06 = New-Object system.Data.DataColumn ProtectionGroup,([string])
$pdsTable.Columns.Add($pdsCol01)
$pdsTable.Columns.Add($pdsCol02)
$pdsTable.Columns.Add($pdsCol03)
$pdsTable.Columns.Add($pdsCol04)
$pdsTable.Columns.Add($pdsCol05)
$pdsTable.Columns.Add($pdsCol06)

$protVMInfos = @()
$protGroups = $srm01.ListProtectionGroups($protection01)
ForEach ($protGroup in $protGroups) {

	$protGroupPeer = $srm01.GetPeer($protGroup)
	$protState = $srm01.GetProtectionState($protGroup)
	$protGroupInfo = $srm01.GetInfo($protGroup)

	$row = $pgTable.NewRow()
	$row.Name = $protGroupInfo.name
	$row.Type = $protGroupInfo.type
	$row.State = $protState
	$row.PeerState = $protGroupPeer.state
	$pgTable.Rows.Add($row)

	$protVMs = $srm01.ListProtectedVms($protGroup)
	#$protVMs += $srm01.ListAssociatedVms($protGroup)
	$moVMs = @()
	ForEach($protVM in $protVMs) { $moVMs += $protVM.vm }
	if(($moVMs | Measure-Object).Count) {
		$protVMInfos += $srm01.ProtectionGroupQueryVmProtection($protGroup, $moVMs)
	}

	$protDatastores = @()
	Try {

		#you can list protected datastores for protection groups which use the array-based protection only
		$protDatastores = $srm01.ListProtectedDatastores($protGroup)
	}
	Catch {
	}

	ForEach($protDatastore in $protDatastores) {
		$datastoreID = $protDatastore.value
		$protDatastoreObj = Get-Datastore | Where {$_.id -like "*$datastoreID"}
		$protDatastorePath = Get-VIHumanReadablePath -VIObject $protDatastoreObj

		$row = $pdsTable.NewRow()
		$row.Id = $protDatastoreObj.Id
		$row.Name = $protDatastoreObj.Name
		$row.Uid = $protDatastoreObj.Uid
		$row.CapacityMB = $protDatastoreObj.CapacityMB
		$row.Path = $protDatastorePath
		$row.ProtectionGroup = $protGroupInfo.name

		$pdsTable.Rows.Add($row)
	}
}

Write-Host "Exporting protection groups..."
Write-Host "Row count:" $pgTable.Rows.Count
$pgTable | Format-Table -AutoSize
if(-Not [string]::IsNullOrEmpty($CsvExportFolder)) { $pgTable | Export-Csv "$CsvExportFolder\ProtectionGroups.csv" }

Write-Host "Exporting protected datastores..."
Write-Host "Row count:" $pdsTable.Rows.Count
$pdsTable | Format-Table -AutoSize
if(-Not [string]::IsNullOrEmpty($CsvExportFolder)) { $pdsTable | Export-Csv "$CsvExportFolder\ProtectedDatastores.csv" }

##### Virtual Machines
$vmTable = New-Object System.Data.DataTable "Virtual Machines"
$vmCol01 = New-Object system.Data.DataColumn Id,([string])
$vmCol02 = New-Object system.Data.DataColumn Name,([string])
$vmCol03 = New-Object system.Data.DataColumn ProtectionGroupName,([string])
$vmCol04 = New-Object system.Data.DataColumn ProtectedVmId,([string])
$vmCol05 = New-Object system.Data.DataColumn PeerProtectedVmId,([string])
$vmCol06 = New-Object system.Data.DataColumn Status,([string])
$vmCol07 = New-Object system.Data.DataColumn ReplMgr,([string])
$vmCol08 = New-Object system.Data.DataColumn ReplPort,([string])
$vmCol09 = New-Object system.Data.DataColumn ReplProto,([string])
$vmCol10 = New-Object system.Data.DataColumn ReplRpo,([string])
$vmCol11 = New-Object system.Data.DataColumn ReplPause,([string])
$vmCol12 = New-Object system.Data.DataColumn ReplOpp,([string])
$vmCol13 = New-Object system.Data.DataColumn ReplQuiesce,([string])
$vmCol14 = New-Object system.Data.DataColumn ReplConfGen,([string])
$vmTable.Columns.Add($vmCol01)
$vmTable.Columns.Add($vmCol02)
$vmTable.Columns.Add($vmCol03)
$vmTable.Columns.Add($vmCol04)
$vmTable.Columns.Add($vmCol05)
$vmTable.Columns.Add($vmCol06)
$vmTable.Columns.Add($vmCol07)
$vmTable.Columns.Add($vmCol08)
$vmTable.Columns.Add($vmCol09)
$vmTable.Columns.Add($vmCol10)
$vmTable.Columns.Add($vmCol11)
$vmTable.Columns.Add($vmCol12)
$vmTable.Columns.Add($vmCol13)
$vmTable.Columns.Add($vmCol14)

ForEach ($protVMInfo in $protVMInfos) {

	$vmID = $protVMInfo.vm.value

	$vmObj = Get-VM -Id "*$vmID"
	#$vmObj = Get-VM | Where {$_.id -like "*$vmID"}
	#$vmPath = Get-VIHumanReadablePath -VIObject $protVMInfo.vm

	$row = $vmTable.NewRow()
	$row.Id = $vmID
	$row.Name = $vmObj.name
	$row.ProtectionGroupName = $protVMInfo.protectionGroupName
	$row.ProtectedVmId = $protVMInfo.protectedVM
	$row.PeerProtectedVmId = $protVMInfo.peerProtectedVM
	$row.Status = $protVMInfo.status

	$row.ReplMgr = ($vmObj.extensionData.config.extraconfig | where {$_.Key -eq "hbr_filter.destination"}).Value
	$row.ReplPort = ($vmObj.extensionData.config.extraconfig | where {$_.Key -eq "hbr_filter.port"}).Value
	$row.ReplProto = ($vmObj.extensionData.config.extraconfig | where {$_.Key -eq "hbr_filter.protocol"}).Value
	$row.ReplRpo = ($vmObj.extensionData.config.extraconfig | where {$_.Key -eq "hbr_filter.rpo"}).Value
	$row.ReplPause = ($vmObj.extensionData.config.extraconfig | where {$_.Key -eq "hbr_filter.pause"}).Value
	$row.ReplOpp = ($vmObj.extensionData.config.extraconfig | where {$_.Key -eq "hbr_filter.opp"}).Value
	$row.ReplQuiesce = ($vmObj.extensionData.config.extraconfig | where {$_.Key -eq "hbr_filter.quiesce"}).Value
	$row.ReplConfGen = ($vmObj.extensionData.config.extraconfig | where {$_.Key -eq "hbr_filter.configGen"}).Value

	$vmTable.Rows.Add($row)
}
Write-Host "Exporting VMs..."
Write-Host "Row count:" $vmTable.Rows.Count
$vmTable | Format-Table -AutoSize
if(-Not [string]::IsNullOrEmpty($CsvExportFolder)) { $vmTable | Export-Csv "$CsvExportFolder\VirtualMachines.csv" }

##### Replicated Datastores
$rdsTable = New-Object System.Data.DataTable "Replicated Datastores"
$rdsCol01 = New-Object system.Data.DataColumn Id,([string])
$rdsCol02 = New-Object system.Data.DataColumn Name,([string])
$rdsCol03 = New-Object system.Data.DataColumn Uid,([string])
$rdsCol04 = New-Object system.Data.DataColumn CapacityMB,([Int64])
$rdsCol05 = New-Object system.Data.DataColumn Path,([string])
$rdsTable.Columns.Add($rdsCol01)
$rdsTable.Columns.Add($rdsCol02)
$rdsTable.Columns.Add($rdsCol03)
$rdsTable.Columns.Add($rdsCol04)
$rdsTable.Columns.Add($rdsCol05)

$replDatastores = $srm01.ListReplicatedDatastores($protection01)
ForEach($replDatastore in $replDatastores) {

	$datastoreID = $replDatastore.value
	$replDatastoreObj = Get-Datastore | Where {$_.id -like "*$datastoreID"}
	$replDatastorePath = Get-VIHumanReadablePath -VIObject $replDatastoreObj

	$row = $rdsTable.NewRow()
	$row.Id = $replDatastoreObj.Id
	$row.Name = $replDatastoreObj.Name
	$row.Uid = $replDatastoreObj.Uid
	$row.CapacityMB = $replDatastoreObj.CapacityMB
	$row.Path = $replDatastorePath

	$rdsTable.Rows.Add($row)
}

Write-Host "Exporting replicated datastores..."
Write-Host "Row count:" $rdsTable.Rows.Count
$rdsTable | Format-Table -AutoSize
if(-Not [string]::IsNullOrEmpty($CsvExportFolder)) { $rdsTable | Export-Csv "$CsvExportFolder\ReplicatedDatastores.csv" }

##### Inventory Mappings
$imTable = New-Object System.Data.DataTable "Inventory Mappings"
$imCol01 = New-Object system.Data.DataColumn Id,([string])
$imCol02 = New-Object system.Data.DataColumn Name,([string])
$imCol03 = New-Object system.Data.DataColumn Type,([string])
$imCol04 = New-Object system.Data.DataColumn Path,([string])
$imTable.Columns.Add($imCol01)
$imTable.Columns.Add($imCol02)
$imTable.Columns.Add($imCol03)
$imTable.Columns.Add($imCol04)

$invMappingInfo = $srm01.ListInventoryMappings($protection01)
ForEach($mapFolder in $invMappingInfo.folders) {

	$folderID = $mapFolder.value
	$mapFolderObj = Get-Folder | Where {$_.id -like "*$folderID"}
	$mapFolderPath = Get-VIHumanReadablePath -VIObject $mapFolderObj

	$row = $imTable.NewRow()
	$row.Id = $mapFolderObj.Id
	$row.Name = $mapFolderObj.Name
	$row.Type = $mapFolder.Type
	$row.Path = $mapFolderPath

	$imTable.Rows.Add($row)

	<#
	$mapFolderObj | fl
	$mapFolderObj.ExtensionData | fl
	#>
}

ForEach($mapPool in $invMappingInfo.pools) {

	$poolID = $mapPool.value
	$mapPoolObj = Get-ResourcePool | Where {$_.id -like "*$poolID"}
	$mapPoolPath = Get-VIHumanReadablePath -VIObject $mapPoolObj

	$row = $imTable.NewRow()
	$row.Id = $mapPoolObj.Id
	$row.Name = $mapPoolObj.Name
	$row.Type = $mapPool.Type
	$row.Path = $mapPoolPath

	$imTable.Rows.Add($row)
}

Write-Host "Exporting inventory mappings..."
Write-Host "Row count:" $imTable.Rows.Count
$imTable | Format-Table -AutoSize
if(-Not [string]::IsNullOrEmpty($CsvExportFolder)) { $imTable | Export-Csv "$CsvExportFolder\InventoryMappings.csv" }

##### Disconnecting...
$srm01.SRMLogout($srmApi01)

}

The cmdlet connects to a SRM Server, extracts configuration settings and prints them in tables to a stdout. The tables can be also stored in CSV files. A connection to a vCenter Server is also required prior to executing the cmdlet.

A piece of code which gets vShere Replication settings was inspired by William Lam’s article.

The function Get-VIHumanReadablePath was taken from the ForwardOrReverse blog:

Function Get-VIHumanReadablePath
{
    Param
    (
        $VIObject
    )

    Try {
        if ($VIObject.GetType().BaseType -ne [VMware.Vim.ManagedEntity]) {

            $VIObject = $VIObject | Get-View -ErrorAction SilentlyContinue
        }
    }
    Catch {
        $VIObject = $null
    }

    If (!$VIObject) {

        throw "Get-VIHumanReadablePath: Could not get the Managed Entity representation of -target"
    }

    $objPath = $VIObject.Name
    while ($VIObject.Parent)
    {
        $VIObject = Get-View -Id $VIObject.Parent
        $objPath = $VIObject.Name + "/" + $objPath
    }

    $objPath
}

Go ahead, get and run the cmdlet if you find it useful!

,

  1. #1 by Matt Ray on March 4, 2014 - 18:40

    Great script, just what I was looking for, thanks! I found a typo on line 154, it should be $protDatastoreObj = instead of $protDatastoresObj =

    Matt

    • #2 by ljolek on March 4, 2014 - 20:31

      Updated, Thanks Matt!

(will not be published)