-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathHTTPListener.psm1
More file actions
199 lines (177 loc) · 9 KB
/
HTTPListener.psm1
File metadata and controls
199 lines (177 loc) · 9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
# Copyright (c) 2014 Microsoft Corp.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
Function ConvertTo-HashTable {
<#
.Synopsis
Convert an object to a HashTable
.Description
Convert an object to a HashTable excluding certain types. For example, ListDictionaryInternal doesn't support serialization therefore
can't be converted to JSON.
.Parameter InputObject
Object to convert
.Parameter ExcludeTypeName
Array of types to skip adding to resulting HashTable. Default is to skip ListDictionaryInternal and Object arrays.
.Parameter MaxDepth
Maximum depth of embedded objects to convert. Default is 4.
.Example
$bios = get-ciminstance win32_bios
$bios | ConvertTo-HashTable
#>
Param (
[Parameter(Mandatory=$true,ValueFromPipeline=$true)]
[Object]$InputObject,
[string[]]$ExcludeTypeName = @("ListDictionaryInternal","Object[]"),
[ValidateRange(1,10)][Int]$MaxDepth = 4
)
Process {
Write-Verbose "Converting to hashtable $($InputObject.GetType())"
#$propNames = Get-Member -MemberType Properties -InputObject $InputObject | Select-Object -ExpandProperty Name
$propNames = $InputObject.psobject.Properties | Select-Object -ExpandProperty Name
$hash = @{}
$propNames | % {
if ($InputObject.$_ -ne $null) {
if ($InputObject.$_ -is [string] -or (Get-Member -MemberType Properties -InputObject ($InputObject.$_) ).Count -eq 0) {
$hash.Add($_,$InputObject.$_)
} else {
if ($InputObject.$_.GetType().Name -in $ExcludeTypeName) {
Write-Verbose "Skipped $_"
} elseif ($MaxDepth -gt 1) {
$hash.Add($_,(ConvertTo-HashTable -InputObject $InputObject.$_ -MaxDepth ($MaxDepth - 1)))
}
}
}
}
$hash
}
}
Function Start-HTTPListener {
<#
.Synopsis
Creates a new HTTP Listener accepting PowerShell command line to execute
.Description
Creates a new HTTP Listener enabling a remote client to execute PowerShell command lines using a simple REST API.
This function requires running from an elevated administrator prompt to open a port.
Use Ctrl-C to stop the listener. You'll need to send another web request to allow the listener to stop since
it will be blocked waiting for a request.
.Parameter Port
Port to listen, default is 8888
.Parameter URL
URL to listen, default is /
.Parameter Auth
Authentication Schemes to use, default is IntegratedWindowsAuthentication
.Example
Start-HTTPListener -Port 8080 -Url PowerShell
Invoke-WebRequest -Uri "http://localhost:8888/PowerShell?command=get-service winmgmt&format=text" -UseDefaultCredentials | Format-List *
#>
Param (
[Parameter()]
[Int] $Port = 8888,
[Parameter()]
[String] $Url = "",
[Parameter()]
[System.Net.AuthenticationSchemes] $Auth = "Anonymous"
#[System.Net.AuthenticationSchemes]::IntegratedWindowsAuthentication
)
Process {
$ErrorActionPreference = "Stop"
# $CurrentPrincipal = New-Object Security.Principal.WindowsPrincipal( [Security.Principal.WindowsIdentity]::GetCurrent())
# if ( -not ($currentPrincipal.IsInRole( [Security.Principal.WindowsBuiltInRole]::Administrator ))) {
# Write-Error "This script must be executed from an elevated PowerShell session" -ErrorAction Stop
# }
if ($Url.Length -gt 0 -and -not $Url.EndsWith('/')) {
$Url += "/"
}
$listener = New-Object System.Net.HttpListener
$prefix = "http://*:$Port/$Url"
$listener.Prefixes.Add($prefix)
$listener.AuthenticationSchemes = $Auth
try {
$listener.Start()
while ($true) {
$statusCode = 200
Write-Warning "Note that thread is blocked waiting for a request. After using Ctrl-C to stop listening, you need to send a valid HTTP request to stop the listener cleanly."
Write-Warning "Sending 'exit' command will cause listener to stop immediately"
Write-Verbose "Listening on $port..."
$context = $listener.GetContext()
$request = $context.Request
# if (!$request.IsAuthenticated) {
# Write-Warning "Rejected request as user was not authenticated"
# $statusCode = 403
# $commandOutput = "Unauthorized"
# } else {
$identity = $context.User.Identity
Write-Verbose "Received request $(get-date) from $($identity.Name):"
$request | fl * | Out-String | Write-Verbose
# only allow requests that are the same identity as the one who started the listener
# if ($identity.Name -ne $CurrentPrincipal.Identity.Name) {
# Write-Warning "Rejected request as user doesn't match current security principal of listener"
# $statusCode = 403
# $commandOutput = "Unauthorized"
# } else {
if (-not $request.QueryString.HasKeys()) {
$commandOutput = "SYNTAX: command=<string> format=[JSON|TEXT|XML|NONE|CLIXML]"
$Format = "TEXT"
} else {
$command = $request.QueryString.Item("command")
if ($command -eq "exit") {
Write-Verbose "Received command to exit listener"
return
}
$Format = $request.QueryString.Item("format")
if ($Format -eq $Null) {
$Format = "JSON"
}
Write-Verbose "Command = $command"
Write-Verbose "Format = $Format"
try {
$script = $ExecutionContext.InvokeCommand.NewScriptBlock($command)
$commandOutput = & $script
} catch {
$commandOutput = $_ | ConvertTo-HashTable
$statusCode = 500
}
}
$commandOutput = switch ($Format) {
TEXT { $commandOutput | Out-String ; break }
JSON { $commandOutput | ConvertTo-JSON; break }
XML { $commandOutput | ConvertTo-XML -As String; break }
CLIXML { [System.Management.Automation.PSSerializer]::Serialize($commandOutput) ; break }
default { "Invalid output format selected, valid choices are TEXT, JSON, XML, and CLIXML"; $statusCode = 501; break }
}
# }
# }
Write-Verbose "Response:"
if (!$commandOutput) {
$commandOutput = [string]::Empty
}
Write-Verbose $commandOutput
$response = $context.Response
$response.StatusCode = $statusCode
$buffer = [System.Text.Encoding]::UTF8.GetBytes($commandOutput)
$response.ContentLength64 = $buffer.Length
$output = $response.OutputStream
$output.Write($buffer,0,$buffer.Length)
$output.Close()
}
} finally {
$listener.Stop()
}
}
}