Skip to content

Instantly share code, notes, and snippets.

@PanosGreg
Last active May 8, 2023 18:37
Show Gist options
  • Save PanosGreg/1dc55b5bd87a640ac78512db0952ca47 to your computer and use it in GitHub Desktop.
Save PanosGreg/1dc55b5bd87a640ac78512db0952ca47 to your computer and use it in GitHub Desktop.
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