Public/ps1/Job/Get-ApprxrRemoteJob.ps1

<#
.SYNOPSIS
    Retrieves and executes a remote job for a specified Apprxr pipeline.
 
.DESCRIPTION
    Fetches a job assignment from the remote server for the given pipeline, processes the job, and logs the result. Supports multiple job types:
      - SQL: Executes a SQL job using Start-ApprxrJobSql
      - FILE: Handles file jobs using Start-ApprxrJobFile
      - HTTP: Executes HTTP jobs using Start-ApprxrJobHtml
      - PRINT: Sends a base64 PDF/ZPL to a printer (auto-detects Zebra) and returns print queue
      - GET_PRINTERS: Returns all available printers with driver, type, and Zebra/label detection
      - GET_PRINT_QUEUE: Returns the current print queue for a printer (or all printers)
      - REFRESH_SPOOLER: Graceful Restart-Service Spooler (clears stale in-memory state)
      - RESTART_SPOOLER: Force-stop with Stop-Process escalation, then start (no file cleanup)
      - CLEAN_SPOOLER: Force-stop + delete every file in spool\PRINTERS + start (destructive recovery)
      - FULL_SPOOLER: CLEAN_SPOOLER plus restart PrintNotify and Winmgmt (WMI) - use when Get-Printer / cmdlets hang even though Spooler itself is fine
      - RESET_SPOOLER: Legacy alias for CLEAN_SPOOLER (back-compat with v1.0.464)
      - INSTALL_SUMATRAPDF: Downloads SumatraPDF portable to %LOCALAPPDATA%\SumatraPDF\ and stores the path in config (used by PRINT for silent PDF printing)
      - START_PIPELINES: Starts all Apprxr pipelines with startup validation
      - START_PIPELINE: Starts a single pipeline by name (arguments.Name)
      - STOP_PIPELINES: Stops all Apprxr jobs
      - GET_PIPELINES: Retrieves all pipeline names
      - CREATE_PIPELINE: Creates a new pipeline job
      - SELFUPDATE: Triggers Apprxr module upgrade
      - GET_VERSION: Returns Apprxr environment information from Get-ApprxrInfo, including the module version
      - LOGFILE: Returns the last N lines of the current log file
      - ERRORLOG: Returns the last N lines of the error log file (errors only)
      - ROTATE_LOG: Rotates the current log file to an archive (independent of retention)
      - GET_LOGFILES: Returns a list of all available log files with size and last modified date
      - DELETE_LOGFILE: Deletes a specific log file by name (from job.arguments.FileName)
      - POWERSHELL: Executes arbitrary PowerShell code (if provided)
    Logs all actions and sends results back to the remote server.
 
.PARAMETER pipeline
    The name of the pipeline to retrieve a job for. Use 'Default' for the default channel.
 
.EXAMPLE
    Get-ApprxrRemoteJob -pipeline 'Pipeline1'
    # Retrieves and executes a remote job for the 'Pipeline1' channel.
 
.EXAMPLE
    Get-ApprxrRemoteJob -pipeline 'Default'
    # Retrieves and executes a remote job for the default channel.
 
.NOTES
    - Used for remote job management and execution in Apprxr automation.
    - Handles multiple job types and actions as described above.
    - Sends job results back to the remote server via Get-ApprxrResult.
    - Logs all received jobs and actions for auditing.
    - Errors are returned in the result object and logged.
#>

