-
Notifications
You must be signed in to change notification settings - Fork 3
/
Get-PortState.psm1
executable file
·363 lines (258 loc) · 14.6 KB
/
Get-PortState.psm1
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
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
<#
.SYNOPSIS
This checks remote computers or devices for open TCP ports by attempting a TCP socket
connection to the specified port or ports using the .NET Net.Sockets.TcpClient class.
.DESCRIPTION
Use the parameter -ComputerName to specify the target computer(s), and the parameter
-Port to specify port(s) to check.
Examples:
Get-PortState -Comp server01 -Port 3389, 445, 80
Get-PortState -Comp (gc hosts.txt) -Port 22,80 -NoPing -AsJob
Copyright (c) 2012, Svendsen Tech.
All rights reserved.
Author: Joakim Svendsen
More extensive documenatation online:
http://www.powershelladmin.com
http://www.powershelladmin.com/wiki/Check_for_open_TCP_ports_using_PowerShell
.PARAMETER ComputerName
Target computer name(s) or IP address(es).
.PARAMETER Port
TCP port(s) to check whether are open.
.PARAMETER ExportToCsv
Create a CSV report and save it to the specified file name, using UTF-8 encoding.
The file will be overwritten without you being prompted, if it exists.
.PARAMETER Timeout
Timeout in millliseconds before the script considers a port closed. Default 3000 ms
(3 seconds). For speeding things up. Only in effect when used with the -AsJob parameter.
.PARAMETER AsJob
Use one job for each port connection. This allows you to override the possibly lengthy
timeout from the connecting socket, and a port is considered closed if we haven't been
able to connect within the allowed time. Default 3000 ms. See the -Timeout parameter.
This may be quite resource-consuming! Ports that are determined to be closed via timeout
will be tagged with a "(t)" for timeout.
.PARAMETER Dns
Try to determine IP if given a host name or host name if given an IP. Multiple values
are joined with semicolons: ";".
.PARAMETER NoPing
Do not try to ping the target computer if this is specified. By default, the script
skips the port checks on targets that do not respond to ICMP ping and populate the
fields with hyphens for these hosts. Be aware that computers that do not resolve via
DNS/WINS/NetBIOS will also be reported as having failed the ping check.
.PARAMETER ContinueOnPingFail
Try to check the target computer for open ports even if it does not respond to ping.
Be aware that computers that do not resolve via DNS/WINS/NetBIOS will also be processed like
this (and it should report them as "closed").
.PARAMETER Quiet
Do not display results with Write-Host directly as the script progresses.
.PARAMETER NoSummary
Do not display a summary at the end. The summary includes start and end time of the script,
and the output file name if you specified -ExportToCsv.
#>
function Get-PortState {
param([Parameter(Mandatory=$true)][string[]] $ComputerName,
[Parameter(Mandatory=$true)][int[]] $Port,
[string] $ExportToCsv = '', # initialize to a false value
[int] $Timeout = 3000, # initialize to three seconds
[switch] $AsJob,
[switch] $Dns,
[switch] $NoPing,
[switch] $ContinueOnPingFail,
[switch] $NoSummary,
[switch] $Quiet = $false
)
$StartTime = Get-Date
if ($Timeout -ne 3000 -and -not $AsJob) { Write-Host -Fore Red "Warning. Timeout not in effect without -AsJob" }
if ($ContinueOnPingFail -and $NoPing) { Write-Host -Fore Red "Warning. -ContinueOnPingFail not in effect with -NoPing" }
# Main data hash. Assumes computer names are unique. If the same computer is
# specified multiple times, it will be processed multiple times, and the data
# from the last time it was processed will overwrite the older data.
$script:Data = @{}
# Process each computer specified.
foreach ($Computer in $ComputerName) {
# Initialize a new custom PowerShell object to hold the data.
$script:Data.$Computer = New-Object PSObject
Add-Member -Name 'ComputerName' -Value $Computer -MemberType NoteProperty -InputObject $script:Data.$Computer
# Try to ping if -Ping was specified.
if (-not $NoPing) {
if (Test-Connection -Count 1 -ErrorAction SilentlyContinue $Computer) {
# Produce output to the host and add data to the object.
Write-UnlessQuiet -Quiet: $Quiet "${Computer}: Responded to ICMP ping."
Add-Member -Name 'Ping' -Value 'Yes' -MemberType NoteProperty -InputObject $script:Data.$Computer
}
else {
# Produce output to the host and add data to the object.
Write-UnlessQuiet -Quiet: $Quiet "${Computer}: Did not respond to ICMP ping."
Add-Member -Name 'Ping' -Value 'No' -MemberType NoteProperty -InputObject $script:Data.$Computer
# If -ContinueOnPingFail was not specified, do this:
if (-not $ContinueOnPingFail) {
# Set all port states to "-" (not checked).
foreach ($SinglePort in $Port) {
Add-Member -Name $SinglePort -Value '-' -MemberType NoteProperty -InputObject $script:Data.$Computer
}
# Continue to the next iteration/computer in the loop.
continue
}
}
}
if ($Dns) {
$ErrorActionPreference = 'SilentlyContinue'
$HostEntry = [System.Net.Dns]::GetHostEntry($Computer)
$Result = $?
$ErrorActionPreference = $MyEAP
# It looks like it's "successful" even when it isn't, for any practical purposes (pass in IP, get IP as .HostName)...
if ($Result) {
## This is a best-effort attempt at handling things flexibly.
##
# I think this should mostly work... If I pass in an IPv4 address that doesn't
# resolve to a host name, the same IP seems to be used to populate the HostName property.
# So this means that you'll get the IP address twice for IPs that don't resolve, but
# it will still say it resolved. For IPs that do resolve to a host name, you will
# correctly get the host name in the IP/DNS column. For host names or IPs that resolve to
# one or more IP addresses, you will get the IPs joined together with semicolons.
# Both IPv6 and IPv4 may be reported depending on your environment.
if ( ($HostEntry.HostName -split '\.')[0] -ieq ($Computer -split '\.')[0] ) {
$IPDns = ($HostEntry | Select -Expand AddressList | Select -Expand IPAddressToString) -join ';'
}
else {
$IPDns = $HostEntry.HostName
}
# Produce output to the host and add data to the object.
Write-UnlessQuiet -Quiet: $Quiet "${Computer}: Resolved to: $IPDns"
Add-Member -Name 'IP/DNS' -Value $IPDns -MemberType NoteProperty -InputObject $script:Data.$Computer
}
# This seems useless in my test environment, but who knows.
else {
Write-UnlessQuiet -Quiet: $Quiet "${Computer}: Did not resolve."
Add-Member -Name 'IP/DNS' -Value '-' -MemberType NoteProperty -InputObject $script:Data.$Computer
}
} # end of if $Dns
# Here we check if the ports are open and store data in the object.
foreach ($SinglePort in $Port) {
if ($AsJob) {
# This implementation using jobs is to support a custom timeout before proceeding.
# Default: 3000 milliseconds (3 seconds).
$Job = Start-Job -ArgumentList $Computer, $SinglePort -ScriptBlock {
param([string] $Computer, [int] $SinglePort)
# Create a new Net.Sockets.TcpClient object to use for testing open TCP ports.
# It needs to be created inside the job's script block.
$Socket = New-Object Net.Sockets.TcpClient
# Suppress error messages
$ErrorActionPreference = 'SilentlyContinue'
# Try to connect
$Socket.Connect($Computer, $SinglePort)
# Make error messages visible again
$ErrorActionPreference = 'Continue'
if ($Socket.Connected) {
# Close the socket.
$Socket.Close()
# Return success string
'connected'
}
else {
'not connected'
}
} # end of script block
# If we check the state of the job without a little nap, we'll probably have to
# sleep longer because it hasn't finished yet. About 250 ms seems to work in my environment.
# Adding a little... Maybe I should make this a parameter to the script as well.
Start-Sleep -Milliseconds 400
if ($Job.State -ne 'Completed') {
#Write-UnlessQuiet -Quiet: $Quiet 'Sleeping...'
Start-Sleep -Milliseconds $Timeout
}
if ($Job.State -eq 'Completed') {
# Get the results (either 'connected' or 'not connected')
$JobResult = Receive-Job $Job
if ($JobResult -ieq 'connected') {
# Produce output to the host and add data to the object.
Write-UnlessQuiet -Quiet: $Quiet "${Computer}: Port $SinglePort is open"
Add-Member -Name $SinglePort -Value 'Open' -MemberType NoteProperty -InputObject $script:Data.$Computer
}
else {
# Produce output to the host and add data to the object.
Write-UnlessQuiet -Quiet: $Quiet "${Computer}: Port $SinglePort is closed or filtered"
Add-Member -Name $SinglePort -Value 'Closed' -MemberType NoteProperty -InputObject $script:Data.$Computer
}
}
# Assume we couldn't connect within the timeout period.
else {
# Produce output to the host and add data to the object.
Write-UnlessQuiet -Quiet: $Quiet "${Computer}: Port $SinglePort is closed or filtered (timeout: $Timeout ms)"
Add-Member -Name $SinglePort -Value 'Closed (t)' -MemberType NoteProperty -InputObject $script:Data.$Computer
}
# Stopping and removing the job causes it to wait beyond the timeout... Let's accumulate crap.
#Stop-Job -Job $Job
#Remove-Job -Force -Job $Job
$Job = $null
} # end of if ($AsJob)
# Do it the usual way without jobs. No custom timeout support.
else {
# Create a new Net.Sockets.TcpClient object to use for testing open TCP ports.
$Socket = New-Object Net.Sockets.TcpClient
# Suppress error messages
$ErrorActionPreference = 'SilentlyContinue'
# Try to connect
$Socket.Connect($Computer, $SinglePort)
# Make error messages visible again
$ErrorActionPreference = $MyEAP
if ($Socket.Connected) {
# Produce output to the host and add data to the object.
Write-UnlessQuiet -Quiet: $Quiet "${Computer}: Port $SinglePort is open"
Add-Member -Name $SinglePort -Value 'Open' -MemberType NoteProperty -InputObject $script:Data.$Computer
# Close the socket.
$Socket.Close()
}
else {
# Produce output to the host and add data to the object.
Write-UnlessQuiet -Quiet: $Quiet "${Computer}: Port $SinglePort is closed or filtered"
Add-Member -Name $SinglePort -Value 'Closed' -MemberType NoteProperty -InputObject $script:Data.$Computer
}
# Reset the variable. Apparently necessary.
$Socket = $null
}
} # end of foreach port
} # end of foreach computer
# Create a properties hash to use with Select-Object later.
$script:Properties = ,@{n='ComputerName'; e={$_.Name}}
# Add the ping and DNS headers if necessary.
if (-not $NoPing) { $script:Properties += @{n='Ping'; e={$_.Value.Ping}} }
if ($Dns) { $script:Properties += @{n='IP/DNS'; e={$_.Value.'IP/DNS'}} }
# Create the dynamic properties with this hack.
$Port | Sort-Object | ForEach-Object {
$script:Properties += @{n="Port $_"; e=[ScriptBlock]::Create("`$_.Value.[string]$_")}
}
$ErrorActionPreference = 'Continue'
# If they want CSV, (try to) create the file they specified
if ($ExportToCsv) {
$script:Data.GetEnumerator() | Sort Name | Select-Object $script:Properties |
ConvertTo-Csv -NoTypeInformation | Set-Content -Encoding utf8 $ExportToCsv
}
$ErrorActionPreference = $MyEAP
# Display summary results.
if (-not $NoSummary) {
Write-Host -ForegroundColor Green @"
Start time: $StartTime
End time: $(Get-Date)
$(if ($ExportToCsv) { "Output file: $ExportToCsv" })
"@
}
# Finally, emit formatted objects to the pipeline.
$script:Data.GetEnumerator() | Sort Name | Select-Object $script:Properties #| Format-Table -AutoSize
} # end of Get-PortState function
function Get-PortStateLast {
# Just return the data from the last run with the last dynamic properties,
# or nothing if Get-PortState hasn't been run prior to this.
if ($script:Data.Count) {
$script:Data.GetEnumerator() | Sort Name | Select-Object $script:Properties #| Format-Table -AutoSize
}
else {
throw 'No data has been collected yet, or the module was reloaded.'
}
}
####### END OF FUNCTIONS #######
Set-StrictMode -Version Latest
$script:MyEAP = 'Stop'
$ErrorActionPreference = $MyEAP
$script:Data = @{}
$script:Properties = @{}
#Export-ModuleMember -Function Get-PortState, Get-PortStateLast