- Building a web server with PowerShell - Fri, Jul 21 2017
- Managing MSI installations using the Windows Installer PowerShell module - Thu, Jun 22 2017
- Validating file and folder paths in PowerShell parameters - Fri, Jun 16 2017
System.Net.HttpListener is a powerful and generally underused class introduced to .NET in .NET Framework 2.0 and is still included in the current version of .NET. Thus, our web server is going to run on any system that has PowerShell and at least .NET 2.0. This means we can run it on most Windows server and client operating systems without having to install anything or download any libraries to support our code.
Let's create the listener to get started:
$listener = New-Object System.Net.HttpListener
Configuring the HttpListener ^
The first thing to do when creating an HttpListener is tell our web server where it should listen. Should it listen and respond to requests coming from other PCs on the network or only requests coming from the localhost? What port should it listen on? What name should it respond to? We answer all of that using a single line of code:
This single line of code is deceptively simple. Setting up prefixes can be the most complex process of creating a self-hosted application. Please read this article that explains UrlPrefix formatting. I recommend using a localhost prefix during development as this does not require any special setup to run and does not need administrative rights. Let's just leave that line as it is for now, and we will get back to setting this up to listen for requests coming from the network at the end of this article. For now, all we need to do is start the listener.
Preventing directory traversal ^
We are going to serve files out of the current working directory in PowerShell, and we want to restrict access to only the files and folders in this directory. We can easily prevent the requester using the \..\..\.. notation in a URL to perform directory traversal attacks. We do this by creating a PSDrive from the current directory and using this as the root file path when our code is looking for requested files.
New-PSDrive -Name MyPowerShellSite -PSProvider FileSystem -Root $PWD.Path
Accessing the request with the HttpListenerContext object ^
Alright, so we created a listener, we told it where to listen, and we started it. If you were curious you might have opened a web browser at this point to the URL given above, just to see what happens. I'm sorry to disappoint you, but we have one more step left before our listener will begin working with a web browser.
We must tell our listener to get an HttpListenerContext object that gives us access to request and response objects. We use the getContext() method on the $listener to do this synchronously. HttpListener does support asynchronous calls, but that is beyond the scope of this article.
$Context = $listener.GetContext()
The console should hang waiting for an HTTP request to hit the URL it is listening on. Going to http://localhost:8080/ using any web browser should populate our $Context variable, and the browser will begin to spin waiting for a response.
The context variable ^
Inspection of the context variable reveals three properties:
- Request: This contains information that came from the browser or HTTP client (URL requested, cookies, query string, HTTP method, etc.).
- Response: This automatically created object will send a response back to the browser.
- User: Setting an authentication method on the HttpListener would populate this with details about the user (such as username and password or a Windows Identity object).
You can read more details about the HttpListenerContext .NET Class on MSDN. This .NET class can do a lot, but we will focus on the simple stuff here. All we need to know is what file the browser is requesting and send it back using the response.
Reading the request with getContext() ^
A URL property stores the details of the URL that the web browser or HTTP client requested. For example, if I browse to http://localhost:8080/MyFile.txt and then run the getContext() method, I can access the URL with $Context.Request.Url:
Sending the HTTP response ^
The LocalPath property looks like it has exactly the information we need. Let's go ahead and get that file and send it back to the web browser.
$URL = $Context.Request.Url.LocalPath $Content = Get-Content -Encoding Byte -Path "MyPowerShellSite:$URL" $Context.Response.ContentType = [System.Web.MimeMapping]::GetMimeMapping("MyPowerShellSite:$URL") $Context.Response.OutputStream.Write($Content, 0, $Content.Length) $Context.Response.Close()
The MimeMapping method detects the file type and converts it to a file type the browser understands. You should see the contents of your text file exposed in the browser now!
This is of course a very simplistic example and only serves a single request. A more detailed example with directory browsing showcases how to use integrated Windows authentication and send back errors to a browser in this Github gist.
If you would like a ready-to-go self-hosted PowerShell web server solution written with HttpListener, you can check out the following projects:
Subscribe to 4sysops newsletter!
To set it up on something other than localhost with HTTPS and without running as admin, I recommend this article.