Skip to content

Instantly share code, notes, and snippets.

@gpduck
Last active January 12, 2017 12:59
Show Gist options
  • Save gpduck/7716935 to your computer and use it in GitHub Desktop.
Save gpduck/7716935 to your computer and use it in GitHub Desktop.
PowerShell Web Server Framework
<#
.SYNOPSIS
Start a web server that will route requests to a series of script blocks as defined by the -Routes parameter.
.DESCRIPTION
Starts a single-threaded web server and responds to requests by executing the script blocks that are
defined as routes on the command line.
.NOTES
Copyright 2013 Chris Duck
The idea for this script came from Parul Jain's work at http://poshcode.org/4073
.PARAMETER Routes
A hashtable that maps a URL fragment to a script block to be executed for matching requests. The URL will be
matched as a regex to the LocalPath portion of the requested URL. Use the LocalPath property from
[Uri]"http://test/path?qs=test" to identify what part of your URL will be included in LocalPath.
The script block has 2 automatic variables available to it:
$Request - The [System.Net.HttpListenerRequest] object representing the incoming request.
$Response - The [System.Net.HttpListenerResponse] object representing the outgoing response.
There is also a helper function available in the script block:
Send-Response -Body <String> [-ContentType <String>]
Send-Response -Path <String> [-ContentType <String>]
The default content type is "text/plain". The Send-Response function will close the response
output stream, so the entire response must be passed as the value for the Body parameter. The
Path parameter reads in the contents of the supplied file (as a single string) and sends it as
the response.
To stop the web server, set $Running = $False in any of your script blocks. If the routes table
does not include a route with the key "Exit", a default exit route will be added.
If you would like to send a redirect, use:
$Response.Redirect("http://newurl")
$Response.Close()
.PARAMETER IPAddress
The IPAddress or host name to listen on. By default it will listen on all local addresses.
.PARAMETER Port
The port to listen on. By default it will listen on port 80.
.EXAMPLE
$Routes = @{
"process" = {
Send-Response -Body (Get-Process | ConvertTo-Html) -ContentType "text/html"
}
".*" = {
Send-Response -Body "Unknown address, try /Process or /Exit"
}
}
Start-Webserver -Routes $Routes
This will start a server that responds to requests for /Process by listing out all the running processes in an HTML form.
Any other request will generate a plain text page suggesting the user try /Process or the default implementation of /Exit
See Get-Help Start-Webserver -Parameter Routes for more information on building routes.
.EXAMPLE
$Routes = @{
"process" = {
if($Request.QueryString["format"] -eq "json") {
Send-Response -Body (Get-Process | ConvertTo-Json) -ContentType "application/json"
} else {
Send-Response -Body (Get-Process | ConvertTo-Html) -ContentType "text/html"
}
}
}
Start-Webserver -Routes $Routes -Port 8080
This shows how to use parameters passed in the query string to alter your output. To have the script output JSON instead of HTML,
simply request /Process?format=json
.EXAMPLE
#Add the System.Web assembly so we can use System.Web.HttpUtility to UrlEncode the return URL
Add-Type -AssemblyName System.Web
$Routes = @{
"GetBarcode" = {
$ProcessUrl = 'http://' + $Request.UserHostAddress + '/ProcessCode?code={CODE}'
$RedirectUrl = "zxing://scan/?ret=" + [System.Web.HttpUtility]::UrlEncode($ProcessUrl)
Write-Debug "Redirecting to $RedirectUrl"
$Response.Redirect($RedirectUrl)
$Response.Close()
}
"ProcessCode" = {
Send-Response -Body "The barcode was $($Request.QueryString['code'])"
}
}
Start-Webserver -Routes $Routes
This example creates a web server that launches Google's Barcode Scanner application when an Android phone browses to /GetBarcode. When
a barcode is scanned, the phone is redirected to /ProcessCode?code=THEBARCODE and a message is returned to the browser displaying the
value of the barcode that was scanned.
This shows how to use a redirect as well as process input from the user using a query string.
#>
function Start-Webserver {
param(
$Routes,
$IPAddress = "+",
$Port = "80"
)
function Send-Response {
[CmdletBinding()]
param(
[Parameter(Mandatory,ParameterSetName="Raw")]
[ValidateNotNull()]
$Body,
[Parameter(Mandatory,ParameterSetName="File")]
$Path,
$ContentType = "text/plain"
)
$Response.ContentType = $ContentType
if($PsCmdlet.ParameterSetName -eq "File") {
$Body = Get-Content -Path $Path -Raw
}
[byte[]]$Buffer = [System.Text.Encoding]::UTF8.GetBytes($Body)
$Response.ContentLength64 = $Buffer.Length
$Response.OutputStream.Write($Buffer, 0 , $Buffer.Length)
$Response.OutputStream.Close()
}
if(!$Routes.ContainsKey("Exit")) {
$Routes.Add("Exit", {
Write-Debug "Exiting"
$Running = $False
Send-Response -Body "Quitting..."
})
}
$Listener = New-Object System.Net.HttpListener
$ListenPrefix = "http://${IPAddress}:${Port}/"
Write-Debug "Listening on $ListenPrefix"
$Listener.Prefixes.Add($ListenPrefix)
$Running = $true
try {
$Listener.Start()
while($Running) {
$Ctx = $Listener.GetContext()
$Request = $Ctx.Request
$Response = $Ctx.Response
Write-Verbose "Request accepted for $($Request.Url)"
$RouteFound = $false
foreach($Route in $Routes.Keys) {
if($Request.Url.LocalPath -match $Route) {
Write-Debug "Matched route $Route"
$RouteFound = $true
. $Routes[$Route]
break;
} else {
Write-Debug "Route $Route doesn't match"
}
}
if(!$RouteFound) {
Write-Warning "No route found for $($Request.Url)"
$Response.Close()
}
}
$Listener.Stop()
} catch {
Write-Error -Message "Could not start listener" -Exception $_.Exception
} finally {
if($Listener.IsListening) {
$Listener.Stop()
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment