Public/Invoke/Invoke-SystemRepair.ps1

function Invoke-SystemRepair {
    <#
    .SYNOPSIS
    Invokes system repair operations on a local or remote computer.
 
    .DESCRIPTION
    Performs various system repair operations including DISM RestoreHealth,
    StartComponentCleanup, ResetBase, SFC /scannow, and Windows Update component
    reset.
 
    .PARAMETER RestoreHealth
    Run DISM RestoreHealth operation.
 
    .PARAMETER StartComponentCleanup
    Run DISM StartComponentCleanup operation.
 
    .PARAMETER ResetBase
    Run DISM ResetBase operation.
 
    .PARAMETER SfcScannow
    Run SFC /scannow operation.
 
    .PARAMETER ResetUpdateComponents
    Reset Windows Update components.
 
    .PARAMETER ComputerName
    Target computer name for remote execution.
 
    .PARAMETER Local
    Run operations locally instead of remotely.
 
    .PARAMETER Credential
    Credentials for remote session.
 
    .PARAMETER UseCredSSP
    Uses CredSSP authentication for WSMan remoting so remote DISM can access
    delegated network resources such as UNC repair sources.
 
    .PARAMETER OperationTimeoutMinutes
    Maximum minutes to wait for each DISM/SFC operation.
 
    .PARAMETER WaitPollSeconds
    Poll interval used by Wait-TerminalState during local execution.
 
    .PARAMETER WaitHeartbeatSeconds
    Heartbeat interval used by Wait-TerminalState during local execution.
 
    .PARAMETER RepairSource
    Optional DISM repair source path (for example, WIM/ESD or SxS folder).
 
    .PARAMETER RepairSourceIndex
    Optional image index used when RepairSource points to a WIM/ESD file and an
    explicit index is not already provided in the source string.
 
    .PARAMETER LimitAccess
    When set, DISM does not contact Windows Update and uses only local source content.
    #>

    [CmdletBinding(SupportsShouldProcess = $true)]
    param(
        [switch]$RestoreHealth,
        [switch]$StartComponentCleanup,
        [switch]$ResetBase,
        [switch]$SfcScannow,
        [switch]$ResetUpdateComponents,
        [string]$ComputerName,
        [switch]$Local,
        [pscredential]$Credential,
        [switch]$UseCredSSP,
        [string]$RepairSource,
        [ValidateRange(1, 999)]
        [int]$RepairSourceIndex = 1,
        [switch]$LimitAccess,
        [ValidateRange(1, 480)]
        [int]$OperationTimeoutMinutes = 60,
        [ValidateRange(1, 300)]
        [int]$WaitPollSeconds = 5,
        [ValidateRange(0, 3600)]
        [int]$WaitHeartbeatSeconds = 300
    )

    if (-not ($RestoreHealth -or $StartComponentCleanup -or $ResetBase -or $SfcScannow -or $ResetUpdateComponents)) {
        Write-Log -Level Warn -Message "No operations specified. Choose at least one operation to run."
        return
    }

    Initialize-TechToolboxRuntime

    function Get-VersionTokenFromText {
        param([string]$Text)

        if ([string]::IsNullOrWhiteSpace($Text)) {
            return $null
        }

        $m = [regex]::Match($Text, '(?i)\b(1\d|2\d)H[12]\b')
        if (-not $m.Success) {
            return $null
        }

        return $m.Value.ToUpperInvariant()
    }

    function Get-RemoteWindowsVersionInfo {
        [CmdletBinding()]
        param([Parameter(Mandatory)][System.Management.Automation.Runspaces.PSSession]$Session)

        Invoke-Command -Session $Session -ScriptBlock {
            $cv = Get-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion' -ErrorAction Stop

            $displayVersion = [string]$cv.DisplayVersion
            $releaseId = [string]$cv.ReleaseId
            $productName = [string]$cv.ProductName

            $text = @($displayVersion, $releaseId, $productName) -join ' '
            $tokenMatch = [regex]::Match($text, '(?i)\b(1\d|2\d)H[12]\b')

            [pscustomobject]@{
                DisplayVersion = $displayVersion
                ReleaseId = $releaseId
                ProductName = $productName
                VersionToken = if ($tokenMatch.Success) { $tokenMatch.Value.ToUpperInvariant() } else { $null }
            }
        }
    }

    $repair = $script:cfg.settings.systemRepair
    $runRemoteDefault = $repair.runRemote ?? $true
    $retryRestoreHealthWithoutSource = $false
    $usingDefaultRepairSource = $false

    if (-not $PSBoundParameters.ContainsKey('UseCredSSP') -and $repair.ContainsKey('useCredSSPByDefault')) {
        $UseCredSSP = [bool]$repair['useCredSSPByDefault']
    }

    if (-not $PSBoundParameters.ContainsKey('LimitAccess') -and $repair.ContainsKey('limitAccessByDefault')) {
        $LimitAccess = [bool]$repair['limitAccessByDefault']
    }

    if ($repair.ContainsKey('retryWithoutSourceOnNotFound')) {
        $retryRestoreHealthWithoutSource = [bool]$repair['retryWithoutSourceOnNotFound']
    }

    $targetComputer = $ComputerName
    if (-not $Local) {
        if (-not $targetComputer -and $repair.ContainsKey("defaultComputerName")) {
            $targetComputer = $repair["defaultComputerName"]
        }
    }

    $runRemoteEffective =
    -not $Local -and
    -not [string]::IsNullOrWhiteSpace($targetComputer) -and
    $runRemoteDefault

    if ($runRemoteEffective -and -not $PSBoundParameters.ContainsKey('RepairSource') -and $repair.ContainsKey('defaultRepairSource')) {
        $RepairSource = [string]$repair['defaultRepairSource']
        $usingDefaultRepairSource = -not [string]::IsNullOrWhiteSpace($RepairSource)
    }

    if (-not $PSBoundParameters.ContainsKey('RepairSourceIndex') -and $repair.ContainsKey('defaultRepairSourceIndex')) {
        $RepairSourceIndex = [int]$repair['defaultRepairSourceIndex']
    }

    $targetLabel = if ($runRemoteEffective) { "remote host $targetComputer" } else { "local machine" }

    Write-Log -Level Info -Message ("Preparing system repair operations on {0}." -f $targetLabel)

    $ops = @()
    if ($RestoreHealth) { $ops += "DISM RestoreHealth" }
    if ($StartComponentCleanup) { $ops += "DISM StartComponentCleanup" }
    if ($ResetBase) { $ops += "DISM ResetBase" }
    if ($SfcScannow) { $ops += "SFC /scannow" }
    if ($ResetUpdateComponents) { $ops += "Reset Windows Update Components" }

    $operationDesc = $ops -join ", "

    if (-not $PSCmdlet.ShouldProcess($targetLabel, "Run: $operationDesc")) { return }

    if ($runRemoteEffective) {
        Write-Log -Level Info -Message ("Executing repair operations remotely on [{0}]." -f $targetComputer)

        $moduleRoot = Get-ModuleRoot
        $workerLocal = Join-Path $moduleRoot 'Workers\Invoke-SystemRepair.Worker.ps1'
        $workerRemote = Join-Path $moduleRoot 'Workers\Invoke-SystemRepair.Worker.ps1'

        # build helper list
        $helperLibs = @(
            Join-Path $moduleRoot 'Private\Logging\Write-Log.ps1'
            Join-Path $moduleRoot 'Private\System\Utilities\ReusableHelpers\WaitingHeartbeatScripts\Wait-TerminalState.ps1'
            Join-Path $moduleRoot 'Private\System\Utilities\ReusableHelpers\WaitingHeartbeatScripts\Get-DotPulse.ps1'
        )

        $workerFiles = @(
            Join-Path $moduleRoot 'Workers\Invoke-SystemRepair.Worker.ps1'
            Join-Path $moduleRoot 'Workers\Reset-WindowsUpdateComponents.Worker.ps1'
        )

        $pkg = New-HelpersPackage -HelperLibs $helperLibs -WorkerFiles $workerFiles

        $session = $null
        try {
            $session = Start-NewPSRemoteSession -ComputerName $targetComputer -Credential $Credential -UseCredSSP:$UseCredSSP

            if ($usingDefaultRepairSource -and -not [string]::IsNullOrWhiteSpace($RepairSource)) {
                $sourceVersionToken = Get-VersionTokenFromText -Text $RepairSource

                if (-not [string]::IsNullOrWhiteSpace($sourceVersionToken)) {
                    $remoteVersionInfo = Get-RemoteWindowsVersionInfo -Session $session
                    $remoteVersionToken = [string]$remoteVersionInfo.VersionToken

                    if (-not [string]::IsNullOrWhiteSpace($remoteVersionToken) -and $sourceVersionToken -ne $remoteVersionToken) {
                        Write-Log -Level Warn -Message (
                            "Skipping defaultRepairSource because source version [{0}] does not match remote host version [{1}] on [{2}]." -f
                            $sourceVersionToken,
                            $remoteVersionToken,
                            $targetComputer
                        )
                        $RepairSource = $null
                    }
                    else {
                        Write-Log -Level Info -Message (
                            "defaultRepairSource version check passed on [{0}] (source={1}, host={2})." -f
                            $targetComputer,
                            $sourceVersionToken,
                            ($(if ([string]::IsNullOrWhiteSpace($remoteVersionToken)) { 'unknown' } else { $remoteVersionToken }))
                        )
                    }
                }
            }

            $result = Invoke-RemoteWorker `
                -Session $session `
                -HelpersZip $pkg.ZipPath `
                -HelpersZipHash $pkg.ZipHash `
                -WorkerRemotePath $workerRemote `
                -WorkerLocalPath $workerLocal `
                -EntryPoint 'Invoke-SystemRepairCore' `
                -InformationAction Continue `
                -EntryParameters @{
                RestoreHealth           = $RestoreHealth
                StartComponentCleanup   = $StartComponentCleanup
                ResetBase               = $ResetBase
                SfcScannow              = $SfcScannow
                ResetUpdateComponents   = $ResetUpdateComponents
                RepairSource            = $RepairSource
                RepairSourceIndex       = $RepairSourceIndex
                RetryWithoutSourceOnNotFound = $retryRestoreHealthWithoutSource
                LimitAccess             = $LimitAccess
                OperationTimeoutMinutes = $OperationTimeoutMinutes
                WaitPollSeconds         = $WaitPollSeconds
                WaitHeartbeatSeconds    = $WaitHeartbeatSeconds
            }
        }
        catch {
            $err = $_
            $msg = $err.Exception.Message
            $details = $err.ScriptStackTrace
            if ($details) {
                Write-Log -Level Error -Message ("Invoke-SystemRepair remote failed on {0}: {1}`n{2}" -f $targetComputer, $msg, $details)
            }
            else {
                Write-Log -Level Error -Message ("Invoke-SystemRepair remote failed on {0}: {1}" -f $targetComputer, $msg)
            }
            return
        }
        finally {
            if ($session) { Remove-PSSession -Session $session -ErrorAction SilentlyContinue }
        }

        $failedOps = @()
        foreach ($name in @('RestoreHealthResult', 'StartComponentCleanup', 'ResetBaseResult', 'SfcResult', 'ResetWUResult')) {
            $op = $result.$name
            if ($null -ne $op -and ($op.PSObject.Properties.Name -contains 'Success') -and -not [bool]$op.Success) {
                $failedOps += $name
            }
        }

        if ($failedOps.Count -gt 0) {
            Write-Log -Level Warn -Message ("System repair finished on {0} with failures: {1}." -f $targetLabel, ($failedOps -join ', '))
        }
        else {
            Write-Log -Level Ok -Message ("System repair operations completed on {0}." -f $targetLabel)
        }
        return $result
    }
    else {
        $localParams = @{}
        if ($RestoreHealth) { $localParams.RestoreHealth = $true }
        if ($StartComponentCleanup) { $localParams.StartComponentCleanup = $true }
        if ($ResetBase) { $localParams.ResetBase = $true }
        if ($SfcScannow) { $localParams.SfcScannow = $true }
        if ($ResetUpdateComponents) { $localParams.ResetUpdateComponents = $true }
        if (-not [string]::IsNullOrWhiteSpace($RepairSource)) { $localParams.RepairSource = $RepairSource }
        $localParams.RepairSourceIndex = $RepairSourceIndex
        $localParams.RetryWithoutSourceOnNotFound = $retryRestoreHealthWithoutSource
        if ($LimitAccess) { $localParams.LimitAccess = $true }
        $localParams.OperationTimeoutMinutes = $OperationTimeoutMinutes
        $localParams.WaitPollSeconds = $WaitPollSeconds
        $localParams.WaitHeartbeatSeconds = $WaitHeartbeatSeconds

        $result = Invoke-SystemRepairLocal @localParams

        $failedOps = @()
        foreach ($name in @('RestoreHealthResult', 'StartComponentCleanup', 'ResetBaseResult', 'SfcResult', 'ResetWUResult')) {
            $op = $result.$name
            if ($null -ne $op -and ($op.PSObject.Properties.Name -contains 'Success') -and -not [bool]$op.Success) {
                $failedOps += $name
            }
        }

        if ($failedOps.Count -gt 0) {
            Write-Log -Level Warn -Message ("System repair finished on {0} with failures: {1}." -f $targetLabel, ($failedOps -join ', '))
        }
        else {
            Write-Log -Level Ok -Message ("System repair operations completed on {0}." -f $targetLabel)
        }
        return $result
    }
}

