FastPing.psm1
class FastPingResponse { [String] $HostName [Nullable[Double]] $RoundtripAverage [Boolean] $Online [System.Net.NetworkInformation.IPStatus] $Status FastPingResponse( [String] $HostName, [Nullable[Double]] $RoundtripAverage, [Boolean] $Online, [System.Net.NetworkInformation.IPStatus] $Status ) { $this.HostName = $HostName if ($null -ne $RoundtripAverage) { $this.RoundtripAverage = $RoundtripAverage } $this.Online = $Online $this.Status = $Status } } <# .SYNOPSIS Converts a Decimal IP address into a 32-bit unsigned integer. .DESCRIPTION ConvertToDecimalIP takes a decimal IP, uses a shift operation on each octet and returns a single UInt32 value. .INPUTS System.Net.IPAddress .EXAMPLE ConvertToDecimalIP 1.2.3.4 Converts an IP address to an unsigned 32-bit integer value. .NOTES This code is copied from the Indented.Net.IP module (https://github.com/indented-automation/Indented.Net.IP). The copy is due to not wanting to take a dependency, and that module licensed with a permissive license. Thanks Chris Dent! #> function ConvertToDecimalIP { [CmdletBinding()] [OutputType([UInt32])] param ( # An IP Address to convert. [Parameter(Mandatory, Position = 1, ValueFromPipeline )] [IPAddress] $IPAddress ) process { [UInt32]([IPAddress]::HostToNetworkOrder($IPAddress.Address) -shr 32 -band [UInt32]::MaxValue) } } <# .SYNOPSIS Converts IP address formats to a set a known styles. .DESCRIPTION ConvertToNetwork ensures consistent values are recorded from parameters which must handle differing addressing formats. This Cmdlet allows all other the other functions in this module to offload parameter handling. .NOTES Change log: 05/03/2016 - Chris Dent - Refactored and simplified. 14/01/2014 - Chris Dent - Created. This code is copied from the Indented.Net.IP module (https://github.com/indented-automation/Indented.Net.IP). The copy is due to not wanting to take a dependency, and that module licensed with a permissive license. Thanks Chris Dent! #> function ConvertToNetwork { [CmdletBinding()] [OutputType('Indented.Net.IP.Network')] param ( # Either a literal IP address, a network range expressed as CIDR notation, or an IP address and subnet mask in a string. [Parameter(Mandatory = $true, Position = 1)] [String] $IPAddress, # A subnet mask as an IP address. [Parameter(Position = 2)] [AllowNull()] [String] $SubnetMask ) $validSubnetMaskValues = @( '0.0.0.0', '128.0.0.0', '192.0.0.0', '224.0.0.0', '240.0.0.0', '248.0.0.0', '252.0.0.0', '254.0.0.0', '255.0.0.0', '255.128.0.0', '255.192.0.0', '255.224.0.0', '255.240.0.0', '255.248.0.0', '255.252.0.0', '255.254.0.0', '255.255.0.0', '255.255.128.0', '255.255.192.0', '255.255.224.0', '255.255.240.0', '255.255.248.0', '255.255.252.0', '255.255.254.0', '255.255.255.0', '255.255.255.128', '255.255.255.192', '255.255.255.224', '255.255.255.240', '255.255.255.248', '255.255.255.252', '255.255.255.254', '255.255.255.255' ) $network = [PSCustomObject]@{ IPAddress = $null SubnetMask = $null MaskLength = 0 PSTypeName = 'Indented.Net.IP.Network' } # Override ToString $network | Add-Member ToString -MemberType ScriptMethod -Force -Value { '{0}/{1}' -f $this.IPAddress, $this.MaskLength } if (-not $psboundparameters.ContainsKey('SubnetMask') -or $SubnetMask -eq '') { $IPAddress, $SubnetMask = $IPAddress.Split([Char[]]'\/ ', [StringSplitOptions]::RemoveEmptyEntries) } # IPAddress while ($IPAddress.Split('.').Count -lt 4) { $IPAddress += '.0' } if ([IPAddress]::TryParse($IPAddress, [Ref]$null)) { $network.IPAddress = [IPAddress]$IPAddress } else { $errorRecord = [System.Management.Automation.ErrorRecord]::new( [ArgumentException]'Invalid IP address.', 'InvalidIPAddress', 'InvalidArgument', $IPAddress ) $pscmdlet.ThrowTerminatingError($errorRecord) } # SubnetMask if ($null -eq $SubnetMask -or $SubnetMask -eq '') { $network.SubnetMask = [IPAddress]$validSubnetMaskValues[32] $network.MaskLength = 32 } else { $maskLength = 0 if ([Int32]::TryParse($SubnetMask, [Ref]$maskLength)) { if ($MaskLength -ge 0 -and $maskLength -le 32) { $network.SubnetMask = [IPAddress]$validSubnetMaskValues[$maskLength] $network.MaskLength = $maskLength } else { $errorRecord = [System.Management.Automation.ErrorRecord]::new( [ArgumentException]'Mask length out of range (expecting 0 to 32).', 'InvalidMaskLength', 'InvalidArgument', $SubnetMask ) $pscmdlet.ThrowTerminatingError($errorRecord) } } else { while ($SubnetMask.Split('.').Count -lt 4) { $SubnetMask += '.0' } $maskLength = $validSubnetMaskValues.IndexOf($SubnetMask) if ($maskLength -ge 0) { $Network.SubnetMask = [IPAddress]$SubnetMask $Network.MaskLength = $maskLength } else { $errorRecord = [System.Management.Automation.ErrorRecord]::new( [ArgumentException]'Invalid subnet mask.', 'InvalidSubnetMask', 'InvalidArgument', $SubnetMask ) $pscmdlet.ThrowTerminatingError($errorRecord) } } } $network } <# .SYNOPSIS Get a list of IP addresses within the specified network. .DESCRIPTION GetNetworkRange finds the network and broadcast address as decimal values then starts a counter between the two, returning IPAddress for each. .INPUTS System.String .EXAMPLE GetNetworkRange 192.168.0.0 255.255.255.0 Returns all IP addresses in the range 192.168.0.0/24. .EXAMPLE GetNetworkRange 10.0.8.0/22 Returns all IP addresses in the range 192.168.0.0 255.255.252.0. .NOTES This code is copied from the Indented.Net.IP module (https://github.com/indented-automation/Indented.Net.IP). The copy is due to not wanting to take a dependency, and that module licensed with a permissive license. Thanks Chris Dent! #> function GetNetworkRange { [CmdletBinding(DefaultParameterSetName = 'FromIPAndMask')] [OutputType([IPAddress])] param ( # Either a literal IP address, a network range expressed as CIDR notation, or an IP address and subnet mask in a string. [Parameter(Mandatory, Position = 1, ValueFromPipeline, ParameterSetName = 'FromIPAndMask')] [String] $IPAddress, # A subnet mask as an IP address. [Parameter(Position = 2, ParameterSetName = 'FromIPAndMask')] [String] $SubnetMask, # Include the network and broadcast addresses when generating a network address range. [Parameter(ParameterSetName = 'FromIPAndMask')] [Switch] $IncludeNetworkAndBroadcast, # The start address of a range. [Parameter(Mandatory, ParameterSetName = 'FromStartAndEnd')] [IPAddress] $StartIPAddress, # The end address of a range. [Parameter(Mandatory, ParameterSetName = 'FromStartAndEnd')] [IPAddress] $EndIPAddress ) process { if ($pscmdlet.ParameterSetName -eq 'FromIPAndMask') { try { $null = $psboundparameters.Remove('IncludeNetworkAndBroadcast') $network = ConvertToNetwork @psboundparameters } catch { $pscmdlet.ThrowTerminatingError($_) } $decimalIP = ConvertToDecimalIP -IPAddress $network.IPAddress $decimalMask = ConvertToDecimalIP -IPAddress $network.SubnetMask $startDecimal = $decimalIP -band $decimalMask $endDecimal = $decimalIP -bor (-bnot $decimalMask -band [UInt32]::MaxValue) if (-not $IncludeNetworkAndBroadcast) { $startDecimal++ $endDecimal-- } } else { $startDecimal = ConvertToDecimalIP -IPAddress $StartIPAddress $endDecimal = ConvertToDecimalIP -IPAddress $EndIPAddress } for ($i = $startDecimal; $i -le $endDecimal; $i++) { [IPAddress]([IPAddress]::NetworkToHostOrder([Int64]$i) -shr 32 -band [UInt32]::MaxValue) } } } <# .SYNOPSIS Performs a series of asynchronous pings against a set of target hosts. .DESCRIPTION This function uses System.Net.Networkinformation.Ping object to perform a series of asynchronous pings against a set of target hosts. .PARAMETER HostName String array of target hosts. .PARAMETER Count Number of echo requests to send. Aliased with 'n', like ping.exe. .PARAMETER Continuous Enables continuous pings against the target hosts. Stop with CTRL+C. Aliases with 't', like ping.exe. .PARAMETER Timeout Timeout in milliseconds to wait for each reply. Defaults to 2 seconds (5000). Aliased with 'w', like ping.exe. Per MSDN Documentation, "When specifying very small numbers for timeout, the Ping reply can be received even if timeout milliseconds have elapsed." (https://msdn.microsoft.com/en-us/library/ms144955.aspx). .PARAMETER Interval Number of milliseconds between echo requests. .PARAMETER RoundtripAveragePingCount Number of echo requests to send for calculating the Roundtrip average. Defaults to 4. .EXAMPLE Invoke-FastPing -HostName 'andrewpearce.io' HostName RoundtripAverage Online -------- ---------------- ------ andrewpearce.io 22 True .EXAMPLE Invoke-FastPing -HostName 'andrewpearce.io','doesnotexist.andrewpearce.io' HostName RoundtripAverage Online -------- ---------------- ------ doesnotexist.andrewpearce.io False andrewpearce.io 22 True .EXAMPLE Invoke-FastPing -HostName 'andrewpearce.io' -Count 5 This example pings the host 'andrewpearce.io' five times. .EXAMPLE fp andrewpearce.io -n 5 This example pings the host 'andrewpearce.io' five times using syntax similar to ping.exe. .EXAMPLE Invoke-FastPing -HostName 'microsoft.com' -Timeout 500 This example pings the host 'microsoft.com' with a 500 millisecond timeout. .EXAMPLE fp microsoft.com -w 500 This example pings the host 'microsoft.com' with a 500 millisecond timeout using syntax similar to ping.exe. .EXAMPLE fp andrewpearce.io -Continuous This example pings the host 'andrewpearce.io' continuously until CTRL+C is used. .EXAMPLE fp andrewpearce.io -t This example pings the host 'andrewpearce.io' continuously until CTRL+C is used. #> function Invoke-FastPing { [CmdletBinding(DefaultParameterSetName='Count')] [Alias('FastPing', 'fping', 'fp')] param ( [Parameter(Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] [ValidateNotNullOrEmpty()] [Alias('Computer', 'ComputerName', 'Host')] [String[]] $HostName, [Parameter(ParameterSetName='Count')] [ValidateRange(1, [Int]::MaxValue)] [Alias('N')] [Int] $Count = 1, [Parameter(ParameterSetName='Continuous')] [Alias('T')] [Switch] $Continuous, [ValidateRange(1, [Int]::MaxValue)] [Alias('W')] [Int] $Timeout = 5000, [ValidateRange(1, [Int]::MaxValue)] [Int] $Interval = 1000, [ValidateRange(1, [Int]::MaxValue)] [Int] $RoundtripAveragePingCount = 4 ) begin { # The time used for the ping async wait() method $asyncWaitMilliseconds = 500 # Used to control the Count of echo requests $loopCounter = 0 # Used to control the Interval between echo requests $loopTimer = [System.Diagnostics.Stopwatch]::new() } process { try { while ($true) { $loopTimer.Restart() # Objects to hold items as we process pings $queue = [System.Collections.Queue]::new() $pingHash = @{} # Start an asynchronous ping against each computer foreach ($hn in $HostName) { if ($pingHash.Keys -notcontains $hn) { $pingHash.Add($hn, [System.Collections.ArrayList]::new()) } for ($i = 0; $i -lt $RoundtripAveragePingCount; $i++) { $ping = [System.Net.Networkinformation.Ping]::new() $object = @{ Host = $hn Ping = $ping Async = $ping.SendPingAsync($hn, $Timeout) } $queue.Enqueue($object) } } # Process the asynchronous pings while ($queue.Count -gt 0) { $object = $queue.Dequeue() try { # Wait for completion if ($object.Async.Wait($asyncWaitMilliseconds) -eq $true) { [Void]$pingHash[$object.Host].Add(@{ Host = $object.Host RoundtripTime = $object.Async.Result.RoundtripTime Status = $object.Async.Result.Status }) continue } } catch { # The Wait() method can throw an exception if the host does not exist. if ($object.Async.IsCompleted -eq $true) { [Void]$pingHash[$object.Host].Add(@{ Host = $object.Host RoundtripTime = $object.Async.Result.RoundtripTime Status = $object.Async.Result.Status }) continue } else { Write-Warning -Message ('Unhandled exception: {0}' -f $_.Exception.Message) } } $queue.Enqueue($object) } # Using the ping results in pingHash, calculate the average RoundtripTime foreach ($key in $pingHash.Keys) { $pingStatus = $pingHash.$key.Status | Select-Object -Unique if ($pingStatus -eq [System.Net.NetworkInformation.IPStatus]::Success) { $online = $true $status = [System.Net.NetworkInformation.IPStatus]::Success } elseif ($pingStatus.Count -eq 1) { $online = $false $status = $pingStatus } else { $online = $false $status = [System.Net.NetworkInformation.IPStatus]::Unknown } if ($online -eq $true) { $latency = [System.Collections.ArrayList]::new() foreach ($value in $pingHash.$key) { if (-not([String]::IsNullOrWhiteSpace($value.RoundtripTime))) { [Void]$latency.Add($value.RoundtripTime) } } $measuredLatency = $latency | Measure-Object -Average -Sum if ($measuredLatency.Average) { $roundtripAverage = [Math]::Round($measuredLatency.Average, 0) } elseif ($measuredLatency.Sum -eq 0) { $roundtripAverage = 0 } else { $roundtripAverage = $null } } else { $roundtripAverage = $null } [FastPingResponse]::new( $key, $roundtripAverage, $online, $status ) } # End result processing # Increment the loop counter $loopCounter++ if ($loopCounter -lt $Count -or $Continuous -eq $true) { $timeToSleep = $Interval - $loopTimer.Elapsed.TotalMilliseconds if ($timeToSleep -gt 0) { Start-Sleep -Milliseconds $timeToSleep } } else { break } } } catch { throw } finally { $loopTimer.Stop() } } # End Process } <# .SYNOPSIS Performs a ping sweep against a series of target IP Addresses. .DESCRIPTION This function calculates the list of IP Addresses to target, and wraps a call to Invoke-FastPingto perform the ping sweep. .PARAMETER StartIP The IP Address to start from. .PARAMETER EndIp The IP Address to finish with. .PARAMETER IPAddress An IP Address, to be matched with an appropriate Subnet Mask. .PARAMETER SubnetMask A Subnet Mask for network range calculations. .EXAMPLE Invoke-PingSweep -StartIP '1.1.1.1' -EndIP '1.1.1.5' HostName RoundtripAverage Online Status -------- ---------------- ------ ------ 1.1.1.3 19 True Success 1.1.1.4 22 True Success 1.1.1.1 21 True Success 1.1.1.2 19 True Success 1.1.1.5 24 True Success .EXAMPLE Invoke-PingSweep -IPAddress '1.1.1.1' -SubnetMask '255.255.255.252' HostName RoundtripAverage Online Status -------- ---------------- ------ ------ 1.1.1.2 21 True Success 1.1.1.1 16 True Success #> function Invoke-PingSweep { [CmdletBinding(DefaultParameterSetName = 'FromStartAndEnd')] [Alias('PingSweep', 'psweep')] param ( [Parameter( Mandatory = $true, Position = 0, ParameterSetName = 'FromStartAndEnd')] [ValidateScript( {[System.Net.IPAddress]$_} )] [String] $StartIP, [Parameter( Mandatory = $true, Position = 1, ParameterSetName = 'FromStartAndEnd')] [ValidateScript( {[System.Net.IPAddress]$_} )] [String] $EndIP, [Parameter( Mandatory = $true, Position = 0, ParameterSetName = 'FromIPAndMask')] [ValidateScript( {[System.Net.IPAddress]$_} )] [String] $IPAddress, [Parameter( Mandatory = $true, Position = 1, ParameterSetName = 'FromIPAndMask')] [ValidateScript( {[System.Net.IPAddress]$_} )] [String] $SubnetMask ) switch ($PSCmdlet.ParameterSetName) { 'FromIPAndMask' { $getNetworkRange = @{ IPAddress = $IPAddress SubnetMask = $SubnetMask } } 'FromStartAndEnd' { $getNetworkRange = @{ StartIPAddress = $StartIP EndIPAddress = $EndIP } } } $networkRange = (GetNetworkRange @getNetworkRange).IPAddressToString Invoke-FastPing -HostName $networkRange } |