Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- <#
- .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 /' = @"
- <html><body>
- !HEADERLINE
- <pre>!RESULT</pre>
- <form method="GET" action="/">
- <b>!PROMPT </b><input type="text" maxlength=255 size=80 name="command" value='!FORMFIELD'>
- <input type="submit" name="button" value="Enter">
- </form>
- </body></html>
- "@
- 'GET /script' = @"
- <html><body>
- !HEADERLINE
- <form method="POST" enctype="multipart/form-data" action="/script">
- <p><b>Script to execute:</b><input type="file" name="filedata"></p>
- <b>Parameters:</b><input type="text" maxlength=255 size=80 name="parameter">
- <input type="submit" name="button" value="Execute">
- </form>
- </body></html>
- "@
- 'GET /download' = @"
- <html><body>
- !HEADERLINE
- <pre>!RESULT</pre>
- <form method="POST" action="/download">
- <b>Path to file:</b><input type="text" maxlength=255 size=80 name="filepath" value='!FORMFIELD'>
- <input type="submit" name="button" value="Download">
- </form>
- </body></html>
- "@
- 'POST /download' = @"
- <html><body>
- !HEADERLINE
- <pre>!RESULT</pre>
- <form method="POST" action="/download">
- <b>Path to file:</b><input type="text" maxlength=255 size=80 name="filepath" value='!FORMFIELD'>
- <input type="submit" name="button" value="Download">
- </form>
- </body></html>
- "@
- 'GET /upload' = @"
- <html><body>
- !HEADERLINE
- <form method="POST" enctype="multipart/form-data" action="/upload">
- <p><b>File to upload:</b><input type="file" name="filedata"></p>
- <b>Path to store on webserver:</b><input type="text" maxlength=255 size=80 name="filepath">
- <input type="submit" name="button" value="Upload">
- </form>
- </body></html>
- "@
- 'POST /script' = "<html><body>!HEADERLINE<pre>!RESULT</pre></body></html>"
- 'POST /upload' = "<html><body>!HEADERLINE<pre>!RESULT</pre></body></html>"
- 'GET /exit' = "<html><body>Stopped powershell webserver</body></html>"
- 'GET /quit' = "<html><body>Stopped powershell webserver</body></html>"
- 'GET /log' = "<html><body>!HEADERLINELog of powershell webserver:<br /><pre>!RESULT</pre></body></html>"
- 'GET /starttime' = "<html><body>!HEADERLINEPowershell webserver started at $(Get-Date -Format s)</body></html>"
- 'GET /time' = "<html><body>!HEADERLINECurrent time: !RESULT</body></html>"
- 'GET /beep' = "<html><body>!HEADERLINEBEEP...</body></html>"
- }
- # Set navigation header line for all web pages
- $HEADERLINE = "<p><a href='/'>Command execution</a> <a href='/script'>Execute script</a> <a href='/download'>Download file</a> <a href='/upload'>Upload file</a> <a href='/log'>Web logs</a> <a href='/starttime'>Webserver start time</a> <a href='/time'>Current time</a> <a href='/beep'>Beep</a> <a href='/quit'>Stop webserver</a></p>"
- # 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 = '<html><body>Page not found</body></html>'
- }
- }
- # 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."
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement