remote controller working!
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -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()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user