PureStorage.CBS.AVS.psm1
. $PSScriptRoot/PureStorage.CommonUtil.ps1 . $PSScriptRoot/PureStorage.RunCommandLauncher.ps1 . $PSScriptRoot/PureStorage.CBS.AVS.VMFS.ps1 . $PSScriptRoot/PureStorage.CBS.AVS.Configuration.ps1 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 Pure Cloud Block Store Connection. The connection can be created using Connect-Pf2Array cmdlet .PARAMETER PureCloudBlockStoreCredential Pure Cloud Block Store Credentials .PARAMETER AVSCloudName AVS cloud name .PARAMETER AzureSubscriptionId Azure subscription Id. If not provided, default subscription will be used .PARAMETER AVSResourceGroup AVS ResourceGroup .EXAMPLE Build-PCBSCluster -ClusterName "mycluster" -PureCloudBlockStoreConnection $CBSConnection ` -AVSCloudName $AVSCloudName -AzureSubscriptionId $AzureSubscriptionId -AVSResourceGroup $AVSResourceGroup #> [CmdletBinding()] Param ( [Parameter(Mandatory=$true, ValueFromPipeline=$true)] [String]$ClusterName, [Parameter(Mandatory=$false)] $PureCloudBlockStoreConnection, [Parameter(Mandatory=$false)] [String]$AVSCloudName, [Parameter(Mandatory=$false)] [String]$AzureSubscriptionId, [Parameter(Mandatory=$false)] [String]$AVSResourceGroup ) Write-Progress -Activity "Building cluster" -Status "0% Complete:" -PercentComplete 1 Connect-AVSvCenter -AVSResourceGroupName $AVSResourceGroup -AVSPrivateCloudName $AVSCloudName -AzureSubscriptionId $AzureSubscriptionId $cluster = Get-Cluster -Name $ClusterName if (-not $cluster) { throw "Could not find cluster '$ClusterName'..." } $fa = Connect-PureCloudBlockStore -PureCloudBlockStoreConnection $PureCloudBlockStoreConnection Write-Progress -Activity "Configuring iSCSI" -Status "25% Complete:" -PercentComplete 25 $updated_hosts = New-PCBSHostGroupfromVcCluster -FlashArray $fa -Cluster $cluster ` -AVSCloudName $AVSCloudName -AzureSubscriptionId $AzureSubscriptionId -AVSResourceGroup $AVSResourceGroup Write-Progress -Activity "Removing unused hosts" -Status "50% Complete:" -PercentComplete 50 Remove-PCBSUnusedHosts -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 -CmdletName "Remove-VMHostStaticiSCSITargets" -Parameters $params ` -AVSCloudName $AVSCloudName -AzureSubscriptionId $AzureSubscriptionId -AVSResourceGroup $AVSResourceGroup } Write-Host "Cluster '$ClusterName' is successfully built" Write-Progress -Activity "Operation is done" -Status "100% Complete:" -PercentComplete 100 } 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 PureCloudBlockStoreConnection Pure Cloud Block Store Connection. The connection can be created using Connect-Pf2Array cmdlet .PARAMETER AVSCloudName AVS cloud name .PARAMETER AzureSubscriptionId Azure subscription Id. If not provided, default subscription will be used .PARAMETER AVSResourceGroup AVS ResourceGroup .EXAMPLE New-PCBSVmfsDatastore -ClusterName myClusterName -PureCloudBlockStoreConnection $CBSConnection -DatastoreName MyVMFSStore -Size 4GB ` -AVSCloudName $AVSCloudName -AzureSubscriptionId $AzureSubscriptionId -AVSResourceGroup $AVSResourceGroup Create a datastore "MyVMFS" #> [CmdletBinding()] Param ( [Parameter(Mandatory=$true)] [String]$ClusterName, [Parameter(Mandatory=$true, ValueFromPipeline=$true)] [String]$DatastoreName, [Parameter(Mandatory=$true)] [UInt64]$Size, [Parameter(Mandatory=$false)] $PureCloudBlockStoreConnection, [Parameter(Mandatory=$false)] [String]$AVSCloudName, [Parameter(Mandatory=$false)] [String]$AzureSubscriptionId, [Parameter(Mandatory=$false)] [String]$AVSResourceGroup ) Write-Progress -Activity "Creating datastore" -Status "0% Complete:" -PercentComplete 1 Connect-AVSvCenter -AVSResourceGroupName $AVSResourceGroup -AVSPrivateCloudName $AVSCloudName -AzureSubscriptionId $AzureSubscriptionId $Cluster = Get-Cluster -Name $ClusterName -ErrorAction Ignore if (-not $Cluster) { throw "Cluster $ClusterName does not exist." } $fa = Connect-PureCloudBlockStore -PureCloudBlockStoreConnection $PureCloudBlockStoreConnection New-PfaVmfs -Cluster $Cluster -Flasharray $fa -Name $DatastoreName -Size $Size ` -AVSCloudName $AVSCloudName -AzureSubscriptionId $AzureSubscriptionId -AVSResourceGroup $AVSResourceGroup Write-Host "Datastore '$DatastoreName' is successfully created" Write-Progress -Activity "Operation is done" -Status "100% Complete:" -Completed } 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 Pure Cloud Block Store Connection. The connection can be created using Connect-Pf2Array cmdlet .PARAMETER DatastoreName Datastore name. Optional parameter. If the parameter is not specified, a generated name will be used. .PARAMETER AVSCloudName AVS cloud name .PARAMETER AzureSubscriptionId Azure subscription Id. If not provided, default subscription will be used .PARAMETER AVSResourceGroup AVS ResourceGroup .EXAMPLE Restore-PCBSVmfsDatastore -ClusterName myClusterName -VolumeSnapshotName mySnapshotName -PureCloudBlockStoreConnection $CBSConnection ` -AVSCloudName $AVSCloudName -AzureSubscriptionId $AzureSubscriptionId -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 -AzureSubscriptionId $AzureSubscriptionId -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=$false)] [String]$AVSCloudName, [Parameter(Mandatory=$false)] [String]$AzureSubscriptionId, [Parameter(Mandatory=$false)] [String]$AVSResourceGroup ) Write-Progress -Activity "Restoring datastore" -Status "0% Complete:" -PercentComplete 1 Connect-AVSvCenter -AVSResourceGroupName $AVSResourceGroup -AVSPrivateCloudName $AVSCloudName -AzureSubscriptionId $AzureSubscriptionId $fa = Connect-PureCloudBlockStore -PureCloudBlockStoreConnection $PureCloudBlockStoreConnection $Cluster = Get-Cluster -Name $ClusterName -ErrorAction Ignore if (-not $Cluster) { throw "Cluster '$ClusterName' does not exist." } if (-not [string]::IsNullOrEmpty($DatastoreName)) { $Datastore = Get-Datastore -Name $DatastoreName -ErrorAction Ignore if ($Datastore) { throw "Datastore '$Datastore' already exists." } } switch ($PSCmdlet.ParameterSetName) { 'Volume' { Write-Host "Creating datastore from volume..." $NewDatastore = Restore-PfaVmfsFromVolume -FlashArray $fa -ClusterName $ClusterName -VolumeName $VolumeName -DatastoreName $DatastoreName ` -AVSCloudName $AVSCloudName -AzureSubscriptionId $AzureSubscriptionId -AVSResourceGroup $AVSResourceGroup break } 'VolumeSnapshot' { Write-Host "Creating datastore from volume snapshot..." $NewDatastore = Restore-PfaVmfsFromVolumeSnapshot -FlashArray $fa -ClusterName $ClusterName -VolumeSnapshotName $VolumeSnapshotName -DatastoreName $DatastoreName ` -AVSCloudName $AVSCloudName -AzureSubscriptionId $AzureSubscriptionId -AVSResourceGroup $AVSResourceGroup break } 'ProtectionGroupSnapshot' { Write-Host "Creating datastore from protection group snapshot..." $NewDatastore = Restore-PfaVmfsFromProtectionGroupSnapshot -FlashArray $fa -ClusterName $ClusterName -ProtectionGroupSnapshotName $ProtectionGroupSnapshotName ` -AVSCloudName $AVSCloudName -AzureSubscriptionId $AzureSubscriptionId -AVSResourceGroup $AVSResourceGroup break } 'Pod' { Write-Host "Creating datastore from pod..." $NewDatastore = Restore-PfaVmfsFromPod -FlashArray $fa -ClusterName $ClusterName -PodName $PodName ` -AVSCloudName $AVSCloudName -AzureSubscriptionId $AzureSubscriptionId -AVSResourceGroup $AVSResourceGroup break } } Write-Progress -Activity "Operation is done" -Status "100% Complete:" -Completed Write-Host "Datastore '$($NewDatastore.Name)' is successfully restored" 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 Pure Cloud Block Store Connection. The connection can be created using Connect-Pf2Array cmdlet .PARAMETER AVSCloudName AVS cloud name .PARAMETER AzureSubscriptionId Azure subscription Id. If not provided, default subscription will be used .PARAMETER AVSResourceGroup AVS ResourceGroup .EXAMPLE Remove-PCBSVmfsDatastore -ClusterName "mycluster" -DatastoreName "myDatastore" PureCloudBlockStoreConnection $CBSConnection ` -AVSCloudName $AVSCloudName -AzureSubscriptionId $AzureSubscriptionId -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=$false)] [String]$AVSCloudName, [Parameter(Mandatory=$false)] [String]$AzureSubscriptionId, [Parameter(Mandatory=$false)] [String]$AVSResourceGroup ) Write-Progress -Activity "Removing datastore" -Status "0% Complete:" -PercentComplete 1 Connect-AVSvCenter -AVSResourceGroupName $AVSResourceGroup -AVSPrivateCloudName $AVSCloudName -AzureSubscriptionId $AzureSubscriptionId $FlashArray = Connect-PureCloudBlockStore -PureCloudBlockStoreConnection $PureCloudBlockStoreConnection Remove-PfaVmfsDatastore -ClusterName $ClusterName -DatastoreName $DataStoreName -FlashArray $FlashArray ` -AVSCloudName $AVSCloudName -AzureSubscriptionId $AzureSubscriptionId -AVSResourceGroup $AVSResourceGroup Write-Host "Datastore '$DatastoreName' is successfully removed" Write-Progress -Activity "Operation is done" -Status "100% Complete:" -Completed } 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 Pure Cloud Block Store Connection. The connection can be created using Connect-Pf2Array cmdlet .PARAMETER AVSCloudName AVS cloud name .PARAMETER AzureSubscriptionId Azure subscription Id. If not provided, default subscription will be used .PARAMETER AVSResourceGroup AVS ResourceGroup .EXAMPLE Set-PCBSVmfsCapacity -ClusterName "mycluster" -DatastoreName myDatastore -Size 3GB -PureCloudBlockStoreConnection $CBSConnection ` -AVSCloudName $AVSCloudName -AzureSubscriptionId $AzureSubscriptionId -AVSResourceGroup $AVSResourceGroup Expand size of the datastore myDatastore to 3GB. #> [CmdletBinding()] Param ( [Parameter(Mandatory=$true)] [String]$ClusterName, [Parameter(Mandatory=$true, ValueFromPipeline=$true)] [string]$DatastoreName, [Parameter(Mandatory=$true)] [UInt64]$Size, [Parameter(Mandatory=$false)] $PureCloudBlockStoreConnection, [Parameter(Mandatory=$false)] [String]$AVSCloudName, [Parameter(Mandatory=$false)] [String]$AzureSubscriptionId, [Parameter(Mandatory=$false)] [String]$AVSResourceGroup ) Write-Progress -Activity "Resizing datastore" -Status "0% Complete:" -PercentComplete 1 Connect-AVSvCenter -AVSResourceGroupName $AVSResourceGroup -AVSPrivateCloudName $AVSCloudName -AzureSubscriptionId $AzureSubscriptionId $FlashArray = Connect-PureCloudBlockStore -PureCloudBlockStoreConnection $PureCloudBlockStoreConnection $Cluster = Get-Cluster -Name $ClusterName -ErrorAction Ignore if (-not $Cluster) { throw "Cluster $ClusterName does not exist." } Set-PfaVmfsCapacity -ClusterName $ClusterName -FlashArray $FlashArray -DatastoreName $DatastoreName -SizeInByte $Size ` -AVSCloudName $AVSCloudName -AzureSubscriptionId $AzureSubscriptionId -AVSResourceGroup $AVSResourceGroup Write-Host "Datastore '$DatastoreName' is successfully resized" Write-Progress -Activity "Operation is done" -Status "100% Complete:" -Completed } <# .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 .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 ) $ResourceGroup = Get-AzResourceGroup $MonitorResourceGroup -ErrorAction ignore if (-not $ResourceGroup) { throw "Pure Storage CBS AVS monitor $MonitorResourceGroup does not exist" } if ([string]::IsNullOrEmpty($ResourceGroup.Tags["PureStorage.CBS.AVS"])) { throw "The resource group provided is not Pure Storage CBS AVS monitor resource group. Only Pure Storage CBS AVS monitor resource group can be removed by this command" } # smartDetector is auto configured without tag. Ignore this component $NonMonitorResources = Get-AzResource -ResourceGroupName $MonitorResourceGroup | Where-Object { [string]::IsNullOrEmpty($_.tags["AVSMonitorResourceGroupName"]) -and $_.ResourceType -ne "microsoft.alertsmanagement/smartDetectorAlertRules"} if ($NonMonitorResources.Count -ge 1) { throw "Non Pure Storage CBS AVS monitor resource $($MonitorResources.Name) detected. Please manually remove the resource before removing the whole monitor" } $MonitorFuncApp = Get-AzFunctionApp -ResourceGroupName $MonitorResourceGroup $MonitorKeyVault = Get-AzKeyVault -ResourceGroupName $MonitorResourceGroup # Vnet name here is constructed as {vNetResouceGUI}-{SubnetName} # eg. fece391b-8f4e-4e05-a203-e5961cdd9fd1_subnet-avsfuncappsbqzuuqxofe2q $vNetResourceGUID = $MonitorFuncApp.SiteConfig.VnetName.Split("_")[0] $MonitorSubnetName = $MonitorFuncApp.SiteConfig.VnetName.Split("_")[1] $MonitorVNet = get-AzVirtualNetwork | Where-Object {$_.ResourceGuid -eq $vNetResourceGUID} Write-Host "Removing resource group $MonitorResourceGroup..." Remove-AzResourceGroup $MonitorResourceGroup -Force | Out-Null # Remove subnet Write-Host "Removing subnet $MonitorSubnetName from vNet $($MonitorVNet.Name)..." Remove-AzVirtualNetworkSubnetConfig -Name $MonitorSubnetName -VirtualNetwork $MonitorVNet | Set-AzVirtualNetwork | Out-Null # Purge key vault Write-Host "Purging key vault $($MonitorKeyVault.VaultName)..." Remove-AzKeyVault -Name $MonitorKeyVault.VaultName -InRemovedState -Force -Location $ResourceGroup.Location | Out-Null } <# .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 ResourceGroup .PARAMETER PureCloudBlockStoreEndpoint Pure Cloud Block Store endpoint address .PARAMETER PureCloudBlockStoreCredential Pure Cloud Block Store credentials .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 MonitorIntervalInMinute Optional. The default monitor interval is 10 minutes. .EXAMPLE Deploy-PCBSAVSMonitor -PureCloudBlockStoreEndpoint "192.168.2.100" -PureCloudBlockStoreCredential (Get-Credential) -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]$PureCloudBlockStoreEndpoint, [Parameter(Mandatory=$true)] [pscredential]$PureCloudBlockStoreCredential, [Parameter(Mandatory=$true)] [String]$VNetName, [Parameter(Mandatory=$true)] [String]$VNetResourceGroup, [Parameter(Mandatory=$true)] [String]$VNetSubnetAddress, [Parameter(Mandatory=$false)] [ValidateScript({ $_ -ge 10 }, ErrorMessage = "The minimum interval for the monitor is 10 minutes.")] [int]$MonitorIntervalInMinute = 10 ) $ProductVersion = (Get-Module "PureStorage.CBS.AVS").Version.ToString() $ResourceGroup = Get-AzResourceGroup $MonitorResourceGroup -ErrorAction ignore if (-not $ResourceGroup) { Write-Host "Resource group $MonitorResourceGroup does not exist. Creating the resource group..." New-AzResourceGroup $MonitorResourceGroup -Location $MonitorResourceGroupRegion -Tag @{'PureStorage.CBS.AVS' = $ProductVersion} | Out-Null } else { if ($ResourceGroup.location -ne $MonitorResourceGroupRegion) { throw "The resource group $MonitorResourceGroup exists but its region $($ResourceGroup.location) does not match provided region $MonitorResourceGroupRegion" } # If the resource group exists and it's empty, we'll use the resource group even though there is no tag $Resources = Get-AzResource -ResourceGroupName $MonitorResourceGroup if ($Resources.Count -eq 0) { Set-AzResourceGroup -Name $MonitorResourceGroup -Tag @{'PureStorage.CBS.AVS' = $ProductVersion} } elseif (-not $ResourceGroup.Tags["PureStorage.CBS.AVS"]) { throw "The resource group $MonitorResourceGroup exists but not used by Pure Storage CBS AVS monitor. Please select another name for Pure Storage CBS AVS monitor" } } $DeploymentTemplatePath = Join-Path -Path $PSScriptRoot -ChildPath 'templates' -AdditionalChildPath 'Main.bicep' $DeploymentId = (New-Guid).ToString() $DeploymentParams = @{ "PureCloudBlockStoreEndpoint" = $PureCloudBlockStoreEndpoint; "PureCloudBlockStoreUsername" = $PureCloudBlockStoreCredential.UserName; "PureCloudBlockStorePassword" = $PureCloudBlockStoreCredential.Password; "AVSCloudName" = $AVSCloudName; "AVSResourceGroup" = $AVSResourceGroup; "VNetName" = $VNetName; "VNetResourceGroupName" = $VNetResourceGroup; "SubnetAddressRange" = $VNetSubnetAddress; "MonitorIntervalInMinute" = $MonitorIntervalInMinute "DeploymentId" = $DeploymentId } Write-Host "Deploying AVS monitoring infrastructure to Azure..." New-AzResourceGroupDeployment -Name "PCBSMonitorDeployment_$DeploymentId" -ResourceGroupName $MonitorResourceGroup -TemplateFile $DeploymentTemplatePath -TemplateParameterObject $DeploymentParams } <# .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 .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 ) $ResourceGroup = Get-AzResourceGroup -Name $MonitorResourceGroup if (-not $ResourceGroup) { throw "Resource group $MonitorResourceGroup does not exist" } if (-not $ResourceGroup.Tags["PureStorage.CBS.AVS"]) { throw "Resouce group $MonitorResourceGroup specified does not host Pure Storage CBS AVS monitor" } $PureCloudBlockStoreEndpointOrigin = $PureCloudBlockStoreEndpoint Write-Host "Adding Pure Cloud Block Store $PureCloudBlockStoreEndpointOrigin to monitor resource group $MonitorResourceGroup..." $UserPrincipalName = (Get-AzContext).Account.Id $KeyVault = Get-AzKeyVault -ResourceGroupName $MonitorResourceGroup Set-AzKeyVaultAccessPolicy -VaultName $KeyVault.VaultName -UserPrincipalName $UserPrincipalName -PermissionsToSecrets set,delete,get,purge,list # Make sure the credential works before adding to the monitor $Array = Connect-Pfa2array -Endpoint $PureCloudBlockStoreEndpoint -Credential $PureCloudBlockStoreCredential -IgnoreCertificateError -ErrorAction Stop if (-not $Array) { throw "Failed to connect to the Pure Cloud Block Store. Please check the endpoint and credential of the Pure Cloud Block Store." } if ($PureCloudBlockStoreEndpoint -match "^\d+.\d+.\d+.\d+$") { $PureCloudBlockStoreEndpoint = $PureCloudBlockStoreEndpoint.Replace(".", "-") } $Secret = Get-AzKeyVaultSecret -VaultName $KeyVault.VaultName | where-object {$_.Name -eq "$($PureCloudBlockStoreEndpoint)-$($KeyVault.VaultName)-username"} if ($Secret) { Write-Host "Overriding the existing credential for Pure Cloud Block Store $PureCloudBlockStoreEndpointOrigin..." } Set-AzKeyVaultSecret -VaultName $KeyVault.VaultName -Name "$($PureCloudBlockStoreEndpoint)-$($KeyVault.VaultName)-username" -SecretValue (ConvertTo-SecureString -String $PureCloudBlockStoreCredential.UserName -AsPlainText -Force) Set-AzKeyVaultSecret -VaultName $KeyVault.VaultName -Name "$($PureCloudBlockStoreEndpoint)-$($KeyVault.VaultName)-password" -SecretValue $PureCloudBlockStoreCredential.Password Write-Host "The Pure Cloud Block Store $PureCloudBlockStoreEndpointOrigin is successfully added to monitor resource group $MonitorResourceGroup." } <# .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 ) $ResourceGroup = Get-AzResourceGroup -Name $MonitorResourceGroup if (-not $ResourceGroup) { throw "Resource group $MonitorResourceGroup does not exist" } if (-not $ResourceGroup.Tags["PureStorage.CBS.AVS"]) { throw "Resouce group $MonitorResourceGroup specified does not host Pure Storage CBS AVS monitor" } $PureCloudBlockStoreEndpointOrigin = $PureCloudBlockStoreEndpoint $KeyVault = Get-AzKeyVault -ResourceGroupName $MonitorResourceGroup Write-Host "Removing Pure Cloud Block Store $PureCloudBlockStoreEndpointOrigin from monitor resource group $MonitorResourceGroup..." $UserPrincipalName = (Get-AzContext).Account.Id Set-AzKeyVaultAccessPolicy -VaultName $KeyVault.VaultName -UserPrincipalName $UserPrincipalName -PermissionsToSecrets set,delete,get,purge,list if ($PureCloudBlockStoreEndpoint -match "^\d+.\d+.\d+.\d+$") { $PureCloudBlockStoreEndpoint = $PureCloudBlockStoreEndpoint.Replace(".", "-") } $Secret = Get-AzKeyVaultSecret -VaultName $KeyVault.VaultName | where-object {$_.Name -eq "$($PureCloudBlockStoreEndpoint)-$($KeyVault.VaultName)-username"} if (-not $Secret) { throw "Pure Cloud Block Store $PureCloudBlockStoreEndpointOrigin does not exist in the monitor resource group $MonitorResourceGroup" } Remove-AzKeyVaultSecret -VaultName $KeyVault.VaultName -Name "$($PureCloudBlockStoreEndpoint)-$($KeyVault.VaultName)-username" -Force Remove-AzKeyVaultSecret -VaultName $KeyVault.VaultName -Name "$($PureCloudBlockStoreEndpoint)-$($KeyVault.VaultName)-password" -Force # Purge secret Purge-AzureSecretWithRetry -KeyVaultName $KeyVault.VaultName -SecretName "$($PureCloudBlockStoreEndpoint)-$($KeyVault.VaultName)-username" Purge-AzureSecretWithRetry -KeyVaultName $KeyVault.VaultName -SecretName "$($PureCloudBlockStoreEndpoint)-$($KeyVault.VaultName)-password" Write-Host "The Pure Cloud Block Store $PureCloudBlockStoreEndpointOrigin is successfully removed." } 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 # SIG # Begin signature block # MIIjUAYJKoZIhvcNAQcCoIIjQTCCIz0CAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB # gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR # AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQUWvdSsSObgRIhERm2nYDgQQxM # G3Kggh12MIIFMDCCBBigAwIBAgIQBAkYG1/Vu2Z1U0O1b5VQCDANBgkqhkiG9w0B # AQsFADBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYD # VQQLExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVk # IElEIFJvb3QgQ0EwHhcNMTMxMDIyMTIwMDAwWhcNMjgxMDIyMTIwMDAwWjByMQsw # CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu # ZGlnaWNlcnQuY29tMTEwLwYDVQQDEyhEaWdpQ2VydCBTSEEyIEFzc3VyZWQgSUQg # Q29kZSBTaWduaW5nIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA # +NOzHH8OEa9ndwfTCzFJGc/Q+0WZsTrbRPV/5aid2zLXcep2nQUut4/6kkPApfmJ # 1DcZ17aq8JyGpdglrA55KDp+6dFn08b7KSfH03sjlOSRI5aQd4L5oYQjZhJUM1B0 # sSgmuyRpwsJS8hRniolF1C2ho+mILCCVrhxKhwjfDPXiTWAYvqrEsq5wMWYzcT6s # cKKrzn/pfMuSoeU7MRzP6vIK5Fe7SrXpdOYr/mzLfnQ5Ng2Q7+S1TqSp6moKq4Tz # rGdOtcT3jNEgJSPrCGQ+UpbB8g8S9MWOD8Gi6CxR93O8vYWxYoNzQYIH5DiLanMg # 0A9kczyen6Yzqf0Z3yWT0QIDAQABo4IBzTCCAckwEgYDVR0TAQH/BAgwBgEB/wIB # ADAOBgNVHQ8BAf8EBAMCAYYwEwYDVR0lBAwwCgYIKwYBBQUHAwMweQYIKwYBBQUH # AQEEbTBrMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wQwYI # KwYBBQUHMAKGN2h0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEFz # c3VyZWRJRFJvb3RDQS5jcnQwgYEGA1UdHwR6MHgwOqA4oDaGNGh0dHA6Ly9jcmw0 # LmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcmwwOqA4oDaG # NGh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RD # QS5jcmwwTwYDVR0gBEgwRjA4BgpghkgBhv1sAAIEMCowKAYIKwYBBQUHAgEWHGh0 # dHBzOi8vd3d3LmRpZ2ljZXJ0LmNvbS9DUFMwCgYIYIZIAYb9bAMwHQYDVR0OBBYE # FFrEuXsqCqOl6nEDwGD5LfZldQ5YMB8GA1UdIwQYMBaAFEXroq/0ksuCMS1Ri6en # IZ3zbcgPMA0GCSqGSIb3DQEBCwUAA4IBAQA+7A1aJLPzItEVyCx8JSl2qB1dHC06 # GsTvMGHXfgtg/cM9D8Svi/3vKt8gVTew4fbRknUPUbRupY5a4l4kgU4QpO4/cY5j # DhNLrddfRHnzNhQGivecRk5c/5CxGwcOkRX7uq+1UcKNJK4kxscnKqEpKBo6cSgC # PC6Ro8AlEeKcFEehemhor5unXCBc2XGxDI+7qPjFEmifz0DLQESlE/DmZAwlCEIy # sjaKJAL+L3J+HNdJRZboWR3p+nRka7LrZkPas7CM1ekN3fYBIM6ZMWM9CBoYs4Gb # T8aTEAb8B4H6i9r5gkn3Ym6hU/oSlBiFLpKR6mhsRDKyZqHnGKSaZFHvMIIFNzCC # BB+gAwIBAgIQC4jZOitkx57ksuMgsWXX0jANBgkqhkiG9w0BAQsFADByMQswCQYD # VQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGln # aWNlcnQuY29tMTEwLwYDVQQDEyhEaWdpQ2VydCBTSEEyIEFzc3VyZWQgSUQgQ29k # ZSBTaWduaW5nIENBMB4XDTIwMDczMDAwMDAwMFoXDTIzMTAwNDEyMDAwMFowdDEL # MAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDU1vdW50 # YWluIFZpZXcxGzAZBgNVBAoTElB1cmUgU3RvcmFnZSwgSW5jLjEbMBkGA1UEAxMS # UHVyZSBTdG9yYWdlLCBJbmMuMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC # AQEA6nefE6+A0nNMY82xtkQb+akwI0oxLqEbRY65bE4re+CVQV2xP89/7FIAXooq # jxNOvrtWicWTGOZjBdAFEXXAUEyu9CkWFOXGLV3/QkcEfY3e3Z3jypa6h1EznSEp # 3wSQEIVbigi6jR2s7NDnDoSDAKnzcGcSZ8Nz7akXFrN8PAmg3gy8a/rwm2Ko7ClR # ZUHj1C/OMPXUqiN0Q4FyAsaeFmvg2PX2twxo192WRdNro1dkKNfmDvym2ss6MXcq # gFEjBcZtHDv3e/i0BjT24Jm1C27IVYZzVf28dqmVfPo7l8bLEHsVKgrRf0VzS2wI # R08gXiMrohf9tv3AbvtPk7ZaawIDAQABo4IBxTCCAcEwHwYDVR0jBBgwFoAUWsS5 # eyoKo6XqcQPAYPkt9mV1DlgwHQYDVR0OBBYEFJOwFn7S4Lj6QdNnH6tpm3FMFWJI # MA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzB3BgNVHR8EcDBu # MDWgM6Axhi9odHRwOi8vY3JsMy5kaWdpY2VydC5jb20vc2hhMi1hc3N1cmVkLWNz # LWcxLmNybDA1oDOgMYYvaHR0cDovL2NybDQuZGlnaWNlcnQuY29tL3NoYTItYXNz # dXJlZC1jcy1nMS5jcmwwTAYDVR0gBEUwQzA3BglghkgBhv1sAwEwKjAoBggrBgEF # BQcCARYcaHR0cHM6Ly93d3cuZGlnaWNlcnQuY29tL0NQUzAIBgZngQwBBAEwgYQG # CCsGAQUFBwEBBHgwdjAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQu # Y29tME4GCCsGAQUFBzAChkJodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGln # aUNlcnRTSEEyQXNzdXJlZElEQ29kZVNpZ25pbmdDQS5jcnQwDAYDVR0TAQH/BAIw # ADANBgkqhkiG9w0BAQsFAAOCAQEAMVAa7mXxhBHf0dTzf6LKDncN8KnzB59KBvd0 # KvXZc6FoLHGi2Wg6XBSP+9mdDMMYOkohqstSk7RD+reT8xiptrIkSMcVcTog1Z3e # JjYTK8B7QsSpuu2lo0RWA5rdvqMJ+lVzbbjteTq+uicP4T/EDwv2q+iPAgpXQD8y # r084ExDWJtMfhvy0cxh555xx88rvFWOhJnXYiFtjaO9dp7f2TnZRJ44rmB98jc9E # BR/8GLOi/BhyPiiU4nBv8JIHVP1E5zIt8/9PhfpenmiWBbuuP0YLnzrqRhswtJaq # jJirYNLYojmINrbvdcpEKGK1AitsnuOjFadLI7bc696Y35lUATCCBY0wggR1oAMC # AQICEA6bGI750C3n79tQ4ghAGFowDQYJKoZIhvcNAQEMBQAwZTELMAkGA1UEBhMC # VVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0 # LmNvbTEkMCIGA1UEAxMbRGlnaUNlcnQgQXNzdXJlZCBJRCBSb290IENBMB4XDTIy # MDgwMTAwMDAwMFoXDTMxMTEwOTIzNTk1OVowYjELMAkGA1UEBhMCVVMxFTATBgNV # BAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTEhMB8G # A1UEAxMYRGlnaUNlcnQgVHJ1c3RlZCBSb290IEc0MIICIjANBgkqhkiG9w0BAQEF # AAOCAg8AMIICCgKCAgEAv+aQc2jeu+RdSjwwIjBpM+zCpyUuySE98orYWcLhKac9 # WKt2ms2uexuEDcQwH/MbpDgW61bGl20dq7J58soR0uRf1gU8Ug9SH8aeFaV+vp+p # VxZZVXKvaJNwwrK6dZlqczKU0RBEEC7fgvMHhOZ0O21x4i0MG+4g1ckgHWMpLc7s # Xk7Ik/ghYZs06wXGXuxbGrzryc/NrDRAX7F6Zu53yEioZldXn1RYjgwrt0+nMNlW # 7sp7XeOtyU9e5TXnMcvak17cjo+A2raRmECQecN4x7axxLVqGDgDEI3Y1DekLgV9 # iPWCPhCRcKtVgkEy19sEcypukQF8IUzUvK4bA3VdeGbZOjFEmjNAvwjXWkmkwuap # oGfdpCe8oU85tRFYF/ckXEaPZPfBaYh2mHY9WV1CdoeJl2l6SPDgohIbZpp0yt5L # HucOY67m1O+SkjqePdwA5EUlibaaRBkrfsCUtNJhbesz2cXfSwQAzH0clcOP9yGy # shG3u3/y1YxwLEFgqrFjGESVGnZifvaAsPvoZKYz0YkH4b235kOkGLimdwHhD5QM # IR2yVCkliWzlDlJRR3S+Jqy2QXXeeqxfjT/JvNNBERJb5RBQ6zHFynIWIgnffEx1 # P2PsIV/EIFFrb7GrhotPwtZFX50g/KEexcCPorF+CiaZ9eRpL5gdLfXZqbId5RsC # AwEAAaOCATowggE2MA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFOzX44LScV1k # TN8uZz/nupiuHA9PMB8GA1UdIwQYMBaAFEXroq/0ksuCMS1Ri6enIZ3zbcgPMA4G # A1UdDwEB/wQEAwIBhjB5BggrBgEFBQcBAQRtMGswJAYIKwYBBQUHMAGGGGh0dHA6 # Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBDBggrBgEFBQcwAoY3aHR0cDovL2NhY2VydHMu # ZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENBLmNydDBFBgNVHR8E # PjA8MDqgOKA2hjRodHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRBc3N1 # cmVkSURSb290Q0EuY3JsMBEGA1UdIAQKMAgwBgYEVR0gADANBgkqhkiG9w0BAQwF # AAOCAQEAcKC/Q1xV5zhfoKN0Gz22Ftf3v1cHvZqsoYcs7IVeqRq7IviHGmlUIu2k # iHdtvRoU9BNKei8ttzjv9P+Aufih9/Jy3iS8UgPITtAq3votVs/59PesMHqai7Je # 1M/RQ0SbQyHrlnKhSLSZy51PpwYDE3cnRNTnf+hZqPC/Lwum6fI0POz3A8eHqNJM # QBk1RmppVLC4oVaO7KTVPeix3P0c2PR3WlxUjG/voVA9/HYJaISfb8rbII01YBwC # A8sgsKxYoA5AY8WYIsGyWfVVa88nq2x2zm8jLfR+cWojayL/ErhULSd+2DrZ8LaH # lv1b0VysGMNNn3O3AamfV6peKOK5lDCCBq4wggSWoAMCAQICEAc2N7ckVHzYR6z9 # KGYqXlswDQYJKoZIhvcNAQELBQAwYjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERp # Z2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTEhMB8GA1UEAxMY # RGlnaUNlcnQgVHJ1c3RlZCBSb290IEc0MB4XDTIyMDMyMzAwMDAwMFoXDTM3MDMy # MjIzNTk1OVowYzELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMu # MTswOQYDVQQDEzJEaWdpQ2VydCBUcnVzdGVkIEc0IFJTQTQwOTYgU0hBMjU2IFRp # bWVTdGFtcGluZyBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMaG # NQZJs8E9cklRVcclA8TykTepl1Gh1tKD0Z5Mom2gsMyD+Vr2EaFEFUJfpIjzaPp9 # 85yJC3+dH54PMx9QEwsmc5Zt+FeoAn39Q7SE2hHxc7Gz7iuAhIoiGN/r2j3EF3+r # GSs+QtxnjupRPfDWVtTnKC3r07G1decfBmWNlCnT2exp39mQh0YAe9tEQYncfGpX # evA3eZ9drMvohGS0UvJ2R/dhgxndX7RUCyFobjchu0CsX7LeSn3O9TkSZ+8OpWNs # 5KbFHc02DVzV5huowWR0QKfAcsW6Th+xtVhNef7Xj3OTrCw54qVI1vCwMROpVymW # Jy71h6aPTnYVVSZwmCZ/oBpHIEPjQ2OAe3VuJyWQmDo4EbP29p7mO1vsgd4iFNmC # KseSv6De4z6ic/rnH1pslPJSlRErWHRAKKtzQ87fSqEcazjFKfPKqpZzQmiftkaz # nTqj1QPgv/CiPMpC3BhIfxQ0z9JMq++bPf4OuGQq+nUoJEHtQr8FnGZJUlD0UfM2 # SU2LINIsVzV5K6jzRWC8I41Y99xh3pP+OcD5sjClTNfpmEpYPtMDiP6zj9NeS3YS # UZPJjAw7W4oiqMEmCPkUEBIDfV8ju2TjY+Cm4T72wnSyPx4JduyrXUZ14mCjWAkB # KAAOhFTuzuldyF4wEr1GnrXTdrnSDmuZDNIztM2xAgMBAAGjggFdMIIBWTASBgNV # HRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBS6FtltTYUvcyl2mi91jGogj57IbzAf # BgNVHSMEGDAWgBTs1+OC0nFdZEzfLmc/57qYrhwPTzAOBgNVHQ8BAf8EBAMCAYYw # EwYDVR0lBAwwCgYIKwYBBQUHAwgwdwYIKwYBBQUHAQEEazBpMCQGCCsGAQUFBzAB # hhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wQQYIKwYBBQUHMAKGNWh0dHA6Ly9j # YWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRSb290RzQuY3J0MEMG # A1UdHwQ8MDowOKA2oDSGMmh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2Vy # dFRydXN0ZWRSb290RzQuY3JsMCAGA1UdIAQZMBcwCAYGZ4EMAQQCMAsGCWCGSAGG # /WwHATANBgkqhkiG9w0BAQsFAAOCAgEAfVmOwJO2b5ipRCIBfmbW2CFC4bAYLhBN # E88wU86/GPvHUF3iSyn7cIoNqilp/GnBzx0H6T5gyNgL5Vxb122H+oQgJTQxZ822 # EpZvxFBMYh0MCIKoFr2pVs8Vc40BIiXOlWk/R3f7cnQU1/+rT4osequFzUNf7WC2 # qk+RZp4snuCKrOX9jLxkJodskr2dfNBwCnzvqLx1T7pa96kQsl3p/yhUifDVinF2 # ZdrM8HKjI/rAJ4JErpknG6skHibBt94q6/aesXmZgaNWhqsKRcnfxI2g55j7+6ad # cq/Ex8HBanHZxhOACcS2n82HhyS7T6NJuXdmkfFynOlLAlKnN36TU6w7HQhJD5TN # OXrd/yVjmScsPT9rp/Fmw0HNT7ZAmyEhQNC3EyTN3B14OuSereU0cZLXJmvkOHOr # pgFPvT87eK1MrfvElXvtCl8zOYdBeHo46Zzh3SP9HSjTx/no8Zhf+yvYfvJGnXUs # HicsJttvFXseGYs2uJPU5vIXmVnKcPA3v5gA3yAWTyf7YGcWoWa63VXAOimGsJig # K+2VQbc61RWYMbRiCQ8KvYHZE/6/pNHzV9m8BPqC3jLfBInwAM1dwvnQI38AC+R2 # AibZ8GV2QqYphwlHK+Z/GqSFD/yYlvZVVCsfgPrA8g4r5db7qS9EFUrnEw4d2zc4 # GqEr9u3WfPwwggbAMIIEqKADAgECAhAMTWlyS5T6PCpKPSkHgD1aMA0GCSqGSIb3 # DQEBCwUAMGMxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjE7 # MDkGA1UEAxMyRGlnaUNlcnQgVHJ1c3RlZCBHNCBSU0E0MDk2IFNIQTI1NiBUaW1l # U3RhbXBpbmcgQ0EwHhcNMjIwOTIxMDAwMDAwWhcNMzMxMTIxMjM1OTU5WjBGMQsw # CQYDVQQGEwJVUzERMA8GA1UEChMIRGlnaUNlcnQxJDAiBgNVBAMTG0RpZ2lDZXJ0 # IFRpbWVzdGFtcCAyMDIyIC0gMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC # ggIBAM/spSY6xqnya7uNwQ2a26HoFIV0MxomrNAcVR4eNm28klUMYfSdCXc9FZYI # L2tkpP0GgxbXkZI4HDEClvtysZc6Va8z7GGK6aYo25BjXL2JU+A6LYyHQq4mpOS7 # eHi5ehbhVsbAumRTuyoW51BIu4hpDIjG8b7gL307scpTjUCDHufLckkoHkyAHoVW # 54Xt8mG8qjoHffarbuVm3eJc9S/tjdRNlYRo44DLannR0hCRRinrPibytIzNTLlm # yLuqUDgN5YyUXRlav/V7QG5vFqianJVHhoV5PgxeZowaCiS+nKrSnLb3T254xCg/ # oxwPUAY3ugjZNaa1Htp4WB056PhMkRCWfk3h3cKtpX74LRsf7CtGGKMZ9jn39cFP # cS6JAxGiS7uYv/pP5Hs27wZE5FX/NurlfDHn88JSxOYWe1p+pSVz28BqmSEtY+VZ # 9U0vkB8nt9KrFOU4ZodRCGv7U0M50GT6Vs/g9ArmFG1keLuY/ZTDcyHzL8IuINeB # rNPxB9ThvdldS24xlCmL5kGkZZTAWOXlLimQprdhZPrZIGwYUWC6poEPCSVT8b87 # 6asHDmoHOWIZydaFfxPZjXnPYsXs4Xu5zGcTB5rBeO3GiMiwbjJ5xwtZg43G7vUs # fHuOy2SJ8bHEuOdTXl9V0n0ZKVkDTvpd6kVzHIR+187i1Dp3AgMBAAGjggGLMIIB # hzAOBgNVHQ8BAf8EBAMCB4AwDAYDVR0TAQH/BAIwADAWBgNVHSUBAf8EDDAKBggr # BgEFBQcDCDAgBgNVHSAEGTAXMAgGBmeBDAEEAjALBglghkgBhv1sBwEwHwYDVR0j # BBgwFoAUuhbZbU2FL3MpdpovdYxqII+eyG8wHQYDVR0OBBYEFGKK3tBh/I8xFO2X # C809KpQU31KcMFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6Ly9jcmwzLmRpZ2ljZXJ0 # LmNvbS9EaWdpQ2VydFRydXN0ZWRHNFJTQTQwOTZTSEEyNTZUaW1lU3RhbXBpbmdD # QS5jcmwwgZAGCCsGAQUFBwEBBIGDMIGAMCQGCCsGAQUFBzABhhhodHRwOi8vb2Nz # cC5kaWdpY2VydC5jb20wWAYIKwYBBQUHMAKGTGh0dHA6Ly9jYWNlcnRzLmRpZ2lj # ZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRHNFJTQTQwOTZTSEEyNTZUaW1lU3RhbXBp # bmdDQS5jcnQwDQYJKoZIhvcNAQELBQADggIBAFWqKhrzRvN4Vzcw/HXjT9aFI/H8 # +ZU5myXm93KKmMN31GT8Ffs2wklRLHiIY1UJRjkA/GnUypsp+6M/wMkAmxMdsJiJ # 3HjyzXyFzVOdr2LiYWajFCpFh0qYQitQ/Bu1nggwCfrkLdcJiXn5CeaIzn0buGqi # m8FTYAnoo7id160fHLjsmEHw9g6A++T/350Qp+sAul9Kjxo6UrTqvwlJFTU2WZoP # VNKyG39+XgmtdlSKdG3K0gVnK3br/5iyJpU4GYhEFOUKWaJr5yI+RCHSPxzAm+18 # SLLYkgyRTzxmlK9dAlPrnuKe5NMfhgFknADC6Vp0dQ094XmIvxwBl8kZI4DXNlpf # lhaxYwzGRkA7zl011Fk+Q5oYrsPJy8P7mxNfarXH4PMFw1nfJ2Ir3kHJU7n/NBBn # 9iYymHv+XEKUgZSCnawKi8ZLFUrTmJBFYDOA4CPe+AOk9kVH5c64A0JH6EE2cXet # /aLol3ROLtoeHYxayB6a1cLwxiKoT5u92ByaUcQvmvZfpyeXupYuhVfAYOd4Vn9q # 78KVmksRAsiCnMkaBXy6cbVOepls9Oie1FqYyJ+/jbsYXEP10Cro4mLueATbvdH7 # WwqocH7wl4R44wgDXUcsY6glOJcB0j862uXl9uab3H4szP8XTE0AotjWAQ64i+7m # 4HJViSwnGWH2dwGMMYIFRDCCBUACAQEwgYYwcjELMAkGA1UEBhMCVVMxFTATBgNV # BAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTExMC8G # A1UEAxMoRGlnaUNlcnQgU0hBMiBBc3N1cmVkIElEIENvZGUgU2lnbmluZyBDQQIQ # C4jZOitkx57ksuMgsWXX0jAJBgUrDgMCGgUAoHAwEAYKKwYBBAGCNwIBDDECMAAw # GQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEOMAwGCisG # AQQBgjcCARUwIwYJKoZIhvcNAQkEMRYEFPR/dZ9hCbioUHY/5prrtccWLTNNMA0G # CSqGSIb3DQEBAQUABIIBANRQWNOAI9Q7ShqIG/BHyrhQbsS7SIo80XqFBuiZPj31 # SyavTugj2FnOSW6a/qhVzSavGCHS76tLgVxZVvL8M3nOrL3/nYQVzOCA+A/rfVTx # zf/7gtI5AK1LrBDN+aPDs5i4v/WrWoTb41Lps7I3NPLl821kTTmMV0ABzPQWgT2X # +I2yonqfIHyBHRjsJOWKDLDKqEGMSdvXY+XhZ0L/V5+SS2BXPbyp+w0pIr4mApBd # aKIG1P7/vkwDiuCUaSx5yWGcr5qF1MQdKaLTh98StLBF5hFJ9SA1rLZzwz/tL0gC # QxWc7HmUfJPwVvuCzkTXTNpgQ/It2hYgtzd9roKJw5mhggMgMIIDHAYJKoZIhvcN # AQkGMYIDDTCCAwkCAQEwdzBjMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNl # cnQsIEluYy4xOzA5BgNVBAMTMkRpZ2lDZXJ0IFRydXN0ZWQgRzQgUlNBNDA5NiBT # SEEyNTYgVGltZVN0YW1waW5nIENBAhAMTWlyS5T6PCpKPSkHgD1aMA0GCWCGSAFl # AwQCAQUAoGkwGAYJKoZIhvcNAQkDMQsGCSqGSIb3DQEHATAcBgkqhkiG9w0BCQUx # DxcNMjMwODAxMjMxODA2WjAvBgkqhkiG9w0BCQQxIgQgL0Hti46C9vFMJsfdR4S4 # x209lIuofVd6y2t4jk9AfM0wDQYJKoZIhvcNAQEBBQAEggIAEdQAuyOtzV4hd/Vq # PUqlHmSBwywmDLhwsorhhX59ySqtbFqPHErMP5wY81X9ac6bAW+/MBImTTUq4xhx # Rl/pQaxbdhUWm6EBri/XQdnGBGHUrDa9NqaSSmeXib7pZdecYirKl6nVYnYsYkqG # BRcQJKk3EXUbGSNdwEGgOVXtDgtMgBC/5p4bQx+njS2iFfyWhe1i7eQ4UVDH7DVW # +MHNAyM9YDVCAqKJfOM1KBN6QelmPgSf7TIMoavOaMCE73izgwDdb/F7sSxt58Td # IfyS8RG5ssofOuMbZCr0i4DBqj6Hwk0GsqiWqW4NL9lacRB+uldvgbZw/4c3yAlG # cLx3TuXFBqFxInhPiapijNSLCwYyFZVtAMqrxM+FMeSw7GtdOYe397Wv4vqAJk/m # ZGRXnu9HpfYGozMKDsoP78G5G32J1iPWJgIoLwVHJJp4d1H3mYxQV/7tR5FKZ0ZI # q6XvsRlC9pixvLH9vi7p3pLUBgfPfXE72svafFQpbUMC6yvhHnBgONaetRE2K+JP # E2ZbZYTnwKO3kEIMnCBkXjANnMvhF9IWJWnopr7+iE6gvMPtfAlh1FiRgt9Owa0y # QB+DdXEonZ09sQL7pSdfYqu8IlSAU/A4zl/5oCNL7CcxatlPvvcEr6fRmkNNI5R+ # zVvUMuR2Sa1l0zIyhTs+ktp84Pk= # SIG # End signature block |