# SIG # Begin signature block
# MIIfAgYJKoZIhvcNAQcCoIIe8zCCHu8CAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCS+8fr9OwTW7cm
# bPFDMAlN6WfFlrxLbTXLpuYDJ9/SDaCCGEowggUMMIIC9KADAgECAhAR+U4xG7FH
# qkyqS9NIt7l5MA0GCSqGSIb3DQEBCwUAMB4xHDAaBgNVBAMME1ZBRFRFSyBDb2Rl
# IFNpZ25pbmcwHhcNMjUxMjE5MTk1NDIxWhcNMjYxMjE5MjAwNDIxWjAeMRwwGgYD
# VQQDDBNWQURURUsgQ29kZSBTaWduaW5nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A
# MIICCgKCAgEA3pzzZIUEY92GDldMWuzvbLeivHOuMupgpwbezoG5v90KeuN03S5d
# nM/eom/PcIz08+fGZF04ueuCS6b48q1qFnylwg/C/TkcVRo0WFcKoFGT8yGxdfXi
# caHtapZfbSRh73r7qR7w0CioVveNBVgfMsTgE0WKcuwxemvIe/ptmkfzwAiw/IAC
# Ib0E0BjiX4PySbwWy/QKy/qMXYY19xpRItVTKNBtXzADUtzPzUcFqJU83vM2gZFs
# Or0MhPvM7xEVkOWZFBAWAubbMCJ3rmwyVv9keVDJChhCeLSz2XR11VGDOEA2OO90
# Y30WfY9aOI2sCfQcKMeJ9ypkHl0xORdhUwZ3Wz48d3yJDXGkduPm2vl05RvnA4T6
# 29HVZTmMdvP2475/8nLxCte9IB7TobAOGl6P1NuwplAMKM8qyZh62Br23vcx1fXZ
# TJlKCxBFx1nTa6VlIJk+UbM4ZPm954peB/fIqEacm8LkZ0cPwmLE5ckW7hfK4Trs
# o+RaudU1sKeA+FvpOWgsPccVRWcEYyGkwbyTB3xrIBXA+YckbANZ0XL7fv7x29hn
# gXbZipGu3DnTISiFB43V4MhNDKZYfbWdxze0SwLe8KzIaKnwlwRgvXDMwXgk99Mi
# EbYa3DvA/5ZWikLW9PxBFD7Vdr8ZiG/tRC9I2Y6fnb+PVoZKc/2xsW0CAwEAAaNG
# MEQwDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMDMB0GA1UdDgQW
# BBRfYLVE8caSc990rnrIHUjoB7X/KjANBgkqhkiG9w0BAQsFAAOCAgEAiGB2Wmk3
# QBtd1LcynmxHzmu+X4Y5DIpMMNC2ahsqZtPUVcGqmb5IFbVuAdQphL6PSrDjaAR8
# 1S8uTfUnMa119LmIb7di7TlH2F5K3530h5x8JMj5EErl0xmZyJtSg7BTiBA/UrMz
# 6WCf8wWIG2/4NbV6aAyFwIojfAcKoO8ng44Dal/oLGzLO3FDE5AWhcda/FbqVjSJ
# 1zMfiW8odd4LgbmoyEI024KkwOkkPyJQ2Ugn6HMqlFLazAmBBpyS7wxdaAGrl18n
# 6bS7QuAwCd9hitdMMitG8YyWL6tKeRSbuTP5E+ASbu0Ga8/fxRO5ZSQhO6/5ro1j
# PGe1/Kr49Uyuf9VSCZdNIZAyjjeVAoxmV0IfxQLKz6VOG0kGDYkFGskvllIpQbQg
# WLuPLJxoskJsoJllk7MjZJwrpr08+3FQnLkRuisjDOc3l4VxFUsUe4fnJhMUONXT
# Sk7vdspgxirNbLmXU4yYWdsizz3nMUR0zebUW29A+HYme16hzrMPOeyoQjy4I5XX
# 3wXAFdworfPEr/ozDFrdXKgbLwZopymKbBwv6wtT7+1zVhJXr+jGVQ1TWr6R+8ea
# tIOFnY7HqGaxe5XB7HzOwJKdj+bpHAfXft1vUoiKr16VajLigcYCG8MdwC3sngO3
# JDyv2V+YMfsYBmItMGBwvizlQ6557NbK95EwggWNMIIEdaADAgECAhAOmxiO+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
# twGpn1eqXijiuZQwgga0MIIEnKADAgECAhANx6xXBf8hmS5AQyIMOkmGMA0GCSqG
# SIb3DQEBCwUAMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMx
# GTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IFRy
# dXN0ZWQgUm9vdCBHNDAeFw0yNTA1MDcwMDAwMDBaFw0zODAxMTQyMzU5NTlaMGkx
# CzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjFBMD8GA1UEAxM4
# RGlnaUNlcnQgVHJ1c3RlZCBHNCBUaW1lU3RhbXBpbmcgUlNBNDA5NiBTSEEyNTYg
# MjAyNSBDQTEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC0eDHTCphB
# cr48RsAcrHXbo0ZodLRRF51NrY0NlLWZloMsVO1DahGPNRcybEKq+RuwOnPhof6p
# vF4uGjwjqNjfEvUi6wuim5bap+0lgloM2zX4kftn5B1IpYzTqpyFQ/4Bt0mAxAHe
# HYNnQxqXmRinvuNgxVBdJkf77S2uPoCj7GH8BLuxBG5AvftBdsOECS1UkxBvMgEd
# gkFiDNYiOTx4OtiFcMSkqTtF2hfQz3zQSku2Ws3IfDReb6e3mmdglTcaarps0wjU
# jsZvkgFkriK9tUKJm/s80FiocSk1VYLZlDwFt+cVFBURJg6zMUjZa/zbCclF83bR
# VFLeGkuAhHiGPMvSGmhgaTzVyhYn4p0+8y9oHRaQT/aofEnS5xLrfxnGpTXiUOeS
# LsJygoLPp66bkDX1ZlAeSpQl92QOMeRxykvq6gbylsXQskBBBnGy3tW/AMOMCZIV
# NSaz7BX8VtYGqLt9MmeOreGPRdtBx3yGOP+rx3rKWDEJlIqLXvJWnY0v5ydPpOjL
# 6s36czwzsucuoKs7Yk/ehb//Wx+5kMqIMRvUBDx6z1ev+7psNOdgJMoiwOrUG2Zd
# SoQbU2rMkpLiQ6bGRinZbI4OLu9BMIFm1UUl9VnePs6BaaeEWvjJSjNm2qA+sdFU
# eEY0qVjPKOWug/G6X5uAiynM7Bu2ayBjUwIDAQABo4IBXTCCAVkwEgYDVR0TAQH/
# BAgwBgEB/wIBADAdBgNVHQ4EFgQU729TSunkBnx6yuKQVvYv1Ensy04wHwYDVR0j
# BBgwFoAU7NfjgtJxXWRM3y5nP+e6mK4cD08wDgYDVR0PAQH/BAQDAgGGMBMGA1Ud
# JQQMMAoGCCsGAQUFBwMIMHcGCCsGAQUFBwEBBGswaTAkBggrBgEFBQcwAYYYaHR0
# cDovL29jc3AuZGlnaWNlcnQuY29tMEEGCCsGAQUFBzAChjVodHRwOi8vY2FjZXJ0
# cy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkUm9vdEc0LmNydDBDBgNVHR8E
# PDA6MDigNqA0hjJodHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVz
# dGVkUm9vdEc0LmNybDAgBgNVHSAEGTAXMAgGBmeBDAEEAjALBglghkgBhv1sBwEw
# DQYJKoZIhvcNAQELBQADggIBABfO+xaAHP4HPRF2cTC9vgvItTSmf83Qh8WIGjB/
# T8ObXAZz8OjuhUxjaaFdleMM0lBryPTQM2qEJPe36zwbSI/mS83afsl3YTj+IQhQ
# E7jU/kXjjytJgnn0hvrV6hqWGd3rLAUt6vJy9lMDPjTLxLgXf9r5nWMQwr8Myb9r
# EVKChHyfpzee5kH0F8HABBgr0UdqirZ7bowe9Vj2AIMD8liyrukZ2iA/wdG2th9y
# 1IsA0QF8dTXqvcnTmpfeQh35k5zOCPmSNq1UH410ANVko43+Cdmu4y81hjajV/gx
# dEkMx1NKU4uHQcKfZxAvBAKqMVuqte69M9J6A47OvgRaPs+2ykgcGV00TYr2Lr3t
# y9qIijanrUR3anzEwlvzZiiyfTPjLbnFRsjsYg39OlV8cipDoq7+qNNjqFzeGxcy
# tL5TTLL4ZaoBdqbhOhZ3ZRDUphPvSRmMThi0vw9vODRzW6AxnJll38F0cuJG7uEB
# YTptMSbhdhGQDpOXgpIUsWTjd6xpR6oaQf/DJbg3s6KCLPAlZ66RzIg9sC+NJpud
# /v4+7RWsWCiKi9EOLLHfMR2ZyJ/+xhCx9yHbxtl5TPau1j/1MIDpMPx0LckTetiS
# uEtQvLsNz3Qbp7wGWqbIiOWCnb5WqxL3/BAPvIXKUjPSxyZsq8WhbaM2tszWkPZP
# ubdcMIIG7TCCBNWgAwIBAgIQCoDvGEuN8QWC0cR2p5V0aDANBgkqhkiG9w0BAQsF
# ADBpMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xQTA/BgNV
# BAMTOERpZ2lDZXJ0IFRydXN0ZWQgRzQgVGltZVN0YW1waW5nIFJTQTQwOTYgU0hB
# MjU2IDIwMjUgQ0ExMB4XDTI1MDYwNDAwMDAwMFoXDTM2MDkwMzIzNTk1OVowYzEL
# MAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMTswOQYDVQQDEzJE
# aWdpQ2VydCBTSEEyNTYgUlNBNDA5NiBUaW1lc3RhbXAgUmVzcG9uZGVyIDIwMjUg
# MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANBGrC0Sxp7Q6q5gVrMr
# V7pvUf+GcAoB38o3zBlCMGMyqJnfFNZx+wvA69HFTBdwbHwBSOeLpvPnZ8ZN+vo8
# dE2/pPvOx/Vj8TchTySA2R4QKpVD7dvNZh6wW2R6kSu9RJt/4QhguSssp3qome7M
# rxVyfQO9sMx6ZAWjFDYOzDi8SOhPUWlLnh00Cll8pjrUcCV3K3E0zz09ldQ//nBZ
# ZREr4h/GI6Dxb2UoyrN0ijtUDVHRXdmncOOMA3CoB/iUSROUINDT98oksouTMYFO
# nHoRh6+86Ltc5zjPKHW5KqCvpSduSwhwUmotuQhcg9tw2YD3w6ySSSu+3qU8DD+n
# igNJFmt6LAHvH3KSuNLoZLc1Hf2JNMVL4Q1OpbybpMe46YceNA0LfNsnqcnpJeIt
# K/DhKbPxTTuGoX7wJNdoRORVbPR1VVnDuSeHVZlc4seAO+6d2sC26/PQPdP51ho1
# zBp+xUIZkpSFA8vWdoUoHLWnqWU3dCCyFG1roSrgHjSHlq8xymLnjCbSLZ49kPmk
# 8iyyizNDIXj//cOgrY7rlRyTlaCCfw7aSUROwnu7zER6EaJ+AliL7ojTdS5PWPsW
# eupWs7NpChUk555K096V1hE0yZIXe+giAwW00aHzrDchIc2bQhpp0IoKRR7YufAk
# prxMiXAJQ1XCmnCfgPf8+3mnAgMBAAGjggGVMIIBkTAMBgNVHRMBAf8EAjAAMB0G
# A1UdDgQWBBTkO/zyMe39/dfzkXFjGVBDz2GM6DAfBgNVHSMEGDAWgBTvb1NK6eQG
# fHrK4pBW9i/USezLTjAOBgNVHQ8BAf8EBAMCB4AwFgYDVR0lAQH/BAwwCgYIKwYB
# BQUHAwgwgZUGCCsGAQUFBwEBBIGIMIGFMCQGCCsGAQUFBzABhhhodHRwOi8vb2Nz
# cC5kaWdpY2VydC5jb20wXQYIKwYBBQUHMAKGUWh0dHA6Ly9jYWNlcnRzLmRpZ2lj
# ZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRHNFRpbWVTdGFtcGluZ1JTQTQwOTZTSEEy
# NTYyMDI1Q0ExLmNydDBfBgNVHR8EWDBWMFSgUqBQhk5odHRwOi8vY3JsMy5kaWdp
# Y2VydC5jb20vRGlnaUNlcnRUcnVzdGVkRzRUaW1lU3RhbXBpbmdSU0E0MDk2U0hB
# MjU2MjAyNUNBMS5jcmwwIAYDVR0gBBkwFzAIBgZngQwBBAIwCwYJYIZIAYb9bAcB
# MA0GCSqGSIb3DQEBCwUAA4ICAQBlKq3xHCcEua5gQezRCESeY0ByIfjk9iJP2zWL
# pQq1b4URGnwWBdEZD9gBq9fNaNmFj6Eh8/YmRDfxT7C0k8FUFqNh+tshgb4O6Lgj
# g8K8elC4+oWCqnU/ML9lFfim8/9yJmZSe2F8AQ/UdKFOtj7YMTmqPO9mzskgiC3Q
# YIUP2S3HQvHG1FDu+WUqW4daIqToXFE/JQ/EABgfZXLWU0ziTN6R3ygQBHMUBaB5
# bdrPbF6MRYs03h4obEMnxYOX8VBRKe1uNnzQVTeLni2nHkX/QqvXnNb+YkDFkxUG
# tMTaiLR9wjxUxu2hECZpqyU1d0IbX6Wq8/gVutDojBIFeRlqAcuEVT0cKsb+zJNE
# suEB7O7/cuvTQasnM9AWcIQfVjnzrvwiCZ85EE8LUkqRhoS3Y50OHgaY7T/lwd6U
# Arb+BOVAkg2oOvol/DJgddJ35XTxfUlQ+8Hggt8l2Yv7roancJIFcbojBcxlRcGG
# 0LIhp6GvReQGgMgYxQbV1S3CrWqZzBt1R9xJgKf47CdxVRd/ndUlQ05oxYy2zRWV
# FjF7mcr4C34Mj3ocCVccAvlKV9jEnstrniLvUxxVZE/rptb7IRE2lskKPIJgbaP5
# t2nGj/ULLi49xTcBZU8atufk+EMF/cWuiC7POGT75qaL6vdCvHlshtjdNXOCIUjs
# arfNZzGCBg4wggYKAgEBMDIwHjEcMBoGA1UEAwwTVkFEVEVLIENvZGUgU2lnbmlu
# ZwIQEflOMRuxR6pMqkvTSLe5eTANBglghkgBZQMEAgEFAKCBhDAYBgorBgEEAYI3
# AgEMMQowCKACgAChAoAAMBkGCSqGSIb3DQEJAzEMBgorBgEEAYI3AgEEMBwGCisG
# AQQBgjcCAQsxDjAMBgorBgEEAYI3AgEVMC8GCSqGSIb3DQEJBDEiBCDyvaH3/ap+
# mYuGhBcxAiZawumXq4cp5KlwelLoResS/TANBgkqhkiG9w0BAQEFAASCAgA3+WAj
# Z/nDvtwgwPycUfNhF3Ym+z7t64wJuzSJB2/Ll813cpBT+KT0vjWbluo/sUUj8rJ+
# AWjUOI6a+ILuG1ukRAIt11/EwYXxqOC0ZD/oToCfMJBttmX5Jr4oi+zXnlMZNRzA
# VFXnN3uopYeQajEjeG/dHrxwa+TJSxpz4t9AkQyH4dqP76EukOTzL3HUvF++Vgjh
# IF2AeZj/Fj5ynZoiS3FTNRo2XrIPl3HOes7pjTDx/HYIm39qGRcxXgeZtdB66uwF
# 2W/bB+hZV/+FJx4M4a+1Qxkn9DLIG+Gjwh2W1SUfgpHAkalRrQU7/WnolvxNyvRl
# UsivR4hC+8yc3ReTxXg61FZZgcYb5YZnL83+JjS+xn6aCS5zSEGHEUC7GKGsLtm+
# msS6xI4BKuRKkoJzjPs+0iIumiHsYJEUiACCcn4N13Vfe7O8ESgBP08Tt3utkt2n
# +UUvn/xp3eiDZpMEL002+bAgdaWA4H7erUIVfJvWoFhEzzU6GVcOj6ikhdIhoR+Y
# NQo+BiHxGrmadx/e1sT5QgAnXZ+tp/vWQtZZeyqQpB8tWMezQ7mjVHSZu8IPc+ph
# aukdFEo7YFFyQxWmbgJCUIPl+bGzfEYSsYpAJJmQcPvbUE9QPo/LgDckda30pCA5
# XPX5aGfPiw2g/Ne5oxXGirER19cqilWvAGk996GCAyYwggMiBgkqhkiG9w0BCQYx
# ggMTMIIDDwIBATB9MGkxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwg
# SW5jLjFBMD8GA1UEAxM4RGlnaUNlcnQgVHJ1c3RlZCBHNCBUaW1lU3RhbXBpbmcg
# UlNBNDA5NiBTSEEyNTYgMjAyNSBDQTECEAqA7xhLjfEFgtHEdqeVdGgwDQYJYIZI
# AWUDBAIBBQCgaTAYBgkqhkiG9w0BCQMxCwYJKoZIhvcNAQcBMBwGCSqGSIb3DQEJ
# BTEPFw0yNjA2MDExMzQxMThaMC8GCSqGSIb3DQEJBDEiBCB33Bw5d8Av0Yo0tbbR
# VwzeSQt8jZTPXAogqNEEUB9ZVDANBgkqhkiG9w0BAQEFAASCAgCh3rDiTrxWrw1I
# saT58oFxp5OKRKRhi6A0VYpMliUTC2BrxdEbRBsBMlN3h88/Nr4CBGpoImZ2EmDS
# 6XLAYIKpOXb2VOTMY3UqdshGgYwRDBK7uPMsnYpLZxK7lj8whNP/GaGgituypPqV
# dJniMplqBqLKOlCyBN0RwwBPc3v9e1SPoIFDlYuh9MOqG17cBf7AXoN90n41X3/r
# CRMIpTHI82Db6vUScqquuvtqBAhCWQBZV2RqqPYgDc448kMlicyGTdyNfDKmPE/X
# eQeQk17RyquFOvWr/c3u0c+NN1zwgs0yflbzKzeGmj4xZi2ww+dEcgsuRfwwB5Tt
# E9B1UiISH+98aPPDXWBBEyxDR1UobkoygOrUZCoZmdLBSKdFkqnZYJoy0v/pVz9Q
# rl1VcncS/6TfbXLmiATnmA5Yp7QhD1FMCUdcgiEUuWc2RkGXlICa6isVrcts7n0p
# Fp799WwhsAp6Bk26vfhKoaEfiGfY3s4w3kQqhB8v11KgjyehQT7nKyXbIeN6SH3s
# qXZsw2ghHzb1IobnsHmxoDbep9hGTvMAHc/qwxGHkvOedjKZxw3lkX8F3N2dMhgG
# nPDmbtEr4oTGXXr7Mj1GqdwWmEnr8er2TLzx9x9TRsl/jXVH7DL89dCrwhrGuPTg
# 06FWEqC4R2VYCV+s0MwGfXr45Uv4dA==
# SIG # End signature block