http.server.psm1
function Start-HttpServer { param( [int]$Port = 8000, [string]$Root = (Get-Location), [switch]$Public, [Alias('if')][string]$IndexFile ) if (-not (Get-Command Start-ThreadJob -ErrorAction SilentlyContinue)) { Write-Error "Start-ThreadJob is not available. Run: Install-Module ThreadJob" return } if ($Global:HttpServerJob -and ($Global:HttpServerJob.State -eq 'Running')) { Write-Host "[-] Server already running (Job ID: $($Global:HttpServerJob.Id)). Use Stop-HttpServer to stop it." return } $bind = if ($Public) { "*" } else { "localhost" } $prefix = "http://$bind`:$Port/" $rootFull = (Resolve-Path $Root).Path # Launch listener in background $job = Start-ThreadJob -Name 'PowerShellHttpServer' -ScriptBlock { param($prefix, $rootFull) # MIME type lookup $mime = @{ '.html' = 'text/html'; '.htm' = 'text/html' '.json' = 'application/json' '.js' = 'application/javascript' '.css' = 'text/css' '.png' = 'image/png' '.jpg' = 'image/jpeg'; '.jpeg' = 'image/jpeg' '.svg' = 'image/svg+xml' '.ico' = 'image/x-icon' } $listener = [System.Net.HttpListener]::new() $listener.Prefixes.Add($prefix) $listener.Start() Write-Output "[+] Serving $rootFull at $prefix" try { while ($listener.IsListening) { $ctx = $listener.GetContext() $req = $ctx.Request $res = $ctx.Response # Determine requested path (default to index.html) $rel = $req.Url.LocalPath.TrimStart('/') if ([string]::IsNullOrWhiteSpace($rel)) { $rel = 'index.html' } $file = Join-Path $rootFull $rel Write-Output "[REQ] $($req.Url.LocalPath) → $file" if (Test-Path $file -PathType Leaf) { # Serve the requested file $bytes = [System.IO.File]::ReadAllBytes($file) $ext = [IO.Path]::GetExtension($file).ToLower() $res.ContentType = $mime[$ext] ? $mime[$ext] : 'application/octet-stream' $res.ContentLength64 = $bytes.Length $res.OutputStream.Write($bytes, 0, $bytes.Length) } else { # 404 fallback $msg = "404 – $rel" $buf = [Text.Encoding]::UTF8.GetBytes($msg) $res.StatusCode = 404 $res.ContentType = 'text/plain' $res.ContentLength64 = $buf.Length $res.OutputStream.Write($buf, 0, $buf.Length) } $res.OutputStream.Close() } } finally { $listener.Stop() } } -ArgumentList $prefix, $rootFull $Global:HttpServerJob = $job Start-Sleep -Milliseconds 500 Receive-Job -Id $job.Id | Write-Host Write-Host "[*] Server running. Use Stop-HttpServer to stop it." } function Stop-HttpServer { $job = $Global:HttpServerJob if (-not $job -or $job.State -ne 'Running') { # Fallback: try to find the job by name $job = Get-Job -Name 'PowerShellHttpServer' -ErrorAction SilentlyContinue | Where-Object { $_.State -eq 'Running' } } if ($job) { try { Stop-Job $job.Id -ErrorAction SilentlyContinue while ((Get-Job -Id $job.Id).State -ne 'Stopped') { Start-Sleep -Milliseconds 100 } Remove-Job $job.Id -ErrorAction SilentlyContinue Remove-Variable -Name HttpServerJob -Scope Global -ErrorAction SilentlyContinue Write-Host "[*] HTTP server stopped and cleaned up." } catch { Write-Host "[-] Error stopping job: $_" } } else { Write-Host "[-] No running HTTP server job found." } } Export-ModuleMember -Function Start-HttpServer, Stop-HttpServer |