PureStorage.FlashArray.Backup.psm1
Set-StrictMode -Version 1.0 $here = Split-Path -Parent $MyInvocation.MyCommand.Path -ErrorAction Stop Import-Module PureStoragePowerShellSDK2 Add-Type -AssemblyName System.Core Add-Type -AssemblyName System.Security $script:StatusSucceeded = "Succeeded" $script:SDKPrefix = "PSB" $RestLimit = 100 $RetentionPolicySecs = 86400 $RetentionPolicySnapsPerDay = 4 $RetentionPolicyDaysToKeep = 7 $PSBSDK_Remoting_Dir = "$here" . "$PSBSDK_Remoting_Dir\Utils.ps1" . "$PSBSDK_Remoting_Dir\Utils.Log.ps1" . "$PSBSDK_Remoting_Dir\Utils.Exported.ps1" . "$PSBSDK_Remoting_Dir\Utils.VMWare.ps1" . "$PSBSDK_Remoting_Dir\Utils.VolumeSet.ps1" . "$PSBSDK_Remoting_Dir\Utils.VVOLS.ps1" . "$PSBSDK_Remoting_Dir\Utils.CrashCons.ps1" # Tag Keys enum PureTagKeys { SnapType HistoryId MountId Metadata } # Tag Namespaces $NamespaceVolSet = "$($script:SDKPrefix)_volset" $NamespaceMount = "$($script:SDKPrefix)_mount" $NamespaceMeta = "$($script:SDKPrefix)_meta" # Status msgs $validateVolSetMsg = "Validating Volume set." $validatePgroupMsg = "Validating protection group choice." $takingSnapshotMsg = "Taking snapshot" $replicatingSnapshotMsg = "Replicating snapshot" $noPgroupWarnMsg = "The volume set specified contains multiple volumes. Taking snapshots of multiple volumes outside of a protection group will not guarantee crash consistency. Are you sure you want to proceed?" $noPgroupDescMsg = "Taking snapshots of multiple volumes without Protection Groups. Crash consistency cannot be guaranteed." $noPgroupTitleMsg = "NoPgroup Option Selected" $userAbortedMsg = "Operation cancelled by the user" $GetCorVolMsg = "Getting corresponding volumes on FlashArray" $TagVolsMsg = "Tagging matching volumes on FlashArray" $LoadTagsMsg = "Loading tags from FlashArray" $BldVolSetMsg = "Building volume set objects" $RemMetaMsg = "Removing metadata" $FindMatPgMsg = "Finding matching pgroups" $vSetExistsDescMsg = "Overwriting existing volume set. This might affect existing Backups" $vSetExistsWarnMsg = "A volume set with this name already exists, do you want to overwrite it? This will affect other future Backups." $vSetExistsTitleMsg = "Volume Set Name already exists" $ModulePath = "$env:ProgramW6432\WindowsPowerShell\Modules" if (-not $env:PSModulePath.Contains($ModulePath)) { $env:PSModulePath += ";$ModulePath" } ################# EXPORTED CMDLETS ################# <# .Synopsis Creates a snapshot on all of the Pure Volumes in the Volume Set. .Description Create a snapshot on all of the Pure Volumes in the Volume Set. Decide on the Protection Group behavior by utilizing the appropriate parameters. If no Protection Groups exist, volume snapshots can be taken using the -NoPgroup parameter with the caveat that if there is more than 1 volume it is not guaranteed to be consistent without using a Protection Group snapshot. A Protection Group can be created using the -createpgroup parameter. If a single Protection Group exists you can declare it in the Invoke-PsbSnapshotJob cmdlet with the -PgroupName parameter. If more than one Protection Groups exist, you can enumerate their names with this cmdlet and choose the one you want, then declare it with the Invoke-PsbSnapshotJob cmdlet with the -PgroupName parameter. You can also let the Invoke-PsbSnapshotJob choose the Protection Group that has all volumes in the volume set, with the fewest number of other volumes with the -UseBestPgroupMatch parameter, as denoted in the "ExtraVolumesCount" column from this cmdlet. .Parameter VolumeSetName Volume Set Name that identifies an existing Volume Set on the Flash Array. .Parameter FlashArrayAddress The FlashArray address .Parameter FlashArrayCredential FlashArray credentials .Parameter ComputerAddress The name of the Computer .Parameter ComputerCredential Computer credentials .Parameter Path The Path to the disks to be backed up. Must be Drive Letters or Mount Points. .Parameter VCenterAddress The name of the controlling VCenter, if the computer is a VM .Parameter VCenterCredential VCenter credentials .Parameter VMName The name of the VM, if the computer is a VM. This may be different from the ComputerAddress. .Parameter VMPersistentId The PersistentId (also referred to as the InstanceUuid or MoRef) of the VM. .Parameter SkipValidation Do not validate configuration before saving. .Parameter VolumeType The type of the volumes being added to the set. Valid values are RDM, VVOL or Physical .Parameter PgroupName The name of an existing Protection Group to be used for taking the snapshot. .Parameter UseBestPgroupMatch The best match among the Protection Groups that include all volumes in the volume set will be used for taking the snapshot. .Parameter CreatePgroup A protection group with the name specified by 'NewPgroupName' will be created, including all volumes in the volume set. .Parameter NewPgroupSuffix The suffix that will be used to create the new Protection Group. The protection group will be named PSB-NewPgroupSuffix .Parameter NoPgroup When selected, the function will use volume snapshots rather than Protection Group Snapshots. Volume snapshots are taken individually and cannot guarantee consistency among multiple volumes in the set. Volume snapshots are not subject to retention policies and need to be removed manually. .Example Invoke-PsbSnapshotJob -FlashArrayAddress $FlashArrayEndpoint -FlashArrayCredential $FlashArrayCredential -VolumeSetName qsqlvm4g -VolumeType VVOL -VCenterAddress $vCenterEndpoint -VCenterCredential $vCenterCredential -ComputerAddress $vmAddress -ComputerCredential $vmCredential -Path 'g:\' -VMName $vmName -VMPersistentId $vmpid -usebestpgroupmatch Creates Protection Group snapshots on all volumes in the Volume Set and uses the best Protection Group. .Example Invoke-PsbSnapshotJob -FlashArrayAddress $FlashArrayEndpoint -FlashArrayCredential $FlashArrayCredential -VolumeSetName qsqlvm4g -VolumeType VVOL -VCenterAddress $vCenterEndpoint -VCenterCredential $vCenterCredential -ComputerAddress $vmAddress -ComputerCredential $vmCredential -Path 'g:\' -VMName $vmName -VMPersistentId $vmpid -createpgroup -newpgroupsuffix temp-pgroup1 Creates Protection Group snapshots on all volumes in the Volume Set and creates a Protection Group with the declared suffix. .Example Invoke-PsbSnapshotJob -FlashArrayAddress $FlashArrayEndpoint -FlashArrayCredential $FlashArrayCredential -VolumeSetName qsqlvm4-g -VolumeType VVOL -VCenterAddress $vCenterEndpoint -VCenterCredential $vCenterCredential -ComputerAddress $vmAddress -ComputerCredential $vmCredential -Path 'g:\' -VMName $vmName -VMPersistentId $vmpid -pgroupname asyncdemo Creates Protection Group snapshots on all volumes in the Volume Set and creates a uses the declared Protection Group asyncdemo. .Example Invoke-PsbSnapshotJob -FlashArrayAddress $FlashArrayEndpoint -FlashArrayCredential $FlashArrayCredential -VolumeSetName qsqlvm4-g -VolumeType VVOL -VCenterAddress $vCenterEndpoint -VCenterCredential $vCenterCredential -ComputerAddress $vmAddress -ComputerCredential $vmCredential -Path 'g:\' -VMName $vmName -VMPersistentId $vmpid -nopgroup Creates volume snapshots on all volumes in the Volume Set. #> function Invoke-PsbSnapshotJob { [CmdletBinding(SupportsShouldProcess,ConfirmImpact='High')] Param ( [parameter(Mandatory = $true)] [string] $FlashArrayAddress, [parameter(Mandatory = $true)] [PSCredential] $FlashArrayCredential, [parameter(Mandatory = $true)] [string] $VolumeSetName, [parameter(Mandatory = $true)] [string] $ComputerAddress, [parameter(ParameterSetName='Credential_Virtual_UseExistingPgroup' , Mandatory = $true)] [parameter(ParameterSetName='Credential_Virtual_UseBestPgroupMatch' , Mandatory = $true)] [parameter(ParameterSetName='Credential_Virtual_CreatePgroup' , Mandatory = $true)] [parameter(ParameterSetName='Credential_Virtual_NoPgroup' , Mandatory = $true)] [parameter(ParameterSetName='Credential_Physical_UseExistingPgroup' , Mandatory = $true)] [parameter(ParameterSetName='Credential_Physical_UseBestPgroupMatch' , Mandatory = $true)] [parameter(ParameterSetName='Credential_Physical_CreatePgroup' , Mandatory = $true)] [parameter(ParameterSetName='Credential_Physical_NoPgroup' , Mandatory = $true)] [PSCredential] $ComputerCredential, [parameter(ParameterSetName='PSSession_Virtual_UseExistingPgroup' , Mandatory = $true)] [parameter(ParameterSetName='PSSession_Virtual_UseBestPgroupMatch' , Mandatory = $true)] [parameter(ParameterSetName='PSSession_Virtual_CreatePgroup' , Mandatory = $true)] [parameter(ParameterSetName='PSSession_Virtual_NoPgroup' , Mandatory = $true)] [parameter(ParameterSetName='PSSession_Physical_UseExistingPgroup' , Mandatory = $true)] [parameter(ParameterSetName='PSSession_Physical_UseBestPgroupMatch' , Mandatory = $true)] [parameter(ParameterSetName='PSSession_Physical_CreatePgroup' , Mandatory = $true)] [parameter(ParameterSetName='PSSession_Physical_NoPgroup' , Mandatory = $true)] [System.Management.Automation.Runspaces.PSSession] $ComputerSession, [parameter(Mandatory = $false)] [string] $Path, [parameter(ParameterSetName='Credential_Physical_UseExistingPgroup', Mandatory = $true)] [parameter(ParameterSetName='Credential_Virtual_UseExistingPgroup', Mandatory = $true)] [parameter(ParameterSetName='PSSession_Physical_UseExistingPgroup', Mandatory = $true)] [parameter(ParameterSetName='PSSession_Virtual_UseExistingPgroup', Mandatory = $true)] [string] $PgroupName, [parameter(ParameterSetName='Credential_Physical_UseBestPgroupMatch', Mandatory = $true)] [parameter(ParameterSetName='Credential_Virtual_UseBestPgroupMatch', Mandatory = $true)] [parameter(ParameterSetName='PSSession_Physical_UseBestPgroupMatch', Mandatory = $true)] [parameter(ParameterSetName='PSSession_Virtual_UseBestPgroupMatch', Mandatory = $true)] [switch] $UseBestPgroupMatch, [parameter(ParameterSetName='Credential_Physical_CreatePgroup', Mandatory = $true)] [parameter(ParameterSetName='Credential_Virtual_CreatePgroup', Mandatory = $true)] [parameter(ParameterSetName='PSSession_Physical_CreatePgroup', Mandatory = $true)] [parameter(ParameterSetName='PSSession_Virtual_CreatePgroup', Mandatory = $true)] [switch] $CreatePgroup, [parameter(ParameterSetName='Credential_Physical_CreatePgroup', Mandatory = $true)] [parameter(ParameterSetName='Credential_Virtual_CreatePgroup', Mandatory = $true)] [parameter(ParameterSetName='PSSession_Physical_CreatePgroup', Mandatory = $true)] [parameter(ParameterSetName='PSSession_Virtual_CreatePgroup', Mandatory = $true)] [string] $NewPgroupSuffix, [parameter(ParameterSetName='Credential_Physical_NoPgroup', Mandatory = $true)] [parameter(ParameterSetName='Credential_Virtual_NoPgroup', Mandatory = $true)] [parameter(ParameterSetName='PSSession_Physical_NoPgroup', Mandatory = $true)] [parameter(ParameterSetName='PSSession_Virtual_NoPgroup', Mandatory = $true)] [switch] $NoPgroup, [parameter(ParameterSetName='Credential_Physical_UseBestPgroupMatch', Mandatory = $false)] [parameter(ParameterSetName='Credential_Virtual_UseBestPgroupMatch', Mandatory = $false)] [parameter(ParameterSetName='PSSession_Physical_UseBestPgroupMatch', Mandatory = $false)] [parameter(ParameterSetName='PSSession_Virtual_UseBestPgroupMatch', Mandatory = $false)] [parameter(ParameterSetName='Credential_Physical_UseExistingPgroup', Mandatory = $false)] [parameter(ParameterSetName='Credential_Virtual_UseExistingPgroup', Mandatory = $false)] [parameter(ParameterSetName='PSSession_Physical_UseExistingPgroup', Mandatory = $false)] [parameter(ParameterSetName='PSSession_Virtual_UseExistingPgroup', Mandatory = $false)] [switch] $ReplicateNow, [parameter(Mandatory = $true)] [ValidateSet("Physical", "RDM", "VVOL")] [string] $VolumeType, [parameter(ParameterSetName='Credential_Virtual_UseExistingPgroup' , Mandatory = $true)] [parameter(ParameterSetName='Credential_Virtual_UseBestPgroupMatch' , Mandatory = $true)] [parameter(ParameterSetName='Credential_Virtual_CreatePgroup' , Mandatory = $true)] [parameter(ParameterSetName='Credential_Virtual_NoPgroup' , Mandatory = $true)] [parameter(ParameterSetName='PSSession_Virtual_UseExistingPgroup' , Mandatory = $true)] [parameter(ParameterSetName='PSSession_Virtual_UseBestPgroupMatch' , Mandatory = $true)] [parameter(ParameterSetName='PSSession_Virtual_CreatePgroup' , Mandatory = $true)] [parameter(ParameterSetName='PSSession_Virtual_NoPgroup' , Mandatory = $true)] [string] $VCenterAddress, [parameter(ParameterSetName='Credential_Virtual_UseExistingPgroup' , Mandatory = $true)] [parameter(ParameterSetName='Credential_Virtual_UseBestPgroupMatch' , Mandatory = $true)] [parameter(ParameterSetName='Credential_Virtual_CreatePgroup' , Mandatory = $true)] [parameter(ParameterSetName='Credential_Virtual_NoPgroup' , Mandatory = $true)] [parameter(ParameterSetName='PSSession_Virtual_UseExistingPgroup' , Mandatory = $true)] [parameter(ParameterSetName='PSSession_Virtual_UseBestPgroupMatch' , Mandatory = $true)] [parameter(ParameterSetName='PSSession_Virtual_CreatePgroup' , Mandatory = $true)] [parameter(ParameterSetName='PSSession_Virtual_NoPgroup' , Mandatory = $true)] [PSCredential] $VCenterCredential, [parameter(ParameterSetName='Credential_Virtual_UseExistingPgroup' , Mandatory = $false)] [parameter(ParameterSetName='Credential_Virtual_UseBestPgroupMatch' , Mandatory = $false)] [parameter(ParameterSetName='Credential_Virtual_CreatePgroup' , Mandatory = $false)] [parameter(ParameterSetName='Credential_Virtual_NoPgroup' , Mandatory = $false)] [parameter(ParameterSetName='PSSession_Virtual_UseExistingPgroup' , Mandatory = $true)] [parameter(ParameterSetName='PSSession_Virtual_UseBestPgroupMatch' , Mandatory = $true)] [parameter(ParameterSetName='PSSession_Virtual_CreatePgroup' , Mandatory = $true)] [parameter(ParameterSetName='PSSession_Virtual_NoPgroup' , Mandatory = $true)] [string] $VMName, [parameter(ParameterSetName='Credential_Virtual_UseExistingPgroup' , Mandatory = $false)] [parameter(ParameterSetName='Credential_Virtual_UseBestPgroupMatch' , Mandatory = $false)] [parameter(ParameterSetName='Credential_Virtual_CreatePgroup' , Mandatory = $false)] [parameter(ParameterSetName='Credential_Virtual_NoPgroup' , Mandatory = $false)] [parameter(ParameterSetName='PSSession_Virtual_UseExistingPgroup' , Mandatory = $true)] [parameter(ParameterSetName='PSSession_Virtual_UseBestPgroupMatch' , Mandatory = $true)] [parameter(ParameterSetName='PSSession_Virtual_CreatePgroup' , Mandatory = $true)] [parameter(ParameterSetName='PSSession_Virtual_NoPgroup' , Mandatory = $true)] [string] $VMPersistentId, [parameter(Mandatory = $false)] [switch] $SkipValidation, [parameter(Mandatory = $false)] [switch] $Force ) return Invoke-Pfa2CmdletWrapper -FunctionName $MyInvocation.MyCommand -Parameters $MyInvocation.BoundParameters } <# .Synopsis Retrieves history of backup jobs. .Description Returns list of snapshots for Volume Set specified by VolumeSetName. .Parameter FlashArrayAddress The FlashArray address .Parameter FlashArrayCredential FlashArray credentials .Parameter VolumeSetName Volume Set Name that identifies an existing Volume Set on the Flash Array. .Parameter All When specified, snapshots for all volume sets will be returned. .Parameter IncludeNonSDKSnapshots Switch. When used will include snapshots created outside the SDK. .Parameter UseLocalTime Switch. When used, creation time will be returned in local time format. .Parameter Limit Limit of history items returned. Default is 10. .Example Get-PsbSnapshotJobHistory -FlashArrayAddress $FlashArrayEndpoint -FlashArrayCredential $FlashArrayCredential -all Retrieves a list of all snapshots from all Volume Sets. .Example Get-PsbSnapshotJobHistory -flasharrayaddress $FlashArrayEndpoint -flasharraycredential $FlashArrayCredential -VolumeSetName sqlvm4-g Retrieves a list of all snapshots from the specified Volume Set. .Example Get-PsbSnapshotJobHistory -flasharrayaddress $FlashArrayEndpoint -flasharraycredential $FlashArrayCredential -VolumeSetName sqlvm4-g -IncludeNonSDKSnapshots Retrieves a list of all snapshots from the specified Volume Set including snapshots that were not taken using Invoke-PsbSnapshotJob. #> function Get-PsbSnapshotJobHistory { Param ( [parameter(Mandatory = $true)] [string] $FlashArrayAddress, [parameter(Mandatory = $true)] [PSCredential] $FlashArrayCredential, [parameter(ParameterSetName='ByVolumeSet', Mandatory = $true)] [string] $VolumeSetName, [parameter(ParameterSetName="All", Mandatory=$true)] [switch] $All, [parameter(Mandatory=$false)] [switch] $IncludeNonSDKSnapshots, [parameter(Mandatory=$false)] [switch] $UseLocalTime, [parameter(Mandatory = $false)] [int] $Limit=10 ) return Invoke-Pfa2CmdletWrapper -FunctionName $MyInvocation.MyCommand -Parameters $MyInvocation.BoundParameters } <# .Synopsis Removes a snapshot from the backup history. .Description Removes a specified snapshot, or all snapshots from the backup history. Snapshots cannot be removed if they are mounted. .Parameter FlashArrayAddress The FlashArray address .Parameter FlashArrayCredential FlashArray credentials .Parameter HistoryId HistoryId of a previously invoked backup job. .Example $snaps = Get-PsbSnapshotJobHistory -FlashArrayAddress $FlashArrayEndpoint -FlashArrayCredential $FlashArrayCredential -VolumeSetName qsqlvm4g Remove-PsbSnapshotSet -FlashArrayAddress $FlashArrayEndpoint -FlashArrayCredential $FlashArrayCredential -HistoryId $snaps[0].historyid Enumerate snapshots for a specified Volume Set and then remove the newest snapshot from the FlashArray. #> function Remove-PsbSnapshotSet { [cmdletbinding(SupportsShouldProcess=$true, ConfirmImpact='High')] Param ( [parameter(Mandatory = $true)] [string] $FlashArrayAddress, [parameter(Mandatory = $true)] [PSCredential] $FlashArrayCredential, [parameter(Mandatory = $true)] [string] $HistoryId, [parameter(Mandatory = $false)] [switch] $Force ) return Invoke-Pfa2CmdletWrapper -FunctionName $MyInvocation.MyCommand -Parameters $MyInvocation.BoundParameters } <# .Synopsis Mounts a snapshot from the Backup History to the specified machine. The disk type will be preserved in the mount. For example a Volume Set using pRDM will mount a copy of its snapshots as a pRDM. A Volume Set using a vVol will mount a copy of its snapshots as vVol. A Volume Set using physical cannot be mounted to a VM, unless the VM is using in-guest iSCSI where it will be treated as physical. .Description Creates a volume copy of a snapshot and exposes it as a disk on the specified machine. .Parameter HistoryId HistoryId of a previously invoked backup job. .Parameter FlashArrayAddress Flash Array Name where the snapshots is located. .Parameter FlashArrayCredential FlashArray credentials .Parameter Path Drive Letters or Mount POints to be used to expose volume copies. .Parameter ComputerAddress Credential Name for computer to be used on the mount. If not specified, it will use the same as the one specified on history. .Parameter ComputerCredential Mount computer credentials .Parameter VMName VM Name for computer to be used on the mount if volume type is RDM or vVols. If not specified, it will use the same as the one specified on history. .Parameter VMPersistentId VM Persistent ID for computer to be used on the mount if volume type is RDM or vVols. If not specified, it will use the same as the one specified on history. .Parameter VCenterAddress VCenter Credential Name for environment to be used on the mount if volume type is RDM or vVols. If not specified, it will use the same as the one specified on history. .Parameter VCenterCredential vCenter credentials .Parameter DiskTimeout Timeout in seconds to wait for mounted disk partitions to load. Default is 5 seconds. .Parameter DiskLoadRetries Number of retries to load mounted disk partitions. Default is 5 retries. .Example $history = Get-PsbSnapshotJobHistory -FlashArrayAddress $FlashArrayEndpoint -VolumeSetName $myVolSet -Limit 1 Mount-PsbSnapshotSet -HistoryId $history.HistoryId -FlashArrayAddress $FlashArrayEndpoint -Path 'e:,f:' First enumerate the backup history for $myVolSet retaining the most recent snapshot. Then mount the returned history to the original computer on drive letters E: and F: .Example $history = Get-PsbSnapshotJobHistory -FlashArrayAddress $FlashArrayEndpoint -FlashArrayCredential $fa30 -volumesetname e0801 Mount-PsbSnapshotSet -HistoryId $history[0].HistoryId -FlashArrayAddress $FlashArrayEndpoint -flasharraycredential $fa30 -ComputerAddress sqlvm4 -computercredential $sqlvm4 -Path 'c:\mp61' First enumerate the backup history for volume set ‘e0801’ retaining the most recent snapshot. Then mount the returned history to a new server using mount point c:\mp61. #> function Mount-PsbSnapshotSet { [CmdletBinding()] Param ( [parameter(Mandatory = $true)] [string] $HistoryId, [parameter(Mandatory = $true)] [string] $FlashArrayAddress, [parameter(Mandatory = $true)] [PSCredential] $FlashArrayCredential, [Parameter(Mandatory = $true)] [string] $Path, [Parameter(Mandatory = $false)] [string] $ComputerAddress, [parameter(ParameterSetName= 'Credential_Physical', Mandatory = $true)] [parameter(ParameterSetName= 'Credential_Virtual', Mandatory = $true)] [PSCredential] $ComputerCredential, [parameter(ParameterSetName= 'PSSession_Physical', Mandatory = $true)] [parameter(ParameterSetName= 'PSSession_Virtual', Mandatory = $true)] [System.Management.Automation.Runspaces.PSSession] $ComputerSession, [parameter(ParameterSetName= 'PSSession_Virtual', Mandatory = $false)] [parameter(ParameterSetName= 'Credential_Virtual', Mandatory = $false)] [string] $VMName, [parameter(ParameterSetName= 'PSSession_Virtual', Mandatory = $false)] [parameter(ParameterSetName= 'Credential_Virtual', Mandatory = $false)] [string] $VMPersistentId, [parameter(ParameterSetName= 'PSSession_Virtual', Mandatory = $true)] [parameter(ParameterSetName= 'Credential_Virtual', Mandatory = $true)] [string] $VCenterAddress, [parameter(ParameterSetName= 'PSSession_Virtual', Mandatory = $true)] [parameter(ParameterSetName= 'Credential_Virtual', Mandatory = $true)] [PSCredential] $VCenterCredential, [parameter(ParameterSetName= 'PSSession_Virtual', Mandatory = $false)] [parameter(ParameterSetName= 'Credential_Virtual', Mandatory = $false)] [string] $DatastoreName, [parameter(Mandatory = $false)] [int] $DiskTimeout = 5, [parameter(Mandatory = $false)] [int] $DiskLoadRetries = 5 ) return Invoke-Pfa2CmdletWrapper -FunctionName $MyInvocation.MyCommand -Parameters $MyInvocation.BoundParameters } <# .Synopsis Dismount a Volume Set snapshot from a host, remove the entry from the Mount History, and delete it from the FlashArray. .Description Unexposes disk from the mount machine, and then removes the volume copy. .Parameter MountId MountId of the Mount history to be dismounted. .Parameter FlashArrayAddress The FlashArray address .Parameter FlashArrayCredential FlashArray credentials .Parameter Paths Drive Letters or Mount POints to be used to expose volume copies. .Parameter ComputerAddress Credential Name for computer to be used on the mount. If not specified, it will use the same as the one specified on history. .Parameter ComputerCredential Mount computer credentials .Parameter VMName VM Name for computer to be used on the mount if volume type is RDM or vVols. If not specified, it will use the same as the one specified on history. .Parameter VCenterAddress VCenter Address for environment to be used on the mount if volume type is RDM or vVols. .Parameter VCenterCredential vCenter credentials .Parameter VCenterCredential vCenter credentials .Example $mountHistory = Get-PsbSnapshotSetMountHistory -FlashArrayAddress $FlashArrayEndpoint -FlashArrayCredential (Get-Credential) Dismount-PsbSnapshotSet -FlashArrayAddress $FlashArrayEndpoint -MountId $mountHistory[0].MountId -ComputerAddress $sqlvm4 -ComputerCredential $sqlvm4 First enumerate the Mount History. Then dismount the first mount history entry. #> function Dismount-PsbSnapshotSet { [CmdletBinding()] Param ( [Parameter(Mandatory = $true)] [string] $MountId, [parameter(Mandatory = $true)] [string] $FlashArrayAddress, [parameter(Mandatory = $true)] [PSCredential] $FlashArrayCredential, [Parameter(Mandatory = $true)] [string] $ComputerAddress, [parameter(ParameterSetName= 'Credential_Physical', Mandatory = $true)] [parameter(ParameterSetName= 'Credential_Virtual', Mandatory = $true)] [PSCredential] $ComputerCredential, [parameter(ParameterSetName= 'PSSession_Physical', Mandatory = $true)] [parameter(ParameterSetName= 'PSSession_Virtual', Mandatory = $true)] [System.Management.Automation.Runspaces.PSSession] $ComputerSession, [parameter(ParameterSetName= 'PSSession_Virtual', Mandatory = $true)] [parameter(ParameterSetName= 'Credential_Virtual', Mandatory = $true)][string] $VCenterAddress, [parameter(ParameterSetName= 'PSSession_Virtual', Mandatory = $true)] [parameter(ParameterSetName= 'Credential_Virtual', Mandatory = $true)][PSCredential] $VCenterCredential ) return Invoke-Pfa2CmdletWrapper -FunctionName $MyInvocation.MyCommand -Parameters $MyInvocation.BoundParameters } <# .Synopsis Retrieves history of mounted backup jobs. .Description Returns Mount details for all mounted jobs in a Flash Array. .Parameter FlashArrayAddress The FlashArray address where volume copies were created. .Parameter FlashArrayCredential FlashArray credentials .Parameter HistoryId When specified, only mounted jobs for this History Id will be returned. .Example Get-PsbSnapshotJobHistory -FlashArrayAddress FlashArrayEndpoint -FlashArrayCredential (Get-Credential) #> function Get-PsbSnapshotSetMountHistory { [CmdletBinding()] Param ( [parameter(Mandatory = $false)] [string] $HistoryId, [parameter(Mandatory = $true)] [string] $FlashArrayAddress, [parameter(Mandatory = $true)] [PSCredential] $FlashArrayCredential ) return Invoke-Pfa2CmdletWrapper -FunctionName $MyInvocation.MyCommand -Parameters $MyInvocation.BoundParameters } <# .Synopsis Create a volume set from the specified disk paths that can include drive letters and mount points. A recommended prerequisite is to get the VM Persistent ID so automation does not fail in the event the friendly name for the VM is changed in vCenter. The disk type can be physical where a HOST is configured on the FlashArray, a VMware pRDM, or a VMware vVol (Virtual Volume). .Description Receives a list of disk paths, finds the corresponding volumes on the FlashArray, and then tags the corresponding volumes with the declared VolumeSet tag. .Parameter VolumeSetName Volume Set Name to identify set of volumes .Parameter ComputerAddress The name of the host machine. .Parameter ComputerCredential Computer credentials .Parameter FlashArrayAddress The FlashArray address .Parameter FlashArrayCredential FlashArray credentials .Parameter VCenterAddress The name of the controlling VCenter, if the host is a VM .Parameter VMName The name of the VM, if the host is a VM. This may be different from the ComputerAddress. .Parameter VMPersistentId The PersistentId (also referred to as the InstanceUuid or MoRef) of the VM. .Parameter Path Paths that points to the disks to be included on the volumeset. Should be drive letters or a mount point paths. .Parameter SkipValidation Do not validate configuration before saving. .Parameter VolumeType The type of the volumes being added to the set. Valid values are RDM, VVOL or Physical .Example #First query the VMPersistentID for SQLVM2 and assign it to variable $VMPID. $VMPID = Get-PSBVMPersistentId -VCenterAddress $vCenterEndpoint -VCenterCredential $vCenterCredential -VMName sqlvm2 #Then create a volume set named myvolSet for disks E and F on server sqlvm2 using FlashArray fa30. New-PsbVolumeSet -VolumeSetName myvolSet -ComputerAddress sqlvm2 -ComputerCredential $vmCredential -FlashArrayAddress fa30 -FlashArrayCredential $FlashArrayCredential -Path 'E:\,F:\' -VCenterAddress $vCenterEndpoint -vCenterCredential $vCenterCredential -VMName sqlvm2 -VMPersistentID $VMPID -VolumeType vvol #> function New-PsbVolumeSet { [CmdletBinding(SupportsShouldProcess,ConfirmImpact='High')] Param ( [parameter(Mandatory = $true)] [string] $VolumeSetName, [parameter(Mandatory = $true)] [string] $ComputerAddress, [parameter(ParameterSetName= 'Credential_Physical', Mandatory = $true)] [parameter(ParameterSetName= 'Credential_Virtual', Mandatory = $true)] [pscredential] $ComputerCredential, [parameter(ParameterSetName= 'PSSession_Physical', Mandatory = $true)] [parameter(ParameterSetName= 'PSSession_Virtual', Mandatory = $true)] [System.Management.Automation.Runspaces.PSSession] $ComputerSession, [parameter(Mandatory = $true)] [string] $Path, [parameter(Mandatory = $false)] [ValidateSet("Physical", "RDM", "VVOL")] [string] $VolumeType, [parameter(Mandatory = $true)] [string] $FlashArrayAddress, [parameter(Mandatory = $true)] [pscredential] $FlashArrayCredential, [parameter(ParameterSetName= 'Credential_Virtual', Mandatory = $true)] [parameter(ParameterSetName= 'Session_Virtual', Mandatory = $true)] [string] $VCenterAddress, [parameter(ParameterSetName= 'Credential_Virtual', Mandatory = $true)] [parameter(ParameterSetName= 'Session_Virtual', Mandatory = $true)] [pscredential] $VCenterCredential, [parameter(ParameterSetName= 'Credential_Virtual', Mandatory = $false)] [parameter(ParameterSetName= 'Session_Virtual', Mandatory = $false)] [string] $VMName, [parameter(ParameterSetName= 'Credential_Virtual', Mandatory = $false)] [parameter(ParameterSetName= 'Session_Virtual', Mandatory = $false)] [parameter(Mandatory = $false)] [string] $VMPersistentId, [parameter(Mandatory = $false)] [switch] $SkipValidation, [parameter(Mandatory = $false)] [switch] $Force ) return Invoke-Pfa2CmdletWrapper -FunctionName $MyInvocation.MyCommand -Parameters $MyInvocation.BoundParameters } <# .Synopsis Get volume set details. .Description Retrieves one or all volume sets on a FlashArray and returns volume set details .Parameter VolumeSetName Optional. Volume Set Name that identifies an existing Volume Set. If not specified, function will return details of all existing Volume Sets on the Flash Array. .Parameter FlashArrayAddress The FlashArray address .Parameter FlashArrayCredential FlashArray credentials .Example # Returns a list of all existing volume sets on flasharray fa30. Get-PsbVolumeSet -FlashArrayAddress fa30 -FlashArrayCredential (Get-Credential) .Example # Returns details of volume set myvolSet from flasharray fa30. Get-PsbVolumeSet -FlashArrayAddress fa30 -FlashArrayCredential $fa30 -VolumeSetName myvolSet #> function Get-PsbVolumeSet { [CmdletBinding()] Param ( [parameter(Mandatory = $true)] [string] $FlashArrayAddress, [parameter(Mandatory = $true)] [PSCredential] $FlashArrayCredential, [parameter(Mandatory = $false)] [string] $VolumeSetName ) return Invoke-Pfa2CmdletWrapper -FunctionName $MyInvocation.MyCommand -Parameters $MyInvocation.BoundParameters } <# .Synopsis Removes a volume set from FlashArray. .Parameter VolumeSetName Volume Set Name that identifies an existing Volume Set to be removed from Flash Array. .Parameter FlashArrayAddress The FlashArray address .Parameter FlashArrayCredential FlashArray credentials .Example # Remove myvolSet metadata from flasharray fa30. Remove-PsbVolumeSet -FlashArrayAddress fa30 -FlashArrayCredential (Get-Credential) -VolumeSetName myvolSet .Example # Remove myvolSet metadata from flasharray fa30. Remove-PsbVolumeSet -FlashArrayAddress fa30 -FlashArrayCredential $FlashArrayCredential -VolumeSetName myvolSet # If desired, remove the Protection Group leveraging the dependent PureStoragePowerShellSDK2 # First connect to the FlashArray $pfa30 = connect-pfa2array -endpoint fa30 -credential $FlashArrayCredential -IgnoreCertificateError # Get properties on the protection group (optional) Get-Pfa2ProtectionGroup -Array $pfa30 -Name psb-qsqlvm4 # Destroy the protection group. Volumes do not need to be removed before destroying. Update-Pfa2ProtectionGroup -Array $pfa30 -Destroyed $true -Name psb-qsqlvm4 # If you destroy the protection group by accident and want to recover, rerun the command with ‘-Destroyed’ set to false false. (optional) Update-Pfa2ProtectionGroup -Array $pfa30 -Destroyed $false -Name psb-qsqlvm4 #> function Remove-PsbVolumeSet { [CmdletBinding()] Param ( [parameter(Mandatory = $true)] [string] $FlashArrayAddress, [parameter(Mandatory = $true)] [PSCredential] $FlashArrayCredential, [parameter(Mandatory = $true)] [string] $VolumeSetName ) return Invoke-Pfa2CmdletWrapper -FunctionName $MyInvocation.MyCommand -Parameters $MyInvocation.BoundParameters } <# .Synopsis Retrieves a list of matching Protection Groups for a volume set .Description Search FlashArray for all of the Protection Groups that include all of the volumes that are a part of the specified volume set. It can be used to help determine which pgroup should be used when invoking a backup. If no Protection Groups exist, volume snapshots can be taken with the caveat that if there is more than 1 volume it is not guaranteed to be consistent without using a Protection Group snapshot. A Protection Group can be created using the -createpgroup parameter of Invoke-PsbSnapshotJob. If a single Protection Group exists you can declare it in the Invoke-PsbSnapshotJob cmdlet with the -PgroupName parameter. If more than one Protection Groups exist, you can enumerate their names with this cmdlet and choose the one you want, then declare it with the Invoke-PsbSnapshotJob cmdlet with the -PgroupName parameter. You can also let the Invoke-PsbSnapshotJob choose the Protection Group that has all volumes in the volume set, with the fewest number of other volumes with the -UseBestPgroupMatch parameter, as denoted in the “ExtraVolumesCount” column from this cmdlet. .Parameter VolumeSetName Volume Set Name that identifies an existing Volume Set on the Flash Array. .Parameter FlashArrayAddress The FlashArray address .Parameter FlashArrayCredential FlashArray credentials .Example Get-PsbVolumeSetProtectionGroup -FlashArrayAddress fa30 -FlashArrayCredential (Get-Credential) -VolumeSetName myvolSet Returns a list of matching pgroups for myvolSet on flasharray fa30. #> function Get-PsbVolumeSetProtectionGroup { [CmdletBinding()] Param ( [parameter(Mandatory = $true)] [string] $FlashArrayAddress, [parameter(Mandatory = $true)] [PSCredential] $FlashArrayCredential, [parameter(Mandatory = $true)] [string] $VolumeSetName ) return Invoke-Pfa2CmdletWrapper -FunctionName $MyInvocation.MyCommand -Parameters $MyInvocation.BoundParameters } <# .Synopsis Get the list of drive letters available for mounting a snapshot. .Description Query the specified ComputerAddress to get a list of available drive letters. This information can be used in the Mount-PsbSnapshotSet cmdlet to mount a snapshot set to those drive letters using the Path parameter. If an available drive letter is in use as a mapped network drive, the drive letter will have to be unmapped from the share before the mounted disk is accessible. .Parameter ComputerAddress The name of the VM. .Parameter ComputerCredential The Credential for the VM. .Parameter ComputerSession The PSSession for the VM. .Example $session = new-pssession Get-PSBAvailableDrive -ComputerAddress $vmName -ComputerSession $session #> function Get-PSBAvailableDrive { Param ( [Parameter(Mandatory = $true)] [string] $ComputerAddress, [parameter(ParameterSetName= 'Credential', Mandatory = $true)] [PSCredential] $ComputerCredential, [parameter(ParameterSetName= 'PSSession', Mandatory = $true)] [System.Management.Automation.Runspaces.PSSession] $ComputerSession ) return Invoke-Pfa2CmdletWrapper -FunctionName $MyInvocation.MyCommand -Parameters $MyInvocation.BoundParameters } <# .Synopsis Get VM Persistent ID (or IDs if more than one VM has the same name) from vCenter. The VM friendly name in vCenter, the vCenter hostname, FQDN, or IP Address, and the vCenter credential are required parameters. .Description Get VM Persistent ID (or IDs if more than one VM has the same name) from vCenter. The VM Persistent ID is needed when creating a new volume set (New-PsbVolumeSet) and when invoking a snapshot (Invoke-PsbSnapshotJob) so that operations can succeed even if the VM is renamed in vCenter. .Parameter VCenterAddress The friendly name of the controlling VCenter previously configured with Add-PfaBackupCred .Parameter VCenterCredential The credential for the controlling VCenter previously configured with Add-PfaBackupCred .Parameter VMName VM name in vCenter .Example Get-PSBVMPersistentId -VMName $vmName -VCenterAddress $vCenterEndpoint -VCenterCredential $vCenterCredential #> function Get-PSBVMPersistentId { Param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [String] $VCenterAddress, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [PSCredential] $VCenterCredential, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string] $VMName ) return Invoke-Pfa2CmdletWrapper -FunctionName $MyInvocation.MyCommand -Parameters $MyInvocation.BoundParameters } ################# WRAPPER ################# function Invoke-Pfa2CmdletWrapper { Param ( $FunctionName, $Parameters ) $params = ($Parameters.Keys | ForEach-Object { "-$_ '$($Parameters[$_])'" }) -join " " $FlashArrayAddress = $Parameters.FlashArrayAddress $FlashArrayCredential = $Parameters.FlashArrayCredential # log start and create basic objects Write-Log -Level INFO -FunctionName $FunctionName -Msg ($script:EnteringMsg + $params) Write-Progress -Activity $FunctionName -Status $script:VerCredMsg $workflowId = New-WorkflowID if ($Parameters['thread_id']){ $thread_id = $Parameters['thread_id'] # most operations do not expect a thread_id, we will add the parameter back if needed $Parameters.Remove('thread_id') | Out-Null } else { $thread_id = $FunctionName } # Create REST Client $RestClient = $null #"Get-PSBAvailableDrive", "Get-PSBVMPersistentId" are helper functions that do not talk to the FA if(-not (@("Get-PSBAvailableDrive", "Get-PSBVMPersistentId") -contains $FunctionName)){ try{ if ($null -eq $FlashArrayCredential) { ThrowErrorCode -ErrorCode $ErrorCode_MissingFACred } # throws exception if fails to connect $RestClient = New-PureRestClient -FAEndpoint $FlashArrayAddress -FACredential $FlashArrayCredential } catch{ # catch issues with credentials and connection to the FA New-LogFatalErrorAndThrow -FunctionName $FunctionName -ErrorMsg $_.Exception.Message -Exception $_ } New-PhoneHomeWorkflowLogEntry -RestClient $RestClient -Event $script:BeginWorkflow -ID $workflowId -Name $FunctionName -thread_id $thread_id $Parameters['RestClient'] = $RestClient $Parameters.Remove("FlashArrayAddress") | Out-Null $Parameters.Remove("FlashArrayCredential") | Out-Null } $Parameters['FunctionName'] = $FunctionName $removeSessions = $false $result = $null try { # validate windows credentials if($parameters['ComputerAddress']){ if($Parameters['ComputerSession']){ if($parameters['ComputerAddress'] -ne $Parameters['ComputerSession'].ComputerName){ ThrowErrorCode -ErrorCode $ErrorCode_InvalidComputerSession } if ($Parameters['ComputerCredential']){ $Parameters.Remove("ComputerCredential") | Out-Null } } elseif ($Parameters['ComputerCredential']){ $ComputerSession = New-RemoteSession -ComputerAddress $parameters['ComputerAddress'] -Credential $parameters['ComputerCredential'] -ErrorAction SilentlyContinue -ErrorVariable sdkError if(-not $ComputerSession){ ThrowErrorCode -ErrorCode $ErrorCode_InvalidComputerCred -innerException $sdkError[0].Exception } $Parameters.Remove("ComputerCredential") | Out-Null $Parameters['ComputerSession'] = $ComputerSession $removeSessions = $true } else{ # TODO: review error ThrowErrorCode -ErrorCode $ErrorCode_InvalidComputerCred } } # validate vcenter credentials if($parameters['VCenterAddress']){ # check if powerCLI is installed if(-not (Get-Module -ListAvailable VMware.PowerCLI)){ ThrowErrorCode -ErrorCode $ErrorCode_MissingPowerCLI } if(-not $parameters['VCenterCredential']){ ThrowErrorCode -ErrorCode $ErrorCode_MissingVcenterCreds } try{ Connect-VCenter -VCenterAddress $parameters['VCenterAddress'] -Credential $parameters['VCenterCredential'] | Out-Null } catch{ # will add powercli error to the exception ThrowErrorCode -ErrorCode $ErrorCode_InvalidVcenterCred -innerException $_.Exception } } # run actual operation Switch ($FunctionName) { 'Invoke-PsbSnapshotJob' { $Parameters['thread_id'] = $thread_id $Parameters['workflowId'] = $workflowId $result = Invoke-BackupOp @Parameters } 'New-PsbVolumeSet' { $result = Invoke-CreateVolumeSetOp @Parameters } 'Mount-PsbSnapshotSet' { $result = Invoke-MountOp @Parameters } 'Get-PsbSnapshotJobHistory' { $result = Invoke-GetHistoryOp @Parameters } 'Remove-PsbSnapshotSet' { $result = Invoke-RemoveOp @Parameters } 'Dismount-PsbSnapshotSet' { $result = Invoke-DismountOp @Parameters } 'Get-PsbSnapshotSetMountHistory' { $result = Invoke-GetMountHistoryOp @Parameters } 'Get-PsbVolumeSet' { $result = Invoke-GetVolumeSetOp @Parameters } 'Remove-PsbVolumeSet' { $result = Invoke-RemoveVolumeSetOp @Parameters } 'Get-PsbVolumeSetProtectionGroup' { $result = Invoke-GetVolumeSetPgroupsOp @Parameters } 'Get-PSBAvailableDrive' { $result = Invoke-GetAvailableDriveOp @Parameters } 'Get-PSBVMPersistentId' { $result = Invoke-GetVMPersistentIdOp @Parameters } } } catch { New-LogFatalErrorAndThrow -RestClient $RestClient -ID $workflowId -FunctionName $FunctionName -ErrorMsg $_.Exception.Message -Exception $_ -thread_id $thread_id } finally{ if($removeSessions){ if(Test-Path variable:ComputerSession){ # Cleanup remote sessions Remove-PSSession -Session $ComputerSession -ErrorAction SilentlyContinue | Out-Null } } } # log completion Write-Progress -Activity $FunctionName -Status $script:OpFinishedMsg -Completed Write-Log -Level INFO -FunctionName $FunctionName -Msg $script:OpFinishedMsg if($RestClient){ New-PhoneHomeWorkflowLogEntry -RestClient $RestClient -Event $script:CompleteWorkflow -ID $workflowId -Name $FunctionName -thread_id $thread_id } return $result } ################# CMDLETS IMPLEMENTATION ################# function Invoke-BackupOp { [CmdletBinding(SupportsShouldProcess,ConfirmImpact='High')] Param ( [parameter(Mandatory = $true)] [string] $VolumeSetName, [parameter(Mandatory = $false)] [string] $ComputerAddress, [parameter(Mandatory = $false)] [System.Management.Automation.Runspaces.PSSession] $ComputerSession, [parameter(Mandatory = $false)] [string] $Path, [parameter(Mandatory = $false)] [ValidateSet("Physical", "RDM", "VVOL")] [string] $VolumeType, [parameter(Mandatory = $false)] [string] $VCenterAddress, [parameter(Mandatory = $false)] [PSCredential] $VCenterCredential, [parameter(Mandatory = $false)] [string] $VMName, [parameter(Mandatory = $false)] [string] $VMPersistentId, [parameter(Mandatory = $false)] [switch] $SkipValidation, [parameter(ParameterSetName='ExistingPG', Mandatory = $true)] [string] $PgroupName, [parameter(ParameterSetName='BestMatch', Mandatory = $true)] [switch] $UseBestPgroupMatch, [parameter(ParameterSetName='CreatePG', Mandatory = $true)] [switch] $CreatePgroup, [parameter(ParameterSetName='CreatePG', Mandatory = $true)] [string] $NewPgroupSuffix, [parameter(ParameterSetName='NoPgroup', Mandatory = $true)] [switch] $NoPgroup, [parameter(ParameterSetName='ExistingPG', Mandatory = $false)] [parameter(ParameterSetName='BestMatch', Mandatory = $false)] [switch] $ReplicateNow, [parameter(Mandatory = $true)] [string] $thread_id, [parameter(Mandatory = $true)] [string] $workflowId, [parameter(Mandatory = $true)] [string] $FunctionName, [parameter(Mandatory = $true)] $RestClient, [parameter(Mandatory = $false)] [switch] $Force ) if ($Force){ $ConfirmPreference = 'None' } Write-Progress -Activity $FunctionName -Status $validateVolSetMsg $volset = Invoke-CreateVolumeSetOp -FunctionName "Invoke-CreateVolumeSetOp" ` -VolumeSetName $VolumeSetName ` -ComputerAddress $ComputerAddress ` -ComputerSession $ComputerSession ` -Path $Path ` -VolumeType $VolumeType ` -RestClient $RestClient ` -VCenterAddress $VCenterAddress ` -VCenterCredential $VCenterCredential ` -VMName $VMName ` -VMPersistentId $VMPersistentId $affectedVolumes = $volset.Resources Write-Progress -Activity $FunctionName -Status $validatePgroupMsg $results = @() $useReplicateNow = $false $newHistoryId = (Get-Date -UFormat %s).replace(".", "") if ($NoPgroup) { ### uses volume snapshots instead of PG if($affectedVolumes.Count -gt 1){ # Asks the user for confirmation to use non pgroup snapshots for multiple volumes (can result in inconsistent snapshots) if ($PSCmdlet.ShouldProcess($noPgroupDescMsg, $noPgroupWarnMsg, $noPgroupTitleMsg)){ New-PhoneHomeWorkflowLogEntry -RestClient $RestClient -Event $script:ContextWorkflow -ID $workflowId -Name $FunctionName -Context $noPgroupDescMsg -thread_id $thread_id } else { # non-pgroup backup aborted by user New-PhoneHomeWorkflowLogEntry -RestClient $RestClient -Event $script:AbortWorkflow -ID $workflowId -Name $FunctionName -thread_id $thread_id Write-Log -Level INFO -FunctionName $FunctionName -Msg $userAbortedMsg return $false } } # no confirmation needed for pgroup snaps $ConfirmPreference = "None" $FinalPgroupName = $null # taking volumes snapshots Write-Progress -Activity $FunctionName -Status $takingSnapshotMsg $volSnaps = New-Pfa2VolumeSnapshot -Array $RestClient -SourceIds $affectedVolumes.Id -Suffix "$($SDKPrefix)-$($newHistoryId)" -ErrorAction Stop # tag snapshots as non-pgroup snapshots Set-Pfa2VolumeSnapshotTagsBatch -Array $RestClient -ResourceIds $volSnaps.Id -TagCopyable $false -TagNamespace $NamespaceMeta -TagKey "$([PureTagKeys]::SnapType)" -TagValue 'NoPgroup' | Out-Null $results = $volSnaps } else { ### uses pgroup snapshots # no confirmation needed for pgroup snaps $ConfirmPreference = "None" # process pgroup option $FinalPgroupName = ProcessPgroupOption -RestClient $RestClient -affectedVolumes $affectedVolumes -PgroupName $PgroupName -CreatePgroup:$CreatePgroup -UseBestPgroupMatch:$UseBestPgroupMatch -NewPgroupSuffix $NewPgroupSuffix $pgroupObj = Get-Pfa2ProtectionGroup -Array $RestClient -Name $FinalPgroupName # will use replicate_now param if the user enable replicate now switch and the pgroup used has a target set for replication # using replicate now with retention police can cause snapshots to disappear from the source array $useReplicateNow = $replicateNow -and ($pgroupObj.TargetCount -gt 0) # take a pgroup snapshot Write-Progress -Activity $FunctionName -Status $takingSnapshotMsg $results = New-Pfa2ProtectionGroupSnapshot -Array $RestClient -SourceNames $pgroupObj.Name -ApplyRetention $true -Suffix "$($SDKPrefix)-$($newHistoryId)" -ErrorAction Stop # get volume snapshots from pgroup snapshot $volSnaps = Get-Pfa2VolumeSnapshot -Array $RestClient -Filter "name='$($results.Name).*'" # tag volume snapshots as part of a pgroup snapshot Set-Pfa2VolumeSnapshotTagsBatch -Array $RestClient -ResourceIds $volSnaps.Id -TagCopyable $false -TagNamespace $NamespaceMeta -TagKey "$([PureTagKeys]::SnapType)" -TagValue 'Pgroup' | Out-Null # filter volume snapshots to only invlude volumes that were part of the job $volSnaps = $volSnaps | where-object {$_.Source.Id -in $affectedVolumes.Id} } # tag history id to volume snapshots Set-Pfa2VolumeSnapshotTagsBatch -Array $RestClient -ResourceIds $volSnaps.Id -TagCopyable $false -TagNamespace $NamespaceMeta -TagKey "$([PureTagKeys]::HistoryId)" -TagValue "$($VolumeSetName)#$($newHistoryId)" | Out-Null #replicate pgroup snapshot if($useReplicateNow){ Write-Progress -Activity $FunctionName -Status $replicatingSnapshotMsg $rep_res = New-Pfa2RemoteProtectionGroupSnapshot -Name $results.Name -Array $RestClient -ErrorAction SilentlyContinue if(-not $rep_res){ $msg = GetErrorMessage -ErrorCode $ErrorCode_FailToReplicate New-PhoneHomeWorkflowLogEntry -RestClient $RestClient -Event $script:ErrorWorkflow -ID $workflowId -Name $FunctionName -ErrorMsg $msg -thread_id $thread_id Write-Error $msg } } $msg = $results | Format-List | Out-String Write-Log -Level INFO -FunctionName $FunctionName -Msg $Msg return New-HistoryObject -HistoryId "$($VolumeSetName)#$($newHistoryId)" -VolumeSet $volset -Snapshots $volSnaps.Name -CreationDate $volSnaps[0].Created -PgroupName $FinalPgroupName -SDKSnap $true } function Invoke-GetHistoryOp { Param ( [parameter(ParameterSetName='VolSet', Mandatory = $true)] [string] $VolumeSetName, [parameter(ParameterSetName="All", Mandatory=$true)] [switch] $All, [parameter(Mandatory=$false)] [switch] $IncludeNonSDKSnapshots, [parameter(Mandatory=$false)] [switch] $UseLocalTime, [parameter(Mandatory = $false)] [int] $Limit=10, [parameter(Mandatory = $true)] [string] $FunctionName, [parameter(Mandatory = $true)] $RestClient ) Write-Progress -Activity $FunctionName -Status $LoadTagsMsg $history = @() # gets pgroup snapshots $history += RetrievePgroupSnapshots -RestClient $RestClient -VolumeSetName $VolumeSetName -IncludeNonSDKSnapshots:$IncludeNonSDKSnapshots -UseLocalTime:$UseLocalTime -Limit $Limit # get non-pgroup snapshots $history += RetrieveNonPgroupSnapshots -RestClient $RestClient -VolumeSetName $VolumeSetName -UseLocalTime:$UseLocalTime -Limit $Limit return $history | Sort-Object -descending {$_.CreationDate} | Select-Object -First $limit } function Invoke-RemoveOp { [cmdletbinding(SupportsShouldProcess=$true, ConfirmImpact='High')] Param ( [parameter(Mandatory = $true)] [string] $HistoryId, [parameter(Mandatory = $true)] [string] $FunctionName, [parameter(Mandatory = $true)] $RestClient, [parameter(Mandatory = $false)] [switch] $Force ) if ($Force){ $ConfirmPreference = 'None' } $HistoryItem = Get-HistoryObject -RestClient $RestClient -HistoryId $HistoryId if (-not $HistoryItem.SDKSnap) { ThrowErrorCode -ErrorCode $ErrorCode_CantDelNonSDKSnaps } if ($HistoryItem.ProtectionGroup) { $snapName = $HistoryItem.Snapshots | select-object -first 1 $snapNameParts = $snapName.Split(".") if($snapNameParts.Count -ne 3){ ThrowErrorCode -ErrorCode $ErrorCode_CantParseSnapshot } $pgroupSnapName = "$($snapNameParts[0]).$($snapNameParts[1])" Remove-Pfa2ProtectionGroupSnapshot -Array $RestClient -Name $pgroupSnapName -ErrorAction SilentlyContinue | Out-Null } else { foreach ($snap in $HistoryItem.Snapshots){ Remove-Pfa2VolumeSnapshot -Array $RestClient -Name $snap -ErrorAction SilentlyContinue | Out-Null } } return $true } function Invoke-MountOp { [CmdletBinding()] Param ( [parameter(Mandatory = $true)] [string] $HistoryId, [Parameter(Mandatory = $true)] [string] $Path, [Parameter(Mandatory = $false)] [string] $ComputerAddress, [parameter(Mandatory = $false)] [System.Management.Automation.Runspaces.PSSession] $ComputerSession, [Parameter(Mandatory = $false)] [string] $VMName, [Parameter(Mandatory = $false)] [string] $VMPersistentId, [Parameter(Mandatory = $false)] [string] $VCenterAddress, [Parameter(Mandatory = $false)] [PSCredential] $VCenterCredential, [Parameter(Mandatory = $false)] [string] $DatastoreName, [parameter(Mandatory = $true)] [string] $FunctionName, [parameter(Mandatory = $true)] $RestClient, [parameter(Mandatory = $false)] [int] $DiskTimeout = 5, [parameter(Mandatory = $false)] [int] $DiskLoadRetries = 5 ) $FlashArrayAddress = $RestClient.ArrayName # get history object from history Id $HistoryItem = Get-HistoryObject -RestClient $RestClient -HistoryId $HistoryId if(-not $HistoryItem){ ThrowErrorCode -ErrorCode $ErrorCode_HistoryNotFound -params @($HistoryId) } # get virtualization info if rdm or vvol $VCenterAddress, $ComputerAddress, $MountVM, $hostports = Get-VirtualizationInfo -HistoryItem $HistoryItem -VCenterAddress $VCenterAddress -VCenterCredential $VCenterCredential -Computer $ComputerAddress -VMName $VMName -VMPersistentId $VMPersistentId # get drive letters for mount # TODO: need to update to work with mount point - TMAN-18554 $Paths = $Path -split ',' # Validate destination, which could be a new drive letter or an existing mount point $Paths = ValidatePath -Paths $Paths -ComputerAddress $ComputerAddress -ComputerSession $ComputerSession -DiskCount $HistoryItem.Snapshots.Count $pathsToStore = @() foreach ($p in @($Paths)) { if ($p.Length -eq 1){ $pathsToStore += "$($p):" } else{ $pathsToStore += $p } } # will throw exception early on if tag too long or if it has invalid chars CheckMountedVolumeTags -ComputerAddress $ComputerAddress -Paths $pathsToStore -VCenterAddress $VCenterAddress -VMPersistentId $VMPersistentId -VolumeType $HistoryItem.VolumeType # if it's not a virtualized environment, get hostports directly from computer if (-not $VCenterAddress) { $FAIqns = @(Get-Pfa2Port -Array $RestClient | Where-Object {$_.iqn}).Iqn Write-Log -Level INFO -FunctionName $FunctionName -Msg "Flash Array IQNs: $($FAIqns -join ', ')" $hostports = Get-InitiatorPorts -session $ComputerSession -iqns $FAIqns } # if not vvols, get matching hosts/hostgroups for the hostports found if ("VVOL" -ine $HistoryItem.VolumeType) { # find matching hosts/hostgroups to connect mounted volumes to $matchingHosts = Get-MatchingHosts -hostports $hostports -RestClient $RestClient if (-not $matchingHosts) { # We assume the host already exists ThrowErrorCode -ErrorCode $ErrorCode_HostNotFound -params @($hostports, $FlashArrayAddress) } $matchingHGs = Get-MatchingHostGroups -matchingHosts $matchingHosts } # get snapshot objects for the history item $snapshots = $HistoryItem.Snapshots | Get-Pfa2VolumeSnapshot -Array $RestClient # create volumes from snapshots included on history entry $newVolumesSerials = @() $diskIds = @() $newMountId = (Get-Date -UFormat %s).replace(".", "") try{ foreach ($snapshot in $snapshots) { if("VVOL" -ieq $HistoryItem.VolumeType) { # create vvol on vm $disk,$newVolume = $MountVM | Copy-PfaSnapshotToNewVvolVmdk -SnapshotName $snapshot.Name -RestClient $RestClient -DatastoreName $DatastoreName # validate vvol if (-not ($disk.PSObject.Properties.Name -contains "ExtensionData")) { ThrowErrorCode -ErrorCode $ErrorCode_ExtentionDataMissing -params @($disk.gettype()) } # get volume serial from vvol uuid $vmHardDiskUuid = $disk.ExtensionData.Backing.uuid | foreach {$_.replace(' ','').replace('-','')} #get volume serial $diskIds += $vmHardDiskUuid $newVolumesSerials += $newVolume.Serial } else { # create vol copy from snap $newVolName = NewVolumeName -sourceId $snapshot.Source.Id -uniqueId $newMountId $EV = $null $newVolume = New-Pfa2Volume -Array $RestClient -Name $newVolName -SourceName $snapshot.Name -ErrorAction SilentlyContinue -ErrorVariable EV if ($EV) { ThrowErrorCode -ErrorCode $ErrorCode_VolumeCreateFailed -innerException $EV.Exception } # connect volume to host on FA Connect-VolToHost -volumeName $newVolume.Name -matchingHosts $matchingHosts -matchingHGs $matchingHGs -RestClient $RestClient # get volume serial $diskIds += $newVolume.Serial $newVolumesSerials += $newVolume.Serial } } if("RDM" -ieq $HistoryItem.VolumeType) { # Connect RDM volumes to vm on vcenter Connect-PfaVMHardDisk -VolumeSerials $newVolumesSerials -VMName $MountVM.Name -VCenterAddress $VCenterAddress -VCenterCredential $VCenterCredential ` -RestClient $RestClient -VMPersistentId $MountVM.PersistentId -DatastoreName $DatastoreName | Out-Null } #generate mount id $MountId = "$($newMountId)#$($HistoryID)" # tag newly created volumes with mount history $volumes = New-MountedVolumesTags -MountId $MountId -ComputerAddress $ComputerAddress -Paths $pathsToStore -VCenterAddress $VCenterAddress -VMPersistentId $MountVM.PersistentId -MountVolumesSerials $newVolumesSerials -VolumeType $HistoryItem.VolumeType -RestClient $RestClient # expose newly created volumes to the selected drive letters Expose-Disks -session $ComputerSession -Paths $Paths -Serials $diskIds -VolumeType $HistoryItem.VolumeType -DiskTimeout $DiskTimeout -DiskLoadRetries $DiskLoadRetries # create mount object to be returned $mountObject = New-MountObject -MountId $newMountId -HistoryId $HistoryId -Volumetype $HistoryItem.VolumeType -Computer $ComputerAddress -Paths ($pathsToStore -Join ',') -vCenter $VCenterAddress -VMId $MountVM.PersistentId -Resources $volumes } catch{ $Msg = "Mount operation failed. Cleaning up volume copies." Write-Progress -Activity $FunctionName -Status $Msg Write-Log -Level WARN -FunctionName $FunctionName -Msg "$Msg [$($newVolumesSerials -join ",")]" # unexpose disks on $diskIds if($diskIds){ CleanupMountByDiskId -ComputerSession $ComputerSession -RestClient $RestClient -DiskIds $diskIds -newVolumesSerials $newVolumesSerials` -VCenterAddress $VCenterAddress -VCenterCredential $VCenterCredential -VMName $MountVM.Name -FunctionName $FunctionName -ErrorAction Continue } # destroy volumes on $newvolumeSerials if($newVolumesSerials){ CleanupMountByVolSerial -RestClient $RestClient -newVolumesSerials $newVolumesSerials -VolumeType $HistoryItem.VolumeType -VCenterAddress $VCenterAddress` -VCenterCredential $VCenterCredential -MountVM $MountVM -FunctionName $FunctionName -ErrorAction Continue } throw } $Msg = "Snapshot successfully mounted to drive(s) $PathStr. Mount ID: $($MountId)." Write-Progress -Activity $FunctionName -Status $Msg Write-Log -Level INFO -FunctionName $FunctionName -Msg $Msg #TODO: cleanup if anything fails - TMAN-18555 return $mountObject } function Invoke-DismountOp{ [CmdletBinding()] Param ( [Parameter(Mandatory = $true)] [string] $MountId, [Parameter(Mandatory = $true)] [string] $ComputerAddress, [parameter(Mandatory = $true)] [System.Management.Automation.Runspaces.PSSession] $ComputerSession, [Parameter(Mandatory = $false)] [string] $VCenterAddress, [Parameter(Mandatory = $false)] [PSCredential] $VCenterCredential, [parameter(Mandatory = $true)] [string] $FunctionName, [parameter(Mandatory = $true)] $RestClient ) # Gets mount history details from Mount ID $History = Get-MountObject -RestClient $RestClient -MountId $MountId # Logs dismount details $msg = "History:[$($History.Id)] Drive(s): [$($History.Paths -Join ',')] on Host: [$($History.Computer)]" Write-Log -Level INFO -FunctionName $FunctionName -Msg $msg # validate basic mount info if ([String]::IsNullOrWhiteSpace($History.Computer)) { ThrowErrorCode -Error $Script:DismountPathNull } if (-not $History.Paths -or $History.Paths.Count -eq 0) { ThrowErrorCode -Error $Script:DismountComputerNull } $mountedPaths = @() foreach ($p in @($History.Paths)){ if ($p.Length -eq 2){ # it's a drive letter like 'E:' $mountedPaths += "$($p[0])" } else{ # it's a mount point like 'C:/mountpoint' $mountedPaths += $p } } # gets virtualization info if($History.vCenter) { # TODO: Do we still need vCenter in History? $EsxiDetails = Get-ESXiDetails -VCenterAddress $VCenterAddress -VCenterCredential $VCenterCredential -VMPersistentId $History.VMId $VM = $EsxiDetails.VM } if ($History.Computer -ne $ComputerAddress) { ThrowErrorCode -ErrorCode $ErrorCode_MissingCreds -params @($History.Computer) } $mountedVolumes = $History.Resources | Get-Pfa2Volume -Array $RestClient $success = $true if ("VVOL" -ieq $History.VolumeType) { #vvol foreach ($SinglePath in $mountedPaths) { # gets vvol disk info Write-Log -Level INFO -FunctionName $FunctionName -Msg "Removing Disk $SinglePath..." $verboseDisk = Get-PfaVerboseDiskInfo -Session $ComputerSession -RestClient $RestClient -Path $SinglePath -VCenterAddress $VCenterAddress -VCenterCredential $VCenterCredential -VMName $VM.Name if ($null -eq $verboseDisk[$SinglePath]) { # vvol was probably removed manually, log error but continues $Msg = "Could not find matching disk to Drive:$($SinglePath) in VM: $($VM.Name)" Write-Log -Level ERROR -FunctionName $FunctionName -Msg $Msg Write-Error $Msg $success = $false continue } if(-not $verboseDisk[$SinglePath].ArrayVolumeInfo.VolumeSerial -in $mountedVolumes.Serial){ ThrowErrorCode -ErrorCode $ErrorCode_MountVolsSerialError -params @($verboseDisk[$SinglePath].ArrayVolumeInfo.VolumeSerial,$SinglePath, $VM.Name, ($mountedVolumes.Serial -Join ';')) } # unexpose vvol from VM Write-Log -Level INFO -FunctionName $FunctionName -Msg "Unexposing $SinglePath" $success = $success -and (Unexpose-Disks -Session $ComputerSession -Paths $SinglePath) # destroy vvol $verboseDisk[$SinglePath].VMWareInfo.VMDisk | Remove-HardDisk -DeletePermanently -Confirm:$false } } else { # take disks offline $success = Unexpose-Disks -Session $ComputerSession -Paths $mountedPaths -VolumeSerialNumbers @($mountedVolumes.Serial) foreach ($Volume in @($mountedVolumes)) { # remove RDM volumes from VM if ("RDM" -ieq $History.VolumeType) { Remove-PfaVMHardDisk -VolumeSerial $Volume.Serial -VMName $($VM.Name) -VMPersistentId $($VM.PersistentId) -VCenterAddress $VCenterAddress -VCenterCredential $VCenterCredential } # remove all host/hostgroup connections Disconnect-VolHosts -volumeName $Volume.Name -RestClient $RestClient # destroy vol Remove-Pfa2Volume -Array $RestClient -Name $Volume.Name } } return $success } function Invoke-GetMountHistoryOp { [CmdletBinding()] Param ( [parameter(Mandatory = $false)] [string] $HistoryId, [parameter(Mandatory = $true)] [string] $FunctionName, [parameter(Mandatory = $true)] $RestClient ) # get mounted volumes by Mount Id # each mountid will be returned once per volume mounted # group results by mount id so we can gather the volumes for each mount $MountIds = Get-Pfa2VolumeTag -Array $RestClient -Namespaces $NamespaceMount -Filter "key='$([PureTagKeys]::MountId)'" -ResourceDestroyed:$false | Group-Object -Property {$_.Value} #### REST CALL #### # if user selected a history Id, filter results to only include mountIDs for that history if ($historyId) { $MountIds = $MountIds | Where-object {$_.Name.EndsWith("#$($HistoryID)")} } $mountHistory = @() foreach ($group in $MountIds) { $parts = $group.Name -Split '#' # id format: MountId#HistoryId # HistoryId: volset#guid if ($parts.Count -ne 3) { #invalid ids continue } # parse Ids $MountId = $parts[0] $HistoryId = "$($parts[1])#$($parts[2])" # get mounted volumes list $mountedVolumes = $group.Group.Resource # get metadata for each volume in mount entry $metadata = Get-Pfa2VolumeTag -Array $RestClient -Namespaces $NamespaceMount -Filter "key='$([PureTagKeys]::Metadata)'" -ResourceIds $mountedVolumes.Id #### REST CALL #### # load metadata into volset object try{ $volSet = @(ParseMetadataTags -Tags $metadata) } catch { # either could not parse environment tags, or volume set was not complete continue } # create mount history object $mountHistory += New-MountObject -MountId $MountId -HistoryId $HistoryId -Volumetype $volSet.VolumeType -Computer $volSet.Computer -Paths $volSet.Paths -vCenter $volSet.vCenter -VMId $volSet.VMId -Resources $volSet.Resources } return $mountHistory } function Invoke-CreateVolumeSetOp { [CmdletBinding(SupportsShouldProcess,ConfirmImpact='High')] Param ( [parameter(Mandatory = $true)] [string] $VolumeSetName, [parameter(Mandatory = $true)] [string] $ComputerAddress, [parameter(Mandatory = $true)] [System.Management.Automation.Runspaces.PSSession] $ComputerSession, [parameter(Mandatory = $true)] [string] $Path, [parameter(Mandatory = $false)] [ValidateSet("Physical", "RDM", "VVOL")] [string] $VolumeType, [parameter(Mandatory = $false)] [string] $VCenterAddress, [parameter(Mandatory = $false)] [PSCredential] $VCenterCredential, [parameter(Mandatory = $false)] [string] $VMName, [parameter(Mandatory = $false)] [string] $VMPersistentId, [parameter(Mandatory = $false)] [switch] $SkipValidation, [parameter(Mandatory = $true)] [string] $FunctionName, [parameter(Mandatory = $true)] $RestClient, [parameter(Mandatory = $false)] [switch] $Force ) if ($Force){ $ConfirmPreference = 'None' } if (-not (ValidatePureObjectName -Name $VolumeSetName)) { ThrowErrorCode -ErrorCode $ErrorCode_PurityNameRequirements -params @("VolumeSetName",$VolumeSetName) } # error out if tag already exist Write-Progress -Activity $FunctionName -Status $LoadTagsMsg $volumeSetTags = Get-Pfa2VolumeTag -Array $RestClient -Namespaces $NamespaceVolSet -Filter "Key=""$VolumeSetName""" $ValidatedVolumeType = ValidateVolumeType -VCenterAddress $VCenterAddress -VolumeType $VolumeType if (-not [String]::IsNullOrEmpty($VCenterAddress)) { # caller supplied VCenterAddress - our SQL Server is on a VM if ($null -eq $VCenterCredential) { ThrowErrorCode -ErrorCode $ErrorCode_MissingVcenterCreds } $VCenter = Connect-VCenter -VCenterAddress $VCenterAddress -Credential $VCenterCredential try { $VM = Get-SingleVMOnly -Name $VMName -VMPersistentId $VMPersistentId -Server $VCenter # Get the current name and Id from the VM we found. # Handles the case where the user supplied just the name or id, # or if they gave both, but the name doesn't coorespond to the Id. $VMName = $VM.Name $VMPersistentId = $VM.PersistentId } catch [VMware.VimAutomation.Sdk.Types.V1.ErrorHandling.VimException.VimException] { if ($_.Exception.PSObject.Properties.Name -contains "ErrorCategory" -and $_.Exception.ErrorCategory -eq "ObjectNotFound") { ThrowErrorCode -ErrorCode $ErrorCode_VmMissingOnVcenter -params @((Get-VmNameToString $VMName $VMPersistentId), $VCenterAddress) -innerException $_.Exception } throw } } Write-Progress -Activity $FunctionName -Status $GetCorVolMsg # gather volumes list if($VolumeType -eq "physical"){ $affectedVolumes = Get-PfaVerboseDiskInfo -Session $ComputerSession -RestClient $RestClient -Path $Path } else{ $affectedVolumes = Get-PfaVerboseDiskInfo -Session $ComputerSession ` -RestClient $RestClient -Path $Path -VCenterAddress $VCenterAddress -VCenterCredential $VCenterCredential -VMName $VMName } if (-not $SkipValidation) { # Validate all volumes are of the same type specified in the config foreach ($diskPath in $affectedVolumes.Keys) { $disk = $affectedVolumes[$diskPath] if (-not $disk.ComputerDiskInfo){ # drive letter/path did not exist on target computer ThrowErrorCode -ErrorCode $ErrorCode_InvalidPath -params @($diskPath) } $diskVolumeType = $disk.VolumeType if ($ValidatedVolumeType -ne $diskVolumeType){ # disk type did not match type specified ThrowErrorCode -ErrorCode $ErrorCode_InvalidVolType -params @($diskPath,$diskVolumeType) } } } # if volumeSetTags already existed, a volume set with this name already exists on the FA, check if they are the same if($volumeSetTags.Count -gt 0){ $areVolumeSetsDifferent = -not (CheckSameListsOfUniqueElements -List1 $volumeSetTags.Resource.Id -List2 $affectedVolumes.Values.ArrayVolumeInfo.VolumeId) # if volume sets are different, check if user wants to proceed if ($areVolumeSetsDifferent -and -not ($PSCmdlet.ShouldProcess($vSetExistsDescMsg, $vSetExistsWarnMsg, $vSetExistsTitleMsg))){ ThrowErrorCode -ErrorCode $ErrorCode_userAbort } # removes old volume set Invoke-RemoveVolumeSetOp -FunctionName "Invoke-RemoveVolumeSetOp" -VolumeSetName $VolumeSetName -RestClient $RestClient | Out-Null } $ConfirmPreference = "None" # tag volumes Write-Progress -Activity $FunctionName -Status $TagVolsMsg # TODO: any problems using # as a delimiter, better options? # verify that values do not contain delimiter if($Path -like '*#*') { ThrowErrorCode -ErrorCode $ErrorCode_BadDelimeter -params @( "Path",$Path )} if($ComputerAddress -like '*#*') { ThrowErrorCode -ErrorCode $ErrorCode_BadDelimeter -params @( "Computer Address",$ComputerAddress )} if($VCenterAddress -like '*#*') { ThrowErrorCode -ErrorCode $ErrorCode_BadDelimeter -params @( "VCenter Address",$VCenterAddress )} if($VMPersistentId -like '*#*') { ThrowErrorCode -ErrorCode $ErrorCode_BadDelimeter -params @( "VM Persistent Id",$VMPersistentId )} foreach ($diskPath in $affectedVolumes.Keys) { # note: tag values can have up to 256 character # VolCount: 1 to 3 chars # VolType: 3 or 8 chars # Delimiters: 5 chars # Remaining: 240Chars / 4 = ~60 chars per field $envTagVal = "$($affectedVolumes.Count)#$($ValidatedVolumeType)#$($ComputerAddress)#$($diskPath)#$($VCenterAddress)#$($VMPersistentId)" if ($envTagVal.Length -gt 256) { ThrowErrorCode -ErrorCode $ErrorCode_EnvTagTooLong -params @($envTagVal) } $disk = $affectedVolumes[$diskPath] $envTag = Set-Pfa2VolumeTagBatch -Array $RestClient -ResourceIds $disk.ArrayVolumeInfo.VolumeId -TagCopyable $false -TagNamespace $NamespaceVolSet -TagKey $VolumeSetName -TagValue $envTagVal if (-not $envTag) { ThrowErrorCode -ErrorCode $ErrorCode_FailToSetMetaTag } } $volumes = @() foreach ($vol in $affectedVolumes.Values.ArrayVolumeInfo) { $volumes += @{ Id = $vol.VolumeId Name = $vol.VolumeName } } return New-VolumeSetObject -Name $VolumeSetName -VolumeType $ValidatedVolumeType -Computer $ComputerAddress -Paths $affectedVolumes.Keys -vCenter $VCenterAddress -VMId $VMPersistentId -Resources $volumes } function Invoke-GetVolumeSetOp { [CmdletBinding()] Param ( [parameter(Mandatory = $false)] [string] $VolumeSetName, [parameter(Mandatory = $true)] [string] $FunctionName, [parameter(Mandatory = $true)] $RestClient ) Write-Progress -Activity $FunctionName -Status $LoadTagsMsg if ($VolumeSetName) { $volumeSetTags = Get-Pfa2VolumeTag -Array $RestClient -Namespaces $NamespaceVolSet -Filter "Key=""$VolumeSetName""" } else { $volumeSetTags = Get-Pfa2VolumeTag -Array $RestClient -Namespaces $NamespaceVolSet } Write-Progress -Activity $FunctionName -Status $BldVolSetMsg $volumeSetNames = $volumeSetTags.Key | Sort-Object | Get-Unique $volumeSets = @() foreach ($name in $volumeSetNames) { $volumeTags = $volumeSetTags | where-object {$_.Key -eq $name} $volumeSets += ParseMetadataTags -Tags $volumeTags } Write-Progress -Activity $FunctionName -Status $OpFinishedMsg -Completed return $volumeSets } function Invoke-RemoveVolumeSetOp { [CmdletBinding()] Param ( [parameter(Mandatory = $true)] [string] $VolumeSetName, [parameter(Mandatory = $true)] [string] $FunctionName, [parameter(Mandatory = $true)] $RestClient ) Write-Progress -Activity $FunctionName -Status $LoadTagsMsg $volumeSetTags = Get-Pfa2VolumeTag -Array $RestClient -Namespaces $NamespaceVolSet -Filter "Key=""$VolumeSetName"" " -ErrorAction Stop $resources = $volumeSetTags.Resource.Id Write-Progress -Activity $FunctionName -Status $RemMetaMsg # TODO: check for confirmation? Remove-Pfa2VolumeTag -Array $RestClient -ResourceIds $resources -Namespaces $NamespaceVolSet -Keys @("$VolumeSetName") -ErrorAction Stop | Out-Null Write-Progress -Activity $FunctionName -Status $ -Completed } function Invoke-GetVolumeSetPgroupsOp { [CmdletBinding()] Param ( [parameter(Mandatory = $true)] [string] $VolumeSetName, [parameter(Mandatory = $true)] [string] $FunctionName, [parameter(Mandatory = $true)] $RestClient ) Write-Progress -Activity $FunctionName -Status $LoadTagsMsg $volumeSetTags = Get-Pfa2VolumeTag -Array $RestClient -Namespaces $NamespaceVolSet -Filter "Key=""$VolumeSetName"" " Write-Progress -Activity $FunctionName -Status $FindMatPgMsg $matchingPgroups = Get-MatchingPgroups -RestClient $RestClient -VolumesNames $volumeSetTags.Resource.name Write-Progress -Activity $FunctionName -Status $OpFinishedMsg return $matchingPgroups } # ExportedUtils Export-ModuleMember -Function Get-PSBAvailableDrive Export-ModuleMember -Function Get-PSBVMPersistentId # Volume set Export-ModuleMember -Function New-PsbVolumeSet Export-ModuleMember -Function Get-PsbVolumeSet Export-ModuleMember -Function Remove-PsbVolumeSet Export-ModuleMember -Function Get-PsbVolumeSetProtectionGroup # Main Workflow Export-ModuleMember -Function Invoke-PsbSnapshotJob Export-ModuleMember -Function Get-PsbSnapshotJobHistory Export-ModuleMember -Function Remove-PsbSnapshotSet Export-ModuleMember -Function Mount-PsbSnapshotSet Export-ModuleMember -Function Dismount-PsbSnapshotSet Export-ModuleMember -Function Get-PsbSnapshotSetMountHistory # SIG # Begin signature block # MIIn+AYJKoZIhvcNAQcCoIIn6TCCJ+UCAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB # gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR # AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQU3i5FqVAAEDPKpxOcTVrl8XxF # xgqggiEoMIIFjTCCBHWgAwIBAgIQDpsYjvnQLefv21DiCEAYWjANBgkqhkiG9w0B # AQwFADBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYD # VQQLExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVk # IElEIFJvb3QgQ0EwHhcNMjIwODAxMDAwMDAwWhcNMzExMTA5MjM1OTU5WjBiMQsw # CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu # ZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQw # ggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC/5pBzaN675F1KPDAiMGkz # 7MKnJS7JIT3yithZwuEppz1Yq3aaza57G4QNxDAf8xukOBbrVsaXbR2rsnnyyhHS # 5F/WBTxSD1Ifxp4VpX6+n6lXFllVcq9ok3DCsrp1mWpzMpTREEQQLt+C8weE5nQ7 # bXHiLQwb7iDVySAdYyktzuxeTsiT+CFhmzTrBcZe7FsavOvJz82sNEBfsXpm7nfI # SKhmV1efVFiODCu3T6cw2Vbuyntd463JT17lNecxy9qTXtyOj4DatpGYQJB5w3jH # trHEtWoYOAMQjdjUN6QuBX2I9YI+EJFwq1WCQTLX2wRzKm6RAXwhTNS8rhsDdV14 # Ztk6MUSaM0C/CNdaSaTC5qmgZ92kJ7yhTzm1EVgX9yRcRo9k98FpiHaYdj1ZXUJ2 # h4mXaXpI8OCiEhtmmnTK3kse5w5jrubU75KSOp493ADkRSWJtppEGSt+wJS00mFt # 6zPZxd9LBADMfRyVw4/3IbKyEbe7f/LVjHAsQWCqsWMYRJUadmJ+9oCw++hkpjPR # iQfhvbfmQ6QYuKZ3AeEPlAwhHbJUKSWJbOUOUlFHdL4mrLZBdd56rF+NP8m800ER # ElvlEFDrMcXKchYiCd98THU/Y+whX8QgUWtvsauGi0/C1kVfnSD8oR7FwI+isX4K # Jpn15GkvmB0t9dmpsh3lGwIDAQABo4IBOjCCATYwDwYDVR0TAQH/BAUwAwEB/zAd # BgNVHQ4EFgQU7NfjgtJxXWRM3y5nP+e6mK4cD08wHwYDVR0jBBgwFoAUReuir/SS # y4IxLVGLp6chnfNtyA8wDgYDVR0PAQH/BAQDAgGGMHkGCCsGAQUFBwEBBG0wazAk # BggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEMGCCsGAQUFBzAC # hjdodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRBc3N1cmVkSURS # b290Q0EuY3J0MEUGA1UdHwQ+MDwwOqA4oDaGNGh0dHA6Ly9jcmwzLmRpZ2ljZXJ0 # LmNvbS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcmwwEQYDVR0gBAowCDAGBgRV # HSAAMA0GCSqGSIb3DQEBDAUAA4IBAQBwoL9DXFXnOF+go3QbPbYW1/e/Vwe9mqyh # hyzshV6pGrsi+IcaaVQi7aSId229GhT0E0p6Ly23OO/0/4C5+KH38nLeJLxSA8hO # 0Cre+i1Wz/n096wwepqLsl7Uz9FDRJtDIeuWcqFItJnLnU+nBgMTdydE1Od/6Fmo # 8L8vC6bp8jQ87PcDx4eo0kxAGTVGamlUsLihVo7spNU96LHc/RzY9HdaXFSMb++h # UD38dglohJ9vytsgjTVgHAIDyyCwrFigDkBjxZgiwbJZ9VVrzyerbHbObyMt9H5x # aiNrIv8SuFQtJ37YOtnwtoeW/VvRXKwYw02fc7cBqZ9Xql4o4rmUMIIGrjCCBJag # AwIBAgIQBzY3tyRUfNhHrP0oZipeWzANBgkqhkiG9w0BAQsFADBiMQswCQYDVQQG # EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl # cnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwHhcNMjIw # MzIzMDAwMDAwWhcNMzcwMzIyMjM1OTU5WjBjMQswCQYDVQQGEwJVUzEXMBUGA1UE # ChMORGlnaUNlcnQsIEluYy4xOzA5BgNVBAMTMkRpZ2lDZXJ0IFRydXN0ZWQgRzQg # UlNBNDA5NiBTSEEyNTYgVGltZVN0YW1waW5nIENBMIICIjANBgkqhkiG9w0BAQEF # AAOCAg8AMIICCgKCAgEAxoY1BkmzwT1ySVFVxyUDxPKRN6mXUaHW0oPRnkyibaCw # zIP5WvYRoUQVQl+kiPNo+n3znIkLf50fng8zH1ATCyZzlm34V6gCff1DtITaEfFz # sbPuK4CEiiIY3+vaPcQXf6sZKz5C3GeO6lE98NZW1OcoLevTsbV15x8GZY2UKdPZ # 7Gnf2ZCHRgB720RBidx8ald68Dd5n12sy+iEZLRS8nZH92GDGd1ftFQLIWhuNyG7 # QKxfst5Kfc71ORJn7w6lY2zkpsUdzTYNXNXmG6jBZHRAp8ByxbpOH7G1WE15/teP # c5OsLDnipUjW8LAxE6lXKZYnLvWHpo9OdhVVJnCYJn+gGkcgQ+NDY4B7dW4nJZCY # OjgRs/b2nuY7W+yB3iIU2YIqx5K/oN7jPqJz+ucfWmyU8lKVEStYdEAoq3NDzt9K # oRxrOMUp88qqlnNCaJ+2RrOdOqPVA+C/8KI8ykLcGEh/FDTP0kyr75s9/g64ZCr6 # dSgkQe1CvwWcZklSUPRR8zZJTYsg0ixXNXkrqPNFYLwjjVj33GHek/45wPmyMKVM # 1+mYSlg+0wOI/rOP015LdhJRk8mMDDtbiiKowSYI+RQQEgN9XyO7ZONj4KbhPvbC # dLI/Hgl27KtdRnXiYKNYCQEoAA6EVO7O6V3IXjASvUaetdN2udIOa5kM0jO0zbEC # AwEAAaOCAV0wggFZMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFLoW2W1N # hS9zKXaaL3WMaiCPnshvMB8GA1UdIwQYMBaAFOzX44LScV1kTN8uZz/nupiuHA9P # MA4GA1UdDwEB/wQEAwIBhjATBgNVHSUEDDAKBggrBgEFBQcDCDB3BggrBgEFBQcB # AQRrMGkwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBBBggr # BgEFBQcwAoY1aHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1 # c3RlZFJvb3RHNC5jcnQwQwYDVR0fBDwwOjA4oDagNIYyaHR0cDovL2NybDMuZGln # aWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZFJvb3RHNC5jcmwwIAYDVR0gBBkwFzAI # BgZngQwBBAIwCwYJYIZIAYb9bAcBMA0GCSqGSIb3DQEBCwUAA4ICAQB9WY7Ak7Zv # mKlEIgF+ZtbYIULhsBguEE0TzzBTzr8Y+8dQXeJLKftwig2qKWn8acHPHQfpPmDI # 2AvlXFvXbYf6hCAlNDFnzbYSlm/EUExiHQwIgqgWvalWzxVzjQEiJc6VaT9Hd/ty # dBTX/6tPiix6q4XNQ1/tYLaqT5Fmniye4Iqs5f2MvGQmh2ySvZ180HAKfO+ovHVP # ulr3qRCyXen/KFSJ8NWKcXZl2szwcqMj+sAngkSumScbqyQeJsG33irr9p6xeZmB # o1aGqwpFyd/EjaDnmPv7pp1yr8THwcFqcdnGE4AJxLafzYeHJLtPo0m5d2aR8XKc # 6UsCUqc3fpNTrDsdCEkPlM05et3/JWOZJyw9P2un8WbDQc1PtkCbISFA0LcTJM3c # HXg65J6t5TRxktcma+Q4c6umAU+9Pzt4rUyt+8SVe+0KXzM5h0F4ejjpnOHdI/0d # KNPH+ejxmF/7K9h+8kaddSweJywm228Vex4Ziza4k9Tm8heZWcpw8De/mADfIBZP # J/tgZxahZrrdVcA6KYawmKAr7ZVBtzrVFZgxtGIJDwq9gdkT/r+k0fNX2bwE+oLe # Mt8EifAAzV3C+dAjfwAL5HYCJtnwZXZCpimHCUcr5n8apIUP/JiW9lVUKx+A+sDy # Divl1vupL0QVSucTDh3bNzgaoSv27dZ8/DCCBrAwggSYoAMCAQICEAitQLJg0pxM # n17Nqb2TrtkwDQYJKoZIhvcNAQEMBQAwYjELMAkGA1UEBhMCVVMxFTATBgNVBAoT # DERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTEhMB8GA1UE # AxMYRGlnaUNlcnQgVHJ1c3RlZCBSb290IEc0MB4XDTIxMDQyOTAwMDAwMFoXDTM2 # MDQyODIzNTk1OVowaTELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJ # bmMuMUEwPwYDVQQDEzhEaWdpQ2VydCBUcnVzdGVkIEc0IENvZGUgU2lnbmluZyBS # U0E0MDk2IFNIQTM4NCAyMDIxIENBMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCC # AgoCggIBANW0L0LQKK14t13VOVkbsYhC9TOM6z2Bl3DFu8SFJjCfpI5o2Fz16zQk # B+FLT9N4Q/QX1x7a+dLVZxpSTw6hV/yImcGRzIEDPk1wJGSzjeIIfTR9TIBXEmtD # mpnyxTsf8u/LR1oTpkyzASAl8xDTi7L7CPCK4J0JwGWn+piASTWHPVEZ6JAheEUu # oZ8s4RjCGszF7pNJcEIyj/vG6hzzZWiRok1MghFIUmjeEL0UV13oGBNlxX+yT4Us # SKRWhDXW+S6cqgAV0Tf+GgaUwnzI6hsy5srC9KejAw50pa85tqtgEuPo1rn3MeHc # reQYoNjBI0dHs6EPbqOrbZgGgxu3amct0r1EGpIQgY+wOwnXx5syWsL/amBUi0nB # k+3htFzgb+sm+YzVsvk4EObqzpH1vtP7b5NhNFy8k0UogzYqZihfsHPOiyYlBrKD # 1Fz2FRlM7WLgXjPy6OjsCqewAyuRsjZ5vvetCB51pmXMu+NIUPN3kRr+21CiRshh # WJj1fAIWPIMorTmG7NS3DVPQ+EfmdTCN7DCTdhSmW0tddGFNPxKRdt6/WMtyEClB # 8NXFbSZ2aBFBE1ia3CYrAfSJTVnbeM+BSj5AR1/JgVBzhRAjIVlgimRUwcwhGug4 # GXxmHM14OEUwmU//Y09Mu6oNCFNBfFg9R7P6tuyMMgkCzGw8DFYRAgMBAAGjggFZ # MIIBVTASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBRoN+Drtjv4XxGG+/5h # ewiIZfROQjAfBgNVHSMEGDAWgBTs1+OC0nFdZEzfLmc/57qYrhwPTzAOBgNVHQ8B # Af8EBAMCAYYwEwYDVR0lBAwwCgYIKwYBBQUHAwMwdwYIKwYBBQUHAQEEazBpMCQG # CCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wQQYIKwYBBQUHMAKG # NWh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRSb290 # RzQuY3J0MEMGA1UdHwQ8MDowOKA2oDSGMmh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNv # bS9EaWdpQ2VydFRydXN0ZWRSb290RzQuY3JsMBwGA1UdIAQVMBMwBwYFZ4EMAQMw # CAYGZ4EMAQQBMA0GCSqGSIb3DQEBDAUAA4ICAQA6I0Q9jQh27o+8OpnTVuACGqX4 # SDTzLLbmdGb3lHKxAMqvbDAnExKekESfS/2eo3wm1Te8Ol1IbZXVP0n0J7sWgUVQ # /Zy9toXgdn43ccsi91qqkM/1k2rj6yDR1VB5iJqKisG2vaFIGH7c2IAaERkYzWGZ # gVb2yeN258TkG19D+D6U/3Y5PZ7Umc9K3SjrXyahlVhI1Rr+1yc//ZDRdobdHLBg # XPMNqO7giaG9OeE4Ttpuuzad++UhU1rDyulq8aI+20O4M8hPOBSSmfXdzlRt2V0C # FB9AM3wD4pWywiF1c1LLRtjENByipUuNzW92NyyFPxrOJukYvpAHsEN/lYgggnDw # zMrv/Sk1XB+JOFX3N4qLCaHLC+kxGv8uGVw5ceG+nKcKBtYmZ7eS5k5f3nqsSc8u # pHSSrds8pJyGH+PBVhsrI/+PteqIe3Br5qC6/To/RabE6BaRUotBwEiES5ZNq0RA # 443wFSjO7fEYVgcqLxDEDAhkPDOPriiMPMuPiAsNvzv0zh57ju+168u38HcT5uco # P6wSrqUvImxB+YJcFWbMbA7KxYbD9iYzDAdLoNMHAmpqQDBISzSoUSC7rRuFCOJZ # DW3KBVAr6kocnqX9oKcfBnTn8tZSkP2vhUgh+Vc7tJwD7YZF9LRhbr9o4iZghurI # r6n+lB3nYxs6hlZ4TjCCBsIwggSqoAMCAQICEAVEr/OUnQg5pr/bP1/lYRYwDQYJ # KoZIhvcNAQELBQAwYzELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJ # bmMuMTswOQYDVQQDEzJEaWdpQ2VydCBUcnVzdGVkIEc0IFJTQTQwOTYgU0hBMjU2 # IFRpbWVTdGFtcGluZyBDQTAeFw0yMzA3MTQwMDAwMDBaFw0zNDEwMTMyMzU5NTla # MEgxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjEgMB4GA1UE # AxMXRGlnaUNlcnQgVGltZXN0YW1wIDIwMjMwggIiMA0GCSqGSIb3DQEBAQUAA4IC # DwAwggIKAoICAQCjU0WHHYOOW6w+VLMj4M+f1+XS512hDgncL0ijl3o7Kpxn3GIV # WMGpkxGnzaqyat0QKYoeYmNp01icNXG/OpfrlFCPHCDqx5o7L5Zm42nnaf5bw9Yr # IBzBl5S0pVCB8s/LB6YwaMqDQtr8fwkklKSCGtpqutg7yl3eGRiF+0XqDWFsnf5x # XsQGmjzwxS55DxtmUuPI1j5f2kPThPXQx/ZILV5FdZZ1/t0QoRuDwbjmUpW1R9d4 # KTlr4HhZl+NEK0rVlc7vCBfqgmRN/yPjyobutKQhZHDr1eWg2mOzLukF7qr2JPUd # vJscsrdf3/Dudn0xmWVHVZ1KJC+sK5e+n+T9e3M+Mu5SNPvUu+vUoCw0m+PebmQZ # BzcBkQ8ctVHNqkxmg4hoYru8QRt4GW3k2Q/gWEH72LEs4VGvtK0VBhTqYggT02ke # fGRNnQ/fztFejKqrUBXJs8q818Q7aESjpTtC/XN97t0K/3k0EH6mXApYTAA+hWl1 # x4Nk1nXNjxJ2VqUk+tfEayG66B80mC866msBsPf7Kobse1I4qZgJoXGybHGvPrhv # ltXhEBP+YUcKjP7wtsfVx95sJPC/QoLKoHE9nJKTBLRpcCcNT7e1NtHJXwikcKPs # CvERLmTgyyIryvEoEyFJUX4GZtM7vvrrkTjYUQfKlLfiUKHzOtOKg8tAewIDAQAB # o4IBizCCAYcwDgYDVR0PAQH/BAQDAgeAMAwGA1UdEwEB/wQCMAAwFgYDVR0lAQH/ # BAwwCgYIKwYBBQUHAwgwIAYDVR0gBBkwFzAIBgZngQwBBAIwCwYJYIZIAYb9bAcB # MB8GA1UdIwQYMBaAFLoW2W1NhS9zKXaaL3WMaiCPnshvMB0GA1UdDgQWBBSltu8T # 5+/N0GSh1VapZTGj3tXjSTBaBgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsMy5k # aWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkRzRSU0E0MDk2U0hBMjU2VGltZVN0 # YW1waW5nQ0EuY3JsMIGQBggrBgEFBQcBAQSBgzCBgDAkBggrBgEFBQcwAYYYaHR0 # cDovL29jc3AuZGlnaWNlcnQuY29tMFgGCCsGAQUFBzAChkxodHRwOi8vY2FjZXJ0 # cy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkRzRSU0E0MDk2U0hBMjU2VGlt # ZVN0YW1waW5nQ0EuY3J0MA0GCSqGSIb3DQEBCwUAA4ICAQCBGtbeoKm1mBe8cI1P # ijxonNgl/8ss5M3qXSKS7IwiAqm4z4Co2efjxe0mgopxLxjdTrbebNfhYJwr7e09 # SI64a7p8Xb3CYTdoSXej65CqEtcnhfOOHpLawkA4n13IoC4leCWdKgV6hCmYtld5 # j9smViuw86e9NwzYmHZPVrlSwradOKmB521BXIxp0bkrxMZ7z5z6eOKTGnaiaXXT # UOREEr4gDZ6pRND45Ul3CFohxbTPmJUaVLq5vMFpGbrPFvKDNzRusEEm3d5al08z # jdSNd311RaGlWCZqA0Xe2VC1UIyvVr1MxeFGxSjTredDAHDezJieGYkD6tSRN+9N # UvPJYCHEVkft2hFLjDLDiOZY4rbbPvlfsELWj+MXkdGqwFXjhr+sJyxB0JozSqg2 # 1Llyln6XeThIX8rC3D0y33XWNmdaifj2p8flTzU8AL2+nCpseQHc2kTmOt44Owde # OVj0fHMxVaCAEcsUDH6uvP6k63llqmjWIso765qCNVcoFstp8jKastLYOrixRoZr # uhf9xHdsFWyuq69zOuhJRrfVf8y2OMDY7Bz1tqG4QyzfTkx9HmhwwHcK1ALgXGC7 # KP845VJa1qwXIiNO9OzTF/tQa/8Hdx9xl0RBybhG02wyfFgvZ0dl5Rtztpn5aywG # Ru9BHvDwX+Db2a2QgESvgBBBijCCB2cwggVPoAMCAQICEATd+82EVAN2YngfhA+f # z/UwDQYJKoZIhvcNAQELBQAwaTELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lD # ZXJ0LCBJbmMuMUEwPwYDVQQDEzhEaWdpQ2VydCBUcnVzdGVkIEc0IENvZGUgU2ln # bmluZyBSU0E0MDk2IFNIQTM4NCAyMDIxIENBMTAeFw0yMzEwMDQwMDAwMDBaFw0y # NjExMTUyMzU5NTlaMG8xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9u # MREwDwYDVQQHEwhCZWxsZXZ1ZTEbMBkGA1UEChMSUHVyZSBTdG9yYWdlLCBJbmMu # MRswGQYDVQQDExJQdXJlIFN0b3JhZ2UsIEluYy4wggIiMA0GCSqGSIb3DQEBAQUA # A4ICDwAwggIKAoICAQCdhXqOLFS3HR5KD2RtAOzGdwKU0mMGGHfU7qUo1YFvDCN8 # vF/X8LDhouGtsZPdIfd298orsXHfXYElTgBo91gba7SqKBWi9xdXTqMR5vpt41K/ # a554AgiQp02nfYwuspZoAGnt//mDJ6ErP1jUFiWuwHsYsxk0gFEayp5xIKzmj3q4 # 9g+AenKpktbDn6HPpXZPdvg+g+GR9lPpiJo7Z40SIqzaacJsVcl5MhPfbFdLeP1s # n0MBW3BiYLyz4CEUq8IA2vJ2557N0uB0UzWERE31brL0mBn5gB1g8Zij9VsI9J5+ # Q+THKYIgwknlnXFiSwQhQbJ3Cn7IVotei1M/D011XjUR66kNHm02VVDsbxX92xLf # qIX7BZ0e6shMsOFVakkdM00nXhfRscDkRqEQ+IwgC3vcyJgp/QRX0SfWaaD5G0fi # ECMBZtmq5hijTJ18MAW2KaFePW0PIn9IRnoXS3tx9coXVJMTFwnLYdIukelF4jIW # 779IP5lQH7IBNHS01BgysjWVaQhPYxWZYtsxyRUX3gVRjFChhOtBNCAy2S+YYjUS # TOM7CdUNTtCARX/HgcRYxxU7UTOYXPYyabdQu3mFF8yD5YNkarlgc4TQ+H1PWnIU # l7pq3P0ZSaE5Est24ApVi6wlZC/Q3jQRKPziRg8x7Zv1TZX8TfxPDmE0Nsd+BwID # AQABo4ICAzCCAf8wHwYDVR0jBBgwFoAUaDfg67Y7+F8Rhvv+YXsIiGX0TkIwHQYD # VR0OBBYEFCvH/lBQxrVtiuuihv+e6+2VgDPXMD4GA1UdIAQ3MDUwMwYGZ4EMAQQB # MCkwJwYIKwYBBQUHAgEWG2h0dHA6Ly93d3cuZGlnaWNlcnQuY29tL0NQUzAOBgNV # HQ8BAf8EBAMCB4AwEwYDVR0lBAwwCgYIKwYBBQUHAwMwgbUGA1UdHwSBrTCBqjBT # oFGgT4ZNaHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZEc0 # Q29kZVNpZ25pbmdSU0E0MDk2U0hBMzg0MjAyMUNBMS5jcmwwU6BRoE+GTWh0dHA6 # Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRHNENvZGVTaWduaW5n # UlNBNDA5NlNIQTM4NDIwMjFDQTEuY3JsMIGUBggrBgEFBQcBAQSBhzCBhDAkBggr # BgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMFwGCCsGAQUFBzAChlBo # dHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkRzRDb2Rl # U2lnbmluZ1JTQTQwOTZTSEEzODQyMDIxQ0ExLmNydDAJBgNVHRMEAjAAMA0GCSqG # SIb3DQEBCwUAA4ICAQCrjkZxw1B2w/pYu4siB36x5J9Xate13IDt57bxZvc7OGgz # limeUq1/HObzW4Vej9tESBpT6a5FBuVSXQXvYQntQczEFBBksRXyad3tx5/xElHA # LaE6BCZUtPKu3/CSrgsvVi7OgWNybOFWF2Xk9K1djImG55J7jOY+8ZKegUSlHPjB # 8HF9G4VdS85L2SuFaRzOMTEIW+h0Ihkp6Js1rbe0YLZu/ad6VWFKoX++FDg3cbM8 # FLf482p+XCmuX/qZuFmySYDQQ4jvNiecEiyZ4m6HUryx9Fagc0NBADiOJc1R2p2I # QbBasyndhn8KWlGSudJ+uCfuzD6ukGVe4kOpYlqkzVeOscetaY0/5v+896yP4FA8 # NS68I2eMuKbis2ouOIrAVkNPdymBjaEW1U6q979upeEG22UjjrRkq5qSdO+nk2tK # NL1ZIc92bqIs132yuwVZ6A7Dvez03VSitT2UVBMz0BKNy1EnZ4hjqBrApU+Bbcwc # 7nPV9hKKbEFKCcCNLpkAP8SCVX6r7qMyqYhAl+XKSfCkMpxRD2LykRup5mz54cQP # RPoy86iVhFhWUez1O3t371sgYulMuxaff5mXK3xlzYZUHpJGkOYntQ2VlqUpl/VO # KcNTXWnuPOyuUZY0b9tWU0Ofs8Imp7+lULJ7XUbrJoY1bUa22ce912PVBsWOojGC # BjowggY2AgEBMH0waTELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJ # bmMuMUEwPwYDVQQDEzhEaWdpQ2VydCBUcnVzdGVkIEc0IENvZGUgU2lnbmluZyBS # U0E0MDk2IFNIQTM4NCAyMDIxIENBMQIQBN37zYRUA3ZieB+ED5/P9TAJBgUrDgMC # GgUAoHAwEAYKKwYBBAGCNwIBDDECMAAwGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcC # AQQwHAYKKwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUwIwYJKoZIhvcNAQkEMRYE # FML6G2R31XMzInsWO3nuJ54Ca24HMA0GCSqGSIb3DQEBAQUABIICAGvi/z0/0irD # oojBnE6I/tB9DMA/HKR9P5D+zOuwlcc9VN4s3AThSTBxo0VX2fW73ICeJNgco9Es # QqwL6yhXBCNGPv17jQsp0sFkxZ0/1JP+KIIoHND5rX2im6XHWx9Smr6NKLDq3ayZ # c5LiN1UNMZAOFOhDaIZsCR5NpYLsjypOG3K1otkKH1r11hKpyjpEp+Vhw1o7IdkO # i1rifU+s36i49HI/DB4uJH+g2CwJ59hlrsAuDc+6y2EO4/hFQHl79kPw6Q9boI4n # Ae0SNVhxBapBzTRPK0l3clnR/hdovjQn7l5be0fjt2fzz7JQcdjo+dec5plWSEWn # p+YURH5himOUbORyYKMiH5ahD05XBezyqNs+c6yQsCSiF28MfyieBHPh+pkAg39E # yIBqjtHGzmEYQiVIOxpA/O+hMwcSIJADNl1RNYFwQzP6UuNG+J7WwVcFUIW4e2Iw # J+WHb9L2heiWiZaDnccZ35x8QS+DfaYJ5q4CQ9fUDziTC7Jb8d17aT6TG5hi2bfA # UZa0pPg1orpPLHuHKrSi+aYVu8Lz8LEViERgeT45eNRx1X42RvTNn+pdwrCEN3RG # lpHDA7t7uHTKTTG37CpFXf2CR4BxYz05qI7ZC0rgHLdUdf2/MfQOiecPxgrzxgNA # pBKlGbLTx0PqExXmTuY+9xhcZpAW0NWnoYIDIDCCAxwGCSqGSIb3DQEJBjGCAw0w # ggMJAgEBMHcwYzELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMu # MTswOQYDVQQDEzJEaWdpQ2VydCBUcnVzdGVkIEc0IFJTQTQwOTYgU0hBMjU2IFRp # bWVTdGFtcGluZyBDQQIQBUSv85SdCDmmv9s/X+VhFjANBglghkgBZQMEAgEFAKBp # MBgGCSqGSIb3DQEJAzELBgkqhkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8XDTIzMTEx # MDAwMjY0M1owLwYJKoZIhvcNAQkEMSIEIO+st5YOFw9sm1qgco+hfdR4Ah2Qrwvd # RniN8uxJxLEVMA0GCSqGSIb3DQEBAQUABIICAHS7G2PrNw2N+0VM3KsAL1V4dLA1 # 3YGCKYnli0+/fM3oxScunjJaGgVrIJy3XNuUkEYNgB5LbCXRcLBGfsZQucD0S2Bb # 2G2XXJm04GzW1Lfu1nP1wpExglae26cSZpH9ppnePh2Kp729o9ugCbJd7iKGHKLa # WJP0u6JWo/HBHykmVlqA8C18ZDUaoQ7NyxVLzvM1s8lhAA2D6fo2QRpuFSKc9Px7 # aw+cQP0b8Rm8dfqN4c4P0kdOU7QDuHXXE4v52DxfO6f+/oC1AFxFLoL/DxFYDtUu # W4QlVsDy9SOAttYclTQzq6AEHWCS6iyYhf6FTaGkFN/uSHydi0vEE+rDM+gOiI1f # bMm7bUK3zpPWyD71NmLHXSO1qFBUjtvET+AEZfK4MN0KUDpI/IpUKJ0GQn9TFuCm # l2K6COQ80bztf1I4OxSQFWk9yOn/vxCz8xXNuUyJsXIlnIY7okwKKTbZVk/Y6T0L # yK3i92mJIkpl8mMmyu7uaI7fSlSXJDsuINWzmPdBNfoo+IH2xpTbtEpQ51un47DF # AMD2BMGkFINa536bajpHSw+o5I7KWXMbQh5B7SsmkIh3KrVwzTuCbArFiJVTLRqg # JjpPcjlaM9A+Q25FHicXucsRvJ+7OG6K8oj9VziRj4pIZ9UN1OrhJO/vqQTAWrUs # +3oSIGV+soLhve5+ # SIG # End signature block |