Public/Start_Stop/SecureCrimp/Restart-SecureCrimpStack.ps1

function Restart-SecureCrimpStack {
    <#
    .SYNOPSIS
        Restarts the Secure Crimp stack on a remote server.
    .DESCRIPTION
        This function stops and starts the Secure Crimp backend and frontend
        services on a specified remote server.
    .PARAMETER Server
        The remote server to connect to. Default comes from
        settings.secureCrimp.server in config.json.
    .PARAMETER Credential
        The credentials to use for the remote session.
    .PARAMETER TaskList
        The list of scheduled tasks to restart. Default comes from
        settings.secureCrimp.stackTasks in config.json.
    .PARAMETER BackendPorts
        The list of backend ports to check and stop processes on.
    .PARAMETER FrontendPorts
        The list of frontend ports to check and stop processes on.
    .PARAMETER StopTimeoutSec
        The timeout in seconds to wait for tasks to stop.
    .PARAMETER SleepSeconds
        The number of seconds to sleep between operations.
    #>

    [CmdletBinding()]
    param(
        [string]$Server,
        [pscredential]$Credential,

        # Start backend first (avoids nginx proxying to a dead upstream)
        [string[]]$TaskList = @("Secure Crimp - Run Server", "Secure Crimp - Nginx"),

        [int[]]$BackendPorts = @(5000, 8000),
        [int[]]$FrontendPorts = @(80, 443),

        [int]$StopTimeoutSec = 20,
        [int]$SleepSeconds = 2,
        [int]$StartValidationSec = 15,
        [int]$StartValidationPollMs = 500
    )

    Initialize-TechToolboxRuntime

    $secureCrimpCfg = $null
    if ($script:cfg -and $script:cfg.settings) {
        $secureCrimpCfg = $script:cfg.settings.secureCrimp
    }

    if (-not $PSBoundParameters.ContainsKey('Server')) {
        $configuredServer = [string]$secureCrimpCfg.server
        if (-not [string]::IsNullOrWhiteSpace($configuredServer)) {
            $Server = $configuredServer
        }
    }

    if (-not $PSBoundParameters.ContainsKey('TaskList')) {
        $configuredTaskList = @($secureCrimpCfg.stackTasks | Where-Object { -not [string]::IsNullOrWhiteSpace([string]$_) })
        if ($configuredTaskList.Count -gt 0) {
            $TaskList = @($configuredTaskList)
        }
    }

    $sessParams = @{ ComputerName = $Server }
    if ($PSBoundParameters.ContainsKey('Credential')) { $sessParams.Credential = $Credential }
    $writeTaskLogShimSource = Get-RemoteWriteTaskLogShimSource

    $session = $null
    try {
        $session = Start-NewPSRemoteSession @sessParams

        Invoke-Command -Session $session -ErrorAction Stop -ArgumentList $TaskList, $BackendPorts, $FrontendPorts, $StopTimeoutSec, $SleepSeconds, $StartValidationSec, $StartValidationPollMs, $writeTaskLogShimSource -ScriptBlock {
        param($TaskList, $BackendPorts, $FrontendPorts, $StopTimeoutSec, $SleepSeconds, $StartValidationSec, $StartValidationPollMs, $WriteTaskLogShimSource)

        . ([ScriptBlock]::Create($WriteTaskLogShimSource))

        function Stop-ProcessByPort {
            param([int[]]$Ports)

            foreach ($port in $Ports) {
                $pids = Get-NetTCPConnection -State Listen -ErrorAction SilentlyContinue |
                Where-Object { $_.LocalPort -eq $port } |
                Select-Object -ExpandProperty OwningProcess -Unique

                foreach ($processId in $pids) {
                    if (-not $processId -or $processId -eq 0) { continue }
                    try {
                        $proc = Get-Process -Id $processId -ErrorAction Stop
                        Write-TaskLog -Level "INFO" -Message "Stopping PID $processId ($($proc.ProcessName)) owning port $port"
                        Stop-Process -Id $processId -Force -ErrorAction Stop
                    }
                    catch {
                        Write-TaskLog -Level "WARN" -Message "Failed stopping PID $processId for port ${port}: $($_.Exception.Message)"
                    }
                }
            }
        }

        function Restart-LongRunningTask {
            param(
                [Parameter(Mandatory)][string]$TaskName,
                [int]$TimeoutSec = 20,
                [int]$ValidationSec = 15,
                [int]$ValidationPollMs = 500
            )

            try {
                $null = Get-ScheduledTask -TaskName $TaskName -ErrorAction Stop
            }
            catch {
                Write-TaskLog -Level "ERROR" -Message "Scheduled task not found: '$TaskName'"
                return $false
            }

            try {
                $info = Get-ScheduledTaskInfo -TaskName $TaskName -ErrorAction Stop
                if ($info.State -eq 'Running') {
                    Write-TaskLog -Level "INFO" -Message "Stopping scheduled task: $TaskName"
                    Stop-ScheduledTask -TaskName $TaskName -ErrorAction SilentlyContinue

                    $sw = [Diagnostics.Stopwatch]::StartNew()
                    do {
                        Start-Sleep -Milliseconds 500
                        $info = Get-ScheduledTaskInfo -TaskName $TaskName -ErrorAction SilentlyContinue
                    } while ($info -and $info.State -eq 'Running' -and $sw.Elapsed.TotalSeconds -lt $TimeoutSec)

                    if ($info -and $info.State -eq 'Running') {
                        Write-TaskLog -Level "WARN" -Message "Task still Running after $TimeoutSec sec: $TaskName (continuing to Start anyway)"
                    }
                    else {
                        Write-TaskLog -Level "INFO" -Message "Task stopped: $TaskName"
                    }
                }

                Write-TaskLog -Level "INFO" -Message "Starting scheduled task: $TaskName"
                Start-ScheduledTask -TaskName $TaskName -ErrorAction Stop

                $startWatch = [Diagnostics.Stopwatch]::StartNew()
                $post = $null
                do {
                    Start-Sleep -Milliseconds $ValidationPollMs
                    $post = Get-ScheduledTaskInfo -TaskName $TaskName -ErrorAction SilentlyContinue

                    if (-not $post) {
                        continue
                    }

                    if ($post.State -eq 'Running') {
                        Write-TaskLog -Level "INFO" -Message "Task healthy: $TaskName :: Running"
                        return $true
                    }

                    if ($post.State -eq 'Ready' -and $post.LastTaskResult -eq 0) {
                        Write-TaskLog -Level "INFO" -Message "Task healthy: $TaskName :: Ready (LastTaskResult: 0)"
                        return $true
                    }

                    if ($post.State -eq 'Ready' -and $post.LastTaskResult -ne 0) {
                        Write-TaskLog -Level "ERROR" -Message "Task unhealthy after start: $TaskName :: Ready (LastTaskResult: $($post.LastTaskResult))"
                        return $false
                    }
                } while ($startWatch.Elapsed.TotalSeconds -lt $ValidationSec)

                if ($post) {
                    if ($post.State -eq 'Running') {
                        Write-TaskLog -Level "INFO" -Message "Task healthy after wait: $TaskName :: Running"
                        return $true
                    }

                    if ($post.State -eq 'Ready' -and $post.LastTaskResult -eq 0) {
                        Write-TaskLog -Level "INFO" -Message "Task healthy after wait: $TaskName :: Ready (LastTaskResult: 0)"
                        return $true
                    }

                    Write-TaskLog -Level "ERROR" -Message "Task did not become healthy after start: $TaskName :: State=$($post.State), LastTaskResult=$($post.LastTaskResult)"
                    return $false
                }

                Write-TaskLog -Level "ERROR" -Message "Task info unavailable after start: $TaskName"
                return $false
            }
            catch {
                Write-TaskLog -Level "ERROR" -Message "Failed restarting task '$TaskName': $($_.Exception.Message)"
                return $false
            }
        }

        # Stop backend first, then nginx
        Write-TaskLog -Level "INFO" -Message "Stopping backend by ports: $($BackendPorts -join ', ')"
        Stop-ProcessByPort -Ports $BackendPorts
        Start-Sleep -Seconds $SleepSeconds

        Write-TaskLog -Level "INFO" -Message "Stopping frontend by ports: $($FrontendPorts -join ', ')"
        Stop-ProcessByPort -Ports $FrontendPorts
        Start-Sleep -Seconds $SleepSeconds

        # Restart tasks (long-running safe)
        $failedTasks = [System.Collections.Generic.List[string]]::new()
        foreach ($t in $TaskList) {
            Write-TaskLog -Level "INFO" -Message "Restarting task: $t"
            $ok = Restart-LongRunningTask -TaskName $t -TimeoutSec $StopTimeoutSec -ValidationSec $StartValidationSec -ValidationPollMs $StartValidationPollMs
            if (-not $ok) {
                $failedTasks.Add($t) | Out-Null
            }
            Start-Sleep -Seconds $SleepSeconds
        }

        if ($failedTasks.Count -gt 0) {
            throw "SecureCrimp stack task restart failed for: $($failedTasks -join ', ')"
        }

        # Verify ports are listening again
        Start-Sleep -Seconds 3
        $expected = @($BackendPorts + $FrontendPorts) | Sort-Object -Unique
        $listening = Get-NetTCPConnection -State Listen -ErrorAction SilentlyContinue |
        Where-Object { $_.LocalPort -in $expected } |
        Select-Object -ExpandProperty LocalPort -Unique

        $missing = $expected | Where-Object { $_ -notin $listening }
        if ($missing) {
            Write-TaskLog -Level "WARN" -Message "Restart finished but ports not listening yet: $($missing -join ', ')"
        }
        else {
            Write-TaskLog -Level "INFO" -Message "Restart successful. Ports listening: $($expected -join ', ')"
        }
        }
    }
    finally {
        if ($session) { Remove-PSSession -Session $session -ErrorAction SilentlyContinue }
    }
}

