Public/Get-ADFSTkHealth.ps1

function Get-ADFSTkHealth {
    [CmdletBinding()]
    param (
        $ConfigFile,
        [ValidateSet("CriticalOnly", "Default", "Full")]
        $HealthCheckMode = "Default",
        [switch]$Silent
    )

    $healtChecks = @{
        CheckSignature          = ($HealthCheckMode -ne "CriticalOnly") #Don't run i CriticalOnly
        CheckConfigVersion      = $true
        MFAAccesControlPolicy   = $true
        RemovedSPsStillInSPHash = ($HealthCheckMode -eq "Full") #Only run in Full mode
        MissingSPsInADFS        = ($HealthCheckMode -eq "Full") #Only run in Full mode
        ScheduledTaskPresent    = ($HealthCheckMode -eq "Full") #Checks if there are a Scheduled Task with the name 'Import Federated Metadata with ADFSToolkit'
    }

    enum Result {
        None
        Pass
        Warning
        Fail
    }

    $healthResults = @()

    #Get All paths
    if ([string]::IsNullOrEmpty($Global:ADFSTkPaths)) {
        $Global:ADFSTkPaths = Get-ADFSTKPaths
    }

    #region get config file(s)
    $configFiles = @()
    if ($PSBoundParameters.ContainsKey('configFile')) {
        $configFiles += $configFile
    }
    else {
        $configFiles = Get-ADFSTkConfiguration -ConfigFilesOnly | ? Enabled -eq $true | select -ExpandProperty ConfigFile
    }
    #endregion

    #region check script signatures
    if ($healtChecks.CheckSignature) {
        Write-ADFSTkVerboseLog (Get-ADFSTkLanguageText healthCheckSignatureStartMessage)
        $Signatures = Get-ChildItem -Path $Global:ADFSTkPaths.modulePath -Filter *.ps1 -Recurse | Get-AuthenticodeSignature
        $validSignatures = $Signatures | ? Status -eq Valid | Select -ExpandProperty Path
        $invalidSignatures = $Signatures | ? Status -eq HashMismatch | Select -ExpandProperty Path
        $missingSignatures = $Signatures | ? Status -eq NotSigned | Select -ExpandProperty Path

        Write-ADFSTkVerboseLog (Get-ADFSTkLanguageText healthCheckSignatureValidSignaturesResult -f $validSignatures.Count)
        Write-ADFSTkVerboseLog (Get-ADFSTkLanguageText healthCheckSignatureInvalidSignaturesResult -f $invalidSignatures.Count)
        Write-ADFSTkVerboseLog (Get-ADFSTkLanguageText healthCheckSignatureMissingSignaturesResult -f $missingSignatures.Count)

        #Signature(s) missing...
        if ($missingSignatures.Count -gt 0) {
            if ($Global:ADFSTkSkipNotSignedHealthCheck -eq $true) {
                $resultObject = [PSCustomObject]@{
                    CheckID       = "CheckSignature"
                    CheckName     = "Signature check"
                    ResultValue   = [Result]::Pass
                    ResultText    = Get-ADFSTkLanguageText healthCheckSignatureSkipNotSignedMessage
                    ResultData    = @()
                    ReferenceFile = ""
                    FixID         = ""
                }

                Write-ADFSTkVerboseLog $resultObject.ResultText

                $healthResults += $resultObject
            }
            else {
                $resultObject = [PSCustomObject]@{
                    CheckID       = "CheckSignature"
                    CheckName     = "Signature check"
                    ResultValue   = [Result]::Fail
                    ResultText    = Get-ADFSTkLanguageText healthCheckSignatureMissingSignaturesResult -f $missingSignatures.Count
                    ResultData    = $missingSignatures
                    ReferenceFile = ""
                    FixID         = ""
                }

                Write-ADFSTkLog (Get-ADFSTkLanguageText healthCheckSignatureMissingSignaturesMessage -f ($missingSignatures | Out-String)) -EntryType Warning

                $healthResults += $resultObject
            }
        }

        #Invalid signature(s)...
        if ($invalidSignatures.Count -gt 0) {
            $resultObject = [PSCustomObject]@{
                CheckID       = "CheckSignature"
                CheckName     = Get-ADFSTkLanguageText healthCheckSignatureName
                ResultValue   = [Result]::Fail
                ResultText    = Get-ADFSTkLanguageText healthCheckSignatureInvalidSignaturesMessage -f ($invalidSignatures | Out-String)
                ResultData    = $invalidSignatures
                ReferenceFile = ""
                FixID         = ""
            }

            Write-ADFSTkVerboseLog $resultObject.ResultText -EntryType Warning

            $healthResults += $resultObject
        }

        if ($resultObject.ResultValue -eq [Result]::Pass) {
            Write-ADFSTkVerboseLog (Get-ADFSTkLanguageText healthCheckSignaturePass)
        }
        else {
            Write-ADFSTkVerboseLog (Get-ADFSTkLanguageText healthCheckSignatureFail)
        }
    }
    #endregion

    #region check config version
    if ($healtChecks.CheckConfigVersion) {

        Write-ADFSTkVerboseLog (Get-ADFSTkLanguageText healthCheckConfigVersionStartMessage)

        foreach ($cf in $configFiles) {
            $resultObject = [PSCustomObject]@{
                CheckID       = "CheckConfigVersion"
                CheckName     = "Version control"
                ResultValue   = [Result]::None
                ResultText    = ""
                ResultData    = @()
                ReferenceFile = $cf
                FixID         = ""
            }
            Write-ADFSTkVerboseLog (Get-ADFSTkLanguageText healhCheckConfigVersionVerifyingPath -f $cf)
            if (Test-Path $cf) {
                Write-ADFSTkVerboseLog (Get-ADFSTkLanguageText healhCheckConfigVersionVerifyingPathSucceeded)
                Write-ADFSTkVerboseLog (Get-ADFSTkLanguageText healhCheckConfigVersionVerifyingXMLParse)
                try {
                    [xml]$xmlCf = Get-Content $cf
                    Write-ADFSTkVerboseLog (Get-ADFSTkLanguageText healhCheckConfigVersionVerifyingXMLParseSucceeded)

                    #Check against compatible version
                    Write-ADFSTkVerboseLog (Get-ADFSTkLanguageText healthCheckConfigVersionVerifyingVersionStart)
                    Write-ADFSTkVerboseLog (Get-ADFSTkLanguageText healthCheckConfigVersionVerifyingVersionCompareVersions -f $xmlCf.configuration.ConfigVersion, $Global:ADFSTkCompatibleInstitutionConfigVersion)
                    if ([float]$xmlCf.configuration.ConfigVersion -ge [float]$Global:ADFSTkCompatibleInstitutionConfigVersion) {
                        $resultObject.ResultValue = [Result]::Pass
                        $resultObject.ResultText = Get-ADFSTkLanguageText healthCheckConfigVersionVerifyingVersionSucceeded

                        Write-ADFSTkVerboseLog $resultObject.ResultText
                    }
                    else {
                        $resultObject.ResultValue = [Result]::Fail
                        $resultObject.ResultText = Get-ADFSTkLanguageText healthIncompatibleInstitutionConfigVersion -f $xmlCf.configuration.ConfigVersion, $Global:ADFSTkCompatibleInstitutionConfigVersion
                        $resultObject.ResultData = $xmlCf.configuration.ConfigVersion

                        Write-ADFSTkLog $resultObject.ResultText -EntryType Warning
                    }
                }
                catch {
                    $resultObject.ResultValue = [Result]::Fail
                    $resultObject.ResultText = Get-ADFSTkLanguageText healhCheckConfigVersionVerifyingXMLParseFailed -f $cf
                    $resultObject.ResultData = $cf

                    Write-ADFSTkLog $resultObject.ResultText -EntryType Warning
                }
            }
            else {
                $resultObject.ResultValue = [Result]::Fail
                $resultObject.ResultText = Get-ADFSTkLanguageText cFileDontExist -f $cf
                $resultObject.ResultData = $cf

                Write-ADFSTkLog $resultObject.ResultText  -EntryType Warning
            }
            $healthResults += $resultObject

            if ($resultObject.ResultValue -eq [Result]::Pass) {
                Write-ADFSTkVerboseLog (Get-ADFSTkLanguageText healthCheckConfigVersionPass)
            }
            else {
                Write-ADFSTkVerboseLog (Get-ADFSTkLanguageText healthCheckConfigVersionFail)
            }
        }
    }
    #endregion

    #region check Access Control Policy if MFA Adapter is installed
    if ($healtChecks.MFAAccesControlPolicy) {
        $resultObject = [PSCustomObject]@{
            CheckID       = "MFAAccesControlPolicy"
            CheckName     = "MFA Access Control Policy"
            ResultValue   = [Result]::None
            ResultText    = ""
            ResultData    = @()
            ReferenceFile = ""
            FixID         = ""
        }

        #Only if MFA Adapter installed!
        # Check if the ADFSTK MFA Adapter is installed and add rules if so
        if ([string]::IsNullOrEmpty($Global:ADFSTKRefedsMFAUsernamePasswordAdapterInstalled)) {
            $Global:ADFSTKRefedsMFAUsernamePasswordAdapterInstalled = ![string]::IsNullOrEmpty((Get-AdfsAuthenticationProvider -Name RefedsMFAUsernamePasswordAdapter -WarningAction Ignore))
        }

        if ($Global:ADFSTKRefedsMFAUsernamePasswordAdapterInstalled) {
            if ((Get-AdfsAccessControlPolicy -Identifier ADFSToolkitPermitEveryoneAndRequireMFA) -eq $null) {
                $resultObject.ResultValue = [Result]::Fail
                $resultObject.ResultText = Get-ADFSTkLanguageText healthMFAAccesControlPolicyInstalledACPMissing
                $resultObject.FixID = "CreateACP"
            }
            else {
                $resultObject.ResultValue = [Result]::Pass
                $resultObject.ResultText = Get-ADFSTkLanguageText healthMFAAccesControlPolicyInstalledACPPresent
            }
        }
        else {
            $resultObject.ResultValue = [Result]::Pass
            $resultObject.ResultText = Get-ADFSTkLanguageText healthMFAAccesControlPolicyNotInstalled
        }

        $healthResults += $resultObject

        if ($resultObject.ResultValue -eq [Result]::Pass) {
            Write-ADFSTkVerboseLog (Get-ADFSTkLanguageText healthMFAAccesControlPolicyPass)
        }
        else {
            Write-ADFSTkVerboseLog (Get-ADFSTkLanguageText healthMFAAccesControlPolicyFail)
        }
    }
    #endregion

    #region check removedSPsStillInSPHash
    if ($healtChecks.removedSPsStillInSPHash) {
        #Automatically remove SP's from SPHash File that's not in the Metadata

        foreach ($cf in $configFiles) {
            $resultObject = [PSCustomObject]@{
                CheckID       = "RemovedSPsStillInSPHash"
                CheckName     = "SP's in SPHash File not in Metadata"
                ResultValue   = [Result]::None
                ResultText    = ""
                ResultData    = @()
                ReferenceFile = $cf
                FixID         = ""
            }

            try {
                $instConfig = Get-ADFSTkInstitutionConfig -ConfigFile $cf

                $spHashFile = Join-Path $Global:ADFSTkPaths.cacheDir $instConfig.configuration.SPHashFile
                $metadataCacheFile = Join-Path $Global:ADFSTkPaths.cacheDir $instConfig.configuration.MetadataCacheFile
                if (Test-Path $spHashFile) {
                    try {
                        $fromHash = [string[]](Import-Clixml $spHashFile).Keys
                    }
                    catch {
                        #What to do?
                        #Rename it? Delete it?
                        $resultObject.Checkname = "SPHash File corrupt"
                        $resultObject.ResultValue = [Result]::Fail
                        $resultObject.ResultText = (Get-ADFSTkLanguageText healthCheckRemovedSPsStillInSPHashSPHashCorrupt -f $spHashFile)
                        $resultObject.ReferenceFile = $spHashFile
                        $resultObject.FixID = "DeleteSPHashFile"
                    }

                    if ($resultObject.ResultValue -ne [Result]::Fail) {
                        $MetadataXML = Get-ADFSTkMetadata -metadataURL $instConfig.configuration.metadataURL -CachedMetadataFile $metadataCacheFile

                        $RawAllSPs = $MetadataXML.EntitiesDescriptor.EntityDescriptor | ? { $_.SPSSODescriptor -ne $null }
                        $MetadataSPs = $RawAllSPs.EntityID

                        $compare = Compare-ADFSTkObject $MetadataSPs $fromHash -CompareType InSecondSetOnly

                        if ($compare.MembersInCompareSet -gt 0) {
                            $resultObject.ResultValue = [Result]::Warning
                            $resultObject.ResultText = (Get-ADFSTkLanguageText healthCheckRemovedSPsStillInSPHashMissingInMetadata -f $compare.MembersInCompareSet)
                            $resultObject.ReferenceFile = $spHashFile
                            $resultObject.ResultData = $compare.CompareSet
                            $resultObject.FixID = "RemoveSPsFromSPHashFile"
                        }
                        else {
                            $resultObject.ResultValue = [Result]::Pass
                            $resultObject.ResultText = Get-ADFSTkLanguageText healthCheckRemovedSPsStillInSPHashNoSPsMissingInMetadata
                        }
                    }
                }
                else {
                    $resultObject.CheckID = "SPHashMissing"
                    $resultObject.CheckName = "SP Hash File existance"
                    $resultObject.ResultValue = [Result]::Warning
                    $resultObject.ResultText = (Get-ADFSTkLanguageText healthCheckRemovedSPsStillInSPHashAllSPsWillBeImported -f $spHashFile)
                    $resultObject.ReferenceFile = $spHashFile
                }
            }
            catch {
                $resultObject.ResultValue = [Result]::Fail
                $resultObject.ResultText = $_
            }
            $healthResults += $resultObject
        }
    }
    #endregion

    #region remove/rerun missing SP's
    
    if ($healtChecks.MissingSPsInADFS) {
        #Automatically remove SP's from SPHash File that's not in the Metadata

        foreach ($cf in $configFiles) {
            $resultObject = [PSCustomObject]@{
                CheckID       = "MissingSPsInADFS"
                CheckName     = "SP's in SPHash File missing in ADFS"
                ResultValue   = [Result]::None
                ResultText    = ""
                ResultData    = @()
                ReferenceFile = $cf
                FixID         = ""
            }

            try {
                $instConfig = Get-ADFSTkInstitutionConfig -ConfigFile $cf

                $spHashFile = Join-Path $Global:ADFSTkPaths.cacheDir $instConfig.configuration.SPHashFile
                $metadataCacheFile = Join-Path $Global:ADFSTkPaths.cacheDir $instConfig.configuration.MetadataCacheFile
                if (Test-Path $spHashFile) {
                    try {
                        $fromHash = [string[]](Import-Clixml $spHashFile).Keys
                    }
                    catch {
                        #What to do?
                        #Rename it? Delete it?
                        $resultObject.Checkname = "SPHash File corrupt"
                        $resultObject.ResultValue = [Result]::Fail
                        $resultObject.ResultText = (Get-ADFSTkLanguageText healthCheckRemovedSPsStillInSPHashSPHashCorrupt -f $spHashFile)
                        $resultObject.ReferenceFile = $spHashFile
                        $resultObject.FixID = "DeleteSPHashFile"
                    }

                    if ($resultObject.ResultValue -ne [Result]::Fail) {
                        $installed = [string[]](Get-ADFSTkToolEntityId -All | Select -ExpandProperty Identifier)
    
                        $compare = Compare-ADFSTkObject $installed $fromHash -CompareType InSecondSetOnly
    
                        if ($compare.MembersInCompareSet -gt 0) {
                            $resultObject.ResultValue = [Result]::Warning
                            $resultObject.ResultText = (Get-ADFSTkLanguageText healthMissingSPsInADFSSPsMissingInADFS -f $compare.MembersInCompareSet)
                            $resultObject.ResultData = $compare.CompareSet
                            $resultObject.ReferenceFile = $spHashFile
                            $resultObject.FixID = "AddMissingSPsInADFS"
                        }
                        else {
                            $resultObject.ResultValue = [Result]::Pass
                            $resultObject.ResultText = Get-ADFSTkLanguageText healthMissingSPsInADFSNoSPsMissingInADFS
                        }
                    }
                }
                else {
                    $resultObject.CheckID = "SPHashMissing"
                    $resultObject.CheckName = "SP Hash File existance"
                    $resultObject.ResultValue = [Result]::Warning
                    $resultObject.ResultText = (Get-ADFSTkLanguageText healthCheckRemovedSPsStillInSPHashAllSPsWillBeImported -f $spHashFile)
                    $resultObject.ReferenceFile = $spHashFile
                }
            }
            catch {
                $resultObject.ResultValue = [Result]::Fail
                $resultObject.ResultText = $_
            }
            $healthResults += $resultObject
        }
    }
    #endregion

    #region Check if Scheduled Task is present
    if ($healtChecks.ScheduledTaskPresent) {
        #Automatically remove SP's from SPHash File that's not in the Metadata

        $resultObject = [PSCustomObject]@{
            CheckID       = "ScheduledTaskPresent"
            CheckName     = "Scheduled Task present"
            ResultValue   = [Result]::None
            ResultText    = ""
            ResultData    = @()
            ReferenceFile = ""
            FixID         = ""
        }

        $schedTask = Get-ScheduledTask -TaskName 'Import Federated Metadata with ADFSToolkit' -TaskPath "\ADFSToolkit\" -ErrorAction SilentlyContinue

        if (![string]::IsNullOrEmpty($schedTask)) {
            $resultObject.ResultValue = [Result]::Pass
            $resultObject.ResultText = Get-ADFSTkLanguageText healthScheduledTaskPresentScheduledTaskPresent
        }
        else {
            $resultObject.ResultValue = [Result]::Warning
            $resultObject.ResultText = Get-ADFSTkLanguageText healthScheduledTaskPresentScheduledTaskNotPresent
            $resultObject.FixID = "RegisterScheduledTask"
        }
        $healthResults += $resultObject
    }
    #endregion

    #region Show result
    if (!$Silent) {
        $healthResults | Select CheckName, ResultValue, ResultText, ReferenceFile | sort ResultValue, CheckName, ReferenceFile | ft -AutoSize -Wrap
    }

    #endregion


    #region Correct fixable errors
    $FixedAnything = $false
    
    #region MFAAccesControlPolicy
    #createACP
    $MFAAccesControlPolicy = $healthResults | ? FixID -eq "CreateACP"
    if (![String]::IsNullOrEmpty($MFAAccesControlPolicy)) {
        if ($Silent -or (Get-ADFSTkAnswer (Get-ADFSTkLanguageText healthMFAAccesControlPolicyRegisterACP))) {
            New-ADFSTKAccessControlPolicy

            Write-ADFSTkLog "Access Control Policy 'ADFSTk:Permit everyone and force MFA' created."

            $MFAAccesControlPolicy.ResultValue = [Result]::Pass
            $MFAAccesControlPolicy.ResultText = (Get-ADFSTkLanguageText healthFixed) + $resultObject.ResultText

            $FixedAnything = $true
        }
    }
    #endregion

    #region RemovedSPsStillInSPHash
    #Do we have SP's in SP Hash file that are missing in the Metadata? They should be removed...
    $removedSPsStillInSPHash = $healthResults | ? FixID -eq "RemoveSPsFromSPHashFile"
    if (![String]::IsNullOrEmpty($removedSPsStillInSPHash)) {
        foreach ($resultObject in $removedSPsStillInSPHash) {
            if ($Silent -or (Get-ADFSTkAnswer (Get-ADFSTkLanguageText healthRemoveSPsFromSPHashFileRemoveSPsNotInMetadata -f $resultObject.ReferenceFile ))) {
                Remove-ADFSTkEntityHash -SPHashFile $resultObject.ReferenceFile -EntityIDs $resultObject.ResultData
                
                $resultObject.ResultText = (Get-ADFSTkLanguageText healthFixed) + $resultObject.ResultText
                $resultObject.ResultValue = [Result]::Pass 

                $FixedAnything = $true
            }
            else {
                #No need to fail, carry on!
            }
        }
    }

    #Do we have corrupt SP Hash files?
    $removedSPsStillInSPHash = $healthResults | ? FixID -eq 'DeleteSPHashFile'
    if (![String]::IsNullOrEmpty($removedSPsStillInSPHash)) {
        foreach ($resultObject in $removedSPsStillInSPHash) {
            if ($Silent -or (Get-ADFSTkAnswer (Get-ADFSTkLanguageText healthDeleteSPHashFileDeleteCorruptSPHashFile -f $resultObject.ReferenceFile ))) {
                Remove-Item -Path $resultObject.ReferenceFile
                
                $resultObject.ResultText = (Get-ADFSTkLanguageText healthFixed) + $resultObject.ResultText
                $resultObject.ResultValue = [Result]::Pass 

                $FixedAnything = $true
            }
            else {
                #The fail result will stand!
            }
        }
    }
    #endregion

    #region RemovedSPsStillInSPHash
    #Do we have SP's in SP Hash file that are missing in ADFS? They should be removed from SP Hash File...
    $addMissingSPs = $healthResults | ? FixID -eq "AddMissingSPsInADFS"
    if (![String]::IsNullOrEmpty($addMissingSPs)) {
        foreach ($resultObject in $addMissingSPs) {
            if ($Silent -or (Get-ADFSTkAnswer (Get-ADFSTkLanguageText healthAddMissingSPsInADFSRemoveMissingSPs -f $resultObject.ReferenceFile ))) {
                Remove-ADFSTkEntityHash -SPHashFile $resultObject.ReferenceFile -EntityIDs $resultObject.ResultData

                $resultObject.ResultText = (Get-ADFSTkLanguageText healthFixed) + $resultObject.ResultText
                $resultObject.ResultValue = [Result]::Pass 

                $FixedAnything = $true
            }
            else {
                #No need to fail, carry on!
            }
        }
    }

    #endregion
        
    #region Add Scheduled Task
    #Only if run manually
    if (!$Silent) {
        $addMissingSPs = $healthResults | ? FixID -eq "RegisterScheduledTask"
        if (![String]::IsNullOrEmpty($addMissingSPs)) {
            if ((Get-ADFSTkAnswer (Get-ADFSTkLanguageText healthRegisterScheduledTaskCreateSchedTask))) {
                Register-ADFSTkScheduledTask
                
                $resultObject.ResultText = (Get-ADFSTkLanguageText healthFixed) + $resultObject.ResultText
                $resultObject.ResultValue = [Result]::Pass 

                $FixedAnything = $true
            }
        }
    }
    #endregion

    if ($FixedAnything -and -not $Silent) {
        $healthResults | Select CheckName, ResultValue, ResultText, ReferenceFile | sort ResultValue, CheckName, ReferenceFile | ft -AutoSize -Wrap
    }

    if ($Silent) {
        return !($healthResults.ResultValue.Contains([Result]::Fail))
    }
    else {
        if ($healthResults.ResultValue.Contains([Result]::Fail)) {
            Write-ADFSTkLog -Message (Get-ADFSTkLanguageText healthFailed) -EntryType Error
        }
        elseif ($healthResults.ResultValue.Contains([Result]::Warning)) {
            Write-ADFSTkLog -Message (Get-ADFSTkLanguageText healthPassedWithWarnings) -EntryType Warning
        }
        else {
            Write-ADFSTkLog -Message (Get-ADFSTkLanguageText healthPassed) -EntryType Information -ForegroundColor Green
        }
    }
}
# SIG # Begin signature block
# MIId/gYJKoZIhvcNAQcCoIId7zCCHesCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCDLcfA3fPJXETiY
# 6WJc9R29OKM7Rocvky024/XH3TvleKCCGKwwggR9MIIDZaADAgECAgMb5xUwDQYJ
# KoZIhvcNAQELBQAwYzELMAkGA1UEBhMCVVMxITAfBgNVBAoTGFRoZSBHbyBEYWRk
# eSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28gRGFkZHkgQ2xhc3MgMiBDZXJ0aWZp
# Y2F0aW9uIEF1dGhvcml0eTAeFw0xNDAxMDEwNzAwMDBaFw0zMTA1MzAwNzAwMDBa
# MIGDMQswCQYDVQQGEwJVUzEQMA4GA1UECBMHQXJpem9uYTETMBEGA1UEBxMKU2Nv
# dHRzZGFsZTEaMBgGA1UEChMRR29EYWRkeS5jb20sIEluYy4xMTAvBgNVBAMTKEdv
# IERhZGR5IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IC0gRzIwggEiMA0GCSqG
# SIb3DQEBAQUAA4IBDwAwggEKAoIBAQC/cWII8fpZNPcbyRij94BJWOkigxOmxSBD
# ATuE8eaFSZ8n6vaEG06gtNtwmMcyAbEFPgdO7vT6Ty9ZMCLnqxlWa+KAB/zzFnWA
# OVF75fk1tnROqY2CE+S2P6kDg/qivooVan/eC8O2GRQFyurDqASUO0Z8Mg3zAGYi
# yI1pbTaMERi307IcYLQ4+gKMztPdRgfeCj7rXXzIfPuwK1OkkmJpUSUFYRpEgYws
# qUOWI9+sOoGaDinFHKnpXR62np4wCjnO8YiA+0tdzDLshWJDJTQCVicBkbQ7cCo/
# brHonIgBfZ/U+dtTbWCdvyznWKu4X0b8zsQbAzwJ60kxXGlGs+BHAgMBAAGjggEX
# MIIBEzAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQU
# OpqFBxBnKLbv9r0FQW4gwZTaD94wHwYDVR0jBBgwFoAU0sSw0pHUTBFxs2HLPaH+
# 3ahq1OMwNAYIKwYBBQUHAQEEKDAmMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5n
# b2RhZGR5LmNvbS8wMgYDVR0fBCswKTAnoCWgI4YhaHR0cDovL2NybC5nb2RhZGR5
# LmNvbS9nZHJvb3QuY3JsMEYGA1UdIAQ/MD0wOwYEVR0gADAzMDEGCCsGAQUFBwIB
# FiVodHRwczovL2NlcnRzLmdvZGFkZHkuY29tL3JlcG9zaXRvcnkvMA0GCSqGSIb3
# DQEBCwUAA4IBAQBZC1O9koYRpyR77Vsxzx0fbHDFuG6+Trv2vpdQ4TB/uihcYpTC
# 434z9/tCdoXblRyMIlh1CQyIZWc5ChYJxaA4l6TFI5M/tBimAQZEkeOnaSe0WiV/
# Orcyzd2E/yo4KTOk3Weyhf6hiCAcUInI3Cr2QgM3TOaI39WvJPKxw9/MtezgmV63
# SVQgPJQYDMccUhhJpG3hs1gLydjs2a4cMo4ocA3i/qYXnoQPvVdws1rpH6CGU7vv
# fP9pC+BIw7eTC8gKVMSsXRRnN2zKpS8xCDeqbm+MvJviV10kga+Xl5yErWysN0xm
# 82GRESDkvjCfeqQpCbDhNF9kdxhAUd+MMKavMIIE0DCCA7igAwIBAgIBBzANBgkq
# hkiG9w0BAQsFADCBgzELMAkGA1UEBhMCVVMxEDAOBgNVBAgTB0FyaXpvbmExEzAR
# BgNVBAcTClNjb3R0c2RhbGUxGjAYBgNVBAoTEUdvRGFkZHkuY29tLCBJbmMuMTEw
# LwYDVQQDEyhHbyBEYWRkeSBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcy
# MB4XDTExMDUwMzA3MDAwMFoXDTMxMDUwMzA3MDAwMFowgbQxCzAJBgNVBAYTAlVT
# MRAwDgYDVQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMRowGAYDVQQK
# ExFHb0RhZGR5LmNvbSwgSW5jLjEtMCsGA1UECxMkaHR0cDovL2NlcnRzLmdvZGFk
# ZHkuY29tL3JlcG9zaXRvcnkvMTMwMQYDVQQDEypHbyBEYWRkeSBTZWN1cmUgQ2Vy
# dGlmaWNhdGUgQXV0aG9yaXR5IC0gRzIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
# ggEKAoIBAQC54MsQ1K92vdSTYuswZLiBCGzDBNliF44v/z5lz4/OYuY8UhzaFkVL
# Vat4a2ODYpDOD2lsmcgaFItMzEUz6ojcnqOvK/6AYZ15V8TPLvQ/MDxdR/yaFrzD
# N5ZBUY4RS1T4KL7QjL7wMDge87Am+GZHY23ecSZHjzhHU9FGHbTj3ADqRay9vHHZ
# qm8A29vNMDp5T19MR/gd71vCxJ1gO7GyQ5HYpDNO6rPWJ0+tJYqlxvTV0KaudAVk
# V4i1RFXULSo6Pvi4vekyCgKUZMQWOlDxSq7neTOvDCAHf+jfBDnCaQJsY1L6d8Eb
# yHSHyLmTGFBUNUtpTrw700kuH9zB0lL7AgMBAAGjggEaMIIBFjAPBgNVHRMBAf8E
# BTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUQMK9J47MNIMwojPX+2yz
# 8LQsgM4wHwYDVR0jBBgwFoAUOpqFBxBnKLbv9r0FQW4gwZTaD94wNAYIKwYBBQUH
# AQEEKDAmMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5nb2RhZGR5LmNvbS8wNQYD
# VR0fBC4wLDAqoCigJoYkaHR0cDovL2NybC5nb2RhZGR5LmNvbS9nZHJvb3QtZzIu
# Y3JsMEYGA1UdIAQ/MD0wOwYEVR0gADAzMDEGCCsGAQUFBwIBFiVodHRwczovL2Nl
# cnRzLmdvZGFkZHkuY29tL3JlcG9zaXRvcnkvMA0GCSqGSIb3DQEBCwUAA4IBAQAI
# fmyTEMg4uJapkEv/oV9PBO9sPpyIBslQj6Zz91cxG7685C/b+LrTW+C05+Z5Yg4M
# otdqY3MxtfWoSKQ7CC2iXZDXtHwlTxFWMMS2RJ17LJ3lXubvDGGqv+QqG+6EnriD
# fcFDzkSnE3ANkR/0yBOtg2DZ2HKocyQetawiDsoXiWJYRBuriSUBAA/NxBti21G0
# 0w9RKpv0vHP8ds42pM3Z2Czqrpv1KrKQ0U11GIo/ikGQI31bS/6kA1ibRrLDYGCD
# +H1QQc7CoZDDu+8CL9IVVO5EFdkKrqeKM+2xLXY2JtwE65/3YR8V3Idv7kaWKK2h
# Jn0KCacuBKONvPi8BDABMIIE/jCCA+agAwIBAgIQDUJK4L46iP9gQCHOFADw3TAN
# BgkqhkiG9w0BAQsFADByMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQg
# SW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMTEwLwYDVQQDEyhEaWdpQ2Vy
# dCBTSEEyIEFzc3VyZWQgSUQgVGltZXN0YW1waW5nIENBMB4XDTIxMDEwMTAwMDAw
# MFoXDTMxMDEwNjAwMDAwMFowSDELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lD
# ZXJ0LCBJbmMuMSAwHgYDVQQDExdEaWdpQ2VydCBUaW1lc3RhbXAgMjAyMTCCASIw
# DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMLmYYRnxYr1DQikRcpja1HXOhFC
# vQp1dU2UtAxQtSYQ/h3Ib5FrDJbnGlxI70Tlv5thzRWRYlq4/2cLnGP9NmqB+in4
# 3Stwhd4CGPN4bbx9+cdtCT2+anaH6Yq9+IRdHnbJ5MZ2djpT0dHTWjaPxqPhLxs6
# t2HWc+xObTOKfF1FLUuxUOZBOjdWhtyTI433UCXoZObd048vV7WHIOsOjizVI9r0
# TXhG4wODMSlKXAwxikqMiMX3MFr5FK8VX2xDSQn9JiNT9o1j6BqrW7EdMMKbaYK0
# 2/xWVLwfoYervnpbCiAvSwnJlaeNsvrWY4tOpXIc7p96AXP4Gdb+DUmEvQECAwEA
# AaOCAbgwggG0MA4GA1UdDwEB/wQEAwIHgDAMBgNVHRMBAf8EAjAAMBYGA1UdJQEB
# /wQMMAoGCCsGAQUFBwMIMEEGA1UdIAQ6MDgwNgYJYIZIAYb9bAcBMCkwJwYIKwYB
# BQUHAgEWG2h0dHA6Ly93d3cuZGlnaWNlcnQuY29tL0NQUzAfBgNVHSMEGDAWgBT0
# tuEgHf4prtLkYaWyoiWyyBc1bjAdBgNVHQ4EFgQUNkSGjqS6sGa+vCgtHUQ23eNq
# erwwcQYDVR0fBGowaDAyoDCgLoYsaHR0cDovL2NybDMuZGlnaWNlcnQuY29tL3No
# YTItYXNzdXJlZC10cy5jcmwwMqAwoC6GLGh0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNv
# bS9zaGEyLWFzc3VyZWQtdHMuY3JsMIGFBggrBgEFBQcBAQR5MHcwJAYIKwYBBQUH
# MAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBPBggrBgEFBQcwAoZDaHR0cDov
# L2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0U0hBMkFzc3VyZWRJRFRpbWVz
# dGFtcGluZ0NBLmNydDANBgkqhkiG9w0BAQsFAAOCAQEASBzctemaI7znGucgDo5n
# Rv1CclF0CiNHo6uS0iXEcFm+FKDlJ4GlTRQVGQd58NEEw4bZO73+RAJmTe1ppA/2
# uHDPYuj1UUp4eTZ6J7fz51Kfk6ftQ55757TdQSKJ+4eiRgNO/PT+t2R3Y18jUmmD
# gvoaU+2QzI2hF3MN9PNlOXBL85zWenvaDLw9MtAby/Vh/HUIAHa8gQ74wOFcz8QR
# cucbZEnYIpp1FUL1LTI4gdr0YKK6tFL7XOBhJCVPst/JKahzQ1HavWPWH1ub9y4b
# TxMd90oNcX6Xt/Q/hOvB46NJofrOp79Wz7pZdmGJX36ntI5nePk2mOHLKNpbh6aK
# LzCCBRwwggQEoAMCAQICCGXB0JJJvDvXMA0GCSqGSIb3DQEBCwUAMIG0MQswCQYD
# VQQGEwJVUzEQMA4GA1UECBMHQXJpem9uYTETMBEGA1UEBxMKU2NvdHRzZGFsZTEa
# MBgGA1UEChMRR29EYWRkeS5jb20sIEluYy4xLTArBgNVBAsTJGh0dHA6Ly9jZXJ0
# cy5nb2RhZGR5LmNvbS9yZXBvc2l0b3J5LzEzMDEGA1UEAxMqR28gRGFkZHkgU2Vj
# dXJlIENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTIxMDExOTE4MzczNloX
# DTIyMDMwODE4NTgwMFowXjELMAkGA1UEBhMCQ0ExEDAOBgNVBAgTB09udGFyaW8x
# DzANBgNVBAcTBk90dGF3YTEVMBMGA1UEChMMQ0FOQVJJRSBJbmMuMRUwEwYDVQQD
# EwxDQU5BUklFIEluYy4wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZ
# hfCjFqiTmN1uLoySixnwaOjf/ZAL9P6SvjlCaBA2mutoorEgnzUP8HnOIcvMRgEM
# PmpaZ8egM93Bmx9d41xoarsQpCN3DhYOo+b3fWnPucVtpxbul2OFePv63mw/uvr+
# dqkv4b/f3Tg+ilQbpsNonbvh9MKEFv8Pn9koj0ySV+qxz34PxTVAe6g//pel3/3i
# 9fqilCnIEcx4zg/+NKBeOWROSs4oXo3IvBjVrunmz+YuieSr78TqIE6hD8JF2q1w
# KwfMB3+x7dEXZAus9WtIU/qITATtEfO9QAgrrYL4F1MLN+osSp8my5eCOjnLTQc4
# 7q574V3zQhsIHW7yBXLdAgMBAAGjggGFMIIBgTAMBgNVHRMBAf8EAjAAMBMGA1Ud
# JQQMMAoGCCsGAQUFBwMDMA4GA1UdDwEB/wQEAwIHgDA1BgNVHR8ELjAsMCqgKKAm
# hiRodHRwOi8vY3JsLmdvZGFkZHkuY29tL2dkaWcyczUtNi5jcmwwXQYDVR0gBFYw
# VDBIBgtghkgBhv1tAQcXAjA5MDcGCCsGAQUFBwIBFitodHRwOi8vY2VydGlmaWNh
# dGVzLmdvZGFkZHkuY29tL3JlcG9zaXRvcnkvMAgGBmeBDAEEATB2BggrBgEFBQcB
# AQRqMGgwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmdvZGFkZHkuY29tLzBABggr
# BgEFBQcwAoY0aHR0cDovL2NlcnRpZmljYXRlcy5nb2RhZGR5LmNvbS9yZXBvc2l0
# b3J5L2dkaWcyLmNydDAfBgNVHSMEGDAWgBRAwr0njsw0gzCiM9f7bLPwtCyAzjAd
# BgNVHQ4EFgQUUPnMg2nmYS8l7rmax3weVkrgz5AwDQYJKoZIhvcNAQELBQADggEB
# AGabJLu09gdYHt7ZMbpJ4048ZIiXwVLE/HNcnApTghNaHnSSiMI2xTsmbrM/lYsm
# pwFuws1c2fMBvyDRgkzR/4+RIjoQJpLrHy1QABYlWAIKMqdFmfqty0QApgIkGN2+
# scMxKMWJGND8qp3KM+5C8TNTsO0gPVfdaarX2TmLM6yIQcgxD8YZMd0mqdR7rcCe
# bgMeAdHLYPQu/HM0Cj3qtzFx/CZzz93CAlh8Dx5woqeNJixQMLK28MhU8y6NSN5o
# KnD/8EESudRzXyoowZ2N4YJzyye5UL9pxhniDKs444w1r5XcjQYDo11G8Y4up4XW
# 1cFtLNulHYcKhAnQ7XHswxMwggUxMIIEGaADAgECAhAKoSXW1jIbfkHkBdo2l8IV
# MA0GCSqGSIb3DQEBCwUAMGUxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2Vy
# dCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xJDAiBgNVBAMTG0RpZ2lD
# ZXJ0IEFzc3VyZWQgSUQgUm9vdCBDQTAeFw0xNjAxMDcxMjAwMDBaFw0zMTAxMDcx
# MjAwMDBaMHIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAX
# BgNVBAsTEHd3dy5kaWdpY2VydC5jb20xMTAvBgNVBAMTKERpZ2lDZXJ0IFNIQTIg
# QXNzdXJlZCBJRCBUaW1lc3RhbXBpbmcgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IB
# DwAwggEKAoIBAQC90DLuS82Pf92puoKZxTlUKFe2I0rEDgdFM1EQfdD5fU1ofue2
# oPSNs4jkl79jIZCYvxO8V9PD4X4I1moUADj3Lh477sym9jJZ/l9lP+Cb6+NGRwYa
# VX4LJ37AovWg4N4iPw7/fpX786O6Ij4YrBHk8JkDbTuFfAnT7l3ImgtU46gJcWvg
# zyIQD3XPcXJOCq3fQDpct1HhoXkUxk0kIzBdvOw8YGqsLwfM/fDqR9mIUF79Zm5W
# YScpiYRR5oLnRlD9lCosp+R1PrqYD4R/nzEU1q3V8mTLex4F0IQZchfxFwbvPc3W
# Te8GQv2iUypPhR3EHTyvz9qsEPXdrKzpVv+TAgMBAAGjggHOMIIByjAdBgNVHQ4E
# FgQU9LbhIB3+Ka7S5GGlsqIlssgXNW4wHwYDVR0jBBgwFoAUReuir/SSy4IxLVGL
# p6chnfNtyA8wEgYDVR0TAQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwEwYD
# VR0lBAwwCgYIKwYBBQUHAwgweQYIKwYBBQUHAQEEbTBrMCQGCCsGAQUFBzABhhho
# dHRwOi8vb2NzcC5kaWdpY2VydC5jb20wQwYIKwYBBQUHMAKGN2h0dHA6Ly9jYWNl
# cnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcnQwgYEG
# A1UdHwR6MHgwOqA4oDaGNGh0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9EaWdpQ2Vy
# dEFzc3VyZWRJRFJvb3RDQS5jcmwwOqA4oDaGNGh0dHA6Ly9jcmwzLmRpZ2ljZXJ0
# LmNvbS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcmwwUAYDVR0gBEkwRzA4Bgpg
# hkgBhv1sAAIEMCowKAYIKwYBBQUHAgEWHGh0dHBzOi8vd3d3LmRpZ2ljZXJ0LmNv
# bS9DUFMwCwYJYIZIAYb9bAcBMA0GCSqGSIb3DQEBCwUAA4IBAQBxlRLpUYdWac3v
# 3dp8qmN6s3jPBjdAhO9LhL/KzwMC/cWnww4gQiyvd/MrHwwhWiq3BTQdaq6Z+Cei
# Zr8JqmDfdqQ6kw/4stHYfBli6F6CJR7Euhx7LCHi1lssFDVDBGiy23UC4HLHmNY8
# ZOUfSBAYX4k4YU1iRiSHY4yRUiyvKYnleB/WCxSlgNcSR3CzddWThZN+tpJn+1Nh
# iaj1a5bA9FhpDXzIAbG5KHW3mWOFIoxhynmUfln8jA/jb7UBJrZspe6HUSHkWGCb
# ugwtK22ixH67xCUrRwIIfEmuE7bhfEJCKMYYVs9BNLZmXbZ0e/VWMyIvIjayS6JK
# ldj1po5SMYIEqDCCBKQCAQEwgcEwgbQxCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdB
# cml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMRowGAYDVQQKExFHb0RhZGR5LmNv
# bSwgSW5jLjEtMCsGA1UECxMkaHR0cDovL2NlcnRzLmdvZGFkZHkuY29tL3JlcG9z
# aXRvcnkvMTMwMQYDVQQDEypHbyBEYWRkeSBTZWN1cmUgQ2VydGlmaWNhdGUgQXV0
# aG9yaXR5IC0gRzICCGXB0JJJvDvXMA0GCWCGSAFlAwQCAQUAoIGEMBgGCisGAQQB
# gjcCAQwxCjAIoAKAAKECgAAwGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYK
# KwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIK2D/RTs
# MiZCGHi+5hkKLcbJLBT+bJvt7DXJ5WDCVl+AMA0GCSqGSIb3DQEBAQUABIIBAGyX
# ptHUbdJBtJ7nyipKZwIFzEHgk8KZk3nlc2dn0+uHxgmpf0+H/qf/bfhtq4fLUZGk
# Y6GRZbkAAK84J5zwPWMSvDd534H6trZRbBrxQRcstZ/q84j46BsAiFlH9OhxG7Rz
# vKfJLCHy6SghtCod2kKAq7aYJeTwsK9qKGvox9YpHqSTtLbKjexq8JQYHxKUwvVa
# 5J0JkbBuzbvccEM1PDdwH8BoY5GUT9pnixMFDYKnDzXgY/yymjUhNGbsYl52hIZH
# FB/d4T+d60rEi3zhCcGgIpVnij3nIyRcibdoX6wvRTK7s6PeDKu7ekIsKXqBb2Sh
# 0IBQrC5NKlZcm+/AbU6hggIwMIICLAYJKoZIhvcNAQkGMYICHTCCAhkCAQEwgYYw
# cjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQ
# d3d3LmRpZ2ljZXJ0LmNvbTExMC8GA1UEAxMoRGlnaUNlcnQgU0hBMiBBc3N1cmVk
# IElEIFRpbWVzdGFtcGluZyBDQQIQDUJK4L46iP9gQCHOFADw3TANBglghkgBZQME
# AgEFAKBpMBgGCSqGSIb3DQEJAzELBgkqhkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8X
# DTIxMTExNTIwMDY0OFowLwYJKoZIhvcNAQkEMSIEIDZVLGzoYRYgzX7TzWxMKmXr
# fuJeiYr8+9Xi17IsKAOCMA0GCSqGSIb3DQEBAQUABIIBAMJCg8qHlQw2l516wQtD
# oHtN4JLw5k3CpJwJbUpSAw+NL36wKMVakl2K177iuzPob6r28PEMtS1VgeoFoY2+
# jRKi1v3G9WcYCTMIjOSXVQ5bJCWmvv1CVS0lMtIMlEOq1a9KVQHWEupSZX5RJWWm
# ywRyCIdRjabPlD5beUrNqG8d1oBCOF9wwhFIx+GS/D2RyNr2lcRerC3r/ULjj7e5
# az5nimvVmRdeRXQWEH4H95vxwacFmuvCQORgUlqmMOfpjLcrPQVNmnjkEH8cHQdx
# PjNZG7SUAVbWVxw9MQ9FHU+UIRpc5CdwZW+PwIvPhvtUJuWQyncla8mJ1ummTp1F
# UEs=
# SIG # End signature block