Public/Invoke/Enable-NetFx3.ps1

function Enable-NetFx3 {
    <#
    .SYNOPSIS
        Enables the .NET Framework 3.5 feature on the specified computer(s).
 
    .DESCRIPTION
        This function allows you to enable the .NET Framework 3.5 feature on
        local or remote computers. It supports specifying a source path for the
        feature files, using CredSSP for delegated credentials, and various
        other options.
 
    .PARAMETER ComputerName
        The name(s) of the target computer(s) where the .NET Framework 3.5
        feature should be enabled.
 
    .PARAMETER Credential
        The credentials to use for the remote session. If not specified, the
        script will attempt to use stored domain admin credentials.
 
    .PARAMETER Source
        The path to the source files for the .NET Framework 3.5 feature. Can be
        a local path or a UNC path.
 
    .PARAMETER UseCredSSP
        Switch to indicate whether to use CredSSP for delegated credentials in
        remote sessions.
 
    .PARAMETER Quiet
        Switch to suppress output messages.
 
    .PARAMETER NoRestart
        Switch to prevent automatic restart after enabling the feature.
 
    .PARAMETER TimeoutMinutes
        The maximum time, in minutes, to wait for the feature to be enabled.
        Default is 60 minutes.
 
    .PARAMETER Validate
        Switch to perform validation after enabling the feature.
    #>

    [CmdletBinding()]
    param(
        [string[]]$ComputerName,
        [System.Management.Automation.PSCredential]$Credential,
        [string]$Source,
        [switch]$UseCredSSP,
        [switch]$Quiet,
        [switch]$NoRestart,
        [int]$TimeoutMinutes = 60,
        [switch]$Validate
    )

    Initialize-TechToolboxRuntime

    # ----------------------------
    # Remote mode
    # ----------------------------
    if ($ComputerName -and $ComputerName.Count -gt 0) {
        if (-not $Credential -and $script:domainAdminCred) {
            $Credential = $script:domainAdminCred
        }

        if ($Source -and -not ($Source.StartsWith('\\'))) {
            Write-Log -Level 'Warn' -Message "[Enable-NetFx3] -Source '$Source' is not a UNC path. Ensure it exists on EACH target."
        }

        if ($Source -and $Source.StartsWith('\\') -and -not $UseCredSSP) {
            Write-Log -Level 'Warn' -Message "[Enable-NetFx3] UNC source detected without -UseCredSSP. If the share requires delegated credentials, remote feature enablement can fail with access denied."
        }

        Write-Log -Level 'Info' -Message "[Enable-NetFx3] Remote mode → targets: $($ComputerName -join ', ')"

        $moduleRoot = Get-ModuleRoot
        $workerLocal = Join-Path $moduleRoot 'Workers\Enable-NetFx3.worker.ps1'

        $pkg = New-HelpersPackage -HelperLibs @() -WorkerFiles @( $workerLocal )

        function Get-Tail {
            param(
                [string]$Text,
                [int]$Lines = 8
            )

            if ([string]::IsNullOrWhiteSpace($Text)) { return $null }
            $parts = @($Text -split "`r?`n" | Where-Object { -not [string]::IsNullOrWhiteSpace($_) })
            if ($parts.Count -eq 0) { return $null }
            ($parts | Select-Object -Last $Lines) -join "`n"
        }

        function Format-NetFx3State {
            param([string]$StateValue)

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

            $trimmed = $StateValue.Trim()
            switch ($trimmed) {
                '0' { return 'Unknown' }
                '1' { return 'Disabled' }
                '2' { return 'Enabled' }
                '3' { return 'EnablePending' }
                '4' { return 'DisablePending' }
                default { return $trimmed }
            }
        }

        $results = @()

        foreach ($cn in $ComputerName) {
            $session = $null
            try {
                $session = Start-NewPSRemoteSession -ComputerName $cn -Credential $Credential -UseCredSSP:$UseCredSSP

                $r = Invoke-RemoteWorker `
                    -Session          $session `
                    -HelpersZip       $pkg.ZipPath `
                    -HelpersZipHash   $pkg.ZipHash `
                    -WorkerRemotePath 'IGNORED' `
                    -WorkerLocalPath  $workerLocal `
                    -EntryPoint       'Enable-NetFx3Core' `
                    -EntryParameters  @{
                    Source         = $Source
                    TimeoutMinutes = $TimeoutMinutes
                    Validate       = $Validate
                    NoRestart      = $NoRestart
                    Quiet          = $Quiet
                }

                if ($r -and $r.SystemTaskPending) {
                    $taskName = [string]$r.SystemTaskName
                    $resultPath = [string]$r.SystemTaskResultPath
                    $workDir = [string]$r.SystemTaskWorkDir

                    if ($r.SystemTaskReused) {
                        Write-Log -Level 'Info' -Message "[Enable-NetFx3][$cn] Reusing existing pending SYSTEM fallback task '$taskName'."
                    }

                    Wait-TerminalState `
                        -Target "SYSTEM fallback $cn" `
                        -PollScript {
                        $statusObj = Invoke-Command -Session $session -ScriptBlock {
                            param($tn, $rp)

                            if (Test-Path -LiteralPath $rp) {
                                return [pscustomobject]@{ Status = 'ResultReady' }
                            }

                            $statusOut = & schtasks.exe /Query /TN $tn /FO LIST /V 2>$null
                            if (-not $statusOut) {
                                return [pscustomobject]@{ Status = 'Unknown' }
                            }

                            $stateLine = @($statusOut | Where-Object { $_ -match '^\s*Status\s*:\s*' } | Select-Object -First 1)
                            $state = if ($stateLine) { ($stateLine -replace '^\s*Status\s*:\s*', '').Trim() } else { 'Unknown' }

                            if ($state -match 'Could not start|Could not run') {
                                return [pscustomobject]@{ Status = 'TaskFinishedNoResult' }
                            }

                            [pscustomobject]@{ Status = 'Waiting' }
                        } -ArgumentList $taskName, $resultPath

                        $statusObj
                    } `
                        -GetStatus { param($o) $o.Status } `
                        -TerminalStates @{
                        ResultReady          = @{ Level = 'Ok'; Message = "[Enable-NetFx3][$cn] SYSTEM fallback result ready." }
                        TaskFinishedNoResult = @{ Level = 'Warn'; Message = "[Enable-NetFx3][$cn] SYSTEM task finished without result file." }
                    } `
                        -TimeoutSeconds ([int][TimeSpan]::FromMinutes([Math]::Max(1, $TimeoutMinutes)).TotalSeconds) `
                        -PollSeconds 15 `
                        -HeartbeatSeconds 120 `
                        -NotFoundToken 'TaskRunning' `
                        -NotFoundMessage 'Waiting for SYSTEM feature task result...' `
                        -WaitingMessage "Waiting SYSTEM feature task [$cn] " `
                        -ThrowOnTimeout:$false `
                        -ReturnLastOnTimeout `
                        -ContextFormatter { param($lastObj, $lastStatus) "Computer=$cn Task=$taskName LastStatus=$lastStatus" } | Out-Null

                    $fallback = Invoke-Command -Session $session -ScriptBlock {
                        param($rp)

                        $result = [pscustomobject]@{
                            ExitCode       = 1
                            Success        = $false
                            RebootRequired = $false
                            State          = $null
                            Message        = 'SYSTEM fallback task did not produce result.json before timeout or completion.'
                        }

                        try {
                            if (Test-Path -LiteralPath $rp) {
                                $raw = Get-Content -LiteralPath $rp -Raw -Encoding UTF8
                                $parsed = $raw | ConvertFrom-Json
                                if ($parsed) {
                                    $result.ExitCode = [int]$parsed.ExitCode
                                    $result.Success = [bool]$parsed.Success
                                    $result.RebootRequired = [bool]$parsed.RebootRequired
                                    $result.State = Format-NetFx3State -StateValue ([string]$parsed.State)
                                    $result.Message = [string]$parsed.Message
                                }
                            }
                        }
                        catch {
                            $result.Message = $_.Exception.Message
                        }

                        $result
                    } -ArgumentList $resultPath

                    if (-not $r.SystemTaskReused) {
                        Invoke-Command -Session $session -ScriptBlock {
                            param($tn, $wd)

                            try { & schtasks.exe /Delete /TN $tn /F 2>$null | Out-Null } catch {}

                            if (-not [string]::IsNullOrWhiteSpace($wd) -and (Test-Path -LiteralPath $wd)) {
                                try { Remove-Item -LiteralPath $wd -Recurse -Force -ErrorAction Stop } catch {}
                            }
                        } -ArgumentList $taskName, $workDir | Out-Null
                    }

                    $r.SystemTaskPending = $false
                    $r.SystemFallbackUsed = $true
                    $r.ExitCode = [int]$fallback.ExitCode
                    $r.Success = [bool]$fallback.Success
                    $r.RebootRequired = [bool]$fallback.RebootRequired
                    $r.State = $fallback.State
                    $r.Message = $fallback.Message
                    $r.DismStdOutTail = $null
                    $r.DismStdErrTail = Get-Tail -Text $fallback.Message -Lines 8
                }

                if ($r) { $results += $r }
            }
            catch {
                $results += [pscustomobject]@{
                    ComputerName   = $cn
                    ExitCode       = 1
                    Success        = $false
                    RebootRequired = $false
                    State          = $null
                    Message        = $_.Exception.Message
                }
            }
            finally {
                if ($session) { Remove-PSSession -Session $session -ErrorAction SilentlyContinue }
            }
        }

        foreach ($r in $results) {
            if ($r.Success) {
                if ($r.SystemFallbackUsed) {
                    Write-Log -Level 'Warn' -Message "[Enable-NetFx3][$($r.ComputerName)] Completed via SYSTEM fallback path."
                }

                if ($r.RebootRequired) {
                    Write-Log -Level 'Warn' -Message "[Enable-NetFx3][$($r.ComputerName)] Success (reboot required)."
                }
                else {
                    Write-Log -Level 'Ok' -Message "[Enable-NetFx3][$($r.ComputerName)] Success."
                }
            }
            else {
                $tail = if ($r.Message) { " - $($r.Message)" } else { "" }
                Write-Log -Level 'Error' -Message "[Enable-NetFx3][$($r.ComputerName)] Failed (Exit $($r.ExitCode))$tail"

                if ($r.ExitCode -eq 5) {
                    Write-Log -Level 'Warn' -Message "[Enable-NetFx3][$($r.ComputerName)] Exit 5 typically means access denied. Confirm elevated token on target and use -Source with -UseCredSSP for UNC media when required."
                }
                elseif ($r.Message -match '(?i)access is denied|0x80070005') {
                    Write-Log -Level 'Warn' -Message "[Enable-NetFx3][$($r.ComputerName)] Access denied indicates the remote token is not elevated enough for feature servicing. Configure the target WinRM endpoint with -RunAsCredential (an admin account) and retry."
                }

                if ($r.ExitCode -eq 5 -and -not $Source) {
                    Write-Log -Level 'Warn' -Message "[Enable-NetFx3][$($r.ComputerName)] Try again with -Source \\server\share\sources\sxs (matching OS build) plus -UseCredSSP."
                }
            }
        }

        return $results
    }

    # ----------------------------
    # Local mode
    # ----------------------------
    Write-Log -Level 'Info' -Message "[Enable-NetFx3] Starting enablement (local)."

    $invokeParams = @{
        Online      = $true
        FeatureName = 'NetFx3'
        All         = $true
        ErrorAction = 'Stop'
    }
    if ($NoRestart) { $invokeParams.NoRestart = $true }
    if ($Source) {
        $invokeParams.Source = $Source
        $invokeParams.LimitAccess = $true
    }

    $overallSuccess = $false
    $exitCode = 1
    $state = $null
    $msg = $null
    $savedProgressPreference = $null

    try {
        if ($Quiet) {
            $savedProgressPreference = $ProgressPreference
            $ProgressPreference = 'SilentlyContinue'
        }

        $result = Enable-WindowsOptionalFeature @invokeParams
        $state = (Get-WindowsOptionalFeature -Online -FeatureName NetFx3 -ErrorAction Stop).State
        $restartNeeded = [bool]$result.RestartNeeded -or ($state -in 'EnablePending', 'EnabledPending')

        if ($state -in 'Enabled', 'EnablePending', 'EnabledPending') {
            $overallSuccess = $true
            $exitCode = if ($restartNeeded) { 3010 } else { 0 }

            if ($exitCode -eq 3010) {
                Write-Log -Level 'Warn' -Message "[Enable-NetFx3] Reboot required to complete NetFx3 enablement."
            }
            else {
                Write-Log -Level 'Ok' -Message "[Enable-NetFx3] Feature enablement completed successfully."
            }
        }
        else {
            $msg = "NetFx3 state not enabled after operation (State=$state)."
            Write-Log -Level 'Error' -Message "[Enable-NetFx3] $msg"
            $overallSuccess = $false
            $exitCode = 1
        }
    }
    catch {
        $overallSuccess = $false
        $msg = $_.Exception.Message
        $exitCode = if ($_.Exception -is [System.UnauthorizedAccessException]) { 5 } else { 1 }
        Write-Log -Level 'Error' -Message "[Enable-NetFx3] Failed: $msg"
    }
    finally {
        if ($Quiet -and $null -ne $savedProgressPreference) {
            $ProgressPreference = $savedProgressPreference
        }
    }

    if ($overallSuccess -and $Validate) {
        try {
            $state = (Get-WindowsOptionalFeature -Online -FeatureName NetFx3).State
            Write-Log -Level 'Info' -Message "[Enable-NetFx3] Feature state: $state"
            if ($state -in 'Enabled', 'EnablePending', 'EnabledPending') {
                Write-Log -Level 'Ok' -Message "[Enable-NetFx3] NetFx3 enablement validated."
            }
            else {
                Write-Log -Level 'Error' -Message "[Enable-NetFx3] NetFx3 state not enabled after operation."
                $overallSuccess = $false
            }
        }
        catch {
            Write-Log -Level 'Warn' -Message "[Enable-NetFx3] Validation skipped: $($_.Exception.Message)"
        }
    }

    [pscustomobject]@{
        ComputerName   = $env:COMPUTERNAME
        ExitCode       = $exitCode
        Success        = [bool]$overallSuccess
        RebootRequired = ($exitCode -eq 3010)
        State          = $state
        Message        = $msg
    }
}

