Start-Webserver.ps1
<#
.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. Any other call delivers the static content that fits to the path provided. If the static path is a directory, a file index.htm, index.html, default.htm or default.html in this directory is delivered if present. 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 .Parameter BASEDIR Base directory for static content (default: current directory) .Inputs None .Outputs None .Example Start-Webserver Starts webserver with binding to http://localhost:8080/ .Example Start-Webserver "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 -Command \"Start-Webserver http://+:8080/\"" /SC ONSTART /RU SYSTEM /RL HIGHEST /F Starts powershell webserver as scheduled task as user local system every time the computer starts. 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 running with low priority per default, so some functions might be slow. .Notes Version 1.6, 2024-01-31 Author: Markus Scholtes .LINK https://github.com/MScholtes/WebServer .LINK https://github.com/MScholtes/TechNet-Gallery #> function Start-Webserver { Param([STRING]$BINDING = 'http://localhost:8080/', [STRING]$BASEDIR = "") # 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/' if ($BASEDIR -eq "") { # current filesystem path as base path for static content $BASEDIR = (Get-Location -PSProvider "FileSystem").ToString() } # convert to absolute path $BASEDIR = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($BASEDIR) $IDXLIST = "\index.htm", "\index.html", "\default.htm", "\default.html" $NOIDXFILE = $TRUE foreach ($IDXNAME in $IDXLIST) { # index file in base dir? $CHECKFILE = $BASEDIR.TrimEnd("/\") + $IDXNAME if (Test-Path $CHECKFILE -PathType Leaf) { # index file exists, end loop $NOIDXFILE = $FALSE break } } # MIME hash table for static content $MIMEHASH = @{".avi"="video/x-msvideo"; ".crt"="application/x-x509-ca-cert"; ".css"="text/css"; ".der"="application/x-x509-ca-cert"; ".doc"="application/msword"; ".flv"="video/x-flv"; ".gif"="image/gif"; ".htm"="text/html"; ".html"="text/html"; ".ico"="image/x-icon"; ".jar"="application/java-archive"; ".jpeg"="image/jpeg"; ".jpg"="image/jpeg"; ".js"="application/javascript"; ".json"="application/json"; ".mjs"="application/javascript"; ".mov"="video/quicktime"; ".mp3"="audio/mpeg"; ".mp4"="video/mp4"; ".mpeg"="video/mpeg"; ".mpg"="video/mpeg"; ".pdf"="application/pdf"; ".pem"="application/x-x509-ca-cert"; ".pl"="application/x-perl"; ".png"="image/png"; ".rss"="application/rss+xml"; ".shtml"="text/html"; ".svg"="image/svg+xml"; ".txt"="text/plain"; ".war"="application/java-archive"; ".wmv"="video/x-ms-wmv"; ".xml"="application/xml"; ".xsl"="application/xml"} # HTML answer templates for specific calls, placeholders !RESULT, !FORMFIELD, !PROMPT are allowed $HTMLRESPONSECONTENTS = @{ 'GET /command' = @" <!doctype html><html><body> !HEADERLINE <pre>!RESULT</pre> <form method="GET" action="/command"> <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' = @" <!doctype html><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' = @" <!doctype html><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' = @" <!doctype html><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' = @" <!doctype html><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' = "<!doctype html><html><body>!HEADERLINE<pre>!RESULT</pre></body></html>" 'POST /upload' = "<!doctype html><html><body>!HEADERLINE<pre>!RESULT</pre></body></html>" 'GET /exit' = "<!doctype html><html><body>Stopped webserver</body></html>" 'GET /quit' = "<!doctype html><html><body>Stopped webserver</body></html>" 'GET /log' = "<!doctype html><html><body>!HEADERLINELog of webserver:<br /><pre>!RESULT</pre></body></html>" 'GET /starttime' = "<!doctype html><html><body>!HEADERLINEWebserver started at $(Get-Date -Format s)</body></html>" 'GET /time' = "<!doctype html><html><body>!HEADERLINECurrent time: !RESULT</body></html>" 'GET /beep' = "<!doctype html><html><body>!HEADERLINEBEEP...</body></html>" } # Set navigation header line for all web pages $HEADERLINE = "<p><a href='/command'>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 webserver "$(Get-Date -Format s) Starting webserver..." $LISTENER = New-Object System.Net.HttpListener $LISTENER.Prefixes.Add($BINDING) $LISTENER.Start() $Error.Clear() try { "$(Get-Date -Format s) Webserver started." $WEBLOG = "$(Get-Date -Format s) Webserver started.`n" while ($LISTENER.IsListening) { # analyze incoming request $CONTEXT = $LISTENER.GetContext() $REQUEST = $CONTEXT.Request $RESPONSE = $CONTEXT.Response $RESPONSEWRITTEN = $FALSE # start logging $LOGLINE = "$(Get-Date -Format s) $($REQUEST.RemoteEndPoint.Address.ToString())" # is there a fixed coding for the request? $RECEIVED = '{0} {1}' -f $REQUEST.httpMethod, $REQUEST.Url.LocalPath if (($RECEIVED -eq "GET /") -and ($NOIDXFILE)) { $RECEIVED = "GET /command" } $HTMLRESPONSE = $HTMLRESPONSECONTENTS[$RECEIVED] $RESULT = '' # check for known commands switch ($RECEIVED) { "GET /command" { # 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 = "" $RESULT = Invoke-Expression -EA SilentlyContinue $FORMFIELD 2> $NULL | Out-String } catch { # just ignore. Error handling comes afterwards since not every error throws an exception } 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. Use Windows 1252 to ensure no data loss in process of bytes-string conversion $READER = New-Object System.IO.StreamReader($REQUEST.InputStream, [System.Text.Encoding]::GetEncoding(1252)) $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" | ForEach-Object { # 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 = "" $RESULT = Invoke-Expression -EA SilentlyContinue $EXECUTE 2> $NULL | Out-String } catch { # just ignore. Error handling comes afterwards since not every error throws an exception } 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. Use Windows 1252 to ensure no data loss in process of bytes-string conversion $READER = New-Object System.IO.StreamReader($REQUEST.InputStream, [System.Text.Encoding]::GetEncoding(1252)) $DATA = $READER.ReadToEnd() $READER.Close() $REQUEST.InputStream.Close() # get headers into hash table $HEADER = @{} $DATA.Split('&') | ForEach-Object { $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)) { # HTML escape name for possible cjk filenames $ESCFORMFIELD = [Net.WebUtility]::HtmlDecode($FORMFIELD) # check if file exists if (Test-Path $ESCFORMFIELD -PathType Leaf) { try { # ... download file $BUFFER = [System.IO.File]::ReadAllBytes($ESCFORMFIELD) $RESPONSE.ContentLength64 = $BUFFER.Length $RESPONSE.SendChunked = $FALSE $RESPONSE.ContentType = "application/octet-stream" $FILENAME = Split-Path -Leaf $ESCFORMFIELD $RESPONSE.AddHeader("Content-Disposition", "attachment; filename=$FILENAME") $RESPONSE.AddHeader("Last-Modified", [IO.File]::GetLastWriteTime($ESCFORMFIELD).ToString('r')) $RESPONSE.AddHeader("Server", "Powershell Webserver/1.6 on ") $RESPONSE.OutputStream.Write($BUFFER, 0, $BUFFER.Length) # mark response as already given $RESPONSEWRITTEN = $TRUE } catch { # just ignore. Error handling comes afterwards since not every error throws an exception } 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. Use Windows 1252 to ensure no data loss in process of bytes-string conversion $READER = New-Object System.IO.StreamReader($REQUEST.InputStream, [System.Text.Encoding]::GetEncoding(1252)) $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" | ForEach-Object { # 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 { # HTML escape name for possible cjk filenames $ESCTARGETNAME = [Net.WebUtility]::HtmlDecode($TARGETNAME) # ... save file with the Windows 1252 encoding to preserve special characters [IO.File]::WriteAllText($ESCTARGETNAME, $FILEDATA, [System.Text.Encoding]::GetEncoding(1252)) } catch { # just ignore. Error handling comes afterwards since not every error throws an exception } 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 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 webserver, nothing to do here break } "GET /exit" { # stop webserver, nothing to do here break } default { # unknown command, check if path to file # create physical path based upon the base dir and url $CHECKDIR = $BASEDIR.TrimEnd("/\") + $REQUEST.Url.LocalPath $CHECKFILE = "" if (Test-Path $CHECKDIR -PathType Container) { # physical path is a directory $IDXLIST = "/index.htm", "/index.html", "/default.htm", "/default.html" foreach ($IDXNAME in $IDXLIST) { # check if an index file is present $CHECKFILE = $CHECKDIR.TrimEnd("/\") + $IDXNAME if (Test-Path $CHECKFILE -PathType Leaf) { # index file found, path now in $CHECKFILE break } $CHECKFILE = "" } if ($CHECKFILE -eq "") { # generate directory listing $HTMLRESPONSE = "<!doctype html><html><head><title>$($REQUEST.Url.LocalPath)</title><meta charset=""utf-8""></head><body><H1>$($REQUEST.Url.LocalPath)</H1><hr><pre>" if ($REQUEST.Url.LocalPath -ne "" -And $REQUEST.Url.LocalPath -ne "/" -And $REQUEST.Url.LocalPath -ne "`\" -And $REQUEST.Url.LocalPath -ne ".") { # link to parent directory $PARENTDIR = (Split-Path $REQUEST.Url.LocalPath -Parent) -replace '\\','/' if ($PARENTDIR.IndexOf("/") -ne 0) { $PARENTDIR = "/" + $PARENTDIR } $PARENTDIR = $PARENTDIR.TrimEnd("/\") $HTMLRESPONSE += "<pre><a href=""$PARENTDIR/"">[Parent directory]</a><br><br>" } # read in directory listing $ENTRIES = Get-ChildItem -EA SilentlyContinue -Path $CHECKDIR # process directories $ENTRIES | Where-Object { $_.PSIsContainer } | ForEach-Object { $HTMLRESPONSE += "$($_.LastWriteTime.ToString()) <dir> <a href=""$(Join-Path $REQUEST.Url.LocalPath $_.Name)/"">$($_.Name)</a><br>" } # process files $ENTRIES | Where-Object { !$_.PSIsContainer } | ForEach-Object { $HTMLRESPONSE += "$($_.LastWriteTime.ToString()) $("{0,10}" -f $_.Length) <a href=""$(Join-Path $REQUEST.Url.LocalPath $_.Name)"">$($_.Name)</a><br>" } # end of directory listing $HTMLRESPONSE += "</pre><hr></body></html>" } } else { # no directory, check for file if (Test-Path $CHECKDIR -PathType Leaf) { # file found, path now in $CHECKFILE $CHECKFILE = $CHECKDIR } } if ($CHECKFILE -ne "") { # static content available $EXTENSION = [IO.Path]::GetExtension($CHECKFILE) if ($EXTENSION -in @(".bat", ".cmd", ".ps1", ".psp")) { # ... execute script # retrieve parameters $PARAMETERS = '' if ($REQUEST.HasEntityBody) { # POST method # read complete header (inkl. file data) into string. Use Windows 1252 to ensure no data loss in process of bytes-string conversion $READER = New-Object System.IO.StreamReader($REQUEST.InputStream, [System.Text.Encoding]::GetEncoding(1252)) $DATA = $READER.ReadToEnd() $READER.Close() $REQUEST.InputStream.Close() # remove boundary marker from parameter string $PARAMETERS = ([URI]::UnescapeDataString($DATA)) -replace "\+"," " -replace "&"," " } else { # GET method $PARAMETERS = [URI]::UnescapeDataString($REQUEST.Url.Query) if (![STRING]::IsNullOrEmpty($PARAMETERS)) { # remove boundary marker from query string $PARAMETERS = $PARAMETERS.Substring(1) -replace "\+"," " -replace "&"," " } } $HTMLRESPONSE = "<!doctype html><html><body><pre>!RESULT</pre></body></html>" switch ($EXTENSION) { ".ps1" { try { $SCRIPTFILE = [System.IO.File]::ReadAllText($CHECKFILE) $EXECUTE = "function Powershell-WebServer-Func {`n" + $SCRIPTFILE + "`n}`nPowershell-WebServer-Func " + $PARAMETERS $RESULT = "" $RESULT = Invoke-Expression -EA SilentlyContinue $EXECUTE 2> $NULL | Out-String } catch { # just ignore. Error handling comes afterwards since not every error throws an exception } if ($Error.Count -gt 0) { # retrieve error message on error $RESULT += "`nError while executing script '$CHECKFILE'`n`n" $RESULT += $Error[0] $Error.Clear() } break } { $_ -in (".bat",".cmd") } { try { $RESULT = "" $RESULT = cmd.exe /c $CHECKFILE $PARAMETERS 2>&1 } catch { # just ignore. Error handling comes afterwards since not every error throws an exception } if ($Error.Count -gt 0) { # retrieve error message on error $RESULT += "`nError while executing script '$CHECKFILE'`n`n" $RESULT += $Error[0] $Error.Clear() } break } ".psp" { try { $SCRIPTFILE = [System.IO.File]::ReadAllText($CHECKFILE) # assume text mode at script start $CURRENTMODE = $TRUE $PARSEDSCRIPT = "@`"`r`n" # create array of script lines $TEXTLINES = $SCRIPTFILE.split("`n") -replace "\r$","" if ($TEXTLINES[0].TrimStart() -match '<%*') { # text starts with code $PARSEDSCRIPT = "" $CURRENTMODE = $FALSE $TEXTLINES[0] = $TEXTLINES[0].TrimStart().SubString(2) } for ($l = 0; $l -lt $TEXTLINES.Length; $l++) { # iterate through lines if ($TEXTLINES[$l].IndexOf("%") -ge 0) { $TEXTISEMPTY = $FALSE $STARTCODEINLINE = $FALSE $PARSEDLINE = "" $TEXTPART = $TEXTLINES[$l].split("%") for ($i = 0; $i -lt $TEXTPART.Length; $i++) { # iterate through text block between percent signs if ($CURRENTMODE) { # current mode is text mode if ($TEXTPART[$i][$TEXTPART[$i].Length - 1] -eq "<") { # '<%' found, switch to code mode $PARSEDLINE += "$($TEXTPART[$i].Substring(0, $TEXTPART[$i].Length - 1))" if ($PARSEDLINE.Length -gt 0) { # if exist process text before '<%' $PARSEDSCRIPT += $PARSEDLINE -replace '\"@','`"@' -replace '\$','`$' $PARSEDLINE = "" } $CURRENTMODE = $FALSE $STARTCODEINLINE = $TRUE } else { # '%' without impact found if ($i -eq 0) { $PARSEDLINE += "$($TEXTPART[$i])" } else { $PARSEDLINE += "%$($TEXTPART[$i])" } } } else { # current mode is code mode if ($TEXTPART[$i][0] -eq ">") { # '%>' found, switch to text mode $CURRENTMODE = $TRUE if ($PARSEDLINE.Length -gt 0) { # if exist process code before '%>' if ($STARTCODEINLINE) { # code block starts and stops in current line if ($PARSEDLINE.TrimStart() -match '=*') { $PARSEDLINE = $PARSEDLINE.TrimStart().TrimStart('=') } $PARSEDSCRIPT += "`$($PARSEDLINE)" $STARTCODEINLINE = $FALSE } else { if (($TEXTPART[$i].Length -gt 1) -Or ($i -lt $TEXTPART.Length-1) -Or ($l -lt $TEXTLINES.Length-1)) { $PARSEDSCRIPT += "$PARSEDLINE`r`n@`"`r`n" } else { # omit switching to text mode if last command of script $PARSEDSCRIPT += "$PARSEDLINE`r`n" $CURRENTMODE = $FALSE } } $PARSEDLINE = "" } else { if (($TEXTPART[$i].Length -gt 1) -Or ($i -lt $TEXTPART.Length-1) -Or ($l -lt $TEXTLINES.Length-1)) { $PARSEDSCRIPT += "@`"`r`n" } else { # omit switching to text mode if last command of script $CURRENTMODE = $FALSE } } if ($TEXTPART[$i][$TEXTPART[$i].Length - 1] -eq "<") { # switch to script mode at the end of current block $PARSEDSCRIPT += "$($TEXTPART[$i].Substring(1, $TEXTPART[$i].Length - 2))" -replace '\"@','`"@' -replace '\$','`$' $CURRENTMODE = $FALSE $STARTCODEINLINE = $TRUE } else { # continue with command mode at the end of current block $PARSEDLINE += "$($TEXTPART[$i].Substring(1, $TEXTPART[$i].Length - 1))" if ($PARSEDLINE.Length -eq 0) { # avoid empty text manipulations $TEXTISEMPTY = $TRUE } } } else { # '%' without impact found if (($i -lt $TEXTPART.Length-1) -And ($TEXTPART[$i+1][0] -ne ">")) { $PARSEDLINE += "$($TEXTPART[$i])%" } else { $PARSEDLINE += "$($TEXTPART[$i])" } } } } # process remaining text of line if ($STARTCODEINLINE) { # text started as if inline code, but not ended so in line if ($TEXTLINES[$l] -ne "<%") { $PARSEDSCRIPT += "`r`n" } $PARSEDSCRIPT += "`"@`r`n$PARSEDLINE" } else { # process text if not empty if (!$TEXTISEMPTY) { if ($CURRENTMODE) { $PARSEDSCRIPT += $PARSEDLINE -replace '\"@','`"@' -replace '\$','`$' } else { $PARSEDSCRIPT += $PARSEDLINE } } } $PARSEDSCRIPT += "`r`n" } else { # no percent sign found in line if ($CURRENTMODE) { $PARSEDSCRIPT += $TEXTLINES[$l] -replace '\"@','`"@' -replace '\$','`$' } else { $PARSEDSCRIPT += $TEXTLINES[$l] } $PARSEDSCRIPT += "`r`n" } } if ($CURRENTMODE) { # stop text mode at end of script $PARSEDSCRIPT += "`"@`r`n" } $EXECUTE = "function Powershell-WebServer-Func {`n" + $PARSEDSCRIPT + "`n}`nPowershell-WebServer-Func " + $PARAMETERS $RESULT = "" $RESULT = Invoke-Expression -EA SilentlyContinue $EXECUTE 2> $NULL | Out-String $HTMLRESPONSE = "!RESULT" } catch { # just ignore. Error handling comes afterwards since not every error throws an exception } if ($Error.Count -gt 0) { # retrieve error message on error $RESULT += "`nError while executing file '$CHECKFILE'`n`n" $RESULT += $Error[0] $Error.Clear() } break } } } else { # ... deliver static content try { $BUFFER = [System.IO.File]::ReadAllBytes($CHECKFILE) $RESPONSE.ContentLength64 = $BUFFER.Length $RESPONSE.SendChunked = $FALSE if ($MIMEHASH.ContainsKey($EXTENSION)) { # known mime type for this file's extension available $RESPONSE.ContentType = $MIMEHASH.Item($EXTENSION) } else { # no, serve as binary download $RESPONSE.ContentType = "application/octet-stream" $FILENAME = Split-Path -Leaf $CHECKFILE $RESPONSE.AddHeader("Content-Disposition", "attachment; filename=$FILENAME") } $RESPONSE.AddHeader("Last-Modified", [IO.File]::GetLastWriteTime($CHECKFILE).ToString('r')) $RESPONSE.AddHeader("Server", "Powershell Webserver/1.6 on ") $RESPONSE.OutputStream.Write($BUFFER, 0, $BUFFER.Length) # mark response as already given $RESPONSEWRITTEN = $TRUE } catch { # just ignore. Error handling comes afterwards since not every error throws an exception } if ($Error.Count -gt 0) { # retrieve error message on error $RESULT = "`nError while downloading '$CHECKFILE'`n`n" $RESULT += $Error[0] $Error.Clear() } } } else { # no file to serve found, return error if (!(Test-Path $CHECKDIR -PathType Container)) { $RESPONSE.StatusCode = 404 $HTMLRESPONSE = '<!doctype html><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.AddHeader("Last-Modified", [DATETIME]::Now.ToString('r')) $RESPONSE.AddHeader("Server", "Powershell Webserver/1.6 on ") $RESPONSE.OutputStream.Write($BUFFER, 0, $BUFFER.Length) } # logging $LOGLINE += " $($RESPONSE.StatusCode) $($REQUEST.httpMethod) $($REQUEST.Url.PathAndQuery)" # ... to console $LOGLINE # and to log variable $WEBLOG += "$LOGLINE`n" # 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 webserver..." break; } } } finally { # Stop webserver $LISTENER.Stop() $LISTENER.Close() "$(Get-Date -Format s) Webserver stopped." } } |