PureStorage.CBS.AVS.Monitor.ps1

$SCHEMA_VERSION = "1.1.0"

function Invoke-AzFunctionDeployment {
    <#
    .SYNOPSIS
      Deploys an Azure resource group deployment with retry logic for transient host runtime errors.
    .DESCRIPTION
      Wraps New-AzResourceGroupDeployment with automatic retry on transient InternalServerError
      from Azure Functions host runtime. Non-transient errors are thrown immediately without retry.
    .PARAMETER DeploymentName
      The name of the Azure resource group deployment.
    .PARAMETER ResourceGroupName
      The name of the resource group to deploy to.
    .PARAMETER TemplateFile
      The path to the Bicep or ARM template file.
    .PARAMETER TemplateParameterObject
      A hashtable of template parameters to pass to the deployment.
    .PARAMETER MaxRetries
      Optional. Maximum number of retry attempts. Default is 3.
    .PARAMETER RetryDelaySeconds
      Optional. Delay in seconds between retry attempts. Default is 30.
    #>

    Param(
        [Parameter(Mandatory=$true)]
        [String]$DeploymentName,

        [Parameter(Mandatory=$true)]
        [String]$ResourceGroupName,

        [Parameter(Mandatory=$true)]
        [String]$TemplateFile,

        [Parameter(Mandatory=$true)]
        [hashtable]$TemplateParameterObject,

        [Parameter(Mandatory=$false)]
        [int]$MaxRetries = 3,

        [Parameter(Mandatory=$false)]
        [int]$RetryDelaySeconds = 30,

        [Parameter(Mandatory=$true)]
        $Logger
    )

    $retryCount = 0
    $deployed = $false
    do {
        try {
            $retryCount++
            New-AzResourceGroupDeployment -Name $DeploymentName -ResourceGroupName $ResourceGroupName `
              -TemplateFile $TemplateFile -TemplateParameterObject $TemplateParameterObject -ErrorAction Stop
            $deployed = $true
        } catch {
            $isTransient = $_.Exception.Message -like "*InternalServerError*from host runtime*"
            if ((-not $isTransient) -or ($retryCount -ge $MaxRetries)) {
                throw
            }
            $Logger.LogWarning("Host runtime is not ready yet: $_")
            $Logger.LogWarning("Retrying in $RetryDelaySeconds seconds... (attempt $($retryCount + 1) of $MaxRetries)")
            Start-Sleep -Seconds $RetryDelaySeconds
        }
    } while (-not $deployed)
}
function Update-ResourceGroupTags {
    Param(
        [Parameter(Mandatory=$true)]
        [String]$MonitorResourceGroup,

        [Parameter(Mandatory=$true)]
        [ValidateSet("host", "capacity")]
        [String]$MonitorType,

        [Parameter(Mandatory=$true)]
        [bool] $IsFreshDeployment
    )

    # Add tag to resource group
    $ResourceGroup = Get-AzResourceGroup $MonitorResourceGroup -ErrorAction ignore
    if (-not $ResourceGroup) {
        throw "Resource group $MonitorResourceGroup does not exist"
    }

    $Tags = $ResourceGroup.Tags
    if ($MonitorType -eq "host") {
        $Tags["PureStorage.CBS.AVS.HostMonitor"] = "True"
    }
    if ($MonitorType -eq "capacity") {
        if (-not $IsFreshDeployment) {
            # For backward compatibility, set the HostMonitor tag to true if we are adding a deployment to existing HostMonitor deployment
            if (-not $Tags["PureStorage.CBS.AVS.CapacityMonitor"] ){
                # If CapacityMonitor tag was already set, then compatability check was done in the past
                $Tags["PureStorage.CBS.AVS.HostMonitor"] = "True"
            }
        }
        $Tags["PureStorage.CBS.AVS.CapacityMonitor"] = "True"
    }
    $Tags['PureStorage.CBS.AVS'] = $ProductVersion
    $Tags["PureStorage.CBS.AVS.SCHEMA_VERSION"] = $SCHEMA_VERSION
    Set-AzResourceGroup -Name $MonitorResourceGroup -Tag $Tags
}

function Deploy-MonitoringResource {
    param (
      [Parameter(Mandatory=$true)]
          [String]$MonitorResourceGroup,

          [Parameter(Mandatory=$true)]
          [String]$MonitorResourceGroupRegion,

          [Parameter(Mandatory=$false)]
          [String]$AVSCloudName,

          [Parameter(Mandatory=$false)]
          [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)]
          [int]$MonitorIntervalInMinute,

          [Parameter(Mandatory=$false)]
          [ValidateRange(1, 100)]
          [int]$DefaultUtilizationThreshold=$DEFAULT_UTILIZATION_THRESHOLD,

          [Parameter(Mandatory=$true)]
          [ValidateSet("host", "capacity")]
          [string]$MonitorType,

          [Parameter(Mandatory=$false)]
          [int]$DefaultRunCommandTimeoutInMinute=$DEFAULT_RUNCOMMAND_TIMEOUT_IN_MINUTE,

          [Parameter(Mandatory=$true)]
          $Logger
    )

    $ProductVersion = (Get-Module "PureStorage.CBS.AVS").Version.ToString()
    $ResourceGroup = Get-AzResourceGroup $MonitorResourceGroup -ErrorAction ignore
    if (-not $ResourceGroup) {
        $IsFreshDeployment = $true
        $Logger.LogInfo("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.Tags["PureStorage.CBS.AVS"]) {
            $IsFreshDeployment = $false
        }

        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 -ne 0) -and (-not $ResourceGroup.Tags["PureStorage.CBS.AVS"])) {
            throw "The resource group $MonitorResourceGroup exists but not used by Pure Storage AVS monitor. Please select another name for Pure Storage monitor"
        }
        Update-ResourceGroupTags -MonitorResourceGroup $MonitorResourceGroup -MonitorType $MonitorType -IsFreshDeployment $IsFreshDeployment
    }

    $DeploymentId = (New-Guid).ToString()
    $DeploymentParams = @{        
        "VNetName" = $VNetName;
        "VNetResourceGroupName" = $VNetResourceGroup;
        "DeploymentId" = $DeploymentId
    }

    if ($MonitorType -eq "capacity") {
        $DeploymentTemplatePath = Join-Path -Path $PSScriptRoot -ChildPath 'templates/BaseMonitor' -AdditionalChildPath 'Main.bicep'
        $DeploymentParams["DeploymentType"] = "CapacityMonitor"
        $DeploymentParams["DefaultUtilizationThreshold"] = $DefaultUtilizationThreshold
        $DeploymentParams["capacityMonitorIntervalInMinute"] = $MonitorIntervalInMinute
    }

    if ($MonitorType -eq "host") {
        $DeploymentParams["AVSCloudName"] = $AVSCloudName
        $DeploymentParams["AVSResourceGroup"] = $AVSResourceGroup
        $DeploymentTemplatePath = Join-Path -Path $PSScriptRoot -ChildPath 'templates/BaseMonitor' -AdditionalChildPath 'Main.bicep'
        $DeploymentParams["DeploymentType"] = "HostMonitor"
        $DeploymentParams["MonitorIntervalInMinute"] = $MonitorIntervalInMinute
        $DeploymentParams["DefaultRunCommandTimeoutInMinute"] = $DefaultRunCommandTimeoutInMinute
    }

    if ($VNetSubnetAddress) {
        $DeploymentParams["SubnetAddressRange"] = $VNetSubnetAddress
    }

    if ($VNetSubnetName) {
        $DeploymentParams["SubnetName"] = $VNetSubnetName
    }

    $Logger.LogInfo("Deploying monitoring infrastructure to Azure...")
    Invoke-AzFunctionDeployment -DeploymentName "PCBSMonitorDeployment_$DeploymentId" `
      -ResourceGroupName $MonitorResourceGroup -TemplateFile $DeploymentTemplatePath `
      -TemplateParameterObject $DeploymentParams -Logger $Logger
  }

  function Add-MonitorArray {
    param (
      [Parameter(Mandatory=$true)]
      [String]$MonitorResourceGroup,

      [Parameter(Mandatory=$true)]
      [String]$PureCloudBlockStoreEndpoint,

      [Parameter(Mandatory=$true)]
      [pscredential]$PureCloudBlockStoreCredential,

      [Parameter(Mandatory=$false)]
      [int] $UtilizationThreshold,

      [Parameter(Mandatory=$true)]
      [ValidateSet("host", "capacity")]
      [String]$MonitorType,

      [Parameter(Mandatory=$false)]
      [Switch]$Force,

      [Parameter(Mandatory=$true)]
      $Logger
    )

    $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 monitor"
    }

    $PureCloudBlockStoreEndpointOrigin = $PureCloudBlockStoreEndpoint
    $Logger.LogInfo("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

    if ($Force) {
        $Logger.LogWarning("Warning skipping check for $PureCloudBlockStoreEndpoint connectivity.")
    }
    else {
        # Make sure the credential works before adding to the monitor
        $Array = Connect-Pfa2array -Endpoint $PureCloudBlockStoreEndpoint -Credential $PureCloudBlockStoreCredential  -IgnoreCertificateError -ErrorAction Ignore
        if (-not $Array) {
            $msg = "Failed to connect to the Pure Cloud Block Store. Please check the endpoint and credential of the Pure Cloud Block Store."
            throw $msg
        }
    }

    if ($PureCloudBlockStoreEndpoint -match "^\d+.\d+.\d+.\d+$") {
        $PureCloudBlockStoreEndpoint = $PureCloudBlockStoreEndpoint.Replace(".", "-")
    }

    $data_prefix = ""
    if ($MonitorType -eq "capacity") {
        $data_prefix = "capacity-"
    }
    $Secret = Get-AzKeyVaultSecret -VaultName $KeyVault.VaultName | where-object {$_.Name -eq "$($PureCloudBlockStoreEndpoint)-$($KeyVault.VaultName)-${data_prefix}username"}
    if ($Secret) {
        $Logger.LogInfo("Overriding the existing credential for Pure Cloud Block Store $PureCloudBlockStoreEndpointOrigin...")
    }

    Set-AzKeyVaultSecret -VaultName $KeyVault.VaultName -Name "$($PureCloudBlockStoreEndpoint)-$($KeyVault.VaultName)-${data_prefix}username" -SecretValue (ConvertTo-SecureString -String $PureCloudBlockStoreCredential.UserName -AsPlainText -Force)
    Set-AzKeyVaultSecret -VaultName $KeyVault.VaultName -Name "$($PureCloudBlockStoreEndpoint)-$($KeyVault.VaultName)-${data_prefix}password" -SecretValue $PureCloudBlockStoreCredential.Password

    if ($MonitorType -eq "capacity") {

      if ($UtilizationThreshold) {
        Set-AzKeyVaultSecret -VaultName $KeyVault.VaultName -Name "$($PureCloudBlockStoreEndpoint)-$($KeyVault.VaultName)-${data_prefix}threshold" -SecretValue (ConvertTo-SecureString -String $UtilizationThreshold -AsPlainText -Force)
      }
    }

    $Logger.LogInfo("The Pure Cloud Block Store $PureCloudBlockStoreEndpointOrigin is successfully added to monitor resource group $MonitorResourceGroup.")
  }

function Remove-FunctionFromFunctionApp {
    Param(

        [Parameter(Mandatory = $true)]
        [String] $ResourceGroupName,

        [Parameter(Mandatory = $true)]
        [String]$FunctionName,

        [Parameter(Mandatory = $true)]
        [String]$FunctionAppName,

        [Parameter(Mandatory=$true)]
        $Logger
    )

    $Context = Get-AzContext
    $SubscriptionId = $Context.Subscription.Id
    $Logger.LogInfo("Removing function $FunctionName from FunctinApp $FunctionAppName...")
    $uri = "/subscriptions/$SubscriptionId/resourceGroups/$ResourceGroupName/providers/Microsoft.Web/sites/$FunctionAppName/functions/$($FunctionName)?api-version=2016-08-01"
    Invoke-AzRest -Method DELETE -Path $uri
}

function Get-FunctionFromFunctionApp {
    Param(

        [Parameter(Mandatory = $true)]
        [String] $ResourceGroupName,

        [Parameter(Mandatory = $true)]
        [String]$FunctionName,

        [Parameter(Mandatory = $true)]
        [String]$FunctionAppName
    )

    $Context = Get-AzContext
    $SubscriptionId = $Context.Subscription.Id
    $uri = "/subscriptions/$SubscriptionId/resourceGroups/$ResourceGroupName/providers/Microsoft.Web/sites/$FunctionAppName/functions/$($FunctionName)?api-version=2016-08-01"
    Invoke-AzRest -Method Get -Path $uri
}

function Test-MonitorFunctionExistence {
    Param(
        [Parameter(Mandatory = $true)]
        [String]$ResourceGroupName,

        [Parameter(Mandatory = $true)]
        [ValidateSet("host", "capacity")]
        [String]$MonitorType
    )

    $FunctionApp = Get-AzFunctionApp -ResourceGroupName $ResourceGroupName -ErrorAction Ignore
    if (-not $FunctionApp) {
        throw "Function App does not exist in the resource group $ResourceGroupName. No valid monitor deployment found. Try to deploy the monitor again"
    }
    if ($MonitorType -eq "host") {
        $FunctionName = "BuildClusterTrigger"
        $ErrorMessage = "AVS host monitor does not exist in the monitor resource group $ResourceGroupName"
    } elseif ($MonitorType -eq "capacity") {
        $FunctionName = "CapacityMonitorTrigger"
        $ErrorMessage = "Capacity monitor does not exist in the monitor resource group $ResourceGroupName"
    }
    $FunctionAppGetResponse = Get-FunctionFromFunctionApp -ResourceGroupName $ResourceGroupName -FunctionName $FunctionName -FunctionAppName $FunctionApp.Name
    if ($FunctionAppGetResponse.StatusCode -ne 200) {
        throw $ErrorMessage
    }
}

  function Remove-Monitor {
    Param(
        [Parameter(Mandatory = $true)]
        [String]$MonitorResourceGroup,

        [Parameter(Mandatory = $true)]
        [ValidateSet("host", "capacity")]
        [String]$MonitorType,

        [Parameter(Mandatory = $false)]
        [Switch]$RemoveSubnet,

        [Parameter(Mandatory=$true)]
        $Logger
    )

    $ResourceGroup = Get-AzResourceGroup $MonitorResourceGroup -ErrorAction ignore

    if (-not $ResourceGroup) {
        throw "Pure Storage monitor $MonitorResourceGroup does not exist"
    }

    if ([string]::IsNullOrEmpty($ResourceGroup.Tags["PureStorage.CBS.AVS"])) {
        throw "The resource group provided is not Pure Storage monitor resource group. Only Pure Storage 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 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}

    # Check if we need to do partial removal
    $IsPartial = $false
    if ($MonitorType -eq "host") {
        if ($ResourceGroup.Tags["PureStorage.CBS.AVS.CapacityMonitor"]) {
            $IsPartial = $true

            if ($RemoveSubnet) {
                throw "Cannot remove subnet when the monitor resource group $MonitorResourceGroup is hosting capacity monitor. Please remove the capacity monitor first"
            }
            $Logger.LogWarning("The monitor resource group $MonitorResourceGroup is hosting capacity monitor. Removing only the host monitor resources")
            Remove-FunctionFromFunctionApp -ResourceGroupName $MonitorResourceGroup -FunctionName "BuildClusterTrigger" -FunctionAppName $MonitorFuncApp.Name -Logger $Logger
            $Tags = $ResourceGroup.Tags
            $Tags.Remove("PureStorage.CBS.AVS.HostMonitor")
            Set-AzResourceGroup -Name $MonitorResourceGroup -Tag $Tags
        }
    }

    if ($MonitorType -eq "capacity") {
        if ($ResourceGroup.Tags["PureStorage.CBS.AVS.HostMonitor"]) {
            $IsPartial = $true

            if ($RemoveSubnet) {
                throw "Cannot remove subnet when the monitor resource group $MonitorResourceGroup is hosting host monitor. Please remove the host monitor first"
            }
            $Logger.LogWarning("The monitor resource group $MonitorResourceGroup is hosting host monitor. Removing only the capacity monitor resources")
            Remove-FunctionFromFunctionApp -ResourceGroupName $MonitorResourceGroup -FunctionName "CapacityMonitorTrigger" -FunctionAppName $MonitorFuncApp.Name -Logger $Logger
            $Tags = $ResourceGroup.Tags
            $Tags.Remove("PureStorage.CBS.AVS.CapacityMonitor")
            Set-AzResourceGroup -Name $MonitorResourceGroup -Tag $Tags
        }
    }

    if (-not $IsPartial) {
        $Logger.LogInfo("Removing resource group $MonitorResourceGroup...")
        Remove-AzResourceGroup $MonitorResourceGroup -Force | Out-Null

        # Remove subnet
        if ($RemoveSubnet) {
            $Logger.LogInfo("Removing subnet $MonitorSubnetName from vNet $($MonitorVNet.Name)...")
            Remove-AzVirtualNetworkSubnetConfig -Name $MonitorSubnetName -VirtualNetwork $MonitorVNet | Set-AzVirtualNetwork | Out-Null
        }

        # Purge key vault
        $Logger.LogInfo("Purging key vault $($MonitorKeyVault.VaultName)...")
        Remove-AzKeyVault -Name $MonitorKeyVault.VaultName -InRemovedState -Force -Location $ResourceGroup.Location | Out-Null
    }
  }

  function Remove-MonitorArray {
    Param (
      [Parameter(Mandatory=$true)]
      [String]$MonitorResourceGroup,

      [Parameter(Mandatory=$true)]
      [String]$PureCloudBlockStoreEndpoint,

      [Parameter(Mandatory=$true)]
      [ValidateSet("host", "capacity")]
      [String]$MonitorType,

      [Parameter(Mandatory=$true)]
      $Logger
    )
    $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
    $Logger.LogInfo("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(".", "-")
    }

    $data_prefix = ""
    if ($MonitorType -eq "capacity") {
        $data_prefix = "capacity-"
    }
    $Secret = Get-AzKeyVaultSecret -VaultName $KeyVault.VaultName | where-object {$_.Name -eq "$($PureCloudBlockStoreEndpoint)-$($KeyVault.VaultName)-${data_prefix}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)-${data_prefix}username" -Force
    Remove-AzKeyVaultSecret -VaultName $KeyVault.VaultName -Name "$($PureCloudBlockStoreEndpoint)-$($KeyVault.VaultName)-${data_prefix}password" -Force

    # Purge secret
    Purge-AzureSecretWithRetry -KeyVaultName $KeyVault.VaultName -SecretName "$($PureCloudBlockStoreEndpoint)-$($KeyVault.VaultName)-${data_prefix}username"
    Purge-AzureSecretWithRetry -KeyVaultName $KeyVault.VaultName -SecretName "$($PureCloudBlockStoreEndpoint)-$($KeyVault.VaultName)-${data_prefix}password"


    $Logger.LogInfo("The Pure Cloud Block Store $PureCloudBlockStoreEndpointOrigin is successfully removed.")
  }

  function Get-Monitor {
    param (
        [Parameter(Mandatory=$true)]
        [String]$MonitorResourceGroup,
        [Parameter(Mandatory=$true)]
        [ValidateSet("host", "capacity")]
        [String]$MonitorType
      )

    $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"
    }

    Test-MonitorFunctionExistence -ResourceGroupName $MonitorResourceGroup -MonitorType $MonitorType

    # Get global utilization threshold
    if ($MonitorType -eq "capacity") {
        $DefaultUtilizationThreshold = Get-MonitorEnvironmentConfig -ResourceGroupName $MonitorResourceGroup -ConfigName "DEFAULT_UTILIZATION_THRESHOLD"
    }

    if ($MonitorType -eq "host") {
        $DefaultRunCommandTimeoutInMinute = Get-MonitorEnvironmentConfig -ResourceGroupName $MonitorResourceGroup -ConfigName "DEFAULT_RUNCOMMAND_TIMEOUT_IN_MINUTE"
    }

    $KeyVault = Get-AzKeyVault -ResourceGroupName $MonitorResourceGroup -ErrorAction Ignore
    if (-not $KeyVault) {
        throw "Key Vault does not exist in the monitor resource group $MonitorResourceGroup. No valid monitor deployment found. Try to deploy the monitor again"
    }

    # List CBS arras and their corresponding utilization threshold
    $UserPrincipalName = (Get-AzContext).Account.Id
    $KeyVault = Get-AzKeyVault -ResourceGroupName $MonitorResourceGroup
    Set-AzKeyVaultAccessPolicy -VaultName $KeyVault.VaultName  -UserPrincipalName $UserPrincipalName -PermissionsToSecrets set,delete,get,purge,list

    if ($MonitorType -eq "capacity") {
        $SecretSplitter = "-$($KeyVault.VaultName)-capacity-username"
    } elseif ($MonitorType -eq "host"){
        $SecretSplitter = "-$($KeyVault.VaultName)-username"
    }
    $Secrets = Get-AzKeyVaultSecret -VaultName $KeyVault.VaultName | where-object {$_.Name -like "*$($SecretSplitter)"}
    $MonitorArrays = @()
    foreach ($Secret in $Secrets) {
        $ArrayName = ($Secret.name -Split $SecretSplitter)[0]
        if ($MonitorType -eq "capacity") {
            $EncodedThreshold = Get-AzKeyVaultSecret -VaultName $KeyVault.VaultName -Name "$($ArrayName)-$($KeyVault.VaultName)-capacity-threshold"
            if ($EncodedThreshold) {
                $Threshold = [int] (ConvertFrom-SecureString -SecureString $EncodedThreshold.SecretValue -AsPlainText)
            }
            else {
                $Threshold = $null
            }
        }

        # 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("-", ".")
        }

        $MonitorArrays += @{ArrayName = $ArrayName; Threshold=$Threshold}
    }

    $MonitorFuncApp = Get-AzFunctionApp -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}
    $MonitorSubnet = Get-AzVirtualNetworkSubnetConfig -Name $MonitorSubnetName -VirtualNetwork $MonitorVNet

    if ($MonitorType -eq "capacity") {
        $result = [PSCustomObject]@{
            DefaultUtilizationThreshold   = $DefaultUtilizationThreshold
            MonitorArrays                 = $MonitorArrays
            MonitorResourceGroupName      = $ResourceGroup.ResourceGroupName
            MonitorRegion                 = $ResourceGroup.location
            MonitorVnetName               = $MonitorVNet.Name
            MonitorVnetResourceGroupName  = $MonitorVNet.ResourceGroupName
            MonitorVnetRegion             = $MonitorVNet.Location
            MonitorVnetProvisioningState  = $MonitorVNet.ProvisioningState
            MonitorSubnetName             = $MonitorSubnet.Name
            MonitorSubnetAddressPrefix    = $MonitorSubnet.AddressPrefix
        }
    } elseif ($MonitorType -eq "host") {
        $result = [PSCustomObject]@{
            MonitorArrays                 = $MonitorArrays
            MonitorResourceGroupName      = $ResourceGroup.ResourceGroupName
            MonitorRegion                 = $ResourceGroup.location
            MonitorVnetName               = $MonitorVNet.Name
            MonitorVnetResourceGroupName  = $MonitorVNet.ResourceGroupName
            MonitorVnetRegion             = $MonitorVNet.Location
            MonitorVnetProvisioningState  = $MonitorVNet.ProvisioningState
            MonitorSubnetName             = $MonitorSubnet.Name
            MonitorSubnetAddressPrefix    = $MonitorSubnet.AddressPrefix
            DefaultRunCommandTimeoutInMinute = $DefaultRunCommandTimeoutInMinute
        }
    }
    
    return $result
  }

  function Get-MonitorEnvironmentConfig {
    [CmdletBinding()]
    Param (
      [Parameter(Mandatory = $true)]
      $ResourceGroupName,
      [Parameter(Mandatory = $true)]
      [ValidateSet("DEFAULT_UTILIZATION_THRESHOLD", "DEFAULT_RUNCOMMAND_TIMEOUT_IN_MINUTE")]
      $ConfigName
    )
  
    $AzFunctionApp = Get-AzFunctionApp -ResourceGroupName $ResourceGroupName
    if (-not $AzFunctionApp) {
      throw "Azure function does not exist in the resource group $ResourceGroupName"
    }
    $AppSettings = Get-AzFunctionAppSetting -ResourceGroupName $ResourceGroupName -Name $AzFunctionApp.Name

    $ConfigValue = if ($ConfigName -eq "DEFAULT_UTILIZATION_THRESHOLD") {
      $DEFAULT_UTILIZATION_THRESHOLD
    } elseif ($ConfigName -eq "DEFAULT_RUNCOMMAND_TIMEOUT_IN_MINUTE") {
      $DEFAULT_RUNCOMMAND_TIMEOUT_IN_MINUTE
    }

    if ($AppSettings[$ConfigName]) {
      $ConfigValue = [int]$AppSettings[$ConfigName]
    }
    return $ConfigValue
  }
# SIG # Begin signature block
# MIIpRQYJKoZIhvcNAQcCoIIpNjCCKTICAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCBY4W8f71PYy5WF
# CM7xr0hBJ+do2I1jwrw5+oWPCcrwbKCCDfIwggbmMIIEzqADAgECAhB3vQ4DobcI
# +FSrBnIQ2QRHMA0GCSqGSIb3DQEBCwUAMFMxCzAJBgNVBAYTAkJFMRkwFwYDVQQK
# ExBHbG9iYWxTaWduIG52LXNhMSkwJwYDVQQDEyBHbG9iYWxTaWduIENvZGUgU2ln
# bmluZyBSb290IFI0NTAeFw0yMDA3MjgwMDAwMDBaFw0zMDA3MjgwMDAwMDBaMFkx
# CzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMS8wLQYDVQQD
# EyZHbG9iYWxTaWduIEdDQyBSNDUgQ29kZVNpZ25pbmcgQ0EgMjAyMDCCAiIwDQYJ
# KoZIhvcNAQEBBQADggIPADCCAgoCggIBANZCTfnjT8Yj9GwdgaYw90g9z9DljeUg
# IpYHRDVdBs8PHXBg5iZU+lMjYAKoXwIC947Jbj2peAW9jvVPGSSZfM8RFpsfe2vS
# o3toZXer2LEsP9NyBjJcW6xQZywlTVYGNvzBYkx9fYYWlZpdVLpQ0LB/okQZ6dZu
# bD4Twp8R1F80W1FoMWMK+FvQ3rpZXzGviWg4QD4I6FNnTmO2IY7v3Y2FQVWeHLw3
# 3JWgxHGnHxulSW4KIFl+iaNYFZcAJWnf3sJqUGVOU/troZ8YHooOX1ReveBbz/IM
# BNLeCKEQJvey83ouwo6WwT/Opdr0WSiMN2WhMZYLjqR2dxVJhGaCJedDCndSsZlR
# Qv+hst2c0twY2cGGqUAdQZdihryo/6LHYxcG/WZ6NpQBIIl4H5D0e6lSTmpPVAYq
# gK+ex1BC+mUK4wH0sW6sDqjjgRmoOMieAyiGpHSnR5V+cloqexVqHMRp5rC+QBmZ
# y9J9VU4inBDgoVvDsy56i8Te8UsfjCh5MEV/bBO2PSz/LUqKKuwoDy3K1JyYikpt
# WjYsL9+6y+JBSgh3GIitNWGUEvOkcuvuNp6nUSeRPPeiGsz8h+WX4VGHaekizIPA
# tw9FbAfhQ0/UjErOz2OxtaQQevkNDCiwazT+IWgnb+z4+iaEW3VCzYkmeVmda6tj
# cWKQJQ0IIPH/AgMBAAGjggGuMIIBqjAOBgNVHQ8BAf8EBAMCAYYwEwYDVR0lBAww
# CgYIKwYBBQUHAwMwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQU2rONwCSQ
# o2t30wygWd0hZ2R2C3gwHwYDVR0jBBgwFoAUHwC/RoAK/Hg5t6W0Q9lWULvOljsw
# gZMGCCsGAQUFBwEBBIGGMIGDMDkGCCsGAQUFBzABhi1odHRwOi8vb2NzcC5nbG9i
# YWxzaWduLmNvbS9jb2Rlc2lnbmluZ3Jvb3RyNDUwRgYIKwYBBQUHMAKGOmh0dHA6
# Ly9zZWN1cmUuZ2xvYmFsc2lnbi5jb20vY2FjZXJ0L2NvZGVzaWduaW5ncm9vdHI0
# NS5jcnQwQQYDVR0fBDowODA2oDSgMoYwaHR0cDovL2NybC5nbG9iYWxzaWduLmNv
# bS9jb2Rlc2lnbmluZ3Jvb3RyNDUuY3JsMFYGA1UdIARPME0wQQYJKwYBBAGgMgEy
# MDQwMgYIKwYBBQUHAgEWJmh0dHBzOi8vd3d3Lmdsb2JhbHNpZ24uY29tL3JlcG9z
# aXRvcnkvMAgGBmeBDAEEATANBgkqhkiG9w0BAQsFAAOCAgEACIhyJsav+qxfBsCq
# jJDa0LLAopf/bhMyFlT9PvQwEZ+PmPmbUt3yohbu2XiVppp8YbgEtfjry/RhETP2
# ZSW3EUKL2Glux/+VtIFDqX6uv4LWTcwRo4NxahBeGQWn52x/VvSoXMNOCa1Za7j5
# fqUuuPzeDsKg+7AE1BMbxyepuaotMTvPRkyd60zsvC6c8YejfzhpX0FAZ/ZTfepB
# 7449+6nUEThG3zzr9s0ivRPN8OHm5TOgvjzkeNUbzCDyMHOwIhz2hNabXAAC4ShS
# S/8SS0Dq7rAaBgaehObn8NuERvtz2StCtslXNMcWwKbrIbmqDvf+28rrvBfLuGfr
# 4z5P26mUhmRVyQkKwNkEcUoRS1pkw7x4eK1MRyZlB5nVzTZgoTNTs/Z7KtWJQDxx
# pav4mVn945uSS90FvQsMeAYrz1PYvRKaWyeGhT+RvuB4gHNU36cdZytqtq5NiYAk
# CFJwUPMB/0SuL5rg4UkI4eFb1zjRngqKnZQnm8qjudviNmrjb7lYYuA2eDYB+sGn
# iXomU6Ncu9Ky64rLYwgv/h7zViniNZvY/+mlvW1LWSyJLC9Su7UpkNpDR7xy3bzZ
# v4DB3LCrtEsdWDY3ZOub4YUXmimi/eYI0pL/oPh84emn0TCOXyZQK8ei4pd3iu/Y
# TT4m65lAYPM8Zwy2CHIpNVOBNNwwggcEMIIE7KADAgECAgxcuW61kTkv+4t8zgQw
# DQYJKoZIhvcNAQELBQAwWTELMAkGA1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNp
# Z24gbnYtc2ExLzAtBgNVBAMTJkdsb2JhbFNpZ24gR0NDIFI0NSBDb2RlU2lnbmlu
# ZyBDQSAyMDIwMB4XDTI0MDMxMTE0MDQxMloXDTI3MDMxMjE0MDQxMlowcjELMAkG
# A1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFDASBgNVBAcTC1NhbnRhIENs
# YXJhMRswGQYDVQQKExJQdXJlIFN0b3JhZ2UsIEluYy4xGzAZBgNVBAMTElB1cmUg
# U3RvcmFnZSwgSW5jLjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMCQ
# rioSn48IvHpTg5dofsUYj/pNTDidwjYUrcxVu78NoyhSweG8FhcxDi/SI40+8Fcc
# l3D5ZoqpjkFnGhzSwmpxU3J4AP7+fdTZht9eWD1I5qKY07esYwdPDV4yg+csPfdG
# PqI2XjRfT5UC3YkXQeUrX8KQZldD4KqvgxzpYcuBwsgHbTb/eArpi68YgFR2jgZG
# yZigfy8RuJMrL1thcBOe/VWjUyK21wVT8cuunBYFaStLHhsRBRMDcZBDuTSGC4ev
# E6oaCqlQbdMl9YFJ64mDQsKlCxrr7rmLVtcVzKGwmjp4b2xRwE+RmTh6JtrUL9Wx
# /3a3UzgAnDNimfwp85zoL48kyLtHqQ3FI8tVKGm+aBOgBZfmURoy7fbp4zKhGgqF
# bpOmILO16i4f999YsEEJQgIF3CtyH1R60/ZZWlDmoeeEgjAGrnd14muU5Hk3Cksr
# 43uPUAg+fV78Y0fDV85ibm42ZwwPuz6MI4HhYNUlGzRwIQ31vjaGuAMWHNqFKkcO
# 0JuIeHQ/gFKPnYIxnGC9H9R4Kw/uMezqtnYJwGU2epB/ABl/w7U4NgU2ZOxWB5BF
# y4frZ3f+hNgbjFUjMaXnVFotOJxXntzjdSl4znw8DaKiC5ooChteZMITG9p078p/
# TUsOJQbUtFADSY1hsfCfB7t+gJSNt5peS9GOZIMVAgMBAAGjggGxMIIBrTAOBgNV
# HQ8BAf8EBAMCB4AwgZsGCCsGAQUFBwEBBIGOMIGLMEoGCCsGAQUFBzAChj5odHRw
# Oi8vc2VjdXJlLmdsb2JhbHNpZ24uY29tL2NhY2VydC9nc2djY3I0NWNvZGVzaWdu
# Y2EyMDIwLmNydDA9BggrBgEFBQcwAYYxaHR0cDovL29jc3AuZ2xvYmFsc2lnbi5j
# b20vZ3NnY2NyNDVjb2Rlc2lnbmNhMjAyMDBWBgNVHSAETzBNMEEGCSsGAQQBoDIB
# MjA0MDIGCCsGAQUFBwIBFiZodHRwczovL3d3dy5nbG9iYWxzaWduLmNvbS9yZXBv
# c2l0b3J5LzAIBgZngQwBBAEwCQYDVR0TBAIwADBFBgNVHR8EPjA8MDqgOKA2hjRo
# dHRwOi8vY3JsLmdsb2JhbHNpZ24uY29tL2dzZ2NjcjQ1Y29kZXNpZ25jYTIwMjAu
# Y3JsMBMGA1UdJQQMMAoGCCsGAQUFBwMDMB8GA1UdIwQYMBaAFNqzjcAkkKNrd9MM
# oFndIWdkdgt4MB0GA1UdDgQWBBSzJ9KiDCa3UBiAajy+Iioj5kQjzDANBgkqhkiG
# 9w0BAQsFAAOCAgEAHsFQixeQEcoHurq9NWSUt4S39Q+UGP6crmVq3Wwy9g23YbdW
# g+SgMxoLUqdoDfA4k4B6Dyoo0jEQzn2kxnsnT9lNHKrcZHH88dv0hjfiH2qAiQWa
# zPjS3LhK2J6nhpyipJPpyRaSQG4x4aG0NB2D4WUfUz9CGAYsERJGww/wkTaaxMip
# ttKDTaI1C49u1igDfRzIO+Q8vuyyBFLiYTno/df97xtjNC+KxxFhDhl/4tawK6kw
# xaVzCMAfj48I67Wbo4DMH6pM1s19as7c3qp92i3MylGKsB6+u+o7UkbSdLNkS4AL
# I33CJOUc+GoK3Nt5IXXCFJTQFHBXkBdAur3gmlXEm8vlNG/1Sbxr0H7T1e7ABGH/
# 48o/+PeMLuCc72EeK5dJ4cX9NEQ3QnTsZHwGnYzjEOvOvP0s1c7yNsDbcUHoIqQv
# b5xS5aqMU5G+8sdPQ1nwpPf7gGaEEbAVW4w51Pam42qeN9HIPa+ZinXnsN02Kk1Q
# w0QwUqzaQy9W/gIquI0KOjw0LmoW9M/8S0lrjpEq2eEeUw9WQLhhUEIirFxGPtjq
# iCLiiS9CZ+kf2vWLJKUspkYv+OHT3q805Zg1dJsBFAzEYUFLb1mhmigDEO9bsMor
# jECIL2ijE5zHtbGkalrrsPWu8tiDT/B7P9GSYzKfOOy4PoOIfWSK0IxlS7Ixghqp
# MIIapQIBATBpMFkxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52
# LXNhMS8wLQYDVQQDEyZHbG9iYWxTaWduIEdDQyBSNDUgQ29kZVNpZ25pbmcgQ0Eg
# MjAyMAIMXLlutZE5L/uLfM4EMA0GCWCGSAFlAwQCAQUAoIGXMBkGCSqGSIb3DQEJ
# AzEMBgorBgEEAYI3AgEEMBwGCisGAQQBgjcCAQsxDjAMBgorBgEEAYI3AgEVMCsG
# CisGAQQBgjcCAQwxHTAboRmAF2h0dHBzOi8vcHVyZXN0b3JhZ2UuY29tMC8GCSqG
# SIb3DQEJBDEiBCDoSh1uodmBOyiBaswl9Z8do8KoqEBV074U6TxJTOEJUTANBgkq
# hkiG9w0BAQEFAASCAgCIAkZdbXrvkJ5Xs9vmoktzH7k1Liev9tvoTok6AiC8XX7Q
# D+QtwY1/oEWs0Aqj6iQil5UWz7mDkAJzD20ly9ZFz01EKsPn9VgLSadaAiE3wS2f
# EMH0eROKEfZDgPjn+CIhja4Sh1t/QBRg94zHmDVDQeYGXftGKSh82Th9v+CTZibY
# eMbWRo4MjC+RJhslNGgPheBd+mgV0v/0nX+7ANRerltKo0mZhB7Xf5sF4HczpXzq
# ikiNnBCXQNwJ9UY5l+m6/ygB0SZKexV9boZr/cNNxDPlPCd5lp/eAN+/oDqCU9Dp
# 2XCweFTNoKnML3i8EDeIKl0/kR6xBPD5ga2YG0tSsWLlNBPx/EF3i9fFF3bdbOyv
# i3oo/cWV5zyiEeFeFXOoO1iVOcS39cSd4uewP95GNAySCij+lat7TtvH+XjTGZju
# J/10LaByKWIc1rGIG3YTbvsc5CbtLxBhRsh07Ok6TzVpg4RnC76RkDzkJopxobjP
# WH33N53kuMVVyPiNm4UPxLETvqGodL2zOlTfpp0JjCa3L3aP79vuuDQVord/68V6
# oV/mk+vUK7o4+FY94OxiWs96z400nlrTg6ixeTyt/4cQAzNabHQJ2/IeCELB40w4
# w740j0UOi7orPY+/smUFN2AfLnF4XdTwXzzReb6WphUCg0W7TOtOeVEeYDL2TKGC
# F3cwghdzBgorBgEEAYI3AwMBMYIXYzCCF18GCSqGSIb3DQEHAqCCF1AwghdMAgED
# MQ8wDQYJYIZIAWUDBAIBBQAweAYLKoZIhvcNAQkQAQSgaQRnMGUCAQEGCWCGSAGG
# /WwHATAxMA0GCWCGSAFlAwQCAQUABCBcAX3YGwbPDxxzqu1xLUQ07FXoFYwvYu8w
# eosqTAIEvgIRAJIIQ66JJSnyXBGxj5pZkZQYDzIwMjYwNTIxMDkxNjU5WqCCEzow
# ggbtMIIE1aADAgECAhAKgO8YS43xBYLRxHanlXRoMA0GCSqGSIb3DQEBCwUAMGkx
# CzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjFBMD8GA1UEAxM4
# RGlnaUNlcnQgVHJ1c3RlZCBHNCBUaW1lU3RhbXBpbmcgUlNBNDA5NiBTSEEyNTYg
# MjAyNSBDQTEwHhcNMjUwNjA0MDAwMDAwWhcNMzYwOTAzMjM1OTU5WjBjMQswCQYD
# VQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xOzA5BgNVBAMTMkRpZ2lD
# ZXJ0IFNIQTI1NiBSU0E0MDk2IFRpbWVzdGFtcCBSZXNwb25kZXIgMjAyNSAxMIIC
# IjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA0EasLRLGntDqrmBWsytXum9R
# /4ZwCgHfyjfMGUIwYzKomd8U1nH7C8Dr0cVMF3BsfAFI54um8+dnxk36+jx0Tb+k
# +87H9WPxNyFPJIDZHhAqlUPt281mHrBbZHqRK71Em3/hCGC5KyyneqiZ7syvFXJ9
# A72wzHpkBaMUNg7MOLxI6E9RaUueHTQKWXymOtRwJXcrcTTPPT2V1D/+cFllESvi
# H8YjoPFvZSjKs3SKO1QNUdFd2adw44wDcKgH+JRJE5Qg0NP3yiSyi5MxgU6cehGH
# r7zou1znOM8odbkqoK+lJ25LCHBSai25CFyD23DZgPfDrJJJK77epTwMP6eKA0kW
# a3osAe8fcpK40uhktzUd/Yk0xUvhDU6lvJukx7jphx40DQt82yepyekl4i0r8OEp
# s/FNO4ahfvAk12hE5FVs9HVVWcO5J4dVmVzix4A77p3awLbr89A90/nWGjXMGn7F
# QhmSlIUDy9Z2hSgctaepZTd0ILIUbWuhKuAeNIeWrzHKYueMJtItnj2Q+aTyLLKL
# M0MheP/9w6CtjuuVHJOVoIJ/DtpJRE7Ce7vMRHoRon4CWIvuiNN1Lk9Y+xZ66laz
# s2kKFSTnnkrT3pXWETTJkhd76CIDBbTRofOsNyEhzZtCGmnQigpFHti58CSmvEyJ
# cAlDVcKacJ+A9/z7eacCAwEAAaOCAZUwggGRMAwGA1UdEwEB/wQCMAAwHQYDVR0O
# BBYEFOQ7/PIx7f391/ORcWMZUEPPYYzoMB8GA1UdIwQYMBaAFO9vU0rp5AZ8esri
# kFb2L9RJ7MtOMA4GA1UdDwEB/wQEAwIHgDAWBgNVHSUBAf8EDDAKBggrBgEFBQcD
# CDCBlQYIKwYBBQUHAQEEgYgwgYUwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRp
# Z2ljZXJ0LmNvbTBdBggrBgEFBQcwAoZRaHR0cDovL2NhY2VydHMuZGlnaWNlcnQu
# Y29tL0RpZ2lDZXJ0VHJ1c3RlZEc0VGltZVN0YW1waW5nUlNBNDA5NlNIQTI1NjIw
# MjVDQTEuY3J0MF8GA1UdHwRYMFYwVKBSoFCGTmh0dHA6Ly9jcmwzLmRpZ2ljZXJ0
# LmNvbS9EaWdpQ2VydFRydXN0ZWRHNFRpbWVTdGFtcGluZ1JTQTQwOTZTSEEyNTYy
# MDI1Q0ExLmNybDAgBgNVHSAEGTAXMAgGBmeBDAEEAjALBglghkgBhv1sBwEwDQYJ
# KoZIhvcNAQELBQADggIBAGUqrfEcJwS5rmBB7NEIRJ5jQHIh+OT2Ik/bNYulCrVv
# hREafBYF0RkP2AGr181o2YWPoSHz9iZEN/FPsLSTwVQWo2H62yGBvg7ouCODwrx6
# ULj6hYKqdT8wv2UV+Kbz/3ImZlJ7YXwBD9R0oU62PtgxOao872bOySCILdBghQ/Z
# LcdC8cbUUO75ZSpbh1oipOhcUT8lD8QAGB9lctZTTOJM3pHfKBAEcxQFoHlt2s9s
# XoxFizTeHihsQyfFg5fxUFEp7W42fNBVN4ueLaceRf9Cq9ec1v5iQMWTFQa0xNqI
# tH3CPFTG7aEQJmmrJTV3Qhtfparz+BW60OiMEgV5GWoBy4RVPRwqxv7Mk0Sy4QHs
# 7v9y69NBqycz0BZwhB9WOfOu/CIJnzkQTwtSSpGGhLdjnQ4eBpjtP+XB3pQCtv4E
# 5UCSDag6+iX8MmB10nfldPF9SVD7weCC3yXZi/uuhqdwkgVxuiMFzGVFwYbQsiGn
# oa9F5AaAyBjFBtXVLcKtapnMG3VH3EmAp/jsJ3FVF3+d1SVDTmjFjLbNFZUWMXuZ
# yvgLfgyPehwJVxwC+UpX2MSey2ueIu9THFVkT+um1vshETaWyQo8gmBto/m3acaP
# 9QsuLj3FNwFlTxq25+T4QwX9xa6ILs84ZPvmpovq90K8eWyG2N01c4IhSOxqt81n
# MIIGtDCCBJygAwIBAgIQDcesVwX/IZkuQEMiDDpJhjANBgkqhkiG9w0BAQsFADBi
# MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
# d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3Qg
# RzQwHhcNMjUwNTA3MDAwMDAwWhcNMzgwMTE0MjM1OTU5WjBpMQswCQYDVQQGEwJV
# UzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xQTA/BgNVBAMTOERpZ2lDZXJ0IFRy
# dXN0ZWQgRzQgVGltZVN0YW1waW5nIFJTQTQwOTYgU0hBMjU2IDIwMjUgQ0ExMIIC
# IjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAtHgx0wqYQXK+PEbAHKx126NG
# aHS0URedTa2NDZS1mZaDLFTtQ2oRjzUXMmxCqvkbsDpz4aH+qbxeLho8I6jY3xL1
# IusLopuW2qftJYJaDNs1+JH7Z+QdSKWM06qchUP+AbdJgMQB3h2DZ0Mal5kYp77j
# YMVQXSZH++0trj6Ao+xh/AS7sQRuQL37QXbDhAktVJMQbzIBHYJBYgzWIjk8eDrY
# hXDEpKk7RdoX0M980EpLtlrNyHw0Xm+nt5pnYJU3Gmq6bNMI1I7Gb5IBZK4ivbVC
# iZv7PNBYqHEpNVWC2ZQ8BbfnFRQVESYOszFI2Wv82wnJRfN20VRS3hpLgIR4hjzL
# 0hpoYGk81coWJ+KdPvMvaB0WkE/2qHxJ0ucS638ZxqU14lDnki7CcoKCz6eum5A1
# 9WZQHkqUJfdkDjHkccpL6uoG8pbF0LJAQQZxst7VvwDDjAmSFTUms+wV/FbWBqi7
# fTJnjq3hj0XbQcd8hjj/q8d6ylgxCZSKi17yVp2NL+cnT6Toy+rN+nM8M7LnLqCr
# O2JP3oW//1sfuZDKiDEb1AQ8es9Xr/u6bDTnYCTKIsDq1BtmXUqEG1NqzJKS4kOm
# xkYp2WyODi7vQTCBZtVFJfVZ3j7OgWmnhFr4yUozZtqgPrHRVHhGNKlYzyjlroPx
# ul+bgIspzOwbtmsgY1MCAwEAAaOCAV0wggFZMBIGA1UdEwEB/wQIMAYBAf8CAQAw
# HQYDVR0OBBYEFO9vU0rp5AZ8esrikFb2L9RJ7MtOMB8GA1UdIwQYMBaAFOzX44LS
# cV1kTN8uZz/nupiuHA9PMA4GA1UdDwEB/wQEAwIBhjATBgNVHSUEDDAKBggrBgEF
# BQcDCDB3BggrBgEFBQcBAQRrMGkwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRp
# Z2ljZXJ0LmNvbTBBBggrBgEFBQcwAoY1aHR0cDovL2NhY2VydHMuZGlnaWNlcnQu
# Y29tL0RpZ2lDZXJ0VHJ1c3RlZFJvb3RHNC5jcnQwQwYDVR0fBDwwOjA4oDagNIYy
# aHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZFJvb3RHNC5j
# cmwwIAYDVR0gBBkwFzAIBgZngQwBBAIwCwYJYIZIAYb9bAcBMA0GCSqGSIb3DQEB
# CwUAA4ICAQAXzvsWgBz+Bz0RdnEwvb4LyLU0pn/N0IfFiBowf0/Dm1wGc/Do7oVM
# Y2mhXZXjDNJQa8j00DNqhCT3t+s8G0iP5kvN2n7Jd2E4/iEIUBO41P5F448rSYJ5
# 9Ib61eoalhnd6ywFLerycvZTAz40y8S4F3/a+Z1jEMK/DMm/axFSgoR8n6c3nuZB
# 9BfBwAQYK9FHaoq2e26MHvVY9gCDA/JYsq7pGdogP8HRtrYfctSLANEBfHU16r3J
# 05qX3kId+ZOczgj5kjatVB+NdADVZKON/gnZruMvNYY2o1f4MXRJDMdTSlOLh0HC
# n2cQLwQCqjFbqrXuvTPSegOOzr4EWj7PtspIHBldNE2K9i697cvaiIo2p61Ed2p8
# xMJb82Yosn0z4y25xUbI7GIN/TpVfHIqQ6Ku/qjTY6hc3hsXMrS+U0yy+GWqAXam
# 4ToWd2UQ1KYT70kZjE4YtL8Pbzg0c1ugMZyZZd/BdHLiRu7hAWE6bTEm4XYRkA6T
# l4KSFLFk43esaUeqGkH/wyW4N7OigizwJWeukcyIPbAvjSabnf7+Pu0VrFgoiovR
# Diyx3zEdmcif/sYQsfch28bZeUz2rtY/9TCA6TD8dC3JE3rYkrhLULy7Dc90G6e8
# BlqmyIjlgp2+VqsS9/wQD7yFylIz0scmbKvFoW2jNrbM1pD2T7m3XDCCBY0wggR1
# oAMCAQICEA6bGI750C3n79tQ4ghAGFowDQYJKoZIhvcNAQEMBQAwZTELMAkGA1UE
# BhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2lj
# ZXJ0LmNvbTEkMCIGA1UEAxMbRGlnaUNlcnQgQXNzdXJlZCBJRCBSb290IENBMB4X
# DTIyMDgwMTAwMDAwMFoXDTMxMTEwOTIzNTk1OVowYjELMAkGA1UEBhMCVVMxFTAT
# BgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTEh
# MB8GA1UEAxMYRGlnaUNlcnQgVHJ1c3RlZCBSb290IEc0MIICIjANBgkqhkiG9w0B
# AQEFAAOCAg8AMIICCgKCAgEAv+aQc2jeu+RdSjwwIjBpM+zCpyUuySE98orYWcLh
# Kac9WKt2ms2uexuEDcQwH/MbpDgW61bGl20dq7J58soR0uRf1gU8Ug9SH8aeFaV+
# vp+pVxZZVXKvaJNwwrK6dZlqczKU0RBEEC7fgvMHhOZ0O21x4i0MG+4g1ckgHWMp
# Lc7sXk7Ik/ghYZs06wXGXuxbGrzryc/NrDRAX7F6Zu53yEioZldXn1RYjgwrt0+n
# MNlW7sp7XeOtyU9e5TXnMcvak17cjo+A2raRmECQecN4x7axxLVqGDgDEI3Y1Dek
# LgV9iPWCPhCRcKtVgkEy19sEcypukQF8IUzUvK4bA3VdeGbZOjFEmjNAvwjXWkmk
# wuapoGfdpCe8oU85tRFYF/ckXEaPZPfBaYh2mHY9WV1CdoeJl2l6SPDgohIbZpp0
# yt5LHucOY67m1O+SkjqePdwA5EUlibaaRBkrfsCUtNJhbesz2cXfSwQAzH0clcOP
# 9yGyshG3u3/y1YxwLEFgqrFjGESVGnZifvaAsPvoZKYz0YkH4b235kOkGLimdwHh
# D5QMIR2yVCkliWzlDlJRR3S+Jqy2QXXeeqxfjT/JvNNBERJb5RBQ6zHFynIWIgnf
# fEx1P2PsIV/EIFFrb7GrhotPwtZFX50g/KEexcCPorF+CiaZ9eRpL5gdLfXZqbId
# 5RsCAwEAAaOCATowggE2MA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFOzX44LS
# cV1kTN8uZz/nupiuHA9PMB8GA1UdIwQYMBaAFEXroq/0ksuCMS1Ri6enIZ3zbcgP
# MA4GA1UdDwEB/wQEAwIBhjB5BggrBgEFBQcBAQRtMGswJAYIKwYBBQUHMAGGGGh0
# dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBDBggrBgEFBQcwAoY3aHR0cDovL2NhY2Vy
# dHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENBLmNydDBFBgNV
# HR8EPjA8MDqgOKA2hjRodHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRB
# c3N1cmVkSURSb290Q0EuY3JsMBEGA1UdIAQKMAgwBgYEVR0gADANBgkqhkiG9w0B
# AQwFAAOCAQEAcKC/Q1xV5zhfoKN0Gz22Ftf3v1cHvZqsoYcs7IVeqRq7IviHGmlU
# Iu2kiHdtvRoU9BNKei8ttzjv9P+Aufih9/Jy3iS8UgPITtAq3votVs/59PesMHqa
# i7Je1M/RQ0SbQyHrlnKhSLSZy51PpwYDE3cnRNTnf+hZqPC/Lwum6fI0POz3A8eH
# qNJMQBk1RmppVLC4oVaO7KTVPeix3P0c2PR3WlxUjG/voVA9/HYJaISfb8rbII01
# YBwCA8sgsKxYoA5AY8WYIsGyWfVVa88nq2x2zm8jLfR+cWojayL/ErhULSd+2DrZ
# 8LaHlv1b0VysGMNNn3O3AamfV6peKOK5lDGCA3wwggN4AgEBMH0waTELMAkGA1UE
# BhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMUEwPwYDVQQDEzhEaWdpQ2Vy
# dCBUcnVzdGVkIEc0IFRpbWVTdGFtcGluZyBSU0E0MDk2IFNIQTI1NiAyMDI1IENB
# MQIQCoDvGEuN8QWC0cR2p5V0aDANBglghkgBZQMEAgEFAKCB0TAaBgkqhkiG9w0B
# CQMxDQYLKoZIhvcNAQkQAQQwHAYJKoZIhvcNAQkFMQ8XDTI2MDUyMTA5MTY1OVow
# KwYLKoZIhvcNAQkQAgwxHDAaMBgwFgQU3WIwrIYKLTBr2jixaHlSMAf7QX4wLwYJ
# KoZIhvcNAQkEMSIEIJrXtfE3yZT8hxlfzd7vOYqiDnKpQg6l2BUvSuDBUuKTMDcG
# CyqGSIb3DQEJEAIvMSgwJjAkMCIEIEqgP6Is11yExVyTj4KOZ2ucrsqzP+NtJpqj
# NPFGEQozMA0GCSqGSIb3DQEBAQUABIICAEW9yreDhlITCt+EotxJAAfkcteoQUQc
# pYQkC/GjTM+FT1+OBtAh22HgEXTotTAaFg/ef2Y1jrh4k1D0dq/cFbJxvrHAn4Xa
# xUz1FT/hOiLlICz7O7o76DMfaf9baysIJrHyZKozbABGejf5QSUUSjg6sz+K5+Z2
# Y7vXTjiwW4eu7EuVuJ4dXqQ94uLptrZXnSqwImMJxk4fGLo34P2QgpGjvqHnZLaQ
# MxVpui8gosoOxeZWg+ibNLtjon+EgmHvFRFPP6wH95noJ5iph2ccUTS5HFVCUbtv
# ipysQvS+GhbDTU73kuQb4KTqbB/hYB6M9gd4yj4TluulIvHeEOdKDbH/nBVFGJwJ
# jPRjJtIpVkhE+YoSk8ygHObkZKibOeNSnPn8gGYbmI1lHBv7jhf04DblpYw3TlN6
# tDPXCh7H+cLzkiuC23PHi8TfWTKBUKA+yImOsdBpb4qWAuOioNvNdKMPpNbVoCTe
# D0NcsnsP1RYi0IW0fwctG3Q7djYLPBpG2DNGW6zg3NUxIwZGZovOxts2IP3ICsG/
# FRmBuW+y6Hcvwsc0yIJZ7NRPd681g4ZSZRDOYL4db+voCv+mNnbgua29xjBLmAj8
# FphAmCEVk9wPO0K5/aDhL5tR9VwmwYsErHodiDYm1jSw71q3sWHacj3jzj4YK5yN
# kaCfMxtcEUyr
# SIG # End signature block