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 |