# SIG # Begin signature block
# MIIfAgYJKoZIhvcNAQcCoIIe8zCCHu8CAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCAQ8zgCgV1zq904
# p2o5uLfOBWRTN/etGVnmooLNLvUDNqCCGEowggUMMIIC9KADAgECAhAR+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
# AQQBgjcCAQsxDjAMBgorBgEEAYI3AgEVMC8GCSqGSIb3DQEJBDEiBCBbUXhdfDUp
# +4DtjuX4VzMfJtvIB4h+RFutKgGZNXnYuzANBgkqhkiG9w0BAQEFAASCAgDCutO7
# h05iAeWA6KTeQ0gEwXLibOfNl5TvVOiBL4bRGg7nklB0uSx/MWOmJYX6SL2WzfN+
# 7SIext5lBVoXRYALdwuysc0ArHeHVZGHnFhmcE+iyLYmIK4PlaOH2ZbSp2lNbtbp
# 8yKgiP9cJRk4zlplTtYjofvcz7l49Po5GxxhPzc+DpnDEwiZ8x80rsdeByCq6EYV
# P3AKiwKyon6f7k3/GEjm5wRQ9nnyVhqrQxsP1lwBBHnt42ZwADBOh85+r71aVU6p
# ZAmrdTZLH6+FEjN26npiOLyByh6wQrqJ8Yi6ddC+Vj6Dg3Vrtuhq90QmUKbxsyfx
# kca1p64D9wtJxukTZZqL0UVZbW40iJ4xutJnfvLVKP/mJsdpfK1V8PtjmZILCHqM
# X4H0MTOlWlBCB/8dD1MrXAiKmTAfHOL5eOvANT+FSUiiYboxwRRhK74PuMHbk9uJ
# edIMFGoBohXXEfRXe8EXEXWCU1hLeEbvz0tXEKPumEaSuoCdJ75hp/MewOSxzuFe
# JQ4LEq2f+upbld0ti98QMuwaC2Tn0Sdz3xSb627JGZcqGE4kCTAS93dCpYYj3M4b
# vbRfzAUhZU0pN1835hJtyBIpwZjvXSCQO9/gnu5+dFblOml9LhcuRMhau1hy9QGN
# xJe4puqkYNu2+PqAf3FgF8g+knEXS5hv8a5x9aGCAyYwggMiBgkqhkiG9w0BCQYx
# ggMTMIIDDwIBATB9MGkxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwg
# SW5jLjFBMD8GA1UEAxM4RGlnaUNlcnQgVHJ1c3RlZCBHNCBUaW1lU3RhbXBpbmcg
# UlNBNDA5NiBTSEEyNTYgMjAyNSBDQTECEAqA7xhLjfEFgtHEdqeVdGgwDQYJYIZI
# AWUDBAIBBQCgaTAYBgkqhkiG9w0BCQMxCwYJKoZIhvcNAQcBMBwGCSqGSIb3DQEJ
# BTEPFw0yNjA1MjgxNDMzNDlaMC8GCSqGSIb3DQEJBDEiBCCqp5NmakHYzbRjyELX
# o1SX1yxFSomEkyyMV9Hu83wjDDANBgkqhkiG9w0BAQEFAASCAgCaI5m3ExauNa1i
# ypXufxV+JxlYiItJ/zV1cRA1sfyM3CTwzVVKrF7aqT0m1B0GE6yBn6cqKeGoK9SS
# K8VeIrIN1VTJLMNDKXqooma/yI0F2MVLLzZHuS+4uMCBIvlMN8Xc4fx+E88i4ZV+
# GNDOOrCR937xi/6omNy8SmH5jF0tMDkCRevOTc1kisb75vQzMj98r6hV0HkVtrJT
# QHrlMYQs6slQaamXDR1l7WhfrFn7hym7nK+vGMO8p7mTnC2tvsme5M/VrJoprJo9
# sj3b3N6yVboBGbmu30W5tMFMrgTaeqaNitG8b0NGovstEHg4JyiaA4nDM4hCqNTk
# cpBGz0eZFP3hCG4b/SR1GQnGuWdFdwoXQUB3D+ajiFSeqYiIWqtZ5llRQ1DVXzN5
# S3GQLFBP89UEfSctIPymSbQ4OigEDA7F7Fimb8zUadqzvXjsvshJlaQUwiiwPBYB
# xUwjXbmlDt1HG1Kzl798TlCnxJWnLzJlu7gWYn5ezIxEvRjIE0sDsm4Z9dXBhZfR
# CCNy4dOe9NhE7PEquPfqMqs/E3+A3jeXg9nurqBX8uoBxA+Thd5ROgsodBBKFUDD
# pHMhbeDyWs2GWRlyjhEhjxWyhXxxiUaBgCGFtqc3tB5XRKH7cVPDnSUePGs+6cQs
# PXlkRTiO7KPOEcKUo0XWAkiaoHgskQ==
# SIG # End signature block