AzStackHciAddNode/AzStackHci.AddNode.Helpers.psm1
Import-LocalizedData -BindingVariable lanTxt -FileName AzStackHci.AddNode.Strings.psd1 function Test-ADCredential { [CmdletBinding()] param ( [pscredential] $ActiveDirectoryCredential ) try { $severity = 'CRITICAL' $adModuleExists = [bool](Get-Module ActiveDirectory -ListAvailable -ErrorAction SilentlyContinue) if ($adModuleExists) { [bool]$adTest = QueryAD -ActiveDirectoryCredential $ActiveDirectoryCredential $detail = $lanTxt.TestAD -f $AdTest, $true if ($adTest) { $status = 'SUCCESS' Log-Info $detail } else { $status = 'FAILURE' Log-Info $detail -Type $severity } } else { $status = 'FAILURE' $detail = $lanTxt.NoADModule Log-info $detail -Type $severity } $params = @{ Name = 'AzStackHci_AddNode_AD_Credential_Check' Title = 'Test AD Credential' DisplayName = 'Test AD Credential' Severity = $severity Description = 'Checking AD Credential is valid' Tags = @{} Remediation = 'https://learn.microsoft.com/en-us/azure-stack/hci/manage/add-server' TargetResourceID = 'AD Credential' TargetResourceName = 'AD Credential' TargetResourceType = 'Credential' Timestamp = [datetime]::UtcNow Status = $Status AdditionalData = @{ Source = $ENV:COMPUTERNAME Resource = 'AD Credential' Detail = $detail Status = $status TimeStamp = [datetime]::UtcNow } HealthCheckSource = $ENV:EnvChkrId } New-AzStackHciResultObject @params } catch { throw ("Error testing AD credential: {0}" -f $_.Exception) } } function QueryAD { [CmdletBinding()] param ( [pscredential] $ActiveDirectoryCredential ) try { $domain = $ActiveDirectoryCredential.GetNetworkCredential().domain if ([string]::IsNullOrEmpty($domain)) { if (($ActiveDirectoryCredential.UserName -split '@').count -le 1) { throw "Credential should contain domain" } else { $domain = ($ActiveDirectoryCredential.UserName -split '@')[1] } } return [bool](Get-ADDomain -Credential $ActiveDirectoryCredential -Server $domain -ErrorAction SilentlyContinue) } catch { throw ("Error retrieving AD Object: {0}" -f $_.Exception) } } function Test-LocalCredential { [CmdletBinding()] param ( [pscredential] $LocalCredential, [string[]] $Ipv4OrHostName ) try { $severity = 'CRITICAL' $PsSession = Microsoft.PowerShell.Core\New-PsSession -ComputerName $Ipv4OrHostName -Credential $LocalCredential Copy-RemoteItem -PsSession $PsSession -SourcePath (Join-Path (Split-Path -Parent $PSScriptRoot) "AzStackHci.EnvironmentChecker.PortableUtilities.psm1") -CmdletName "Test-Elevation" $IsAdmin = { if (Get-Command -Name Test-Elevation -ErrorAction SilentlyContinue) { Test-Elevation } else { throw "Cannot find Test-Elevation function" } } $results = @() $results += [bool](Microsoft.PowerShell.Core\Invoke-Command -Session $PsSession -ScriptBlock $IsAdmin -ErrorAction SilentlyContinue) $instanceResults = @() $instanceResults += foreach ($result in $results) { $detail = $lanTxt.TestLocalCredential -f $LocalCredential.UserName, $Ipv4OrHostName, $result, $true if ($result) { $status = 'SUCCESS' Log-Info $detail } else { $status = 'FAILURE' Log-Info $detail -Type $severity } $params = @{ Name = 'AzStackHci_AddNode_Local_Credential_Check' Title = 'Test Local Credential' DisplayName = 'Test Local Credential' Severity = $severity Description = 'Checking Local Credential is valid' Tags = @{} Remediation = 'https://learn.microsoft.com/en-us/azure-stack/hci/manage/add-server' TargetResourceID = 'Local Credential' TargetResourceName = 'Local Credential' TargetResourceType = 'Credential' Timestamp = [datetime]::UtcNow Status = $Status AdditionalData = @{ Source = $Ipv4OrHostName Resource = 'Local Credential' Detail = $detail Status = $status TimeStamp = [datetime]::UtcNow } HealthCheckSource = $ENV:EnvChkrId } New-AzStackHciResultObject @params } } catch { throw ("Error testing local credential {0} on {1}. Error: {2}" -f $LocalCredential.UserName, $Ipv4OrHostName, $_.Exception) } finally { Remove-UtilityModule -PsSession $PsSession $PsSession | Remove-PSSession -ErrorAction SilentlyContinue } } function Test-ClusterNodeName { [CmdletBinding()] param ( [string[]] $ComputerName ) try { $severity = 'CRITICAL' [string[]]$clusterNodes = Get-ClusterNodeNames [string[]]$nodeNamesInUse = $ComputerName | Where-Object { $_ -in $clusterNodes } $detail = $lanTxt.TestClusterNodeName -f ($computerName -join ','), [bool]$nodeNamesInUse, $false if ($nodeNamesInUse.Count -eq 0) { Log-Info $detail $status = 'SUCCESS' } else { $detail = $detail + "`r`n" + $lanTxt.RemoveClusterNodeName -f ($nodeNamesInUse -join ',') $status = 'FAILURE' Log-Info $detail -Type $severity } $clusterName = Get-LocalClusterName $params = @{ Name = 'AzStackHci_AddNode_ComputerName_Check' Title = 'Test ComputerName' DisplayName = 'Test ComputerName' Severity = $severity Description = 'Checking ComputerName(s) is not in use in existing cluster' Tags = @{} Remediation = 'https://learn.microsoft.com/en-us/azure-stack/hci/manage/add-server' TargetResourceID = $clusterName TargetResourceName = $clusterName TargetResourceType = 'Computer Name' Timestamp = [datetime]::UtcNow Status = $Status AdditionalData = @{ Source = $ENV:COMPUTERNAME Resource = $ComputerName -join ',' Detail = $detail Status = $status TimeStamp = [datetime]::UtcNow } HealthCheckSource = $ENV:EnvChkrId } New-AzStackHciResultObject @params } catch { throw ($lanTxt.NodeNameCheckError -f ($ComputerName -join ','), $_.Exception) } } function Get-ClusterNodeNames { <# .SYNOPSIS Get the cluster node names from the local cluster .OUTPUTS Returns the cluster node names. #> [CmdletBinding()] param () try { $ClusterNodeNames = Get-ClusterNode | Select-Object -ExpandProperty Name Log-Info "Local Cluster Name: $($ClusterNodeNames -join ', ')" return $ClusterNodeNames } catch { throw ("Error retrieving cluster node names: {0}" -f $_.Exception) } } function Get-LocalClusterName { <# .SYNOPSIS Get the cluster name from the local cluster .OUTPUTS Returns the cluster name. #> [CmdletBinding()] param () try { $ClusterName = Get-Cluster | Select-Object -ExpandProperty Name Log-Info "Local Cluster Name: $ClusterName" return $ClusterName } catch { throw ("Error retrieving cluster name: {0}" -f $_.Exception) } } function Test-ComputerName { [CmdletBinding()] param ( [string[]] $ComputerName, [pscredential] $LocalCredential, [string[]] $Ipv4OrHostName ) try { # if no IP is provided, we assume this is repair and return $severity = 'CRITICAL' if ($Ipv4OrHostName.Count -eq 0 -or -not $Ipv4OrHostName) { return } # Build a hashtable of IPs and computer names [array]$nodeIpMap += GetCsNameFromIps -LocalCredential $LocalCredential -Ipv4OrHostName $Ipv4OrHostName # verify all computername names if ($null -in $nodeIpMap.ComputerName) { throw "Failed to resolve ComputerName from all IP(s): $($Ipv4OrHostName -join ', ')" } $dtl = "" [string[]]$NodeMapComputerNames = $nodeIpMap | Select-Object -ExpandProperty ComputerName $compareResult = Compare-Object -ReferenceObject $ComputerName -DifferenceObject $NodeMapComputerNames if (-not $compareResult) { $status = 'SUCCESS' $dtl = "All ComputerName(s) match the IP(s): {0}" -f (($nodeIpMap | ForEach-Object { "'$($_.Ipv4OrHostName)' -> '$($_.ComputerName)'" }) -join ', ') } else { $status = 'FAILURE' $dtl += "Inconsistency found between ComputerName(s) and IP(s):" foreach ($cr in $compareResult) { if ($cr.SideIndicator -eq '=>') { $foundOn = $nodeIpMap | Where-Object { $_.ComputerName -eq $cr.InputObject } | Select-Object -ExpandProperty Ipv4OrHostName $dtl += "`r`n - ComputerName '$($cr.InputObject)' was found on $($foundOn -join ',')" } else { $dtl += "`r`n - ComputerName '$($cr.InputObject)' was not found on the provided IPs.`r`nPlease check the IP(s) and ComputerName(s) provided are correct." } } Log-Info -Message $dtl -Type $severity } $params = @{ Name = 'AzStackHci_AddNode_ComputerNameIPs_Match' Title = 'Test ComputerName IP' DisplayName = 'Test ComputerName IP' Severity = $severity Description = 'Checking Computer(s) have correct IP(s) configured' Tags = @{} Remediation = 'https://learn.microsoft.com/en-us/azure-stack/hci/manage/add-server' TargetResourceID = "$($Ipv4OrHostName -join ',')" TargetResourceName = "$($ComputerName -join ',')" TargetResourceType = 'Ip Configuration' Timestamp = [datetime]::UtcNow Status = $Status AdditionalData = @{ Source = ($ComputerName -join ',') Resource = ($Ipv4OrHostName -join ',') Detail = $dtl Status = $status TimeStamp = [datetime]::UtcNow } HealthCheckSource = $ENV:EnvChkrId } New-AzStackHciResultObject @params } catch { throw ("Error testing IP {0} is set locally on {1}. Error: {2}" -f ($Ipv4OrHostName -join ','), ($ComputerName -join ','), $_) } } function GetCsNameFromIps { <# .SYNOPSIS Get the computer name from the IP address .PARAMETER Ipv4OrHostName The IP address or host name to resolve .OUTPUTS Returns the computer name associated with the provided IP address or host name. #> [CmdletBinding()] param ( [pscredential] $LocalCredential, [string[]] $Ipv4OrHostName ) $sb = { (Get-WmiObject -ClassName Win32_OperatingSystem | Select-Object -expand CSName) } $ipNodeMap = @() foreach ($ip in $Ipv4OrHostName) { $csName = (Microsoft.PowerShell.Core\Invoke-Command -ComputerName $ip -Credential $LocalCredential -ScriptBlock $sb -ErrorAction SilentlyContinue) $ipNodeMap += New-Object -TypeName PSObject -Property @{ ComputerName = $csName Ipv4OrHostName = $ip } } return $ipNodeMap } function Test-Quorum { <# .SYNOPSIS Check if single node cluster has correct quorum configuration #> [CmdletBinding()] param() try { # Get the current number of nodes in the cluster $severity = 'CRITICAL' $CurrentNumberOfNodes = (Get-ClusterNode).Count # if the cluster has only one node, check if the quorum is set to NodeandFileShareMajority or CloudWitness if ($CurrentNumberOfNodes -eq 1) { $Quorum = Get-ClusterQuorum $detail = $lanTxt.QuorumCheck -f $Quorum.Cluster.Name, $CurrentNumberOfNodes, $Quorum.QuorumResource.ResourceType.Name,'File Share Witness or Cloud Witness' if ($Quorum.QuorumResource.ResourceType.Name -eq 'File Share Witness' -or $Quorum.QuorumResource.ResourceType.Name -eq 'Cloud Witness') { Log-Info $detail $Status = 'SUCCESS' } else { Log-Info $detail -Type $severity $Status = 'FAILURE' } $params = @{ Name = 'AzStackHci_AddNode_Quorum_Resource_Check' Title = 'Test Quorum Resource' DisplayName = 'Test Quorum Resource' Severity = $severity Description = 'Checking Quorum Resource is set to File Share Witness or CloudWitness' Tags = @{} Remediation = 'https://learn.microsoft.com/en-us/azure-stack/hci/manage/add-server' TargetResourceID = "$($Quorum.Cluster.Name)/$($Quorum.QuorumResource.Name)" TargetResourceName = $Quorum.QuorumResource.Name TargetResourceType = 'Quorum Resource' Timestamp = [datetime]::UtcNow Status = $Status AdditionalData = @{ Source = $Quorum.Cluster.Name Resource = $Quorum.QuorumResource.Name Detail = $detail Status = $status TimeStamp = [datetime]::UtcNow } HealthCheckSource = $ENV:EnvChkrId } New-AzStackHciResultObject @params } } catch { throw ("Error cluster quorum on {0}. Error: {1}" -f (Get-Cluster).Name, $_.Exception) } } function Test-DriveLetterConsistency { <# .SYNOPSIS Check if the DriveLetters are consistent across the cluster nodes. #> [CmdletBinding()] param ( [pscredential] $LocalCredential, [string[]] $Ipv4OrHostName, [string[]] $DriveLetter = @('C','D') ) try { $severity = 'CRITICAL' # get DriveLetters from the remote node $addNodeDriveLetters = @() $addNodeDriveLetters = Get-RemoteDriveLetter -LocalCredential $LocalCredential -Ipv4OrHostName $Ipv4OrHostName -DriveLetter $DriveLetter $localNodeDriveLetters = Get-LocalDriveLetter -DriveLetter $DriveLetter $instanceResults = @() foreach ($addNode in $addNodeDriveLetters) { if ($null -eq $addNode.DriveLetters) { $status = 'FAILURE' $detail = $lanTxt.DriveLetterConsistencyCheckError -f $addNode.ComputerName, ($addNode.DriveLetters -join ','), $DriveLetter -join ',' Log-Info $detail -Type $severity } else { if (Compare-Object -ReferenceObject $addNode.DriveLetters -DifferenceObject $localNodeDriveLetters) { $status = 'FAILURE' $detail = $lanTxt.DriveLetterInconsistent -f $addNode.computername, ($addNode.DriveLetters -join ','), $env:computername, ($localNodeDriveLetters -join ','), ($DriveLetter -join ',') Log-Info $detail -Type $severity } else { $status = 'SUCCESS' $detail = $lanTxt.DriveLetterConsistent -f $addNode.computername, ($addNode.DriveLetters -join ','), $env:computername, ($localNodeDriveLetters -join ',') Log-Info $detail } } $params = @{ Name = 'AzStackHci_AddNode_DriveLetter_Consistency_Check' Title = 'Test DriveLetter Consistency' DisplayName = 'Test DriveLetter Consistency' Severity = $severity Description = 'Checking DriveLetters are consistent across the cluster nodes' Tags = @{} Remediation = 'https://learn.microsoft.com/en-us/azure-stack/hci/manage/add-server' TargetResourceID = $addNode.ComputerName TargetResourceName = $addNode.ComputerName TargetResourceType = 'DriveLetter' Timestamp = [datetime]::UtcNow Status = $Status AdditionalData = @{ Source = $addNode.ComputerName Resource = $addNode.DriveLetters -join ',' Detail = $detail Status = $status TimeStamp = [datetime]::UtcNow } HealthCheckSource = $ENV:EnvChkrId } $instanceResults += New-AzStackHciResultObject @params } return $instanceResults } catch { throw ("Error checking DriveLetter consistency to node(s): $($Ipv4OrHostName -join ','). $($_.exception.StackTrace)") } } function Get-RemoteDriveLetter { [CmdletBinding()] param ( [pscredential] $LocalCredential, [string[]] $Ipv4OrHostName, [string[]] $DriveLetter = @('C', 'D') ) try { $addNodeDriveLetters = @() $addNodeDriveLetters = Microsoft.PowerShell.Core\Invoke-Command -ComputerName $Ipv4OrHostName -Credential $LocalCredential -ScriptBlock { $remoteDriveLetters = Get-Volume | Where-Object { $_.DriveLetter -in $USING:DriveLetter } | Select-Object -ExpandProperty DriveLetter | Sort-Object return (New-Object -TypeName PSObject -Property @{ ComputerName = $env:COMPUTERNAME DriveLetters = $remoteDriveLetters }) } return $addNodeDriveLetters } catch { throw ("Error retrieving DriveLetters from remote node {0}. Error: {1}" -f ($Ipv4OrHostName -join ','), $_.Exception) } } function Get-LocalDriveLetter { [CmdletBinding()] param ( [string[]] $DriveLetter = @('C', 'D') ) try { $driveLetters = @() $driveLetters = Get-Volume | Where-Object { $_.DriveLetter -in $DriveLetter } | Select-Object -ExpandProperty DriveLetter | Sort-Object return $driveLetters } catch { throw ("Error retrieving local DriveLetters. Error: {0}" -f $_.Exception) } } Export-ModuleMember -Function Test-* # SIG # Begin signature block # MIIoLQYJKoZIhvcNAQcCoIIoHjCCKBoCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCApwxw3kJgGw4ds # iqKuc99NSZXYgnZRIQJW+m0vwhj7SKCCDXYwggX0MIID3KADAgECAhMzAAAEhV6Z # 7A5ZL83XAAAAAASFMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p # bmcgUENBIDIwMTEwHhcNMjUwNjE5MTgyMTM3WhcNMjYwNjE3MTgyMTM3WjB0MQsw # CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u # ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB # AQDASkh1cpvuUqfbqxele7LCSHEamVNBfFE4uY1FkGsAdUF/vnjpE1dnAD9vMOqy # 5ZO49ILhP4jiP/P2Pn9ao+5TDtKmcQ+pZdzbG7t43yRXJC3nXvTGQroodPi9USQi # 9rI+0gwuXRKBII7L+k3kMkKLmFrsWUjzgXVCLYa6ZH7BCALAcJWZTwWPoiT4HpqQ # hJcYLB7pfetAVCeBEVZD8itKQ6QA5/LQR+9X6dlSj4Vxta4JnpxvgSrkjXCz+tlJ # 67ABZ551lw23RWU1uyfgCfEFhBfiyPR2WSjskPl9ap6qrf8fNQ1sGYun2p4JdXxe # UAKf1hVa/3TQXjvPTiRXCnJPAgMBAAGjggFzMIIBbzAfBgNVHSUEGDAWBgorBgEE # AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUuCZyGiCuLYE0aU7j5TFqY05kko0w # RQYDVR0RBD4wPKQ6MDgxHjAcBgNVBAsTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEW # MBQGA1UEBRMNMjMwMDEyKzUwNTM1OTAfBgNVHSMEGDAWgBRIbmTlUAXTgqoXNzci # tW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8vd3d3Lm1pY3Jvc29mdC5j # b20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3JsMGEG # CCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDovL3d3dy5taWNyb3NvZnQu # Y29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3J0 # MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIBACjmqAp2Ci4sTHZci+qk # tEAKsFk5HNVGKyWR2rFGXsd7cggZ04H5U4SV0fAL6fOE9dLvt4I7HBHLhpGdE5Uj # Ly4NxLTG2bDAkeAVmxmd2uKWVGKym1aarDxXfv3GCN4mRX+Pn4c+py3S/6Kkt5eS # DAIIsrzKw3Kh2SW1hCwXX/k1v4b+NH1Fjl+i/xPJspXCFuZB4aC5FLT5fgbRKqns # WeAdn8DsrYQhT3QXLt6Nv3/dMzv7G/Cdpbdcoul8FYl+t3dmXM+SIClC3l2ae0wO # lNrQ42yQEycuPU5OoqLT85jsZ7+4CaScfFINlO7l7Y7r/xauqHbSPQ1r3oIC+e71 # 5s2G3ClZa3y99aYx2lnXYe1srcrIx8NAXTViiypXVn9ZGmEkfNcfDiqGQwkml5z9 # nm3pWiBZ69adaBBbAFEjyJG4y0a76bel/4sDCVvaZzLM3TFbxVO9BQrjZRtbJZbk # C3XArpLqZSfx53SuYdddxPX8pvcqFuEu8wcUeD05t9xNbJ4TtdAECJlEi0vvBxlm # M5tzFXy2qZeqPMXHSQYqPgZ9jvScZ6NwznFD0+33kbzyhOSz/WuGbAu4cHZG8gKn # lQVT4uA2Diex9DMs2WHiokNknYlLoUeWXW1QrJLpqO82TLyKTbBM/oZHAdIc0kzo # STro9b3+vjn2809D0+SOOCVZMIIHejCCBWKgAwIBAgIKYQ6Q0gAAAAAAAzANBgkq # hkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x # EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv # bjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5 # IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYwNzA4MjEwOTA5WjB+MQswCQYDVQQG # EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG # A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYDVQQDEx9NaWNyb3NvZnQg # Q29kZSBTaWduaW5nIFBDQSAyMDExMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC # CgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGfQhsqa+laUKq4BjgaBEm6f8MMHt03 # a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRgJGyvnkmc6Whe0t+bU7IKLMOv2akr # rnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NEt13YxC4Ddato88tt8zpcoRb0Rrrg # OGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnnDb6gE3e+lD3v++MrWhAfTVYoonpy # 4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+EGvKhL1nkkDstrjNYxbc+/jLTswM9 # sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+tGSOEy/S6A4aN91/w0FK/jJSHvMAh # dCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxhH2rhKEmdX4jiJV3TIUs+UsS1Vz8k # A/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AVs70b1FVL5zmhD+kjSbwYuER8ReTB # w3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f7Fufr/zdsGbiwZeBe+3W7UvnSSmn # Eyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3DKI8sj0A3T8HhhUSJxAlMxdSlQy90 # lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9JawvEagbJjS4NaIjAsCAwEAAaOCAe0w # ggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBRIbmTlUAXTgqoXNzcitW2o # ynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYwDwYD # VR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBDuRQFTuHqp8cx0SOJNDBa # BgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2Ny # bC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3JsMF4GCCsG # AQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3dy5taWNyb3NvZnQuY29t # L3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3J0MIGfBgNV # HSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/BggrBgEFBQcCARYzaHR0cDovL3d3 # dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2NzL3ByaW1hcnljcHMuaHRtMEAGCCsG # AQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAAbwBsAGkAYwB5AF8AcwB0AGEAdABl # AG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQBn8oalmOBUeRou09h0ZyKb # C5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+vj/oCso7v0epo/Np22O/IjWll11l # hJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4HLimb5j0bpdS1HXeUOeLpZMlEPXh6 # I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6aC6VoCo/KmtYSWMfCWluWpiW5IP0 # wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiXmE0OPQvyCInWH8MyGOLwxS3OW560 # STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn+N4sOiBpmLJZiWhub6e3dMNABQam # ASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAqZaPDXVJihsMdYzaXht/a8/jyFqGa # J+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5hYbXw3MYbBL7fQccOKO7eZS/sl/ah # XJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/RXceNcbSoqKfenoi+kiVH6v7RyOA # 9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXmr/r8i+sLgOppO6/8MO0ETI7f33Vt # Y5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMykXcGhiJtXcVZOSEXAQsmbdlsKgEhr # /Xmfwb1tbWrJUnMTDXpQzTGCGg0wghoJAgEBMIGVMH4xCzAJBgNVBAYTAlVTMRMw # EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN # aWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNp # Z25pbmcgUENBIDIwMTECEzMAAASFXpnsDlkvzdcAAAAABIUwDQYJYIZIAWUDBAIB # BQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEO # MAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIPp10wfNwdQWnyTBMP1WOWec # vLbhMKFkGaoQR+M9tc27MEIGCisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8A # cwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEB # BQAEggEAs5T9TdKbEqUOXIkJd6YN0tp+bfbO+OwLtezoy4L+zZzsXodCZMtfS9gg # CQDdiZUouKbeCrAPRSf4+yfaQR3npI2pK1cglJkjpu5uzOa+AF8oZc6PCJ9GHbug # AduNbKOVjY+N7AxwFuoKZLgugy4fiwa7mx+/Cv7J/VSHg4ieSh4Dke5YLg1TNB7t # EIjDP0Sk+KtqG3170r2QV/JUgVP5YAzkPnw/3PxFyIFPiRJ1M+uaI+y8k+Yhkygm # 29yJ4Gw9cJSmdyIVNrpD7b3c8H9J0sOuSZlqJM7e3r4S8LsKXsQKX8CX62jLN9qi # 5x5Dpu2SeRz6jvxxPopwzvdQKd2WeaGCF5cwgheTBgorBgEEAYI3AwMBMYIXgzCC # F38GCSqGSIb3DQEHAqCCF3AwghdsAgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFSBgsq # hkiG9w0BCRABBKCCAUEEggE9MIIBOQIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFl # AwQCAQUABCC0tJ314N5JQ+YDXxBPmEkip43SGdbrBkCaYbhcdJe7LAIGaKOkTfBN # GBMyMDI1MDkwOTE5MjIwNC4yMjZaMASAAgH0oIHRpIHOMIHLMQswCQYDVQQGEwJV # UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE # ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1l # cmljYSBPcGVyYXRpb25zMScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046OTYwMC0w # NUUwLUQ5NDcxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2Wg # ghHtMIIHIDCCBQigAwIBAgITMwAAAgTY4A4HlzJYmAABAAACBDANBgkqhkiG9w0B # AQsFADB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE # BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYD # VQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDAeFw0yNTAxMzAxOTQy # NDdaFw0yNjA0MjIxOTQyNDdaMIHLMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2Fz # aGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENv # cnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1lcmljYSBPcGVyYXRpb25z # MScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046OTYwMC0wNUUwLUQ5NDcxJTAjBgNV # BAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2UwggIiMA0GCSqGSIb3DQEB # AQUAA4ICDwAwggIKAoICAQDw3Sbcee2d66vkWGTIXhfGqqgQGxQXTnq44XlUvNzF # St7ELtO4B939jwZFX7DrRt/4fpzGNkFdGpc7EL5S86qKYv360eXjW+fIv1lAqDD3 # 1d/p8Ai9/AZz8M95zo0rDpK2csz9WAyR9FtUDx52VOs9qP3/pgpHvgUvD8s6/3KN # ITzms8QC1tJ3TMw1cRn9CZgVIYzw2iD/ZvOW0sbF/DRdgM8UdtxjFIKTXTaI/bJh # sQge3TwayKQ2j85RafFFVCR5/ChapkrBQWGwNFaPzpmYN46mPiOvUxriISC9nQ/G # rDXUJWzLDmchrmr2baABJevvw31UYlTlLZY6zUmjkgaRfpozd+Glq9TY2E3Dglr6 # PtTEKgPu2hM6v8NiU5nTvxhDnxdmcf8UN7goeVlELXbOm7j8yw1xM9IyyQuUMWko # rBaN/5r9g4lvYkMohRXEYB0tMaOPt0FmZmQMLBFpNRVnXBTa4haXvn1adKrvTz8V # lfnHxkH6riA/h2AlqYWhv0YULsEcHnaDWgqA29ry+jH097MpJ/FHGHxk+d9kH2L5 # aJPpAYuNmMNPB7FDTPWAx7Apjr/J5MhUx0i07gV2brAZ9J9RHi+fMPbS+Qm4AonC # 5iOTj+dKCttVRs+jKKuO63CLwqlljvnUCmuSavOX54IXOtKcFZkfDdOZ7cE4DioP # 1QIDAQABo4IBSTCCAUUwHQYDVR0OBBYEFBp1dktAcGpW/Km6qm+vu4M1GaJfMB8G # A1UdIwQYMBaAFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMF8GA1UdHwRYMFYwVKBSoFCG # Tmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY3Jvc29mdCUy # MFRpbWUtU3RhbXAlMjBQQ0ElMjAyMDEwKDEpLmNybDBsBggrBgEFBQcBAQRgMF4w # XAYIKwYBBQUHMAKGUGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY2Vy # dHMvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIwMTAoMSkuY3J0MAwG # A1UdEwEB/wQCMAAwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgwDgYDVR0PAQH/BAQD # AgeAMA0GCSqGSIb3DQEBCwUAA4ICAQBecv6sRw2HTLMyUC1WJJ+FR+DgA9Jkv0lG # sIt4y69CmOj8R63oFbhSmcdpakxqNbr8v9dyTb4RDyNqtohiiXbtrXmQK5X7y/Q+ # +F0zMotTtTpTPvG3eltyV/LvO15mrLoNQ7W4VH58aLt030tORxs8VnAQQF5BmQQM # Oua+EQgH4f1F4uF6rl3EC17JBSJ0wjHSea/n0WYiHPR0qkz/NRAf8lSUUV0gbIMa # wGIjn7+RKyCr+8l1xdNkK/F0UYuX3hG0nE+9Wc0L4A/enluUN7Pa9vOV6Vi3BOJS # T0RY/ax7iZ45leM8kqCw7BFPcTIkWzxpjr2nCtirnkw7OBQ6FNgwIuAvYNTU7r60 # W421YFOL5pTsMZcNDOOsA01xv7ymCF6zknMGpRHuw0Rb2BAJC9quU7CXWbMbAJLd # Z6XINKariSmCX3/MLdzcW5XOycK0QhoRNRf4WqXRshEBaY2ymJvHO48oSSY/kpuY # vBS3ljAAuLN7Rp8jWS7t916paGeE7prmrP9FJsoy1LFKmFnW+vg43ANhByuAEXq9 # Cay5o7K2H5NFnR5wj/SLRKwK1iyUX926i1TEviEiAh/PVyJbAD4koipig28p/6HD # uiYOZ0wUkm/a5W8orIjoOdU3XsJ4i08CfNp5I73CsvB5QPYMcLpF9NO/1LvoQAw3 # UPdL55M5HTCCB3EwggVZoAMCAQICEzMAAAAVxedrngKbSZkAAAAAABUwDQYJKoZI # hvcNAQELBQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAw # DgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24x # MjAwBgNVBAMTKU1pY3Jvc29mdCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAy # MDEwMB4XDTIxMDkzMDE4MjIyNVoXDTMwMDkzMDE4MzIyNVowfDELMAkGA1UEBhMC # VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV # BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRp # bWUtU3RhbXAgUENBIDIwMTAwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC # AQDk4aZM57RyIQt5osvXJHm9DtWC0/3unAcH0qlsTnXIyjVX9gF/bErg4r25Phdg # M/9cT8dm95VTcVrifkpa/rg2Z4VGIwy1jRPPdzLAEBjoYH1qUoNEt6aORmsHFPPF # dvWGUNzBRMhxXFExN6AKOG6N7dcP2CZTfDlhAnrEqv1yaa8dq6z2Nr41JmTamDu6 # GnszrYBbfowQHJ1S/rboYiXcag/PXfT+jlPP1uyFVk3v3byNpOORj7I5LFGc6XBp # Dco2LXCOMcg1KL3jtIckw+DJj361VI/c+gVVmG1oO5pGve2krnopN6zL64NF50Zu # yjLVwIYwXE8s4mKyzbnijYjklqwBSru+cakXW2dg3viSkR4dPf0gz3N9QZpGdc3E # XzTdEonW/aUgfX782Z5F37ZyL9t9X4C626p+Nuw2TPYrbqgSUei/BQOj0XOmTTd0 # lBw0gg/wEPK3Rxjtp+iZfD9M269ewvPV2HM9Q07BMzlMjgK8QmguEOqEUUbi0b1q # GFphAXPKZ6Je1yh2AuIzGHLXpyDwwvoSCtdjbwzJNmSLW6CmgyFdXzB0kZSU2LlQ # +QuJYfM2BjUYhEfb3BvR/bLUHMVr9lxSUV0S2yW6r1AFemzFER1y7435UsSFF5PA # PBXbGjfHCBUYP3irRbb1Hode2o+eFnJpxq57t7c+auIurQIDAQABo4IB3TCCAdkw # EgYJKwYBBAGCNxUBBAUCAwEAATAjBgkrBgEEAYI3FQIEFgQUKqdS/mTEmr6CkTxG # NSnPEP8vBO4wHQYDVR0OBBYEFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMFwGA1UdIARV # MFMwUQYMKwYBBAGCN0yDfQEBMEEwPwYIKwYBBQUHAgEWM2h0dHA6Ly93d3cubWlj # cm9zb2Z0LmNvbS9wa2lvcHMvRG9jcy9SZXBvc2l0b3J5Lmh0bTATBgNVHSUEDDAK # BggrBgEFBQcDCDAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMC # AYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvX # zpoYxDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20v # cGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYI # KwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5j # b20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDANBgkqhkiG # 9w0BAQsFAAOCAgEAnVV9/Cqt4SwfZwExJFvhnnJL/Klv6lwUtj5OR2R4sQaTlz0x # M7U518JxNj/aZGx80HU5bbsPMeTCj/ts0aGUGCLu6WZnOlNN3Zi6th542DYunKmC # VgADsAW+iehp4LoJ7nvfam++Kctu2D9IdQHZGN5tggz1bSNU5HhTdSRXud2f8449 # xvNo32X2pFaq95W2KFUn0CS9QKC/GbYSEhFdPSfgQJY4rPf5KYnDvBewVIVCs/wM # nosZiefwC2qBwoEZQhlSdYo2wh3DYXMuLGt7bj8sCXgU6ZGyqVvfSaN0DLzskYDS # PeZKPmY7T7uG+jIa2Zb0j/aRAfbOxnT99kxybxCrdTDFNLB62FD+CljdQDzHVG2d # Y3RILLFORy3BFARxv2T5JL5zbcqOCb2zAVdJVGTZc9d/HltEAY5aGZFrDZ+kKNxn # GSgkujhLmm77IVRrakURR6nxt67I6IleT53S0Ex2tVdUCbFpAUR+fKFhbHP+Crvs # QWY9af3LwUFJfn6Tvsv4O+S3Fb+0zj6lMVGEvL8CwYKiexcdFYmNcP7ntdAoGokL # jzbaukz5m/8K6TT4JDVnK+ANuOaMmdbhIurwJ0I9JZTmdHRbatGePu1+oDEzfbzL # 6Xu/OHBE0ZDxyKs6ijoIYn/ZcGNTTY3ugm2lBRDBcQZqELQdVTNYs6FwZvKhggNQ # MIICOAIBATCB+aGB0aSBzjCByzELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hp # bmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jw # b3JhdGlvbjElMCMGA1UECxMcTWljcm9zb2Z0IEFtZXJpY2EgT3BlcmF0aW9uczEn # MCUGA1UECxMeblNoaWVsZCBUU1MgRVNOOjk2MDAtMDVFMC1EOTQ3MSUwIwYDVQQD # ExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNloiMKAQEwBwYFKw4DAhoDFQC6 # PYHRw9+9SH+1pwy6qzVG3k9lbqCBgzCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1w # IFBDQSAyMDEwMA0GCSqGSIb3DQEBCwUAAgUA7Gp45zAiGA8yMDI1MDkwOTA5NTkw # M1oYDzIwMjUwOTEwMDk1OTAzWjB3MD0GCisGAQQBhFkKBAExLzAtMAoCBQDsanjn # AgEAMAoCAQACAhdFAgH/MAcCAQACAhQYMAoCBQDsa8pnAgEAMDYGCisGAQQBhFkK # BAIxKDAmMAwGCisGAQQBhFkKAwKgCjAIAgEAAgMHoSChCjAIAgEAAgMBhqAwDQYJ # KoZIhvcNAQELBQADggEBAEy/zxZn/eJP7mNC+ik3qOl787yHEwwJMjAvcZ5cPoQu # LPWn4r08ywGO3jfRi9UX3O0lFhgWGzENyLffejZa4wRPVizOv5lWt+XNxhDk9Xun # WisBXVR8QTufZfS8uuGZZCIfSRmiKufQ0ID22uJXAMHqkH0J3c//zjYfQqPD8BJ/ # Q0r9th3hNSpZn8rsr5V4wOynJaUvbT7RNeS1K7DGi6I3mIZHsYhYHIp+WWIg0Vvv # nl6XzdUY6SrrpLKQ1931iecC/Gf8uIuNj1knI3ymtNwSkhOp3IPfoCJ4tqQSQZZm # 12eiqYVCMSlIBTddCaYAiSAklsjM+Brw3hSMOzT6YvwxggQNMIIECQIBATCBkzB8 # MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVk # bW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1N # aWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAITMwAAAgTY4A4HlzJYmAABAAAC # BDANBglghkgBZQMEAgEFAKCCAUowGgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEE # MC8GCSqGSIb3DQEJBDEiBCDuZeEbZPjkcJp8L941TjlWbzDZprwvm6AkVIibA2LB # PDCB+gYLKoZIhvcNAQkQAi8xgeowgecwgeQwgb0EIPnteGX9Wwq8VdJM6mjfx1GE # Jsu7/6kU6l0SS5rcebn+MIGYMIGApH4wfDELMAkGA1UEBhMCVVMxEzARBgNVBAgT # Cldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29m # dCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENB # IDIwMTACEzMAAAIE2OAOB5cyWJgAAQAAAgQwIgQgUer8geqTMk40hdkMD03WMHK2 # PBn5sabVYAb9uyCD8KkwDQYJKoZIhvcNAQELBQAEggIAxOE2jKXutuMUUkypYt3W # dCQBxaS+/6AXF3MpbTEcrSsWQnFUy4vHm50tUKxUqBE0VkaLulXrqf8yQhgxeg2M # VUzx1X6hvg7sQ9KK84m9Ll1MU4X010CL4/lvoI2njkhBxVjPMzQYiEYRloeUh3pS # 7jY+D51wJoSvHIgw+c+6/xLvCDzE6oFxPF1W8wxQd7H4pUt6uaUkhe1LDfAsk7fT # b1auOGGQAKn/Kw+WSAD2A4kOmoPkK181y/Qq7CXJ7p8BnLZNnErA6tKHSlmKMvPB # o+a7XvA77bwrQJ/2Kz0EWB9yF0gEGROu/V1m0Q8lZFr23dV/ZPSIWl2sHURAbnpR # bWiqheRWj4pJaqTxKvSuS2PAw3nNnBAXjguP22B2SZswvvvRILZOGb/rFVBo9x72 # CKUEtmpEL/KJM9Hc9ANnS33pObqgxXtdJYsNimNsFyPbH1XasvlxNj9zhJG3y8MG # IhRsdk9x9wvtdGmsJL6D0IeSDShn0eDzg9Y2VfLM1WqnY+0yoVm2Ngsfd1SAu5cq # vvjTEFPPqWDeXXWBLCfpgqbTgvaLZiNatkKeFmy2k9fSWUnUI01qnToouwhfugxm # mCrbBc6djojq1i6CHJvK2aELF8OYeKQ+UJH2x7343kkbjUssd41sXXbZDcETl7cJ # IGBj70cuI/ZWflzTQoY/c1E= # SIG # End signature block |