View difference between Paste ID: p0v0G26x and VUmfeRgW
SHOW: | | - or go back to the newest paste.
1
<#
2
.Synopsis
3
Starts powershell webserver
4
.Description
5
Starts webserver as powershell process.
6
Call of the root page (e.g. http://localhost:8080/) returns a powershell execution web form.
7
Call of /script uploads a powershell script and executes it (as a function).
8
Call of /log returns the webserver logs, /starttime the start time of the webserver, /time the current time.
9
/download downloads and /upload uploads a file. /beep generates a sound and /quit or /exit stops the webserver.
10
11
You may have to configure a firewall exception to allow access to the chosen port, e.g. with:
12
  netsh advfirewall firewall add rule name="Powershell Webserver" dir=in action=allow protocol=TCP localport=8080
13
14
After stopping the webserver you should remove the rule, e.g.:
15
  netsh advfirewall firewall delete rule name="Powershell Webserver"
16
.Parameter BINDING
17
Binding of the webserver
18
.Inputs
19
None
20
.Outputs
21
None
22
.Example
23
Start-Webserver.ps1
24
25
Starts webserver with binding to http://localhost:8080/
26
.Example
27
Start-Webserver.ps1 "http://+:8080/"
28
29
Starts webserver with binding to all IP addresses of the system.
30
Administrative rights are necessary.
31
.Example
32
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
33
34
Starts powershell webserver as scheduled task as user local system every time the computer starts (when the
35
correct path to the file Start-WebServer.ps1 is given).
36
You can start the webserver task manually with
37
  schtasks.exe /Run /TN "Powershell Webserver"
38
Delete the webserver task with
39
  schtasks.exe /Delete /TN "Powershell Webserver"
40
Scheduled tasks are always running with low priority, so some functions might be slow.
41
.Notes
42
Author: Markus Scholtes, 2016-10-22
43
#>
44
Param([STRING]$BINDING = 'http://localhost:8080/')
45
46
# No adminstrative permissions are required for a binding to "localhost"
47
# $BINDING = 'http://localhost:8080/'
48
# Adminstrative permissions are required for a binding to network names or addresses.
49
# + takes all requests to the port regardless of name or ip, * only requests that no other listener answers:
50
# $BINDING = 'http://+:8080/'
51
52
# HTML answer templates for specific calls, placeholders !RESULT, !FORMFIELD, !PROMPT are allowed
53
$HTMLRESPONSECONTENTS = @{
54
  'GET /'  =  @"
55
<html><body>
56
	!HEADERLINE
57
	<pre>!RESULT</pre>
58
	<form method="GET" action="/">
59
	<b>!PROMPT&nbsp;</b><input type="text" maxlength=255 size=80 name="command" value='!FORMFIELD'>
60
  <input type="submit" name="button" value="Enter">
61
	</form>
62
</body></html>
63
"@
64
  'GET /script'  =  @"
65
<html><body>
66
	!HEADERLINE
67
	<form method="POST" enctype="multipart/form-data" action="/script">
68
	<p><b>Script to execute:</b><input type="file" name="filedata"></p>
69
	<b>Parameters:</b><input type="text" maxlength=255 size=80 name="parameter">
70
  <input type="submit" name="button" value="Execute">
71
	</form>
72
</body></html>
73
"@
74
  'GET /download'  =  @"
75
<html><body>
76
	!HEADERLINE
77
	<pre>!RESULT</pre>
78
	<form method="POST" action="/download">
79
	<b>Path to file:</b><input type="text" maxlength=255 size=80 name="filepath" value='!FORMFIELD'>
80
  <input type="submit" name="button" value="Download">
81
	</form>
82
</body></html>
83
"@
84
  'POST /download'  =  @"
85
<html><body>
86
	!HEADERLINE
87
	<pre>!RESULT</pre>
88
	<form method="POST" action="/download">
89
	<b>Path to file:</b><input type="text" maxlength=255 size=80 name="filepath" value='!FORMFIELD'>
90
  <input type="submit" name="button" value="Download">
91
	</form>
92
</body></html>
93
"@
94
  'GET /upload'  =  @"
95
<html><body>
96
	!HEADERLINE
97
	<form method="POST" enctype="multipart/form-data" action="/upload">
98
	<p><b>File to upload:</b><input type="file" name="filedata"></p>
99
	<b>Path to store on webserver:</b><input type="text" maxlength=255 size=80 name="filepath">
100
  <input type="submit" name="button" value="Upload">
101
	</form>
102
</body></html>
103
"@
104
  'POST /script' = "<html><body>!HEADERLINE<pre>!RESULT</pre></body></html>"
105
  'POST /upload' = "<html><body>!HEADERLINE<pre>!RESULT</pre></body></html>"
106
  'GET /exit' = "<html><body>Stopped powershell webserver</body></html>"
107
  'GET /quit' = "<html><body>Stopped powershell webserver</body></html>"
108
  'GET /log' = "<html><body>!HEADERLINELog of powershell webserver:<br /><pre>!RESULT</pre></body></html>"
109
  'GET /starttime' = "<html><body>!HEADERLINEPowershell webserver started at $(Get-Date -Format s)</body></html>"
110
  'GET /time' = "<html><body>!HEADERLINECurrent time: !RESULT</body></html>"
111
  'GET /beep' = "<html><body>!HEADERLINEBEEP...</body></html>"
112
}
113
114
# Set navigation header line for all web pages
115
$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>"
116
117
# Starting the powershell webserver
118
"$(Get-Date -Format s) Starting powershell webserver..."
119
$LISTENER = New-Object System.Net.HttpListener
120
$LISTENER.Prefixes.Add($BINDING)
121
$LISTENER.Start()
122
$Error.Clear()
123
124
try
125
{
126
	"$(Get-Date -Format s) Powershell webserver started."
127
	$WEBLOG = "$(Get-Date -Format s) Powershell webserver started.`n"
128
  while ($LISTENER.IsListening)
129
  {
130
    # analyze incoming request
131
    $CONTEXT = $LISTENER.GetContext()
132
    $REQUEST = $CONTEXT.Request
133
    $RESPONSE = $CONTEXT.Response
134
    $RESPONSEWRITTEN = $FALSE
135
136
    # log to console
137
    "$(Get-Date -Format s) $($REQUEST.RemoteEndPoint.Address.ToString()) $($REQUEST.httpMethod) $($REQUEST.Url.PathAndQuery)"
138
    # and in log variable
139
    $WEBLOG += "$(Get-Date -Format s) $($REQUEST.RemoteEndPoint.Address.ToString()) $($REQUEST.httpMethod) $($REQUEST.Url.PathAndQuery)`n"
140
141
    # is there a fixed coding for the request?
142
    $RECEIVED = '{0} {1}' -f $REQUEST.httpMethod, $REQUEST.Url.LocalPath
143
    $HTMLRESPONSE = $HTMLRESPONSECONTENTS[$RECEIVED]
144
	  $RESULT = ''
145
146
    # check for known commands
147
    switch ($RECEIVED)
148
    {
149
      "GET /"
150
      {	# execute command
151
	  		# retrieve GET query string
152
	  		$FORMFIELD = ''
153
	    	$FORMFIELD = [URI]::UnescapeDataString(($REQUEST.Url.Query -replace "\+"," "))
154
	    	# remove fixed form fields out of query string
155
	    	$FORMFIELD = $FORMFIELD -replace "\?command=","" -replace "\?button=enter","" -replace "&command=","" -replace "&button=enter",""
156
	    	# when command is given...
157
    		if (![STRING]::IsNullOrEmpty($FORMFIELD))
158
	   		{
159
		    	try {
160
		    		# ... execute command
161
		    		$RESULT = Invoke-Expression -EA SilentlyContinue $FORMFIELD 2> $NULL | Out-String
162
		    	}
163
		    	catch	{}
164
		    	if ($Error.Count -gt 0)
165
		    	{ # retrieve error message on error
166
		    		$RESULT += "`nError while executing '$FORMFIELD'`n`n"
167
		    		$RESULT += $Error[0]
168
		    		$Error.Clear()
169
		    	}
170
	    	}
171
	    	# preset form value with command for the caller's convenience
172
   			$HTMLRESPONSE = $HTMLRESPONSE -replace '!FORMFIELD', $FORMFIELD
173
   			# insert powershell prompt to form
174
	  		$PROMPT = "PS $PWD>"
175
   			$HTMLRESPONSE = $HTMLRESPONSE -replace '!PROMPT', $PROMPT
176
        break
177
      }
178
179
      "GET /script"
180
      { # present upload form, nothing to do here
181
      	break
182
      }
183
184
      "POST /script"
185
      { # upload and execute script
186
187
      	# only if there is body data in the request
188
      	if ($REQUEST.HasEntityBody)
189
      	{
190
      		# set default message to error message (since we just stop processing on error)
191
      		$RESULT = "Received corrupt or incomplete form data"
192
193
      		# check content type
194
      		if ($REQUEST.ContentType)
195
      		{
196
						# retrieve boundary marker for header separation
197
						$BOUNDARY = $NULL
198
						if ($REQUEST.ContentType -match "boundary=(.*);")
199
						{	$BOUNDARY = "--" + $MATCHES[1] }
200
						else
201
						{ # marker might be at the end of the line
202
							if ($REQUEST.ContentType -match "boundary=(.*)$")
203
							{ $BOUNDARY = "--" + $MATCHES[1] }
204
						}
205
206
	      		if ($BOUNDARY)
207
	      		{ # only if header separator was found
208
209
							# read complete header (inkl. file data) into string
210
      				$READER = New-Object System.IO.StreamReader($REQUEST.InputStream, $REQUEST.ContentEncoding)
211
							$DATA = $READER.ReadToEnd()
212
							$READER.Close()
213
							$REQUEST.InputStream.Close()
214
215
							$PARAMETERS = ""
216
							$SOURCENAME = ""
217
218
							# separate headers by boundary string
219
							$DATA -replace "$BOUNDARY--\r\n", "$BOUNDARY`r`n--" -split "$BOUNDARY\r\n" | % {
220
								# omit leading empty header and end marker header
221
								if (($_ -ne "") -and ($_ -ne "--"))
222
								{
223
									# only if well defined header (separation between meta data and data)
224
									if ($_.IndexOf("`r`n`r`n") -gt 0)
225
									{
226
										# header data before two CRs is meta data
227
										# first look for the file in header "filedata"
228
										if ($_.Substring(0, $_.IndexOf("`r`n`r`n")) -match "Content-Disposition: form-data; name=(.*);")
229
										{
230
											$HEADERNAME = $MATCHES[1] -replace '\"'
231
											# headername "filedata"?
232
											if ($HEADERNAME -eq "filedata")
233
											{ # yes, look for source filename
234
												if ($_.Substring(0, $_.IndexOf("`r`n`r`n")) -match "filename=(.*)")
235
												{ # source filename found
236
													$SOURCENAME = $MATCHES[1] -replace "`r`n$" -replace "`r$" -replace '\"'
237
													# store content of file in variable
238
													$FILEDATA = $_.Substring($_.IndexOf("`r`n`r`n") + 4) -replace "`r`n$"
239
											  }
240
											}
241
										}
242
										else
243
										{ # look for other headers (we need "parameter")
244
											if ($_.Substring(0, $_.IndexOf("`r`n`r`n")) -match "Content-Disposition: form-data; name=(.*)")
245
											{ # header found
246
												$HEADERNAME = $MATCHES[1] -replace '\"'
247
												# headername "parameter"?
248
												if ($HEADERNAME -eq "parameter")
249
												{ # yes, look for paramaters
250
													$PARAMETERS = $_.Substring($_.IndexOf("`r`n`r`n") + 4) -replace "`r`n$" -replace "`r$"
251
												}
252
											}
253
										}
254
									}
255
								}
256
							}
257
258
							if ($SOURCENAME -ne "")
259
							{ # execute only if a source file exists
260
261
								$EXECUTE = "function Powershell-WebServer-Func {`n" + $FILEDATA + "`n}`nPowershell-WebServer-Func " + $PARAMETERS
262
		    				try {
263
		    					# ... execute script
264
		    					$RESULT = Invoke-Expression -EA SilentlyContinue $EXECUTE 2> $NULL | Out-String
265
		    				}
266
		    				catch	{}
267
		    				if ($Error.Count -gt 0)
268
		    				{ # retrieve error message on error
269
		    					$RESULT += "`nError while executing script $SOURCENAME`n`n"
270
		    					$RESULT += $Error[0]
271
		    					$Error.Clear()
272
		    				}
273
							}
274
							else
275
							{
276
								$RESULT = "No file data received"
277
							}
278
      			}
279
					}
280
      	}
281
      	else
282
      	{
283
      		$RESULT = "No client data received"
284
      	}
285
      	break
286
      }
287
288
      { $_ -like "* /download" } # GET or POST method are allowed for download page
289
      {	# download file
290
291
      	# is POST data in the request?
292
      	if ($REQUEST.HasEntityBody)
293
      	{ # POST request
294
					# read complete header into string
295
   				$READER = New-Object System.IO.StreamReader($REQUEST.InputStream, $REQUEST.ContentEncoding)
296
					$DATA = $READER.ReadToEnd()
297
					$READER.Close()
298
					$REQUEST.InputStream.Close()
299
300
					# get headers into hash table
301
					$HEADER = @{}
302
  				$DATA.Split('&') | % { $HEADER.Add([URI]::UnescapeDataString(($_.Split('=')[0] -replace "\+"," ")), [URI]::UnescapeDataString(($_.Split('=')[1] -replace "\+"," "))) }
303
304
  				# read header 'filepath'
305
  				$FORMFIELD = $HEADER.Item('filepath')
306
	    		# remove leading and trailing double quotes since Test-Path does not like them
307
	    		$FORMFIELD = $FORMFIELD -replace "^`"","" -replace "`"$",""
308
      	}
309
      	else
310
      	{ # GET request
311
312
		  		# retrieve GET query string
313
		  		$FORMFIELD = ''
314
	  	  	$FORMFIELD = [URI]::UnescapeDataString(($REQUEST.Url.Query -replace "\+"," "))
315
	    		# remove fixed form fields out of query string
316
	    		$FORMFIELD = $FORMFIELD -replace "\?filepath=","" -replace "\?button=download","" -replace "&filepath=","" -replace "&button=download",""
317
	    		# remove leading and trailing double quotes since Test-Path does not like them
318
	    		$FORMFIELD = $FORMFIELD -replace "^`"","" -replace "`"$",""
319
      	}
320
321
	    	# when path is given...
322
    		if (![STRING]::IsNullOrEmpty($FORMFIELD))
323
	   		{ # check if file exists
324
    			if (Test-Path $FORMFIELD -PathType Leaf)
325
    			{
326
			    	try {
327
			    		# ... download file
328
							$BUFFER = [System.IO.File]::ReadAllBytes($FORMFIELD)
329
							$RESPONSE.ContentLength64 = $BUFFER.Length
330
							$RESPONSE.SendChunked = $FALSE
331
							$RESPONSE.ContentType = "application/octet-stream"
332
							$FILENAME = Split-Path -Leaf $FORMFIELD
333
							$RESPONSE.AddHeader("Content-disposition", "attachment; filename=$FILENAME")
334
							$RESPONSE.OutputStream.Write($BUFFER, 0, $BUFFER.Length)
335
							# mark response as already given
336
	    				$RESPONSEWRITTEN = $TRUE
337
			    	}
338
			    	catch	{}
339
			    	if ($Error.Count -gt 0)
340
			    	{ # retrieve error message on error
341
			    		$RESULT += "`nError while downloading '$FORMFIELD'`n`n"
342
			    		$RESULT += $Error[0]
343
			    		$Error.Clear()
344
			    	}
345
		    	}
346
		    	else
347
		    	{
348
		    		# ... file not found
349
		    		$RESULT = "File $FORMFIELD not found"
350
		    	}
351
	    	}
352
	    	# preset form value with file path for the caller's convenience
353
   			$HTMLRESPONSE = $HTMLRESPONSE -replace '!FORMFIELD', $FORMFIELD
354
        break
355
      }
356
357
      "GET /upload"
358
      { # present upload form, nothing to do here
359
      	break
360
      }
361
362
      "POST /upload"
363
      { # upload file
364
365
      	# only if there is body data in the request
366
      	if ($REQUEST.HasEntityBody)
367
      	{
368
      		# set default message to error message (since we just stop processing on error)
369
      		$RESULT = "Received corrupt or incomplete form data"
370
371
      		# check content type
372
      		if ($REQUEST.ContentType)
373
      		{
374
						# retrieve boundary marker for header separation
375
						$BOUNDARY = $NULL
376
						if ($REQUEST.ContentType -match "boundary=(.*);")
377
						{	$BOUNDARY = "--" + $MATCHES[1] }
378
						else
379
						{ # marker might be at the end of the line
380
							if ($REQUEST.ContentType -match "boundary=(.*)$")
381
							{ $BOUNDARY = "--" + $MATCHES[1] }
382
						}
383
384
	      		if ($BOUNDARY)
385
	      		{ # only if header separator was found
386
387
							# read complete header (inkl. file data) into string
388
      				$READER = New-Object System.IO.StreamReader($REQUEST.InputStream, $REQUEST.ContentEncoding)
389
							$DATA = $READER.ReadToEnd()
390
							$READER.Close()
391
							$REQUEST.InputStream.Close()
392
393
							# variables for filenames
394
							$FILENAME = ""
395
							$SOURCENAME = ""
396
397
							# separate headers by boundary string
398
							$DATA -replace "$BOUNDARY--\r\n", "$BOUNDARY`r`n--" -split "$BOUNDARY\r\n" | % {
399
								# omit leading empty header and end marker header
400
								if (($_ -ne "") -and ($_ -ne "--"))
401
								{
402
									# only if well defined header (seperation between meta data and data)
403
									if ($_.IndexOf("`r`n`r`n") -gt 0)
404
									{
405
										# header data before two CRs is meta data
406
										# first look for the file in header "filedata"
407
										if ($_.Substring(0, $_.IndexOf("`r`n`r`n")) -match "Content-Disposition: form-data; name=(.*);")
408
										{
409
											$HEADERNAME = $MATCHES[1] -replace '\"'
410
											# headername "filedata"?
411
											if ($HEADERNAME -eq "filedata")
412
											{ # yes, look for source filename
413
												if ($_.Substring(0, $_.IndexOf("`r`n`r`n")) -match "filename=(.*)")
414
												{ # source filename found
415
													$SOURCENAME = $MATCHES[1] -replace "`r`n$" -replace "`r$" -replace '\"'
416
													# store content of file in variable
417
													$FILEDATA = $_.Substring($_.IndexOf("`r`n`r`n") + 4) -replace "`r`n$"
418
											  }
419
											}
420
										}
421
										else
422
										{ # look for other headers (we need "filepath" to know where to store the file)
423
											if ($_.Substring(0, $_.IndexOf("`r`n`r`n")) -match "Content-Disposition: form-data; name=(.*)")
424
											{ # header found
425
												$HEADERNAME = $MATCHES[1] -replace '\"'
426
												# headername "filepath"?
427
												if ($HEADERNAME -eq "filepath")
428
												{ # yes, look for target filename
429
													$FILENAME = $_.Substring($_.IndexOf("`r`n`r`n") + 4) -replace "`r`n$" -replace "`r$" -replace '\"'
430
												}
431
											}
432
										}
433
									}
434
								}
435
							}
436
437
							if ($FILENAME -ne "")
438
							{ # upload only if a targetname is given
439
								if ($SOURCENAME -ne "")
440
								{ # only upload if source file exists
441
442
									# check or construct a valid filename to store
443
									$TARGETNAME = ""
444
									# if filename is a container name, add source filename to it
445
									if (Test-Path $FILENAME -PathType Container)
446
									{
447
										$TARGETNAME = Join-Path $FILENAME -ChildPath $(Split-Path $SOURCENAME -Leaf)
448
									} else {
449
										# try name in the header
450
										$TARGETNAME = $FILENAME
451
									}
452
453
						    	try {
454
			    					# ... save file with the same encoding as received
455
										[IO.File]::WriteAllText($TARGETNAME, $FILEDATA, $REQUEST.ContentEncoding)
456
			    				}
457
			    				catch	{}
458
			    				if ($Error.Count -gt 0)
459
			    				{ # retrieve error message on error
460
			    					$RESULT += "`nError saving '$TARGETNAME'`n`n"
461
			    					$RESULT += $Error[0]
462
			    					$Error.Clear()
463
			    				}
464
			    				else
465
			    				{ # success
466
      							$RESULT = "File $SOURCENAME successfully uploaded as $TARGETNAME"
467
      						}
468
								}
469
								else
470
								{
471
									$RESULT = "No file data received"
472
								}
473
							}
474
							else
475
							{
476
								$RESULT = "Missing target file name"
477
							}
478
      			}
479
					}
480
      	}
481
      	else
482
      	{
483
      		$RESULT = "No client data received"
484
      	}
485
      	break
486
      }
487
488
      "GET /log"
489
      { # return the webserver log (stored in log variable)
490
      	$RESULT = $WEBLOG
491
      	break
492
      }
493
494
      "GET /time"
495
      { # return current time
496
      	$RESULT = Get-Date -Format s
497
      	break
498
      }
499
500
      "GET /starttime"
501
      { # return start time of the powershell webserver (already contained in $HTMLRESPONSE, nothing to do here)
502
      	break
503
      }
504
505
      "GET /beep"
506
      { # Beep
507
      	[CONSOLE]::beep(800, 300) # or "`a" or [char]7
508
      	break
509
      }
510
511
      "GET /quit"
512
      { # stop powershell webserver, nothing to do here
513
      	break
514
      }
515
516
      "GET /exit"
517
      { # stop powershell webserver, nothing to do here
518
      	break
519
      }
520
521
      default
522
 			{	# unknown command, return error
523
      	$RESPONSE.StatusCode = 404
524
      	$HTMLRESPONSE = '<html><body>Page not found</body></html>'
525
    	}
526
527
    }
528
529
    # only send response if not already done
530
    if (!$RESPONSEWRITTEN)
531
    {
532
    	# insert header line string into HTML template
533
   		$HTMLRESPONSE = $HTMLRESPONSE -replace '!HEADERLINE', $HEADERLINE
534
535
    	# insert result string into HTML template
536
   		$HTMLRESPONSE = $HTMLRESPONSE -replace '!RESULT', $RESULT
537
538
    	# return HTML answer to caller
539
    	$BUFFER = [Text.Encoding]::UTF8.GetBytes($HTMLRESPONSE)
540
    	$RESPONSE.ContentLength64 = $BUFFER.Length
541
    	$RESPONSE.OutputStream.Write($BUFFER, 0, $BUFFER.Length)
542
		}
543
544
    # and finish answer to client
545
    $RESPONSE.Close()
546
547
    # received command to stop webserver?
548
    if ($RECEIVED -eq 'GET /exit' -or $RECEIVED -eq 'GET /quit')
549
    { # then break out of while loop
550
    	"$(Get-Date -Format s) Stopping powershell webserver..."
551
    	break;
552
    }
553
  }
554
}
555
finally
556
{
557
  # Stop powershell webserver
558
  $LISTENER.Stop()
559
  $LISTENER.Close()
560
  "$(Get-Date -Format s) Powershell webserver stopped."
561
}