Private/System/Utilities/ReusableHelpers/RemoteWorkHelpers/Invoke-RemoteWorker.ps1

function Invoke-RemoteWorker {
    <#
    .SYNOPSIS
    Invokes a remote worker script on a specified PSSession.
 
    .DESCRIPTION
    This function stages helper scripts and worker scripts to a remote session,
    verifies their integrity, and executes the specified entry point with
    provided parameters.
 
    .PARAMETER Session
    The PSSession to use for remote execution.
 
    .PARAMETER HelpersZip
    Path to the helpers zip file to stage on the remote session.
 
    .PARAMETER HelpersZipHash
    SHA256 hash of the helpers zip file for integrity verification.
 
    .PARAMETER WorkerRemotePath
    Remote path for the worker scripts (currently unused).
 
    .PARAMETER WorkerLocalPath
    Local path to the worker scripts.
 
    .PARAMETER EntryPoint
    The entry point script to execute on the remote session.
 
    .PARAMETER EntryParameters
    Hashtable of parameters to pass to the entry point script.
 
    .PARAMETER ForceUpdate
    Switch to force update of the staged scripts (currently unused).
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [System.Management.Automation.Runspaces.PSSession]$Session,

        [string]$HelpersZip,
        [string]$HelpersZipHash,

        # NOTE: Stages into $env:TEMP\TT_Worker_{guid}\workers.
        # WorkerRemotePath is kept for signature compatibility but isn't used.
        [Parameter(Mandatory)]
        [string]$WorkerRemotePath,

        [Parameter(Mandatory)]
        [string]$WorkerLocalPath,

        [Parameter(Mandatory)]
        [string]$EntryPoint,

        [Parameter()]
        [hashtable]$EntryParameters,

        # NOTE: currently unused (kept for compatibility)
        [Parameter()]
        [switch]$ForceUpdate = $true
    )

    # StrictMode-friendly predefs (prevents "not set" reads during refactors)
    $remoteTmp = $null
    $remoteZip = $null
    $remoteHelpers = $null
    $remoteWorkers = $null
    $remoteWorkerPath = $null
    $expandedOk = $null
    $result = $null

    # 1) Create remote temp root
    $remoteTmp = Invoke-Command -Session $Session -ScriptBlock {
        $base = Join-Path $env:TEMP ("TT_Worker_{0}" -f ([guid]::NewGuid()))
        New-Item -ItemType Directory -Path $base -Force | Out-Null
        $base
    } -ErrorAction Stop

    try {
        # Canonical paths
        $remoteZip = Join-Path $remoteTmp 'helpers.zip'
        $remoteHelpers = Join-Path $remoteTmp 'helpers'
        $remoteWorkers = Join-Path $remoteTmp 'workers'

        if (-not $EntryParameters) { $EntryParameters = @{} }

        # Decide if helpers zip should be staged/expanded (worker-only packages supported)
        $stageHelpersZip = $false
        if (-not [string]::IsNullOrWhiteSpace($HelpersZip) -and (Test-Path -LiteralPath $HelpersZip)) {
            $zipInfo = Get-Item -LiteralPath $HelpersZip -ErrorAction Stop
            if ($zipInfo.Length -gt 0) { $stageHelpersZip = $true }
        }

        # 2–4) Helpers package stage (optional)
        if ($stageHelpersZip) {
            if ([string]::IsNullOrWhiteSpace($HelpersZipHash)) {
                throw "HelpersZipHash is required when HelpersZip is provided."
            }

            # 2) Push package zip
            Copy-Item -ToSession $Session -Path $HelpersZip -Destination $remoteZip -Force -ErrorAction Stop

            # 3) Verify + expand on remote into WORK ROOT
            $expandedOk = Invoke-Command -Session $Session -ScriptBlock {
                param($zipPath, $expectedHash, $workRoot)

                if (-not (Test-Path -LiteralPath $zipPath)) {
                    throw "Remote zip not found: $zipPath"
                }

                $actual = (Get-FileHash -LiteralPath $zipPath -Algorithm SHA256).Hash
                if ($actual -ne $expectedHash) {
                    throw "Hash mismatch for package. Expected $expectedHash, got $actual."
                }

                New-Item -ItemType Directory -Path $workRoot -Force | Out-Null

                try {
                    if (Get-Command Expand-Archive -ErrorAction Ignore) {
                        Expand-Archive -Path $zipPath -DestinationPath $workRoot -Force
                    }
                    else {
                        Add-Type -AssemblyName System.IO.Compression.FileSystem
                        [System.IO.Compression.ZipFile]::ExtractToDirectory($zipPath, $workRoot, $true)
                    }
                }
                catch {
                    throw "Expand package failed: $($_.Exception.Message)"
                }

                $helpersDir = Join-Path $workRoot 'helpers'
                $workersDir = Join-Path $workRoot 'workers'

                # Some packages contain helper libs only; ensure standard folders exist.
                if (-not (Test-Path -LiteralPath $workersDir)) {
                    New-Item -ItemType Directory -Path $workersDir -Force | Out-Null
                }

                if (-not (Test-Path -LiteralPath $helpersDir)) {
                    # Create it so downstream logic can assume it exists
                    New-Item -ItemType Directory -Path $helpersDir -Force | Out-Null
                }

                $true
            } -ArgumentList $remoteZip, $HelpersZipHash, $remoteTmp -ErrorAction Stop

            if (-not $expandedOk) { throw "Failed to expand package on remote." }

            # 4) Import helper libraries only (in-memory) + inject TT runtime vars
            Invoke-Command -Session $Session -ScriptBlock {
                param($helpersDir, $workersDir, $workRoot)

                # Keep behavior consistent even if zip omitted empty folders.
                if (-not (Test-Path -LiteralPath $workersDir)) {
                    New-Item -ItemType Directory -Path $workersDir -Force | Out-Null
                }

                # helpers optional: create if missing
                if (-not (Test-Path -LiteralPath $helpersDir)) {
                    New-Item -ItemType Directory -Path $helpersDir -Force | Out-Null
                }

                if (-not $script:TT) { $script:TT = [ordered]@{} }
                $script:TT.IsRemote = $true
                $script:TT.WorkRoot = $workRoot
                $script:TT.HelpersRoot = $helpersDir
                $script:TT.WorkersRoot = $workersDir

                $helperFiles = Get-ChildItem -LiteralPath $helpersDir -Filter '*.ps1' -File -ErrorAction SilentlyContinue |
                Sort-Object FullName

                foreach ($hf in $helperFiles) {
                    $text = Get-Content -LiteralPath $hf.FullName -Raw -Encoding UTF8
                    . ([ScriptBlock]::Create($text))
                }

                $true
            } -ArgumentList $remoteHelpers, $remoteWorkers, $remoteTmp -ErrorAction Stop | Out-Null

        }
        else {
            # Worker-only mode: create structure + set TT runtime vars, skip helpers zip entirely
            Invoke-Command -Session $Session -ScriptBlock {
                param($helpersDir, $workersDir, $workRoot)

                New-Item -ItemType Directory -Path $workRoot   -Force | Out-Null
                New-Item -ItemType Directory -Path $helpersDir -Force | Out-Null
                New-Item -ItemType Directory -Path $workersDir -Force | Out-Null

                if (-not $script:TT) { $script:TT = [ordered]@{} }
                $script:TT.IsRemote = $true
                $script:TT.WorkRoot = $workRoot
                $script:TT.HelpersRoot = $helpersDir
                $script:TT.WorkersRoot = $workersDir

                $true
            } -ArgumentList $remoteHelpers, $remoteWorkers, $remoteTmp -ErrorAction Stop | Out-Null
        }

        # 5) Stage main worker into workroot\workers (Option B)
        if (-not $WorkerLocalPath) {
            throw "WorkerLocalPath must be provided when always-copy mode is enabled."
        }

        $remoteWorkerPath = Join-Path $remoteWorkers (Split-Path $WorkerLocalPath -Leaf)

        # Ensure workers dir exists (should, but be defensive)
        Invoke-Command -Session $Session -ScriptBlock {
            param($dir)
            if (-not (Test-Path -LiteralPath $dir)) {
                New-Item -ItemType Directory -Path $dir -Force | Out-Null
            }
        } -ArgumentList $remoteWorkers -ErrorAction Stop

        Copy-Item -ToSession $Session -Path $WorkerLocalPath -Destination $remoteWorkerPath -Force -ErrorAction Stop

        # 6) Import worker (in-memory) & call entry function (Option B)
        $result = Invoke-Command -Session $Session -ScriptBlock {
            param($workerPath, $entry, $entryParams, $helpersDir, $workersDir, $workRoot)

            if (-not (Test-Path -LiteralPath $workerPath)) { throw "Worker not found: $workerPath" }

            if (-not $script:TT) { $script:TT = [ordered]@{} }
            $script:TT.IsRemote = $true
            $script:TT.WorkRoot = $workRoot
            $script:TT.HelpersRoot = $helpersDir
            $script:TT.WorkersRoot = $workersDir
            $script:TT.WorkerPath = $workerPath

            # Import worker INTO THIS SCOPE
            $workerText = Get-Content -LiteralPath $workerPath -Raw -Encoding UTF8
            . ([ScriptBlock]::Create($workerText))

            $fn = Get-Command -Name $entry -ErrorAction SilentlyContinue
            if (-not $fn) { throw "Entry function not found in worker: $entry" }

            # Filter entryParams to only what the entry function actually supports
            $filtered = @{}
            if ($entryParams) {
                foreach ($k in $entryParams.Keys) {
                    if ($fn.Parameters.ContainsKey($k)) {
                        $filtered[$k] = $entryParams[$k]
                    }
                }

                $ignored = @($entryParams.Keys | Where-Object { -not $fn.Parameters.ContainsKey($_) })
                if ($ignored.Count -gt 0) {
                    Write-Verbose ("IgnoredEntryParams: {0}" -f ($ignored -join ', '))
                }
            }

            # If the worker expects HelpersPath, default it to the staged helpers folder.
            if ($fn.Parameters.ContainsKey('HelpersPath')) {
                if ((-not $filtered.ContainsKey('HelpersPath')) -or [string]::IsNullOrWhiteSpace([string]$filtered.HelpersPath)) {
                    $filtered.HelpersPath = $helpersDir
                }
            }

            & $entry @filtered

    } -ArgumentList $remoteWorkerPath, $EntryPoint, $EntryParameters, $remoteHelpers, $remoteWorkers, $remoteTmp -ErrorAction Stop -InformationAction Continue

        return $result
    }
    finally {
        # 7) Cleanup remote temp (best effort)
        try {
            Invoke-Command -Session $Session -ScriptBlock {
                param($root)
                if (Test-Path -LiteralPath $root) {
                    Remove-Item -LiteralPath $root -Recurse -Force -ErrorAction SilentlyContinue
                }
            } -ArgumentList $remoteTmp -ErrorAction SilentlyContinue | Out-Null
        }
        catch { }
    }
}

