oob.psm1
function Start-OOB { <# .SYNOPSIS Launch an Out-of-Band listener (Node) with a Cloudflare tunnel. .PARAMETER ResponseType What the listener should return. Defaults to **None** (204). None → 204 No Content (stealth beacon) Image → Serve a static file (PNG/GIF/ICO…) File → Serve any file (PDF/TXT/ZIP…) Redirect → 302 Location: <url> Script → application/javascript body .PARAMETER ResponseFile Path to the file to serve (required for Image / File). .PARAMETER RedirectUrl Where to send the browser on a 302 (required for Redirect). .PARAMETER ScriptBody Inline JavaScript text (required for Script). .PARAMETER ContentType Override Content-Type when using ResponseFile/File (optional). #> [CmdletBinding()] param ( [int] $Port = 8000, [string] $Name = 'oob', [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)" # ── script & args ─────────────────────────────────────────────── if (-not $Path.StartsWith('/')) { $Path = "/$Path" } $jsPath = Join-Path $PSScriptRoot 'oob-server.js' $nodeArgs = @( "`"$jsPath`"", "--port" , $Port, "--name" , $Name, "--path" , $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 if persistent without -Tail) ── $windowStyle = if ($Persistent -and -not $Tail) { 'Hidden' } else { 'Normal' } if (-not $script:__oobProc -or $script:__oobProc.HasExited) { $script:__oobProc = Start-Process node ` -ArgumentList $nodeArgs ` -WindowStyle $windowStyle ` -PassThru Start-Sleep 1 } # ── tunnel handling (unchanged) ───────────────────────────────── if (-not $Persistent) { $tName = "${Name}_$(Get-Random -min 1000 -max 9999)" $script:__oobTunnel = Start-TempTunnel -Name $tName -Url "http://localhost:$Port" -TimeoutSeconds $Timeout Write-Host "🌐 Temp tunnel : $($script:__oobTunnel.Url)" } else { $script:__oobTunnel = Start-PersistentTunnel -Name $Name Write-Host "🌐 Persist URL : $($script:__oobTunnel.Url)" } # ── (tail-log logic unchanged) … ──────────────────────────────── # -- snipped for brevity -- } function Stop-OOB { <# .SYNOPSIS Cleanly tear down anything Start-OOB created: • cloudflared tunnel (temp or persistent) • node listener • log-tail job • any child pwsh wrapper hosting cloudflared #> [CmdletBinding()] param() # ── 1. Tunnel teardown ─────────────────────────────────────────── if ($script:__oobTunnel) { try { switch ($script:__oobTunnel.Type) { 'Temporary' { Stop-TempTunnel -Name $script:__oobTunnel.Name -ErrorAction SilentlyContinue } 'Persistent' { Stop-PersistentTunnel -Name $script:__oobTunnel.Name -ErrorAction SilentlyContinue } default { # If Type is missing or unexpected, attempt both Stop-TempTunnel -Name $script:__oobTunnel.Name -ErrorAction SilentlyContinue Stop-PersistentTunnel -Name $script:__oobTunnel.Name -ErrorAction SilentlyContinue } } } catch { } $script:__oobTunnel = $null } # ── 2. Kill the node listener ──────────────────────────────────── if ($script:__oobProc -and -not $script:__oobProc.HasExited) { try { Stop-Process -Id $script:__oobProc.Id -Force -ErrorAction SilentlyContinue } catch { } $script:__oobProc = $null } # ── 3. Stop & remove the tail job ──────────────────────────────── if ($script:__logJob) { try { Stop-Job -Job $script:__logJob -Force -ErrorAction SilentlyContinue Remove-Job -Job $script:__logJob -Force -ErrorAction SilentlyContinue } catch { } $script:__logJob = $null } # ── 4. Kill any child pwsh wrappers hosting cloudflared ────────── # (Start-PersistentTunnel launches a child pwsh process) try { Get-CimInstance Win32_Process -Filter "Name='pwsh.exe'" | Where-Object { $_.ParentProcessId -eq $PID } | ForEach-Object { Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue } } catch { } Write-Host "🛑 OOB listener, tunnel, and helper processes stopped." } Export-ModuleMember -Function Start-OOB, Stop-OOB |