# SIG # Begin signature block
# MIIfAgYJKoZIhvcNAQcCoIIe8zCCHu8CAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCDDwc2b8wArMH3W
# j+f7UiRgCqZmyZgfCyu3grq7+9wvfKCCGEowggUMMIIC9KADAgECAhAR+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
# AQQBgjcCAQsxDjAMBgorBgEEAYI3AgEVMC8GCSqGSIb3DQEJBDEiBCArrCa8Aimo
# tSnQpyWH+/HvArQLvc/d1sG3zF+wqsAm8DANBgkqhkiG9w0BAQEFAASCAgDQk4jL
# UBcS/KoAS50r7DKzfW4rcAnpOXWkiI+nednP0tDd8xpqPdag+FFHvL5opHpM4Mzd
# 3Fa6QDBwvx4BauoEZmPrguzgyvVyEj1tB8IvofQ6eSxNSE4H1WYs8Z1LNahLzB68
# FBNTjfRs8d9laZqv4UmhPnWJfi9meM4zUt8Td8Hm74nSEJnUsT0Uwu0qbstegvx6
# 1nG2bLmVv5WVk+ovxA/Homt80SxH9F4RJueWFuFHAgIhrgUPzsrtduIlOkgS1TJb
# IErJeR2q2ChN2jDIvUAcjtAZsknrYR/XC5/u7JMKuYRvDp9T2dYePdn4l1os9a8t
# VJKf4B1e/wIRWIFCMni+XHszUv8MOmfw6oLpNCzroid993LhlQel3DEiHLdh8ZGl
# bUxnZIv7qhGps7wifx5Z2MZ3IVawacIfNtAxeTDns9nC1lAv8J7xRqWu5+stOJop
# Tp0PAa/LePgwaR1cUoBhYdwnkyg6UBHTXLhM1STqtVEzzzHCwCuMNwKhDCdPilHR
# dl75UPWr8amiK5ic1FQvKWmn7K+M4edFUwzCALDDwg0GVYvpLtSZlkOP4CnoFq9T
# oMvfkYVxgJHacYFkTOb6StgxoitoBEYHDX/elnW3xq6Qf30JTAE4Ozwa+fDYnO2E
# yakiOhyDHcUFMkzZGShV3fRskHCe38mAmHx4maGCAyYwggMiBgkqhkiG9w0BCQYx
# ggMTMIIDDwIBATB9MGkxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwg
# SW5jLjFBMD8GA1UEAxM4RGlnaUNlcnQgVHJ1c3RlZCBHNCBUaW1lU3RhbXBpbmcg
# UlNBNDA5NiBTSEEyNTYgMjAyNSBDQTECEAqA7xhLjfEFgtHEdqeVdGgwDQYJYIZI
# AWUDBAIBBQCgaTAYBgkqhkiG9w0BCQMxCwYJKoZIhvcNAQcBMBwGCSqGSIb3DQEJ
# BTEPFw0yNjA2MDQyMTEwNDJaMC8GCSqGSIb3DQEJBDEiBCBvh13/amgeTW53SYuj
# yw+MIIRqgHkZmf8kot3/eaz+ezANBgkqhkiG9w0BAQEFAASCAgCtdFsC9W8TgfoN
# pPG26aeL5YjK16wZB61C9ny0AK3cuh+pBfSwVwrOhmACai0ODN14oklyItkWFt66
# gFFMwXQwhzzpOXHpjMTSXf3hte3dNOmc0iNrRcLLSWMJMHiXZS9Cqtb57IBc5TV5
# q/3TSvRFxBIjp2kbSrnoEwPQ4FRIgUQJU2nONp3m1o2FFu6OEQfuf1vAc3jmhAlv
# JYe6uf2GH5t1PMH/TQPYvXnC/Z1bK0vfzS+8BkgD6JDz5t+SqzxPKcWkiqDaTJO3
# G+ldW7LyG8Lc/Bx6AunsNFGQa9zXV/0G/c2/YXxyJypwNz+vDvgON6Ndnq9a/erV
# xa+crSww8UvjoVaID7qFda07oo5pKuSiqR+eZKQBCYl/XuseYWYtDVQJP0m1bK1/
# dfHoaSxrN6pwBpDPj2/+0b6hgJC7Bss3lC4RmUQaUwpow4klS6JfNITjkRhXMM7b
# I5c1DnTAj7ew8Qw6pG6YJCwcCIKmeG8ZsZlV/z+gI4J5zt0xCLltPARa2RTkQ7br
# cCNWKW06x87tRBVJwzbYInks+OqsDJ6BKDCNVNVXQAP4/S72N/0iAJ8iSUfh3b+o
# GldB+hxcY/MWqDKf4C1f7QUi8mcbA8ovC3+V8ePnL/KVNtqUIB5+gcKb6mlf6Qso
# 4qITD9T9Y2CaK85HodlQeDx0bwEuRQ==
# SIG # End signature block