<# .Synopsis Starts powershell webserver .Description Starts webserver as powershell process. Call of the root page (e.g. http://localhost:8080/) returns a powershell execution web form. Call of /script uploads a powershell script and executes it (as a function). Call of /log returns the webserver logs, /starttime the start time of the webserver, /time the current time. /download downloads and /upload uploads a file. /beep generates a sound and /quit or /exit stops the webserver. You may have to configure a firewall exception to allow access to the chosen port, e.g. with: netsh advfirewall firewall add rule name="Powershell Webserver" dir=in action=allow protocol=TCP localport=8080 After stopping the webserver you should remove the rule, e.g.: netsh advfirewall firewall delete rule name="Powershell Webserver" .Parameter BINDING Binding of the webserver .Inputs None .Outputs None .Example Start-Webserver.ps1 Starts webserver with binding to http://localhost:8080/ .Example Start-Webserver.ps1 "http://+:8080/" Starts webserver with binding to all IP addresses of the system. Administrative rights are necessary. .Example schtasks.exe /Create /TN "Powershell Webserver" /TR "powershell -file C:\Users\Markus\Documents\Start-WebServer.ps1 http://+:8080/" /SC ONSTART /RU SYSTEM /RL HIGHEST /F Starts powershell webserver as scheduled task as user local system every time the computer starts (when the correct path to the file Start-WebServer.ps1 is given). You can start the webserver task manually with schtasks.exe /Run /TN "Powershell Webserver" Delete the webserver task with schtasks.exe /Delete /TN "Powershell Webserver" Scheduled tasks are always running with low priority, so some functions might be slow. .Notes Author: Markus Scholtes, 2016-10-22 #> Param([STRING]$BINDING = 'http://localhost:8080/') # No adminstrative permissions are required for a binding to "localhost" # $BINDING = 'http://localhost:8080/' # Adminstrative permissions are required for a binding to network names or addresses. # + takes all requests to the port regardless of name or ip, * only requests that no other listener answers: # $BINDING = 'http://+:8080/' # HTML answer templates for specific calls, placeholders !RESULT, !FORMFIELD, !PROMPT are allowed $HTMLRESPONSECONTENTS = @{ 'GET /' = @" !HEADERLINE
!RESULT
!PROMPT 
"@ 'GET /script' = @" !HEADERLINE

Script to execute:

Parameters:
"@ 'GET /download' = @" !HEADERLINE
!RESULT
Path to file:
"@ 'POST /download' = @" !HEADERLINE
!RESULT
Path to file:
"@ 'GET /upload' = @" !HEADERLINE

File to upload:

Path to store on webserver:
"@ 'POST /script' = "!HEADERLINE
!RESULT
" 'POST /upload' = "!HEADERLINE
!RESULT
" 'GET /exit' = "Stopped powershell webserver" 'GET /quit' = "Stopped powershell webserver" 'GET /log' = "!HEADERLINELog of powershell webserver:
!RESULT
" 'GET /starttime' = "!HEADERLINEPowershell webserver started at $(Get-Date -Format s)" 'GET /time' = "!HEADERLINECurrent time: !RESULT" 'GET /beep' = "!HEADERLINEBEEP..." } # Set navigation header line for all web pages $HEADERLINE = "

Command execution Execute script Download file Upload file Web logs Webserver start time Current time Beep Stop webserver

