modules/AzStack.Insights/AzStack.Insights.Helper.psm1
|
<################################################################
# # # Copyright (C) Microsoft Corporation. All rights reserved. # # # ################################################################> function Test-isFailoverCluster { [CmdletBinding()] param() # Detecting Failover Cluster based on the presence of the Get-Cluster cmdlet return ((Get-Command "Get-Cluster" -ErrorAction Ignore) -and (Get-Cluster -ErrorAction Ignore)) } function Get-FirewallEndpoints { <# .SYNOPSIS Retrieves the required firewall endpoints for a specified Azure region. .DESCRIPTION This function returns a collection of firewall endpoints that need to be opened for Azure Local operations in the specified region. This comes from https://learn.microsoft.com/en-us/azure/azure-local/concepts/system-requirements-23h2?view=azloc-2601&tabs=azure-public .OUTPUTS Collection of PSObjects representing firewall endpoints. .EXAMPLE $endpoints = Get-FirewallEndpoints -Region 'eastus' Retrieves all firewall endpoints for the 'eastus' region and stores them in the $endpoints variable. #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string]$Region ) $endpointRootDir = Get-Item -Path "$PSScriptRoot\config\firewall_endpoints" try { switch ($Region) { 'australiaeast' { $configData = Import-PowerShellDataFile -Path (Join-Path -Path $endpointRootDir.FullName -ChildPath "AustraliaEastEndpoints.psd1") } 'canadacentral' { $configData = Import-PowerShellDataFile -Path (Join-Path -Path $endpointRootDir.FullName -ChildPath "CanadaCentralEndpoints.psd1") } 'eastus' { $configData = Import-PowerShellDataFile -Path (Join-Path -Path $endpointRootDir.FullName -ChildPath "EastUSEndpoints.psd1") } 'centralindia' { $configData = Import-PowerShellDataFile -Path (Join-Path -Path $endpointRootDir.FullName -ChildPath "IndiaCentralEndpoints.psd1") } 'japaneast' { $configData = Import-PowerShellDataFile -Path (Join-Path -Path $endpointRootDir.FullName -ChildPath "JapanEastEndpoints.psd1") } 'southcentralus' { $configData = Import-PowerShellDataFile -Path (Join-Path -Path $endpointRootDir.FullName -ChildPath "SouthCentralUSEndpoints.psd1") } 'southeastasia' { $configData = Import-PowerShellDataFile -Path (Join-Path -Path $endpointRootDir.FullName -ChildPath "SoutheastAsiaEndpoints.psd1") } 'westeurope' { $configData = Import-PowerShellDataFile -Path (Join-Path -Path $endpointRootDir.FullName -ChildPath "WestEuropeEndpoints.psd1") } 'usgovvirginia' { $configData = Import-PowerShellDataFile -Path (Join-Path -Path $endpointRootDir.FullName -ChildPath "USGovVirginiaEndpoints.psd1") } default { return $null } } return $configData.Endpoints } catch { throw "Failed to load firewall endpoints for region '$Region'. $_" } } function Get-MocConfigCached { <# .SYNOPSIS Returns MOC configuration, using the global cache when available. .DESCRIPTION Reads from $Global:MocArbCachedMocConfig if populated (set by the MocArb component). On cache miss, calls Get-MocConfig directly with up to 5 retry attempts and exponential backoff to handle transient MOC file lock errors on the shared catalogs file. .PARAMETER CallerName Label used in diagnostic messages. .OUTPUTS The MOC configuration object, or $null if all attempts fail. #> [CmdletBinding()] param ( [Parameter(Mandatory = $false)] [string]$CallerName = 'MocArb' ) if ($Global:MocArbCachedMocConfig) { return $Global:MocArbCachedMocConfig } Write-Verbose "[$env:COMPUTERNAME][$CallerName] Cache miss, calling Get-MocConfig directly" $mocConfig = $null for ($attempt = 1; $attempt -le 5; $attempt++) { try { $mocConfig = Get-MocConfig -ErrorAction SilentlyContinue 2>$null } catch { } if ($mocConfig) { Write-Verbose "[$env:COMPUTERNAME][$CallerName] Get-MocConfig succeeded on attempt $attempt" return $mocConfig } if ($attempt -lt 5) { $backoff = 2 * $attempt Write-Warning "[$env:COMPUTERNAME][$CallerName] Get-MocConfig attempt $attempt failed, retrying in ${backoff}s" Start-Sleep -Seconds $backoff } } Write-Warning "[$env:COMPUTERNAME][$CallerName] Get-MocConfig failed after 5 attempts" return $null } function Get-ArbControlPlaneVMs { <# .SYNOPSIS Returns local Hyper-V VM objects for the ARB control-plane VM. .DESCRIPTION Calls Get-VM -Name '*control-plan*' on the local node, then disambiguates when multiple VMs match (e.g., AKS workloads also create control-plane VMs). Uses two methods to identify the real ARB VM: 1. Cluster group: ARB VM belongs to the '<clusterName>-arcbridge' cluster group. The cluster group name reliably contains 'arcbridge' even though the VM name does not. 2. MOC kubeconfig IP: Matches the API server IP from the ARB kubeconfig to VM network adapters. Kubeconfigs under paths containing 'arcbridge' are preferred over AKS kubeconfigs. Falls back to returning all matches if disambiguation fails. .OUTPUTS Array of Hyper-V VM objects, or empty array if none found on this node. #> [CmdletBinding()] param () $vms = @(Get-VM -Name '*control-plan*' -ErrorAction Ignore) if ($vms.Count -le 1) { return $vms } # Multiple VMs matched — AKS workloads can create additional control-plane VMs. Write-Verbose "[$env:COMPUTERNAME][Get-ArbControlPlaneVMs] Found $($vms.Count) VMs matching '*control-plan*', disambiguating ARB VM..." # Method 1: Cross-reference with the ARB failover cluster group (<clusterName>-arcbridge) # The cluster group name reliably contains 'arcbridge'; the VM name itself does not. try { $clusterName = Get-AzsSupportEceManagementClusterName -ErrorAction Stop $arbGroupName = "$clusterName-arcbridge" $arbGroup = Get-ClusterGroup -Name $arbGroupName -ErrorAction Ignore if ($arbGroup) { $arbGroupResources = @(Get-ClusterResource -InputObject $arbGroup -ErrorAction Ignore | Where-Object { $_.ResourceType -eq 'Virtual Machine' }) if ($arbGroupResources.Count -gt 0) { $arbResourceNames = @($arbGroupResources | ForEach-Object { $_.Name }) # Cluster resource names for VMs are typically 'Virtual Machine <VMName>' $matched = @($vms | Where-Object { $vmName = $_.Name $arbResourceNames -contains $vmName -or $arbResourceNames -contains "Virtual Machine $vmName" }) if ($matched.Count -gt 0) { Write-Verbose "[$env:COMPUTERNAME][Get-ArbControlPlaneVMs] Narrowed to $($matched.Count) VM(s) by cluster group '$arbGroupName'" return $matched } } } } catch { Write-Warning "[$env:COMPUTERNAME][Get-ArbControlPlaneVMs] Cluster group cross-reference skipped: $_" } # Method 2: Match VM network adapter IPs against the ARB kubeconfig API server IP # Prefer kubeconfigs whose path contains 'arcbridge' to avoid picking up AKS kubeconfigs. $arbIp = $null $mocCfg = Get-MocConfigCached -CallerName 'Get-ArbControlPlaneVMs' if ($mocCfg -and $mocCfg.WorkingDir -and (Test-Path $mocCfg.WorkingDir)) { $kubeconfigs = @(Get-ChildItem -Path $mocCfg.WorkingDir -Filter 'kubeconfig' -Recurse -Depth 10 -ErrorAction Ignore) # Prefer kubeconfigs under arcbridge paths $arcbridgeKubeconfigs = @($kubeconfigs | Where-Object { $_.FullName -like '*arcbridge*' }) $orderedKubeconfigs = if ($arcbridgeKubeconfigs.Count -gt 0) { $arcbridgeKubeconfigs } else { $kubeconfigs } foreach ($kc in $orderedKubeconfigs) { $content = Get-Content -Path $kc.FullName -Raw -ErrorAction Ignore if ($content -match 'server:\s*https?://([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+):6443') { $arbIp = $matches[1] break } } } if ($arbIp) { $matched = @($vms | Where-Object { $vmIps = @( $_ | Get-VMNetworkAdapter -ErrorAction Ignore | ForEach-Object { $_.IPAddresses } | Where-Object { $_ -match '^\d+\.' } ) $vmIps -contains $arbIp }) if ($matched.Count -gt 0) { Write-Verbose "[$env:COMPUTERNAME][Get-ArbControlPlaneVMs] Narrowed to $($matched.Count) VM(s) by MOC kubeconfig IP ($arbIp)" return $matched } } Write-Warning "[$env:COMPUTERNAME][Get-ArbControlPlaneVMs] Could not disambiguate $($vms.Count) control-plane VMs. Returning all matches." return $vms } function Get-ArbControlPlaneTargets { <# .SYNOPSIS Discovers ARB control-plane VM IPv4 addresses. .DESCRIPTION Uses two methods to find ARB control-plane VM IPs: 1. Local Hyper-V (Get-VM) for VMs matching '*control-plan*' on this node. 2. Parses kubeconfig files in the MOC working directory and ClusterStorage for the Kubernetes API server IP (port 6443). Method 2 uses Get-MocConfigCached to locate the MOC working directory. .PARAMETER CallerName Label used in diagnostic messages. .OUTPUTS Array of unique IPv4 address strings, or empty array if none found. #> [CmdletBinding()] param ( [Parameter(Mandatory = $false)] [string]$CallerName = 'MocArb' ) $arbTargets = @() # Method 1: Local Get-VM for ARB control-plane VMs on this node $arbVMs = Get-ArbControlPlaneVMs if ($arbVMs) { $arbTargets = @( $arbVMs | Get-VMNetworkAdapter -ErrorAction Ignore | ForEach-Object { $_.IPAddresses } | Where-Object { $_ } | Where-Object { $_ -match '^\d+\.' } ) } # Method 2: Parse kubeconfig files for API server IP if ($arbTargets.Count -eq 0) { $searchPaths = @() $mocCfg = Get-MocConfigCached -CallerName $CallerName if ($mocCfg.WorkingDir) { $searchPaths += $mocCfg.WorkingDir } $searchPaths += 'C:\ClusterStorage' foreach ($searchPath in $searchPaths) { if (-not (Test-Path $searchPath)) { continue } $kubeconfigs = @(Get-ChildItem -Path $searchPath -Filter 'kubeconfig' -Recurse -Depth 10 -ErrorAction Ignore) # On clusters with AKS workloads, multiple kubeconfigs exist (ARB + AKS target clusters). # Prefer kubeconfigs whose path contains 'arcbridge' to avoid testing AKS endpoints. $arcbridgeKubeconfigs = @($kubeconfigs | Where-Object { $_.FullName -like '*arcbridge*' }) $orderedKubeconfigs = if ($arcbridgeKubeconfigs.Count -gt 0) { $arcbridgeKubeconfigs } else { $kubeconfigs } foreach ($kc in $orderedKubeconfigs) { $content = Get-Content -Path $kc.FullName -Raw -ErrorAction Ignore if ($content -match 'server:\s*https?://([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+):6443') { $arbTargets += $matches[1] } } $arbTargets = @($arbTargets | Select-Object -Unique) if ($arbTargets.Count -gt 0) { break } } } return $arbTargets } function Get-ArbKubeconfigs { <# .SYNOPSIS Finds ARB kubeconfig files on the local node. .DESCRIPTION Searches the MOC working directory and ClusterStorage for kubeconfig files that contain a Kubernetes API server endpoint (port 6443). .PARAMETER CallerName Label used in diagnostic messages. .OUTPUTS Array of FileInfo objects for matching kubeconfig files. #> [CmdletBinding()] param ( [Parameter(Mandatory = $false)] [string]$CallerName = 'MocArb' ) $searchPaths = @() $mocCfg = Get-MocConfigCached -CallerName $CallerName if ($mocCfg.WorkingDir) { $searchPaths += $mocCfg.WorkingDir } $searchPaths += 'C:\ClusterStorage' $found = @() foreach ($searchPath in $searchPaths) { if (-not (Test-Path $searchPath)) { continue } $kubeconfigs = @(Get-ChildItem -Path $searchPath -Filter 'kubeconfig' -Recurse -Depth 10 -ErrorAction Ignore) foreach ($kc in $kubeconfigs) { $content = Get-Content -Path $kc.FullName -Raw -ErrorAction Ignore if ($content -match 'server:\s*https?://[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+:6443') { $found += $kc } } if ($found.Count -gt 0) { break } } return $found } # SIG # Begin signature block # MIInSQYJKoZIhvcNAQcCoIInOjCCJzYCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCBzuxeAg44Hx7Th # lOt7GcfhJyC83BomupOLe68oqOXHn6CCDLowggX1MIID3aADAgECAhMzAAACHU0Z # yE7XD1dIAAAAAAIdMA0GCSqGSIb3DQEBCwUAMFcxCzAJBgNVBAYTAlVTMR4wHAYD # VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBD # b2RlIFNpZ25pbmcgUENBIDIwMjQwHhcNMjYwNDE2MTg1OTQzWhcNMjcwNDE1MTg1 # OTQzWjB0MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE # BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYD # VQQDExVNaWNyb3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IB # DwAwggEKAoIBAQDQvewXxx9gZZFC6Ys1WBay8BJ8kGA4JQnH5CMafqOASlTpK9H8 # o5ZXTXt0caVQTNMUPt445wXYD+dFtaKWTwDn1I52oUSrC9vJin1Gsqt+zyKJL5Dg # 3eQXbQNR61DmMy20GLTIO3SFed9Rfi/ophgCLGFLDR3r0KvHjwMb/jYWS0celV/4 # Lz27LfAekm8v9E5IXaeiXbAUYZKK090n4CVl3JBtbN+9DtI9SNu/yjvozW52/u7R # X/Ttpa/KDlpuokZ+Zcbvmtd9ur9gFLvZzh41o9MsE/clQtdaFWGvuo6Jua/ntpgk # ey3E5/vBFe+MJPG6phdnuo6r57ZudCudiI1bAgMBAAGjggGbMIIBlzAOBgNVHQ8B # Af8EBAMCB4AwHwYDVR0lBBgwFgYKKwYBBAGCN0wIAQYIKwYBBQUHAwMwHQYDVR0O # BBYEFH6QuMwqcPG0hQlQ6c5jCtTTLrVeMEUGA1UdEQQ+MDykOjA4MR4wHAYDVQQL # ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xFjAUBgNVBAUTDTIzMDAxMis1MDc1NTkw # HwYDVR0jBBgwFoAUf1k/VCHarU/vBeXmo9ctBpQSCDEwYAYDVR0fBFkwVzBVoFOg # UYZPaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jcmwvTWljcm9zb2Z0 # JTIwQ29kZSUyMFNpZ25pbmclMjBQQ0ElMjAyMDI0LmNybDBtBggrBgEFBQcBAQRh # MF8wXQYIKwYBBQUHMAKGUWh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMv # Y2VydHMvTWljcm9zb2Z0JTIwQ29kZSUyMFNpZ25pbmclMjBQQ0ElMjAyMDI0LmNy # dDAMBgNVHRMBAf8EAjAAMA0GCSqGSIb3DQEBCwUAA4ICAQBKTbYOjzwTG/DXGaz9 # s6+fQeaTtDcFmMY+5UyVFCyj7Pv+5i37qfX8lSL/tBIfYQfWsMuBQlfZurJD6r4H # VJ2CeH+1fgiq8dcHdVKoZ3Sa2qXoX3cq9iS8cVb06B7+5/XJ7I0OxHH9fDsvJ3T3 # w5V/ZtAIFmLrl+P0CtG+92uzRsn0nTbdFjOkLMLWPLAU3THohKRlSEMgFJpPkm5n # 5UAZ35xX6FWCrDLsSKb555bTifwa8mJBwdlof0bmfYidH+dxZ1FdDxvLnNl9zeKs # A4kejaaIqqIPguhwAti5Ql7BlTNoJNwxCvBmqW2MQLnCkYN/VVUsR3V2x/rcTNzo # Bf/Z/SpROvdaA2ZOOd1uioXJt3tdLQ7vHpqpib0KfWr/FWXW10q38VxfCnRQBqzb # SuztR7nEMuzX7Ck+B/XaPDXd1qh72+QYyB0Z2VzWmO9zsnb9Uq/dwu8LGeQqnyu6 # 7SDGACvnXii2fb9+US492VTnXSnFKyqwgzUyFMtZK1/sHYTv6bG4TtQUygQxTN+Z # V+aJIlKO2MqZ7bKrAnOzS9m6NgoTdWOq11bTOZwKlIEV/EhV9SWkDmdpR/hPPT2v # 6TEj4F8PT/zHjRezIU5c/DGlt/VhY/pK0XkJtEyMmmS1BMtjU/rqBZVMIm3dnxQs # /TBByr+Cf8Z1r7aifQVQ+WSqzjCCBr0wggSloAMCAQICEzMAAAA5O7Y3Gb8GHWcA # AAAAADkwDQYJKoZIhvcNAQEMBQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpX # YXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQg # Q29ycG9yYXRpb24xMjAwBgNVBAMTKU1pY3Jvc29mdCBSb290IENlcnRpZmljYXRl # IEF1dGhvcml0eSAyMDExMB4XDTI0MDgwODIwNTQxOFoXDTM2MDMyMjIyMTMwNFow # VzELMAkGA1UEBhMCVVMxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEo # MCYGA1UEAxMfTWljcm9zb2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAyNDCCAiIwDQYJ # KoZIhvcNAQEBBQADggIPADCCAgoCggIBANgBnB7jOMeqlRYHNa265v4IY9fH8TKh # emHfPINe1gpLaV3dhg324WwH06LcHbpnsBukCDNitryo0dtS/EW6I/yEL/bLSY8h # KpbfQuWusBPr9qazYcDxCW/qnjb5JsI1s8bNOg3bVATvQVL4tcf03aTycsz8QeCd # M0l/yHRObJ9QqazM1r6VPEOJ7LL+uEEb73w6QCuhs89a1uv1zerOYMnsneRRwCbp # yW11IcggU0cRKDDq1pjVJzIbIF6+oiXXbReOsgeI8zu1FyQfK0fVkaya8SmVHQ/t # Of23mZ4W9k0Ri22QW9p3UgSC5OUDktKxxcCmGL6tXLfOGSWHIIV4YrTJTT6PNty5 # REojHJuZHArkF9VnHTERWoTjAzfI3kP+5b4alUdhgAZ7ttOu1bVnXfHaqPYl2rPs # 20ji03LOVWsh/radgE17es5hL+t6lV0eVHrVhsssROWJuz2MXMCt7iw7lFPG9LXK # Gjsmonn2gotGdHIuEg5JnJMJVmixd5LRlkmgYRZKzhxSCwyoGIq0PhaA7Y+VPct5 # pCHkijcIIDm0nlkK+0KyepolcqGm0T/GYQRMhHJlGOOmVQop36wUVUYklUy++vDW # eEgEo4s7hxN6mIbf2MSIQ/iIfMZgJxC69oukMUXCrOC3SkE/xIkgpfl22MM1itkZ # 35nNXkMolU1lAgMBAAGjggFOMIIBSjAOBgNVHQ8BAf8EBAMCAYYwEAYJKwYBBAGC # NxUBBAMCAQAwHQYDVR0OBBYEFH9ZP1Qh2q1P7wXl5qPXLQaUEggxMBkGCSsGAQQB # gjcUAgQMHgoAUwB1AGIAQwBBMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU # ci06AjGQQ7kUBU7h6qfHMdEjiTQwWgYDVR0fBFMwUTBPoE2gS4ZJaHR0cDovL2Ny # bC5taWNyb3NvZnQuY29tL3BraS9jcmwvcHJvZHVjdHMvTWljUm9vQ2VyQXV0MjAx # MV8yMDExXzAzXzIyLmNybDBeBggrBgEFBQcBAQRSMFAwTgYIKwYBBQUHMAKGQmh0 # dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2kvY2VydHMvTWljUm9vQ2VyQXV0MjAx # MV8yMDExXzAzXzIyLmNydDANBgkqhkiG9w0BAQwFAAOCAgEAFJQfOChP7onn6fLI # MKrSlN1WYKwDFgAddymOUO3FrM8d7B/W/iQ6DxXsDn7D5W4wMwYeLystcEqfkjz4 # NURRgazyMu5yRzQh4LqjA4tStTcJh1opExo7nn5PuPBYnbu0+THSuVHTe0VTTPVh # ily/piFrDo3axQ9P4C+Ol5yet+2gTfekICS5xS+cYfSIvgn0JksVBVMYVI5QFu/q # hnLhsEFEUzG8fvv0hjgkO+lkpV9ty6GkN4vdnd7ya6Q6aR9y34aiM1qmxaxBi6OU # nyNl6fkuun/diTFnYDLTppOkr/mg5WSfCiDVMNCxtj4wPKC5OmHm1DQIt/MNokbb # H3UGsFP1QbzsLocuSqLCvH09Io3fDPTmscR9Y75G4qX7RTX8AdBPo0I6OEojf39z # uFZt0qOHm65YWQE69cZM2ueE1MB05dNNgHK9gTE7zKvK/fg8B2qjW88MT/WF5V5u # vZGtqa9FSL2RazArA+rDPuf6JGYz4HpgMZHB4S6szWSKYBv0VisCzfxgeU+dquXW # 9bd0auYlOB58DPcOYKdc3Se94g+xL4pcEhbB54JOgAkwYTu/9dLeH2pDqeJZAABV # DWRQCaXfO5LgyKwKCLYXpigrZYCjUSBcr+Ve8PFWMhVTQl0v4q8J/AUmQN5W4n10 # 1cY2L4A7GTQG1h32HHAvfQESWP0xghnlMIIZ4QIBATBuMFcxCzAJBgNVBAYTAlVT # MR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jv # c29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMjQCEzMAAAIdTRnITtcPV0gAAAAAAh0w # DQYJYIZIAWUDBAIBBQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYK # KwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIGtFhmXz # CiXY6wcqvaa1O7HoA6t5VHqZowb3hY2nowHDMEIGCisGAQQBgjcCAQwxNDAyoBSA # EgBNAGkAYwByAG8AcwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20w # DQYJKoZIhvcNAQEBBQAEggEASuBSjyZv8Y8Ke8TCunBNdNW7+vgeD5JfhWZbFMkB # zJLO/CuG9oeemRlGqNqAMr5WNof1HpYIwV8NEXEtYCvpaWeXUAbcbgbjYUkhMFvi # z64PMBrcC75k30lmO3vdLrJecusmG/lLVH3aXt9jfKLjICGdSER3ZSASTNzS2bwX # xaw9scMSp4KFJfNUwF2RUT27mu9VbQ+Lf+9aWXNKrif6QIY+3uHEyLOoBstxQhgL # NNbj7hEqHTIVyKpPvigLGN8d5XCh8LKGEsXN1TeCUIax73aAJovaEAvzJix/3hsa # 7M8vDt156RgJ4n1I/ej0tgngajzlnrro0OP9mGuisRWFJ6GCF5cwgheTBgorBgEE # AYI3AwMBMYIXgzCCF38GCSqGSIb3DQEHAqCCF3AwghdsAgEDMQ8wDQYJYIZIAWUD # BAIBBQAwggFSBgsqhkiG9w0BCRABBKCCAUEEggE9MIIBOQIBAQYKKwYBBAGEWQoD # ATAxMA0GCWCGSAFlAwQCAQUABCCAqnnrkas7oKB96vtdP1PtJoaFZ5KD72NuKgFJ # ausUHAIGaedenBaKGBMyMDI2MDUwNTE2MTMwOS4zMTFaMASAAgH0oIHRpIHOMIHL # MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVk # bW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQLExxN # aWNyb3NvZnQgQW1lcmljYSBPcGVyYXRpb25zMScwJQYDVQQLEx5uU2hpZWxkIFRT # UyBFU046QTQwMC0wNUUwLUQ5NDcxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0 # YW1wIFNlcnZpY2WgghHtMIIHIDCCBQigAwIBAgITMwAAAijwpYfX88geQAABAAAC # KDANBgkqhkiG9w0BAQsFADB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGlu # Z3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBv # cmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDAe # Fw0yNjAyMTkxOTQwMDZaFw0yNzA1MTcxOTQwMDZaMIHLMQswCQYDVQQGEwJVUzET # MBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMV # TWljcm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1lcmlj # YSBPcGVyYXRpb25zMScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046QTQwMC0wNUUw # LUQ5NDcxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2UwggIi # MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCujvbk/sqcCSReZaJfCuf1NwRc # c7XknhE6wkLofkNj1mxEAg35qy2xcFjgjartVvA09W8QHcpyMqVSXOTxNHJsmk0q # P2CDLvUAulWg7aS5oBORpEX1oz3n0R2nPqeH0IHK1zJxjxaHW21AbuZ0Z+wM3WYN # zkBlcHmVe03ZG7rlk28h72r5P5ME8FGpFmYW5Hl7psKbgLEfrYAitpttsb+sZsBU # I+hMKl4uLJYotKyZv1ewOIinBfRU8QosivjofaBezUf9NdV+iGrWh321WnSsK3A/ # Jl6GLtbSWXcJWULgbxuqnobPK+YlB3174TMWTgX4YWjG7o0Otz/pjHNCKBbB788d # ynhLdGY6B08E9+4SGrRpsty4iJHOydHCA5M4i5yYRwsdut+gmvxIpT8yNXJcjJCg # 0vO8mv/nFY9Wytv2qmCtCFFivGUWqU20/sUeRooQZGiQOJQn095Cj3isIsvRP8KU # 7hN/EDI8HVsb/NPzMFLvRznrRnj0TOnDiOTUcnYwmk+XfoS1owskcCCCwHnbC00D # 58z83y7K5ZJB745hcn4CE2nR3e6RGsr42y5qtt6Mdz/s7MTnDS2UmVHWX1X/HZe3 # UlX8gj/t63L50xIPqkRCBEdM1ADNUaSfo9OQiKb/bj1diZCGTfEDUBBLop1mhkwI # F82faplV2busZ+U4kQIDAQABo4IBSTCCAUUwHQYDVR0OBBYEFKrJpYz48tzouvVk # BVthASFpQ93DMB8GA1UdIwQYMBaAFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMF8GA1Ud # HwRYMFYwVKBSoFCGTmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY3Js # L01pY3Jvc29mdCUyMFRpbWUtU3RhbXAlMjBQQ0ElMjAyMDEwKDEpLmNybDBsBggr # BgEFBQcBAQRgMF4wXAYIKwYBBQUHMAKGUGh0dHA6Ly93d3cubWljcm9zb2Z0LmNv # bS9wa2lvcHMvY2VydHMvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIw # MTAoMSkuY3J0MAwGA1UdEwEB/wQCMAAwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgw # DgYDVR0PAQH/BAQDAgeAMA0GCSqGSIb3DQEBCwUAA4ICAQCQ6NfLmrRahgVtgWg3 # 83GaS07fHyod6bhcUONt2tet+6BaNuH0r7ABkVHheOpxBdrUrOEYVEaIii9dK3cu # ZLNmp1iUAx/VbmOZYl7xz+tNrjCWqrg1jQmq0oRB8iE4QJpwNhGP67oY5huYIU0D # 4lhDoahqfgKJn/0Bk+9UKDPw5XlUYmreFmJlj9YQzcPPep8MxBXxh/Y5I7vQeRaW # 5SjtiLQOLRk3ggvraDs5Sf49MJV6/BwxXC2rvUfEFX6SUDooqKIE9NgVIRq0RZu7 # Ot0i0Is+HvPP0hB6KwOxMg1SWKOfTtFpWpdo8MJvgKCHkPpXEzgprP+pyIHuO7gV # RlSTsbYBFLh2yId/itM4uYL0R+2SSBBTpSSRthrGuEmElI5BCHMxzMg/oqHSPwZA # IAkM2C4xxi0St7qMuA+m+ZzFYkfoF41QoSJn+HjqhqWYQ0m/SO9/KnJRJJUwMd5T # iMnjZ+E/DJiUry5udiWyQpvfj2hQFI0djhahoAXDazeEciLF2uEnTur9UfjcwOun # /oMY+ULftnOi2jKLMrreV097akzz/JxpnDgYJU/tgU7fQflg7IqiL9+0276+joQH # o21mVeY5YD8Kh/kUaY6Jm/OTM88G7evTz/qnRumxovTjMStvpbAHNRhmSTdIPTV3 # 2CyuxDKS/V5a5iwA+f9ViBo+wjCCB3EwggVZoAMCAQICEzMAAAAVxedrngKbSZkA # AAAAABUwDQYJKoZIhvcNAQELBQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpX # YXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQg # Q29ycG9yYXRpb24xMjAwBgNVBAMTKU1pY3Jvc29mdCBSb290IENlcnRpZmljYXRl # IEF1dGhvcml0eSAyMDEwMB4XDTIxMDkzMDE4MjIyNVoXDTMwMDkzMDE4MzIyNVow # fDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1Jl # ZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMd # TWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwggIiMA0GCSqGSIb3DQEBAQUA # A4ICDwAwggIKAoICAQDk4aZM57RyIQt5osvXJHm9DtWC0/3unAcH0qlsTnXIyjVX # 9gF/bErg4r25PhdgM/9cT8dm95VTcVrifkpa/rg2Z4VGIwy1jRPPdzLAEBjoYH1q # UoNEt6aORmsHFPPFdvWGUNzBRMhxXFExN6AKOG6N7dcP2CZTfDlhAnrEqv1yaa8d # q6z2Nr41JmTamDu6GnszrYBbfowQHJ1S/rboYiXcag/PXfT+jlPP1uyFVk3v3byN # pOORj7I5LFGc6XBpDco2LXCOMcg1KL3jtIckw+DJj361VI/c+gVVmG1oO5pGve2k # rnopN6zL64NF50ZuyjLVwIYwXE8s4mKyzbnijYjklqwBSru+cakXW2dg3viSkR4d # Pf0gz3N9QZpGdc3EXzTdEonW/aUgfX782Z5F37ZyL9t9X4C626p+Nuw2TPYrbqgS # Uei/BQOj0XOmTTd0lBw0gg/wEPK3Rxjtp+iZfD9M269ewvPV2HM9Q07BMzlMjgK8 # QmguEOqEUUbi0b1qGFphAXPKZ6Je1yh2AuIzGHLXpyDwwvoSCtdjbwzJNmSLW6Cm # gyFdXzB0kZSU2LlQ+QuJYfM2BjUYhEfb3BvR/bLUHMVr9lxSUV0S2yW6r1AFemzF # ER1y7435UsSFF5PAPBXbGjfHCBUYP3irRbb1Hode2o+eFnJpxq57t7c+auIurQID # AQABo4IB3TCCAdkwEgYJKwYBBAGCNxUBBAUCAwEAATAjBgkrBgEEAYI3FQIEFgQU # KqdS/mTEmr6CkTxGNSnPEP8vBO4wHQYDVR0OBBYEFJ+nFV0AXmJdg/Tl0mWnG1M1 # GelyMFwGA1UdIARVMFMwUQYMKwYBBAGCN0yDfQEBMEEwPwYIKwYBBQUHAgEWM2h0 # dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvRG9jcy9SZXBvc2l0b3J5Lmh0 # bTATBgNVHSUEDDAKBggrBgEFBQcDCDAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMA # QTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbL # j+iiXGJo0T2UkFvXzpoYxDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1p # Y3Jvc29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0w # Ni0yMy5jcmwwWgYIKwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3 # Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIz # LmNydDANBgkqhkiG9w0BAQsFAAOCAgEAnVV9/Cqt4SwfZwExJFvhnnJL/Klv6lwU # tj5OR2R4sQaTlz0xM7U518JxNj/aZGx80HU5bbsPMeTCj/ts0aGUGCLu6WZnOlNN # 3Zi6th542DYunKmCVgADsAW+iehp4LoJ7nvfam++Kctu2D9IdQHZGN5tggz1bSNU # 5HhTdSRXud2f8449xvNo32X2pFaq95W2KFUn0CS9QKC/GbYSEhFdPSfgQJY4rPf5 # KYnDvBewVIVCs/wMnosZiefwC2qBwoEZQhlSdYo2wh3DYXMuLGt7bj8sCXgU6ZGy # qVvfSaN0DLzskYDSPeZKPmY7T7uG+jIa2Zb0j/aRAfbOxnT99kxybxCrdTDFNLB6 # 2FD+CljdQDzHVG2dY3RILLFORy3BFARxv2T5JL5zbcqOCb2zAVdJVGTZc9d/HltE # AY5aGZFrDZ+kKNxnGSgkujhLmm77IVRrakURR6nxt67I6IleT53S0Ex2tVdUCbFp # AUR+fKFhbHP+CrvsQWY9af3LwUFJfn6Tvsv4O+S3Fb+0zj6lMVGEvL8CwYKiexcd # FYmNcP7ntdAoGokLjzbaukz5m/8K6TT4JDVnK+ANuOaMmdbhIurwJ0I9JZTmdHRb # atGePu1+oDEzfbzL6Xu/OHBE0ZDxyKs6ijoIYn/ZcGNTTY3ugm2lBRDBcQZqELQd # VTNYs6FwZvKhggNQMIICOAIBATCB+aGB0aSBzjCByzELMAkGA1UEBhMCVVMxEzAR # BgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1p # Y3Jvc29mdCBDb3Jwb3JhdGlvbjElMCMGA1UECxMcTWljcm9zb2Z0IEFtZXJpY2Eg # T3BlcmF0aW9uczEnMCUGA1UECxMeblNoaWVsZCBUU1MgRVNOOkE0MDAtMDVFMC1E # OTQ3MSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNloiMKAQEw # BwYFKw4DAhoDFQB1rbmFkzS7qAK1Oav08AUnhbNIUqCBgzCBgKR+MHwxCzAJBgNV # BAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4w # HAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29m # dCBUaW1lLVN0YW1wIFBDQSAyMDEwMA0GCSqGSIb3DQEBCwUAAgUA7aRP9jAiGA8y # MDI2MDUwNTExMTYwNloYDzIwMjYwNTA2MTExNjA2WjB3MD0GCisGAQQBhFkKBAEx # LzAtMAoCBQDtpE/2AgEAMAoCAQACAgHLAgH/MAcCAQACAhULMAoCBQDtpaF2AgEA # MDYGCisGAQQBhFkKBAIxKDAmMAwGCisGAQQBhFkKAwKgCjAIAgEAAgMHoSChCjAI # AgEAAgMBhqAwDQYJKoZIhvcNAQELBQADggEBAB+7nj/krNRMEyGqh4EKyDu1TUuE # 1IZwW5TLG4NiK/U4shiR0WZbDWW+VEjRLSBwnqXmnVQ7WGQai5n4hKqn8JblCQo9 # LijYfvy7OCEtCGCWIdgwjs0gPo39Eo940FjNyVE1+8KjvuEbtdAz4rvzHqWCbywk # CqNYLr2F1xCED7620V2mA/oIJl3TSnn331VuEioCOMFX9ayCzDMFa34Im8Zd0kuo # WXuE7o02meR4DUQKrN2g+q+IrhwPQsD4sD6D8VQp++rmK9U2hVztIP4Wk0GnnuMd # UhJImF14pzXHmRWpkU2OqSmCXKeI2UGW5KPzTOwYoyB2vqahE8Sr0rWobk0xggQN # MIIECQIBATCBkzB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQ # MA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9u # MSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAITMwAAAijw # pYfX88geQAABAAACKDANBglghkgBZQMEAgEFAKCCAUowGgYJKoZIhvcNAQkDMQ0G # CyqGSIb3DQEJEAEEMC8GCSqGSIb3DQEJBDEiBCCV/A9y9GiPllzyCVvk8TVSf2FB # DVv5eXTyU7X/QBpgETCB+gYLKoZIhvcNAQkQAi8xgeowgecwgeQwgb0EIFWxikZR # YGNf4oEVZK1eT45H+3GQ3/qxV75VwuBt+iLXMIGYMIGApH4wfDELMAkGA1UEBhMC # VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV # BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRp # bWUtU3RhbXAgUENBIDIwMTACEzMAAAIo8KWH1/PIHkAAAQAAAigwIgQgYhncZN0h # T0WC3hhHRY1/TINR0P0ezFLwRE+6rjJehpgwDQYJKoZIhvcNAQELBQAEggIAeiiB # 2WshkW5+L/SHJba3AIoAD6gK70rMlhNel0Na+ETtNe+Jn13OSaV87LRPDh9If7VJ # 4cuiC63Et1WaC6Ao3V5Wim20PslhQ3wSKCCim+Sjw63Y7o/JH6Uu3nXDmlCbOCBK # PTWv3zsXupLU5DGArFB2zm9up/S0QNQZHRy1equsUidbwnhXWH5siX51yaajLktJ # vS1F4gZ8+NEHN4QiQ2Ow/siuUkXlwPhuCTyZNaVgY25fklkMuYQskIAsZ4McjHQE # 1mFtvmXYmcTna4MWKpHv1sAdvEEjY7Ct1EneRBuDfImstYwSHFFrZfAlPepkmv7Q # KrJPSdWIKYbsAeraRvaMRx9Lc6qs+67um9xFza62teQNneQc7ygROd7gBiDMzDv7 # xJh/uciTYLEKPjKyVK16DebxzN09MdoVQzAzcFdmrDi6iSotoFN8xeuIdVKcqGOy # S6/nhkPbIfd6rizxOtdRcyLx0TppQfTcAor+PKuNUAbF7mWBYuoaKwmSWLU1t0ci # zNkVlTkco3leq4WLhZCGKea7vcaYDj5plE4lZL+0Noi3e5fi+W49KRW9I6zP2C6+ # XvTInCLB+wYfxpHwxVgKNwnIK0fa6d3n/0AABhX1vK5zlOTdBTe8AoOUr75d9Wkg # wvXt33os9JCul3KP9F1DsWF+woXF3A5vz6HTceg= # SIG # End signature block |