Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
A simple web server built with powershell.
# This is a super **SIMPLE** example of how to create a very basic powershell webserver
# 2019-05-18 UPDATE — Created by me and and evalued by @jakobii and the comunity.
# Http Server
$http = [System.Net.HttpListener]::new()
# Hostname and port to listen on
$http.Prefixes.Add("http://localhost:8080/")
# Start the Http Server
$http.Start()
# Log ready message to terminal
if ($http.IsListening) {
write-host " HTTP Server Ready! " -f 'black' -b 'gre'
write-host "now try going to $($http.Prefixes)" -f 'y'
write-host "then try going to $($http.Prefixes)other/path" -f 'y'
}
# INFINTE LOOP
# Used to listen for requests
while ($http.IsListening) {
# Get Request Url
# When a request is made in a web browser the GetContext() method will return a request object
# Our route examples below will use the request object properties to decide how to respond
$context = $http.GetContext()
# ROUTE EXAMPLE 1
# http://127.0.0.1/
if ($context.Request.HttpMethod -eq 'GET' -and $context.Request.RawUrl -eq '/') {
# We can log the request to the terminal
write-host "$($context.Request.UserHostAddress) => $($context.Request.Url)" -f 'mag'
# the html/data you want to send to the browser
# you could replace this with: [string]$html = Get-Content "C:\some\path\index.html" -Raw
[string]$html = "<h1>A Powershell Webserver</h1><p>home page</p>"
#resposed to the request
$buffer = [System.Text.Encoding]::UTF8.GetBytes($html) # convert htmtl to bytes
$context.Response.ContentLength64 = $buffer.Length
$context.Response.OutputStream.Write($buffer, 0, $buffer.Length) #stream to broswer
$context.Response.OutputStream.Close() # close the response
}
# ROUTE EXAMPLE 2
# http://127.0.0.1/some/form'
if ($context.Request.HttpMethod -eq 'GET' -and $context.Request.RawUrl -eq '/some/form') {
# We can log the request to the terminal
write-host "$($context.Request.UserHostAddress) => $($context.Request.Url)" -f 'mag'
[string]$html = "
<h1>A Powershell Webserver</h1>
<form action='/some/post' method='post'>
<p>A Basic Form</p>
<p>fullname</p>
<input type='text' name='fullname'>
<p>message</p>
<textarea rows='4' cols='50' name='message'></textarea>
<br>
<input type='submit' value='Submit'>
</form>
"
#resposed to the request
$buffer = [System.Text.Encoding]::UTF8.GetBytes($html)
$context.Response.ContentLength64 = $buffer.Length
$context.Response.OutputStream.Write($buffer, 0, $buffer.Length)
$context.Response.OutputStream.Close()
}
# ROUTE EXAMPLE 3
# http://127.0.0.1/some/post'
if ($context.Request.HttpMethod -eq 'POST' -and $context.Request.RawUrl -eq '/some/post') {
# decode the form post
# html form members need 'name' attributes as in the example!
$FormContent = [System.IO.StreamReader]::new($context.Request.InputStream).ReadToEnd()
# We can log the request to the terminal
write-host "$($context.Request.UserHostAddress) => $($context.Request.Url)" -f 'mag'
Write-Host $FormContent -f 'Green'
# the html/data
[string]$html = "<h1>A Powershell Webserver</h1><p>Post Successful!</p>"
#resposed to the request
$buffer = [System.Text.Encoding]::UTF8.GetBytes($html)
$context.Response.ContentLength64 = $buffer.Length
$context.Response.OutputStream.Write($buffer, 0, $buffer.Length)
$context.Response.OutputStream.Close()
}
# powershell will continue looping and listen for new requests...
}
# Note:
# To end the loop you have to kill the powershell terminal. ctrl-c wont work :/
@rhmoult
Copy link

rhmoult commented Apr 21, 2015

Your Load-Packages function appears to be dead code.

@lanekatris
Copy link

lanekatris commented Sep 9, 2015

Great example. I added a route called "/kill", and when that route GET I break out of the while loop to exit gracefully.

@pmsousa
Copy link

pmsousa commented Jan 4, 2017

loonison101, do you have your code anywhere?

@afreeland
Copy link

afreeland commented Nov 17, 2017

@loonison101 I would find you here =)

@pavi2410
Copy link

pavi2410 commented Dec 24, 2017

It's awesome for Windows power users!

@EugeneKosmin
Copy link

EugeneKosmin commented Mar 5, 2018

@jakobii
Copy link

jakobii commented Mar 6, 2018

Working Example

Simple Gist Example

@nobody5050
Copy link

nobody5050 commented Jul 9, 2018

where would be a place to host this tho

@jdgregson
Copy link

jdgregson commented Nov 19, 2019

Note that this is single-threaded. This server can only serve one client at a time.

@Darker
Copy link

Darker commented Jul 28, 2020

Using this example, I managed to create complete server that will serve anything in web/* directory wherever you run it. The server is insecure against directory crawling attacks.

Check this answer for the code: https://stackoverflow.com/a/63133186/607407

@adrwh
Copy link

adrwh commented Jul 11, 2021

Hi, i wish to use something this in an OAuth Authorization Code flow, where the Identity provider will redirect back to my browser a code.. Any ideas?

@ilyash-b
Copy link

ilyash-b commented Aug 3, 2021

Can be improved by de-duplicating code.

        #resposed to the request
        $buffer = [System.Text.Encoding]::UTF8.GetBytes($html)
        $context.Response.ContentLength64 = $buffer.Length
        $context.Response.OutputStream.Write($buffer, 0, $buffer.Length)
        $context.Response.OutputStream.Close()

@rminderhoud
Copy link

rminderhoud commented Oct 29, 2021

To enable the script pipeline to be stopped using CTRL+C we can wrap the while loop in a try/finally and use an async context. E.g.

try {
    while ($http.IsListening) {
        $contextTask = $http.GetContextAsync()
        # Waits in 200ms increments allowing pipeline stops to be processed (i.e. CTRL+C)
        # Credit: https://www.reddit.com/r/PowerShell/comments/9n2q03/comment/e7ju5w4/?utm_source=share&utm_medium=web2x&context=3
        while (-not $contextTask.AsyncWaitHandle.WaitOne(200)) { }
        $context = $contextTask.GetAwaiter().GetResult()
        
        # -- rest of loop
    }
}
finally {
    # This is always called when ctrl+c is used
    $http.Stop()
}

Full Gist: https://gist.github.com/rminderhoud/c603a0a30587ae5c957b211ba386bf37

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment