Public/ps1/Html/Start-ApprxrJobHtml.ps1

<#
        .SYNOPSIS
        Executes an HTTP job for Apprxr, handling authentication, headers, request/response encoding, and structured error handling.
 
        .DESCRIPTION
        Sends an HTTP request (GET or POST) to a specified URL with provided headers, body, and authentication. Handles authentication via Get-ApprxrAuthenticationRoute, logs all request and response details (optionally to HTML log), and returns a structured result. Handles Unicode decoding, ensures UTF-8 encoding for outgoing bodies, and extracts detailed error information from the response. Compatible with both Windows PowerShell and PowerShell Core.
 
        .PARAMETER taskInformation
        Hashtable or object containing the job parameters:
            - url: Target endpoint (required)
            - httpMethod: GET, POST, etc. (optional, defaults based on body)
            - headers: Hashtable of HTTP headers (optional)
            - body: Request body (string, hashtable, or PSCustomObject)
            - contentType: Content-Type header (optional, defaults to application/json; charset=utf-8)
            - channelId, id, type: Used for authentication lookup (optional)
 
        .PARAMETER debugMode
        If specified, enables debug mode which logs all request/response details using Log-JobHtml. Can also be controlled via ApprxrHtmlDebugMode configuration setting.
 
        .EXAMPLE
        Start-ApprxrJobHtml -taskInformation @{ url = 'https://example.com/api'; httpMethod = 'POST'; headers = @{ Authorization = 'Bearer ...' }; body = $jsonBody; contentType = 'application/json' }
 
        .EXAMPLE
        Start-ApprxrJobHtml -taskInformation $taskInfo -debugMode
 
        .NOTES
        - Used for running HTTP-based jobs in Apprxr automation, including REST API calls with authentication and custom headers.
        - Ensures outgoing body is UTF-8 encoded and Content-Type includes charset=utf-8.
        - Handles Unicode escape decoding in both body and URL.
        - Logs to JobHtml.log if Log-JobHtml is available and debug mode is enabled.
        - Returns a hashtable with keys: message, success, and optionally messageCode.
#>

