Test-PingPortThreaded.ps1

<#PSScriptInfo
 
.VERSION 1.0.4
 
.GUID 3121f2fd-ff6f-4ddb-93cc-da97938b71c7
 
.AUTHOR Brian McMahon
 
.COPYRIGHT
(c) Brian McMahon 2023
 
.LICENSEURI
https://github.com/bmcmcm/Test-PingPortThreaded/blob/master/LICENSE.md
 
.PROJECTURI
https://github.com/bmcmcm/Test-PingPortThreaded
 
.EXTERNALMODULEDEPENDENCIES = @("Invoke-Threaded")
 
.RELEASENOTES
Initial Release
 
#>


<#
.SYNOPSIS
Using threading via the "Invoke-Threaded" module, Test-PingPortThreaded pings a group of supplied targets, and port scans targets that are successfully pinged.
 
.DESCRIPTION
Test-PingPortThreaded is a demonstration script for the Invoke-Threaded module. The script will ping IP Addresses or computer hostnames, and scan for open TCP ports. None of the parameters are manditory, but arrays of targets and ports can be supplied instead of using the defaults.
 
.PARAMETER Targets
String[] (defaults to local class C of the localhost's IP Address), IP Addresses or computer hostnames or a combination of each.
 
.PARAMETER PortsToScan
Int[] (defaults to the top well known ports between 1 and 1000), TCP port numbers to scan if a ping is successful
 
.PARAMETER TimeoutInSeconds
Int (default is 2), Number of seconds (not milliseconds) to wait before a ping or TCP port scan times out.
 
.PARAMETER ThreadCount
Int (default is 200), Maximum number of threads that will be started
 
.INPUTS
You cannot pipe objects to Test-PingPortThreaded.ps1.
 
.OUTPUTS
Test-PingPortThreaded.ps1 outputs a PSCustomObject array which contains all successful ping and any successful TCP port scans.
 
.EXAMPLE
The following example uses all defaults and exports the results to Out-GridView
 
PS> .\Test-PingPortThreaded.ps1 | Out-GridView
 
.EXAMPLE
The following example gets targets all of the computer found in the local Active Directory Domain, scans specified ports and exports the results to a CSV file.
 
PS> .\Test-PingPortThreaded.ps1 -Targets (Get-ADComputer -Filter *).Name -PortsToScan @(80,135,137,138,139,443) | Export-CSV -Path C:\Temp\PingPortResults.csv -NoTypeInformation
#>
 

#Requires -Modules invoke-threaded

[CmdletBinding()]
Param(
    [Parameter(Mandatory=$false,Position=0)]
    [string[]]$Targets = (1..254 | ForEach-Object {((Get-NetIPAddress -AddressFamily IPV4).IPAddress.Split('.')[0..2] -join '.') + '.' + $_ }),

    [Parameter(Mandatory=$false,Position=1)]
    [int[]]$PortsToScan = @(7, 19, 20, 21, 22, 23, 25, 42, 43, 49, 53, 67, 68, 69, 70, 79, 80, 88, 102, 110, 113,
        119, 123, 135, 137, 138, 139, 143, 161, 162, 177, 179, 194, 201, 264, 318, 381, 383, 389, 411, 412, 427,
        443, 445, 464, 465, 497, 500, 512, 513, 514, 515, 520, 521, 540, 548, 554, 546, 547, 560, 563, 587, 591,
        593, 596, 631, 636, 639, 646, 691, 860, 873, 902, 989, 990, 993, 995),
    
    [Parameter(Mandatory=$false,Position=2)]
    [int]$TimeoutInSeconds = 2,

    [Parameter(Mandatory=$false,Position=3)]
    [int]$ThreadCount = 200
)

Import-Module Invoke-Threaded
try 
{
    if (!(Get-Command Invoke-Threaded)) 
    {
        Write-Error "Invoke-Threaded command not found, check for missing module or the installation"
        return
    }
}
catch 
{
    Write-Error "Invoke-Threaded command not found, check for missing module or the installation"
}

function Test-ICMP
{
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory=$false,Position=0)]
        [string]$PingTarget,

        [Parameter(Mandatory=$false,Position=1)]
        [int]$TimeoutInSeconds = 2    
    )
    $TargetName = $null
    $regx = '^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$'

    $r = Test-Connection -TargetName $PingTarget -TimeoutSeconds $TimeoutInSeconds -Count 1 -IPv4
    if ($r.Status -eq "Success")
    {
        if ($PingTarget -match $regx)
        {
            $TargetName = [System.Net.DNS]::GetHostEntry($PingTarget).HostName
        } else {
    
        }
        if (!$TargetName) {$TargetName = $PingTarget}
    }
    return [PSCustomObject]@{
        Source = $r.Source
        PingTarget = $r.DisplayAddress
        TargetName = $TargetName
        Latency = $r.Latency
        Status = $r.Status
    }
}

function Test-TCPPort
{
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory=$true,Position=0)]
        [int]$Port,

        [Parameter(Position=1)]
        [string]$Target,
        
        [Parameter(Position=2)]
        [int]$TimeoutInMS = 2000
    )
    $portCheck = (New-Object System.Net.Sockets.TcpClient).ConnectAsync($Target, $Port).Wait($TimeoutInMS)
    if ($portCheck)
    {
        return [PSCustomObject]@{
            Target = $Target
            Port = $Port
        }
    } else { return $null }
}

$param = [System.Collections.Generic.Dictionary[string,object]]::new()
$param.Add("TimeoutInSeconds", $TimeoutInSeconds)
$pings = Invoke-Threaded -ScriptBlock (Get-Command Test-ICMP).ScriptBlock -TargetList $Targets -ParametersToPass $param -MaxThreads $ThreadCount -ThreadWaitSleepTimerMs 100 -MaxThreadWaitTimeSec 30

$tcpChecks = $pings | Where-Object {$_.Status -eq "Success"}
$report = @()
$index = 0
foreach ($target in $tcpChecks)
{
    $report += [PSCustomObject]@{
        Source = $target.Source
        IPAddress = $target.PingTarget
        Hostname = $target.TargetName
        Latency = $target.Latency
        Status = $target.Status
        OpenPorts = ""
        OpenPortArray = @()
    }
    $param = [System.Collections.Generic.Dictionary[string,object]]::new()
    $param.Add("Target", $target.PingTarget)
    $param.Add("TimeoutInMS", $TimeoutInSeconds * 1000)
    $portret = Invoke-Threaded -ScriptBlock (Get-Command Test-TCPPort).ScriptBlock -TargetList $PortsToScan -ParametersToPass $param -MaxThreads $ThreadCount -ThreadWaitSleepTimerMs 100 -MaxThreadWaitTimeSec 30

    foreach ($portresult in $portret)
    {
        $report[$index].OpenPortArray += $portresult.Port
    }
    $report[$index].OpenPortArray = $report[$index].OpenPortArray | Sort-Object
    try {
        $report[$index].OpenPorts = [System.String]::Join(",",$report[$index].OpenPortArray)
    }
    catch {
        #Empty array error, don't care, ha
    }
    $index++
}
$report