remote controller working!

This commit is contained in:
Nathan
2025-12-02 16:23:31 -07:00
parent e568e02cdf
commit 428d9bac6b
2 changed files with 2560 additions and 135 deletions

File diff suppressed because one or more lines are too long

View File

@@ -45,50 +45,52 @@ $logStream = [System.IO.FileStream]::new(
$logWriter = [System.IO.StreamWriter]::new($logStream, [System.Text.Encoding]::UTF8) $logWriter = [System.IO.StreamWriter]::new($logStream, [System.Text.Encoding]::UTF8)
$logWriter.AutoFlush = $true $logWriter.AutoFlush = $true
if (-not ("UnifiedWorkers.WorkerLogSink" -as [type])) { # Create C# event handler class that doesn't require PowerShell runspace
Add-Type -Namespace UnifiedWorkers -Name WorkerLogSink -MemberDefinition @" if (-not ("UnifiedWorkers.ProcessLogHandler" -as [type])) {
$csharpCode = @'
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
public sealed class WorkerLogSink namespace UnifiedWorkers
{
public sealed class ProcessLogHandler
{ {
private readonly TextWriter _writer; private readonly TextWriter _writer;
private readonly string _prefix; private readonly string _prefix;
private readonly object _sync = new object(); private readonly object _lock = new object();
public WorkerLogSink(TextWriter writer, string prefix) public ProcessLogHandler(TextWriter writer, string prefix)
{ {
if (writer == null) _writer = writer ?? throw new ArgumentNullException("writer");
{ _prefix = prefix ?? throw new ArgumentNullException("prefix");
throw new ArgumentNullException(\"writer\");
}
if (prefix == null)
{
throw new ArgumentNullException(\"prefix\");
} }
_writer = writer; public void OnDataReceived(object sender, DataReceivedEventArgs e)
_prefix = prefix;
}
public void OnData(object sender, DataReceivedEventArgs e)
{ {
if (e == null || string.IsNullOrEmpty(e.Data)) if (e == null || string.IsNullOrEmpty(e.Data))
{ {
return; return;
} }
lock (_sync) lock (_lock)
{
try
{ {
var timestamp = DateTime.UtcNow.ToString("u"); var timestamp = DateTime.UtcNow.ToString("u");
var line = string.Format("[{0} {1}] {2}", _prefix, timestamp, e.Data); _writer.WriteLine(string.Format("[{0} {1}] {2}", _prefix, timestamp, e.Data));
_writer.WriteLine(line);
_writer.Flush(); _writer.Flush();
} }
catch
{
// Ignore write errors to prevent cascading failures
} }
} }
"@ }
}
}
'@
Add-Type -TypeDefinition $csharpCode -ErrorAction Stop
} }
function Write-LogLine { function Write-LogLine {
@@ -99,20 +101,39 @@ function Write-LogLine {
if (-not $logWriter) { return } if (-not $logWriter) { return }
$timestamp = (Get-Date).ToString('u') $timestamp = (Get-Date).ToString('u')
$logWriter.WriteLine("[{0} {1}] {2}" -f $Prefix, $timestamp, $Message) $logWriter.WriteLine("[$Prefix $timestamp] $Message")
} }
function Write-ControllerLog { function Write-ControllerLog {
param([string]$Message) param([string]$Message)
Write-LogLine -Prefix 'CTRL' -Message $Message Write-LogLine -Prefix 'CTRL' -Message $Message
} }
function Write-FatalLog {
param([string]$Message)
try {
Write-ControllerLog $Message
}
catch {
$timestamp = (Get-Date).ToString('u')
$fallback = "[CTRL $timestamp] $Message"
try {
[System.IO.File]::AppendAllText($logPath, $fallback + [Environment]::NewLine, [System.Text.Encoding]::UTF8)
}
catch {
# last resort: write to host
Write-Error $fallback
}
}
}
# endregion # endregion
# region Helpers # region Helpers
function Resolve-PayloadBase64 { function Resolve-PayloadBase64 {
if ($PayloadBase64) { if ($PayloadBase64) {
return $PayloadBase64 return $PayloadBase64.Trim()
} }
if ($PayloadBase64Path) { if ($PayloadBase64Path) {
@@ -120,7 +141,12 @@ function Resolve-PayloadBase64 {
throw "Payload file '$PayloadBase64Path' not found." throw "Payload file '$PayloadBase64Path' not found."
} }
return (Get-Content -Path $PayloadBase64Path -Raw) $content = Get-Content -Path $PayloadBase64Path -Raw
if ([string]::IsNullOrWhiteSpace($content)) {
throw "Payload file '$PayloadBase64Path' is empty."
}
return $content.Trim()
} }
throw "No payload data provided to controller." throw "No payload data provided to controller."
@@ -168,6 +194,7 @@ function Get-PendingCommands {
} }
# endregion # endregion
try {
# record initial state before launching worker # record initial state before launching worker
Write-Metadata -Status 'initializing' -WorkerPid $null -ControllerPid $PID -Restarts 0 Write-Metadata -Status 'initializing' -WorkerPid $null -ControllerPid $PID -Restarts 0
@@ -176,13 +203,22 @@ $PayloadBase64 = $resolvedPayloadBase64
try { try {
# Write payload script to disk # Write payload script to disk
# The payload is base64-encoded UTF-16 (Unicode), so decode it properly
Write-ControllerLog "Decoding payload base64 (length: $($resolvedPayloadBase64.Length))"
$payloadBytes = [Convert]::FromBase64String($resolvedPayloadBase64) $payloadBytes = [Convert]::FromBase64String($resolvedPayloadBase64)
[IO.File]::WriteAllBytes($payloadPath, $payloadBytes) Write-ControllerLog "Decoded payload to $($payloadBytes.Length) bytes"
Write-ControllerLog "Payload written to $payloadPath"
# Convert UTF-16 bytes back to string, then write as UTF-8 (PowerShell's preferred encoding)
$payloadText = [Text.Encoding]::Unicode.GetString($payloadBytes)
[IO.File]::WriteAllText($payloadPath, $payloadText, [Text.Encoding]::UTF8)
Write-ControllerLog "Payload written to $payloadPath ($($payloadText.Length) characters)"
} }
catch { catch {
Write-Error "Unable to write payload: $($_.Exception.Message)" Write-FatalLog "Unable to write payload: $($_.Exception.Message)"
exit 1 if ($_.Exception.InnerException) {
Write-FatalLog "Inner exception: $($_.Exception.InnerException.Message)"
}
throw
} }
$restartCount = 0 $restartCount = 0
@@ -217,10 +253,11 @@ while ($restartCount -le $MaxRestarts) {
Write-ControllerLog "Worker process started with PID $($workerProcess.Id)" Write-ControllerLog "Worker process started with PID $($workerProcess.Id)"
Write-Metadata -Status 'running' -WorkerPid $workerProcess.Id -ControllerPid $controllerPid -Restarts $restartCount Write-Metadata -Status 'running' -WorkerPid $workerProcess.Id -ControllerPid $controllerPid -Restarts $restartCount
$stdoutSink = [UnifiedWorkers.WorkerLogSink]::new($logWriter, 'OUT') $stdoutHandler = [UnifiedWorkers.ProcessLogHandler]::new($logWriter, 'OUT')
$stderrSink = [UnifiedWorkers.WorkerLogSink]::new($logWriter, 'ERR') $stderrHandler = [UnifiedWorkers.ProcessLogHandler]::new($logWriter, 'ERR')
$outputHandler = [System.Diagnostics.DataReceivedEventHandler]$stdoutSink.OnData
$errorHandler = [System.Diagnostics.DataReceivedEventHandler]$stderrSink.OnData $outputHandler = [System.Diagnostics.DataReceivedEventHandler]$stdoutHandler.OnDataReceived
$errorHandler = [System.Diagnostics.DataReceivedEventHandler]$stderrHandler.OnDataReceived
$workerProcess.add_OutputDataReceived($outputHandler) $workerProcess.add_OutputDataReceived($outputHandler)
$workerProcess.add_ErrorDataReceived($errorHandler) $workerProcess.add_ErrorDataReceived($errorHandler)
@@ -274,11 +311,20 @@ while ($restartCount -le $MaxRestarts) {
Write-Metadata -Status 'inactive' -WorkerPid $null -ControllerPid $controllerPid -Restarts $restartCount Write-Metadata -Status 'inactive' -WorkerPid $null -ControllerPid $controllerPid -Restarts $restartCount
Write-ControllerLog "Controller exiting." Write-ControllerLog "Controller exiting."
}
catch {
Write-FatalLog "Fatal controller error: $($_.Exception.Message)"
if ($_.ScriptStackTrace) {
Write-FatalLog "Stack: $($_.ScriptStackTrace)"
}
throw
}
finally {
if ($logWriter) { if ($logWriter) {
$logWriter.Dispose() $logWriter.Dispose()
} }
if ($logStream) { if ($logStream) {
$logStream.Dispose() $logStream.Dispose()
} }
}