I am trying to create a timeout for a socket in powershell and I have encountered a problem that I do not know how to solve even after many hours of Google searches.
$tcp = New-Object System.Net.Sockets.TcpClient
$tcp.Connect('127.0.0.0',"port1")
$tcp.Connect('127.0.0.0',"port2")
$tcp.Connect('127.0.0.0',"port3")
When the server side listens to the first port in the series, the client side usually receives the request after one or two seconds. But if the server side listens to the third port in the series - it will take the client 20 seconds of attempt to connect the first port, then another 20 seconds of attempt to connect to the second port, then when it tries to connect to the third port the server listens to - it connects directly.
As I noted at the beginning of the post, I searched for a long time for an answer to this on Google, I tried many attempts myself as well. I would greatly appreciate your help.
How can I change the wait time for each TCP connection attempt?
Here is an example of how you can implement the .ConnectAsync(..)
Method from the TcpClient
Class, which would allow to test async multiple TCP ports and with a specific TimeOut defined as argument.
Note, this function requires .NET Framework 4.5+ if running Windows PowerShell / .NET Core 1.0+ if running PowerShell Core.
using namespace System.Diagnostics
using namespace System.Collections.Generic
using namespace System.Net.Sockets
using namespace System.Threading.Tasks
function Test-TCPConnectionAsync {
[cmdletbinding()]
param(
[parameter(Mandatory, Valuefrompipeline, ValueFromPipelineByPropertyName)]
[alias('ComputerName', 'HostName', 'Host')]
[string[]] $Target,
[parameter(Mandatory, ValueFromPipelineByPropertyName)]
[ValidateRange(1, 65535)]
[int[]] $Port,
[parameter()]
[ValidateRange(5, [int]::MaxValue)]
[int] $TimeOut = 5, # In seconds!
[parameter()]
[switch] $IPv6
)
begin {
$timer = [Stopwatch]::StartNew()
$queue = [List[hashtable]]::new()
$TimeOut = [timespan]::FromSeconds($TimeOut).TotalMilliseconds
if($IPv6.IsPresent) {
$newTcp = { [TCPClient]::new([AddressFamily]::InterNetworkV6) }
return
}
$newTcp = { [TCPClient]::new() }
}
process {
foreach($item in $Target) {
foreach($i in $Port) {
$tcp = & $newTcp
$queue.Add(@{
Instance = $tcp
Task = $tcp.ConnectAsync($item, $i)
Output = [ordered]@{
Source = $env:COMPUTERNAME
Destination = $item
Port = $i
}
})
}
}
}
end {
while($queue -and $timer.ElapsedMilliseconds -le $timeout) {
try {
$id = [Task]::WaitAny($queue.Task, 200)
if($id -eq -1) {
continue
}
$instance, $task, $output = $queue[$id]['Instance', 'Task', 'Output']
if($instance) {
$instance.Dispose()
}
$output['Success'] = $task.Status -eq [TaskStatus]::RanToCompletion
$queue.RemoveAt($id)
[pscustomobject] $output
}
catch {
$PSCmdlet.WriteError($_)
}
}
foreach($item in $queue) {
try {
$instance, $task, $output = $item['Instance', 'Task', 'Output']
$output['Success'] = $task.Status -eq [TaskStatus]::RanToCompletion
if($instance) {
$instance.Dispose()
}
[pscustomobject] $output
}
catch {
$PSCmdlet.WriteError($_)
}
}
}
}
If you want to implement a parallel scan for multiple hosts, you can find an example using Runspaces on my GitHub.
'google.com', 'stackoverflow.com' | Test-TCPConnectionAsync 80, 443, 8080, 389, 636
@'
Target,Port
google.com,80
google.com,443
google.com,8080
google.com,389
google.com,636
cisco.com,80
cisco.com,443
cisco.com,8080
cisco.com,389
cisco.com,636
amazon.com,80
amazon.com,443
amazon.com,8080
amazon.com,389
amazon.com,636
'@ | ConvertFrom-Csv | Test-TCPConnectionAsync