oob.psm1
function Start-OOB { [CmdletBinding()] param( [int] $Port = 8000, [string] $Name = 'oob', [string] $LogName, # ← new optional parameter [string] $Path = '/', [int] $Timeout = 15, [switch] $Persistent, [switch] $Tail, [ValidateSet('None','Image','File','Redirect','Script')] [string] $ResponseType = 'None', [string] $ResponseFile, [string] $RedirectUrl, [string] $ScriptBody, [string] $ContentType ) # ── env checks ──────────────────────────────────────────────── Import-Module Node -ErrorAction Stop; Assert-NodeInstalled Import-Module cloudflared -ErrorAction Stop Write-Host "ℹ️ Node : $(Get-NodePath)" # ── normalize path ───────────────────────────────────────────── if (-not $Path.StartsWith('/')) { $Path = "/$Path" } # ── decide logfile name & create file ────────────────────────── $logRoot = Join-Path $HOME '.oob\logs' if (-not (Test-Path $logRoot)) { New-Item -ItemType Directory -Path $logRoot | Out-Null } if ($LogName) { $fileName = $LogName } else { $stamp = Get-Date -Format "ddMMMyyHHmmss" $fileName = "${Name}_$stamp.log" } $logFile = Join-Path $logRoot "$fileName.log" New-Item -ItemType File -Path $logFile -Force | Out-Null $Global:OOBLogFile = $logFile # ── assemble Node args ──────────────────────────────────────── $jsPath = Join-Path $PSScriptRoot 'oob-server.js' $nodeArgs = @( "`"$jsPath`"", '--port' , $Port, '--name' , $Name, '--path' , $Path, '--logfile' , $logFile, # bare absolute path '--response-type', $ResponseType.ToLower() ) switch ($ResponseType) { 'Image' { $nodeArgs += @('--response-file', $ResponseFile) } 'File' { $nodeArgs += @('--response-file', $ResponseFile) } 'Redirect'{ $nodeArgs += @('--redirect-url', $RedirectUrl) } 'Script' { $nodeArgs += @('--script-body', $ScriptBody) } } if ($ContentType) { $nodeArgs += @('--content-type', $ContentType) } # ── launch Node listener (hidden unless -Tail) ──────────────── $windowStyle = if ($Tail) { 'Normal' } else { 'Hidden' } $script:__oobProc = Start-Process node ` -ArgumentList $nodeArgs ` -WorkingDirectory $PSScriptRoot ` -WindowStyle $windowStyle ` -PassThru Start-Sleep 1 # ── Cloudflare tunnel handling ──────────────────────────────── if (-not $Persistent) { $rand = Get-Random -Minimum 1000 -Maximum 9999 $script:__oobTunnel = Start-TempTunnel ` -Name "${Name}_$rand" ` -Url "http://localhost:$Port" ` -TimeoutSeconds $Timeout $publicUrl = $script:__oobTunnel.Url Write-Host "🌐 Temp tunnel : $publicUrl$Path" } else { # 1) Start (or ensure) the persistent tunnel Start-PersistentTunnel -Name $Name | Out-Null # 2) Query your helper to fetch the assigned URL $t = Get-TunnelStatus | Where-Object Name -EQ $Name | Select-Object -First 1 # 3) Pull out Url and write it $publicBase = $t.Url Write-Host "🌐 Persist URL : $publicBase$Path" } # ── live-tail ──────────────────────────────────────────────── if ($Tail) { if ($script:__logJob -and -not $script:__logJob.HasExited) { Stop-Job $script:__logJob -Force -ErrorAction SilentlyContinue Remove-Job $script:__logJob -Force -ErrorAction SilentlyContinue } $script:__logJob = Start-Job -Name "OOBLog_$Name" -ScriptBlock { param($path) Get-Content -Path $path -Wait -Tail 0 | ForEach-Object { Write-Host "[OOB]" $_ } } -ArgumentList $logFile Write-Host "📝 Tailing log: $logFile" } return ($publicUrl + $Path) } function Stop-OOB { <# .SYNOPSIS Tear down listener, tunnel, tail window, and helper processes. #> [CmdletBinding()] param() ## 1. tunnel teardown ########################################## if ($script:__oobTunnel) { try { switch ($script:__oobTunnel.Type) { 'Temporary' { Stop-TempTunnel -Name $script:__oobTunnel.Name -EA SilentlyContinue } 'Persistent' { Stop-PersistentTunnel -Name $script:__oobTunnel.Name -EA SilentlyContinue } default { Stop-TempTunnel -Name $script:__oobTunnel.Name -EA SilentlyContinue Stop-PersistentTunnel -Name $script:__oobTunnel.Name -EA SilentlyContinue } } } catch {} $script:__oobTunnel = $null } ## 2. kill node listener ####################################### if ($script:__oobProc -and -not $script:__oobProc.HasExited) { Stop-Process -Id $script:__oobProc.Id -Force -EA SilentlyContinue $script:__oobProc = $null } ## 3. close tail window ######################################## if ($script:__tailProc -and -not $script:__tailProc.HasExited) { Stop-Process -Id $script:__tailProc.Id -Force -EA SilentlyContinue $script:__tailProc = $null } ## 4. clean up child pwsh wrappers hosting cloudflared ######### try { Get-CimInstance Win32_Process -Filter "Name='pwsh.exe'" | Where-Object { $_.ParentProcessId -eq $PID } | ForEach-Object { Stop-Process -Id $_.ProcessId -Force -EA SilentlyContinue } } catch {} ## 5. ensure cloudflared.exe is gone ########################### try { Get-CimInstance Win32_Process -Filter "Name='cloudflared.exe'" | Where-Object { $_.CommandLine -match $script:__oobTunnel.Name } | ForEach-Object { Stop-Process -Id $_.ProcessId -Force -EA SilentlyContinue } } catch {} Write-Host "🛑 OOB listener, tunnel, tail window, and helpers stopped." } function Show-OOBLog { <# .SYNOPSIS Spin up a separate window to tail the current OOB logfile. #> if (-not $Global:OOBLogFile -or -not (Test-Path $Global:OOBLogFile)) { Write-Warning "No active OOB logfile found. Run Start-OOB first." return } # close prior tail window if ($script:__tailProc -and -not $script:__tailProc.HasExited) { Stop-Process -Id $script:__tailProc.Id -Force -EA SilentlyContinue } $cmd = "Get-Content -Path `"$Global:OOBLogFile`" -Wait -Tail 0" $script:__tailProc = Start-Process pwsh -ArgumentList "-NoExit","-Command",$cmd ` -WindowStyle Normal -PassThru } Export-ModuleMember -Function Start-OOB, Stop-OOB, Show-OOBLog |