function Start-ApprxrJobHtml {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true)]
        $taskInformation,
        [switch]$debugMode
    )
    # Check if debug mode is enabled
    
    if (-not $debugMode) {
        try {
            $debugConfig = Get-ApprxrConfigurationValue -name "ApprxrHtmlDebugMode" -ErrorAction SilentlyContinue
            if ($debugConfig -and ($debugConfig -eq $true -or $debugConfig -eq "true" -or $debugConfig -eq "1")) {
                $debugMode = $true
            }
        } catch {
            # Configuration value not found, continue without debug mode
        }
    }

    # Unicode decoding and JSON conversion for body
    if ($taskInformation.body) {
        if ($taskInformation.body -is [PSCustomObject]) {
            $taskInformation.body = [string]($taskInformation.body | ConvertTo-Json -Depth 10 -Compress)
            Log-JobHtml "... Body converted to JSON string from PSCustomObject." -debugMode:$debugMode  
        }
        # After ConvertTo-Json the backslash in \uXXXX gets doubled to \\uXXXX.
        # Reduce \\uXXXX (two physical backslashes + uXXXX) back to \uXXXX before decoding.
        # Pattern "\\\\u" = regex \\u = two literal backslashes + u.
        $taskInformation.body = $taskInformation.body -replace "\\\\u", "\u"
        $taskInformation.body = [regex]::Replace($taskInformation.body, "\\u([0-9A-Fa-f]{4})", { param($m) [char]([convert]::ToInt32($m.Groups[1].Value,16)) })
    }


    # Log all input parameters if debug mode is enabled
    Log-JobHtml "Headers: $($taskInformation.headers | Out-String)"  -debugMode:$debugMode
    Log-JobHtml "Body: $($taskInformation.body | Out-String)" -debugMode:$debugMode
    Log-JobHtml "Method: $($taskInformation.httpMethod | Out-String)" -debugMode:$debugMode
    Log-JobHtml "Url: $($taskInformation.url | Out-String)" -debugMode:$debugMode
    Log-JobHtml "ContentType: $($taskInformation.contentType | Out-String)" -debugMode:$debugMode
      

    if (-not $taskInformation.url) {
        return @{
            message = "No url found"
            success = $false
        }
    }

    
    try {
        # Populate authentication info using Get-ApprxrAuthenticationRoute
        $authInfo = Get-ApprxrAuthenticationRoute -hostURI $taskInformation.url -channelId $taskInformation.channelId -id $taskInformation.id -type $taskInformation.type
        if ($authInfo) {
            if ($debugMode) {
                Log-JobHtml ("Authentication Info: $($authInfo | ConvertTo-Json -Depth 10)") -debugMode:$debugMode
            }
        }
    } catch {
        Log ("Error setting authentication: $($_.Exception.Message)")
        return @{
            message = "Error setting authentication: $($_.Exception.Message)"
            success = $false
        }
    }

    $result = @{}
    $headers = @{}
    
    # Handle null or empty headers
    if ($taskInformation.headers) {
        $taskInformation.headers.psobject.Properties | ForEach-Object {
            $headers[$_.Name] = $_.Value
        }
    }



    try {
        # Decode any Unicode escape sequences in the URL (e.g., \u0026 for &)
        $decodedUrl = $taskInformation.url -replace '\\u([0-9A-Fa-f]{4})', { [char]([convert]::ToInt32($args[0],16)) }
        
        Log-JobHtml ("Decoded URL: $decodedUrl")  -debugMode:$debugMode
        
        $invokeParams = @{
            Uri = $decodedUrl
            Method = $taskInformation.httpMethod
            Headers = $headers
            ErrorAction = 'Stop'
        }

        $isCore = $PSVersionTable.PSEdition -eq 'Core'

        # Add authentication/credential if available
        if ($authInfo) {
            # NTLM/Negotiate: Set Credential always, set Authentication only in PowerShell Core
            if ($authInfo.Authentication -eq 'Negotiate' -or $authInfo.Authentication -eq 'NTLM') {
                if ($authInfo.PSObject.Properties['username'] -and $authInfo.PSObject.Properties['password'] -and $authInfo.username -and $authInfo.password) {
                    try {
                        $securePassword = ConvertTo-SecureString -String $authInfo.password -AsPlainText -Force
                        $credential = New-Object System.Management.Automation.PSCredential (($authInfo.username -replace '\\\\','\\'), $securePassword)
                        $invokeParams.Credential = $credential
                        Log-JobHtml ("Adding Credential for user: $($authInfo.username)") -debugMode:$debugMode
                        
                    } catch {
                        Log-JobHtml ("Error creating or assigning credential: $($_.Exception.Message)") -debugMode:$debugMode
                        throw $_
                    }
                }
              
                $invokeParams | Add-Member -MemberType NoteProperty -Name "Authentication" -Value "Negotiate"
                Log-JobHtml ("Credential and authentication assignment complete. Preparing to invoke web request.") -debugMode:$debugMode
            } elseif ($authInfo.Authentication -eq 'Basic') {
                # For Basic, add Authorization header manually
                if ($authInfo.PSObject.Properties['username'] -and $authInfo.PSObject.Properties['password'] -and $authInfo.username -and $authInfo.password) {
                    $pair = "$($authInfo.username):$($authInfo.password)"
                    $bytes = [System.Text.Encoding]::UTF8.GetBytes($pair)
                    $base64 = [Convert]::ToBase64String($bytes)
                    $headers['Authorization'] = "Basic $base64"
                    Log-JobHtml ("Adding Basic Authorization header for user: $($authInfo.username)") -debugMode:$debugMode
                }
                if ($isCore -and $authInfo.PSObject.Properties['Authentication'] -and $authInfo.Authentication) {
                    $invokeParams.Authentication = $authInfo.Authentication
                    Log-JobHtml ("Adding Authentication (Core): $($authInfo.Authentication)") -debugMode:$debugMode
                }
            }
        }
        
        if ($taskInformation.body) {
            $bodyToSend = $taskInformation.body

            # Set default method to POST if body is present and no method specified
            if (-not $taskInformation.httpMethod) { $taskInformation.httpMethod = "POST" }
            
            
            # Always set Content-Type to include charset=utf-8
            if (-not $taskInformation.contentType) {
                $taskInformation.contentType = "application/json"
            }
            if ($taskInformation.contentType -notmatch "charset") {
                $taskInformation.contentType = $taskInformation.contentType.TrimEnd(';') + "; charset=utf-8"
            }
            
            Log-JobHtml ("Setting Content-Type to: $($taskInformation.contentType)") -debugMode:$debugMode
            $invokeParams.ContentType = $taskInformation.contentType


            # If body is an object and content type is application/json, serialize to JSON
            if ($taskInformation.contentType -like 'application/json*' -and ($bodyToSend -is [PSCustomObject] -or $bodyToSend -is [hashtable])) {
                
                Log-JobHtml  ("Converting body object to JSON string.")

                $bodyToSend = $bodyToSend | ConvertTo-Json -Depth 10
            } else {
                Log-JobHtml ("Using body as provided. Because content type is not application/json $($taskInformation.contentType) or body is not an ps object $($bodyToSend -is [PSCustomObject]) neither a hashtable $($bodyToSend -is [hashtable]).")
            }

            # Ensure UTF-8 encoding if body is a string (send as string, not byte array)
            if ($bodyToSend -is [string]) {
                Log-JobHtml ("Ensuring UTF-8 encoding for request body.")

                $utf8NoBom = New-Object System.Text.UTF8Encoding($false)
                $bytes = $utf8NoBom.GetBytes($bodyToSend)
                $utf8Body = $utf8NoBom.GetString($bytes)
                $invokeParams.Body = $utf8Body
                Log-JobHtml ("Outgoing Body (UTF-8): $utf8Body")
            } else {
                $invokeParams.Body = $bodyToSend
                Log-JobHtml ("Outgoing Body (non-string): $($bodyToSend | Out-String)")
            }            
        } else {
            if (-not $taskInformation.httpMethod) { $taskInformation.httpMethod = "GET" }
            if ($taskInformation.body) {
                $bodyToSend = $taskInformation.body
                # If body is an object and content type is application/json, serialize to JSON
                if ($taskInformation.contentType -eq 'application/json' -and ($bodyToSend -is [PSCustomObject] -or $bodyToSend -is [hashtable])) {
                    $bodyToSend = $bodyToSend | ConvertTo-Json -Depth 10
                }
                $invokeParams.Body = $bodyToSend
                $invokeParams.ContentType = $taskInformation.contentType
            }
        }
        
        Log-JobHtml ("Send Invoke-WebRequest Parameters: $($invokeParams | ConvertTo-Json -Depth 10)")
        

        if ($isCore) {
            $response = Invoke-WebRequest @invokeParams
        } else {
            $response = Invoke-WebRequest @invokeParams -UseBasicParsing
        }

        Log-JobHtml ("Invoke-WebRequest completed.") 
        $cookiesObj = Get-CookiesObject $response.Cookies
        Log-JobHtml ("Cookies Object: $($cookiesObj | ConvertTo-Json -Depth 10)")

        if ($null -eq $cookiesObj) { $cookiesObj = @() }
        $headersObj = @{}
        if ($response.PSObject.Properties['Headers']) {
            foreach ($key in $response.Headers.Keys) {
                $headersObj[$key] = $response.Headers[$key]
            }
        } elseif ($response.PSObject.Properties['BaseResponse'] -and $response.BaseResponse.Headers) {
            foreach ($key in $response.BaseResponse.Headers.Keys) {
                $headersObj[$key] = $response.BaseResponse.Headers[$key]
            }
        }
        $resultObj = @{}
        if ($response.PSObject.Properties["StatusCode"]) { $resultObj.StatusCode = $response.StatusCode }
        if ($response.PSObject.Properties["StatusDescription"]) { $resultObj.StatusDescription = $response.StatusDescription }
        $resultObj.Headers = $headersObj
        $resultObj.Cookies = $cookiesObj
        if ($response.PSObject.Properties["Content"]) { $resultObj.Content = $response.Content } else { $resultObj.Content = $response }
        if ($response.PSObject.Properties["RawContent"]) { $resultObj.RawContent = $response.RawContent }
        if ($response.PSObject.Properties["RawContentLength"]) { $resultObj.RawContentLength = $response.RawContentLength }
        if ($response.PSObject.Properties["BaseResponse"]) { $resultObj.BaseResponse = $response.BaseResponse }
        
        Log-JobHtml ("Response: $($resultObj | ConvertTo-Json -Depth 100)")

        $returnObject = @{
            message = $resultObj | ConvertTo-Json -Depth 100
            success = $true
        }
        
        Log-JobHtml ("DEBUG MODE - Full Result Object: $($returnObject | ConvertTo-Json -Depth 100)")
        
        
        return $returnObject
    } catch {
        $headersObj = @{}
        $cookiesObj = @()
        $errorObj = @{}
        $errorContent = $null
        if ($_.Exception.Response) {
            $resp = $_.Exception.Response
            # Try to extract the raw response body (the red text)
            try {
                $reader = [System.IO.StreamReader]::new($resp.GetResponseStream())
                $errorContent = $reader.ReadToEnd()
                $reader.Close()
            } catch {}
            if ($resp.Headers) {
                foreach ($key in $resp.Headers.Keys) {
                    $headersObj[$key] = $resp.Headers[$key]
                }
            }
            $cookiesObj = Get-CookiesObject $resp.Cookies
            $errorObj.Headers = $headersObj
            $errorObj.Cookies = $cookiesObj
            if ($resp.StatusCode) { $errorObj.StatusCode = $resp.StatusCode }
            if ($resp.StatusDescription) { $errorObj.StatusDescription = $resp.StatusDescription }
            if ($resp.Content) { $errorObj.Content = $resp.Content }
            if ($resp.RawContent) { $errorObj.RawContent = $resp.RawContent }
            if ($resp.RawContentLength) { $errorObj.RawContentLength = $resp.RawContentLength }
            if ($resp.BaseResponse) { $errorObj.BaseResponse = $resp.BaseResponse }
            if ($_.Exception.Message) { $errorObj.Message = $_.Exception.Message }
            if ($errorContent) { $errorObj.ErrorContent = $errorContent }
        } else {
            if ($_.Exception.Message) { $errorObj.Message = $_.Exception.Message }
        }
        if ($null -eq $errorObj -or $errorObj.Count -eq 0) {
            Log ("Exception: $($_.Exception.Message)")
        } else {
            Log ("Error: $($errorObj | ConvertTo-Json -Depth 100)")
        }
        
        $returnObject = @{
            message = if ($null -eq $errorObj -or $errorObj.Count -eq 0) { $_.Exception.Message } else { $errorObj | ConvertTo-Json -Depth 100 }
            messageCode = if ($_.Exception.Response) { $_.Exception.Response.StatusCode.value__ } else { $null }
            success = $false
        }
        
        if ($debugMode) {
            Log ("DEBUG MODE - Full Error Object: $($returnObject | ConvertTo-Json -Depth 100)")
        }
        
        return $returnObject
    }

}
# SIG # Begin signature block
# MIInKgYJKoZIhvcNAQcCoIInGzCCJxcCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCDF4VeulSxezkDt
# lHFKEK7c6ZPW0FuqrvD6E2FAhuyhsaCCIKYwggWNMIIEdaADAgECAhAOmxiO+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
# twGpn1eqXijiuZQwggaXMIIEf6ADAgECAhAfsijAKebq6quDMIHCRvIfMA0GCSqG
# SIb3DQEBCwUAMGoxCzAJBgNVBAYTAlBMMSEwHwYDVQQKExhBc3NlY28gRGF0YSBT
# eXN0ZW1zIFMuQS4xODA2BgNVBAMTL0NlcnR1bSBFeHRlbmRlZCBWYWxpZGF0aW9u
# IENvZGUgU2lnbmluZyAyMDIxIENBMB4XDTI2MDIwMzEzNTE0NFoXDTI4MDIwMzEz
# NTE0M1owgaExHTAbBgNVBA8TFFByaXZhdGUgT3JnYW5pemF0aW9uMRMwEQYLKwYB
# BAGCNzwCAQMTAk5MMREwDwYDVQQFEwg5NjMyNDU2MjELMAkGA1UEBhMCTkwxEDAO
# BgNVBAgMB1V0cmVjaHQxEzARBgNVBAcMCkFtZXJzZm9vcnQxETAPBgNVBAoMCEFQ
# UFJYIEJWMREwDwYDVQQDDAhBUFBSWCBCVjCCAaIwDQYJKoZIhvcNAQEBBQADggGP
# ADCCAYoCggGBAOF+mHloTjmkknba8mOejw0Gq3Aczy1dtatfdLM70ken0n07zIlY
# 2jNmyTv5I5RixXAt3LJUwWnUQH8okGFJYvnLZRPyjpCxnEqq8GL6puhKjIIcTs8W
# VhoADX+mFtLXygLrfk1j6A+dnNXjoCQMsVSEYppSGrpMpKsPQONb1mmFRx66vqro
# HcP93qWYTm/f3TpRzlscIxcQ/3Ab+KLeW0sHgC/a8MVHLicbdUe/pka4jMM98Djj
# BtkBc18dI6ECtrDfB2FXXFomvgm5UyPaf2sYxMPL+X/iCNhaQevoT3vttxMvKzAD
# JxJK7VsV2nrCvxfTaP4cVd9FfoxUqjCE0eA/IH3qrKqAv3eONjMrRzoO9jJumusi
# t91wYT1dwTe/o2FAwfmZ7qxofCXTlO008weUDY5UBw8d+BavVvnDNR08eEcCJN37
# wBJn1hhjFZZD6OxVcMEoyhIG5tJqvmBPyR5A1oYeGSL+/xBbLpAQR0oAhAivxvn4
# POX2r4M/wXGJewIDAQABo4IBfzCCAXswDAYDVR0TAQH/BAIwADBBBgNVHR8EOjA4
# MDagNKAyhjBodHRwOi8vY2V2Y3NjYTIwMjEuY3JsLmNlcnR1bS5wbC9jZXZjc2Nh
# MjAyMS5jcmwwdwYIKwYBBQUHAQEEazBpMC4GCCsGAQUFBzABhiJodHRwOi8vY2V2
# Y3NjYTIwMjEub2NzcC1jZXJ0dW0uY29tMDcGCCsGAQUFBzAChitodHRwOi8vcmVw
# b3NpdG9yeS5jZXJ0dW0ucGwvY2V2Y3NjYTIwMjEuY2VyMB8GA1UdIwQYMBaAFKxX
# yggW3D/FMRwKTdv78d6ZJy00MB0GA1UdDgQWBBSEZ5pbrzKazBz6ZCRYAbuQVlDy
# pDBKBgNVHSAEQzBBMAcGBWeBDAEDMDYGCyqEaAGG9ncCBQEHMCcwJQYIKwYBBQUH
# AgEWGWh0dHBzOi8vd3d3LmNlcnR1bS5wbC9DUFMwEwYDVR0lBAwwCgYIKwYBBQUH
# AwMwDgYDVR0PAQH/BAQDAgeAMA0GCSqGSIb3DQEBCwUAA4ICAQARtX2dHRUVP9uH
# 5FuVIvj22UsNMW2gsexnRxeL/xKxY4BXv6UkQmcgABKbYlWGH0wodaKRhrT9NyK1
# tt0P1C53zAkaiwNU7gSge7s/OPk2yA7g++iVBUDXRelZVCWi31V8yRVV+zXAFEkK
# timx00QcEhLFu1DAyjABmKouChHJyqF4dHWytvQZQHw1dfG1OSh6F82thRipW/q1
# ggWzm91QdieGlaSBZy9N881+h4V23R7oJVyWcCUtar19rBJH+/TaZHMo3VDVazhr
# CYUKMf/f6RA+iuSsPcpwMi7BZzTxawQtEe/1vG3RLVP94bWl69gY8LiTe0OU23tp
# 1PuTiHFLEXjTIb9WnBljH2PvUpJVaxQAMZrunKZvuafYSAvcEfSiRvgFg3r1UH9I
# nLjJVxsjfxjnbg5y/5SChh+OBbld7+NdvWVoFUGwqK2IuALD0O2/gtQBS/+UZuiB
# GsWpTv9PWI9/C2kPjEcBjC3sZedZOjkEJNSb8siUDItoswA+ddZRBBLdkNJCIKX4
# +lrKAAXdOqGu1QSu57G/OOLyjeUHEWx73XzoPMdgGfkcbdSBQoWBevDbC2xLzqr6
# EQZhplxGR39HZ4M4GyNFcpL4Xhuw1Gma8ldGTeuNw5ktP5Jmf9zvrjP5epHgMl6r
# jO0VG4wGU+R4e/FPQClKsyxI/cTJMjCCBrQwggScoAMCAQICEA3HrFcF/yGZLkBD
# Igw6SYYwDQYJKoZIhvcNAQELBQAwYjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERp
# Z2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTEhMB8GA1UEAxMY
# RGlnaUNlcnQgVHJ1c3RlZCBSb290IEc0MB4XDTI1MDUwNzAwMDAwMFoXDTM4MDEx
# NDIzNTk1OVowaTELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMu
# MUEwPwYDVQQDEzhEaWdpQ2VydCBUcnVzdGVkIEc0IFRpbWVTdGFtcGluZyBSU0E0
# MDk2IFNIQTI1NiAyMDI1IENBMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC
# ggIBALR4MdMKmEFyvjxGwBysddujRmh0tFEXnU2tjQ2UtZmWgyxU7UNqEY81FzJs
# Qqr5G7A6c+Gh/qm8Xi4aPCOo2N8S9SLrC6Kbltqn7SWCWgzbNfiR+2fkHUiljNOq
# nIVD/gG3SYDEAd4dg2dDGpeZGKe+42DFUF0mR/vtLa4+gKPsYfwEu7EEbkC9+0F2
# w4QJLVSTEG8yAR2CQWIM1iI5PHg62IVwxKSpO0XaF9DPfNBKS7Zazch8NF5vp7ea
# Z2CVNxpqumzTCNSOxm+SAWSuIr21Qomb+zzQWKhxKTVVgtmUPAW35xUUFREmDrMx
# SNlr/NsJyUXzdtFUUt4aS4CEeIY8y9IaaGBpPNXKFifinT7zL2gdFpBP9qh8SdLn
# Eut/GcalNeJQ55IuwnKCgs+nrpuQNfVmUB5KlCX3ZA4x5HHKS+rqBvKWxdCyQEEG
# cbLe1b8Aw4wJkhU1JrPsFfxW1gaou30yZ46t4Y9F20HHfIY4/6vHespYMQmUiote
# 8ladjS/nJ0+k6MvqzfpzPDOy5y6gqztiT96Fv/9bH7mQyogxG9QEPHrPV6/7umw0
# 52AkyiLA6tQbZl1KhBtTasySkuJDpsZGKdlsjg4u70EwgWbVRSX1Wd4+zoFpp4Ra
# +MlKM2baoD6x0VR4RjSpWM8o5a6D8bpfm4CLKczsG7ZrIGNTAgMBAAGjggFdMIIB
# WTASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBTvb1NK6eQGfHrK4pBW9i/U
# SezLTjAfBgNVHSMEGDAWgBTs1+OC0nFdZEzfLmc/57qYrhwPTzAOBgNVHQ8BAf8E
# BAMCAYYwEwYDVR0lBAwwCgYIKwYBBQUHAwgwdwYIKwYBBQUHAQEEazBpMCQGCCsG
# AQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wQQYIKwYBBQUHMAKGNWh0
# dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRSb290RzQu
# Y3J0MEMGA1UdHwQ8MDowOKA2oDSGMmh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9E
# aWdpQ2VydFRydXN0ZWRSb290RzQuY3JsMCAGA1UdIAQZMBcwCAYGZ4EMAQQCMAsG
# CWCGSAGG/WwHATANBgkqhkiG9w0BAQsFAAOCAgEAF877FoAc/gc9EXZxML2+C8i1
# NKZ/zdCHxYgaMH9Pw5tcBnPw6O6FTGNpoV2V4wzSUGvI9NAzaoQk97frPBtIj+ZL
# zdp+yXdhOP4hCFATuNT+ReOPK0mCefSG+tXqGpYZ3essBS3q8nL2UwM+NMvEuBd/
# 2vmdYxDCvwzJv2sRUoKEfJ+nN57mQfQXwcAEGCvRR2qKtntujB71WPYAgwPyWLKu
# 6RnaID/B0ba2H3LUiwDRAXx1Neq9ydOal95CHfmTnM4I+ZI2rVQfjXQA1WSjjf4J
# 2a7jLzWGNqNX+DF0SQzHU0pTi4dBwp9nEC8EAqoxW6q17r0z0noDjs6+BFo+z7bK
# SBwZXTRNivYuve3L2oiKNqetRHdqfMTCW/NmKLJ9M+MtucVGyOxiDf06VXxyKkOi
# rv6o02OoXN4bFzK0vlNMsvhlqgF2puE6FndlENSmE+9JGYxOGLS/D284NHNboDGc
# mWXfwXRy4kbu4QFhOm0xJuF2EZAOk5eCkhSxZON3rGlHqhpB/8MluDezooIs8CVn
# rpHMiD2wL40mm53+/j7tFaxYKIqL0Q4ssd8xHZnIn/7GELH3IdvG2XlM9q7WP/Uw
# gOkw/HQtyRN62JK4S1C8uw3PdBunvAZapsiI5YKdvlarEvf8EA+8hcpSM9LHJmyr
# xaFtoza2zNaQ9k+5t1wwggbNMIIEtaADAgECAhEAu/DMtbe4Mf0hrjJ3iuQMiTAN
# BgkqhkiG9w0BAQwFADCBgDELMAkGA1UEBhMCUEwxIjAgBgNVBAoTGVVuaXpldG8g
# VGVjaG5vbG9naWVzIFMuQS4xJzAlBgNVBAsTHkNlcnR1bSBDZXJ0aWZpY2F0aW9u
# IEF1dGhvcml0eTEkMCIGA1UEAxMbQ2VydHVtIFRydXN0ZWQgTmV0d29yayBDQSAy
# MB4XDTIxMDUxOTA1MzIxM1oXDTM2MDUxODA1MzIxM1owajELMAkGA1UEBhMCUEwx
# ITAfBgNVBAoTGEFzc2VjbyBEYXRhIFN5c3RlbXMgUy5BLjE4MDYGA1UEAxMvQ2Vy
# dHVtIEV4dGVuZGVkIFZhbGlkYXRpb24gQ29kZSBTaWduaW5nIDIwMjEgQ0EwggIi
# MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCUvvjwlqjf9eedRTK+NoavZlVx
# mQTPIN1nK5XQRUQWyPWwHD54wa2aD2kRDrAjBf71/T0XN9Oqms5ML/Jolgryv3JW
# 7/yahp7lg/wn3PDnNwzC1BlFMyCdEktQmUXMmE4mmxSutRY1OGGagENWPjbQ1R/x
# 4HuTByWbG+TUV7dIPxE7TMy9Npco4f09DN6QFRXdk7bZTu58kxu6MFH6mzQkC3IX
# vExfo0c/CT4/hTZD2XknHjXLzi9jzInSHS9GQqeXd6nWbkwEu7znKpNEcSElorDS
# EcRhc1OSOLjUK0x0cOw8whzQ0fWGkPoqgCslbjZoko7U7kj/Fn4TWQ8s75LHjhVa
# o+w2SWMP0ELWYIkq1ComGq3wqBD7VU6rtCDoUf237HkgRBxLXFjwp444oKWnchYM
# Z7epWX8jqntSr4QHeidL72c6XswQIIZsFglV4BO28jO02yS7w4iyY0c6PQs34GZ7
# SWphsN1DaiX9ysc6hovVYoVA94t+3p4TI1RtK9Ayh0atmfYPJqtBfk5kJ1h4nNax
# 0lZmJL9chJjZQb+7XW8MQgstiQdsegaz2QL6Edm+94N8IW6TYaalyO9LeR35A9Mo
# TLo79rdB04naTqWdPTBKh7DpS53ZAQn5dYs2per+m7ZeIxnEWaNcG+PbRh+ZslFb
# 6M75JgN43szB+/VYVQIDAQABo4IBVTCCAVEwDwYDVR0TAQH/BAUwAwEB/zAdBgNV
# HQ4EFgQUrFfKCBbcP8UxHApN2/vx3pknLTQwHwYDVR0jBBgwFoAUtqFUOQLDoD+O
# irz61PgcptE6Dv0wDgYDVR0PAQH/BAQDAgEGMBMGA1UdJQQMMAoGCCsGAQUFBwMD
# MDAGA1UdHwQpMCcwJaAjoCGGH2h0dHA6Ly9jcmwuY2VydHVtLnBsL2N0bmNhMi5j
# cmwwbAYIKwYBBQUHAQEEYDBeMCgGCCsGAQUFBzABhhxodHRwOi8vc3ViY2Eub2Nz
# cC1jZXJ0dW0uY29tMDIGCCsGAQUFBzAChiZodHRwOi8vcmVwb3NpdG9yeS5jZXJ0
# dW0ucGwvY3RuY2EyLmNlcjA5BgNVHSAEMjAwMC4GBFUdIAAwJjAkBggrBgEFBQcC
# ARYYaHR0cDovL3d3dy5jZXJ0dW0ucGwvQ1BTMA0GCSqGSIb3DQEBDAUAA4ICAQC7
# khlZjFbU+gsRdcraFFJKj1PL63MVudnm/AILuNaxm7LIZOYtrZaRWEpR0qd3uhSp
# UEI29fMBZvuu3CHXJsM58hbJFo4YoHnyDJgTkeVmc10fd1NI//cI3nRD1/Alx09y
# WhPZlze0A/Bvwc3LcZtTywfsL5tD3QAL8ZqelrHt1Eb8AnDEnAzKNbm1mwYmD/5d
# y1XkIKfsM1pmRM2ZDcJ4qpwVewE7g+4e+Y+iq42LuaHPvNAkwq0daA9Hp3qemk0c
# RFfM5257MvEsu+6w8+44mpQUOgpASVIjY3l2vdf6BUfBJfsd3pcp/2hfDnXaql9A
# oB7ps7eCZb9VXKm/u2lTRMf5esOcZFy4udsnGBaOwSSUe0chYusfJ9IjsmpoMl4C
# W5E0R/U55Bz6TpDo+gaXQJL4i/zv0UR5papGsDguBASp9g/OfnvGTeWrS9l5orEk
# tu9ExrFfPvAKxZK+4AO9bapPhMATraKo6kx6K51pS1ZO2pf1r3B+lXN5bTLgp8g/
# dygUZm2cmauBAzJA5v/iHOb0b4b4Muqc8dxgcX1jZRLtLvxnAWxkKIMfj7A4M0Jr
# N4rLBUpPFM0+Bp2sR7rtFqNZW56huaDkxO75eNRN2z4A959R5GBRnFWtgE65+B1R
# HAQQ/TOF4mU0Y3oIhVQr3rUh4tdSvIxr8QpsDKIvsDCCBu0wggTVoAMCAQICEAqA
# 7xhLjfEFgtHEdqeVdGgwDQYJKoZIhvcNAQELBQAwaTELMAkGA1UEBhMCVVMxFzAV
# BgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMUEwPwYDVQQDEzhEaWdpQ2VydCBUcnVzdGVk
# IEc0IFRpbWVTdGFtcGluZyBSU0E0MDk2IFNIQTI1NiAyMDI1IENBMTAeFw0yNTA2
# MDQwMDAwMDBaFw0zNjA5MDMyMzU5NTlaMGMxCzAJBgNVBAYTAlVTMRcwFQYDVQQK
# Ew5EaWdpQ2VydCwgSW5jLjE7MDkGA1UEAxMyRGlnaUNlcnQgU0hBMjU2IFJTQTQw
# OTYgVGltZXN0YW1wIFJlc3BvbmRlciAyMDI1IDEwggIiMA0GCSqGSIb3DQEBAQUA
# A4ICDwAwggIKAoICAQDQRqwtEsae0OquYFazK1e6b1H/hnAKAd/KN8wZQjBjMqiZ
# 3xTWcfsLwOvRxUwXcGx8AUjni6bz52fGTfr6PHRNv6T7zsf1Y/E3IU8kgNkeECqV
# Q+3bzWYesFtkepErvUSbf+EIYLkrLKd6qJnuzK8Vcn0DvbDMemQFoxQ2Dsw4vEjo
# T1FpS54dNApZfKY61HAldytxNM89PZXUP/5wWWURK+IfxiOg8W9lKMqzdIo7VA1R
# 0V3Zp3DjjANwqAf4lEkTlCDQ0/fKJLKLkzGBTpx6EYevvOi7XOc4zyh1uSqgr6Un
# bksIcFJqLbkIXIPbcNmA98Oskkkrvt6lPAw/p4oDSRZreiwB7x9ykrjS6GS3NR39
# iTTFS+ENTqW8m6THuOmHHjQNC3zbJ6nJ6SXiLSvw4Smz8U07hqF+8CTXaETkVWz0
# dVVZw7knh1WZXOLHgDvundrAtuvz0D3T+dYaNcwafsVCGZKUhQPL1naFKBy1p6ll
# N3QgshRta6Eq4B40h5avMcpi54wm0i2ePZD5pPIssoszQyF4//3DoK2O65Uck5Wg
# gn8O2klETsJ7u8xEehGifgJYi+6I03UuT1j7FnrqVrOzaQoVJOeeStPeldYRNMmS
# F3voIgMFtNGh86w3ISHNm0IaadCKCkUe2LnwJKa8TIlwCUNVwppwn4D3/Pt5pwID
# AQABo4IBlTCCAZEwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQU5Dv88jHt/f3X85Fx
# YxlQQ89hjOgwHwYDVR0jBBgwFoAU729TSunkBnx6yuKQVvYv1Ensy04wDgYDVR0P
# AQH/BAQDAgeAMBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMIMIGVBggrBgEFBQcBAQSB
# iDCBhTAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMF0GCCsG
# AQUFBzAChlFodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVz
# dGVkRzRUaW1lU3RhbXBpbmdSU0E0MDk2U0hBMjU2MjAyNUNBMS5jcnQwXwYDVR0f
# BFgwVjBUoFKgUIZOaHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1
# c3RlZEc0VGltZVN0YW1waW5nUlNBNDA5NlNIQTI1NjIwMjVDQTEuY3JsMCAGA1Ud
# IAQZMBcwCAYGZ4EMAQQCMAsGCWCGSAGG/WwHATANBgkqhkiG9w0BAQsFAAOCAgEA
# ZSqt8RwnBLmuYEHs0QhEnmNAciH45PYiT9s1i6UKtW+FERp8FgXRGQ/YAavXzWjZ
# hY+hIfP2JkQ38U+wtJPBVBajYfrbIYG+Dui4I4PCvHpQuPqFgqp1PzC/ZRX4pvP/
# ciZmUnthfAEP1HShTrY+2DE5qjzvZs7JIIgt0GCFD9ktx0LxxtRQ7vllKluHWiKk
# 6FxRPyUPxAAYH2Vy1lNM4kzekd8oEARzFAWgeW3az2xejEWLNN4eKGxDJ8WDl/FQ
# USntbjZ80FU3i54tpx5F/0Kr15zW/mJAxZMVBrTE2oi0fcI8VMbtoRAmaaslNXdC
# G1+lqvP4FbrQ6IwSBXkZagHLhFU9HCrG/syTRLLhAezu/3Lr00GrJzPQFnCEH1Y5
# 8678IgmfORBPC1JKkYaEt2OdDh4GmO0/5cHelAK2/gTlQJINqDr6JfwyYHXSd+V0
# 8X1JUPvB4ILfJdmL+66Gp3CSBXG6IwXMZUXBhtCyIaehr0XkBoDIGMUG1dUtwq1q
# mcwbdUfcSYCn+OwncVUXf53VJUNOaMWMts0VlRYxe5nK+At+DI96HAlXHAL5SlfY
# xJ7La54i71McVWRP66bW+yERNpbJCjyCYG2j+bdpxo/1Cy4uPcU3AWVPGrbn5PhD
# Bf3Froguzzhk++ami+r3Qrx5bIbY3TVzgiFI7Gq3zWcxggXaMIIF1gIBATB+MGox
# CzAJBgNVBAYTAlBMMSEwHwYDVQQKExhBc3NlY28gRGF0YSBTeXN0ZW1zIFMuQS4x
# ODA2BgNVBAMTL0NlcnR1bSBFeHRlbmRlZCBWYWxpZGF0aW9uIENvZGUgU2lnbmlu
# ZyAyMDIxIENBAhAfsijAKebq6quDMIHCRvIfMA0GCWCGSAFlAwQCAQUAoIGEMBgG
# CisGAQQBgjcCAQwxCjAIoAKAAKECgAAwGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcC
# AQQwHAYKKwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIE
# IGpbkr/uA9nNFipzICLcZQYhwFcdEbDwt9PjhnuHFqRAMA0GCSqGSIb3DQEBAQUA
# BIIBgEnv7W24/f9ZklPL6OSIpwPOetHUJAI7zfbrhvG6IP6JSXJdR5oLk99W23Qw
# WEUihv1D9W5A4H6sn5oZRM8brciSAfX1Mr49TS8jeS4NNWXK2u0JDYemB86Ekpvr
# A+teGdlOnQ97jZXrWQyC9jW3DlnRjkqCb8z2MHo89TcOrbUA5PREh8qbLgKlKslo
# mMHvPouch38YEODPpBhsPi3Nu757wt8ImTEAU5HZKK5VZ06/tcahagQ8iyAH6TT+
# K44LmRd3HbNn1/6soA/qUlDPo1QF5OMP210GwPbp5coQf4Lo9qKzCSxvy4GGs9p9
# 1tGt7NDSLFoyBcG3osHI5QYrmKITQEerG7NPSJrOVdo/YXdvS04BVdZLj0SmHrDT
# EWh0qsUbMO2Wyj/aTN8x8XWUyxdSFXZ5ipHDw9SnnRCcXpJMU2/c3mTHZJYwNz57
# 82NJAgkeR7eAqiv+gG7HYL4C+rKqT6dS8UM0ugZRRfjeIOLa4OSpfFIscLI6gsl8
# jl6Co6GCAyYwggMiBgkqhkiG9w0BCQYxggMTMIIDDwIBATB9MGkxCzAJBgNVBAYT
# AlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjFBMD8GA1UEAxM4RGlnaUNlcnQg
# VHJ1c3RlZCBHNCBUaW1lU3RhbXBpbmcgUlNBNDA5NiBTSEEyNTYgMjAyNSBDQTEC
# EAqA7xhLjfEFgtHEdqeVdGgwDQYJYIZIAWUDBAIBBQCgaTAYBgkqhkiG9w0BCQMx
# CwYJKoZIhvcNAQcBMBwGCSqGSIb3DQEJBTEPFw0yNjAyMjQxNzI4MjJaMC8GCSqG
# SIb3DQEJBDEiBCD+mnT6hIiu54JXjf7xLPM5fGgTSAH7CMONv3kEbCviJzANBgkq
# hkiG9w0BAQEFAASCAgCjA64sTUQl7sqUzQ/XnMLjwvZXuU4ItO2IK9SaJWb1I7gj
# atno90Aiz5laFumcHMxT15wtYXF6X3CQRONB1i1UTfW8hv4l0mM/lgbHCrevEtei
# svRAaJDKIvn47vo82YQS3F66ZzkCclcIza2Sj2GD4S/IDjyhI1r+bdYOF4V0B4DY
# 3gyCc8RKRySlZMvZlb1GOEf+pkv/jyUvcltTPmSvxBT07Lnu1ZEPHYQHslJAp6uH
# ikA9Lmp55T33pXEXRz8HVmqtTnrNLLjLx+Do5Um0CQZ9G9n76N3NhWFdKQ7/jeSK
# B/C8jJCz+pA5btsTEsWg7tpfET+Vfik8XjebVA6pMKafqTpu/XL3zad+z5tbV8KH
# JdmyaXjZkM62h0X7Y1SkYonQcREM2YdP+AZJ/zaJudKxtN7HpVIQDXh0zsMt5FNq
# MuF6ykWQC8OP3BDSr+tltC37sqY77DzpI59sZiNu9GyMiJ0ySGJ9qhh0mUUO48Fk
# cz+qw1Vmxp9CFG64TGnwiDNXTLG01nUs485NuwXtNE0NPaBqpF4FkLN9i5fthrDQ
# RjGft4d9jwyNdMtpajw2yf97cSjmjcPJ9ZGx6sDNHHAb/ETyQSo8IHMBqZrc6b8r
# NIoQeWNRw27Mczi5onDmFr8/H+M6RA7WN1vhhdrZLrD2SA89VNZ6fmDn6XyRtA==
# SIG # End signature block