Public/Get-ADFSTkHealth.ps1

function Get-ADFSTkHealth {
    [CmdletBinding()]
    param (
        #The path to the institution configuration file that should be handled. If not provided all institution config files present in ADFSTk config will be used.
        $ConfigFile,
        #The HealtheCheckMode states how rigorous tests should be done. Defalut is done every time Sync-ADFSTkAggregates are run.
        [ValidateSet("CriticalOnly", "Default", "Full")]
        $HealthCheckMode = "Default",
        #Silent only reports true/false without output. It also tries to fix errors that can be fixed automatically.
        [switch]$Silent
    )

    $healthChecks = @{
        CheckSignature                = ($HealthCheckMode -ne "CriticalOnly") #Don't run i CriticalOnly
        CheckADFSTkConfigVersion      = $true
        CheckInstitutionConfigVersion = $true
        MFAAccesControlPolicy         = $true
        RemovedSPsStillInSPHash       = ($HealthCheckMode -eq "Full") #Only run in Full mode
        ScheduledTaskPresent          = ($HealthCheckMode -eq "Full") #Checks if the Import Metadata Scheduled Task is present
        MissingSPsInADFS              = ($HealthCheckMode -eq "Full") #Only run in Full mode
        FticksScheduledTaskPresent    = ($HealthCheckMode -eq "Full") #Checks if the F-ticks Scheduled Task is present
    }

    enum Result {
        None
        Pass
        Warning
        Fail
    }

    $healthResults = @()

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

    #region get config file(s)
    $ADFSTkConfig = Get-ADFSTkConfiguration

    $configFiles = @()
    if ($PSBoundParameters.ContainsKey('configFile')) {
        $configFiles += $configFile
    }
    else {
        $configFiles = $ADFSTkConfig.Configuration.ConfigFiles.ConfigFile | ? enabled -eq $true | select -ExpandProperty '#text'
    }
    #endregion

    if (!$Silent) {
        $numberOfHealthChecks = ($healthChecks.Values | ? { $_ -eq $true }).Count
        $numberOfHealthChecksDone = 0
        Write-Progress -Activity "Processing Health Checks..."
    }

    #region check script signatures
    if ($healthChecks.CheckSignature) {
        Write-ADFSTkVerboseLog (Get-ADFSTkLanguageText healthCheckSignatureStartMessage)

        if (!$Silent) {
            $numberOfHealthChecksDone++
            Write-Progress -Activity "Processing Health Checks..." -Status "$numberOfHealthChecksDone/$numberOfHealthChecks" -CurrentOperation (Get-ADFSTkLanguageText healthCheckMissingSignaturesName) -PercentComplete ($numberOfHealthChecksDone / $numberOfHealthChecks * 100)
        }
        
        $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...
        $resultObject = [PSCustomObject]@{
            CheckID       = "CheckSignature"
            CheckName     = Get-ADFSTkLanguageText healthCheckMissingSignaturesName
            ResultValue   = [Result]::Pass
            ResultText    = "No files with missing signatures!"
            ResultData    = @()
            ReferenceFile = ""
            FixID         = ""
        }
        if ($missingSignatures.Count -gt 0) {
            if ($Global:ADFSTkSkipNotSignedHealthCheck -eq $true) {
                $resultObject.ResultText = Get-ADFSTkLanguageText healthCheckSignatureSkipNotSignedMessage

                Write-ADFSTkVerboseLog $resultObject.ResultText

                $healthResults += $resultObject
            }
            else {
                $resultObject.ResultValue = [Result]::Fail
                $resultObject.ResultText = Get-ADFSTkLanguageText healthCheckSignatureMissingSignaturesResult -f $missingSignatures.Count
                $resultObject.ResultData = $missingSignatures
                
                Write-ADFSTkLog (Get-ADFSTkLanguageText healthCheckSignatureMissingSignaturesMessage -f ($missingSignatures | Out-String)) -EntryType Warning

                $healthResults += $resultObject
            }
        }
        else {
            $healthResults += $resultObject
        }

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

            Write-ADFSTkVerboseLog $resultObject.ResultText -EntryType Warning

            $healthResults += $resultObject
        }
        else {
            $healthResults += $resultObject
        }

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

    #region check ADFS Toolkit config version
    if ($healthChecks.CheckADFSTkConfigVersion) {
        Write-ADFSTkVerboseLog (Get-ADFSTkLanguageText healthCheckADFSTkConfigVersionStartMessage)
    
        if (!$Silent) {
            $numberOfHealthChecksDone++
            Write-Progress -Activity "Processing Health Checks..." -Status "$numberOfHealthChecksDone/$numberOfHealthChecks" -CurrentOperation (Get-ADFSTkLanguageText healthCheckADFSTkConfigVersionStartMessage) -PercentComplete ($numberOfHealthChecksDone / $numberOfHealthChecks * 100)
        }
    
        $resultObject = [PSCustomObject]@{
            CheckID       = "CheckADFSTkConfigVersion"
            CheckName     = "ADFS Toolkit Configuration Version control"
            ResultValue   = [Result]::None
            ResultText    = ""
            ResultData    = @()
            ReferenceFile = ""
            FixID         = ""
        }
         
        #Check against compatible version
        Write-ADFSTkVerboseLog (Get-ADFSTkLanguageText healthCheckConfigVersionVerifyingVersionStart)
        Write-ADFSTkVerboseLog (Get-ADFSTkLanguageText healthCheckConfigVersionVerifyingVersionCompareVersions -f $ADFSTkConfig.Configuration.ConfigVersion, $Global:ADFSTkCompatibleADFSTkConfigVersion)
        if ([float]$ADFSTkConfig.Configuration.ConfigVersion -eq [float]$Global:ADFSTkCompatibleADFSTkConfigVersion) {
            $resultObject.ResultValue = [Result]::Pass
            $resultObject.ResultText = Get-ADFSTkLanguageText healthCheckConfigVersionVerifyingVersionSucceeded
    
            Write-ADFSTkVerboseLog $resultObject.ResultText
        }
        else {
            $resultObject.ResultValue = [Result]::Fail
            $resultObject.ResultText = Get-ADFSTkLanguageText healthIncompatibleADFSTkConfigVersion -f $ADFSTkConfig.Configuration.ConfigVersion, $Global:ADFSTkCompatibleADFSTkConfigVersion
            $resultObject.FixID = "FixADFSTkConfigVersion"
    
            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 institution config version
    if ($healthChecks.CheckInstitutionConfigVersion) {
        Write-ADFSTkVerboseLog (Get-ADFSTkLanguageText healthCheckInstitutionConfigVersionStartMessage)

        if (!$Silent) {
            $numberOfHealthChecksDone++
            Write-Progress -Activity "Processing Health Checks..." -Status "$numberOfHealthChecksDone/$numberOfHealthChecks" -CurrentOperation (Get-ADFSTkLanguageText healthCheckInstitutionConfigVersionStartMessage) -PercentComplete ($numberOfHealthChecksDone / $numberOfHealthChecks * 100)
        }

        foreach ($cf in $configFiles) {
            $resultObject = [PSCustomObject]@{
                CheckID       = "CheckInstitutionConfigVersion"
                CheckName     = "Institution Configuration 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 -eq [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
                        $resultObject.FixID = "FixInstitutionConfigVersion"

                        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
    Write-ADFSTkVerboseLog (Get-ADFSTkLanguageText healthCheckMFAAccessPolicyStartMessage)

    if (!$Silent) {
        $numberOfHealthChecksDone++
        Write-Progress -Activity "Processing Health Checks..." -Status "$numberOfHealthChecksDone/$numberOfHealthChecks" -CurrentOperation (Get-ADFSTkLanguageText healthCheckMFAAccessPolicyStartMessage) -PercentComplete ($numberOfHealthChecksDone / $numberOfHealthChecks * 100)
    }

    if ($healthChecks.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 ($healthChecks.removedSPsStillInSPHash) {
        #Automatically remove SP's from SPHash File that's not in the Metadata
        Write-ADFSTkVerboseLog (Get-ADFSTkLanguageText healthCheckRemovedSPsStillInSPHashSPHashStartMessage)

        if (!$Silent) {
            $numberOfHealthChecksDone++
            Write-Progress -Activity "Processing Health Checks..." -Status "$numberOfHealthChecksDone/$numberOfHealthChecks" -CurrentOperation (Get-ADFSTkLanguageText healthCheckRemovedSPsStillInSPHashSPHashStartMessage) -PercentComplete ($numberOfHealthChecksDone / $numberOfHealthChecks * 100)
        }

        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 ($healthChecks.MissingSPsInADFS) {
        #Automatically remove SP's from SPHash File that's not in the Metadata
        Write-ADFSTkVerboseLog (Get-ADFSTkLanguageText healthMissingSPsInADFSStartMessage)

        if (!$Silent) {
            $numberOfHealthChecksDone++
            Write-Progress -Activity "Processing Health Checks..." -Status "$numberOfHealthChecksDone/$numberOfHealthChecks" -CurrentOperation (Get-ADFSTkLanguageText healthMissingSPsInADFSStartMessage) -PercentComplete ($numberOfHealthChecksDone / $numberOfHealthChecks * 100)
        }

        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 the Import Metadata Scheduled Task is present
    if ($healthChecks.ScheduledTaskPresent) {
        Write-ADFSTkVerboseLog (Get-ADFSTkLanguageText healthScheduledTaskPresentStartMessage -f "Import Metadata")

        if (!$Silent) {
            $numberOfHealthChecksDone++
            Write-Progress -Activity "Processing Health Checks..." -Status "$numberOfHealthChecksDone/$numberOfHealthChecks" -CurrentOperation (Get-ADFSTkLanguageText healthScheduledTaskPresentStartMessage -f "Import Metadata") -PercentComplete ($numberOfHealthChecksDone / $numberOfHealthChecks * 100)
        }

        $resultObject = [PSCustomObject]@{
            CheckID       = "ScheduledTaskPresent"
            CheckName     = "Import Metadata Scheduled Task present"
            ResultValue   = [Result]::None
            ResultText    = ""
            ResultData    = @()
            ReferenceFile = ""
            FixID         = ""
        }
        
        $schedTask = Get-ScheduledTask -TaskName (Get-ADFSTkLanguageText confImportMetadata) -TaskPath "\ADFSToolkit\" -ErrorAction SilentlyContinue

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

    #region Check if F-Ticks Scheduled Task is present
    if ($healthChecks.FticksScheduledTaskPresent) {
        Write-ADFSTkVerboseLog (Get-ADFSTkLanguageText healthScheduledTaskPresentStartMessage -f "F-Ticks")

        if (!$Silent) {
            $numberOfHealthChecksDone++
            Write-Progress -Activity "Processing Health Checks..." -Status "$numberOfHealthChecksDone/$numberOfHealthChecks" -CurrentOperation (Get-ADFSTkLanguageText healthScheduledTaskPresentStartMessage -f "F-Ticks") -PercentComplete ($numberOfHealthChecksDone / $numberOfHealthChecks * 100)
        }

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

        $schedTask = Get-ScheduledTask -TaskName (Get-ADFSTkLanguageText confProcessLoginEvents) -TaskPath "\ADFSToolkit\" -ErrorAction SilentlyContinue

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

    #region Show result
    if (!$Silent) {
        Write-Progress -Activity "Processing Health Checks..." -PercentComplete 100 -Completed
        $healthResults | Select CheckName, ResultValue, ResultText, ReferenceFile | sort ResultValue, CheckName, ReferenceFile | ft -AutoSize -Wrap
    }

    #endregion

    #region Correct fixable errors
    $FixedAnything = $false
    
    
    #region Fix incorrect Institution Config Version(s)
    #Only if run manually
    if (!$Silent) {
        $resultObject = $healthResults | ? FixID -eq "FixADFSTkConfigVersion"
            if (![String]::IsNullOrEmpty($resultObject)) {
                if ((Get-ADFSTkAnswer (Get-ADFSTkLanguageText healthUpdateADFSTkConfigVersion))) {
                    Update-ADFSTk
                
                    $resultObject.ResultText = (Get-ADFSTkLanguageText healthFixed) + $resultObject.ResultText
                    $resultObject.ResultValue = [Result]::Pass 

                    $FixedAnything = $true
                }
            }
    }
    #endregion

    #region Fix incorrect Institution Config Version(s)
    #Only if run manually
    if (!$Silent) {
        $resultObjects = $healthResults | ? FixID -eq "FixInstitutionConfigVersion"
        foreach ($resultObject in $resultObjects) {
            if (![String]::IsNullOrEmpty($resultObject)) {
                if ((Get-ADFSTkAnswer (Get-ADFSTkLanguageText healthUpdateInstitutionConfigVersion))) {
                    Update-ADFSTk
                
                    $resultObject.ResultText = (Get-ADFSTkLanguageText healthFixed) + $resultObject.ResultText
                    $resultObject.ResultValue = [Result]::Pass 

                    $FixedAnything = $true
                }
            }
        }
    }
    #endregion

    #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 Import Metadata Scheduled Task
    #Only if run manually
    if (!$Silent) {
        $resultObject = $healthResults | ? FixID -eq "RegisterScheduledTask"
        if (![String]::IsNullOrEmpty($resultObject)) {
            if ((Get-ADFSTkAnswer (Get-ADFSTkLanguageText healthRegisterScheduledTaskCreateSchedTask -f "Import Metadata"))) {
                Register-ADFSTkScheduledTask
                
                $resultObject.ResultText = (Get-ADFSTkLanguageText healthFixed) + $resultObject.ResultText
                $resultObject.ResultValue = [Result]::Pass 

                $FixedAnything = $true
            }
        }
    }
    #endregion

    #region Add F-Ticks Scheduled Task
    #Only if run manually
    if (!$Silent) {
        $resultObject = $healthResults | ? FixID -eq "RegisterFticksScheduledTask"
        if (![String]::IsNullOrEmpty($resultObject)) {
            if ((Get-ADFSTkAnswer (Get-ADFSTkLanguageText healthRegisterScheduledTaskCreateSchedTask -f "F-Ticks"))) {
                Register-ADFSTkFTicksScheduledTask
                
                $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
        }
    }
    <#
.SYNOPSIS
    Use this cmdlet to check the health of your installation of ADFS Toolkit
.DESCRIPTION
    This cmdlet can check different things on the installation of ADFS Toolkit.
    Every time Sync-ADFSTkAggregates are run a default helath check is run. If an
    error is found that the health check can fix you will be asked if you want to
    do so. If the cmdlet is run with -Silent it will automatically fix errors.
.EXAMPLE
    PS C:\> Get-ADFSTkHealth
    Does a default check which includes checking that all files in the module are
    signed and has not been altered, that the institution configuration files has
    the correct version for the installed version of ADFS Toolkit and that the
    MFA Access Control Policy exists (if the Refeds MFA adapter is installed).
.EXAMPLE
    PS C:\> Get-ADFSTkHealth -HealthCheckMode CriticalOnly
    This excludes the check of signatures. Run this if you have changed any files
    in the module.
.EXAMPLE
    PS C:\> Get-ADFSTkHealth -HealthCheckMode Full
    Run this after upgrade of the ADFS Toolkit. It does a full scan of the installation
    including tests of missing SP's.
#>

}
# SIG # Begin signature block
# MIItNgYJKoZIhvcNAQcCoIItJzCCLSMCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCDY5SZ2ZuWKWUAw
# LsIsCwTo8MrqfgUM9FLbsw1L+Vs906CCJj8wggWNMIIEdaADAgECAhAOmxiO+dAt
# 5+/bUOIIQBhaMA0GCSqGSIb3DQEBDAUAMGUxCzAJBgNVBAYTAlVTMRUwEwYDVQQK
# EwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xJDAiBgNV
# BAMTG0RpZ2lDZXJ0IEFzc3VyZWQgSUQgUm9vdCBDQTAeFw0yMjA4MDEwMDAwMDBa
# Fw0zMTExMDkyMzU5NTlaMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2Vy
# dCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lD
# ZXJ0IFRydXN0ZWQgUm9vdCBHNDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC
# ggIBAL/mkHNo3rvkXUo8MCIwaTPswqclLskhPfKK2FnC4SmnPVirdprNrnsbhA3E
# MB/zG6Q4FutWxpdtHauyefLKEdLkX9YFPFIPUh/GnhWlfr6fqVcWWVVyr2iTcMKy
# unWZanMylNEQRBAu34LzB4TmdDttceItDBvuINXJIB1jKS3O7F5OyJP4IWGbNOsF
# xl7sWxq868nPzaw0QF+xembud8hIqGZXV59UWI4MK7dPpzDZVu7Ke13jrclPXuU1
# 5zHL2pNe3I6PgNq2kZhAkHnDeMe2scS1ahg4AxCN2NQ3pC4FfYj1gj4QkXCrVYJB
# MtfbBHMqbpEBfCFM1LyuGwN1XXhm2ToxRJozQL8I11pJpMLmqaBn3aQnvKFPObUR
# WBf3JFxGj2T3wWmIdph2PVldQnaHiZdpekjw4KISG2aadMreSx7nDmOu5tTvkpI6
# nj3cAORFJYm2mkQZK37AlLTSYW3rM9nF30sEAMx9HJXDj/chsrIRt7t/8tWMcCxB
# YKqxYxhElRp2Yn72gLD76GSmM9GJB+G9t+ZDpBi4pncB4Q+UDCEdslQpJYls5Q5S
# UUd0viastkF13nqsX40/ybzTQRESW+UQUOsxxcpyFiIJ33xMdT9j7CFfxCBRa2+x
# q4aLT8LWRV+dIPyhHsXAj6KxfgommfXkaS+YHS312amyHeUbAgMBAAGjggE6MIIB
# NjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTs1+OC0nFdZEzfLmc/57qYrhwP
# TzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYunpyGd823IDzAOBgNVHQ8BAf8EBAMC
# AYYweQYIKwYBBQUHAQEEbTBrMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdp
# Y2VydC5jb20wQwYIKwYBBQUHMAKGN2h0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNv
# bS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcnQwRQYDVR0fBD4wPDA6oDigNoY0
# aHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENB
# LmNybDARBgNVHSAECjAIMAYGBFUdIAAwDQYJKoZIhvcNAQEMBQADggEBAHCgv0Nc
# Vec4X6CjdBs9thbX979XB72arKGHLOyFXqkauyL4hxppVCLtpIh3bb0aFPQTSnov
# Lbc47/T/gLn4offyct4kvFIDyE7QKt76LVbP+fT3rDB6mouyXtTP0UNEm0Mh65Zy
# oUi0mcudT6cGAxN3J0TU53/oWajwvy8LpunyNDzs9wPHh6jSTEAZNUZqaVSwuKFW
# juyk1T3osdz9HNj0d1pcVIxv76FQPfx2CWiEn2/K2yCNNWAcAgPLILCsWKAOQGPF
# mCLBsln1VWvPJ6tsds5vIy30fnFqI2si/xK4VC0nftg62fC2h5b9W9FcrBjDTZ9z
# twGpn1eqXijiuZQwggXfMIIEx6ADAgECAhBOQOQ3VO3mjAAAAABR05R/MA0GCSqG
# SIb3DQEBCwUAMIG+MQswCQYDVQQGEwJVUzEWMBQGA1UEChMNRW50cnVzdCwgSW5j
# LjEoMCYGA1UECxMfU2VlIHd3dy5lbnRydXN0Lm5ldC9sZWdhbC10ZXJtczE5MDcG
# A1UECxMwKGMpIDIwMDkgRW50cnVzdCwgSW5jLiAtIGZvciBhdXRob3JpemVkIHVz
# ZSBvbmx5MTIwMAYDVQQDEylFbnRydXN0IFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRo
# b3JpdHkgLSBHMjAeFw0yMTA1MDcxNTQzNDVaFw0zMDExMDcxNjEzNDVaMGkxCzAJ
# BgNVBAYTAlVTMRYwFAYDVQQKDA1FbnRydXN0LCBJbmMuMUIwQAYDVQQDDDlFbnRy
# dXN0IENvZGUgU2lnbmluZyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0g
# Q1NCUjEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCngY/3FEW2YkPy
# 2K7TJV5IT1G/xX2fUBw10dZ+YSqUGW0nRqSmGl33VFFqgCLGqGZ1TVSDyV5oG6v2
# W2Swra0gvVTvRmttAudFrnX2joq5Mi6LuHccUk15iF+lOhjJUCyXJy2/2gB9Y3/v
# MuxGh2Pbmp/DWiE2e/mb1cqgbnIs/OHxnnBNCFYVb5Cr+0i6udfBgniFZS5/tcnA
# 4hS3NxFBBuKK4Kj25X62eAUBw2DtTwdBLgoTSeOQm3/dvfqsv2RR0VybtPVc51z/
# O5uloBrXfQmywrf/bhy8yH3m6Sv8crMU6UpVEoScRCV1HfYq8E+lID1oJethl3wP
# 5bY9867DwRG8G47M4EcwXkIAhnHjWKwGymUfe5SmS1dnDH5erXhnW1XjXuvH2OxM
# bobL89z4n4eqclgSD32m+PhCOTs8LOQyTUmM4OEAwjignPqEPkHcblauxhpb9Gdo
# BQHNG7+uh7ydU/Yu6LZr5JnexU+HWKjSZR7IH9Vybu5ZHFc7CXKd18q3kMbNe0WS
# kUIDTH0/yvKquMIOhvMQn0YupGaGaFpoGHApOBGAYGuKQ6NzbOOzazf/5p1nAZKG
# 3y9I0ftQYNVc/iHTAUJj/u9wtBfAj6ju08FLXxLq/f0uDodEYOOp9MIYo+P9zgyE
# Ig3zp3jak/PbOM+5LzPG/wc8Xr5F0wIDAQABo4IBKzCCAScwDgYDVR0PAQH/BAQD
# AgGGMBIGA1UdEwEB/wQIMAYBAf8CAQEwHQYDVR0lBBYwFAYIKwYBBQUHAwMGCCsG
# AQUFBwMIMDsGA1UdIAQ0MDIwMAYEVR0gADAoMCYGCCsGAQUFBwIBFhpodHRwOi8v
# d3d3LmVudHJ1c3QubmV0L3JwYTAzBggrBgEFBQcBAQQnMCUwIwYIKwYBBQUHMAGG
# F2h0dHA6Ly9vY3NwLmVudHJ1c3QubmV0MDAGA1UdHwQpMCcwJaAjoCGGH2h0dHA6
# Ly9jcmwuZW50cnVzdC5uZXQvZzJjYS5jcmwwHQYDVR0OBBYEFIK61j2Xzp/PceiS
# N6/9s7VpNVfPMB8GA1UdIwQYMBaAFGpyJnrQHu995ztpUdRsjZ+QEmarMA0GCSqG
# SIb3DQEBCwUAA4IBAQAfXkEEtoNwJFMsVXMdZTrA7LR7BJheWTgTCaRZlEJeUL9P
# bG4lIJCTWEAN9Rm0Yu4kXsIBWBUCHRAJb6jU+5J+Nzg+LxR9jx1DNmSzZhNfFMyl
# cfdbIUvGl77clfxwfREc0yHd0CQ5KcX+Chqlz3t57jpv3ty/6RHdFoMI0yyNf02o
# FHkvBWFSOOtg8xRofcuyiq3AlFzkJg4sit1Gw87kVlHFVuOFuE2bRXKLB/GK+0m4
# X9HyloFdaVIk8Qgj0tYjD+uL136LwZNr+vFie1jpUJuXbheIDeHGQ5jXgWG2hZ1H
# 7LGerj8gO0Od2KIc4NR8CMKvdgb4YmZ6tvf6yK81MIIGgzCCBGugAwIBAgIQNa+3
# e500H2r8j4RGqzE1KzANBgkqhkiG9w0BAQ0FADBpMQswCQYDVQQGEwJVUzEWMBQG
# A1UECgwNRW50cnVzdCwgSW5jLjFCMEAGA1UEAww5RW50cnVzdCBDb2RlIFNpZ25p
# bmcgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIENTQlIxMB4XDTIxMDUw
# NzE5MTk1MloXDTQwMTIyOTIzNTkwMFowYzELMAkGA1UEBhMCVVMxFjAUBgNVBAoT
# DUVudHJ1c3QsIEluYy4xPDA6BgNVBAMTM0VudHJ1c3QgRXh0ZW5kZWQgVmFsaWRh
# dGlvbiBDb2RlIFNpZ25pbmcgQ0EgLSBFVkNTMjCCAiIwDQYJKoZIhvcNAQEBBQAD
# ggIPADCCAgoCggIBAL69pznJpX3sXWXx9Cuph9DnrRrFGjsYzuGhUY1y+s5YH1y4
# JEIPRtUxl9BKTeObMMm6l6ic/kU2zyeA53u4bsEkt9+ndNyF8qMkWEXMlJQ7AuvE
# jXxG9VxmguOkwdMfrG4MUyMO1Dr62kLxg1RfNTJW8rV4m1cASB6pYWEnDnMDQ7bW
# cJL71IWaMMaz5ppeS+8dKthmqxZG/wvYD6aJSgJRV0E8QThOl8dRMm1njmahXk2f
# NSKv1Wq3f0BfaDXMafrxBfDqhabqMoXLwcHKg2lFSQbcCWy6SWUZjPm3NyeMZJ41
# 4+Xs5wegnahyvG+FOiymFk49nM8I5oL1RH0owL2JrWwv3C94eRHXHHBL3Z0ITF4u
# +o29p91j9n/wUjGEbjrY2VyFRJ5jBmnQhlh4iZuHu1gcpChsxv5pCpwerBFgal7J
# aWUu7UMtafF4tzstNfKqT+If4wFvkEaq1agNBFegtKzjbb2dGyiAJ0bH2qpnlfHR
# h3vHyCXphAyPiTbSvjPhhcAz1aA8GYuvOPLlk4C/xsOre5PEPZ257kV2wNRobzBe
# PLQ2+ddFQuASBoDbpSH85wV6KI20jmB798i1SkesFGaXoFppcjFXa1OEzWG6cwcV
# cDt7AfynP4wtPYeM+wjX5S8Xg36Cq08J8inhflV3ZZQFHVnUCt2TfuMUXeK7AgMB
# AAGjggErMIIBJzASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBTOiU+CUaoV
# ooRiyjEjYdJh+/j+eDAfBgNVHSMEGDAWgBSCutY9l86fz3Hokjev/bO1aTVXzzAz
# BggrBgEFBQcBAQQnMCUwIwYIKwYBBQUHMAGGF2h0dHA6Ly9vY3NwLmVudHJ1c3Qu
# bmV0MDEGA1UdHwQqMCgwJqAkoCKGIGh0dHA6Ly9jcmwuZW50cnVzdC5uZXQvY3Ni
# cjEuY3JsMA4GA1UdDwEB/wQEAwIBhjATBgNVHSUEDDAKBggrBgEFBQcDAzBEBgNV
# HSAEPTA7MDAGBFUdIAAwKDAmBggrBgEFBQcCARYaaHR0cDovL3d3dy5lbnRydXN0
# Lm5ldC9ycGEwBwYFZ4EMAQMwDQYJKoZIhvcNAQENBQADggIBAD4AVLgq849mr2EW
# xFiTZPRBi2RVjRs1M6GbkdirRsqrX7y+fnDk0tcHqJYH14bRVwoI0NB4Tfgq37IE
# 85rh13zwwQB6wUCh34qMt8u0HQFh8piapt24gwXKqSwW3JwtDv6nl+RQqZeVwUsq
# jFHjxALga3w1TVO8S5QTi1MYFl6mCqe4NMFssess5DF9DCzGfOGkVugtdtWyE3Xq
# gwCuAHfGb6k97mMUgVAW/FtPEhkOWw+N6kvOBkyJS64gzI5HpnXWZe4vMOhdNI8f
# gk1cQqbyFExQIJwJonQkXDnYiTKFPK+M5Wqe5gQ6pRP/qh3NR0suAgW0ao/rhU+B
# 7wrbfZ8pj6XCP1I4UkGVO7w+W1QwQiMJY95QjYk1RfqruA+Poq17ehGT8Y8ohHto
# eUdq6GQpTR/0HS9tHsiUhjzTWpl6a3yrNfcrOUtPuT8Wku8pjI2rrAEazHFEOctA
# PiASzghw40f+3IDXCADRC2rqIbV5ZhfpaqpW3c0VeLEDwBStPkcYde0KU0syk83/
# gLGQ1hPl5EF4Iu1BguUO37DOlSFF5osB0xn39CtVrNlWc2MQ4LigbctUlpigmSFR
# BqqmDDorY8t52kO50hLM3o9VeukJ8+Ka0yXBezaS2uDlUmfN4+ZUCqWd1HOj0y9d
# BmSFA3d/YNjCvHTJlZFot7d+YRl1MIIGrjCCBJagAwIBAgIQBzY3tyRUfNhHrP0o
# ZipeWzANBgkqhkiG9w0BAQsFADBiMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGln
# aUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhE
# aWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwHhcNMjIwMzIzMDAwMDAwWhcNMzcwMzIy
# MjM1OTU5WjBjMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4x
# OzA5BgNVBAMTMkRpZ2lDZXJ0IFRydXN0ZWQgRzQgUlNBNDA5NiBTSEEyNTYgVGlt
# ZVN0YW1waW5nIENBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxoY1
# BkmzwT1ySVFVxyUDxPKRN6mXUaHW0oPRnkyibaCwzIP5WvYRoUQVQl+kiPNo+n3z
# nIkLf50fng8zH1ATCyZzlm34V6gCff1DtITaEfFzsbPuK4CEiiIY3+vaPcQXf6sZ
# Kz5C3GeO6lE98NZW1OcoLevTsbV15x8GZY2UKdPZ7Gnf2ZCHRgB720RBidx8ald6
# 8Dd5n12sy+iEZLRS8nZH92GDGd1ftFQLIWhuNyG7QKxfst5Kfc71ORJn7w6lY2zk
# psUdzTYNXNXmG6jBZHRAp8ByxbpOH7G1WE15/tePc5OsLDnipUjW8LAxE6lXKZYn
# LvWHpo9OdhVVJnCYJn+gGkcgQ+NDY4B7dW4nJZCYOjgRs/b2nuY7W+yB3iIU2YIq
# x5K/oN7jPqJz+ucfWmyU8lKVEStYdEAoq3NDzt9KoRxrOMUp88qqlnNCaJ+2RrOd
# OqPVA+C/8KI8ykLcGEh/FDTP0kyr75s9/g64ZCr6dSgkQe1CvwWcZklSUPRR8zZJ
# TYsg0ixXNXkrqPNFYLwjjVj33GHek/45wPmyMKVM1+mYSlg+0wOI/rOP015LdhJR
# k8mMDDtbiiKowSYI+RQQEgN9XyO7ZONj4KbhPvbCdLI/Hgl27KtdRnXiYKNYCQEo
# AA6EVO7O6V3IXjASvUaetdN2udIOa5kM0jO0zbECAwEAAaOCAV0wggFZMBIGA1Ud
# EwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFLoW2W1NhS9zKXaaL3WMaiCPnshvMB8G
# A1UdIwQYMBaAFOzX44LScV1kTN8uZz/nupiuHA9PMA4GA1UdDwEB/wQEAwIBhjAT
# BgNVHSUEDDAKBggrBgEFBQcDCDB3BggrBgEFBQcBAQRrMGkwJAYIKwYBBQUHMAGG
# GGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBBBggrBgEFBQcwAoY1aHR0cDovL2Nh
# Y2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZFJvb3RHNC5jcnQwQwYD
# VR0fBDwwOjA4oDagNIYyaHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0
# VHJ1c3RlZFJvb3RHNC5jcmwwIAYDVR0gBBkwFzAIBgZngQwBBAIwCwYJYIZIAYb9
# bAcBMA0GCSqGSIb3DQEBCwUAA4ICAQB9WY7Ak7ZvmKlEIgF+ZtbYIULhsBguEE0T
# zzBTzr8Y+8dQXeJLKftwig2qKWn8acHPHQfpPmDI2AvlXFvXbYf6hCAlNDFnzbYS
# lm/EUExiHQwIgqgWvalWzxVzjQEiJc6VaT9Hd/tydBTX/6tPiix6q4XNQ1/tYLaq
# T5Fmniye4Iqs5f2MvGQmh2ySvZ180HAKfO+ovHVPulr3qRCyXen/KFSJ8NWKcXZl
# 2szwcqMj+sAngkSumScbqyQeJsG33irr9p6xeZmBo1aGqwpFyd/EjaDnmPv7pp1y
# r8THwcFqcdnGE4AJxLafzYeHJLtPo0m5d2aR8XKc6UsCUqc3fpNTrDsdCEkPlM05
# et3/JWOZJyw9P2un8WbDQc1PtkCbISFA0LcTJM3cHXg65J6t5TRxktcma+Q4c6um
# AU+9Pzt4rUyt+8SVe+0KXzM5h0F4ejjpnOHdI/0dKNPH+ejxmF/7K9h+8kaddSwe
# Jywm228Vex4Ziza4k9Tm8heZWcpw8De/mADfIBZPJ/tgZxahZrrdVcA6KYawmKAr
# 7ZVBtzrVFZgxtGIJDwq9gdkT/r+k0fNX2bwE+oLeMt8EifAAzV3C+dAjfwAL5HYC
# JtnwZXZCpimHCUcr5n8apIUP/JiW9lVUKx+A+sDyDivl1vupL0QVSucTDh3bNzga
# oSv27dZ8/DCCBsIwggSqoAMCAQICEAVEr/OUnQg5pr/bP1/lYRYwDQYJKoZIhvcN
# AQELBQAwYzELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMTsw
# OQYDVQQDEzJEaWdpQ2VydCBUcnVzdGVkIEc0IFJTQTQwOTYgU0hBMjU2IFRpbWVT
# dGFtcGluZyBDQTAeFw0yMzA3MTQwMDAwMDBaFw0zNDEwMTMyMzU5NTlaMEgxCzAJ
# BgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjEgMB4GA1UEAxMXRGln
# aUNlcnQgVGltZXN0YW1wIDIwMjMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK
# AoICAQCjU0WHHYOOW6w+VLMj4M+f1+XS512hDgncL0ijl3o7Kpxn3GIVWMGpkxGn
# zaqyat0QKYoeYmNp01icNXG/OpfrlFCPHCDqx5o7L5Zm42nnaf5bw9YrIBzBl5S0
# pVCB8s/LB6YwaMqDQtr8fwkklKSCGtpqutg7yl3eGRiF+0XqDWFsnf5xXsQGmjzw
# xS55DxtmUuPI1j5f2kPThPXQx/ZILV5FdZZ1/t0QoRuDwbjmUpW1R9d4KTlr4HhZ
# l+NEK0rVlc7vCBfqgmRN/yPjyobutKQhZHDr1eWg2mOzLukF7qr2JPUdvJscsrdf
# 3/Dudn0xmWVHVZ1KJC+sK5e+n+T9e3M+Mu5SNPvUu+vUoCw0m+PebmQZBzcBkQ8c
# tVHNqkxmg4hoYru8QRt4GW3k2Q/gWEH72LEs4VGvtK0VBhTqYggT02kefGRNnQ/f
# ztFejKqrUBXJs8q818Q7aESjpTtC/XN97t0K/3k0EH6mXApYTAA+hWl1x4Nk1nXN
# jxJ2VqUk+tfEayG66B80mC866msBsPf7Kobse1I4qZgJoXGybHGvPrhvltXhEBP+
# YUcKjP7wtsfVx95sJPC/QoLKoHE9nJKTBLRpcCcNT7e1NtHJXwikcKPsCvERLmTg
# yyIryvEoEyFJUX4GZtM7vvrrkTjYUQfKlLfiUKHzOtOKg8tAewIDAQABo4IBizCC
# AYcwDgYDVR0PAQH/BAQDAgeAMAwGA1UdEwEB/wQCMAAwFgYDVR0lAQH/BAwwCgYI
# KwYBBQUHAwgwIAYDVR0gBBkwFzAIBgZngQwBBAIwCwYJYIZIAYb9bAcBMB8GA1Ud
# IwQYMBaAFLoW2W1NhS9zKXaaL3WMaiCPnshvMB0GA1UdDgQWBBSltu8T5+/N0GSh
# 1VapZTGj3tXjSTBaBgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsMy5kaWdpY2Vy
# dC5jb20vRGlnaUNlcnRUcnVzdGVkRzRSU0E0MDk2U0hBMjU2VGltZVN0YW1waW5n
# Q0EuY3JsMIGQBggrBgEFBQcBAQSBgzCBgDAkBggrBgEFBQcwAYYYaHR0cDovL29j
# c3AuZGlnaWNlcnQuY29tMFgGCCsGAQUFBzAChkxodHRwOi8vY2FjZXJ0cy5kaWdp
# Y2VydC5jb20vRGlnaUNlcnRUcnVzdGVkRzRSU0E0MDk2U0hBMjU2VGltZVN0YW1w
# aW5nQ0EuY3J0MA0GCSqGSIb3DQEBCwUAA4ICAQCBGtbeoKm1mBe8cI1PijxonNgl
# /8ss5M3qXSKS7IwiAqm4z4Co2efjxe0mgopxLxjdTrbebNfhYJwr7e09SI64a7p8
# Xb3CYTdoSXej65CqEtcnhfOOHpLawkA4n13IoC4leCWdKgV6hCmYtld5j9smViuw
# 86e9NwzYmHZPVrlSwradOKmB521BXIxp0bkrxMZ7z5z6eOKTGnaiaXXTUOREEr4g
# DZ6pRND45Ul3CFohxbTPmJUaVLq5vMFpGbrPFvKDNzRusEEm3d5al08zjdSNd311
# RaGlWCZqA0Xe2VC1UIyvVr1MxeFGxSjTredDAHDezJieGYkD6tSRN+9NUvPJYCHE
# Vkft2hFLjDLDiOZY4rbbPvlfsELWj+MXkdGqwFXjhr+sJyxB0JozSqg21Llyln6X
# eThIX8rC3D0y33XWNmdaifj2p8flTzU8AL2+nCpseQHc2kTmOt44OwdeOVj0fHMx
# VaCAEcsUDH6uvP6k63llqmjWIso765qCNVcoFstp8jKastLYOrixRoZruhf9xHds
# FWyuq69zOuhJRrfVf8y2OMDY7Bz1tqG4QyzfTkx9HmhwwHcK1ALgXGC7KP845VJa
# 1qwXIiNO9OzTF/tQa/8Hdx9xl0RBybhG02wyfFgvZ0dl5Rtztpn5aywGRu9BHvDw
# X+Db2a2QgESvgBBBijCCBsgwggSwoAMCAQICEDVP8/CYmCMy0wHfstCzixQwDQYJ
# KoZIhvcNAQELBQAwYzELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUVudHJ1c3QsIElu
# Yy4xPDA6BgNVBAMTM0VudHJ1c3QgRXh0ZW5kZWQgVmFsaWRhdGlvbiBDb2RlIFNp
# Z25pbmcgQ0EgLSBFVkNTMjAeFw0yNDAzMjIxNDAyMzdaFw0yNTAzMjkxNDAyMzZa
# MIGjMQswCQYDVQQGEwJDQTEQMA4GA1UECBMHT250YXJpbzEPMA0GA1UEBxMGT3R0
# YXdhMRMwEQYLKwYBBAGCNzwCAQMTAkNBMRQwEgYDVQQKEwtDQU5BUklFIElOQzEd
# MBsGA1UEDxMUUHJpdmF0ZSBPcmdhbml6YXRpb24xETAPBgNVBAUTCDI5MDIwOC03
# MRQwEgYDVQQDEwtDQU5BUklFIElOQzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCC
# AgoCggIBAIbar6/W1oMcwPbrTZBOR7XAFQ43Qt/AyPGUT42dOQnfcqWqPlZKN2mr
# euYTFdaAknd2pJZC/3b16Yu2O0ElPuqUBwF0CrKapWGjQ5eUvC65Dn5c0hXmPzIV
# 8snCTqREm8nGl5BZmlvqMc7MxaDrMh88kHVxNpHevYP3729AD7AoBKmiwglAOtZn
# XPNVllH5qx4ZHYKt9dVCbEz0nGjzs1LobknyvRV8onsmGaRDhIYvYb27sttPmmKS
# C6yqnwNlZV00d3KbUt1jOo9PGlS//4ZIw60rm+atIHWvwCVNXLj8WNYOs29xb3Ie
# M1a0JO/QIyQ6n5TZRxns8ateQkuEfsNU9Z5/K9GcyXuiZinGsBjVxvwSQoj2YhXt
# nlz/ZRzKabsH1ZtQ3ygedVpt4d1TGJbU3uJI2PrynXqRYgxTYoJ0F38EBvbgY33T
# iC/fzemTNtqkyYJHAzfE7ksUdYQq6GMSDEy5nZA+vXvTB59aSk63S+mHNTDBRPYB
# Dwg5nn10NUh+0TGUu+MKwoDCq4Qy8w8VdooxS10RHzYflMp4/b9FxhXfWd9/qtFU
# QaZtjEvgV53s4GpVtsa52ij5L9VSu+ta6ZSZAarxiH/5/ni4bn1n+Q/n7bUfTHk8
# y8GT3SoXzKwOZXsSeUVq4my7beGwn13O2efES/cdx1EzxmHZl18NAgMBAAGjggE1
# MIIBMTAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBQSgPtIskRFyN7+qGdwcp+8w1kG
# DDAfBgNVHSMEGDAWgBTOiU+CUaoVooRiyjEjYdJh+/j+eDBnBggrBgEFBQcBAQRb
# MFkwIwYIKwYBBQUHMAGGF2h0dHA6Ly9vY3NwLmVudHJ1c3QubmV0MDIGCCsGAQUF
# BzAChiZodHRwOi8vYWlhLmVudHJ1c3QubmV0L2V2Y3MyLWNoYWluLnA3YzAxBgNV
# HR8EKjAoMCagJKAihiBodHRwOi8vY3JsLmVudHJ1c3QubmV0L2V2Y3MyLmNybDAO
# BgNVHQ8BAf8EBAMCB4AwEwYDVR0lBAwwCgYIKwYBBQUHAwMwIAYDVR0gBBkwFzAH
# BgVngQwBAzAMBgpghkgBhvpsCgECMA0GCSqGSIb3DQEBCwUAA4ICAQBd5heW6V8s
# 8owim1XLvy2aMSMHXB6gjP7suiiyx4WGKZAZn1tY0TWexrjZE66GQI0IxdOrMQQf
# IXfaTLPy3g6qBNf5IazlcanmgKT45dpGVB0ixKmoOnxBCZKtESJvmmBuXe5/QyH1
# /EECx1y8SA1mO6j9uUKhY+/ZG0jHOYQXTuT/X1e2wfEwmJCGKC/OGiLi2iLjRWWH
# EwEnYXw13S0cBqcXhuVpjL+Ce9W/N833TFQ8AA3rjN2ZWuti9RMoO5rabeSXbjGX
# rDWHi5y6MbZL3+QYjHPKSTySjIYjENICf6rFcvCdHnrmNbLP3fWd5plzAObhiJSw
# gXyiBoMOcP+6ZtNMDR/8U3TwUmWbwPH5ulfgVvmyPhchq1cRksxRj6uz48dHSksm
# Dx+pZHMSyDj5yITRTwZBa9Q41+giSxPGRxUB/7HyX5slQGZdW6Y4SQHQ06SGvCkr
# M3dJQ4aGYbw3NBtcwUmhA79pYOu3YhrJmpUXUx5mVQDqnTUrpn6P0RhW9tWopBYS
# lXRJy2qF0UvguDm0jqhSMmc0wnLl725zGLbC2/lG1NLIvGtDRsGAq5zMbAVpDRUM
# m4O04R+WQeCssBMWFT1HR7PmJyfvUo4cQCkpeZRRS2DL5/AlgBINqTiv+lP2nXKw
# mkpg6lEXvNAaRgMSGgtnMNkDbjpU8ldKsTGCBk0wggZJAgEBMHcwYzELMAkGA1UE
# BhMCVVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xPDA6BgNVBAMTM0VudHJ1c3Qg
# RXh0ZW5kZWQgVmFsaWRhdGlvbiBDb2RlIFNpZ25pbmcgQ0EgLSBFVkNTMgIQNU/z
# 8JiYIzLTAd+y0LOLFDANBglghkgBZQMEAgEFAKCBhDAYBgorBgEEAYI3AgEMMQow
# CKACgAChAoAAMBkGCSqGSIb3DQEJAzEMBgorBgEEAYI3AgEEMBwGCisGAQQBgjcC
# AQsxDjAMBgorBgEEAYI3AgEVMC8GCSqGSIb3DQEJBDEiBCBUyCBuTL6lBR9Ys9yc
# zN/u7h6/32IJKRV7qanM8jE7TzANBgkqhkiG9w0BAQEFAASCAgA/VG0Lwmb86DNW
# VnXTjHb3O22s/BT00wr6Q64ihzsGuW6WA6bbrOJhe4/xL6v27u3fkLJr6mmCLqRy
# H+QD2SKHVxyHqK3c72tRnYTM3/Be4A8yHgak2Z3PG6cTsj1/L9Dy46vgKp4vKR6u
# vvDfS1/sH3WKrtlml6poaaNPEi1qCVjkaRVp7G0hpl1Q/seQQ62epPeF/3qnUaMa
# jH64d5hsatU+aeOL2QNSE5GXsNDEytYMznyYrkLW6J9ew+FrdAgAGY7ZvZ4QdoOd
# Sd37DzqzRnOXlYMqfVHNfaipGeeMLQR4mHnUv8e0D+A3k6SU0LQF1YvM0Bf8QySk
# CkZ8Ea0niVldenpYxpLVKm20hnboxJBG6vHb3HGCbJJRqbdP27rIUPIGMNCglhy/
# X7pF88qzqWRXhTHyGhOqajConDbZ/Y4uZg7Ngi0uaxXUP0aXpC6HPe38vpH2KZt3
# kg7FEFgnC+Leguj+buaBjMZHGaTHqFtStFU4uuIYYXq9dDg0BM8EJ9baKL98dFTh
# q0I/by80msBXBJNX0UA+BK66vHKr0g833oyzhXMCBvmVSBI8JRZsDVKLAmWkIGiA
# orMNbjeM9CuS0DKSlBz4gYvYhjNddWTqwl1V7/KFD4B528aT1rR1A5v8SB5XKT9I
# AUpUQvpi4PP+Toi2GUzkGmlNqxoWMqGCAyAwggMcBgkqhkiG9w0BCQYxggMNMIID
# CQIBATB3MGMxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjE7
# MDkGA1UEAxMyRGlnaUNlcnQgVHJ1c3RlZCBHNCBSU0E0MDk2IFNIQTI1NiBUaW1l
# U3RhbXBpbmcgQ0ECEAVEr/OUnQg5pr/bP1/lYRYwDQYJYIZIAWUDBAIBBQCgaTAY
# BgkqhkiG9w0BCQMxCwYJKoZIhvcNAQcBMBwGCSqGSIb3DQEJBTEPFw0yNDA4MjEx
# NTExNTZaMC8GCSqGSIb3DQEJBDEiBCDBPoH58NJoFAXpdZR+GrelmOR731xPmtkF
# rD7tkSiIOjANBgkqhkiG9w0BAQEFAASCAgBbcUYyrDWoIgwVxfi55/GdcLb2YBdh
# ds4p3Bu+ImSxWJI8dhdK5fDUwhSUrnlokpAbqrc3Tyclp5/rWv6xhH+c7/nWGf7q
# pGmfSjXW9D0zIPI6Ci1XIdAB1gT71DWIrH1poWoXxW7sQf3nxG1muxDWmgt4A9ua
# c1Gnxz8zNNwYX1fVmxip7IpQM13mUlp0MlY0lFomjNs1IRZcWvlj7JCvZt8JyuVy
# CIhac/XpD5wD8It1ftcqVBxcTZ9Q2Y29EX031Jt4A/7l2qVEHOjHbGN1Iy7cPoTb
# z45gu8B46M/edUR6CUKm5Uc6iuugX97YIzXVuGEoqCJihggudRYiHr4IBZwkKz7i
# tVZYutAjc5UsYEM9QNB74Sp3Fw9DnI+YFpUJarcaoawecJ4HBlQiKi80rjhCxjdb
# g3aYFUQKAPg8ePtVkn9k8AIjZdykiVqQhA1AA1M2H8kzHjgZOBjlA+snvIDxWtHR
# vHKRvYnOcCeSp/SJ0kxu9TrOUPeFSdWtR0MMJdZQ8+17v0EXifa8ZF9Jm5oPC3tA
# x3Ilhebr07XLYg2SMfNVTK/zR4QxEtoGUadbAUlK8SlYg1294Wt3KF2cE46GOCo2
# 4mcfuUCEQuYrTc14KhKfiEEe/T3SNgcqf+xUkjxI+8rKAYz2lAUoKWlvq07/Cwpj
# +WpCCxsH3jvL/g==
# SIG # End signature block