# SIG # Begin signature block
# MIIfAgYJKoZIhvcNAQcCoIIe8zCCHu8CAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCBRUnV5LnP/IA4S
# wv91JRR0zuPZmH7hO+Oz+rWH+b4zUqCCGEowggUMMIIC9KADAgECAhAR+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
# AQQBgjcCAQsxDjAMBgorBgEEAYI3AgEVMC8GCSqGSIb3DQEJBDEiBCA95XN8IcI/
# 7oJ8FB5V04Su3Uu5F2sDMoEHmr646AZWkjANBgkqhkiG9w0BAQEFAASCAgBeub6a
# YAc79oh1/19vpUgu4ePr8ABUS705TPx7MnLVCh1KUdV+4e3JdY6gycr9zPTJQs/n
# FNoyTAlt0vkN3cLYxdpt9nglKwkT0sZfcyDPPDKKy7eRoleAlfEGzNm38NKnBCZc
# o7KZwTcNd/7gAYULbPozj4tfkuFmGHC44crvxEaUYTNJ5K74b5LEaHZYgFp74gIq
# TdlCcnWibiF0spF5rlPwPwxdfrV3bboq7KFp5AyzhrnmUwHF3XtVcMtrDPhXQM10
# KaSlOgg35nyujCtDGNPX70Ag9SYPUmraWy1wQqM7n95VoQG/2D4rjhzZ+dOEx8Qf
# e6HaUpzfwy7cq11dODmYYuLnl9gDsH/3LCnzfkkNd1Vt3jGJVH3HMprhRQkPG5/V
# tax1R/FeKi04GpRWo08GHB3WzEmP8EoFKrDVvj09YJzapYJ7JlVE8vsvAeZe6J5U
# j8Uc0OB/WhDDfE4TKvyCvtRpNGHgTaQhO/a65NaD3YgPyS/fqACbbxw2wTc3RE9Q
# 8GfFJS1DKzC2SYvRSC99S8XJeP3i5yzaounXYEVoancES56ioN7T468wnvOYou+L
# MMIT4wU89CaJLN2w/2LdepynHwlteNrklo+WOlnrBojHX8aZAmyXkl26Lg895x9S
# Vra9hovAtODuuW4zzznldN8IhhKKsfhwmlvbXaGCAyYwggMiBgkqhkiG9w0BCQYx
# ggMTMIIDDwIBATB9MGkxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwg
# SW5jLjFBMD8GA1UEAxM4RGlnaUNlcnQgVHJ1c3RlZCBHNCBUaW1lU3RhbXBpbmcg
# UlNBNDA5NiBTSEEyNTYgMjAyNSBDQTECEAqA7xhLjfEFgtHEdqeVdGgwDQYJYIZI
# AWUDBAIBBQCgaTAYBgkqhkiG9w0BCQMxCwYJKoZIhvcNAQcBMBwGCSqGSIb3DQEJ
# BTEPFw0yNjA0MjEyMDU0MTFaMC8GCSqGSIb3DQEJBDEiBCDr5asU+wDs+SpKL2Gw
# o4wSfAd64aafcJgAE+83/nwB5TANBgkqhkiG9w0BAQEFAASCAgDEhERQu6LZAu4b
# Od21fk4s1qa1hB6XClglyfCDkMRY9bsrtcnPtjKDff1sy8hhsGHUiprC3jVtSQfd
# br8Xk4+j4RvvoHOlDkrzMV08G3rCBx2Cf29P73L6WwSwGvAE5Dxuw6PoLTlGNlm0
# 1I8OGSAB6FleaosE2M3NAVUc7ki14lwizglq6KBsb/wtGLQBEoGl7G6GGgZ+0DFo
# 9OJ2yzIRROHuoKudxbh7ewQFbE2DWWlfaf5YKZtgtR1jcozsj8ctYULE2E+AAX/2
# Al211QY5bCcPjFckl144Y1QCnxkJBTDGzcrFo+A+6DJzrEnYR/PO0rl+9SNXSULo
# gd6ex6GhjH1lwplfEsy9jE9UZb4s5eemzf/2EYvhC0UPTE429zmGULP3hoElPBAc
# EIvYwSgbD+tb6hjRsywSmVwAz1LOEVemr1lm2xEAyb9BIUr8wg1AKsS8IAvC9whZ
# MB5j/vDPplochbFz+815VJG+NYyW8MN7eDvURhI6H/BLGBB8YoptUJrzYmTXeB9i
# NnX9EVnxCik7Gv7Ru8vMgPTR/9CELfZYab+mLoEvp2VGGPUG1LdmH4DY8W4pGP2O
# d/9X/uwpP+5Z1XNIMql39X9u5BqEpl6wnayKNX9HISmmelx1NTUZtfvxPJKiEWxB
# hgPX8m/1XT4kSxz2h/Wcxm7gfMEUMA==
# SIG # End signature block