vCDSitePair.psm1
## vCDSitePair.psm1 - PowerShell Module for configuring vCloud Director multi- ## site pairing operations. ## Version: 2.0.1 ## Author: Jon Waite ## Copyright: Copyright (c) Jon Waite 2020, All Rights Reserved ## Licence: MIT # Release Notes: # Note that a previous version of this module was released which did not support # operations against vCloud API endpoints which had invalid SSL certificates. # This module uses the new (in PowerShell 6.x and later) 'SkipCertificateCheck' # switch added to Invoke-RestMethod. As such it will not work with previous # versions of PowerShell and requires PowerShell or PowerShell Core v6.0 # or later. # Each cmdlet is documented, use Get-Help <cmdlet name> for details # e.g. Get-Help Get-vCloudSiteName # cmdlets included in this module: # # Name Function # Invoke-vCloud Internal Helper function to submit to the vCD API # Get-vCloudSiteName Shows current name assigned to a vCloud site # Set-vCloudSiteName Sets the name assigned to a vCloud site # Get-vCloudSiteAssoc Shows current site associations for a site # Remove-vCloudSiteAssoc Removes a site association from a site # Invoke-vCDPairSites Create a new site association between 2 sites # Note: # This module is intended for use by cloud providers to configure the overall # ('System' level) site pairing between vCloud Director sites. It is not # intended to be used by tenants to configure their own pairing between # tenant organizations in multiple provider sites - the built-in UI has # the capability for this already by downloading and uploading the site # association XML files. # Helper function to interact with the vCloud API (Not exported): Function Invoke-vCloud{ [cmdletbinding()]Param( [Parameter(Mandatory)][uri]$URI, [string]$method = "get", [string]$apiVersion = "29.0", [string]$body, [string]$contentType, [int]$timeout = 40, [boolean]$waitForTask = $false, [boolean]$allowInsecure ) # Check if we have a valid PowerCLI session to use: $sessionID = ($Global:DefaultCIServers | Where-Object { $_.Name -eq $URI.Host }).SessionID if (!$sessionID) { # If we didn't find an existing PowerCLI session for our URI Write-Error ("No PowerCLI session found for $($uri), exiting.") break } $headers = @{ "x-vcloud-authorization" = $sessionID; "Accept" = "application/*+xml;version=$($apiVersion)" } $parms = @{'Method'=$method; 'URI'=$uri; 'headers'=$headers; 'timeout'=$timeout;} if ($ContentType) { $parms.Add("ContentType", $ContentType) } if ($Body) { $parms.Add("Body",$Body) } if ($allowInsecure) { $parms.Add("SkipCertificateCheck",$true) } # Send request: Try { [xml]$response = Invoke-RestMethod @parms } catch { # Error returned Write-Warning ("Exception: $($_.Exception.Message)") if ( $_.Exception.ItemName ) { Write-Warning ("Failed Item: $($_.Exception.ItemName)") } Return } if ($waitForTask) { # If we've asked to wait for async task to complete if ($response.Task.href) { # and we've got a Task event returned $taskparams = @{'Method'='Get'; 'URI'=$response.Task.href; 'headers'=$headers; 'TimeoutSec'=5} if ($allowInsecure) { $taskparams.Add("SkipCertificateCheck",$true) } Write-Host ("Task submitted successfully, waiting for completion or timeout.") Write-Host ("q=queued, P=pre-running, .=Running:") while ($timeout -gt 0) { # while within our timeout Try { $taskxml = Invoke-RestMethod @taskparams } catch { Write-Warning ("Exception while waiting for task to complete: $($_.Exception.Message)") if ( $_.Exception.ItemName ) { Write-Warning ("Failed Item: $($_.Exception.ItemName)") } Write-Warning ("Task may still be running.") Return } switch ($taskxml.Task.status) { "success" { Write-Host " "; Write-Host "Task completed successfully"; return $true; break } "running" { Write-Host -NoNewline "." } "error" { write-Host " "; Write-Warning "Error running task"; return $false; break } "canceled" { Write-Host " "; Write-Warning "Task was cancelled"; return $false; break } "aborted" { Write-Host " "; Write-Warning "Task was aborted"; return $false; break } "queued" { Write-Host -NoNewline "q" } "preRunning" { Write-Host -NoNewline "P" } } $timeout -= 1 Start-Sleep -s 1 } # Timeout expired Write-Warning "Task timeout reached (task may still be in progress)" return $false } else { Write-Host ("Wait for task requested, but no Task returned from API") } } return $response # return API query response } Function Get-vCloudSiteName{ <# .SYNOPSIS Return the vCD SiteName for a given site URI (if configured) .DESCRIPTION Queries the vCloud REST API and returns the SiteAssociationMember\SiteName from the /api/site/associations/localAssociationData XML. .PARAMETER siteDomain The URI of the site to be accessed, you must be connected to the site already (Connect-CIServer) with an account that has System level access to this vCloud Director instance. .PARAMETER allowInsecure If specified this parameter allows interaction with vCloud API endpoints using an invalid SSL certificate (not recommended in production). If not specified the vCloud API must use a trusted SSL certificate. .OUTPUTS Any configured site name from the localAssociationData (or nothing if no site configuration is returned). .EXAMPLE Get-vCloudSiteName -siteDomain 'sitea.mycloud.com' .NOTES You must have an existing PowerCLI connection to vCloud Director (Connect-CIServer) in your current PowerShell session for calls to suceed. Will only function against vCloud Director versions 9 and later as this API call is not implemented in earlier versions. #> [cmdletbinding()]Param( [Parameter(Mandatory=$true)][string]$siteDomain, [switch]$allowInsecure ) [xml]$localAssociationData = Invoke-vCloud -URI "https://$siteDomain/api/site/associations/localAssociationData" -Method 'Get' -ApiVersion '29.0' -allowInsecure $allowInsecure return $localAssociationData.SiteAssociationMember.SiteName } Function Set-vCloudSiteName{ <# .SYNOPSIS Sets or updates the vCD SiteName for a given site URI .DESCRIPTION Configures the vCloud Director 'SiteName' in localAssociationData with the specified value. If 'SiteNmae' is already configured it will be updated with the value specified. .PARAMETER siteDomain The URI of the site in which the 'SiteName' is to be changed, you must be connected to the site already (Connect-CIServer) with an account that has System level access to the vCloud Director instance. .PARAMETER siteName The 'SiteName' to be set for this vCloud Director site. .PARAMETER allowInsecure If specified this parameter allows interaction with vCloud API endpoints using an invalid SSL certificate (not recommended in production). If not specified the vCloud API must use a trusted SSL certificate. .OUTPUTS The response from the Invoke-vCloud API call. .EXAMPLE Set-vCloudSiteName -siteDomain 'sitea.mycloud.com' -siteName 'SiteA' .NOTES You must have an existing PowerCLI connection to vCloud Director (Connect-CIServer) in your current PowerShell session for calls to suceed. Will only function against vCloud Director versions 9 and later as this API call is not implemented in earlier versions. #> [CmdletBinding()]Param( [Parameter(Mandatory=$true)][string]$siteDomain, [Parameter(Mandatory=$true)][String]$siteName, [switch]$allowInsecure, [Int]$Timeout = 30 ) [xml]$localAssociationData = Invoke-vCloud -URI "https://$siteDomain/api/site/associations/localAssociationData" -Method 'Get' -ApiVersion '29.0' -allowInsecure $allowInsecure $localAssociationData.SiteAssociationMember.SiteName = $siteName $editURI = $localAssociationData.SiteAssociationMember.Link | Where-Object{ $_.rel -eq 'edit' } $response = Invoke-vCloud -URI $editURI.href -ContentType 'application/vnd.vmware.admin.siteAssociation+xml' -Method 'Put' -ApiVersion '29.0' -Body $localAssociationData.InnerXml -WaitForTask $true -allowInsecure $allowInsecure return $response } Function Get-vCloudSiteAssoc{ <# .SYNOPSIS Shows any existing vCloud Director site associations for a given vCloud Director API endpoint. .DESCRIPTION Reads the 'siteAssociations' return from /api/site/associations for the given vCloud Director instance and lists any other vCloud Director sites which have already been associated with the queried site. .PARAMETER siteDomain The URI of the site to query for existing site associations. You must be connected to the site already (Connect-CIServer) with an account that has System level access to the vCloud Director instance. .PARAMETER allowInsecure If specified this parameter allows interaction with vCloud API endpoints using an invalid SSL certificate (not recommended in production). If not specified the vCloud API must use a trusted SSL certificate. .OUTPUTS Any configured site associations for the specified vCloud instance. .EXAMPLE Get-vCloudSiteAssociations -siteDomain 'sitea.mycloud.com' .NOTES You must have an existing PowerCLI connection to vCloud Director (Connect-CIServer) in your current PowerShell session for calls to suceed. Will only function against vCloud Director versions 9 and later as this API call is not implemented in earlier versions. #> [CmdletBinding()]Param( [Parameter(Mandatory=$true)][string]$siteDomain, [switch]$allowInsecure, [Int]$Timeout = 30 ) [xml]$localAssociationData = Invoke-vCloud -URI "https://$siteDomain/api/site/associations/localAssociationData" -Method 'Get' -ApiVersion '29.0' -allowInsecure $allowInsecure $sitename = $localAssociationData.SiteAssociationMember.SiteName $siteid = $localAssociationData.SiteAssociationMember.SiteId Write-Host -ForegroundColor Green ("Site associations for site Id: $($siteid) with site Name: $($sitename)") [xml]$siteAssociationData = Invoke-vCloud -URI "https://$siteDomain/api/site/associations" -Method 'Get' -ApiVersion '29.0' -allowInsecure $allowInsecure $members = $siteAssociationData.SiteAssociations.SiteAssociationMember if ($members.HasChildNodes) { Write-Host -ForegroundColor Green "Associated sites:" foreach ($site in $members) { $siteId = ($site.SiteId).substring($site.SiteId.LastIndexOf(':')+1) Write-Host ("Site: $($site.SiteName) with Site Id: $($siteId) at $(([uri]$site.RestEndpoint).Host)") } } else { Write-Host "No site associations found" } return $members } Function Remove-vCloudSiteAssoc{ <# .SYNOPSIS Removes an existing vCloud site association from the specified endpoint matching the specified remote site Id. .DESCRIPTION Allows an existing site association to be removed from a single vCloud API endpoint. Note that for existing site pairs this will need to be done twice (once for each site each time specifying the appropriate remote site Id) to completely remove the pairing. .PARAMETER siteDomain The URI of the site from which the pairing is to be removed. You must be connected to the site already (Connect-CIServer) with an account that has System level access to the vCloud Director instance. .PARAMETER removeId The site Id of the remote site to be removed from the associations on the vCloud environment specified by siteDomain. Site Ids can be found using the Get-vCloudSiteAssoc cmdlet. .PARAMETER allowInsecure If specified this parameter allows interaction with vCloud API endpoints using an invalid SSL certificate (not recommended in production). If not specified the vCloud API must use a trusted SSL certificate. .OUTPUTS None, console messages indicate the success or failure of the removal attempt. .EXAMPLE Remove-vCloudSiteAssoc -siteDomain 'sitea.mycloud.com' -removeId '438061ba-85c6-4b12-bd2c-fdfb820b2a4f' .NOTES You must have an existing PowerCLI connection to vCloud Director (Connect-CIServer) in your current PowerShell session for calls to suceed. Will only function against vCloud Director versions 9 and later as this API call is not implemented in earlier versions. #> [CmdletBinding()]Param( [Parameter(Mandatory=$true)][string]$siteDomain, [Parameter(Mandatory=$true)][string]$removeId, [switch]$allowInsecure, [Int]$Timeout = 30 ) # Get all current Site Association data into $SAData: [xml]$SAData = Invoke-vCloud -URI "https://$siteDomain/api/site/associations" -Method 'Get' -ApiVersion '29.0' -allowInsecure $allowInsecure # Add Namespace Manager to Site Association Data XML: $nsm = New-Object System.Xml.XmlNamespaceManager($SAData.NameTable) $nsm.AddNamespace("ns","http://www.vmware.com/vcloud/v1.5") # Attempt to locate a node in the XML tree where SiteId matches our removal value: $removeNode = $SAData.SelectSingleNode("//ns:SiteId[.='urn:vcloud:site:$($removeId)']", $nsm).ParentNode if ($removeNode) { # We successfully matched this Site Id in the XML Write-Host ("Matched site association with site Id $($removeId), removing site association") # Remove the matching XML node from $SAData: $removeNode.ParentNode.RemoveChild($removeNode) | Out-Null # And submit the modified XML back to the API: Invoke-vCloud -Uri "https://$siteDomain/api/site/associations" ` -ContentType 'application/vnd.vmware.admin.siteAssociations+xml' ` -Method 'Put' -apiVersion '29.0' -Body $SAData.InnerXml ` -waitForTask $true -allowInsecure $allowInsecure return } else { Write-Host ("Could not match site association with site Id $($removeId), exiting.") return } } Function Invoke-vCDPairSites{ <# .SYNOPSIS Creates a vCloud Director pairing between 2 vCD instances. .DESCRIPTION Creates system-level pairing between two vCloud Director sites. .PARAMETER siteADomain The domain name of the api endpoint for the first site to be paired. .PARAMETER siteBDomain The domain name of the api endpoint for the second site to be paired. .PARAMETER WhatIf A flag that determines whether to actually perform the site pairing or just return information on what would be done. .PARAMETER allowInsecure If specified this parameter allows interaction with vCloud API endpoints using an invalid SSL certificate (not recommended in production). If not specified the vCloud APIs must use a trusted SSL certificates. .OUTPUTS The results of the pairing attempt from Site A -> Site B and from Site B -> Site A. .EXAMPLE Invoke-vCDPairSites -siteADomain 'sitea.mycloud.com' -siteBDomain 'siteb.mycloud.com' -WhatIf $false .NOTES You must have an existing PowerCLI connection to vCloud Director (Connect-CIServer) to both sites in your current PowerShell session for this to suceed. Will only function against vCloud Director versions 9 and later as the API calls are not implemented in earlier versions. #> [CmdletBinding()]Param( [Parameter(Mandatory=$true)][string]$siteADomain, [Parameter(Mandatory=$true)][string]$siteBDomain, [Boolean]$WhatIf = $true, [switch]$allowInsecure, [Int]$Timeout = 30 ) if ($WhatIf) { Write-Host -ForegroundColor Green 'Running in information mode only - no API changes will be made unless you run with -WhatIf $false' } else { Write-Host -ForegroundColor Green 'Running in implementation mode, API changes will be committed' } [xml]$sALAD = Invoke-vCloud -URI "https://$siteADomain/api/site/associations/localAssociationData" -ApiVersion '29.0' -allowInsecure $allowInsecure $sAName = $sALAD.SiteAssociationMember.SiteName Write-Host -ForegroundColor Green "Site A returned site ID as: $($sALAD.SiteAssociationMember.SiteId)" Write-Host -ForegroundColor Green "Site A returned site name as: $sAName" [xml]$sBLAD = Invoke-vCloud -URI "https://$siteBDomain/api/site/associations/localAssociationData" -ApiVersion '29.0' -allowInsecure $allowInsecure $sBName = $sBLAD.SiteAssociationMember.SiteName Write-Host -ForegroundColor Green "Site B returned site ID as: $($sBLAD.SiteAssociationMember.SiteId)" Write-Host -ForegroundColor Green "Site B returned site name as: $sBName" If (!$sAName -or !$sBName) { Write-Host -ForegroundColor Red "Site name is missing for one or more sites, configure with Set-vCloudSiteName before using vCD-PairSites, exiting" return } If ($sALAD.SiteAssociationMember.SiteId -eq $sBLAD.SiteAssociationMember.SiteID) { Write-Host -ForegroundColor Red "Site Id's for site A and site B are identical, Invoke-vCDPairSites must be used between different vCD Cells, exiting" return } if (!$WhatIf) { Write-Host -ForegroundColor Green "Associating $sAName (Site A) with $sBName (Site B)" $result = Invoke-vCloud -URI "https://$siteBDomain/api/site/associations" -Method POST -Body $sALAD.InnerXml -ContentType 'application/vnd.vmware.admin.siteAssociation+xml' -ApiVersion '29.0' -WaitForTask $true -allowInsecure $allowInsecure Write-Host "Returned Result = $result" Write-Host -ForegroundColor Green "Associating $sBName (Site B) with $sAName (Site A)" $result = Invoke-vCloud -URI "https://$siteADomain/api/site/associations" -Method POST -Body $sBLAD.InnerXml -ContentType 'application/vnd.vmware.admin.siteAssociation+xml' -ApiVersion '29.0' -WaitForTask $true -allowInsecure $allowInsecure Write-Host "Returned Result = $result" } else { Write-Host -ForegroundColor Yellow "Not performing site association as running in information mode" } } Export-ModuleMember -Function Get-vCloudSiteName Export-ModuleMember -Function Set-vCloudSiteName Export-ModuleMember -Function Get-vCloudSiteAssoc Export-ModuleMember -Function Remove-vCloudSiteAssoc Export-ModuleMember -Function Invoke-vCDPairSites |