PureStorage.CBS.AVS.psm1
. $PSScriptRoot/PureStorage.Logger.ps1 . $PSScriptRoot/PureStorage.CommonUtil.ps1 . $PSScriptRoot/PureStorage.RunCommandLauncher.ps1 . $PSScriptRoot/PureStorage.CBS.AVS.VMFS.ps1 . $PSScriptRoot/PureStorage.CBS.AVS.VVOLS.ps1 . $PSScriptRoot/PureStorage.CBS.AVS.VVOLS.Replication.ps1 . $PSScriptRoot/PureStorage.CBS.AVS.Configuration.ps1 . $PSScriptRoot/PureStorage.CBS.AVS.Monitor.ps1 $VVOL_WARNIING = "vVols Cmdlets are experimental and not supported in this release. Please use with caution." $DEFAULT_UTILIZATION_THRESHOLD = 80 $DEFAULT_RUNCOMMAND_TIMEOUT_IN_MINUTE = 10 function Build-PCBSCluster { <# .SYNOPSIS Build or update settings for a cluster of ESXi servers .DESCRIPTION Build or update settings for a cluster of ESXi servers. Creates a hostgroup in Pure Cloud Block Store if it does not exists and updates iSCSI settings. Can be used when creating a new cluster or when adding hosts to a cluster. .PARAMETER ClusterName Cluster name .PARAMETER PureCloudBlockStoreConnection Optional. Pure Cloud Block Store Connection. The connection can be created using Connect-Pf2Array cmdlet .PARAMETER AVSCloudName AVS cloud name .PARAMETER AVSResourceGroup AVS Resource group name .PARAMETER SkipMPIOCheck Optional. Skip MPIO check. If the parameter is specified, the MPIO check will be skipped. Please use only if directed by Pure Storage support. .PARAMETER TimeoutInMinutes Optional. Timeout in minutes for RunCommand operations. .EXAMPLE Build-PCBSCluster -ClusterName "mycluster" -PureCloudBlockStoreConnection $CBSConnection ` -AVSCloudName $AVSCloudName -AVSResourceGroup $AVSResourceGroup #> [CmdletBinding()] Param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [String]$ClusterName, [Parameter(Mandatory = $false)] $PureCloudBlockStoreConnection, [Parameter(Mandatory = $true)] [String]$AVSCloudName, [Parameter(Mandatory = $true)] [String]$AVSResourceGroup, [Parameter(Mandatory = $false)] [switch] $SkipMPIOCheck, [Parameter(Mandatory=$false)] [ValidateRange(5, 60)] [int] $TimeoutInMinutes ) Write-Progress -Activity "Building cluster" -Status "0% Complete:" -PercentComplete 1 $fa = Connect-PureCloudBlockStore -PureCloudBlockStoreConnection $PureCloudBlockStoreConnection $WorkflowID = New-WorkflowID New-PhoneHomeWorkflowLogEntry -RestClient $fa -Event "Begin" -ID $WorkflowID -Name $MyInvocation.MyCommand try { $vCenterServer = Connect-AVSvCenter -AVSResourceGroupName $AVSResourceGroup -AVSPrivateCloudName $AVSCloudName $cluster = Get-Cluster -Server $vCenterServer -Name $ClusterName if (-not $cluster) { throw "Could not find cluster '$ClusterName'..." } $TimeoutInMinutes = Get-Timeout -Cluster $cluster -InputParams $PSBoundParameters $ClusterStatus = Get-AVSClusterProvisionStatus -AVSResourceGroupName $AVSResourceGroup -AVSPrivateCloudName $AVSCloudName -AvsClusterName $ClusterName if ($ClusterStatus -ne "Succeeded") { throw "The current provisioning status of cluster $ClusterName is $ClusterStatus. The operation can only proceed when provisioning status is 'Succeeded'" } if (-not $SkipMPIOCheck) { Test-MPIOProvisionStatus -AvsResourceGroupName $AVSResourceGroup -AvsPrivateCloudName $AVSCloudName -Cluster $cluster } else { Write-Warning "Skipping check for MPIO status ..." } Write-Progress -Activity "Configuring iSCSI" -Status "25% Complete:" -PercentComplete 25 $updated_hosts = New-PfaHostGroupfromVcCluster -FlashArray $fa -Cluster $cluster ` -AVSCloudName $AVSCloudName -AVSResourceGroup $AVSResourceGroup -TimeoutInMinutes $TimeoutInMinutes Write-Progress -Activity "Removing unused hosts" -Status "50% Complete:" -PercentComplete 50 Remove-PfaUnusedHosts -FlashArray $fa -Cluster $cluster | Out-Null Write-Progress -Activity "Refreshing iSCSI targets" -Status "75% Complete:" -PercentComplete 75 if ($updated_hosts) { $ethList = (Get-Pfa2NetworkInterface -Array $FlashArray | Where-Object {$_.services -eq "iscsi"} | Where-Object {$_.enabled -eq $true} | Where-Object {$null -ne $_.Eth.address}).Eth $ISCSIAddressList = @() foreach ($eth in $ethList) { $ISCSIAddressList += $eth.address } $params = @{ ClusterName = $ClusterName # Need to join the list as string as RunCommand does not support array type ISCSIAddress = $ISCSIAddressList -join "," } Invoke-RunScript -RunCommandName "Remove-VMHostStaticiSCSITargets" -RunCommandModule "Microsoft.AVS.VMFS" -Parameters $params ` -AVSCloudName $AVSCloudName -AVSResourceGroup $AVSResourceGroup -TimeoutInMinutes $TimeoutInMinutes } Write-Host "Cluster '$ClusterName' is successfully built" Write-Progress -Activity "Operation is done" -Status "100% Complete:" -PercentComplete 100 } catch { New-PhoneHomeWorkflowLogEntry -RestClient $fa -Event "Error" -ID $WorkflowID -Name $MyInvocation.MyCommand -ErrorMessage $_ throw } New-PhoneHomeWorkflowLogEntry -RestClient $fa -Event "Complete" -ID $WorkflowID -Name $MyInvocation.MyCommand Disconnect-VIServer -Server $vCenterServer -Confirm:$false -ErrorAction SilentlyContinue } function New-PCBSVmfsDatastore { <# .SYNOPSIS Creates a new VMFS datastore and mounts to a VMware cluster .DESCRIPTION Creates a new VMFS datastore and mounts to a VMware cluster .PARAMETER ClusterName Cluster name .PARAMETER DatastoreName Datastore name .PARAMETER Size Datastore capacity size in bytes .PARAMETER PodName Optional. Pod name. If the parameter is specified, the backing volume for the datastore will be created in the specified Pod. .PARAMETER NoDefaultProtection Optional. Bypass default protection group. If the parameter is specified, the datastore will not be protected by default protection group .PARAMETER PureCloudBlockStoreConnection Optional. Pure Cloud Block Store Connection. The connection can be created using Connect-Pf2Array cmdlet .PARAMETER AVSCloudName AVS cloud name .PARAMETER AVSResourceGroup AVS Resource group name .PARAMETER TimeoutInMinutes Optional. Timeout in minutes for RunCommand operations. .EXAMPLE New-PCBSVmfsDatastore -ClusterName myClusterName -PureCloudBlockStoreConnection $CBSConnection -DatastoreName MyVMFSStore -Size 4GB ` -AVSCloudName $AVSCloudName -AVSResourceGroup $AVSResourceGroup Create a datastore "MyVMFS" #> [CmdletBinding()] Param ( [Parameter(Mandatory=$true)] [String]$ClusterName, [Parameter(Mandatory=$true, ValueFromPipeline=$true)] [String]$DatastoreName, [ValidateRange(1GB,64TB)] # 1 GB to 64 TB [Parameter(Mandatory=$true)] [UInt64]$Size, [Parameter(Mandatory=$false)] [string]$PodName, [Parameter(Mandatory=$false)] [switch] $NoDefaultProtection, [Parameter(Mandatory=$false)] $PureCloudBlockStoreConnection, [Parameter(Mandatory=$true)] [String]$AVSCloudName, [Parameter(Mandatory=$true)] [String]$AVSResourceGroup, [Parameter(Mandatory=$false)] [ValidateRange(5, 60)] [int] $TimeoutInMinutes ) Write-Progress -Activity "Creating datastore" -Status "0% Complete:" -PercentComplete 1 $fa = Connect-PureCloudBlockStore -PureCloudBlockStoreConnection $PureCloudBlockStoreConnection $WorkflowID = New-WorkflowID New-PhoneHomeWorkflowLogEntry -RestClient $fa -Event "Begin" -ID $WorkflowID -Name $MyInvocation.MyCommand try { $vCenterServer = Connect-AVSvCenter -AVSResourceGroupName $AVSResourceGroup -AVSPrivateCloudName $AVSCloudName $Cluster = Get-Cluster -Server $vCenterServer -Name $ClusterName -ErrorAction Ignore if (-not $Cluster) { throw "Cluster $ClusterName does not exist." } $ClusterStatus = Get-AVSClusterProvisionStatus -AVSResourceGroupName $AVSResourceGroup -AVSPrivateCloudName $AVSCloudName -AvsClusterName $ClusterName if ($ClusterStatus -ne "Succeeded") { throw "The current provisioning status of cluster $ClusterName is $ClusterStatus. The operation can only proceed when provisioning status is 'Succeeded'" } $TimeoutInMinutes = Get-Timeout -Cluster $Cluster -InputParams $PSBoundParameters New-PfaVmfs -Cluster $Cluster -Flasharray $fa -Name $DatastoreName -vCenterServer $vCenterServer -Size $Size -NoDefaultProtection:$NoDefaultProtection.IsPresent ` -PodName $PodName -AVSCloudName $AVSCloudName -AVSResourceGroup $AVSResourceGroup -TimeoutInMinutes $TimeoutInMinutes Write-Host "Datastore '$DatastoreName' is successfully created" Write-Progress -Activity "Operation is done" -Status "100% Complete:" -Completed } catch { New-PhoneHomeWorkflowLogEntry -RestClient $fa -Event "Error" -ID $WorkflowID -Name $MyInvocation.MyCommand -ErrorMessage $_ throw } New-PhoneHomeWorkflowLogEntry -RestClient $fa -Event "Complete" -ID $WorkflowID -Name $MyInvocation.MyCommand Disconnect-VIServer -Server $vCenterServer -Confirm:$false -ErrorAction SilentlyContinue } function Restore-PCBSVmfsDatastore { <# .SYNOPSIS Mounts a copy of a VMFS datastore to a VMware cluster from a Pure Cloud Block Store snapshot, volume, or pod. .DESCRIPTION Takes in a snapshot/volume/pod name, the corresponding Pure Cloud Block Store, and a cluster. The VMFS copy will be resignatured and mounted. .PARAMETER ClusterName Cluster name .PARAMETER VolumeSnapshotName Volume snapshot name. A volume will be created from the volume snapshot. A datastore will be created from the volume .PARAMETER VolumeName Volume name. A datastore will be created from the volume. No volume copy will be created, the volume specified will be directly used for the datastore .PARAMETER ProtectionGroupSnapshotName Protection group snapshot name. All of volume snapshots of the protection group snapshot will be used for restoring. The snapshot will be skipped if not supported. .PARAMETER PodName Pod name. All of volumes of the pod will be used for restoring. The volume will be skipped if not supported .PARAMETER PureCloudBlockStoreConnection Optional. Pure Cloud Block Store Connection. The connection can be created using Connect-Pf2Array cmdlet .PARAMETER DatastoreName Optional. Datastore name. If the parameter is not specified, a generated name will be used. .PARAMETER AVSCloudName AVS cloud name .PARAMETER AVSResourceGroup AVS Resource group name .PARAMETER TimeoutInMinutes Optional. Timeout in minutes for RunCommand operations. .EXAMPLE Restore-PCBSVmfsDatastore -ClusterName myClusterName -VolumeSnapshotName mySnapshotName -PureCloudBlockStoreConnection $CBSConnection ` -AVSCloudName $AVSCloudName -AVSResourceGroup $AVSResourceGroup Takes in a snapshot name, the corresponding Pure Cloud Block Store, and a cluster. The VMFS copy will be resignatured and mounted. .EXAMPLE Restore-PCBSVmfsDatastore -ClusterName myClusterName -VolumeName myVolumeName -PureCloudBlockStoreConnection $CBSConnection ` -AVSCloudName $AVSCloudName -AVSResourceGroup $AVSResourceGroup Takes in a volume name, the corresponding Pure Cloud Block Store, and a cluster. The VMFS copy will be resignatured and mounted. #> [CmdletBinding()] Param ( [Parameter(Mandatory=$true)] [String]$ClusterName, [Parameter(Mandatory = $true, ParameterSetName = 'VolumeSnapshot')] [String]$VolumeSnapshotName, [Parameter(Mandatory = $true, ParameterSetName = 'Volume')] [String]$VolumeName, [Parameter(Mandatory = $true, ParameterSetName = 'ProtectionGroupSnapshot')] [String]$ProtectionGroupSnapshotName, [Parameter(Mandatory = $true, ParameterSetName = 'Pod')] [String]$PodName, [Parameter(Mandatory = $false, ParameterSetName = 'Volume')] [Parameter(Mandatory = $false, ParameterSetName = 'VolumeSnapshot')] [String]$DatastoreName, [Parameter(Mandatory=$false)] $PureCloudBlockStoreConnection, [Parameter(Mandatory=$true)] [String]$AVSCloudName, [Parameter(Mandatory = $true)] [String]$AVSResourceGroup, [Parameter(Mandatory = $false)] [ValidateRange(5, 60)] [int] $TimeoutInMinutes ) Write-Progress -Activity "Restoring datastore" -Status "0% Complete:" -PercentComplete 1 $fa = Connect-PureCloudBlockStore -PureCloudBlockStoreConnection $PureCloudBlockStoreConnection $WorkflowID = New-WorkflowID New-PhoneHomeWorkflowLogEntry -RestClient $fa -Event "Begin" -ID $WorkflowID -Name $MyInvocation.MyCommand try { $vCenterServer = Connect-AVSvCenter -AVSResourceGroupName $AVSResourceGroup -AVSPrivateCloudName $AVSCloudName $Cluster = Get-Cluster -Server $vCenterServer -Name $ClusterName -ErrorAction Ignore if (-not $Cluster) { throw "Cluster '$ClusterName' does not exist." } $ClusterStatus = Get-AVSClusterProvisionStatus -AVSResourceGroupName $AVSResourceGroup -AVSPrivateCloudName $AVSCloudName -AvsClusterName $ClusterName if ($ClusterStatus -ne "Succeeded") { throw "The current provisioning status of cluster $ClusterName is $ClusterStatus. The operation can only proceed when provisioning status is 'Succeeded'" } if (-not [string]::IsNullOrEmpty($DatastoreName)) { $Datastore = Get-Datastore -Server $vCenterServer -Name $DatastoreName -ErrorAction Ignore if ($Datastore) { throw "Datastore '$Datastore' already exists." } } $TimeoutInMinutes = Get-Timeout -Cluster $cluster -InputParams $PSBoundParameters switch ($PSCmdlet.ParameterSetName) { 'Volume' { Write-Host "Creating datastore from volume..." $NewDatastore = Restore-PfaVmfsFromVolume -FlashArray $fa -Cluster $Cluster -VolumeName $VolumeName -DatastoreName $DatastoreName ` -AVSCloudName $AVSCloudName -AVSResourceGroup $AVSResourceGroup -TimeoutInMinutes $TimeoutInMinutes break } 'VolumeSnapshot' { Write-Host "Creating datastore from volume snapshot..." $NewDatastore = Restore-PfaVmfsFromVolumeSnapshot -FlashArray $fa -Cluster $Cluster -VolumeSnapshotName $VolumeSnapshotName -DatastoreName $DatastoreName ` -AVSCloudName $AVSCloudName -AVSResourceGroup $AVSResourceGroup -TimeoutInMinutes $TimeoutInMinutes break } 'ProtectionGroupSnapshot' { Write-Host "Creating datastore from protection group snapshot..." $NewDatastore = Restore-PfaVmfsFromProtectionGroupSnapshot -FlashArray $fa -Cluster $Cluster -ProtectionGroupSnapshotName $ProtectionGroupSnapshotName ` -AVSCloudName $AVSCloudName -AVSResourceGroup $AVSResourceGroup -TimeoutInMinutes $TimeoutInMinutes break } 'Pod' { Write-Host "Creating datastore from pod..." $NewDatastore = Restore-PfaVmfsFromPod -FlashArray $fa -Cluster $Cluster -PodName $PodName ` -AVSCloudName $AVSCloudName -AVSResourceGroup $AVSResourceGroup -TimeoutInMinutes $TimeoutInMinutes break } } Write-Progress -Activity "Operation is done" -Status "100% Complete:" -Completed Write-Host "Datastore '$($NewDatastore.Name)' is successfully restored" } catch { New-PhoneHomeWorkflowLogEntry -RestClient $fa -Event "Error" -ID $WorkflowID -Name $MyInvocation.MyCommand -ErrorMessage $_ throw } New-PhoneHomeWorkflowLogEntry -RestClient $fa -Event "Complete" -ID $WorkflowID -Name $MyInvocation.MyCommand Disconnect-VIServer -Server $vCenterServer -Confirm:$false -ErrorAction SilentlyContinue return $NewDatastore } function Remove-PCBSVmfsDatastore { <# .SYNOPSIS Detaches and unmount a VMFS datastore from a cluster. Remove the datastore from the host group .DESCRIPTION Detaches and unmount a VMFS datastore from a cluster. Remove the datastore from the host group. The connection configured for the volume and cluster host/host group will be removed from Pure Cloud Block Store. The volume will be destroyed if there is no other connection left. .PARAMETER ClusterName Cluster name .PARAMETER DatastoreName Datastore name .PARAMETER PureCloudBlockStoreConnection Optional. Pure Cloud Block Store Connection. The connection can be created using Connect-Pf2Array cmdlet .PARAMETER AVSCloudName AVS cloud name .PARAMETER AVSResourceGroup AVS Resource group name .PARAMETER TimeoutInMinutes Optional. Timeout in minutes for RunCommand operations. .EXAMPLE Remove-PCBSVmfsDatastore -ClusterName "mycluster" -DatastoreName "myDatastore" PureCloudBlockStoreConnection $CBSConnection ` -AVSCloudName $AVSCloudName -AVSResourceGroup $AVSResourceGroup Takes in a datastore name, datastore would be removed. #> [CmdletBinding()] Param ( [Parameter(Mandatory=$true)] [String]$ClusterName, [Parameter(Mandatory=$true, ValueFromPipeline=$true)] [String]$DatastoreName, [Parameter(Mandatory=$false)] $PureCloudBlockStoreConnection, [Parameter(Mandatory=$true)] [String]$AVSCloudName, [Parameter(Mandatory=$true)] [String]$AVSResourceGroup, [Parameter(Mandatory=$false)] [ValidateRange(5, 60)] [int] $TimeoutInMinutes ) Write-Progress -Activity "Removing datastore" -Status "0% Complete:" -PercentComplete 1 $FlashArray = Connect-PureCloudBlockStore -PureCloudBlockStoreConnection $PureCloudBlockStoreConnection $WorkflowID = New-WorkflowID New-PhoneHomeWorkflowLogEntry -RestClient $FlashArray -Event "Begin" -ID $WorkflowID -Name $MyInvocation.MyCommand try { $vCenterServer = Connect-AVSvCenter -AVSResourceGroupName $AVSResourceGroup -AVSPrivateCloudName $AVSCloudName $Cluster = Get-Cluster -Server $vCenterServer -Name $ClusterName -ErrorAction Ignore if (-not $Cluster) { throw "Cluster '$ClusterName' does not exist." } $TimeoutInMinutes = Get-Timeout -Cluster $cluster -InputParams $PSBoundParameters $ClusterStatus = Get-AVSClusterProvisionStatus -AVSResourceGroupName $AVSResourceGroup -AVSPrivateCloudName $AVSCloudName -AvsClusterName $ClusterName if ($ClusterStatus -ne "Succeeded") { throw "The current provisioning status of cluster $ClusterName is $ClusterStatus. The operation can only proceed when provisioning status is 'Succeeded'" } $Datastore = Get-Datastore -Server $vCenterServer -Name $DatastoreName -ErrorAction Ignore if (-not $Datastore) { throw "Datastore '$DatastoreName' does not exist." } Remove-PfaVmfsDatastore -Cluster $Cluster -Datastore $DataStore -FlashArray $FlashArray ` -AVSCloudName $AVSCloudName -AVSResourceGroup $AVSResourceGroup -TimeoutInMinutes $TimeoutInMinutes Write-Host "Datastore '$DatastoreName' is successfully removed" Write-Progress -Activity "Operation is done" -Status "100% Complete:" -Completed } catch { New-PhoneHomeWorkflowLogEntry -RestClient $FlashArray -Event "Error" -ID $WorkflowID -Name $MyInvocation.MyCommand -ErrorMessage $_ throw } New-PhoneHomeWorkflowLogEntry -RestClient $FlashArray -Event "Complete" -ID $WorkflowID -Name $MyInvocation.MyCommand Disconnect-VIServer -Server $vCenterServer -Confirm:$false -ErrorAction SilentlyContinue } function Set-PCBSVmfsCapacity { <# .SYNOPSIS Increase the size of a Pure Cloud Block Store-based VMFS datastore. .DESCRIPTION Takes in a datastore, the corresponding Pure Cloud Block Store, and a new size. Both the volume and the VMFS will be grown. .PARAMETER ClusterName Cluster name .PARAMETER DatastoreName Datastore name .PARAMETER Size New datastore capacity size in bytes .PARAMETER PureCloudBlockStoreConnection Optional. Pure Cloud Block Store Connection. The connection can be created using Connect-Pf2Array cmdlet .PARAMETER AVSCloudName AVS cloud name .PARAMETER AVSResourceGroup AVS Resource group name .PARAMETER TimeoutInMinutes Optional. Timeout in minutes for RunCommand operations. .EXAMPLE Set-PCBSVmfsCapacity -ClusterName "mycluster" -DatastoreName myDatastore -Size 3GB -PureCloudBlockStoreConnection $CBSConnection ` -AVSCloudName $AVSCloudName -AVSResourceGroup $AVSResourceGroup Expand size of the datastore myDatastore to 3GB. #> [CmdletBinding()] Param ( [Parameter(Mandatory=$true)] [String]$ClusterName, [Parameter(Mandatory=$true, ValueFromPipeline=$true)] [string]$DatastoreName, [ValidateRange(1GB,64TB)] # 1 GB to 64 TB [Parameter(Mandatory=$true)] [UInt64]$Size, [Parameter(Mandatory=$false)] $PureCloudBlockStoreConnection, [Parameter(Mandatory=$true)] [String]$AVSCloudName, [Parameter(Mandatory=$true)] [String]$AVSResourceGroup, [Parameter(Mandatory=$false)] [ValidateRange(5, 60)] [int]$TimeoutInMinutes ) Write-Progress -Activity "Resizing datastore" -Status "0% Complete:" -PercentComplete 1 $FlashArray = Connect-PureCloudBlockStore -PureCloudBlockStoreConnection $PureCloudBlockStoreConnection $WorkflowID = New-WorkflowID New-PhoneHomeWorkflowLogEntry -RestClient $FlashArray -Event "Begin" -ID $WorkflowID -Name $MyInvocation.MyCommand try { $vCenterServer = Connect-AVSvCenter -AVSResourceGroupName $AVSResourceGroup -AVSPrivateCloudName $AVSCloudName $Cluster = Get-Cluster -Server $vCenterServer -Name $ClusterName -ErrorAction Ignore if (-not $Cluster) { throw "Cluster '$ClusterName' does not exist." } $TimeoutInMinutes = Get-Timeout -Cluster $cluster -InputParams $PSBoundParameters $ClusterStatus = Get-AVSClusterProvisionStatus -AVSResourceGroupName $AVSResourceGroup -AVSPrivateCloudName $AVSCloudName -AvsClusterName $ClusterName if ($ClusterStatus -ne "Succeeded") { throw "The current provisioning status of cluster $ClusterName is $ClusterStatus. The operation can only proceed when provisioning status is 'Succeeded'" } $Datastore = Get-Datastore -Server $vCenterServer -Name $DatastoreName -ErrorAction SilentlyContinue if (-not $Datastore) { throw "Could not find datastore '$DatastoreName'! Please make sure to select an existing datastore." } Set-PfaVmfsCapacity -Cluster $Cluster -FlashArray $FlashArray -Datastore $Datastore -SizeInByte $Size ` -AVSCloudName $AVSCloudName -AVSResourceGroup $AVSResourceGroup -TimeoutInMinutes $TimeoutInMinutes Write-Host "Datastore '$DatastoreName' is successfully resized" Write-Progress -Activity "Operation is done" -Status "100% Complete:" -Completed } catch { New-PhoneHomeWorkflowLogEntry -RestClient $FlashArray -Event "Error" -ID $WorkflowID -Name $MyInvocation.MyCommand -ErrorMessage $_ throw } New-PhoneHomeWorkflowLogEntry -RestClient $FlashArray -Event "Complete" -ID $WorkflowID -Name $MyInvocation.MyCommand Disconnect-VIServer -Server $vCenterServer -Confirm:$false -ErrorAction SilentlyContinue } function Sync-PCBSClusterVMHostStorage { <# .SYNOPSIS Refresh the storage of all hosts in a cluster .DESCRIPTION Refresh the storage of all hosts in a cluster. This operation is useful when the storage of the hosts is out of sync with the storage of the cluster. For example, a new host is added to the cluster, but the existing datastore does not appear on the new host. .PARAMETER ClusterName Cluster name .PARAMETER AVSCloudName AVS cloud name .PARAMETER AVSResourceGroup AVS Resource group name .PARAMETER TimeoutInMinutes Optional. Timeout in minutes for RunCommand operations. .EXAMPLE Sync-PCBSClusterVMHostStorage -ClusterName "mycluster" -AVSCloudName $AVSCloudName -AVSResourceGroup $AVSResourceGroup Expand size of the datastore myDatastore to 3GB. #> [CmdletBinding()] Param ( [Parameter(Mandatory = $true)] [String]$ClusterName, [Parameter(Mandatory = $true)] [String]$AVSCloudName, [Parameter(Mandatory = $true)] [String]$AVSResourceGroup, [Parameter(Mandatory = $false)] [ValidateRange(5, 60)] [int]$TimeoutInMinutes ) Write-Progress -Activity "Syncing cluster VM host storage" -Status "0% Complete:" -PercentComplete 1 $params = @{ ClusterName = $ClusterName; } Invoke-RunScript -RunCommandName "Sync-ClusterVMHostStorage" -RunCommandModule "Microsoft.AVS.VMFS" -Parameters $params ` -AVSCloudName $AVSCloudName -AVSResourceGroup $AVSResourceGroup -TimeoutInMinutes $TimeoutInMinutes Write-Progress -Activity "Operation is done" -Status "100% Complete:" -Completed } function New-PCBSVASAProvider { <# .SYNOPSIS Register Pure Cloud Block Store VASA providers with vCenter .DESCRIPTION Register Pure Cloud Block Store VASA providers for all array controllers with vCenter. If a VASA provider has a certificate problem, it will be created .PARAMETER PureCloudBlockStoreConnection Optional. Pure Cloud Block Store Connection. The connection can be created using Connect-Pf2Array cmdlet .PARAMETER PureCloudBlockStoreCredential Pure Cloud BlockStore Credential .PARAMETER AVSCloudName AVS cloud name .PARAMETER AVSResourceGroup AVS Resource group name .PARAMETER TimeoutInMinutes Optional. Timeout in minutes for RunCommand operations. .EXAMPLE New-PCBSVASAProvider -PureCloudBlockStoreConnection $CBSConnection -PureCloudBlockStoreCredential $CBSConnectionCredential ` -AVSCloudName $AVSCloudName -AVSResourceGroup $AVSResourceGroup #> [CmdletBinding()] Param( [Parameter(Mandatory=$false)] $PureCloudBlockStoreConnection, [Parameter(Mandatory=$true)] [PSCredential]$PureCloudBlockStoreCredential, [Parameter(Mandatory=$true)] [String]$AVSCloudName, [Parameter(Mandatory=$true)] [String]$AVSResourceGroup, [Parameter(Mandatory=$false)] [ValidateRange(5, 60)] [int]$TimeoutInMinutes = 10 ) Write-Warning $VVOL_WARNIING Write-Progress -Activity "Register VASA provider" -Status "0% Complete:" -PercentComplete 1 $FlashArray = Connect-PureCloudBlockStore -PureCloudBlockStoreConnection $PureCloudBlockStoreConnection $WorkflowID = New-WorkflowID New-PhoneHomeWorkflowLogEntry -RestClient $FlashArray -Event "Begin" -ID $WorkflowID -Name $MyInvocation.MyCommand try { $vCenterServer = Connect-AVSvCenter -AVSResourceGroupName $AVSResourceGroup -AVSPrivateCloudName $AVSCloudName Update-VASAProvider -vCenterServer $vCenterServer -FlashArray $FlashArray -FlashArrayCredential $PureCloudBlockStoreCredential ` -AVSCloudName $AVSCloudName -AVSResourceGroup $AVSResourceGroup -TimeoutInMinutes $TimeoutInMinutes }catch { New-PhoneHomeWorkflowLogEntry -RestClient $FlashArray -Event "Error" -ID $WorkflowID -Name $MyInvocation.MyCommand -ErrorMessage $_ throw } New-PhoneHomeWorkflowLogEntry -RestClient $FlashArray -Event "Complete" -ID $WorkflowID -Name $MyInvocation.MyCommand Write-Host "VASA provider is successfully registered." Write-Progress -Activity "Operation is done" -Status "100% Complete:" -Completed Disconnect-VIServer -Server $vCenterServer -Confirm:$false -ErrorAction SilentlyContinue } function Update-PCBSVASAProvider { <# .SYNOPSIS Refresh Pure Cloud Block Store VASA provider vCenter certificates .DESCRIPTION Refresh Pure Cloud Block Store VASA provider vCenter certificates .PARAMETER PureCloudBlockStoreConnection Optional. Pure Cloud Block Store Connection. The connection can be created using Connect-Pf2Array cmdlet .PARAMETER PureCloudBlockStoreCredential Pure Cloud BlockStore Credential .PARAMETER AVSCloudName AVS cloud name .PARAMETER AVSResourceGroup AVS Resource group name .PARAMETER TimeoutInMinutes Optional. Timeout in minutes for RunCommand operations. .EXAMPLE Update-PCBSVASAProvider -PureCloudBlockStoreConnection $CBSConnection -PureCloudBlockStoreCredential $CBSConnectionCredential ` -AVSCloudName $AVSCloudName -AVSResourceGroup $AVSResourceGroup #> [CmdletBinding()] Param( [Parameter(Mandatory=$false)] $PureCloudBlockStoreConnection, [Parameter(Mandatory=$true)] [PSCredential]$PureCloudBlockStoreCredential, [Parameter(Mandatory=$true)] [String]$AVSCloudName, [Parameter(Mandatory=$true)] [String]$AVSResourceGroup, [Parameter(Mandatory=$false)] [ValidateRange(5, 60)] [int]$TimeoutInMinutes = 10 ) Write-Warning $VVOL_WARNIING Write-Progress -Activity "Update VASA provider" -Status "0% Complete:" -PercentComplete 1 $FlashArray = Connect-PureCloudBlockStore -PureCloudBlockStoreConnection $PureCloudBlockStoreConnection $WorkflowID = New-WorkflowID New-PhoneHomeWorkflowLogEntry -RestClient $FlashArray -Event "Begin" -ID $WorkflowID -Name $MyInvocation.MyCommand try { $vCenterServer = Connect-AVSvCenter -AVSResourceGroupName $AVSResourceGroup -AVSPrivateCloudName $AVSCloudName Update-VASAProvider -vCenterServer $vCenterServer -FlashArray $FlashArray -FlashArrayCredential $PureCloudBlockStoreCredential -RefreshOnly ` -AVSResourceGroup $AVSResourceGroup -AVSCloudName $AVSCloudName -TimeoutInMinutes $TimeoutInMinutes }catch { New-PhoneHomeWorkflowLogEntry -RestClient $FlashArray -Event "Error" -ID $WorkflowID -Name $MyInvocation.MyCommand -ErrorMessage $_ throw } New-PhoneHomeWorkflowLogEntry -RestClient $FlashArray -Event "Complete" -ID $WorkflowID -Name $MyInvocation.MyCommand Write-Host "VASA provider is successfully updated." Write-Progress -Activity "Operation is done" -Status "100% Complete:" -Completed Disconnect-VIServer -Server $vCenterServer -Confirm:$false -ErrorAction SilentlyContinue } function New-PCBSVvolDataStore { <# .SYNOPSIS Mount a vVol datastore on all of the hosts in the cluster. .DESCRIPTION Mount a vVol datastore on all of the hosts in the cluster. .PARAMETER ClusterName Cluster name .PARAMETER DatastoreName Optional. Datastore name. If not provided, a datastore name driven from the Pure Cloud Clock Store Name will be used .PARAMETER UseDefaultStore Optional. Indicates whether to use default root container. If not provided, the default store will not be used and multi-storage Pod container will be used. Note that Non-default store can only be used with Pure Cloud Block Store version 6.4.1 or later. .PARAMETER PureCloudBlockStoreConnection Optional. Pure Cloud Block Store Connection. The connection can be created using Connect-Pf2Array cmdlet .PARAMETER AVSCloudName AVS cloud name .PARAMETER AVSResourceGroup AVS resource group name .PARAMETER TimeoutInMinutes Optional. Timeout in minutes for RunCommand operations. .EXAMPLE New-PCBSVVolDataStore -ClusterName "mycluster" -DatastoreName "myDatastore" -PureCloudBlockStoreConnection $CBSConnection ` -AVSCloudName $AVSCloudName -AVSResourceGroup $AVSResourceGroup #> [CmdletBinding()] Param( [Parameter(Mandatory=$true)] [String]$ClusterName, [Parameter(Mandatory = $true)] [String]$DatastoreName, [Parameter(Mandatory=$false)] [bool]$UseDefaultStore, [Parameter(Mandatory=$false)] $PureCloudBlockStoreConnection, [Parameter(Mandatory=$true)] [String]$AVSCloudName, [Parameter(Mandatory=$true)] [String]$AVSResourceGroup, [Parameter(Mandatory=$false)] [ValidateRange(5, 60)] [int]$TimeoutInMinutes ) Write-Warning $VVOL_WARNIING Write-Progress -Activity "Add datastore" -Status "0% Complete:" -PercentComplete 1 $FlashArray = Connect-PureCloudBlockStore -PureCloudBlockStoreConnection $PureCloudBlockStoreConnection $WorkflowID = New-WorkflowID New-PhoneHomeWorkflowLogEntry -RestClient $FlashArray -Event "Begin" -ID $WorkflowID -Name $MyInvocation.MyCommand try { $vCenterServer = Connect-AVSvCenter -AVSResourceGroupName $AVSResourceGroup -AVSPrivateCloudName $AVSCloudName $Cluster = Get-Cluster -Server $vCenterServer -Name $ClusterName -ErrorAction Ignore if (-not $Cluster) { throw "Cluster $ClusterName does not exist." } $ClusterStatus = Get-AVSClusterProvisionStatus -AVSResourceGroupName $AVSResourceGroup -AVSPrivateCloudName $AVSCloudName -AvsClusterName $ClusterName if ($ClusterStatus -ne "Succeeded") { throw "The current provisioning status of cluster $ClusterName is $ClusterStatus. The operation can only proceed when provisioning status is 'Succeeded'" } $TimeoutInMinutes = Get-Timeout -Cluster $cluster -InputParams $PSBoundParameters $datastore = Mount-VvolDatastore -vCenterServer $vCenterServer -DatastoreName $DatastoreName -Cluster $Cluster -FlashArray $FlashArray -UseDefaultStore $UseDefaultStore ` -AVSCloudName $AVSCloudName -AVSResourceGroup $AVSResourceGroup -TimeoutInMinutes $TimeoutInMinutes # Run Repair-HAConfiguration to resolve an issue where datastore becomes inaccessible $params = @{ "ClusterName" = $ClusterName } Invoke-RunScript -RunCommandName "Repair-HAConfiguration" -RunCommandModule "Microsoft.AVS.VMFS" -Parameters $params ` -AVSCloudName $AVSCloudName -AVSResourceGroup $AVSResourceGroup -TimeoutInMinutes $TimeoutInMinutes Wait-VvolDatastoreCreation -DataStoreName $datastore.Name } catch { New-PhoneHomeWorkflowLogEntry -RestClient $FlashArray -Event "Error" -ID $WorkflowID -Name $MyInvocation.MyCommand -ErrorMessage $_ throw } New-PhoneHomeWorkflowLogEntry -RestClient $FlashArray -Event "Complete" -ID $WorkflowID -Name $MyInvocation.MyCommand Write-Host "Datastore '$($datastore.Name)' is successfully created" Write-Progress -Activity "Operation is done" -Status "100% Complete:" -Completed Disconnect-VIServer -Server $vCenterServer -Confirm:$false -ErrorAction SilentlyContinue } function Remove-PCBSVVolDataStore { <# .SYNOPSIS Remove a vVol datastore from all of the hosts in the cluster. .DESCRIPTION Remove a vVol datastore from all of the hosts in the cluster. .PARAMETER ClusterName Cluster name .PARAMETER DatastoreName Datastore name .PARAMETER PureCloudBlockStoreConnection Optional. Pure Cloud Block Store Connection. The connection can be created using Connect-Pf2Array cmdlet .PARAMETER AVSCloudName AVS Cloud Name .PARAMETER AVSResourceGroup AVS Resource Group .PARAMETER TimeoutInMinutes Optional. Timeout in minutes for RunCommand operations. .EXAMPLE Remove-PCBSVVolDataStore -ClusterName "mycluster" -DatastoreName "myDatastore" -PureCloudBlockStoreConnection $CBSConnection ` -AVSCloudName $AVSCloudName -AVSResourceGroup $AVSResourceGroup #> [CmdletBinding()] Param( [Parameter(Mandatory=$true)] [String]$ClusterName, [Parameter(Mandatory=$true)] [String]$DatastoreName, [Parameter(Mandatory=$false)] $PureCloudBlockStoreConnection, [Parameter(Mandatory=$true)] [String]$AVSCloudName, [Parameter(Mandatory=$true)] [String]$AVSResourceGroup, [Parameter(Mandatory=$false)] [ValidateRange(5, 60)] [int]$TimeoutInMinutes ) Write-Warning $VVOL_WARNIING Write-Progress -Activity "Remove datastore" -Status "0% Complete:" -PercentComplete 1 $FlashArray = Connect-PureCloudBlockStore -PureCloudBlockStoreConnection $PureCloudBlockStoreConnection $WorkflowID = New-WorkflowID New-PhoneHomeWorkflowLogEntry -RestClient $FlashArray -Event "Begin" -ID $WorkflowID -Name $MyInvocation.MyCommand try { $vCenterServer = Connect-AVSvCenter -AVSResourceGroupName $AVSResourceGroup -AVSPrivateCloudName $AVSCloudName $Cluster = Get-Cluster -Server $vCenterServer -Name $ClusterName -ErrorAction Ignore if (-not $Cluster) { throw "Cluster $ClusterName does not exist." } $ClusterStatus = Get-AVSClusterProvisionStatus -AVSResourceGroupName $AVSResourceGroup -AVSPrivateCloudName $AVSCloudName -AvsClusterName $ClusterName if ($ClusterStatus -ne "Succeeded") { throw "The current provisioning status of cluster $ClusterName is $ClusterStatus. The operation can only proceed when provisioning status is 'Succeeded'" } $TimeoutInMinutes = Get-Timeout -Cluster $cluster -InputParams $PSBoundParameters $vCenterServer = Connect-AVSvCenter -AVSResourceGroupName $AVSResourceGroup -AVSPrivateCloudName $AVSCloudName Dismount-VvolDatastore -vCenterServer $vCenterServer -DatastoreName $DatastoreName -Cluster $Cluster -FlashArray $FlashArray ` -AVSCloudName $AVSCloudName -AVSResourceGroup $AVSResourceGroup -TimeoutInMinutes $TimeoutInMinutes Write-Host "Datastore '$DatastoreName' is successfully removed" } catch { New-PhoneHomeWorkflowLogEntry -RestClient $FlashArray -Event "Error" -ID $WorkflowID -Name $MyInvocation.MyCommand -ErrorMessage $_ throw } Write-Progress -Activity "Operation is done" -Status "100% Complete:" -Completed Disconnect-VIServer -Server $vCenterServer -Confirm:$false -ErrorAction SilentlyContinue New-PhoneHomeWorkflowLogEntry -RestClient $FlashArray -Event "Complete" -ID $WorkflowID -Name $MyInvocation.MyCommand } function New-PCBSStoragePolicy { <# .SYNOPSIS Creates a new Pure Cloud Block Store vVol Storage Policy .DESCRIPTION Creates a new Pure Cloud Block Store vVol Storage Policy with specified capabilities .INPUTS Capabilities .OUTPUTS New storage policy .PARAMETER PolicyName Pure Cloud Block Store vVol Storage Policy name. .PARAMETER PolicyDescription Optional. Pure Cloud Block Store vVol Storage Policy description. .PARAMETER SourcePureCloudBlockStores Optional. Name of one or more Pure Cloud Block Store to use. Use comma as seperater if there are multiple names .PARAMETER ReplicationEnabled Optional. Replication capability rule. The input value could be yes, no or empty, If not provided replication will not be enabled. .PARAMETER ReplicationInterval Optional. Replication capability rule. Protection group remote snapshot creation interval in seconds. .PARAMETER ReplicationRetentionInterval Optional. Replication capability rule. Protection group remote snapshot retention interval in seconds. .PARAMETER ReplicationRuleLocalSnapshotEnabled Optional. Replication capability rule. The input value could be yes, no or empty. .PARAMETER ReplicationRuleLocalSnapshotInterval Optional. Replication capability rule. Protection group local snapshot creation interval in seconds. .PARAMETER ReplicationRuleLocalSnapshotRetentionInterval Optional. Replication capability rule. Protection group local snapshot retention interval in seconds. .PARAMETER ReplicationConcurrency Optional. Replication capability rule. The number of target Pure Cloud Block Store to replicate to at once. .PARAMETER ReplicationRansomwareProtection Optional. Replication capability rule. Protection group ransomware protection .PARAMETER TargetPureCloudBlockStores Optional. Replication capability rule. Names of specific Pure Cloud Block Store desired as replication targets. Use comma as seperater if there are multiple names .PARAMETER ConsistencyGroupName Optional. Replication capability rule. A Pure Cloud Block Store protection group name .PARAMETER PerVirtualDiskIOPSLimit Optional. QoS placement capability rule. IOPS limit get applied to per virtual disk .PARAMETER PerVirtualDiskIOPSLimitUnit Optional. QoS placement capability rule. IOPS limit unit (K, M, or -) .PARAMETER PerVirtualDiskBandwidthLimit Optional. QoS placement capability rule. Bandwidth limit get applied to per virtual disk .PARAMETER PerVirtualDiskBandwidthLimitUnit Optional. QoS placement capability rule. Bandwidth limit unit (KB/s, MB/s or GB/s) .PARAMETER VolumeTaggingKey Optional. Volume tagging placement capability. Volumes with this policy will be tagged with the key .PARAMETER VolumeTaggingValue Optional. Volume tagging placement capability. Volumes with this policy will be tagged with the value .PARAMETER VolumeTaggingCopyable Optional. Volume tagging placement capability. When set to yes, any volume copies of this volume will receive the tag .PARAMETER PlacementRuleLocalSnapshotInterval Optional. Local snapshot protection placement capability. Snapshots will be taken in the specified interval in seconds. .PARAMETER PlacementRuleLocalSnapshotRetentionInterval Optional. Local snapshot protection placement capability. Snapshots will be retained for the timespan in seconds. .PARAMETER PlacementRuleLocalSnapshotRetainAdditionalSnapshots Optional. Local snapshot protection placement capability. Optional additional retention: After the retention timespan specified above is up, a number of snapshots will be selected, evenly spaced out, from the last retention timespan. This parameter specifies the number of snapshots to select from the last retention timespan. These snapshots will be retained for a number of additional days (see parameter below). Leave this parameter as "0" if additional retention is not required. .PARAMETER PlacementRuleLocalSnapshotRetainAdditionalDays Optional. Local snapshot protection placement capability. Optional additional retention: This parameter specifies the number of days that additional snapshots should be retained. If a selection for "Retain additional snapshots" (see above) was made, this parameter must also be specified. Leave this parameter as "0" if additional retention is not required. .PARAMETER OffloadType Optional. Snapshot offload protection placement capability. Purity//FA Snap to Cloud is a policy-based solution to manage portable snapshots through the offload of volume snapshots to a target, such as an Azure Blob container, or an S3 bucket, for long-term retention. With different types of offload targets this rule allows policies to specify a specific offload target type or to have no requirement for a specific type of target. .PARAMETER OffloadTargetNames Optional. Snapshot offload protection placement capability. Upon the first assignment of this policy to a VM on a given datastore a consistency group will be created with the settings supplied. This rule specifies which targets are added to the consistency groups target list. If no requirement is selected then the storage provider will selected one of the available offload targets on the compatible datastore's array. .PARAMETER OffloadReplicationInterval Optional. Snapshot offload protection placement capability. Snapshots will be taken and replicated to the offload target in the specified interval. .PARAMETER OffloadOffloadReplicationTime Optional. Snapshot offload protection placement capability. If the replication interval is set to one or more days, optionally set the 'Daily/weekly replication time' to specify the preferred hour of each day when Purity//FA replicates the snapshot. For example, if the replication schedule is set to "Replicate every 4 days at 6pm," Purity//FA replicates the snapshots every four days at or around 6:00 p.m. .PARAMETER OffloadRetentionInterval Optional. Snapshot offload protection placement capability. Replicated snapshots will be retained for the specified timespan on the offload target. .PARAMETER OffloadRetainAdditionalSnapshots Optional. Snapshot offload protection placement capability. After the retention timespan specified is up, a number of snapshots will be selected, evenly spaced out, from the last retention timespan. This parameter specifies the number of snapshots to select from the last retention timespan. These snapshots will be retained for a number of additional days (see parameter below). Leave this parameter as "0" if additional retention is not required. .PARAMETER OffloadRetainAdditionalDays Optional. Snapshot offload protection placement capability. This parameter specifies the number of days that additional snapshots should be retained. If a selection for "Retain additional snapshots" (see above) was made, this parameter must also be specified. Leave this parameter as "0" if additional retention is not required. .PARAMETER OffloadReplicationBlackoutFrom Optional. Snapshot offload protection placement capability. Define a timespan for which replication is suspended or "blacked out". The asynchronous replication process stops during the blackout period. When the blackout period starts, replication processes that are still in progress will not be interrupted. Instead, Purity//FA will wait until the in-progress snapshot replication is complete before it observes the blackout period. This value dictates when the blackout period begins. .PARAMETER OffloadReplicationBlackoutTo Optional. Snapshot offload protection placement capability. Define a timespan for which replication is suspended or "blacked out". The asynchronous replication process stops during the blackout period. When the blackout period starts, replication processes that are still in progress will not be interrupted. Instead, Purity//FA will wait until the in-progress snapshot replication is complete before it observes the blackout period. This value dictates when the blackout period ends and replication jobs can be initiated again. .PARAMETER DefaultProtectionOptout Optional. Default protection opt-out placement capability. Whether should new volumes be placed in default protection groups. If not provided, new volumes will be placed in default protection groups. .PARAMETER AVSCloudName AVS Cloud Name .PARAMETER AVSResourceGroup AVS Resource Group .PARAMETER TimeoutInMinutes Optional. Timeout in minutes for RunCommand operations. .EXAMPLE New-PCBSStoragePolicy Creates the default SPBM policy that indicates a VM should be on a Pure Cloud Block Store using vVols. Default generated name and description. .EXAMPLE New-PCBSStoragePolicy -PolicyName myGreatPolicy ` -AVSCloudName $AVSCloudName -AVSResourceGroup $AVSResourceGroup Creates a SPBM policy with the specified name that indicates a VM should be on a Pure Cloud Block Store using vVols. Default generated description. .EXAMPLE New-PCBSStoragePolicy -PolicyName myGreatReplicationPolicy -ReplicationInterval 7200 -ReplicationEnabled "yes" -ReplicationConcurrency 2 ` -AVSCloudName $AVSCloudName -AVSResourceGroup $AVSResourceGroup Creates a replication-type SPBM policy with the specified name that indicates a VM should be on a Pure Cloud Block Store using vVols, replicated every 2 hours to at least two other Pure Cloud Block Stores. Default generated description. .EXAMPLE New-PCBSStoragePolicy -PolicyName myGreatReplicationPolicy -SourcePureCloudBlockStores "MyArrayOne,MyArrayTwo" ` -AVSCloudName $AVSCloudName -AVSResourceGroup $AVSResourceGroup Creates a SPBM policy with the specified name that indicates a VM should be on the specific Pure Cloud Block Store using vVols. #> [CmdletBinding()] Param( [Parameter(Mandatory=$true)] [String]$PolicyName, [Parameter(Mandatory=$false)] [String]$PolicyDescription = "Pure Storage Cloud Block Store vVol storage policy default description", [Parameter(Mandatory=$false)] [string[]]$SourcePureCloudBlockStores, [Parameter(Mandatory=$false)] [Nullable[boolean]]$ReplicationEnabled, [Parameter(Mandatory=$false)] [Timespan]$ReplicationInterval, [Parameter(Mandatory=$false)] [Timespan]$ReplicationRetentionInterval, [Parameter(Mandatory=$false)] [Nullable[boolean]]$ReplicationRuleLocalSnapshotEnabled, [Parameter(Mandatory=$false)] [Timespan]$ReplicationRuleLocalSnapshotInterval, [Parameter(Mandatory=$false)] [Timespan]$ReplicationRuleLocalSnapshotRetentionInterval, [Parameter(Mandatory=$false)] [int]$ReplicationConcurrency, [Parameter(Mandatory=$false)] [Nullable[boolean]]$ReplicationRansomwareProtection, [Parameter(Mandatory=$false)] [string[]]$TargetPureCloudBlockStores, [Parameter(Mandatory=$false)] [String]$ConsistencyGroupName, [Parameter(Mandatory=$false)] [int]$PerVirtualDiskIOPSLimit, [Parameter(Mandatory=$false)] [String]$PerVirtualDiskIOPSLimitUnit, [Parameter(Mandatory=$false)] [int]$PerVirtualDiskBandwidthLimit, [Parameter(Mandatory=$false)] [String]$PerVirtualDiskBandwidthLimitUnit, [Parameter(Mandatory=$false)] [String]$VolumeTaggingKey, [Parameter(Mandatory=$false)] [String]$VolumeTaggingValue, [Parameter(Mandatory=$false)] [Nullable[boolean]]$VolumeTaggingCopyable, [Parameter(Mandatory=$false)] [Timespan]$PlacementRuleLocalSnapshotInterval, [Parameter(Mandatory=$false)] [Timespan]$PlacementRuleLocalSnapshotRetentionInterval, [Parameter(Mandatory=$false)] [int]$PlacementRuleLocalSnapshotRetainAdditionalSnapshots, [Parameter(Mandatory=$false)] [int]$PlacementRuleLocalSnapshotRetainAdditionalDays, [Parameter(Mandatory=$false)] [ValidateSet("s3", "nfs", "azure")] [string]$OffloadType, [Parameter(Mandatory=$false)] [string[]]$OffloadTargetNames, [Parameter(Mandatory=$false)] [Timespan]$OffloadReplicationInterval, [Parameter(Mandatory=$false)] [ValidateSet("-", "12am", "1am", "2am", "3am", "4am", "5am", "6am", "7am", "8am", "9am", "10am", "11am", "12pm", "1pm", "2pm", "3pm", "4pm", "5pm", "6pm", "7pm", "8pm", "9pm", "10pm", "11pm")] [string]$OffloadOffloadReplicationTime, [Parameter(Mandatory=$false)] [Timespan]$OffloadRetentionInterval, [Parameter(Mandatory=$false)] [int]$OffloadRetainAdditionalSnapshots, [Parameter(Mandatory=$false)] [int]$OffloadRetainAdditionalDays, [Parameter(Mandatory=$false)] [ValidateSet("-", "12am", "1am", "2am", "3am", "4am", "5am", "6am", "7am", "8am", "9am", "10am", "11am", "12pm", "1pm", "2pm", "3pm", "4pm", "5pm", "6pm", "7pm", "8pm", "9pm", "10pm", "11pm")] [string]$OffloadReplicationBlackoutFrom, [Parameter(Mandatory=$false)] [ValidateSet("-", "12am", "1am", "2am", "3am", "4am", "5am", "6am", "7am", "8am", "9am", "10am", "11am", "12pm", "1pm", "2pm", "3pm", "4pm", "5pm", "6pm", "7pm", "8pm", "9pm", "10pm", "11pm")] [string]$OffloadReplicationBlackoutTo, [Parameter(Mandatory=$false)] [Nullable[boolean]]$DefaultProtectionOptout, [Parameter(Mandatory=$true)] [String]$AVSCloudName, [Parameter(Mandatory=$true)] [String]$AVSResourceGroup, [Parameter(Mandatory=$false)] [ValidateRange(5, 60)] [int]$TimeoutInMinutes = 10 ) Write-Warning $VVOL_WARNIING Write-Progress -Activity "Create storage policy" -Status "0% Complete:" -PercentComplete 1 $vCenterServer = Connect-AVSvCenter -AVSResourceGroupName $AVSResourceGroup -AVSPrivateCloudName $AVSCloudName New-VvolStoragePolicy -PolicyName $PolicyName -PolicyDescription $PolicyDescription -SourcePureCloudBlockStores $SourcePureCloudBlockStores -ReplicationEnabled $ReplicationEnabled -ReplicationInterval $ReplicationInterval -ReplicationRetentionInterval $ReplicationRetentionInterval ` -ReplicationRuleLocalSnapshotEnabled $ReplicationRuleLocalSnapshotEnabled -ReplicationRuleLocalSnapshotInterval $ReplicationRuleLocalSnapshotInterval -ReplicationRuleLocalSnapshotRetentionInterval $ReplicationRuleLocalSnapshotRetentionInterval -ReplicationConcurrency $ReplicationConcurrency -ReplicationRansomwareProtection $ReplicationRansomwareProtection ` -TargetPureCloudBlockStores $TargetPureCloudBlockStores -ConsistencyGroupName $ConsistencyGroupName -PerVirtualDiskIOPSLimit $PerVirtualDiskIOPSLimit -PerVirtualDiskIOPSLimitUnit $PerVirtualDiskIOPSLimitUnit ` -PerVirtualDiskBandwidthLimit $PerVirtualDiskBandwidthLimit -PerVirtualDiskBandwidthLimitUnit $PerVirtualDiskBandwidthLimitUnit -VolumeTaggingKey $VolumeTaggingKey -VolumeTaggingValue $VolumeTaggingValue -VolumeTaggingCopyable $VolumeTaggingCopyable ` -PlacementRuleLocalSnapshotInterval $PlacementRuleLocalSnapshotInterval -PlacementRuleLocalSnapshotRetentionInterval $PlacementRuleLocalSnapshotRetentionInterval -PlacementRuleLocalSnapshotRetainAdditionalSnapshots $PlacementRuleLocalSnapshotRetainAdditionalSnapshots -PlacementRuleLocalSnapshotRetainAdditionalDays $PlacementRuleLocalSnapshotRetainAdditionalDays ` -OffloadType $OffloadType -OffloadTargetNames $OffloadTargetNames -OffloadReplicationInterval $OffloadReplicationInterval -OffloadOffloadReplicationTime $OffloadOffloadReplicationTime -OffloadRetentionInterval $OffloadRetentionInterval -OffloadRetainAdditionalSnapshots $OffloadRetainAdditionalSnapshots -OffloadRetainAdditionalDays $OffloadRetainAdditionalDays -OffloadReplicationBlackoutFrom $OffloadReplicationBlackoutFrom -OffloadReplicationBlackoutTo $OffloadReplicationBlackoutTo -DefaultProtectionOptout $DefaultProtectionOptout ` -AVSCloudName $AVSCloudName -AVSResourceGroup $AVSResourceGroup -vCenterServer $vCenterServer -TimeoutInMinutes $TimeoutInMinutes Write-Host "Storage policy '$PolicyName' is successfully created" Write-Progress -Activity "Operation is done" -Status "100% Complete:" -Completed Disconnect-VIServer -Server $vCenterServer -Confirm:$false -ErrorAction SilentlyContinue } function Remove-PCBSStoragePolicy { <# .SYNOPSIS Remove a Pure Cloud Block Store vVol Storage Policy .DESCRIPTION Remove a Pure Cloud Block Store vVol Storage Policy using policy name. The policy must be unused and belong to Pure .INPUTS SPBM policy name .PARAMETER PolicyName Pure Cloud Block Store vVol Storage Policy name .PARAMETER AVSCloudName AVS cloud name .PARAMETER AVSResourceGroup AVS Resource group name .PARAMETER TimeoutInMinutes Optional. Timeout in minutes for RunCommand operations. .EXAMPLE Remove-PCBSStoragePolicy -PolicyName myPolicyName -AVSCloudName $AVSCloudName -AVSResourceGroup $AVSResourceGroup #> [CmdletBinding()] Param( [Parameter(mandatory = $true)] [string]$PolicyName, [Parameter(Mandatory = $true)] [String]$AVSCloudName, [Parameter(Mandatory = $true)] [String]$AVSResourceGroup, [Parameter(Mandatory=$false)] [ValidateRange(5, 60)] [int]$TimeoutInMinutes = 10 ) Write-Warning $VVOL_WARNIING Write-Progress -Activity "Remove storage policy" -Status "0% Complete:" -PercentComplete 1 $vCenterServer = Connect-AVSvCenter -AVSResourceGroupName $AVSResourceGroup -AVSPrivateCloudName $AVSCloudName Remove-VvolStoragePolicy -PolicyName $PolicyName -vCenterServer $vCenterServer ` -AVSCloudName $AVSCloudName -AVSResourceGroup $AVSResourceGroup -TimeoutInMinutes $TimeoutInMinutes Write-Host "Storage policy '$PolicyName' is successfully removed" Write-Progress -Activity "Operation is done" -Status "100% Complete:" -Completed Disconnect-VIServer -Server $vCenterServer -Confirm:$false -ErrorAction SilentlyContinue } function Start-PCBSFailover { <# .SYNOPSIS Issue test failover or failover operation against "Target" replication group. .DESCRIPTION Issue test failover or failover operation against replication group. Must be run on a "Target" Replication Group. .PARAMETER TargetReplicationGroupID Target VMWare Replication Group Id to be used on failover. Must be a "Target" Replication Group. .PARAMETER PointInTimeReplicaName Optional. Point in time replica name to failover to. If not provided, the latest replica will be used. .PARAMETER ClusterName Cluster name where failover VMs will be created. .PARAMETER PowerOn Optional. Indicates whether to or not to power on created VMs. If not provided the VM(s) will be powered on. .PARAMETER TestFailover Optional. Indicates whether to actually perform a failover(false) or to only perform a test failover(true). If not provided will only perform failover test. .PARAMETER AVSCloudName AVS cloud name. The target AVS instance where the failover will be performed. .PARAMETER AVSResourceGroup AVS resource group name. .PARAMETER TimeoutInMinutes Optional. Timeout in minutes for RunCommand operations. .EXAMPLE Start-PCBSFailover -TargetReplicationGroupID myGroupId -ClusterName myclustername -TestFailover $true -AVSCloudName $AVSCloudName -AVSResourceGroup $AVSResourceGroup Issues test failover on replication group specified by id "myGroupId", test VMs will be created under cluster "myclustername", and group replication state will be set to "InTest" .EXAMPLE Start-PCBSFailover -TargetReplicationGroupID myGroupId -ClusterName myclustername -PowerOn $true -AVSCloudName $AVSCloudName -AVSResourceGroup $AVSResourceGroup Issues failover on replication group specified by id "myGroupId", test VMs will be created under cluster "myclustername", VMs created will be powered on, and group replication state will be set to "FailedOver" .EXAMPLE Start-PCBSFailover -TargetReplicationGroupID myGroupId -ClusterName myclustername -PowerOn $true -AVSCloudName $AVSCloudName -AVSResourceGroup $AVSResourceGroup Issues Failover on replication group specified by id "myGroupId", new VMs will be created under cluster "myclustername", VMs created will be powered on, and group replication state will be set to "FailedOver" #> [CmdletBinding()] Param( [Parameter(mandatory = $true)] [String]$ClusterName, [Parameter(mandatory = $true)] [String]$TargetReplicationGroupID, [Parameter(mandatory = $false)] [String]$PointInTimeReplicaName, [Parameter(mandatory = $false)] [switch]$PowerOn, [Parameter(mandatory = $false)] [switch]$TestFailover, [Parameter(Mandatory = $true)] [String]$AVSCloudName, [Parameter(Mandatory = $true)] [String]$AVSResourceGroup, [Parameter(Mandatory=$false)] [ValidateRange(5, 60)] [int]$TimeoutInMinutes = 10 ) Write-Warning $VVOL_WARNIING Write-Progress -Activity "Start failover" -Status "0% Complete:" -PercentComplete 1 $vCenterServer = Connect-AVSvCenter -AVSResourceGroupName $AVSResourceGroup -AVSPrivateCloudName $AVSCloudName Start-VvolReplicationGroupFailover -ReplicationGroupID $TargetReplicationGroupID -PointInTimeReplicaName $PointInTimeReplicaName -ClusterName $ClusterName -PowerOn $PowerOn -TestFailover $TestFailover -vCenterServer $vCenterServer ` -AVSCloudName $AVSCloudName -AVSResourceGroup $AVSResourceGroup -TimeoutInMinutes $TimeoutInMinutes Write-Host "Replication group '$TargetReplicationGroupID' successfully failovered to cluster '$ClusterName'" Write-Progress -Activity "Operation is done" -Status "100% Complete:" -Completed Disconnect-VIServer -Server $vCenterServer -Confirm:$false -ErrorAction SilentlyContinue } function Sync-PCBSReplicationGroup { <# .SYNOPSIS Synchronize a replication group. Must be run on a "Target" Replication Group. .DESCRIPTION Synchronize a replication group. Triggers an on demand snapshot replication job. Must be run on a "Target" Replication Group. .PARAMETER TargetReplicationGroupID Replication Group Id that was used on the test failover .PARAMETER PointInTimeReplicaName Optional. Point in time replica name to sync to. If not provided, a generated name "Sync-*" will be used. .PARAMETER AVSCloudName AVS cloud name .PARAMETER AVSResourceGroup AVS resource group name .PARAMETER TimeoutInMinutes Optional. Timeout in minutes for RunCommand operations. .EXAMPLE Sync-PCBSReplicationGroup -TargetReplicationGroupID myGroupId -PointInTimeReplicaName myReplicaName -AVSCloudName $AVSCloudName -AVSResourceGroup $AVSResourceGroup Synchronizes a replication group specified by id "myGroupId". An on demand snapshot replication job will be triggered on array #> [CmdletBinding()] Param( [Parameter(mandatory=$true)] [String]$TargetReplicationGroupID, [Parameter(mandatory = $false)] [String]$PointInTimeReplicaName, [Parameter(Mandatory = $true)] [String]$AVSCloudName, [Parameter(Mandatory = $true)] [String]$AVSResourceGroup, [Parameter(Mandatory=$false)] [ValidateRange(5, 60) ] [int]$TimeoutInMinutes = 10 ) Write-Warning $VVOL_WARNIING Write-Progress -Activity "Start Replication Group sync" -Status "0% Complete:" -PercentComplete 1 $vCenterServer = Connect-AVSvCenter -AVSResourceGroupName $AVSResourceGroup -AVSPrivateCloudName $AVSCloudName if (-not $PointInTimeReplicaName) { $PointInTimeReplicaName = "Sync-$([guid]::newguid())" } Sync-VvolReplicationGroup -ReplicationGroupID $TargetReplicationGroupID -PointInTimeReplicaName $PointInTimeReplicaName -vCenterServer $vCenterServer ` -AVSCloudName $AVSCloudName -AVSResourceGroup $AVSResourceGroup -TimeoutInMinutes $TimeoutInMinutes Write-Host "Replication group '$TargetReplicationGroupID' is successfully synced to point in time replica '$PointInTimeReplicaName'" Write-Progress -Activity "Operation is done" -Status "100% Complete:" -Completed Disconnect-VIServer -Server $vCenterServer -Confirm:$false -ErrorAction SilentlyContinue } function Stop-PCBSFailoverTest { <# .SYNOPSIS Stops a test failover that was started on replication group .DESCRIPTION Stops a test failover that was started on replication group. Must be used on a "InTest" Replication Group .PARAMETER InTestTargetReplicationGroupID Replication Group Id that was used on the test failover .PARAMETER AVSCloudName AVS cloud name .PARAMETER AVSResourceGroup AVS resource group name .PARAMETER TimeoutInMinutes .EXAMPLE Stop-PCBSFailoverTest -InTestReplicationGroupID myGroupId -AVSCloudName $AVSCloudName -AVSResourceGroup $AVSResourceGroup Stops a test failover that was started on replication group specified by id "myGroupId", test VMs will be stopped and deleted and group replication state will be set to "Target" #> [CmdletBinding()] Param( [Parameter(mandatory=$true)] [String]$InTestTargetReplicationGroupID, [Parameter(Mandatory = $true)] [String]$AVSCloudName, [Parameter(Mandatory = $true)] [String]$AVSResourceGroup, [Parameter(Mandatory=$false)] [ValidateRange(5, 60) ] [int]$TimeoutInMinutes = 10 ) Write-Warning $VVOL_WARNIING Write-Progress -Activity "Stop failover test" -Status "0% Complete:" -PercentComplete 1 $vCenterServer = Connect-AVSvCenter -AVSResourceGroupName $AVSResourceGroup -AVSPrivateCloudName $AVSCloudName Stop-VvolReplicationGroupFailoverTest -ReplicationGroupID $InTestTargetReplicationGroupID -vCenterServer $vCenterServer ` -AVSCloudName $AVSCloudName -AVSResourceGroup $AVSResourceGroup -TimeoutInMinutes $TimeoutInMinutes Write-Host "Failover test associated with Replication group '$InTestTargetReplicationGroupID' is successfully stopped" Write-Progress -Activity "Operation is done" -Status "100% Complete:" -Completed Disconnect-VIServer -Server $vCenterServer -Confirm:$false -ErrorAction SilentlyContinue } function Start-PCBSFailoverCleanup { <# .SYNOPSIS Cleans up the original source site after failing over to a new site. .DESCRIPTION Cleans up the original source site after failing over to a new site. Must be run on source site. Will stop and unregister VMs protected by replication group. .PARAMETER SourceReplicationGroupID Replication Group Id for source group used on the failover .PARAMETER RemoveFromDisk Optional. Indicates whether to remove VMs from disk. .PARAMETER AVSCloudName AVS cloud name .PARAMETER AVSResourceGroup AVS resource group name .EXAMPLE Start-PCBSFailoverCleanup -SourceReplicationGroupID myGroupId -AVSCloudName $AVSCloudName -AVSResourceGroup $AVSResourceGroup Will stop and unregister VMs protected by replication group identified by "myGroupId". #> [CmdletBinding()] Param( [Parameter(Mandatory=$true)] [String]$SourceReplicationGroupID, [Parameter(Mandatory=$false)] [switch]$RemoveFromDisk, [Parameter(Mandatory = $true)] [String]$AVSCloudName, [Parameter(Mandatory = $true)] [String]$AVSResourceGroup ) Write-Warning $VVOL_WARNIING Write-Progress -Activity "Clean up original source site after failover" -Status "0% Complete:" -PercentComplete 1 $vCenterServer = Connect-AVSvCenter -AVSResourceGroupName $AVSResourceGroup -AVSPrivateCloudName $AVSCloudName Start-VvolCleanupSourceReplicationGroupForFailover -ReplicationGroupID $SourceReplicationGroupID -RemoveFromDisk $RemoveFromDisk -vCenterServer $vCenterServer ` -AVSCloudName $AVSCloudName -AVSResourceGroup $AVSResourceGroup Write-Host "The original source site '$SourceReplicationGroupID' is successfully cleaned up" Write-Progress -Activity "Operation is done" -Status "100% Complete:" -Completed Disconnect-VIServer -Server $vCenterServer -Confirm:$false -ErrorAction SilentlyContinue } function Start-PCBSFailoverReverse { <# .SYNOPSIS Reverse replication on a FailedOver replication group and reprotect by assigning a storage policy to affected objects .DESCRIPTION Reverse replication on a FailedOver replication group and reprotect by assigning a storage policy to affected objects. Must be used on a FailedOver Replication Group. The Failedover Replication Group will become "Source" .PARAMETER FailedoverTargetReplicationGroupID Replication Group Id that was used on failover .PARAMETER PolicyName Optional. Policy Name to be used to reprotect objects. .PARAMETER AVSCloudName AVS cloud name .PARAMETER AVSResourceGroup AVS resource group name .PARAMETER TimeoutInMinutes Optional. Timeout in minutes for RunCommand operations. .EXAMPLE Start-PCBSFailoverReverse -FailedoverTargetReplicationGroupID myGroupId -AVSCloudName $AVSCloudName -AVSResourceGroup $AVSResourceGroup Reprotect replication group specified by id "myGroupId", and sets objects storage policy to the default "VVol No Requirements Policy" .EXAMPLE Start-PCBSFailoverReverse -FailedoverTargetReplicationGroupID myGroupId -PolicyName PolicyName -AVSCloudName $AVSCloudName -AVSResourceGroup $AVSResourceGroup Reprotect replication group specified by id "myGroupId", and sets objects storage policy to "PolicyName" #> [CmdletBinding()] Param( [Parameter(mandatory=$true)] [String]$FailedoverTargetReplicationGroupID, [Parameter(mandatory=$false)] [String]$PolicyName, [Parameter(Mandatory = $true)] [String]$AVSCloudName, [Parameter(Mandatory = $true)] [String]$AVSResourceGroup, [Parameter(Mandatory=$false)] [ValidateRange(5, 60) ] [int]$TimeoutInMinutes = 10 ) Write-Warning $VVOL_WARNIING Write-Progress -Activity "Reprotect replication group" -Status "0% Complete:" -PercentComplete 1 $vCenterServer = Connect-AVSvCenter -AVSResourceGroupName $AVSResourceGroup -AVSPrivateCloudName $AVSCloudName Start-VvolReprotectReplicationGroup -ReplicationGroupID $FailedoverTargetReplicationGroupID -PolicyName $PolicyName -vCenterServer $vCenterServer ` -AVSCloudName $AVSCloudName -AVSResourceGroup $AVSResourceGroup -TimeoutInMinutes $TimeoutInMinutes Write-Host "The FailedOver replication group '$FailedoverTargetReplicationGroupID' is successfully reprotected" Write-Progress -Activity "Operation is done" -Status "100% Complete:" -Completed Disconnect-VIServer -Server $vCenterServer -Confirm:$false -ErrorAction SilentlyContinue } <# .SYNOPSIS Fully remove a Pure Storage CBS AVS monitor deployment from Azure infrastructure .DESCRIPTION Fully remove a Pure Storage CBS AVS monitor deployment from Azure infrastructure. The resource group and its resources will be destroyed. The vNet subnet used by the monitor will also be remove from the vNet. .PARAMETER MonitorResourceGroup ResourceGroup to host monitor infrastructure components .PARAMETER RemoveSubnet Optional. Indicates whether to remove the subnet used by the monitor from the vNet. If not provided, the subnet will not be removed. .EXAMPLE Remove-PCBSAVSMonitor -MonitorResourceGroup "myAVSMonitorResourceGroup" .NOTES Must be logged in to Azure (using Connect-AzAccount) before running this cmdlet #> function Remove-PCBSAVSMonitor { [CmdletBinding()] Param ( [Parameter(Mandatory=$true)] [String]$MonitorResourceGroup, [Parameter(Mandatory=$false)] [Switch]$RemoveSubnet ) $params = @{ MonitorResourceGroup = $MonitorResourceGroup MonitorType = "host" RemoveSubnet = $RemoveSubnet } Remove-Monitor @params } <# .SYNOPSIS Deploy or update a Pure Storage CBS AVS monitor to Azure infrastructure .DESCRIPTION Deploy or update a Pure Storage CBS AVS monitor to Azure infrastructure. The monitor will scan periodically for AVS Cluster/Host changes and will make sure to change iSCSI configuration accordingly. .PARAMETER MonitorResourceGroup Resource group to host monitor infrastructure components. The resource group will be created if not exists .PARAMETER MonitorResourceGroupRegion Resource group region to host monitor infrastructure components. .PARAMETER AVSCloudName AVS cloud name .PARAMETER AVSResourceGroup AVS Resource group name .PARAMETER VNetName An existing VNet name. The VNey specified should have access to AVS as well as Pure Storage Cloud Block Store (CBS) array. .PARAMETER VNetResourceGroup VNet REsourceGroup .PARAMETER VNetSubnetAddress The VNet subnet address range in CIDR notation (e.g. 192.168.1.0/24). It must be contained by the address space of the virtual network. .PARAMETER VNetSubnetName If VNetSubnetAddress is specified, then VnetSubnetName can be optionaly used to specify a new subnet name otherwise it is existing VNet subnet name. .PARAMETER MonitorIntervalInMinute Optional. The default monitor interval is 10 minutes. .PARAMETER RunCommandTimeoutInMinute Optional. The default timeout for RunCommand operations is 10 minutes. .EXAMPLE Deploy-PCBSAVSMonitor -AVSCloudName "my-avs" -AVSResourceGroup "avs-resourcegroup" ` -MonitorResourceGroup "NewResourceGroup" -MonitorResourceGroupRegion "westus2" -VNetName "my-vnet" -VNetResourceGroup "vnet-resourcegroup" -VNetSubnetAddress "192.168.3.0/24" .NOTES Must be logged in to Azure (using Connect-AzAccount) before running this cmdlet #> function Deploy-PCBSAVSMonitor { [CmdletBinding()] Param ( [Parameter(Mandatory=$true)] [String]$MonitorResourceGroup, [Parameter(Mandatory=$true)] [String]$MonitorResourceGroupRegion, [Parameter(Mandatory=$true)] [String]$AVSCloudName, [Parameter(Mandatory=$true)] [String]$AVSResourceGroup, [Parameter(Mandatory = $true)] [String]$VNetName, [Parameter(Mandatory=$true)] [String]$VNetResourceGroup, [Parameter(ParameterSetName='NewSubnet', Mandatory=$true)] [String]$VNetSubnetAddress, [Parameter(ParameterSetName='ExistingSubnet', Mandatory=$true)] [Parameter(ParameterSetName='NewSubnet', Mandatory=$false)] [String]$VNetSubnetName, [Parameter(Mandatory = $false)] [ValidateScript({ $_ -ge 10 }, ErrorMessage = "The minimum interval for the monitor is 10 minutes.")] [ValidateScript({ $_ -le 60 }, ErrorMessage = "The maximum interval for the monitor is 60 minutes.")] [int]$MonitorIntervalInMinute = 10, [Parameter(Mandatory = $false)] [ValidateRange(10, 60)] [int] $RunCommandTimeoutInMinute = $DEFAULT_RUNCOMMAND_TIMEOUT_IN_MINUTE ) $params = @{ MonitorResourceGroup = $MonitorResourceGroup MonitorResourceGroupRegion = $MonitorResourceGroupRegion AVSCloudName = $AVSCloudName AVSResourceGroup = $AVSResourceGroup VNetName = $VNetName VNetResourceGroup = $VNetResourceGroup MonitorIntervalInMinute = $MonitorIntervalInMinute MonitorType = "host" DefaultRunCommandTimeoutInMinute = $RunCommandTimeoutInMinute } $paramSetName = $PSCmdlet.ParameterSetName if ($paramSetName -eq "NewSubnet") { $params.Add("VNetSubnetAddress", $VNetSubnetAddress) if ($VNetSubnetName) { $params.Add("VNetSubnetName", $VNetSubnetName) } } else { $params.Add("VNetSubnetName", $VNetSubnetName) } Deploy-MonitoringResource @params } <# .SYNOPSIS Deploy or update a Pure Storage CBS Capacity monitor .DESCRIPTION Deploy or update a Pure Storage CBS Capacity monitor to Azure infrastructure. The monitor will scan periodically for CBS space utilization and will automatically request expanding the storage if the utilization reaches pre-defined threshold .PARAMETER MonitorResourceGroup Resource group to host monitor infrastructure components. The resource group will be created if not exists .PARAMETER MonitorResourceGroupRegion Resource group region to host monitor infrastructure components. .PARAMETER VNetName An existing VNet name. The VNey specified should have access to AVS as well as Pure Storage Cloud Block Store (CBS) array. .PARAMETER VNetResourceGroup VNet REsourceGroup .PARAMETER VNetSubnetAddress The VNet subnet address range in CIDR notation (e.g. 192.168.1.0/24). It must be contained by the address space of the virtual network. .PARAMETER VNetSubnetName If VNetSubnetAddress is specified, then VnetSubnetName can be optionaly used to specify a new subnet name otherwise it is existing VNet subnet name. .PARAMETER DefaultUtilizationThreshold Optional. The default utilization threshold is 80%. .PARAMETER MonitorIntervalInMinute Optional. The default monitor interval is 10 minutes. .EXAMPLE Deploy-PCBSCapacityMonitor -MonitorResourceGroup "NewResourceGroup" -MonitorResourceGroupRegion "westus2" ` -VNetName "my-vnet" -VNetResourceGroup "vnet-resourcegroup" -VNetSubnetAddress "192.168.3.0/24" .NOTES Must be logged in to Azure (using Connect-AzAccount) before running this cmdlet #> function Deploy-PCBSCapacityMonitor { [CmdletBinding()] Param( [Parameter(Mandatory=$true)] [String]$MonitorResourceGroup, [Parameter(Mandatory=$true)] [String]$MonitorResourceGroupRegion, [Parameter(Mandatory = $true)] [String]$VNetName, [Parameter(Mandatory=$true)] [String]$VNetResourceGroup, [Parameter(ParameterSetName='NewSubnet', Mandatory=$true)] [String]$VNetSubnetAddress, [Parameter(ParameterSetName='ExistingSubnet', Mandatory=$true)] [Parameter(ParameterSetName='NewSubnet', Mandatory=$false)] [String]$VNetSubnetName, [Parameter(Mandatory=$false)] [ValidateRange(1, 100)] [int] $DefaultUtilizationThreshold = $DEFAULT_UTILIZATIOn_THRESHOLD, [Parameter(Mandatory=$false)] [ValidateScript({ $_ -ge 10 }, ErrorMessage = "The minimum interval for the monitor is 10 minutes.")] [ValidateScript({ $_ -le 60 }, ErrorMessage = "The maximum interval for the monitor is 60 minutes.")] [int]$MonitorIntervalInMinute = 30 ) $params = @{ MonitorResourceGroup = $MonitorResourceGroup MonitorResourceGroupRegion = $MonitorResourceGroupRegion VNetName = $VNetName VNetResourceGroup = $VNetResourceGroup MonitorIntervalInMinute = $MonitorIntervalInMinute DefaultUtilizationThreshold = $DefaultUtilizationThreshold MonitorType = "capacity" } $paramSetName = $PSCmdlet.ParameterSetName if ($paramSetName -eq "NewSubnet") { $params.Add("VNetSubnetAddress", $VNetSubnetAddress) if ($VNetSubnetName) { $params.Add("VNetSubnetName", $VNetSubnetName) } } else { $params.Add("VNetSubnetName", $VNetSubnetName) } Deploy-MonitoringResource @params } <# .SYNOPSIS List the existing Pure Cloud Block Store in the Pure Storage CBS AVS monitor .DESCRIPTION List the existing Pure Cloud Block Store in the Pure Storage CBS AVS monitor .PARAMETER MonitorResourceGroup ResourceGroup to host monitor infrastructure components .EXAMPLE Get-PCBSAVSMonitorArray -MonitorResourceGroup myMonitor .NOTES Must be logged in to Azure (using Connect-AzAccount) before running this cmdlet #> function Get-PCBSAVSMonitorArray { [CmdletBinding()] Param ( [Parameter(Mandatory=$true)] [String]$MonitorResourceGroup ) $ResourceGroup = Get-AzResourceGroup -Name $MonitorResourceGroup if (-not $ResourceGroup) { throw "Resource group $MonitorResourceGroupdoes not exist" } if (-not $ResourceGroup.Tags["PureStorage.CBS.AVS"]) { throw "Resouce group $MonitorResourceGroup specified does not host Pure Storage CBS AVS monitor" } $UserPrincipalName = (Get-AzContext).Account.Id $KeyVault = Get-AzKeyVault -ResourceGroupName $MonitorResourceGroup Set-AzKeyVaultAccessPolicy -VaultName $KeyVault.VaultName -UserPrincipalName $UserPrincipalName -PermissionsToSecrets set,delete,get,purge,list $Arrays = @() $Secrets = Get-AzKeyVaultSecret -VaultName $KeyVault.VaultName | where-object {$_.Name -like "*-$($KeyVault.VaultName)-username"} foreach ($Secret in $Secrets) { $ArrayName = ($Secret.name -Split "-$($KeyVault.VaultName)-username")[0] # If the string matches the format like "172-168-1-0", the array ip addressed was processed because secret name does not allow "." if ($ArrayName -match "^\d+-\d+-\d+-\d+$") { $ArrayName = $ArrayName.Replace("-", ".") } $Arrays += $ArrayName } return $Arrays } <# .SYNOPSIS Add a Pure Cloud Block Store to an existing Pure Storage CBS AVS monitor .DESCRIPTION Add a Pure Cloud Block Store to an existing Pure Storage CBS AVS monitor. If the Pure Block store already exists, it will be overwritten .PARAMETER MonitorResourceGroup ResourceGroup to host monitor infrastructure components .PARAMETER PureCloudBlockStoreEndpoint Pure Cloud Block Store endpoint address .PARAMETER PureCloudBlockStoreCredential Pure Cloud Block Store credential .PARAMETER Force Optional. If specified, the cmdlet will not throw error if the Pure Cloud Block Store cannot be verified .EXAMPLE Add-PCBSAVSMonitorArray -MonitorResourceGroup myMonitorGroup -PureCloudBlockStoreEndpoint myArray -PureCloudBlockStoreCredential (Get-Credential) .NOTES Must be logged in to Azure (using Connect-AzAccount) before running this cmdlet #> function Add-PCBSAVSMonitorArray { [CmdletBinding()] Param ( [Parameter(Mandatory=$true)] [String]$MonitorResourceGroup, [Parameter(Mandatory=$true)] [String]$PureCloudBlockStoreEndpoint, [Parameter(Mandatory=$true)] [pscredential]$PureCloudBlockStoreCredential, [Parameter(Mandatory=$false)] [Switch]$Force ) $params = @{ MonitorResourceGroup = $MonitorResourceGroup PureCloudBlockStoreEndpoint = $PureCloudBlockStoreEndpoint PureCloudBlockStoreCredential = $PureCloudBlockStoreCredential MonitorType = "host" } if ($Force) { $params.Add("Force", $Force) } Add-MonitorArray @params } <# .SYNOPSIS Remove an existing Pure Cloud Block Store from a Pure Storage CBS AVS monitor .DESCRIPTION Remove an existing Pure Cloud Block Store from a Pure Storage CBS AVS monitor. If the Pure Block store already exists, it will be overwritten .PARAMETER MonitorResourceGroup ResourceGroup to host monitor infrastructure components .PARAMETER PureCloudBlockStoreEndpoint Pure Cloud Block Store endpoint address .EXAMPLE Remove-PCBSAVSMonitorArray -MonitorResourceGroup myMonitorGroup -PureCloudBlockStoreEndpoint myArray .NOTES Must be logged in to Azure (using Connect-AzAccount) before running this cmdlet #> function Remove-PCBSAVSMonitorArray { [CmdletBinding()] Param ( [Parameter(Mandatory=$true)] [String]$MonitorResourceGroup, [Parameter(Mandatory=$true)] [String]$PureCloudBlockStoreEndpoint ) $params = @{ MonitorResourceGroup = $MonitorResourceGroup MonitorType = "host" PureCloudBlockStoreEndpoint = $PureCloudBlockStoreEndpoint } Remove-MonitorArray @params } <# .SYNOPSIS Fully remove a Pure Storage CBS Capacity monitor deployment from Azure infrastructure .DESCRIPTION Fully remove a Pure Storage CBS capacity monitor deployment from Azure infrastructure. The resource group and its resources will be destroyed. The vNet subnet used by the monitor will also be remove from the vNet. .PARAMETER MonitorResourceGroup ResourceGroup to host monitor infrastructure components .PARAMETER RemoveSubnet Optional. Indicates whether to remove the subnet used by the monitor from the vNet. If not provided, the subnet will not be removed. .EXAMPLE Remove-PCBSCapacityMonitor -MonitorResourceGroup "myCapacityMonitorResourceGroup" .NOTES Must be logged in to Azure (using Connect-AzAccount) before running this cmdlet #> function Remove-PCBSCapacityMonitor { [CmdletBinding()] Param ( [Parameter(Mandatory=$true)] [String]$MonitorResourceGroup, [Parameter(Mandatory=$false)] [Switch]$RemoveSubnet ) $params = @{ MonitorResourceGroup = $MonitorResourceGroup MonitorType = "capacity" RemoveSubnet = $RemoveSubnet } Remove-Monitor @params } <# .SYNOPSIS Add a Pure Cloud Block Store to an existing Pure Storage CBS capacity monitor .DESCRIPTION Add a Pure Cloud Block Store to an existing Pure Storage CBS capacity monitor. If the Pure Block store already exists, it will be overwritten .PARAMETER MonitorResourceGroup ResourceGroup to host monitor infrastructure components .PARAMETER PureCloudBlockStoreEndpoint Pure Cloud Block Store endpoint address .PARAMETER PureCloudBlockStoreCredential Pure Cloud Block Store credential .PARAMETER UtilizationThreshold Optional. The utilization threshold for the Pure Cloud Block Store. If not provided, the default threshold will be used. .PARAMETER Force Optional. If specified, the cmdlet will not throw error if the Pure Cloud Block Store cannot be verified .EXAMPLE Add-PCBSCapacityMonitorArray -MonitorResourceGroup myMonitorGroup -PureCloudBlockStoreEndpoint myArray -PureCloudBlockStoreCredential (Get-Credential) ` -UtilizationThreshold 80 .NOTES Must be logged in to Azure (using Connect-AzAccount) before running this cmdlet #> function Add-PCBSCapacityMonitorArray { [CmdletBinding()] Param ( [Parameter(Mandatory = $true)] [String]$MonitorResourceGroup, [Parameter(Mandatory = $true)] [String]$PureCloudBlockStoreEndpoint, [Parameter(Mandatory = $true)] [pscredential]$PureCloudBlockStoreCredential, [Parameter(Mandatory = $false)] [int] $UtilizationThreshold, [Parameter(Mandatory = $false)] [Switch]$Force ) $params = @{ MonitorResourceGroup = $MonitorResourceGroup PureCloudBlockStoreEndpoint = $PureCloudBlockStoreEndpoint PureCloudBlockStoreCredential = $PureCloudBlockStoreCredential MonitorType = "capacity" } if ($UtilizationThreshold) { $params.Add("UtilizationThreshold", $UtilizationThreshold) } if ($Force) { $params.Add("Force", $Force) } Add-MonitorArray @params } <# .SYNOPSIS Get metadata of a Pure Storage CBS capacity monitor .DESCRIPTION Get metadata of a Pure Storage CBS capacity monitor .PARAMETER MonitorResourceGroup ResourceGroup that hosts monitor infrastructure components #> function Get-PCBSCapacityMonitor { param ( [Parameter(Mandatory = $true)] [String]$MonitorResourceGroup ) Get-Monitor -MonitorResourceGroup $MonitorResourceGroup -MonitorType "capacity" return $result } <# .SYNOPSIS Get metadata of a Pure Storage CBS AVS monitor .DESCRIPTION Get metadata of a Pure Storage CBS AVS monitor .PARAMETER MonitorResourceGroup ResourceGroup that hosts monitor infrastructure components #> function Get-PCBSAVSMonitor { param ( [Parameter(Mandatory = $true)] [String]$MonitorResourceGroup ) Get-Monitor -MonitorResourceGroup $MonitorResourceGroup -MonitorType "host" return $result } <# .SYNOPSIS Remove an existing Pure Cloud Block Store from a Pure Storage CBS capacity monitor .DESCRIPTION Remove an existing Pure Cloud Block Store from a Pure Storage CBS capacity monitor. .PARAMETER MonitorResourceGroup ResourceGroup that hosts the monitor infrastructure components .PARAMETER PureCloudBlockStoreEndpoint Pure Cloud Block Store endpoint address #> function Remove-PCBSCapacityMonitorArray { param ( [Parameter(Mandatory = $true)] [String]$MonitorResourceGroup, [Parameter(Mandatory = $true)] [String]$PureCloudBlockStoreEndpoint ) $params = @{ MonitorResourceGroup = $MonitorResourceGroup MonitorType = "capacity" PureCloudBlockStoreEndpoint = $PureCloudBlockStoreEndpoint } Remove-MonitorArray @params } <# .SYNOPSIS Test the availability of a RunCommand package .DESCRIPTION Test the availability of a RunCommand package .PARAMETER RunCommandPackageName The name of the RunCommand package .PARAMETER RunCommandPackageVersion The version of the RunCommand package .PARAMETER AVSCloudName The name of the AVS cloud .PARAMETER AVSResourceGroup The name of the AVS resource group #> function Test-PCBSRunCommandPackageAvailability { param ( [Parameter(Mandatory = $true)] [String] $RunCommandPackageName, [Parameter(Mandatory = $true)] [String] $RunCommandPackageVersion, [Parameter(Mandatory = $true)] [String] $AVSCloudName, [Parameter(Mandatory = $true)] [String] $AVSResourceGroup ) $SubscriptionId = Get-DefaultAzureSubscriptionId Write-Host "Using Azure default subscription: $SubscriptionId..." Test-RunCommandPackageAvailability -SubscriptionId $SubscriptionId -RunCommandModule $RunCommandPackageName -RunCommandPackageVersion $RunCommandPackageVersion -AVSCloudName $AVSCloudName -AVSResourceGroup $AVSResourceGroup } <# .SYNOPSIS Clear iSCSI targets on a VMHost(s) .DESCRIPTION Clear iSCSI targets on a VMHost. The function will clean iSCSI targets on the VMHost provided (or all VMHosts in the cluster if no VMHost is specified). If no ScsiIpAddress, the function will clear all disconnected iSCSI targets on the VMHost(s). .PARAMETER AVSCloudName The name of the AVS cloud .PARAMETER AVSResourceGroup The name of the AVS resource group .PARAMETER ClusterName The name of the cluster .PARAMETER VMHostName The name of the VMHost .PARAMETER ScsiIpAddress The iSCSI address of the target #> function Clear-PCBSiSCSITargets { param ( [Parameter(Mandatory = $true)] [String] $AVSCloudName, [Parameter(Mandatory = $true)] [String] $AVSResourceGroup, [Parameter (Mandatory = $true)] [String]$ClusterName, [Parameter (Mandatory = $false)] [String]$VMHostName, [Parameter (Mandatory = $false)] [String]$ScsiIpAddress ) Write-Progress -Activity "Clearing iSCSI targets" -Status "0% Complete:" -PercentComplete 1 try { $vCenterServer = Connect-AVSvCenter -AVSResourceGroupName $AVSResourceGroup -AVSPrivateCloudName $AVSCloudName $Cluster = Get-Cluster -Name $ClusterName if (-not $Cluster) { throw "Cluster $ClusterName does not exist" } $params = @{ ClusterName = $ClusterName } if ($VMHostName) { $params["VMHostName"] = $VMHostName } if ($ScsiIpAddress) { $params["ISCSIAddress"] = $ScsiIpAddress Invoke-RunScript -RunCommandName "Remove-VMHostStaticiSCSITargets" -RunCommandModule "Microsoft.AVS.VMFS" -Parameters $params ` -AVSCloudName $AVSCloudName -AVSResourceGroup $AVSResourceGroup -TimeoutInMinutes $TimeoutInMinutes } else { Invoke-RunScript -RunCommandName "Clear-DisconnectedIscsiTargets" -RunCommandModule "Microsoft.AVS.VMFS" -Parameters $params ` -AVSCloudName $AVSCloudName -AVSResourceGroup $AVSResourceGroup -TimeoutInMinutes $TimeoutInMinutes } } catch { Write-Progress -Activity "Error" -Status "100% Complete:" -PercentComplete 100 throw } Disconnect-VIServer -Server $vCenterServer -Confirm:$false -ErrorAction SilentlyContinue Write-Progress -Activity "Operation is done" -Status "100% Complete:" -PercentComplete 100 } Export-ModuleMember -Function Build-PCBSCluster Export-ModuleMember -Function New-PCBSVmfsDatastore Export-ModuleMember -Function Restore-PCBSVmfsDatastore Export-ModuleMember -Function Remove-PCBSVmfsDatastore Export-ModuleMember -Function Set-PCBSVmfsCapacity Export-ModuleMember -Function Deploy-PCBSAVSMonitor Export-ModuleMember -Function Remove-PCBSAVSMonitor Export-ModuleMember -Function Add-PCBSAVSMonitorArray Export-ModuleMember -Function Remove-PCBSAVSMonitorArray Export-ModuleMember -Function Get-PCBSAVSMonitorArray Export-ModuleMember -Function Get-PCBSAVSMonitor Export-ModuleMember -Function New-PCBSVvolDataStore Export-ModuleMember -Function Remove-PCBSVvolDataStore Export-ModuleMember -Function New-PCBSVASAProvider Export-ModuleMember -Function Update-PCBSVASAProvider Export-ModuleMember -Function New-PCBSStoragePolicy Export-ModuleMember -Function Remove-PCBSStoragePolicy Export-ModuleMember -Function Start-PCBSFailover Export-ModuleMember -Function Stop-PCBSFailoverTest Export-ModuleMember -Function Start-PCBSFailoverCleanup Export-ModuleMember -Function Start-PCBSFailoverReverse Export-ModuleMember -Function Sync-PCBSReplicationGroup Export-ModuleMember -Function Deploy-PCBSCapacityMonitor Export-ModuleMember -Function Remove-PCBSCapacityMonitor Export-ModuleMember -Function Add-PCBSCapacityMonitorArray Export-ModuleMember -Function Get-PCBSCapacityMonitor Export-ModuleMember -Function Remove-PCBSCapacityMonitorArray Export-ModuleMember -Function Test-PCBSRunCommandPackageAvailability Export-ModuleMember -Function Clear-PCBSiSCSITargets Export-ModuleMember -Function Sync-PCBSClusterVMHostStorage # SIG # Begin signature block # MIIuggYJKoZIhvcNAQcCoIIuczCCLm8CAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCDWoCh7Rg4i2TPG # eMlAw+rNeCCTOsJ5yeL8BcHWFAzjaaCCE2gwggVyMIIDWqADAgECAhB2U/6sdUZI # k/Xl10pIOk74MA0GCSqGSIb3DQEBDAUAMFMxCzAJBgNVBAYTAkJFMRkwFwYDVQQK # ExBHbG9iYWxTaWduIG52LXNhMSkwJwYDVQQDEyBHbG9iYWxTaWduIENvZGUgU2ln # bmluZyBSb290IFI0NTAeFw0yMDAzMTgwMDAwMDBaFw00NTAzMTgwMDAwMDBaMFMx # CzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMSkwJwYDVQQD # EyBHbG9iYWxTaWduIENvZGUgU2lnbmluZyBSb290IFI0NTCCAiIwDQYJKoZIhvcN # AQEBBQADggIPADCCAgoCggIBALYtxTDdeuirkD0DcrA6S5kWYbLl/6VnHTcc5X7s # k4OqhPWjQ5uYRYq4Y1ddmwCIBCXp+GiSS4LYS8lKA/Oof2qPimEnvaFE0P31PyLC # o0+RjbMFsiiCkV37WYgFC5cGwpj4LKczJO5QOkHM8KCwex1N0qhYOJbp3/kbkbuL # ECzSx0Mdogl0oYCve+YzCgxZa4689Ktal3t/rlX7hPCA/oRM1+K6vcR1oW+9YRB0 # RLKYB+J0q/9o3GwmPukf5eAEh60w0wyNA3xVuBZwXCR4ICXrZ2eIq7pONJhrcBHe # OMrUvqHAnOHfHgIB2DvhZ0OEts/8dLcvhKO/ugk3PWdssUVcGWGrQYP1rB3rdw1G # R3POv72Vle2dK4gQ/vpY6KdX4bPPqFrpByWbEsSegHI9k9yMlN87ROYmgPzSwwPw # jAzSRdYu54+YnuYE7kJuZ35CFnFi5wT5YMZkobacgSFOK8ZtaJSGxpl0c2cxepHy # 1Ix5bnymu35Gb03FhRIrz5oiRAiohTfOB2FXBhcSJMDEMXOhmDVXR34QOkXZLaRR # kJipoAc3xGUaqhxrFnf3p5fsPxkwmW8x++pAsufSxPrJ0PBQdnRZ+o1tFzK++Ol+ # A/Tnh3Wa1EqRLIUDEwIrQoDyiWo2z8hMoM6e+MuNrRan097VmxinxpI68YJj8S4O # JGTfAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0G # A1UdDgQWBBQfAL9GgAr8eDm3pbRD2VZQu86WOzANBgkqhkiG9w0BAQwFAAOCAgEA # Xiu6dJc0RF92SChAhJPuAW7pobPWgCXme+S8CZE9D/x2rdfUMCC7j2DQkdYc8pzv # eBorlDICwSSWUlIC0PPR/PKbOW6Z4R+OQ0F9mh5byV2ahPwm5ofzdHImraQb2T07 # alKgPAkeLx57szO0Rcf3rLGvk2Ctdq64shV464Nq6//bRqsk5e4C+pAfWcAvXda3 # XaRcELdyU/hBTsz6eBolSsr+hWJDYcO0N6qB0vTWOg+9jVl+MEfeK2vnIVAzX9Rn # m9S4Z588J5kD/4VDjnMSyiDN6GHVsWbcF9Y5bQ/bzyM3oYKJThxrP9agzaoHnT5C # JqrXDO76R78aUn7RdYHTyYpiF21PiKAhoCY+r23ZYjAf6Zgorm6N1Y5McmaTgI0q # 41XHYGeQQlZcIlEPs9xOOe5N3dkdeBBUO27Ql28DtR6yI3PGErKaZND8lYUkqP/f # obDckUCu3wkzq7ndkrfxzJF0O2nrZ5cbkL/nx6BvcbtXv7ePWu16QGoWzYCELS/h # AtQklEOzFfwMKxv9cW/8y7x1Fzpeg9LJsy8b1ZyNf1T+fn7kVqOHp53hWVKUQY9t # W76GlZr/GnbdQNJRSnC0HzNjI3c/7CceWeQIh+00gkoPP/6gHcH1Z3NFhnj0qinp # J4fGGdvGExTDOUmHTaCX4GUT9Z13Vunas1jHOvLAzYIwggbmMIIEzqADAgECAhB3 # vQ4DobcI+FSrBnIQ2QRHMA0GCSqGSIb3DQEBCwUAMFMxCzAJBgNVBAYTAkJFMRkw # FwYDVQQKExBHbG9iYWxTaWduIG52LXNhMSkwJwYDVQQDEyBHbG9iYWxTaWduIENv # ZGUgU2lnbmluZyBSb290IFI0NTAeFw0yMDA3MjgwMDAwMDBaFw0zMDA3MjgwMDAw # MDBaMFkxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMS8w # LQYDVQQDEyZHbG9iYWxTaWduIEdDQyBSNDUgQ29kZVNpZ25pbmcgQ0EgMjAyMDCC # AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANZCTfnjT8Yj9GwdgaYw90g9 # z9DljeUgIpYHRDVdBs8PHXBg5iZU+lMjYAKoXwIC947Jbj2peAW9jvVPGSSZfM8R # Fpsfe2vSo3toZXer2LEsP9NyBjJcW6xQZywlTVYGNvzBYkx9fYYWlZpdVLpQ0LB/ # okQZ6dZubD4Twp8R1F80W1FoMWMK+FvQ3rpZXzGviWg4QD4I6FNnTmO2IY7v3Y2F # QVWeHLw33JWgxHGnHxulSW4KIFl+iaNYFZcAJWnf3sJqUGVOU/troZ8YHooOX1Re # veBbz/IMBNLeCKEQJvey83ouwo6WwT/Opdr0WSiMN2WhMZYLjqR2dxVJhGaCJedD # CndSsZlRQv+hst2c0twY2cGGqUAdQZdihryo/6LHYxcG/WZ6NpQBIIl4H5D0e6lS # TmpPVAYqgK+ex1BC+mUK4wH0sW6sDqjjgRmoOMieAyiGpHSnR5V+cloqexVqHMRp # 5rC+QBmZy9J9VU4inBDgoVvDsy56i8Te8UsfjCh5MEV/bBO2PSz/LUqKKuwoDy3K # 1JyYikptWjYsL9+6y+JBSgh3GIitNWGUEvOkcuvuNp6nUSeRPPeiGsz8h+WX4VGH # aekizIPAtw9FbAfhQ0/UjErOz2OxtaQQevkNDCiwazT+IWgnb+z4+iaEW3VCzYkm # eVmda6tjcWKQJQ0IIPH/AgMBAAGjggGuMIIBqjAOBgNVHQ8BAf8EBAMCAYYwEwYD # VR0lBAwwCgYIKwYBBQUHAwMwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQU # 2rONwCSQo2t30wygWd0hZ2R2C3gwHwYDVR0jBBgwFoAUHwC/RoAK/Hg5t6W0Q9lW # ULvOljswgZMGCCsGAQUFBwEBBIGGMIGDMDkGCCsGAQUFBzABhi1odHRwOi8vb2Nz # cC5nbG9iYWxzaWduLmNvbS9jb2Rlc2lnbmluZ3Jvb3RyNDUwRgYIKwYBBQUHMAKG # Omh0dHA6Ly9zZWN1cmUuZ2xvYmFsc2lnbi5jb20vY2FjZXJ0L2NvZGVzaWduaW5n # cm9vdHI0NS5jcnQwQQYDVR0fBDowODA2oDSgMoYwaHR0cDovL2NybC5nbG9iYWxz # aWduLmNvbS9jb2Rlc2lnbmluZ3Jvb3RyNDUuY3JsMFYGA1UdIARPME0wQQYJKwYB # BAGgMgEyMDQwMgYIKwYBBQUHAgEWJmh0dHBzOi8vd3d3Lmdsb2JhbHNpZ24uY29t # L3JlcG9zaXRvcnkvMAgGBmeBDAEEATANBgkqhkiG9w0BAQsFAAOCAgEACIhyJsav # +qxfBsCqjJDa0LLAopf/bhMyFlT9PvQwEZ+PmPmbUt3yohbu2XiVppp8YbgEtfjr # y/RhETP2ZSW3EUKL2Glux/+VtIFDqX6uv4LWTcwRo4NxahBeGQWn52x/VvSoXMNO # Ca1Za7j5fqUuuPzeDsKg+7AE1BMbxyepuaotMTvPRkyd60zsvC6c8YejfzhpX0FA # Z/ZTfepB7449+6nUEThG3zzr9s0ivRPN8OHm5TOgvjzkeNUbzCDyMHOwIhz2hNab # XAAC4ShSS/8SS0Dq7rAaBgaehObn8NuERvtz2StCtslXNMcWwKbrIbmqDvf+28rr # vBfLuGfr4z5P26mUhmRVyQkKwNkEcUoRS1pkw7x4eK1MRyZlB5nVzTZgoTNTs/Z7 # KtWJQDxxpav4mVn945uSS90FvQsMeAYrz1PYvRKaWyeGhT+RvuB4gHNU36cdZytq # tq5NiYAkCFJwUPMB/0SuL5rg4UkI4eFb1zjRngqKnZQnm8qjudviNmrjb7lYYuA2 # eDYB+sGniXomU6Ncu9Ky64rLYwgv/h7zViniNZvY/+mlvW1LWSyJLC9Su7UpkNpD # R7xy3bzZv4DB3LCrtEsdWDY3ZOub4YUXmimi/eYI0pL/oPh84emn0TCOXyZQK8ei # 4pd3iu/YTT4m65lAYPM8Zwy2CHIpNVOBNNwwggcEMIIE7KADAgECAgxcuW61kTkv # +4t8zgQwDQYJKoZIhvcNAQELBQAwWTELMAkGA1UEBhMCQkUxGTAXBgNVBAoTEEds # b2JhbFNpZ24gbnYtc2ExLzAtBgNVBAMTJkdsb2JhbFNpZ24gR0NDIFI0NSBDb2Rl # U2lnbmluZyBDQSAyMDIwMB4XDTI0MDMxMTE0MDQxMloXDTI3MDMxMjE0MDQxMlow # cjELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFDASBgNVBAcTC1Nh # bnRhIENsYXJhMRswGQYDVQQKExJQdXJlIFN0b3JhZ2UsIEluYy4xGzAZBgNVBAMT # ElB1cmUgU3RvcmFnZSwgSW5jLjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC # ggIBAMCQrioSn48IvHpTg5dofsUYj/pNTDidwjYUrcxVu78NoyhSweG8FhcxDi/S # I40+8Fccl3D5ZoqpjkFnGhzSwmpxU3J4AP7+fdTZht9eWD1I5qKY07esYwdPDV4y # g+csPfdGPqI2XjRfT5UC3YkXQeUrX8KQZldD4KqvgxzpYcuBwsgHbTb/eArpi68Y # gFR2jgZGyZigfy8RuJMrL1thcBOe/VWjUyK21wVT8cuunBYFaStLHhsRBRMDcZBD # uTSGC4evE6oaCqlQbdMl9YFJ64mDQsKlCxrr7rmLVtcVzKGwmjp4b2xRwE+RmTh6 # JtrUL9Wx/3a3UzgAnDNimfwp85zoL48kyLtHqQ3FI8tVKGm+aBOgBZfmURoy7fbp # 4zKhGgqFbpOmILO16i4f999YsEEJQgIF3CtyH1R60/ZZWlDmoeeEgjAGrnd14muU # 5Hk3Cksr43uPUAg+fV78Y0fDV85ibm42ZwwPuz6MI4HhYNUlGzRwIQ31vjaGuAMW # HNqFKkcO0JuIeHQ/gFKPnYIxnGC9H9R4Kw/uMezqtnYJwGU2epB/ABl/w7U4NgU2 # ZOxWB5BFy4frZ3f+hNgbjFUjMaXnVFotOJxXntzjdSl4znw8DaKiC5ooChteZMIT # G9p078p/TUsOJQbUtFADSY1hsfCfB7t+gJSNt5peS9GOZIMVAgMBAAGjggGxMIIB # rTAOBgNVHQ8BAf8EBAMCB4AwgZsGCCsGAQUFBwEBBIGOMIGLMEoGCCsGAQUFBzAC # hj5odHRwOi8vc2VjdXJlLmdsb2JhbHNpZ24uY29tL2NhY2VydC9nc2djY3I0NWNv # ZGVzaWduY2EyMDIwLmNydDA9BggrBgEFBQcwAYYxaHR0cDovL29jc3AuZ2xvYmFs # c2lnbi5jb20vZ3NnY2NyNDVjb2Rlc2lnbmNhMjAyMDBWBgNVHSAETzBNMEEGCSsG # AQQBoDIBMjA0MDIGCCsGAQUFBwIBFiZodHRwczovL3d3dy5nbG9iYWxzaWduLmNv # bS9yZXBvc2l0b3J5LzAIBgZngQwBBAEwCQYDVR0TBAIwADBFBgNVHR8EPjA8MDqg # OKA2hjRodHRwOi8vY3JsLmdsb2JhbHNpZ24uY29tL2dzZ2NjcjQ1Y29kZXNpZ25j # YTIwMjAuY3JsMBMGA1UdJQQMMAoGCCsGAQUFBwMDMB8GA1UdIwQYMBaAFNqzjcAk # kKNrd9MMoFndIWdkdgt4MB0GA1UdDgQWBBSzJ9KiDCa3UBiAajy+Iioj5kQjzDAN # BgkqhkiG9w0BAQsFAAOCAgEAHsFQixeQEcoHurq9NWSUt4S39Q+UGP6crmVq3Wwy # 9g23YbdWg+SgMxoLUqdoDfA4k4B6Dyoo0jEQzn2kxnsnT9lNHKrcZHH88dv0hjfi # H2qAiQWazPjS3LhK2J6nhpyipJPpyRaSQG4x4aG0NB2D4WUfUz9CGAYsERJGww/w # kTaaxMipttKDTaI1C49u1igDfRzIO+Q8vuyyBFLiYTno/df97xtjNC+KxxFhDhl/ # 4tawK6kwxaVzCMAfj48I67Wbo4DMH6pM1s19as7c3qp92i3MylGKsB6+u+o7UkbS # dLNkS4ALI33CJOUc+GoK3Nt5IXXCFJTQFHBXkBdAur3gmlXEm8vlNG/1Sbxr0H7T # 1e7ABGH/48o/+PeMLuCc72EeK5dJ4cX9NEQ3QnTsZHwGnYzjEOvOvP0s1c7yNsDb # cUHoIqQvb5xS5aqMU5G+8sdPQ1nwpPf7gGaEEbAVW4w51Pam42qeN9HIPa+ZinXn # sN02Kk1Qw0QwUqzaQy9W/gIquI0KOjw0LmoW9M/8S0lrjpEq2eEeUw9WQLhhUEIi # rFxGPtjqiCLiiS9CZ+kf2vWLJKUspkYv+OHT3q805Zg1dJsBFAzEYUFLb1mhmigD # EO9bsMorjECIL2ijE5zHtbGkalrrsPWu8tiDT/B7P9GSYzKfOOy4PoOIfWSK0Ixl # S7IxghpwMIIabAIBATBpMFkxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxT # aWduIG52LXNhMS8wLQYDVQQDEyZHbG9iYWxTaWduIEdDQyBSNDUgQ29kZVNpZ25p # bmcgQ0EgMjAyMAIMXLlutZE5L/uLfM4EMA0GCWCGSAFlAwQCAQUAoIGcMBkGCSqG # SIb3DQEJAzEMBgorBgEEAYI3AgEEMBwGCisGAQQBgjcCAQsxDjAMBgorBgEEAYI3 # AgEVMC8GCSqGSIb3DQEJBDEiBCDnRQ3S20IwVFYFpWpSnxc3XQ+AiWLuJD/WIwUh # ZSm1WDAwBgorBgEEAYI3AgEMMSIwIKACgAChGoAYaHR0cHM6Ly9wdXJlc3RvcmFn # ZS5jb20gMA0GCSqGSIb3DQEBAQUABIICAJSi2SJzaATVuQbkMfNo6jBUv/fWUbxj # 8ikc1jJXmKJPFOWTQhAZmmQSC8TpQaqZcXWdseXjCAiLa+VQu80qgRX9dE0AOoSd # BQ0osHfY1VI1NajQOJfib/3Ng78zx8qvVAFVpTnDkVEwUT4kbsLfDvCRL1JMODar # WJXbmeLPpIyAxFzJWWNVjY8KJd30UwbCMygFmP2NAefrWDv1EVGWN6ZdjC/c6LaX # McWVNnjF17H9m3Kg16GGanIsxgpcpK5gVsQfJLMkc1ea4zI/w/mGirkk902z3CK0 # Gra6z8Rtt/6wt7UlRYFjqmSpQsrqXxn7rk6vy+96BWjwbiYWArduKZE50fo/5YzG # xpBWXMKWAtdpEu/PJ2fEkjvUK1pB//p3Wc6OwLSYE2qVR6gEAQtzUU32AU7X/yld # /WgXRs1n5a/Vz7GPKLL5rs6wVmFby7zhwRSCJko+C5uUujcw2oxFUjgK3J9gInPl # +19/w521+s0JZeRpWNjuXeUqB0k74eN7o+si5ugaVeygjUMA5EGmSbxoeofiF4Lt # 3rfkutNb+Rth+BX1FKtRdq0WGFHxEt2aJxWMVuuf1JRexdisv0M3EP5ZXSa79i4b # 9I2f02aszspdbSPrW4g78z6d/bjpZ+4eY7iA/HdDeGjKlKQ1wxlZRnVDCOm572b8 # fS4eGvM+xwNAoYIXOTCCFzUGCisGAQQBgjcDAwExghclMIIXIQYJKoZIhvcNAQcC # oIIXEjCCFw4CAQMxDzANBglghkgBZQMEAgEFADB3BgsqhkiG9w0BCRABBKBoBGYw # ZAIBAQYJYIZIAYb9bAcBMDEwDQYJYIZIAWUDBAIBBQAEIPcytjXinvYMoFk8+yFf # G8UYH9F4QmjGzaL0KiJM/IQMAhBFd5Xqe3EQrj1O9YaMS2QRGA8yMDI0MTIxMjIx # MjAwMFqgghMDMIIGvDCCBKSgAwIBAgIQC65mvFq6f5WHxvnpBOMzBDANBgkqhkiG # 9w0BAQsFADBjMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4x # OzA5BgNVBAMTMkRpZ2lDZXJ0IFRydXN0ZWQgRzQgUlNBNDA5NiBTSEEyNTYgVGlt # ZVN0YW1waW5nIENBMB4XDTI0MDkyNjAwMDAwMFoXDTM1MTEyNTIzNTk1OVowQjEL # MAkGA1UEBhMCVVMxETAPBgNVBAoTCERpZ2lDZXJ0MSAwHgYDVQQDExdEaWdpQ2Vy # dCBUaW1lc3RhbXAgMjAyNDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB # AL5qc5/2lSGrljC6W23mWaO16P2RHxjEiDtqmeOlwf0KMCBDEr4IxHRGd7+L660x # 5XltSVhhK64zi9CeC9B6lUdXM0s71EOcRe8+CEJp+3R2O8oo76EO7o5tLuslxdr9 # Qq82aKcpA9O//X6QE+AcaU/byaCagLD/GLoUb35SfWHh43rOH3bpLEx7pZ7avVnp # UVmPvkxT8c2a2yC0WMp8hMu60tZR0ChaV76Nhnj37DEYTX9ReNZ8hIOYe4jl7/r4 # 19CvEYVIrH6sN00yx49boUuumF9i2T8UuKGn9966fR5X6kgXj3o5WHhHVO+NBikD # O0mlUh902wS/Eeh8F/UFaRp1z5SnROHwSJ+QQRZ1fisD8UTVDSupWJNstVkiqLq+ # ISTdEjJKGjVfIcsgA4l9cbk8Smlzddh4EfvFrpVNnes4c16Jidj5XiPVdsn5n10j # xmGpxoMc6iPkoaDhi6JjHd5ibfdp5uzIXp4P0wXkgNs+CO/CacBqU0R4k+8h6gYl # dp4FCMgrXdKWfM4N0u25OEAuEa3JyidxW48jwBqIJqImd93NRxvd1aepSeNeREXA # u2xUDEW8aqzFQDYmr9ZONuc2MhTMizchNULpUEoA6Vva7b1XCB+1rxvbKmLqfY/M # /SdV6mwWTyeVy5Z/JkvMFpnQy5wR14GJcv6dQ4aEKOX5AgMBAAGjggGLMIIBhzAO # BgNVHQ8BAf8EBAMCB4AwDAYDVR0TAQH/BAIwADAWBgNVHSUBAf8EDDAKBggrBgEF # BQcDCDAgBgNVHSAEGTAXMAgGBmeBDAEEAjALBglghkgBhv1sBwEwHwYDVR0jBBgw # FoAUuhbZbU2FL3MpdpovdYxqII+eyG8wHQYDVR0OBBYEFJ9XLAN3DigVkGalY17u # T5IfdqBbMFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNv # bS9EaWdpQ2VydFRydXN0ZWRHNFJTQTQwOTZTSEEyNTZUaW1lU3RhbXBpbmdDQS5j # cmwwgZAGCCsGAQUFBwEBBIGDMIGAMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5k # aWdpY2VydC5jb20wWAYIKwYBBQUHMAKGTGh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0 # LmNvbS9EaWdpQ2VydFRydXN0ZWRHNFJTQTQwOTZTSEEyNTZUaW1lU3RhbXBpbmdD # QS5jcnQwDQYJKoZIhvcNAQELBQADggIBAD2tHh92mVvjOIQSR9lDkfYR25tOCB3R # KE/P09x7gUsmXqt40ouRl3lj+8QioVYq3igpwrPvBmZdrlWBb0HvqT00nFSXgmUr # DKNSQqGTdpjHsPy+LaalTW0qVjvUBhcHzBMutB6HzeledbDCzFzUy34VarPnvIWr # qVogK0qM8gJhh/+qDEAIdO/KkYesLyTVOoJ4eTq7gj9UFAL1UruJKlTnCVaM2UeU # UW/8z3fvjxhN6hdT98Vr2FYlCS7Mbb4Hv5swO+aAXxWUm3WpByXtgVQxiBlTVYzq # fLDbe9PpBKDBfk+rabTFDZXoUke7zPgtd7/fvWTlCs30VAGEsshJmLbJ6ZbQ/xll # /HjO9JbNVekBv2Tgem+mLptR7yIrpaidRJXrI+UzB6vAlk/8a1u7cIqV0yef4uaZ # FORNekUgQHTqddmsPCEIYQP7xGxZBIhdmm4bhYsVA6G2WgNFYagLDBzpmk9104WQ # zYuVNsxyoVLObhx3RugaEGru+SojW4dHPoWrUhftNpFC5H7QEY7MhKRyrBe7ucyk # W7eaCuWBsBb4HOKRFVDcrZgdwaSIqMDiCLg4D+TPVgKx2EgEdeoHNHT9l3ZDBD+X # gbF+23/zBjeCtxz+dL/9NWR6P2eZRi7zcEO1xwcdcqJsyz/JceENc2Sg8h3KeFUC # S7tpFk7CrDqkMIIGrjCCBJagAwIBAgIQBzY3tyRUfNhHrP0oZipeWzANBgkqhkiG # 9w0BAQsFADBiMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkw # FwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVz # dGVkIFJvb3QgRzQwHhcNMjIwMzIzMDAwMDAwWhcNMzcwMzIyMjM1OTU5WjBjMQsw # CQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xOzA5BgNVBAMTMkRp # Z2lDZXJ0IFRydXN0ZWQgRzQgUlNBNDA5NiBTSEEyNTYgVGltZVN0YW1waW5nIENB # MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxoY1BkmzwT1ySVFVxyUD # xPKRN6mXUaHW0oPRnkyibaCwzIP5WvYRoUQVQl+kiPNo+n3znIkLf50fng8zH1AT # CyZzlm34V6gCff1DtITaEfFzsbPuK4CEiiIY3+vaPcQXf6sZKz5C3GeO6lE98NZW # 1OcoLevTsbV15x8GZY2UKdPZ7Gnf2ZCHRgB720RBidx8ald68Dd5n12sy+iEZLRS # 8nZH92GDGd1ftFQLIWhuNyG7QKxfst5Kfc71ORJn7w6lY2zkpsUdzTYNXNXmG6jB # ZHRAp8ByxbpOH7G1WE15/tePc5OsLDnipUjW8LAxE6lXKZYnLvWHpo9OdhVVJnCY # Jn+gGkcgQ+NDY4B7dW4nJZCYOjgRs/b2nuY7W+yB3iIU2YIqx5K/oN7jPqJz+ucf # WmyU8lKVEStYdEAoq3NDzt9KoRxrOMUp88qqlnNCaJ+2RrOdOqPVA+C/8KI8ykLc # GEh/FDTP0kyr75s9/g64ZCr6dSgkQe1CvwWcZklSUPRR8zZJTYsg0ixXNXkrqPNF # YLwjjVj33GHek/45wPmyMKVM1+mYSlg+0wOI/rOP015LdhJRk8mMDDtbiiKowSYI # +RQQEgN9XyO7ZONj4KbhPvbCdLI/Hgl27KtdRnXiYKNYCQEoAA6EVO7O6V3IXjAS # vUaetdN2udIOa5kM0jO0zbECAwEAAaOCAV0wggFZMBIGA1UdEwEB/wQIMAYBAf8C # AQAwHQYDVR0OBBYEFLoW2W1NhS9zKXaaL3WMaiCPnshvMB8GA1UdIwQYMBaAFOzX # 44LScV1kTN8uZz/nupiuHA9PMA4GA1UdDwEB/wQEAwIBhjATBgNVHSUEDDAKBggr # BgEFBQcDCDB3BggrBgEFBQcBAQRrMGkwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3Nw # LmRpZ2ljZXJ0LmNvbTBBBggrBgEFBQcwAoY1aHR0cDovL2NhY2VydHMuZGlnaWNl # cnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZFJvb3RHNC5jcnQwQwYDVR0fBDwwOjA4oDag # NIYyaHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZFJvb3RH # NC5jcmwwIAYDVR0gBBkwFzAIBgZngQwBBAIwCwYJYIZIAYb9bAcBMA0GCSqGSIb3 # DQEBCwUAA4ICAQB9WY7Ak7ZvmKlEIgF+ZtbYIULhsBguEE0TzzBTzr8Y+8dQXeJL # Kftwig2qKWn8acHPHQfpPmDI2AvlXFvXbYf6hCAlNDFnzbYSlm/EUExiHQwIgqgW # valWzxVzjQEiJc6VaT9Hd/tydBTX/6tPiix6q4XNQ1/tYLaqT5Fmniye4Iqs5f2M # vGQmh2ySvZ180HAKfO+ovHVPulr3qRCyXen/KFSJ8NWKcXZl2szwcqMj+sAngkSu # mScbqyQeJsG33irr9p6xeZmBo1aGqwpFyd/EjaDnmPv7pp1yr8THwcFqcdnGE4AJ # xLafzYeHJLtPo0m5d2aR8XKc6UsCUqc3fpNTrDsdCEkPlM05et3/JWOZJyw9P2un # 8WbDQc1PtkCbISFA0LcTJM3cHXg65J6t5TRxktcma+Q4c6umAU+9Pzt4rUyt+8SV # e+0KXzM5h0F4ejjpnOHdI/0dKNPH+ejxmF/7K9h+8kaddSweJywm228Vex4Ziza4 # k9Tm8heZWcpw8De/mADfIBZPJ/tgZxahZrrdVcA6KYawmKAr7ZVBtzrVFZgxtGIJ # Dwq9gdkT/r+k0fNX2bwE+oLeMt8EifAAzV3C+dAjfwAL5HYCJtnwZXZCpimHCUcr # 5n8apIUP/JiW9lVUKx+A+sDyDivl1vupL0QVSucTDh3bNzgaoSv27dZ8/DCCBY0w # ggR1oAMCAQICEA6bGI750C3n79tQ4ghAGFowDQYJKoZIhvcNAQEMBQAwZTELMAkG # A1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRp # Z2ljZXJ0LmNvbTEkMCIGA1UEAxMbRGlnaUNlcnQgQXNzdXJlZCBJRCBSb290IENB # MB4XDTIyMDgwMTAwMDAwMFoXDTMxMTEwOTIzNTk1OVowYjELMAkGA1UEBhMCVVMx # FTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNv # bTEhMB8GA1UEAxMYRGlnaUNlcnQgVHJ1c3RlZCBSb290IEc0MIICIjANBgkqhkiG # 9w0BAQEFAAOCAg8AMIICCgKCAgEAv+aQc2jeu+RdSjwwIjBpM+zCpyUuySE98orY # WcLhKac9WKt2ms2uexuEDcQwH/MbpDgW61bGl20dq7J58soR0uRf1gU8Ug9SH8ae # FaV+vp+pVxZZVXKvaJNwwrK6dZlqczKU0RBEEC7fgvMHhOZ0O21x4i0MG+4g1ckg # HWMpLc7sXk7Ik/ghYZs06wXGXuxbGrzryc/NrDRAX7F6Zu53yEioZldXn1RYjgwr # t0+nMNlW7sp7XeOtyU9e5TXnMcvak17cjo+A2raRmECQecN4x7axxLVqGDgDEI3Y # 1DekLgV9iPWCPhCRcKtVgkEy19sEcypukQF8IUzUvK4bA3VdeGbZOjFEmjNAvwjX # WkmkwuapoGfdpCe8oU85tRFYF/ckXEaPZPfBaYh2mHY9WV1CdoeJl2l6SPDgohIb # Zpp0yt5LHucOY67m1O+SkjqePdwA5EUlibaaRBkrfsCUtNJhbesz2cXfSwQAzH0c # lcOP9yGyshG3u3/y1YxwLEFgqrFjGESVGnZifvaAsPvoZKYz0YkH4b235kOkGLim # dwHhD5QMIR2yVCkliWzlDlJRR3S+Jqy2QXXeeqxfjT/JvNNBERJb5RBQ6zHFynIW # IgnffEx1P2PsIV/EIFFrb7GrhotPwtZFX50g/KEexcCPorF+CiaZ9eRpL5gdLfXZ # qbId5RsCAwEAAaOCATowggE2MA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFOzX # 44LScV1kTN8uZz/nupiuHA9PMB8GA1UdIwQYMBaAFEXroq/0ksuCMS1Ri6enIZ3z # bcgPMA4GA1UdDwEB/wQEAwIBhjB5BggrBgEFBQcBAQRtMGswJAYIKwYBBQUHMAGG # GGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBDBggrBgEFBQcwAoY3aHR0cDovL2Nh # Y2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENBLmNydDBF # BgNVHR8EPjA8MDqgOKA2hjRodHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNl # cnRBc3N1cmVkSURSb290Q0EuY3JsMBEGA1UdIAQKMAgwBgYEVR0gADANBgkqhkiG # 9w0BAQwFAAOCAQEAcKC/Q1xV5zhfoKN0Gz22Ftf3v1cHvZqsoYcs7IVeqRq7IviH # GmlUIu2kiHdtvRoU9BNKei8ttzjv9P+Aufih9/Jy3iS8UgPITtAq3votVs/59Pes # MHqai7Je1M/RQ0SbQyHrlnKhSLSZy51PpwYDE3cnRNTnf+hZqPC/Lwum6fI0POz3 # A8eHqNJMQBk1RmppVLC4oVaO7KTVPeix3P0c2PR3WlxUjG/voVA9/HYJaISfb8rb # II01YBwCA8sgsKxYoA5AY8WYIsGyWfVVa88nq2x2zm8jLfR+cWojayL/ErhULSd+ # 2DrZ8LaHlv1b0VysGMNNn3O3AamfV6peKOK5lDGCA3YwggNyAgEBMHcwYzELMAkG # A1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMTswOQYDVQQDEzJEaWdp # Q2VydCBUcnVzdGVkIEc0IFJTQTQwOTYgU0hBMjU2IFRpbWVTdGFtcGluZyBDQQIQ # C65mvFq6f5WHxvnpBOMzBDANBglghkgBZQMEAgEFAKCB0TAaBgkqhkiG9w0BCQMx # DQYLKoZIhvcNAQkQAQQwHAYJKoZIhvcNAQkFMQ8XDTI0MTIxMjIxMjAwMFowKwYL # KoZIhvcNAQkQAgwxHDAaMBgwFgQU29OF7mLb0j575PZxSFCHJNWGW0UwLwYJKoZI # hvcNAQkEMSIEIFowxTTbWRP6tBXTYkIZkcCBCbS2saeJBtPh17b8B/YIMDcGCyqG # SIb3DQEJEAIvMSgwJjAkMCIEIHZ2n6jyYy8fQws6IzCu1lZ1/tdz2wXWZbkFk5hD # j5rbMA0GCSqGSIb3DQEBAQUABIICADlMSa8xuvB/0wTpKF6gqhI0c6Zix83w1w6R # 4RzIZ09XgfGYxGxrjKSmX5hTb8OpjgjhZG2h8Y7clgAMuvB/fY9NW12kq6kpmIRj # Zx5gL2rkB99cTkUPPspRZnP3srNjJVwPUwlEAX6Tz+GNFRMRgituCvXsmz+hPIIe # Tfw4Zj6N9lcBHQ0ZVP7zNn6IFi4NDDGubKuGbyYVPL9PwzQRV5cA/EYpjeaCNxnV # xxdb/RWG5bp6PUHnYzuRvDf1bFGtFzs1hnnwH1AYIboG6+bAN9jxRe/6o808x5dG # E4SnG4bvbjvgvBDv04JhS+8mur/QVuqlwSiAn4cbJANeD2jd8OuhiH0/9gPz9MXY # yy4Ee/kCvIsBrOeC3O/7W3y/Dv9c5a4r0iAtOEsRLn7pXT+kjFU4ZjEw+ZqMMkzP # Sa/ErT4J9jJapujvoMIzTUCyI0QSQvycCc0oAQMVeL6fuRtcfF2FywYgx7cWLMsE # dtzzrOOfkkKG3sKK29R3Ps84QzmiGFbx/PrXLzBr/Gx0PZbKXOMVkd/tGiuvwk1S # WtTG4lnzXfgQKwEpiP1RtQC9eVGegv7PKampijran7O0WLG3REtee4QeisVNtlTY # GiKrQ/5AXxzY23Y6xjI610eUPWBHqe9TswOG6qjmxppC43S2ElJUv6SadYnNdcak # QSviB8OC # SIG # End signature block |