Last active
May 8, 2023 18:37
-
-
Save PanosGreg/1dc55b5bd87a640ac78512db0952ca47 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function Test-TcpConnection { | |
<# | |
.SYNOPSIS | |
Check connectivity on a specific TCP port | |
.DESCRIPTION | |
Check connectivity on a specific TCP port | |
This function does not support Proxy settings. Which means if you are using a proxy | |
then you need to configure it beforehand on either the computer or user level. | |
.EXAMPLE | |
Test-TcpConnection 10.1.1.0 22 -Verbose | |
# check the SSH connectivity of a private IP and also show some verbose info | |
.EXAMPLE | |
'google.com','bing.com' | Test-TcpConnection -Port 80 -PassThru | |
# pass in a number of URLs through the pipeline for checking | |
.EXAMPLE | |
$time = 1..10 | foreach {Test-TcpConnection bing.com -PassThru;Start-Sleep -Mil 500} | |
$time.RoundTrip.TotalSeconds | Measure-Object -Average | fl Count,Average | |
# check a website a number of times and get the average response roundtrip | |
.EXAMPLE | |
$time = 'bing.com' | Test-TcpConnection -PassThru | |
$time.RoundTrip = [timespan]::new(0,0,1,2,0) | |
$time.Duration() | |
$time.Duration('WithColon') | |
$time.Duration('WithSpace') | |
# try out the different options of the .Duration() method | |
.EXAMPLE | |
Test-TcpConnection www.bing.com -PassThru | select * | |
Test-TcpConnection www.bing.com -PassThru -AsUTC | select * | |
# show the full object with either UTC or local time | |
.NOTES | |
- The added .Duration() method can optionally take a single string parameter | |
with value of either 'WithLetter', 'WithColon' or 'WithSpace' | |
- The UseColor switch, turns the default output from Bool into String | |
If used with PassThru, then it adds color to the CanConnect property | |
#> | |
[CmdletBinding()] | |
[OutputType([bool])] # <-- default output | |
[OutputType([object])] # <-- output when using PassThru | |
[OutputType([string])] # <-- default output with UseColor | |
param ( | |
[Parameter(Mandatory,ValueFromPipeline)] | |
[string[]]$Address, # <-- IP Address or URI | |
[uint16]$Port = 443, # <-- from 0 to 65535 | |
[ValidateRange(1,60)] # <-- from 1sec to 1min | |
[int]$TimeoutSec = 3, # <-- 3 seconds timeout by default | |
[switch]$PassThru, # <-- return a [psobject] instead of [bool] | |
[switch]$AsUTC, # <-- show timestamp in UTC instead of local time | |
# only works if used with the PassThru switch | |
[switch]$UseColor # <-- the color applies only to the Connected property | |
) | |
Begin { | |
# set which properties will display by default | |
$Prop = 'Host','Port','Connected','RoundTrip' | |
$PSet = [Management.Automation.PSPropertySet] | |
$DDPS = $PSet::new('DefaultDisplayPropertySet',[string[]]$Prop) | |
$Std = [Management.Automation.PSMemberInfo[]]@($DDPS) | |
# the scriptblock for the custom method .Duration() | |
$Code = { | |
param ( | |
[ValidateSet('WithLetter','WithColon','WithSpace')] | |
[string]$TimeFormat = 'WithLetter' | |
) | |
$Trip = $this.RoundTrip | |
if ($Trip.TotalHours -ge 1) {$fmt1 = 'hh\:mm\:ss' ; $fmt2 = 'h\hm\ms\s' ; $fmt3 = 'h\h\r\ m\m\i\n\ s\s\e\c'} | |
elseif ($Trip.TotalMinutes -ge 1) {$fmt1 = 'mm\:ss' ; $fmt2 = 'm\ms\s' ; $fmt3 = 'm\m\i\n\ s\s\e\c'} | |
elseif ($Trip.TotalSeconds -ge 1) {$fmt1 = 'ss\.fff' ; $fmt2 = 's\sfff\m\s' ; $fmt3 = 's\s\e\c\ fff\m\s'} | |
else {$fmt1 = 'fff' ; $fmt2 = 'fff\m\s' ; $fmt3 = 'fff\m\s'} | |
switch ($TimeFormat) { | |
'WithColon' {$out = $Trip.ToString($fmt1)} # ex. 02:12 | |
'WithLetter' {$out = $Trip.ToString($fmt2).TrimStart('0')} # ex. 2m12s | |
'WithSpace' {$out = $Trip.ToString($fmt3)} # ex. 2min 12sec | |
default {$out = $Trip.ToString()} # ex. 00:02:12.0708419 | |
} | |
Write-Output $out | |
} | |
} #begin block | |
Process { | |
foreach ($ThisAddress in $Address) { | |
Write-Verbose "Check connectivity to $($ThisAddress):$Port" | |
try { | |
$Tcp = [Net.Sockets.TcpClient]::new() | |
$Span = Measure-Command -Expression { | |
$Async = $Tcp.ConnectAsync($ThisAddress,$Port) | |
$CanConnect = $Async.Wait($TimeoutSec*1000) # <-- this blocks | |
} | |
if (-not $CanConnect) { | |
$msg = "Timeout ($TimeoutSec sec) expired" | |
Write-Verbose $msg | |
} | |
} | |
catch { | |
# find the inner-most error message | |
$msg0 = $_.Exception.Message | |
$msg1 = $_.Exception.InnerException.Message | |
$msg2 = $_.Exception.InnerException.InnerException.Message | |
$IsNull1 = [string]::IsNullOrWhiteSpace($msg1) | |
$IsNull2 = [string]::IsNullOrWhiteSpace($msg2) | |
if (-not $IsNull2) {$msg = $msg2} | |
elseif (-not $IsNull1) {$msg = $msg1} | |
else {$msg = $msg0} | |
Write-Verbose $msg | |
$CanConnect = $false | |
} | |
finally { | |
$Date = if ($AsUTC) {[datetime]::UtcNow} else {[datetime]::Now} | |
$Hash = [ordered]@{ | |
PSTypeName = 'TCP.Connection.Result' | |
Date = $Date | |
Host = $ThisAddress | |
Port = $Port | |
Connected = $CanConnect | |
RoundTrip = $Span | |
LocalEndPoint = $Tcp.Client.LocalEndPoint | |
RemoteEndPoint = $Tcp.Client.RemoteEndPoint | |
} | |
if (-not $CanConnect) {$Hash.Error = $msg} # <-- add extra property for Error | |
# add Color to the "Connected" property (if PassThru) or to the Output | |
# (which also means it makes the property into a string instead of Bool) | |
$HasVT100 = $Host.UI.SupportsVirtualTerminal | |
if ($CanConnect) {$Col = '38;2;146;208;80'} # Green | |
else {$Col = '38;2;255;126;0'} # Orange | |
if ($UseColor -and $HasVT100) { | |
$CanConnect = '{0}[{1}m{2}{0}[0m' -f [char]27,$Col,$CanConnect | |
$Hash.Connected = $CanConnect | |
} | |
elseif ($UseColor -and -not $HasVT100) { | |
Write-Warning 'UseColor switch was used but Host does not support VT100' | |
} | |
if (-not $PassThru) {$out = $CanConnect} | |
else { # <-- if PassThru | |
# add view for default properties | |
$params = @{ | |
InputObject = [PSCustomObject]$Hash | |
MemberType = 'MemberSet' | |
Name = 'PSStandardMembers' | |
Value = $Std | |
Force = $true | |
Verbose = $false | |
PassThru = $true | |
} | |
$out = Add-Member @params | |
# add .Duration() method | |
$out | Add-Member -MemberType ScriptMethod -Name Duration -Value $Code | |
} | |
Write-Output $out # <-- either [bool]/[string] or [psobject] | |
# clean-up | |
$Tcp.Close() | |
$Tcp.Dispose() | |
} | |
} #foreach address | |
} #process block | |
End {} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment