[CmdletBinding()] param( [Parameter(Mandatory = $true, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)] [string]$WorkerName, [Parameter(Mandatory = $true, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)] [string]$WorkerType, [Parameter(ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)] [string]$DataRoot = (Join-Path ([Environment]::GetFolderPath('LocalApplicationData')) 'UnifiedWorkers'), [Parameter(ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)] [switch]$CommandOnly, [Parameter(ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)] [string]$Command ) $ErrorActionPreference = 'Continue' # Explicitly clear any pipeline input to prevent binding errors $null = $input # Ensure we can see output immediately try { [Console]::OutputEncoding = [System.Text.Encoding]::UTF8 } catch {} try { $Host.UI.RawUI.BufferSize = New-Object System.Management.Automation.Host.Size(200, 9999) } catch {} Write-Host "========================================" -ForegroundColor Cyan Write-Host "Worker Attach Session Starting" -ForegroundColor Cyan Write-Host "Worker: $WorkerName" -ForegroundColor White Write-Host "Type: $WorkerType" -ForegroundColor White Write-Host "========================================" -ForegroundColor Cyan [Console]::Out.Flush() # Ensure output is visible immediately try { [Console]::OutputEncoding = [System.Text.Encoding]::UTF8 } catch {} try { $Host.UI.RawUI.BufferSize = New-Object System.Management.Automation.Host.Size(200, 9999) } catch {} Write-Host "Starting attach session..." -ForegroundColor Green Write-Host "Worker: $WorkerName, Type: $WorkerType" -ForegroundColor Gray [Console]::Out.Flush() function Get-WorkerPaths { param([string]$Root, [string]$Type, [string]$Name) $instanceRoot = Join-Path -Path (Join-Path -Path $Root -ChildPath $Type) -ChildPath $Name return [pscustomobject]@{ Metadata = Join-Path -Path $instanceRoot -ChildPath 'state\worker-info.json' Command = Join-Path -Path $instanceRoot -ChildPath 'state\commands.txt' Log = Join-Path -Path $instanceRoot -ChildPath 'logs\worker.log' } } $paths = Get-WorkerPaths -Root $DataRoot -Type $WorkerType -Name $WorkerName if (-not (Test-Path $paths.Metadata)) { Write-Host "" Write-Host "ERROR: No worker metadata found for $WorkerName ($WorkerType)." -ForegroundColor Red Write-Host "Metadata path: $($paths.Metadata)" -ForegroundColor Gray Write-Host "Data root: $DataRoot" -ForegroundColor Gray Write-Host "" Write-Host "Press any key to exit..." try { $null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown") } catch { Start-Sleep -Seconds 5 } exit 1 } try { $metadata = Get-Content -Path $paths.Metadata -Raw | ConvertFrom-Json } catch { Write-Host "Unable to read worker metadata: $($_.Exception.Message)" -ForegroundColor Red exit 1 } if (Test-Path $paths.Log) { # ensure log file exists but do not truncate $null = (Get-Item $paths.Log) } else { New-Item -Path $paths.Log -ItemType File -Force | Out-Null } if (-not (Test-Path $paths.Command)) { New-Item -Path $paths.Command -ItemType File -Force | Out-Null } function Send-WorkerCommand { param([string]$Value) $clean = $Value.Trim() if (-not $clean) { return } Add-Content -Path $paths.Command -Value $clean -Encoding UTF8 Write-Host "Sent command '$clean' to $WorkerName." -ForegroundColor DarkGray } if ($CommandOnly) { if (-not $Command) { Write-Host "CommandOnly flag set but no command provided." -ForegroundColor Red exit 1 } Send-WorkerCommand -Value $Command exit 0 } Write-Host "" Write-Host "========================================" -ForegroundColor Cyan Write-Host "Attaching to $WorkerName ($WorkerType) logs." -ForegroundColor Cyan Write-Host "Log file: $($paths.Log)" -ForegroundColor Gray Write-Host "========================================" -ForegroundColor Cyan [Console]::Out.Flush() [Console]::Error.WriteLine("Attach session initialized") # Show initial log content if available if (Test-Path $paths.Log) { $initialLines = Get-Content -Path $paths.Log -Tail 50 -ErrorAction SilentlyContinue if ($initialLines) { Write-Host "--- Recent log output ---" -ForegroundColor DarkGray $initialLines | ForEach-Object { Write-Host $_ } Write-Host "--- End of initial output ---" -ForegroundColor DarkGray } else { Write-Host "Log file exists but is empty. Waiting for worker output..." -ForegroundColor Yellow } } else { Write-Host "Log file does not exist yet. Waiting for worker to start..." -ForegroundColor Yellow } Write-Host "" Write-Host "Type commands and press Enter. Type 'detach' to exit session." -ForegroundColor Yellow Write-Host "" # Use Register-ObjectEvent for file system watcher, or simpler: just tail in background and poll $logJob = Start-Job -ScriptBlock { param($LogPath) if (Test-Path $LogPath) { Get-Content -Path $LogPath -Tail 0 -Wait -ErrorAction SilentlyContinue } else { while (-not (Test-Path $LogPath)) { Start-Sleep -Milliseconds 500 } Get-Content -Path $LogPath -Tail 0 -Wait -ErrorAction SilentlyContinue } } -ArgumentList $paths.Log try { while ($true) { # Check for new log output from background job (non-blocking) $logOutput = Receive-Job -Job $logJob -ErrorAction SilentlyContinue if ($logOutput) { $logOutput | ForEach-Object { Write-Host $_ } [Console]::Out.Flush() } # Use a timeout on Read-Host to allow periodic log checking # Since Read-Host doesn't support timeout, we'll just check logs before each Read-Host # This means logs will be checked whenever user presses Enter $input = Read-Host "> " if ($null -eq $input) { continue } $normalized = $input.Trim() if ($normalized.StartsWith(':')) { $normalized = $normalized.TrimStart(':').Trim() } if ($normalized.Length -eq 0) { continue } if ($normalized.ToLowerInvariant() -eq 'detach') { break } Send-WorkerCommand -Value $input } } catch { Write-Host "Error in attach session: $($_.Exception.Message)" -ForegroundColor Red Write-Host $_.ScriptStackTrace -ForegroundColor DarkRed [Console]::Error.WriteLine("Attach error: $($_.Exception.Message)") } finally { if ($logJob) { Stop-Job -Job $logJob -ErrorAction SilentlyContinue Remove-Job -Job $logJob -ErrorAction SilentlyContinue } Write-Host "Detached from worker $WorkerName." -ForegroundColor Cyan }