function Get-ApprxrRemoteJob{
    param ($pipeline)   

        if (-not [string]::IsNullOrEmpty($pipeline) -and $pipeline -ne "Default") {
            $channelId = "?channelId=$($pipeline)"
        }

        $deliverResult = "api/services/app/RemoteControl/SetDeliveries"
        $getTask= "api/services/app/RemoteControl/GetAssignment$channelId"
    

        #Get the job from the server
        $job = (Get-ApprxrResult -request $getTask).result
        if (-not $job){
            return
        }
        try {

            # Log the job but truncate large payloads (Base64File can be many MB)
            try {
                $jobLogSafe = $job | ConvertTo-Json -Depth 2 -Compress -ErrorAction Stop
                if ($jobLogSafe.Length -gt 2000) { $jobLogSafe = $jobLogSafe.Substring(0, 2000) + '... [truncated]' }
                Log "Receive the following job: $jobLogSafe"
                $jobLogSafe = $null
            } catch {
                Log "Receive job: Action=$($job.Action) Id=$($job.Id) (could not serialize: $_)"
            }
            if ($job.Action -eq "SQL") {
                $sqlResult = Start-ApprxrJobSql -taskInformation $job
                $result = [PSCustomObject]@{
                    data = $sqlResult.data
                    success = $sqlResult.success
                    message = $sqlResult.message
                    Id = $job.Id
                }
                # Truncate SQL log to avoid serialising huge DataTable results.
                $sqlLogJson = $result | ConvertTo-Json -Depth 3
                if ($sqlLogJson.Length -gt 10000) { $sqlLogJson = $sqlLogJson.Substring(0, 10000) + '... [truncated]' }
                Log ("SQL result: " + $sqlLogJson)
                $sqlLogJson = $null
            } elseif ($job.Action -eq "FILE") {
                $fileResult = Start-ApprxrJobFile -Base64File ($job.arguments.Base64File) -FileLocation ($job.arguments.FileLocation) -Mode ($job.arguments.Mode) -FileName ($job.arguments.FileName)
                $result = [PSCustomObject]@{
                    data = $fileResult.data
                    success = $fileResult.success
                    message = $fileResult.message
                    Id = $job.Id
                }
            }  elseif ($job.Action -eq "HTTP") {
                $httpResult = Start-ApprxrJobHtml -taskInformation $job
                $result = [PSCustomObject]@{
                    data = $httpResult.data
                    success = $httpResult.success
                    message = $httpResult.message
                    Id = $job.Id
                }
            } elseif ($job.Action -eq "PRINT") {
                # Wrap the entire PRINT dispatch in its own try/catch.
                # Operators reported the worker failing PRINT with the
                # outer 'There is a unhandled error: Argument type cannot
                # be System.Void.' - that comes from .NET method binding
                # somewhere in the deep print path (e.g. void leaking from
                # WriteAllBytes or SendBytes). The PRINT branch must
                # always produce a structured failure result rather than
                # letting the runtime exception escape to the outer
                # dispatch and surface as 'unhandled error' to the server.
                try {
                    $printResult = Start-ApprxrJobPrint -taskInformation $job
                    if ($null -eq $printResult) {
                        # Defensive: if Start-ApprxrJobPrint somehow returns
                        # nothing (shouldn't happen, but a runtime void could
                        # leak), synthesize a clear failure.
                        $printResult = @{
                            success = $false
                            message = "Start-ApprxrJobPrint returned no result for job '$($job.Id)' - check the host log for [PDF] errors."
                            data    = $null
                        }
                    }
                    $resultData = if ($null -ne $printResult.data) { @($printResult.data) } else { @() }
                    $result = [PSCustomObject]@{
                        data    = $resultData
                        success = [bool]$printResult.success
                        message = "$($printResult.message)"
                        Id      = $job.Id
                    }
                } catch {
                    # Include the script stack trace AND the offending source
                    # line in the log so we can pinpoint which expression
                    # leaked a void. Operators reported the void error
                    # without enough context to locate it; previously we
                    # only logged Exception.Message.
                    $stackOneLine = ($_.ScriptStackTrace -replace "`r", '' -replace "`n", ' >> ')
                    $invokeInfo   = if ($_.InvocationInfo -and $_.InvocationInfo.PositionMessage) {
                        ($_.InvocationInfo.PositionMessage -replace "`r", '' -replace "`n", ' | ')
                    } else { '<no PositionMessage>' }
                    Log -msg "[Get-ApprxrRemoteJob] PRINT dispatch threw: $($_.Exception.Message) | Type=$($_.Exception.GetType().FullName) | At=$invokeInfo | Stack=$stackOneLine" -IsError
                    $result = [PSCustomObject]@{
                        data    = @()
                        success = $false
                        message = "PRINT failed before completing: $($_.Exception.Message) (at $invokeInfo)"
                        Id      = $job.Id
                    }
                }
            } elseif ($job.Action -eq "GET_PRINTERS") {
                try {
                    $printerList = @(Get-ApprxrPrinters)
                    $result = [PSCustomObject]@{
                        success = $true
                        message = "$($printerList.Count) printer(s) found"
                        data    = $printerList
                        Id = $job.Id
                    }
                } catch {
                    $result = [PSCustomObject]@{
                        success = $false
                        message = "Failed to list printers: $_"
                        Id = $job.Id
                    }
                }
            } elseif ($job.Action -eq "GET_PRINT_QUEUE") {
                try {
                    $queuePrinterName = if ($job.arguments -and $job.arguments.PrinterName) { $job.arguments.PrinterName } else { $null }
                    $queueData = Get-ApprxrPrintQueue -PrinterName $queuePrinterName
                    $result = [PSCustomObject]@{
                        success = $true
                        message = "$($queueData.Count) entry/entries in print queue"
                        data    = @($queueData)
                        Id = $job.Id
                    }
                } catch {
                    $result = [PSCustomObject]@{
                        success = $false
                        message = "Failed to get print queue: $_"
                        Id = $job.Id
                    }
                }
            } elseif ($job.Action -eq "INSTALL_SUMATRAPDF") {
                # Provision SumatraPDF on the host so PRINT jobs can use the
                # silent-print path. Optional arguments:
                # InstallPath - override default %LOCALAPPDATA%\SumatraPDF
                # DownloadUrl - override default download URL
                # Force - re-download even if already present
                try {
                    $sumatraParams = @{}
                    if ($job.arguments) {
                        if ($job.arguments.InstallPath) { $sumatraParams['InstallPath'] = $job.arguments.InstallPath }
                        if ($job.arguments.DownloadUrl) { $sumatraParams['DownloadUrl'] = $job.arguments.DownloadUrl }
                        if ($job.arguments.Force)       { $sumatraParams['Force']       = [bool]$job.arguments.Force }
                    }
                    $installResult = Install-ApprxrSumatra @sumatraParams
                    $msg = if ($installResult.Success) {
                        if ($installResult.AlreadyInstalled) {
                            "SumatraPDF already installed at $($installResult.Path); config $(if ($installResult.ConfigStored) { 'updated' } else { 'NOT updated' })."
                        } else {
                            "SumatraPDF installed at $($installResult.Path); config $(if ($installResult.ConfigStored) { 'updated' } else { 'NOT updated' })."
                        }
                    } else {
                        "INSTALL_SUMATRAPDF failed: $(($installResult.Steps | Select-Object -Last 1))"
                    }
                    $result = [PSCustomObject]@{
                        success = [bool]$installResult.Success
                        message = $msg
                        data    = @($installResult)
                        Id      = $job.Id
                    }
                } catch {
                    $result = [PSCustomObject]@{
                        success = $false
                        message = "INSTALL_SUMATRAPDF failed: $($_.Exception.Message)"
                        Id      = $job.Id
                    }
                }
            } elseif ($job.Action -in @("REFRESH_SPOOLER", "RESTART_SPOOLER", "CLEAN_SPOOLER", "FULL_SPOOLER", "RESET_SPOOLER")) {
                # Four escalating recovery actions, plus the legacy RESET_SPOOLER
                # alias kept for back-compat with v1.0.464:
                # REFRESH_SPOOLER - graceful Restart-Service (clears stale state)
                # RESTART_SPOOLER - force-stop with Stop-Process escalation
                # CLEAN_SPOOLER - force-stop + wipe spool folder (destructive)
                # FULL_SPOOLER - CLEAN + restart PrintNotify + restart Winmgmt
                # (use when cmdlets hang but Spooler is fine)
                # RESET_SPOOLER - alias for CLEAN_SPOOLER
                $spoolerMode = switch ($job.Action) {
                    'REFRESH_SPOOLER' { 'Refresh' }
                    'RESTART_SPOOLER' { 'Restart' }
                    'FULL_SPOOLER'    { 'Full' }
                    default           { 'Clean' }
                }
                try {
                    $resetResult = Reset-ApprxrSpooler -Mode $spoolerMode
                    $result = [PSCustomObject]@{
                        success = [bool]$resetResult.Success
                        message = "$($job.Action) [$spoolerMode]: final status '$($resetResult.FinalStatus)'; removed $($resetResult.StuckFilesRemoved) stuck file(s)."
                        data    = @($resetResult)
                        Id      = $job.Id
                    }
                } catch {
                    $result = [PSCustomObject]@{
                        success = $false
                        message = "$($job.Action) failed: $($_.Exception.Message)"
                        Id      = $job.Id
                    }
                }
            } elseif ($job.Action -eq "START_PIPELINES") {
                try {
                    $startResult = Start-ApprxrPipelines
                    $allOk = $startResult.Failed.Count -eq 0
                    $result = [PSCustomObject]@{
                        success = $allOk
                        message = "Started: $($startResult.Started.Count), Failed: $($startResult.Failed.Count)" +
                                  $(if ($startResult.Failed.Count -gt 0) { " ($($startResult.Failed -join ', '))" } else { '' })
                        data    = @($startResult)
                        Id = $job.Id
                    }
                } catch {
                    $result = [PSCustomObject]@{
                        success = $false
                        message = "Failed to start pipelines: $_"
                        Id = $job.Id
                    }
                }
            } elseif ($job.Action -eq "START_PIPELINE") {
                try {
                    if (-not $job.arguments -or -not $job.arguments.Name) {
                        throw "Missing required argument: arguments.Name. Expected: { `"Action`": `"START_PIPELINE`", `"arguments`": { `"Name`": `"PipelineName`" } }"
                    }
                    $startResult = Start-ApprxrPipelines -Name $job.arguments.Name
                    $ok = $startResult.Failed.Count -eq 0
                    $result = [PSCustomObject]@{
                        success = $ok
                        message = if ($ok) { "Pipeline '$($job.arguments.Name)' started" } else { "Pipeline '$($job.arguments.Name)' failed to start" }
                        data    = @($startResult)
                        Id = $job.Id
                    }
                } catch {
                    $result = [PSCustomObject]@{
                        success = $false
                        message = "Failed to start pipeline: $_"
                        Id = $job.Id
                    }
                }
            } elseif ($job.Action -eq "GET_PIPELINES") {
                $pipelineNames = @(Get-ApprxrPipelines)
                $result = [PSCustomObject]@{
                    success = $true
                    message = "Found $($pipelineNames.Count) pipeline(s): $($pipelineNames -join ', ')"
                    data    = $pipelineNames
                    Id = $job.Id
                }
            } elseif ($job.Action -eq "STOP_PIPELINES") {
                try {
                    $stopResult = Stop-ApprxrJobs
                    $allOk = $stopResult.Failed.Count -eq 0
                    $result = [PSCustomObject]@{
                        success = $allOk
                        message = "Stopped: $($stopResult.Stopped.Count), Failed: $($stopResult.Failed.Count)" +
                                  $(if ($stopResult.Failed.Count -gt 0) { " ($($stopResult.Failed -join ', '))" } else { '' })
                        data    = @($stopResult)
                        Id = $job.Id
                    }
                } catch {
                    $result = [PSCustomObject]@{
                        success = $false
                        message = "Failed to stop pipelines: $_"
                        Id = $job.Id
                    }
                }
            } elseif ($job.Action -eq "CREATE_PIPELINE") {
                try {
                    if (-not $job.arguments -or -not $job.arguments.Name) {
                        throw "Missing required argument: arguments.Name. Expected: { `"Action`": `"CREATE_PIPELINE`", `"arguments`": { `"Name`": `"PipelineName`", `"WaitTime`": 10, `"DirectProcessing`": false } }"
                    }
                    $pipelineSleep = if ($job.arguments.WaitTime) { [int]$job.arguments.WaitTime } else { 10 }
                    $pipelineDirect = if ($job.arguments.DirectProcessing) { [bool]$job.arguments.DirectProcessing } else { $false }
                    Create-ApprxrPipelineJob -jobParam $job.arguments
                    $result = [PSCustomObject]@{
                        success = $true
                        message = "Pipeline '$($job.arguments.Name)' created (Sleep=$pipelineSleep, DirectProcessing=$pipelineDirect)"
                        Id = $job.Id
                    }
                } catch {
                    $result = [PSCustomObject]@{
                        success = $false
                        message = "Failed to create pipeline: $_"
                        Id = $job.Id
                    }
                }
            } elseif ($job.Action -eq "SELFUPDATE") {
                $result = [PSCustomObject]@{
                    success = $true
                    message = Start-ApprxrUpgrade
                    Id = $job.Id
                }
            } elseif ($job.Action -eq "GET_VERSION") {
                $info = Get-ApprxrInfo
                $result = [PSCustomObject]@{
                    success = $true
                    message = "Apprxr status generated on $((Get-Date).ToString('yyyy-MM-dd HH:mm:ss')) (version $($info.Version))."
                    data = @($info)
                    Id = $job.Id
                }
            } elseif ($job.Action -eq "LOGFILE") {
                #$lines = if ($job.arguments.Lines) { $job.arguments.Lines } else { 50 }
                $lines = 250
                $logContent = [string](Get-ApprxrLog -Tail $lines | Out-String)
                $result = [PSCustomObject]@{
                    success = $true
                    message = $logContent
                    Id = $job.Id
                }
            } elseif ($job.Action -eq "ERRORLOG") {
                $lines = 250
                $logContent = [string](Get-ApprxrLog -Tail $lines -ErrorLog | Out-String)
                $result = [PSCustomObject]@{
                    success = $true
                    message = if ([string]::IsNullOrWhiteSpace($logContent)) { "(no errors recorded)" } else { $logContent }
                    Id = $job.Id
                }
            } elseif ($job.Action -eq "ROTATE_LOG") {
                try {
                    Log "ROTATE_LOG: Rotating current log file on demand"
                    Move-ApprxrLogFile
                    $result = [PSCustomObject]@{
                        success = $true
                        message = "Log file rotated successfully"
                        Id = $job.Id
                    }
                } catch {
                    $result = [PSCustomObject]@{
                        success = $false
                        message = "Failed to rotate log file: $_"
                        Id = $job.Id
                    }
                }
            } elseif ($job.Action -eq "GET_LOGFILES") {
                try {
                    $logFolder = if ($LogManagement -and $LogManagement.LogFolder) {
                        Split-Path $LogManagement.LogFolder -Parent
                    } else { $null }

                    if (-not $logFolder -or -not (Test-Path $logFolder)) {
                        $result = [PSCustomObject]@{
                            success = $false
                            message = "Log folder not found: $logFolder"
                            Id = $job.Id
                        }
                    } else {
                        $logFiles = @(Get-ChildItem -Path $logFolder -File -ErrorAction Stop |
                            Where-Object { $_.Name -like 'logfile*' } |
                            Sort-Object LastWriteTime -Descending |
                            ForEach-Object {
                                [PSCustomObject]@{
                                    Name         = $_.Name
                                    SizeKB       = [math]::Round($_.Length / 1KB, 1)
                                    LastModified = $_.LastWriteTime.ToString('yyyy-MM-dd HH:mm:ss')
                                }
                            })
                        $result = [PSCustomObject]@{
                            success = $true
                            message = "$($logFiles.Count) log file(s) found"
                            data    = $logFiles
                            Id      = $job.Id
                        }
                    }
                } catch {
                    $result = [PSCustomObject]@{
                        success = $false
                        message = "Failed to list log files: $_"
                        Id = $job.Id
                    }
                }
            } elseif ($job.Action -eq "DELETE_LOGFILE") {
                try {
                    $fileNameToDelete = $job.arguments.FileName
                    if ([string]::IsNullOrWhiteSpace($fileNameToDelete)) {
                        $result = [PSCustomObject]@{
                            success = $false
                            message = "DELETE_LOGFILE: FileName argument is required. Expected: { `"Action`": `"DELETE_LOGFILE`", `"arguments`": { `"FileName`": `"logfile.txt_1712678400`" } }"
                            Id = $job.Id
                        }
                    } else {
                        $logFolder = if ($LogManagement -and $LogManagement.LogFolder) {
                            Split-Path $LogManagement.LogFolder -Parent
                        } else { $null }

                        # Prevent path traversal — only allow plain file names
                        if ($fileNameToDelete -match '[/\\]') {
                            $result = [PSCustomObject]@{
                                success = $false
                                message = "DELETE_LOGFILE: Path separators not allowed in FileName. Use plain file name only, e.g. `"logfile.txt_1712678400`""
                                Id = $job.Id
                            }
                        } else {
                            $targetFile = Join-Path $logFolder $fileNameToDelete

                            # Guard: only delete files that are inside the log folder and match logfile pattern
                            if (-not (Test-Path $targetFile)) {
                                $result = [PSCustomObject]@{
                                    success = $false
                                    message = "DELETE_LOGFILE: File not found: $fileNameToDelete"
                                    Id = $job.Id
                                }
                            } elseif ($fileNameToDelete -notlike 'logfile*') {
                                $result = [PSCustomObject]@{
                                    success = $false
                                    message = "DELETE_LOGFILE: Only log files (logfile*) can be deleted"
                                    Id = $job.Id
                                }
                            } else {
                                Log "DELETE_LOGFILE: Deleting $fileNameToDelete"
                                Remove-Item -Path $targetFile -Force -ErrorAction Stop
                                $result = [PSCustomObject]@{
                                    success = $true
                                    message = "Deleted: $fileNameToDelete"
                                    Id = $job.Id
                                }
                            }
                        }
                    }
                } catch {
                    $result = [PSCustomObject]@{
                        success = $false
                        message = "Failed to delete log file: $_"
                        Id = $job.Id
                    }
                }
            } elseif ($job.Action -like 'POWERSHELL' -and $job.Command) {
                try {
                    $psResult = Invoke-Expression $job.Command
                    $result = [PSCustomObject]@{
                        success = $true
                        message = $psResult
                        executedCommand = $job.Command
                        Id = $job.Id
                    }
                } catch {
                    $result = [PSCustomObject]@{
                        success = $false
                        message = ("PowerShell command failed: " + $_)
                        executedCommand = $job.Command
                    }
                }
            } else {
                $result = [PSCustomObject]@{
                    success = $false
                    message = "Unknown action: '$($job.Action)'. Valid actions: SQL, FILE, HTTP, PRINT, GET_PRINTERS, GET_PRINT_QUEUE, REFRESH_SPOOLER, RESTART_SPOOLER, CLEAN_SPOOLER, FULL_SPOOLER, RESET_SPOOLER, INSTALL_SUMATRAPDF, START_PIPELINES, START_PIPELINE, STOP_PIPELINES, GET_PIPELINES, CREATE_PIPELINE, SELFUPDATE, GET_VERSION, LOGFILE, ERRORLOG, ROTATE_LOG, GET_LOGFILES, DELETE_LOGFILE, POWERSHELL"
                }
            }
        } catch {
            $result = [PSCustomObject]@{
                success = $false
                message = ("There is a unhandled error: " + $_)
            }
        
        }

        # Send result to server (DeliveryInputDto)
        # public class DeliveryInputDto
        # {
        # public Guid Id { get; set; }
        # public JArray Data { get; set; }
        # public string Message { get; set; }
        # public bool Success { get; set; }
        # public DateTime CreationDate { get; set; }
        # }
        # Id must always be the original job.Id.
        $deliveryData = @()
        $deliveryMessage = $null
        $deliverySuccess = $false

        if ($null -ne $result -and $null -ne $result.PSObject.Properties['success']) {
            $deliverySuccess = [bool]$result.success
        }

        if ($null -ne $result -and $null -ne $result.PSObject.Properties['data']) {
            $deliveryData = @($result.data)
        }

        if ($null -ne $result -and $null -ne $result.PSObject.Properties['message']) {
            $messageValue = $result.message
            if ($messageValue -is [string]) {
                $deliveryMessage = [string]$messageValue
            } elseif ($null -ne $messageValue -and $deliveryData.Count -eq 0) {
                # This is a fallback and will be removed later. If message is not a string, it will be transferred via Data (JArray). But if message is not a string and data is empty, we will try to convert message to string and send it via Message property for backward compatibility.
                $deliveryMessage = $messageValue
                # Non-string payloads are transferred via Data (JArray).
                $deliveryData = @($messageValue)
            }
        }

        # --- Clear large incoming payloads from memory before delivery ---
        # Base64File can be many MB; keeping it in $job while building the
        # delivery body doubles memory usage and can crash the pipeline.
        if ($job.arguments -and $job.arguments.PSObject.Properties['Base64File']) {
            $job.arguments.Base64File = $null
        }

        # Truncate delivery message if too large (e.g. huge log output)
        if ($null -ne $deliveryMessage -and $deliveryMessage.Length -gt 50000) {
            $deliveryMessage = $deliveryMessage.Substring(0, 50000) + "`n... [truncated at 50 KB]"
        }

        $deliveryInput = [PSCustomObject]@{
            Id = $job.Id
            Data = $deliveryData
            Message = $deliveryMessage
            Success = $deliverySuccess
            CreationDate = (Get-Date).ToString('o')
        }

        # Serialize once and reuse for both logging and the API call to avoid
        # creating two multi-MB JSON copies of potentially large payloads.
        $deliveryBody = $deliveryInput | ConvertTo-Json -Depth 5
        $deliveryInput = $null

        # Safety check: if the serialized body is extremely large, truncate
        # to avoid OutOfMemoryException on the HTTP POST and 413 from the server.
        $maxDeliveryBytes = 5 * 1024 * 1024  # 5 MB
        if ($deliveryBody.Length -gt $maxDeliveryBytes) {
            Log -msg "[Get-ApprxrRemoteJob] Delivery payload too large ($([math]::Round($deliveryBody.Length / 1MB, 1)) MB). Sending truncated result." -IsError
            $deliveryBody = $null
            $deliveryBody = [PSCustomObject]@{
                Id = $job.Id
                Data = @()
                Message = "Result truncated: payload exceeded $([math]::Round($maxDeliveryBytes / 1MB)) MB. Job completed with Success=$deliverySuccess."
                Success = $deliverySuccess
                CreationDate = (Get-Date).ToString('o')
            } | ConvertTo-Json -Depth 5
        }

        try {
            Get-ApprxrResult -request $deliverResult -body $deliveryBody
        } catch {
            $statusCode = 0
            if ($_.Exception.Response) { try { $statusCode = [int]$_.Exception.Response.StatusCode } catch {} }
            if ($statusCode -eq 0 -and $_.Exception.Message -match '\(413\)') { $statusCode = 413 }
            if ($statusCode -eq 413) {
                Log -msg "[Get-ApprxrRemoteJob] Delivery of job $($job.Id) rejected with 413 (Request Entity Too Large). Payload will not be retried." -IsError
            } else {
                # Log but don't rethrow — a delivery failure must NOT kill the pipeline thread
                Log -msg "[Get-ApprxrRemoteJob] Delivery failed for job $($job.Id): $($_.Exception.Message)" -IsError
            }
        }

        # Release large intermediate objects to avoid accumulation across
        # the tight while($true) loop in the calling job thread.
        Remove-Variable result -ErrorAction SilentlyContinue
        $job            = $null
        $sqlResult      = $null
        $httpResult     = $null
        $fileResult     = $null
        $printResult    = $null
        $deliveryInput  = $null
        $deliveryBody   = $null
        $deliveryData   = $null
        $deliveryMessage = $null
        $deliveryData   = $null
        $deliveryMessage = $null
}
# SIG # Begin signature block
# MIInKgYJKoZIhvcNAQcCoIInGzCCJxcCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCASXi+4GZTIWk9a
# hUhspxZfZgsfSXw0juAa7tJ9cyd9AKCCIKYwggWNMIIEdaADAgECAhAOmxiO+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
# IPtbCD/Qw8in/QQ9jaUXcbv2aUuVMRcoPbsUHoTa1mzYMA0GCSqGSIb3DQEBAQUA
# BIIBgH4+87HTSaPUyGHr3k4vegRInlX6mB6jT3EBD63skDSinjIwPOTnMovIIGgG
# HpbHIRT/lZKJ1kOUe7i6ssgKpHRf7puRKysckKFjCUk9L0AINJlP17yLOMMbP/zN
# EI01KhbvFTq/mHlyZc9ngVNpaQnK1TYqe8dKM8gidngr4iwf6qsD/oD9DBy942T6
# in4H62D4mcfWADGNbAaUkVmAPh6g7/yExFOcAwi2Hvr+G09HEeV/ZPlGdB2lgnMY
# /9CRzL7EVWKnWndvrwImPqsbNQo5bKW0glnDe+bGaCFTtzCnCaiimaxCiykO2K8k
# 2u/bKcE/xHUIVUt3FN8B8i5KCWQVzin1eMPqyfSDn0QhgniBLeKO06NhM5FI8ksG
# aFynNyyHIA+O7BMeyUPw0mrMtqvAiG1l6Bn5OOrU173ihDbf/x88Lcg5sFts9rYf
# YNRt8bXYDJMeuyetq44L//19NLNNmJkq6OiXI6arqEDbhwxIHBFLvZFMKtFHLOKu
# ioRFtaGCAyYwggMiBgkqhkiG9w0BCQYxggMTMIIDDwIBATB9MGkxCzAJBgNVBAYT
# AlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjFBMD8GA1UEAxM4RGlnaUNlcnQg
# VHJ1c3RlZCBHNCBUaW1lU3RhbXBpbmcgUlNBNDA5NiBTSEEyNTYgMjAyNSBDQTEC
# EAqA7xhLjfEFgtHEdqeVdGgwDQYJYIZIAWUDBAIBBQCgaTAYBgkqhkiG9w0BCQMx
# CwYJKoZIhvcNAQcBMBwGCSqGSIb3DQEJBTEPFw0yNjA1MDUxOTAwMjZaMC8GCSqG
# SIb3DQEJBDEiBCAHSjVJBqpf0fVXKpXqL21KCuRQwbIshg8CAko9Kh2bTzANBgkq
# hkiG9w0BAQEFAASCAgAOdAbdVQrePuVG0ZT93Vu6sHwIdSkwgutwy9Q7erARbbWB
# /dIScNlayHs5H0XI2AdqjfP5+zCUdTJEdQPSgCb/21arTaDEluixubgFu3mJJCdZ
# PFSYuI1eulEzjhyfBcK5T7FgF74dUvl8CWKVR/Hq07PN1hI9lQpHX7Y1v8RNdlwD
# cb2I07Ct6LKqKLjbbuPbEHCgMl65jVyrenM1rCX9HROitAqhujql8xSSAwXU8bMq
# mvCU0gYTnOhMpGujBSbPQeLG7MytoNNVfLBScXISqw2tfsPGUuBeyq5lfaHjxbj5
# DbdGqBgQzIlW2GiKoE234ZEaI0Pb+9PrJueIDXwhC/wJNc7agJ0e7W4g9kMzYnHg
# qTYJaMlmccJM6hPN0rnrsYGYixpgNMav0chxNAqXJyoO3hVxUeiBiGISRrzesmHM
# HCaHxgGdhHtRoaorKzL5z06viMw0vHFfY6UihXNgT5k+JZH06MX3EdOpFjCXkY6v
# FprTTfBkI9Qiom8uteiLRKfVeaH1F8R4G2UG8O9POCOEi96p82WAR4Y/5frDnOn/
# 5pSq06DoiJ/+RDOs8+OBLjIQkdaw515DHjLO2e+r/8vfGqqGPgZEcpBYWA6zCdO0
# wKGS9tGDIhGa9FlVRHa68Zy9ICJ1Ub9U0nNlqXxZmN++YeP85GSlN70BsvR4CQ==
# SIG # End signature block