" # Starting the powershell webserver "$(Get-Date -Format s) Starting powershell webserver..." $LISTENER = New-Object System.Net.HttpListener $LISTENER.Prefixes.Add($BINDING) $LISTENER.Start() $Error.Clear() try { "$(Get-Date -Format s) Powershell webserver started." $WEBLOG = "$(Get-Date -Format s) Powershell webserver started.`n" while ($LISTENER.IsListening) { # analyze incoming request $CONTEXT = $LISTENER.GetContext() $REQUEST = $CONTEXT.Request $RESPONSE = $CONTEXT.Response $RESPONSEWRITTEN = $FALSE # log to console "$(Get-Date -Format s) $($REQUEST.RemoteEndPoint.Address.ToString()) $($REQUEST.httpMethod) $($REQUEST.Url.PathAndQuery)" # and in log variable $WEBLOG += "$(Get-Date -Format s) $($REQUEST.RemoteEndPoint.Address.ToString()) $($REQUEST.httpMethod) $($REQUEST.Url.PathAndQuery)`n" # is there a fixed coding for the request? $RECEIVED = '{0} {1}' -f $REQUEST.httpMethod, $REQUEST.Url.LocalPath $HTMLRESPONSE = $HTMLRESPONSECONTENTS[$RECEIVED] $RESULT = '' # check for known commands switch ($RECEIVED) { "GET /" { # execute command # retrieve GET query string $FORMFIELD = '' $FORMFIELD = [URI]::UnescapeDataString(($REQUEST.Url.Query -replace "\+"," ")) # remove fixed form fields out of query string $FORMFIELD = $FORMFIELD -replace "\?command=","" -replace "\?button=enter","" -replace "&command=","" -replace "&button=enter","" # when command is given... if (![STRING]::IsNullOrEmpty($FORMFIELD)) { try { # ... execute command $RESULT = Invoke-Expression -EA SilentlyContinue $FORMFIELD 2> $NULL | Out-String } catch {} if ($Error.Count -gt 0) { # retrieve error message on error $RESULT += "`nError while executing '$FORMFIELD'`n`n" $RESULT += $Error[0] $Error.Clear() } } # preset form value with command for the caller's convenience $HTMLRESPONSE = $HTMLRESPONSE -replace '!FORMFIELD', $FORMFIELD # insert powershell prompt to form $PROMPT = "PS $PWD>" $HTMLRESPONSE = $HTMLRESPONSE -replace '!PROMPT', $PROMPT break } "GET /script" { # present upload form, nothing to do here break } "POST /script" { # upload and execute script # only if there is body data in the request if ($REQUEST.HasEntityBody) { # set default message to error message (since we just stop processing on error) $RESULT = "Received corrupt or incomplete form data" # check content type if ($REQUEST.ContentType) { # retrieve boundary marker for header separation $BOUNDARY = $NULL if ($REQUEST.ContentType -match "boundary=(.*);") { $BOUNDARY = "--" + $MATCHES[1] } else { # marker might be at the end of the line if ($REQUEST.ContentType -match "boundary=(.*)$") { $BOUNDARY = "--" + $MATCHES[1] } } if ($BOUNDARY) { # only if header separator was found # read complete header (inkl. file data) into string $READER = New-Object System.IO.StreamReader($REQUEST.InputStream, $REQUEST.ContentEncoding) $DATA = $READER.ReadToEnd() $READER.Close() $REQUEST.InputStream.Close() $PARAMETERS = "" $SOURCENAME = "" # separate headers by boundary string $DATA -replace "$BOUNDARY--\r\n", "$BOUNDARY`r`n--" -split "$BOUNDARY\r\n" | % { # omit leading empty header and end marker header if (($_ -ne "") -and ($_ -ne "--")) { # only if well defined header (separation between meta data and data) if ($_.IndexOf("`r`n`r`n") -gt 0) { # header data before two CRs is meta data # first look for the file in header "filedata" if ($_.Substring(0, $_.IndexOf("`r`n`r`n")) -match "Content-Disposition: form-data; name=(.*);") { $HEADERNAME = $MATCHES[1] -replace '\"' # headername "filedata"? if ($HEADERNAME -eq "filedata") { # yes, look for source filename if ($_.Substring(0, $_.IndexOf("`r`n`r`n")) -match "filename=(.*)") { # source filename found $SOURCENAME = $MATCHES[1] -replace "`r`n$" -replace "`r$" -replace '\"' # store content of file in variable $FILEDATA = $_.Substring($_.IndexOf("`r`n`r`n") + 4) -replace "`r`n$" } } } else { # look for other headers (we need "parameter") if ($_.Substring(0, $_.IndexOf("`r`n`r`n")) -match "Content-Disposition: form-data; name=(.*)") { # header found $HEADERNAME = $MATCHES[1] -replace '\"' # headername "parameter"? if ($HEADERNAME -eq "parameter") { # yes, look for paramaters $PARAMETERS = $_.Substring($_.IndexOf("`r`n`r`n") + 4) -replace "`r`n$" -replace "`r$" } } } } } } if ($SOURCENAME -ne "") { # execute only if a source file exists $EXECUTE = "function Powershell-WebServer-Func {`n" + $FILEDATA + "`n}`nPowershell-WebServer-Func " + $PARAMETERS try { # ... execute script $RESULT = Invoke-Expression -EA SilentlyContinue $EXECUTE 2> $NULL | Out-String } catch {} if ($Error.Count -gt 0) { # retrieve error message on error $RESULT += "`nError while executing script $SOURCENAME`n`n" $RESULT += $Error[0] $Error.Clear() } } else { $RESULT = "No file data received" } } } } else { $RESULT = "No client data received" } break } { $_ -like "* /download" } # GET or POST method are allowed for download page { # download file # is POST data in the request? if ($REQUEST.HasEntityBody) { # POST request # read complete header into string $READER = New-Object System.IO.StreamReader($REQUEST.InputStream, $REQUEST.ContentEncoding) $DATA = $READER.ReadToEnd() $READER.Close() $REQUEST.InputStream.Close() # get headers into hash table $HEADER = @{} $DATA.Split('&') | % { $HEADER.Add([URI]::UnescapeDataString(($_.Split('=')[0] -replace "\+"," ")), [URI]::UnescapeDataString(($_.Split('=')[1] -replace "\+"," "))) } # read header 'filepath' $FORMFIELD = $HEADER.Item('filepath') # remove leading and trailing double quotes since Test-Path does not like them $FORMFIELD = $FORMFIELD -replace "^`"","" -replace "`"$","" } else { # GET request # retrieve GET query string $FORMFIELD = '' $FORMFIELD = [URI]::UnescapeDataString(($REQUEST.Url.Query -replace "\+"," ")) # remove fixed form fields out of query string $FORMFIELD = $FORMFIELD -replace "\?filepath=","" -replace "\?button=download","" -replace "&filepath=","" -replace "&button=download","" # remove leading and trailing double quotes since Test-Path does not like them $FORMFIELD = $FORMFIELD -replace "^`"","" -replace "`"$","" } # when path is given... if (![STRING]::IsNullOrEmpty($FORMFIELD)) { # check if file exists if (Test-Path $FORMFIELD -PathType Leaf) { try { # ... download file $BUFFER = [System.IO.File]::ReadAllBytes($FORMFIELD) $RESPONSE.ContentLength64 = $BUFFER.Length $RESPONSE.SendChunked = $FALSE $RESPONSE.ContentType = "application/octet-stream" $FILENAME = Split-Path -Leaf $FORMFIELD $RESPONSE.AddHeader("Content-disposition", "attachment; filename=$FILENAME") $RESPONSE.OutputStream.Write($BUFFER, 0, $BUFFER.Length) # mark response as already given $RESPONSEWRITTEN = $TRUE } catch {} if ($Error.Count -gt 0) { # retrieve error message on error $RESULT += "`nError while downloading '$FORMFIELD'`n`n" $RESULT += $Error[0] $Error.Clear() } } else { # ... file not found $RESULT = "File $FORMFIELD not found" } } # preset form value with file path for the caller's convenience $HTMLRESPONSE = $HTMLRESPONSE -replace '!FORMFIELD', $FORMFIELD break } "GET /upload" { # present upload form, nothing to do here break } "POST /upload" { # upload file # only if there is body data in the request if ($REQUEST.HasEntityBody) { # set default message to error message (since we just stop processing on error) $RESULT = "Received corrupt or incomplete form data" # check content type if ($REQUEST.ContentType) { # retrieve boundary marker for header separation $BOUNDARY = $NULL if ($REQUEST.ContentType -match "boundary=(.*);") { $BOUNDARY = "--" + $MATCHES[1] } else { # marker might be at the end of the line if ($REQUEST.ContentType -match "boundary=(.*)$") { $BOUNDARY = "--" + $MATCHES[1] } } if ($BOUNDARY) { # only if header separator was found # read complete header (inkl. file data) into string $READER = New-Object System.IO.StreamReader($REQUEST.InputStream, $REQUEST.ContentEncoding) $DATA = $READER.ReadToEnd() $READER.Close() $REQUEST.InputStream.Close() # variables for filenames $FILENAME = "" $SOURCENAME = "" # separate headers by boundary string $DATA -replace "$BOUNDARY--\r\n", "$BOUNDARY`r`n--" -split "$BOUNDARY\r\n" | % { # omit leading empty header and end marker header if (($_ -ne "") -and ($_ -ne "--")) { # only if well defined header (seperation between meta data and data) if ($_.IndexOf("`r`n`r`n") -gt 0) { # header data before two CRs is meta data # first look for the file in header "filedata" if ($_.Substring(0, $_.IndexOf("`r`n`r`n")) -match "Content-Disposition: form-data; name=(.*);") { $HEADERNAME = $MATCHES[1] -replace '\"' # headername "filedata"? if ($HEADERNAME -eq "filedata") { # yes, look for source filename if ($_.Substring(0, $_.IndexOf("`r`n`r`n")) -match "filename=(.*)") { # source filename found $SOURCENAME = $MATCHES[1] -replace "`r`n$" -replace "`r$" -replace '\"' # store content of file in variable $FILEDATA = $_.Substring($_.IndexOf("`r`n`r`n") + 4) -replace "`r`n$" } } } else { # look for other headers (we need "filepath" to know where to store the file) if ($_.Substring(0, $_.IndexOf("`r`n`r`n")) -match "Content-Disposition: form-data; name=(.*)") { # header found $HEADERNAME = $MATCHES[1] -replace '\"' # headername "filepath"? if ($HEADERNAME -eq "filepath") { # yes, look for target filename $FILENAME = $_.Substring($_.IndexOf("`r`n`r`n") + 4) -replace "`r`n$" -replace "`r$" -replace '\"' } } } } } } if ($FILENAME -ne "") { # upload only if a targetname is given if ($SOURCENAME -ne "") { # only upload if source file exists # check or construct a valid filename to store $TARGETNAME = "" # if filename is a container name, add source filename to it if (Test-Path $FILENAME -PathType Container) { $TARGETNAME = Join-Path $FILENAME -ChildPath $(Split-Path $SOURCENAME -Leaf) } else { # try name in the header $TARGETNAME = $FILENAME } try { # ... save file with the same encoding as received [IO.File]::WriteAllText($TARGETNAME, $FILEDATA, $REQUEST.ContentEncoding) } catch {} if ($Error.Count -gt 0) { # retrieve error message on error $RESULT += "`nError saving '$TARGETNAME'`n`n" $RESULT += $Error[0] $Error.Clear() } else { # success $RESULT = "File $SOURCENAME successfully uploaded as $TARGETNAME" } } else { $RESULT = "No file data received" } } else { $RESULT = "Missing target file name" } } } } else { $RESULT = "No client data received" } break } "GET /log" { # return the webserver log (stored in log variable) $RESULT = $WEBLOG break } "GET /time" { # return current time $RESULT = Get-Date -Format s break } "GET /starttime" { # return start time of the powershell webserver (already contained in $HTMLRESPONSE, nothing to do here) break } "GET /beep" { # Beep [CONSOLE]::beep(800, 300) # or "`a" or [char]7 break } "GET /quit" { # stop powershell webserver, nothing to do here break } "GET /exit" { # stop powershell webserver, nothing to do here break } default { # unknown command, return error $RESPONSE.StatusCode = 404 $HTMLRESPONSE = 'Page not found' } } # only send response if not already done if (!$RESPONSEWRITTEN) { # insert header line string into HTML template $HTMLRESPONSE = $HTMLRESPONSE -replace '!HEADERLINE', $HEADERLINE # insert result string into HTML template $HTMLRESPONSE = $HTMLRESPONSE -replace '!RESULT', $RESULT # return HTML answer to caller $BUFFER = [Text.Encoding]::UTF8.GetBytes($HTMLRESPONSE) $RESPONSE.ContentLength64 = $BUFFER.Length $RESPONSE.OutputStream.Write($BUFFER, 0, $BUFFER.Length) } # and finish answer to client $RESPONSE.Close() # received command to stop webserver? if ($RECEIVED -eq 'GET /exit' -or $RECEIVED -eq 'GET /quit') { # then break out of while loop "$(Get-Date -Format s) Stopping powershell webserver..." break; } } } finally { # Stop powershell webserver $LISTENER.Stop() $LISTENER.Close() "$(Get-Date -Format s) Powershell webserver stopped." }