Test-PowerPing.psm1

##Comments Note: This is my first attempt at providing comments. I'm not sure how detailed to be, feel free to request elaboration if needed.##
##Netmask table for param validation and CIDR/Netmask functionality.
$script:csnl = @{
    '0' = '0.0.0.0'
    '1' = '128.0.0.0'
    '2' = '192.0.0.0'
    '3' = '224.0.0.0'
    '4' = '240.0.0.0'
    '5' = '248.0.0.0'
    '6' = '252.0.0.0'
    '7' = '254.0.0.0'
    '8' = '255.0.0.0'
    '9' = '255.128.0.0'
    '10' = '255.192.0.0'
    '11' = '255.224.0.0'
    '12' = '255.240.0.0'
    '13' = '255.248.0.0'
    '14' = '255.252.0.0'
    '15' = '255.254.0.0'
    '16' = '255.255.0.0'
    '17' = '255.255.128.0'
    '18' = '255.255.192.0'
    '19' = '255.255.224.0'
    '20' = '255.255.240.0'
    '21' = '255.255.248.0'
    '22' = '255.255.252.0'
    '23' = '255.255.254.0'
    '24' = '255.255.255.0'
    '25' = '255.255.255.128'
    '26' = '255.255.255.192'
    '27' = '255.255.255.224'
    '28' = '255.255.255.240'
    '29' = '255.255.255.248'
    '30' = '255.255.255.252'
    '31' = '255.255.255.254'
    '32' = '255.255.255.255'
    }
<#
.SYNOPSIS
Sends single pings asynchronously to remote computers to determine network accessibility.
Can be used as IP range scanner, ping utility, CIDR calculator, or address list generator.
Returns simple ping results (Status,IPv4Address,DeviceName,Time) with status codes.
.DESCRIPTION
Test-PowerPing pings IPv4 addresses asynchronously within a given IPv4 address range.
Ranges can be provided as: Start/EndIP, CIDR, or StartIP with Netmask.
'AddressOnly' switch can be used to calculate IPv4 addresses within a given start/end range.
'IPFilter' parameter can be used to filter IPv4 address lists as needed.
'Sites' parameter set can be used to save collections of ranges and targets.
'CIDR' parameter set can be used to send pings to given CIDR block,
 provide CIDR block information, or generate address lists for given block(s).
'Mask' parameter set can be used to send pings to given range using Start IP and Netmask,
 provide CIDR block information, or generate an address list for the given range.
'Address' parameter set can be used to generate/ping addresses within a given range.
'Targets' parameter set (Default) can be used to send pings to a list of targets.
 
               ** See Examples for Use / Syntax **
Defaults:
    Output: Status,IPv4Address,DeviceName,Time (Online & Offline)
                  Timeout: 1000 ms
                      TTL: 255
                   Buffer: 32bytes
 
    IPFilter: None
 
DYNAMIC PARAMETERS
-Site <String[]>
Name of saved site list.
Requires valid entry from Site List.
-Each site can contain multiple entry types.
-Use "NewSite" to enable add/remove/import/delete/list functionality
[Command Example: Test-PowerPing -Site NewSite -ImportSite $mylist]
 
Parameter Set(s): Sites
Required? true
Position? named
DefaultValue
Accept pipeline input? true (ByValue)
Accept wildcard characters? false
 
.PARAMETER StartIP
Beginning address in IPv4 address range.
[Command Example: Test-PowerPing -StartIP '10.0.0.0' -Netmask '255.255.255.0']
 
Parameter Set(s): Address,Mask
.PARAMETER EndIP
Ending address in IPv4 address range.
[Command Example: Test-Powerping -StartIP '10.0.0.0' -EndIP '10.0.0.255']
 
Parameter Set(s): Address
.PARAMETER Target
Single device name/IPv4 address, or list of device names/IPv4 addresses to be pinged.
[Command Example: Test-PowerPing -Target 'website.com,www.anothersite.com,10.0.0.223']
 
Parameter Set(s): Targets
.PARAMETER CIDR
Address block entry using CIDR notation
-Allows for targeting filtering when providing multiple entries
--- '\AO' can be provided as filter entry to indicate 'AssignOnly'
-Quotes required when providing filter entry
-Filters must be separated by ';' following '\F:'
-Targeted Filter will override default IPFilter/AssignOnly options
[Command Example: Test-PowerPing -CIDR '10.0.0.18/24']
[Command Example: Test-PowerPing -CIDR '10.0.0.18/24,10.0.1.0/24\F:10.0.1.0;10.0.1.1']
 
Parameter Set(s): CIDR
.PARAMETER Netmask
Netmask for given StartIP
[Command Example: Test-PowerPing -StartIP '10.0.0.18' -Netmask '255.255.255.0']
 
Parameter Set(s): Mask
.PARAMETER AddressOnly
Switch to provide IPv4 address list only for Start/EndIP, CIDR Block, or Start with Netmask.
[Command Example: Test-PowerPing -StartIP '10.0.0.0' -EndIP '10.255.255.255' -AddressOnly]
 
Parameter Set(s): Address,Mask,CIDR,Sites
.PARAMETER CIDcalc
Provides CIDR block information about provided block.
-Available for both CIDR and Netmask entries
[Command Example: Test-PowerPing -CIDR '10.0.0.0/8' -CIDcalc]
 
Parameter Set(s): Mask,CIDR,Sites
.PARAMETER AssignOnly
Only returns/pings assignable addressess for given CIDR or Netmask Range.
[Command Example: Test-PowerPing -StartIP '192.168.20.0' -Netmask '255.255.255.0' -AssignOnly]
 
Parameter Set(s): Mask,CIDR
.PARAMETER DevName
Filters output to entries where DeviceName resolves to the provided input.
-Uses '-match' for pattern recognition. (All devices with name starting with 'Mycomputer' below)
-Enables OnlineOnly parameter when used
[Command Example: Test-PowerPing -Site TheBeach -DevName "Mycomputer*"]
 
Parameter Set(s): Address,Mask,CIDR,Sites
.PARAMETER IPFilter
Filters/removes IPv4 address list entries where addresses match the provided input.
-Entries must match '*.*.*.*' pattern
-Full addresses/wildcards can be provided to filter specific IPs ('192.168.50.1','192.168.51.*')
-Range/CIDR can save filters using '\F:<filter>;<filter>' at the end of entry.
[Command Example: Test-PowerPing -CIDR 10.0.0.0/14 -AddressOnly -iPFilter '*.*.*.0','10.1.1.2']
[Command Example: Test-PowerPing -CIDR '10.0.0.0/14\F:*.*.*.0;10.1.1.2' -AddressOnly]
 
Parameter Set(s): Address,Mask,CIDR,Sites
.PARAMETER TTL
Sets 'TimeToLive' option property for asynchronous pings.
-Maximum Input = '255'
[Command Example: Test-PowerPing -Target mysite.com -TTL 128]
 
Parameter Set(s): Address,Mask,CIDR,Sites,Targets
.PARAMETER Timeout
Sets response time maximum property for asynchronous pings in milliseconds.
-Maximum Input = '60000'
[Command Example: Test-PowerPing -Target mydevice -Timeout 3000]
 
Parameter Set(s): Address,Mask,CIDR,Sites,Targets
.PARAMETER Buffer
Sets buffer data property for asynchronous pings in bytes.
-Maximim Input = '65500'
[Command Example: Test-PowerPing -Target mydevice -Timeout 3000 -Buffer 64]
 
Parameter Set(s): Address,Mask,CIDR,Sites,Targets
.PARAMETER OnlineOnly
Resulting output will only display pings with a successful response.
[Command Example: Test-PowerPing -CIDR 192.168.50.0/24 -OnlineOnly]
 
Parameter Set(s): Address,Mask,CIDR,Sites,Targets
.PARAMETER Output
Reduces available output properties based on selection.
[Command Example: Test-PowerPing -Site TheBeach -Output NoTime/DeviceName]
 
Parameter Set(s): Address,Mask,CIDR,Sites,Targets
.PARAMETER AddSite
Site Name for addition to Site List. May require admin permissions.
-Can only use one method [AddSite,RemoveSite,ImportSite,ListSites,DeleteSites] per call
[Command Example: Test-PowerPing -Site NewSite -AddSite 'Site' -Range '"10.0.0.0","10.0.0.255"']
 
Parameter Set(s): Sites
.PARAMETER RangeList
List of ranges or single range for addition to Site List. Used with 'AddSite' Parameter. May require admin.
-Each site entry can contain multiple entry types. Entries will be created in the order provided.
-Type-Range- Provide Start/EndIP seperated by ';' (filter optional) - Ex: '10.0.0.0;10.2.255.255'
-Type-CIDR- Provide CIDR entry (filter optional) - Ex: '10.0.0.0/16'
-Type-Target- Provide single ipaddress, device name, or site address - Ex: 'google.com'
-Excluded characters: <>^`{|}
[Command Example: Test-PowerPing -Site NewSite -AddSite 'Site' -Rangelist '10.0.0.0/16,10.1.0.0;10.1.255.255']
 
Parameter Set(s): Sites
.PARAMETER RemoveSite
Site Name for removal from Site List. May require admin permissions.
-Can only use one method [AddSite,RemoveSite,ImportSite,ListSites,DeleteSites] per call
[Command Example: Test-PowerPing -Site NewSite -RemoveSite 'TheBeach']
 
Parameter Set(s): Sites
.PARAMETER ListSites
Lists saved Site names and ranges.
-Can only use one method [AddSite,RemoveSite,ImportSite,ListSites,DeleteSites] per call
[Command Example: Test-PowerPing -Site NewSite -ListSites]
 
Parameter Set(s): Sites
.PARAMETER DeleteSites
Deletes Site List CSV and resets Site List. May require admin permissions.
-Can only use one method [AddSite,RemoveSite,ImportSite,ListSites,DeleteSites] per call
[Command Example: Test-PowerPing -Site NewSite -DeleteSites]
 
Parameter Set(s): Sites
.PARAMETER ImportSite
Specifies PSObject or Hashtable to be used to import new entries to Sites list. May require admin.
-Can only use one method [AddSite,RemoveSite,ImportSite,ListSites,DeleteSites] per call
[Command Example: Test-PowerPing -Site NewSite -ImportSite $mysitelist]
 
Parameter Set(s): Sites
.EXAMPLE
Test-PowerPing -StartIP '192.168.20.0' -EndIP '192.168.20.255'
Status IPv4Address DeviceName Time
------ ----------- ---------- ----
TimedOut 192.168.20.0: 11010
Online 192.168.20.1 XT8 1
(...)
____________________________________________________________________
Ping addresses within the range 192.168.20.0 - 192.168.20.255
with 32byte buffer, 1000ms timeout, and 255 ttl.
.EXAMPLE
Test-PowerPing -CIDR 192.168.20.0/23 -Timeout '300' -TTL '128' -Buffer '64' -IPFilter "192.168.20.3"
Status IPv4Address DeviceName Time
------ ----------- ---------- ----
TimedOut 192.168.20.2: 11010
TimedOut 192.168.20.4: 11010
(...)
____________________________________________________________________
Ping addresses within the range 192.168.20.0 - 192.168.21.255 with custom ping options,
and an IPFilter. Sends Pings with the provided values for 'Timeout','TTL', and 'Buffer'
Returns Ping result for successful/unsuccessful addresses that do not equal "192.168.20.3"
.EXAMPLE
.
Saving Sites - Acceptable Formats:
 
-Range- StartIP;EndIP\F:IPFilter;IPFilter = 192.168.0.0;192.168.0.255\F:192.168.0.0;192.168.0.255
-CIDR- CIDRBLock\F:IPFilter;IPFilter = 192.168.0.0/24\F:\AO
 ---- '\AO' can be provided as entry on CIDR filter to indicate 'AssignOnly'
-Target- Website,Devicename,Single IP = google.com -or- MyComputer1 -or- 192.168.0.1
 
+Multiple formats with individual filters can be used for a single site entry
Example:
Test-PowerPing -Site NewSite -AddSite 'MyNewSite2' -RangeList '192.168.0.0/25,microsoft.com,192.168.0.128;192.168.0.255'
 
PS C:\> Test-PowerPing -Site NewSite -ListSites
Name Value
---- -----
MyNewSite 10.0.0.0/24,10.0.1.0/24,10.0.3.0;10.0.3.255
MyNewSite2 192.168.0.0/25,microsoft.com,192.168.0.128;192.168.0.255
NewSite "0.0.0.0";"0.0.0.0"
____________________________________________________________________
Adds new site 'MyNewSite2' to Site List with range 192.168.0.0 - 192.168.0.255 and site 'microsoft.com'.
**Note Adding/Removing Sites may require admin permissions depending on module install location**
.EXAMPLE
Test-PowerPing -Site NewSite -Import $mylist
Example List (Hashtable):
$mylist = @{}
$mylist.add("MyNewSite",'10.0.0.0/24,10.0.1.0/24,10.0.2.0;10.0.3.255')
$mylist
Name Value
---- -----
MyNewSite 10.0.0.0/24,10.0.1.0/24,10.0.2.0;10.0.3.255
 
--|||--Equivalent Object can also be used for Import--|||---
 
Example .CSV (Object):
$mylist.GetEnumerator() | Select-Object -Property Name,Value | Export-Csv <YourPathHere> -NoTypeInformation
$mycsv = import-csv <YourPathHere>
Test-PowerPing -Site NewSite -Import $mycsv
____________________________________________________________________
Adds list containing entry "MyNewSite" with range "10.0.0.0" - "10.0.3.255" to Site List using import.
**Note Adding/Removing Sites may require admin permissions depending on module install location**
.EXAMPLE
Test-PowerPing -Target 'powershellgallery.com,www.powershellgallery.com,mozilla.com,TestComputer'
 
Status IPv4Address DeviceName Time
------ ----------- ---------- ----
Online 20.236.44.162 powershellgallery.com 77
DestinationHostUnreachable www.powershellgallery.com: 11003:40.122.208.145
TimedOut mozilla.com: 11010
Error TestComputer: 11001: No such host is known
____________________________________________________________________
Returns ping results for 4 targets: powershellgallery.com, www.powershellgallery.com, mozilla.com,TestComputer
.EXAMPLE
.
PS C:\> Test-PowerPing -CIDR 192.168.20.0/24 -CIDcalc
********-OR-********
PS C:\> Test-PowerPing -StartIP '192.168.20.0' -Netmask '255.255.255.0' -CIDcalc
 
CIDR : 192.168.20.0/24
BaseIP : 192.168.20.0
BroadcastIP : 192.168.20.255
AddressCount : 256
FirstAssignable : 192.168.20.1
LastAssignable : 192.168.20.254
AssignableAddresses : 254
Netmask : 255.255.255.0
____________________________________________________________________
Returns CIDR block information for the provided CIDR block, or StartIP with the provided Netmask.
.EXAMPLE
.
Targeted filtering using CIDR:
 
test-powerping -CIDR '10.0.0.0/11\F:10.0.2.1;10.0.2.20;10.3.*.*,10.32.0.0/11,10.64.0.0/10,10.128.0.0/9' -AddressOnly
TotalSeconds : 64.4129227
 
Standard filtering:
 
test-powerping -StartIP 10.0.0.0 -EndIP 10.255.255.255 -AddressOnly -IPFilter '10.0.2.1,10.0.2.20,10.3.*.*'
TotalSeconds : 212.7417819
____________________________________________________________________
Both commands return a string list containing addressess from 10.0.0.0 to 10.255.255.255, with 2 addresses
and all addresses matching '10.3.*.*' filtered.
+ The top example uses targeted filtering and CIDR block notation to apply filters to the first entry only.
--The rest of the list is generated/added without filtering.
+ The bottom example uses standard filtering with a start/end ip address and applies filters to the entire set.
--Applying filters to large address ranges will significantly increase generation time.
** Use targeted filtering when attempting to filter larger ranges **
.NOTES
Running Large Range Scans or Address Lists May Increase/Max Resource Utilization
- Using multiple filters or large match patterns (10.7.*.*) increases address generation time.
-- Use CIDR parameter with targeted filtering to avoid large filters/large filter sets
--- Targeted Filter will override default IPFilter/AssignOnly options
-- See Examples for direction/syntax
 
'Sites' functionality is dependent on saving a .CSV file to Module install directory.
                    Modifying Sites/SiteList May Require Admin Permissions
All Site Modification Switches/Parameters require 'NewSite' selection for 'Site' Parameter
 
Version: 1.3.0.0
---------------
Date: 12/9/2023
---------------
Author: Hunter Hirsch
.LINK
https://www.powershellgallery.com/packages/Test-PowerPing
.LINK
https://learn.microsoft.com/en-us/dotnet/api/system.net.networkinformation.ipstatus?view=netframework-4.5
.LINK
https://learn.microsoft.com/en-us/windows/win32/winsock/windows-sockets-error-codes-2?redirectedfrom=MSDN
#>

function Test-PowerPing {
[CmdletBinding(DefaultParameterSetName='Targets')]
[OutputType([System.Collections.ArrayList])]
##If/Else ValidateScript method used to allow for custom error messaging. ErrorMessage property not available on PS below v6.2.##
    Param(
          [Parameter(Mandatory,ParameterSetName='Address',ValueFromPipeline,HelpMessage='Enter Valid IPv4 Address (Ex: "10.0.0.0")')]
          [Parameter(Mandatory,ParameterSetName='Mask',ValueFromPipeline,HelpMessage='Enter Valid IPv4 Address (Ex: "10.0.0.0")')]
          [ValidateScript({
            if ($_ -match '^(([0-1]?[0-9]?[0-9]?|2[0-4][0-9]|25[0-5])\.){3}([0-1]?[0-9]?[0-9]?|2[0-4][0-9]|25[0-5]){1}$'){
                $true
                }
            Else{
                Throw Write-Output 'Cannot be null. Must be valid IP Address. Ex: "192.168.32.52"'
                }
            })]
          [ValidateLength(7,15)]
          [string]
          $StartIP,
          [Parameter(Mandatory,ParameterSetName="Address",ValueFromPipeline,HelpMessage='Enter Valid IPv4 Address (Ex: "10.0.0.0")')]
          [ValidateScript({
            if ($_ -match '^(([0-1]?[0-9]?[0-9]?|2[0-4][0-9]|25[0-5])\.){3}([0-1]?[0-9]?[0-9]?|2[0-4][0-9]|25[0-5]){1}$'){
                $true
                }
            Else{
                Throw Write-Output 'Cannot be null. Must be valid IP Address. Ex: "192.168.32.52"'
                }
            })]
          [ValidateLength(7,15)]
          [string]
          $EndIP,
          [Parameter(Mandatory,ParameterSetName="Mask",ValueFromPipeline,HelpMessage='Enter Valid Netmask (Ex: "255.255.255.0")')]
          [ValidateScript({
            if ($_ -in ($csnl.Values)){
                $true
                }
            Else{
                Throw Write-Output 'Cannot be null. Must be valid Netmask. Example: 255.0.0.0'
                }
            })]
          [ValidateLength(9,15)]
          [string]
          $Netmask,
          [Parameter(Mandatory,ParameterSetName='CIDR',ValueFromPipeline,HelpMessage='Enter Valid CIDR Range (Ex: "10.0.0.0/24")')]
          [ValidateScript({
                 $z = $_.split(',')
                 if ($z -match '\\F:'){
                    $z = $z -replace 'f:','F:'
                    $z = ($z -split '\\F:')[0]
                    }
                 $z | foreach-object {if ($_ -match '^(([0-1]?[0-9]?[0-9]?|2[0-4][0-9]|25[0-5])\.){3}([0-1]?[0-9]?[0-9]?|2[0-4][0-9]|25[0-5]){1}(\/([0-9]|[1-2][0-9]|3[0-2]))$'){
                 $true
                 }
            Else{
                Throw Write-Output 'Cannot be null. Entries must be separated by commas. Ex:"10.0.0.0/8","11.0.0.0/12". Filter should follow entry directly "10.0.0.0/8\F:10.0.0.255;*.2.*.*"'
                }
            }
            })]
          [string[]]
          $CIDR,
          [Parameter(ValueFromPipeline,ParameterSetName='Sites')]
          [Parameter(ValueFromPipeline,ParameterSetName='CIDR')]
          [Parameter(ValueFromPipeline,ParameterSetName='Mask')]
          [ValidateScript({
            if (!$AssignOnly){
                $true
                }
            Else{
                Throw Write-Output 'Cannot be used with "AssignOnly" Parameter.'
                }
            })]
          [switch]
          $CIDcalc,
          [Parameter(ParameterSetName='CIDR',ValueFromPipeline)]
          [Parameter(ParameterSetName='Mask',ValueFromPipeline)]
          [Parameter(ParameterSetName='Sites',ValueFromPipeline)]
          [ValidateScript({
            if (!$CIDcalc){
                $true
                }
            Else{
                Throw Write-Output 'Cannot be used with "CIDcalc" Parameter.'
                }
            })]
          [switch]
          $AssignOnly,
          [Parameter(ParameterSetName='Sites',ValueFromPipeline)]
          [ValidateScript({
            if(!($RemoveSite)  -and !($DeleteSites) -and !($ListSites) -and !($importsite) -and $_ -ne $null){
                $true
                }
            Else{
                Throw Write-Output 'Cannot be null. Cannot be used with RemoveSite,DeleteSites,ListSites,ImportSite Parameters.'
                }
            })]
          [string]
          $AddSite,
          [Parameter(ParameterSetName='Sites',ValueFromPipeline)]
          [ValidateScript({
            if (!($AddSite) -and !($DeleteSites) -and !($ListSites) -and !($importsite) -and $_ -ne $null){
                $true
                }
            Else{
                Throw Write-Output 'Cannot be null. Cannot be used with AddSite,DeleteSites,ListSites,ImportSite Parameters.'
                }
            })]
          [string]
          $RemoveSite,
          [Parameter(ParameterSetName='Sites',ValueFromPipeline)]
          [ValidateScript({
            if (!($RemoveSite) -and !($AddSite) -and !($ListSites) -and !($importsite)){
                $true
                }
            Else{
                Throw Write-Output 'Cannot be used with AddSite,RemoveSite,ListSites,ImportSite Parameters.'
                }
            })]
          [switch]
          $DeleteSites,
          [Parameter(ParameterSetName='Sites',ValueFromPipeline)]
          [ValidateScript({
            if (!($RemoveSite) -and !($AddSite) -and !($DeleteSites) -and !($importsite)){
                $true
                }
            Else{
                Throw Write-Output 'Cannot be used with AddSite,RemoveSite,DeleteSites,ImportSite Parameters.'
                }
            })]
          [switch]
          $ListSites,
          [Parameter(ParameterSetName='Sites',ValueFromPipeline)]
          [ValidateScript({
            if (!($AddSite) -and !($DeleteSites) -and !($RemoveSite) -and !($ListSites) -and $_.gettype().name -eq 'PSCustomObject' -or $_.gettype().name -eq 'Hashtable'){
                $true
                }
            Else{
                Throw Write-Output 'Cannot be used with AddSite,RemoveSite,ListSites,DeleteSites Parameters.'
                }
            })]
          [object]
          $ImportSite,
          [Parameter(ParameterSetName='Sites',ValueFromPipeline)]
          [ValidateScript({
            if (!($DeleteSites) -and !($RemoveSite) -and !($ListSites) -and !($importsite) -and $_ -ne $null){
                if ($_.count -gt '1'){
                    $length = ($_ -join ',').Length
                    $r = $_ -join ','
                    if ($length -le 30000 -and $r -notmatch '[<>^`{|}]'){
                        $true
                        }
                    }
                Else{
                    $length = $_.length
                    if ($length -le 30000 -and $_ -notmatch '[<>^`{|}]'){
                        $true
                        }
                    }
                }
            Else{
                Throw Write-Output 'Can only be used with "-Site NewSite -AddSite" Parameters. Cannot be null. Length must be less than "30000" (~500 entries). Type "Get-Help Test-PowerPing -Parameter RangeList" for further information and formatting.'
                }
            })]
          [String[]]
          $RangeList,
          [Parameter(Mandatory,ParameterSetName='Targets',ValueFromPipeline,Position=0,HelpMessage='Enter Device Name, Web Address, or IPv4 address.')]
          [ValidateScript({
            if ($_ | foreach-object {$_ -ne $null -and $_.length -le 2048 -and $_ -notmatch '["<>^`{|}]'}){
                $true
                }
            Else{
                Throw Write-Output 'Cannot be null. Length of each entry must be less than/equal to 2048. Excluded characters: "<>^`{|}'
                }
            })]
          [string[]]
          $Target,
          [Parameter(ValueFromPipeline,ParameterSetName='Sites')]
          [Parameter(ValueFromPipeline,ParameterSetName='Address')]
          [Parameter(ValueFromPipeline,ParameterSetName='CIDR')]
          [Parameter(ValueFromPipeline,ParameterSetName='Mask')]
          [ValidateScript({
            if ($_ -ne $null -and $Output -ne 'NoDeviceName' -and $Output -ne 'NoTime/DeviceName'){
                $true
                }
            Else{
                Throw Write-Output 'Cannot be null. Cannot be used with "Output NoDeviceName" and "Output NoTime/DeviceName" Parameters.'
                }
            })]
          [string[]]
          $DevName,
          [Parameter(ValueFromPipeline,ParameterSetName='Sites')]
          [Parameter(ValueFromPipeline,ParameterSetName='Address')]
          [Parameter(ValueFromPipeline,ParameterSetName='CIDR')]
          [Parameter(ValueFromPipeline,ParameterSetName='Mask')]
          [switch]
          $AddressOnly,
          [Parameter(ValueFromPipeline,ParameterSetName='Sites')]
          [Parameter(ValueFromPipeline,ParameterSetName='Address')]
          [Parameter(ValueFromPipeline,ParameterSetName='CIDR')]
          [Parameter(ValueFromPipeline,ParameterSetName='Mask')]
          [ValidateScript({
            $f = $_.Split(',')
            $f | foreach-object {if ($_ -like '*.*.*.*' -and $_.length -le '15' -and ($_ -replace '(\d)','' -replace '\.','' -replace '\*','').Length -eq '0'){
                    $true
                    }
                Else{
                    Throw Write-Ouput 'Can only filter: IP Addresses (ex:"10.2.3.200") IP Address Patterns (ex:"10.2.*.*"). Entries must be separated by commas ",". Entries must have "*.*.*.*" format'
                    }
                }
            })]
          [string[]]
          $iPFilter,
          [Parameter(ValueFromPipeline,ParameterSetName='Sites')]
          [Parameter(ValueFromPipeline,ParameterSetName='Address')]
          [Parameter(ValueFromPipeline,Position=3,ParameterSetName='Targets')]
          [Parameter(ValueFromPipeline,ParameterSetName='CIDR')]
          [Parameter(ValueFromPipeline,ParameterSetName='Mask')]
          [ValidateScript({[int]$_ -le '255'})]
          [int]
          $TTL,
          [Parameter(ValueFromPipeline,ParameterSetName='Sites')]
          [Parameter(ValueFromPipeline,ParameterSetName='Address')]
          [Parameter(ValueFromPipeline,Position=1,ParameterSetName='Targets')]
          [Parameter(ValueFromPipeline,ParameterSetName='CIDR')]
          [Parameter(ValueFromPipeline,ParameterSetName='Mask')]
          [ValidateScript({[int]$_ -le '60000'})]
          [int]
          $Timeout,
          [Parameter(ValueFromPipeline,ParameterSetName='Sites')]
          [Parameter(ValueFromPipeline,ParameterSetName='Address')]
          [Parameter(ValueFromPipeline,Position=2,ParameterSetName='Targets')]
          [Parameter(ValueFromPipeline,ParameterSetName='CIDR')]
          [Parameter(ValueFromPipeline,ParameterSetName='Mask')]
          [ValidateScript({[int]$_ -le '65500'})]
          [int]
          $Buffer,
          [Parameter(ValueFromPipeline,ParameterSetName='Sites')]
          [Parameter(ValueFromPipeline,ParameterSetName='Address')]
          [Parameter(ValueFromPipeline,Position=4,ParameterSetName='Targets')]
          [Parameter(ValueFromPipeline,ParameterSetName='CIDR')]
          [Parameter(ValueFromPipeline,ParameterSetName='Mask')]
          [switch]
          $OnlineOnly,
          [Parameter(ValueFromPipeline,ParameterSetName='Sites')]
          [Parameter(ValueFromPipeline,ParameterSetName='Address')]
          [Parameter(ValueFromPipeline,Position=5,ParameterSetName='Targets')]
          [Parameter(ValueFromPipeline,ParameterSetName='CIDR')]
          [Parameter(ValueFromPipeline,ParameterSetName='Mask')]
          [ValidateSet('NoTime','NoDeviceName','NoTime/DeviceName')]
          [string]
          $Output
          )
##Site Dynamic param setup, pulls site names from PPSites.csv (if created) and adds to ValidateSet attribute. If no .csv is present, 'NewSite' is the only valid entry. ##
          DynamicParam{
            $pdic = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
            $pcol = New-Object System.Collections.ObjectModel.Collection[System.Attribute]
            $patt = New-Object System.Management.Automation.ParameterAttribute
            $plist = Test-Path ((get-module Test-PowerPing | Select-Object -ExpandProperty ModuleBase) + '\PPSites.csv') -PathType Leaf
            if ($plist){
                $pexp = (get-module Test-PowerPing | Select-Object -ExpandProperty ModuleBase) + '\PPSites.csv' | Import-Csv | Select-Object -ExpandProperty Name
                }
            Else{
                $pexp = 'NewSite'
                }
            $pexps = $pexp -join ','
            $patt.Mandatory = $true
            $patt.HelpMessage = "Enter Site name for existing saved Site, or 'NewSite' to modify Site List. SiteList: $pexps"
            $patt.ParameterSetName = 'Sites'
            $patt.ValueFromPipeline = $true
            $pcol.Add($patt)
            $pvsa = New-Object System.Management.Automation.ValidateSetAttribute(($pexp))
            $pcol.Add($pvsa)
            $RParam = New-Object System.Management.Automation.RuntimeDefinedParameter('Site', [string], $pcol)
            $RParam.Value = 'NewSite'
            $pdic.Add('Site',$RParam)
            return $pdic
          }
begin {
$Site = $PSBoundParameters.Site
Try{
##Address list object used in process loop. Addresses can be generated from multiple sources, managing on single list object allowed for easier management with ping functionality.##
    $adrl = New-Object System.Collections.ArrayList
    if ($Site){
        if ($ListSites -or $AddSite -or $RemoveSite -or $DeleteSites -or $importsite -or $RangeList -and $Site -ne 'NewSite'){
            $Mant = 'Error: (-Site NewSite) Required to Modify Site List'
            Throw
            }
##Building Sites list hashtable from PPSites.csv (if created) or adds single entry 'NewSite' if no .csv has been created. I didn't want a file present unless the functionality was being utilized.##
        $script:sitep = (get-module -name Test-PowerPing | Select-Object -ExpandProperty ModuleBase) + '\PPSites.csv'
        if (Test-Path -Path $sitep -PathType Leaf){
            if (!$DeleteSites -and !$RemoveSite){
                if ((Get-ItemProperty -Path $sitep -Name Length -ErrorAction Ignore | Select-Object -ExpandProperty Length) -gt 1000000){
                    $Mant = 'Error: Sites List exceeds max size: 1000000 (~1mb). Remove entries / Delete List to continue using Site List'
                    Throw
                    }
                }
            $siteimp = Import-Csv $sitep
            $script:Sites = @{}
            $siteimp | ForEach-Object {$Sites.add($_.name,$_.value)}
            }
        Else{
            $script:Sites = @{'NewSite' = '"0.0.0.0";"0.0.0.0"'}
            }
        if ($site -eq 'NewSite'){
            if (!($AddSite) -and !($RemoveSite) -and !($DeleteSites) -and !($ListSites) -and !($ImportSite)){
                $Mant = 'Error: No Action Selected for "NewSite"'
                Throw
                }
            if ($ListSites){
                $Sites
                return
                }
            Try{
                $null = new-item -Path ((get-module -name Test-PowerPing | Select-Object -ExpandProperty ModuleBase) + '\accesstest.txt') -ErrorAction Stop
                $null = remove-item -path ((get-module -name Test-PowerPing | Select-Object -ExpandProperty ModuleBase) + '\accesstest.txt') -Force -ErrorAction Stop
                }
            Catch{
                $Mant = '###Error: Access Denied. Must run PowerShell as Admin to modify Sites ###'
                Throw
                }
            if ($DeleteSites){
                if (Test-Path $sitep -PathType Leaf){
                    Remove-Item -Path $sitep -Force -ErrorAction Stop
                    }
                Write-Output '<<< Update Successful - Module Re-import may be needed >>>'
                Remove-Item -Path Function:\Test-PowerPing
                return
                }
            if ($AddSite -or $RemoveSite -or $ImportSite){
                $siteparam = @{}
                if ($AddSite){
                    if (!$RangeList){
                        Throw Write-Output "Must Provide RangeList for AddSite Entry"
                        }
                    $siteparam.Add('Add',$AddSite)
                    $siteparam.Add('OList',$RangeList)
                    }
                Elseif ($RemoveSite){
                    $siteparam.Add('Remove',$RemoveSite)
                    }
                Elseif ($ImportSite){
                    $siteparam.Add('Import',$ImportSite)
                    }
                if ($siteparam.Count -gt 0){
                    Get-PowerIPSite @siteparam
                    }
                return
                }
            }
        Else{
##Beginning to process rangelist from site selection where site is not 'NewSite'. Rangelists can contain multiple entries, each entry is fed to foreach loop and processed as Start/EndIP, CIDR, or Target.##
##Foreach loop uses pattern validation for sorting, any misformatted entries will be processed as 'targets' unless they contain excluded characters. Any entries including excluded characters should throw errors in param validation.##
            $siterange = $Sites[$site] -split ','
            foreach ($entry in $siterange){
                if ($entry -match '\\F:'){
                    $entry = $entry -replace 'f:','F:'
                    $combo = $entry -split '\\F:' | Where-Object {$_ -ne ''}
                    $nsite = $combo[0] -replace '"',''
                    $nfilter = $combo[1].Split(';') | Where-Object {$_ -ne ''}
                    }
                Else{
                    $nsite = $entry
                    }
                if ($nsite -match '^(([0-1]?[0-9]?[0-9]?|2[0-4][0-9]|25[0-5])\.){3}([0-1]?[0-9]?[0-9]?|2[0-4][0-9]|25[0-5]){1};(([0-1]?[0-9]?[0-9]?|2[0-4][0-9]|25[0-5])\.){3}([0-1]?[0-9]?[0-9]?|2[0-4][0-9]|25[0-5]){1}$'){
                    $rngparam = @{
                        Start = $nsite.split(';')[0]
                        End = $nsite.split(';')[1]
                        }
                    if ($nfilter){
                        $rngparam.Add('Filter',$nfilter)
                        }
                    if ($AddressOnly){
                        Get-PowerIPRange @rngparam
                        }
                    Elseif (!$CIDcalc){
                        $null = $adrl.AddRange((Get-PowerIPRange @rngparam))
                        }
                    }
                Elseif ($nsite -match '^(([0-1]?[0-9]?[0-9]?|2[0-4][0-9]|25[0-5])\.){3}([0-1]?[0-9]?[0-9]?|2[0-4][0-9]|25[0-5]){1}(\/([0-9]|[1-2][0-9]|3[0-2]))$'){
                    $cidparam = @{
                        Range = $nsite
                        }
                    if (!$CIDcalc){
                        if ($nfilter -contains '\AO'){
                            $cidparam.Add('AssignO',$true)
                            $nfilter = $nfilter.Where({$_ -ne '\AO'})
                            }
                        if ($nfilter.count -gt 0){
                            $cidparam.Add('Filter',$nfilter)
                            }
                        }
                    Else{
                        $cidparam.Add('Calculator',$true)
                        }
                    if ($AddressOnly -or $CIDcalc){
                        Get-PowerIPCIDR @cidparam
                        }
                    Else{
                        $null = $adrl.AddRange((Get-PowerIPCIDR @cidparam))
                        }
                    }
                Elseif ($nsite -notmatch '[<>^`{|}]' -and $nsite.length -le 2048){
                    if ($AddressOnly){
                        $nsite
                        }
                    Elseif (!$CIDcalc){
                        $null = $adrl.Add($nsite)
                        }
                    }
                }
            }
        }
    if ($iPFilter){
        $iPFilter = $iPFilter.Split(',')
        }
    if ($StartIP -and !($Netmask)){
        if ($StartIP -eq $EndIP){
            $Target = $StartIP
            if ($AddressOnly){
                $Target
                return
                }
            }
        Else{
            $rngparam = @{
                Start = $StartIP
                End = $EndIP
                }
            if ($iPFilter){
                $rngparam.Add('Filter',$iPFilter)
                }
            if ($AddressOnly){
                Get-PowerIPRange @rngparam
                }
            Else{
                $null = $adrl.AddRange((Get-PowerIPRange @rngparam))
                }
            }
        }
    if ($Netmask){
        $CIDR = $StartIP + '/' + ($csnl.keys | Where-Object {$csnl[$_] -eq $Netmask})
        }
##CIDR parameter setup and Get-PowerIPCIDR run. Checks each CIDR entry for formatting options and overrides app paramater filter options if present.##
##Filtering significantly increases generation time. Targeted filtering provided as a way to reduce generation time on larger sets.##
    if ($CIDR){
        $CIDR = [string[]]$CIDR.Split(',')
        $CIDR | ForEach-Object {
            $cidparam = @{
                Range = $_
                }
            if ($_ -match '\\F:'){
                $_ = $_ -replace 'f:','F:'
                $cidfiltered = $_ -split '\\F:' | Where-Object {$_ -ne ''}
                $cblock = $cidfiltered[0] -replace '"',''
                $cfilter = $cidfiltered[1].Split(';') | Where-Object {$_ -ne ''}
                $cidparam.'Range' = $cblock
                }
            if (!$CIDcalc){
                if ($AssignOnly -or $cfilter){
                    if ($cfilter -contains '\AO'){
                        $cidparam.Add('AssignO',$true)
                        $cfilter = $cfilter.Where({$_ -ne '\AO'})
                        }
                    Elseif (!$cfilter){
                        $cidparam.Add('AssignO',$true)
                        }
                    }
                if ($iPFilter -or $cfilter){
                    if (!$cfilter){
                        $cidparam.Add('Filter',$iPFilter)
                        }
                    Else{
                        $cidparam.Add('Filter',$cfilter)
                        }
                    }
                if ($AddressOnly){
                    Get-PowerIPCIDR @cidparam
                    $cfilter = $null
                    $cblock = $null
                    $cidparam = $null
                    return
                    }
                Else{
                   $null = $adrl.AddRange((Get-PowerIPCIDR @cidparam))
                    }
                }
            Else{
                $cidparam.Add('Calculator',$true)
                Get-PowerIPCIDR @cidparam
                $cfilter = $null
                $cblock = $null
                $cidparam = $null
                return
                }
            $cfilter = $null
            $cblock = $null
            $cidparam = $null
            }
        }
    if ($Target){
        if ($Target.count -eq '1' -and $Target -match ','){
                $Target = $Target.Split(',')
            }
        $Target | ForEach-Object {
            $null = $adrl.Add($_)
            }
        }
    if (!($AddressOnly) -and !($CIDcalc) -and !($Mant) -and $Site -ne 'NewSite'){
        $pque = New-Object System.Collections.Queue
        $dque = New-Object System.Collections.Queue
        $results = @{}
        $npopt = new-object System.Net.NetworkInformation.PingOptions
        $npopt.Ttl = '255'
        if ($TTL){
            $npopt.Ttl = $TTL
        }
        [int]$buf = '32'
        if ($buffer){
            [int]$buf = $buffer
            }
        $pbuffer = [byte[]]::new($buf)
        $time = 1000
        if ($Timeout){
            $time = $Timeout
        }
        if ($DevName){
            $cfnd = New-Object System.Collections.ArrayList
            if ($DevName.count -eq '1' -and $DevName -match ','){
                $DevName = $DevName.Split(',')
                }
            $OnlineOnly = $true
            }
        [int]$so = '0'
        if ($OnlineOnly){
            $so = '1'
            }
        }
    }
Catch {
    if (!($AddressOnly) -and !($CIDcalc) -and $Site -ne 'NewSite' -and !($Mant)){
        $pque.Clear()
        $dque.Clear()
        $adrl.Clear()
        $results.Clear()
        }
##$Mant variable is used to capture custom error messaging for events above. If variable is not present, checks for access denied error and provides message or provides direct error message.##
    if (!($Mant)){
        If ($_.Exception.Message -match "PPSites.csv' is denied" -or $_.Exception.Message -match 'Access to the path is denied'){
            Throw Write-Output '###Error: Access Denied. Must run PowerShell as Admin to modify Sites ###'
            }
        Else{
            Throw $_.Exception.ErrorRecord
            }
        }
    Else{
        Throw Write-Output $Mant
        }
    }
Finally{
    $Sites = $null
    $siteimp = $null
    if ($AddressOnly -or $CIDcalc){
        [System.GC]::Collect()
        }
##Get-PowerIPAddress generation loop uses try/catch for generation. Error entries are expected after successful list generation. Conditional below checks for error entries matching expected loop errors and removes if present.##
    if (!$CIDcalc -and $Site -ne 'NewSite'){
        $global:Error.Clone() | ForEach-Object {
            if ($_.exception -match 'cannot convert value "256"'){
                $global:Error.Remove($_)
                }
            }
        }
    }
}
Process {
##While loop processes address entries into 'newping' objects, adds to ping queue (pque), removes addresses from address list, processes ping results and adds to $results hash @ 256 objects per loop cycle##
#-- 'pque' is used to manage 'newping' objects and process aysync pings. As pings complete, object is dequeued to $results based on parameter input.##
#-- If device name resolution is enabled, objects dequeued from 'pque' are added to 'dque' queue. Host resolution requests are processed asynchronously via GetHostEntryAsync method. Results are added back to given queue number object on $results hash.##
##Batching in sets of 256 allowed for consistent performance with larger lists, and removing address entries within loop process assists with memory managment/resources needed for generation and ping. Queues function faster than direct arraylist manipulation.##
##Thanks to Andrew Pearce for sharing Queue logic in "FastPing" (https://www.powershellgallery.com/packages/FastPing). Logic is modified, but this was my first introduction to Queue objects - much appreciated!##
Try{
if (!($AddressOnly) -and !($CIDcalc) -and $Site -ne 'NewSite'){
    [int]$cnt = 0
    while ($adrl.count -gt 0){
        if ($adrl.count -gt 256){
            for ($i = 0 ; $i -lt 256; $i++){
                $null = $results.Add($cnt,[System.Collections.ArrayList]::new())
                $ping = new-object System.Net.NetworkInformation.Ping
                $newping = @{
                    Queue = $cnt
                    Host = $adrl[$i]
                    Ping = $ping
                    Reply = $ping.SendPingAsync($adrl[$i],$time,$pbuffer,$npopt)
                    }
                $pque.Enqueue($newping)
                $cnt++
                }
            for ($j = 0 ; $j -lt 256; $j++){
                $adrl.RemoveAt(0)
                }
            }
        Else{
            foreach ($ad in $adrl){
                $null = $results.Add($cnt,[System.Collections.ArrayList]::new())
                $ping = new-object System.Net.NetworkInformation.Ping
                $newping = @{
                    Queue = $cnt
                    Host = $ad
                    Ping = $ping
                    Reply = $ping.SendPingAsync($ad,$time,$pbuffer,$npopt)
                    }
                $pque.Enqueue($newping)
                $cnt++
                }
            $endad = $true
            }
        while ($pque.Count -gt 0){
            $newreply = $pque.Dequeue()
            if ($newreply.reply.IsCompleted -eq $true){
                $qcnt = $newreply.queue
                if ($NewReply.Reply.Result.Status -eq 'Success'){
                    if ($Output -notmatch 'device'){
                        if ($newreply.Host -match '^(([0-1]?[0-9]?[0-9]?|2[0-4][0-9]|25[0-5])\.){3}([0-1]?[0-9]?[0-9]?|2[0-4][0-9]|25[0-5]){1}$'){
                            $newdev = @{
                                Queue = $newreply.Queue
                                Name = [net.dns]::GetHostEntryAsync($newreply.Host)
                                }
                            $dque.Enqueue($newdev)
                            }
                        }
                    if ($Output.length -gt '0'){
                        if ($Output -eq 'NoTime/DeviceName'){
                            $null = $results.$qcnt.add([PSCustomObject]@{Status="{0,-11}" -f 'Online'; IPv4Address =if ($newreply.host -match '^(([0-1]?[0-9]?[0-9]?|2[0-4][0-9]|25[0-5])\.){3}([0-1]?[0-9]?[0-9]?|2[0-4][0-9]|25[0-5]){1}$'){"{0,-23}" -f $newreply.host}Else{"{0,-17}" -f $newreply.reply.Result.Address.IPAddressToString}})
                            }
                        elseif ($Output -eq 'NoDeviceName'){
                            $null = $results.$qcnt.add([PSCustomObject]@{Status="{0,-11}" -f 'Online'; IPv4Address =if ($newreply.host -match '^(([0-1]?[0-9]?[0-9]?|2[0-4][0-9]|25[0-5])\.){3}([0-1]?[0-9]?[0-9]?|2[0-4][0-9]|25[0-5]){1}$'){"{0,-23}" -f $newreply.host}Else{"{0,-17}" -f $newreply.reply.Result.Address.IPAddressToString};Time = "{0,-5}" -f $newreply.reply.Result.RoundtripTime})
                            }
                        elseif ($Output -eq 'NoTime'){
                            $null = $results.$qcnt.add([PSCustomObject]@{Status="{0,-11}" -f 'Online'; IPv4Address =if ($newreply.host -match '^(([0-1]?[0-9]?[0-9]?|2[0-4][0-9]|25[0-5])\.){3}([0-1]?[0-9]?[0-9]?|2[0-4][0-9]|25[0-5]){1}$'){"{0,-23}" -f $newreply.host}Else{"{0,-17}" -f $newreply.reply.Result.Address.IPAddressToString};DeviceName = if ($newreply.host -match '^(([0-1]?[0-9]?[0-9]?|2[0-4][0-9]|25[0-5])\.){3}([0-1]?[0-9]?[0-9]?|2[0-4][0-9]|25[0-5]){1}$'){'*N / A*'}Else{$newreply.host}})
                            }
                        }
                    Else{
                        $null = $results.$qcnt.add([PSCustomObject]@{Status="{0,-11}" -f 'Online'; IPv4Address =if ($newreply.host -match '^(([0-1]?[0-9]?[0-9]?|2[0-4][0-9]|25[0-5])\.){3}([0-1]?[0-9]?[0-9]?|2[0-4][0-9]|25[0-5]){1}$'){"{0,-23}" -f $newreply.host}Else{"{0,-17}" -f $newreply.reply.Result.Address.IPAddressToString};DeviceName =if ($newreply.host -match '^(([0-1]?[0-9]?[0-9]?|2[0-4][0-9]|25[0-5])\.){3}([0-1]?[0-9]?[0-9]?|2[0-4][0-9]|25[0-5]){1}$'){'*N / A*'}Else{$newreply.host};Time = "{0,-5}" -f $NewReply.reply.Result.RoundtripTime})
                        }
                    }
                ELSEif ($so -ne '1' -and $NewReply.Reply.Status.value__ -eq '5'){
                    if ($Output.length -gt '0'){
                        if ($Output -eq 'NoTime/DeviceName'){
                            $null = $results.$qcnt.add([PSCustomObject]@{Status="{0,-11}" -f $NewReply.reply.Result.Status;IPv4Address = if ($NewReply.reply.Result.Address.IPAddressToString -eq '0.0.0.0'){"{0,-23}" -f ($newreply.host + ": " + $NewReply.reply.Result.Status.value__)}Else{"{0,-23}" -f ($newreply.host + ": " + $NewReply.reply.Result.Status.value__ + ":" + $NewReply.reply.result.Address.IPAddressToString)}})
                            }
                        elseif ($Output -eq 'NoDeviceName'){
                            $null = $results.$qcnt.add([PSCustomObject]@{Status="{0,-11}" -f $NewReply.reply.Result.Status;IPv4Address = if ($NewReply.reply.Result.Address.IPAddressToString -eq '0.0.0.0'){"{0,-23}" -f ($newreply.host + ": " + $NewReply.reply.Result.Status.value__)}Else{"{0,-23}" -f ($newreply.host + ": " + $NewReply.reply.Result.Status.value__ + ":" + $NewReply.reply.result.Address.IPAddressToString)}; Time =''})
                            }
                        elseif ($Output -eq 'NoTime'){
                            $null = $results.$qcnt.add([PSCustomObject]@{Status="{0,-11}" -f $NewReply.reply.Result.Status;IPv4Address = if ($NewReply.reply.Result.Address.IPAddressToString -eq '0.0.0.0'){"{0,-23}" -f ($newreply.host + ": " + $NewReply.reply.Result.Status.value__)}Else{"{0,-23}" -f ($newreply.host + ": " + $NewReply.reply.Result.Status.value__ + ":" + $NewReply.reply.result.Address.IPAddressToString)}; DeviceName = ''})
                            }
                        }
                    Else{
                        $null = $results.$qcnt.add([PSCustomObject]@{Status="{0,-11}" -f $NewReply.reply.Result.Status;IPv4Address = if ($NewReply.reply.Result.Address.IPAddressToString -eq '0.0.0.0'){"{0,-23}" -f ($newreply.host + ": " + $NewReply.reply.Result.Status.value__)}Else{"{0,-23}" -f ($newreply.host + ": " + $NewReply.reply.Result.Status.value__ + ":" + $NewReply.reply.result.Address.IPAddressToString)}; DeviceName = ''; Time = ''})
                        }
                    }
                ELSEif ($NewReply.Reply.Status.value__ -ne '5'){
                    if ($Output){
                        if ($Output -eq 'NoTime/DeviceName'){
                            $null = $results.$qcnt.add([PSCustomObject]@{Status = "{0,-11}" -f 'Error'; IPv4Address = "{0,-23}" -f ($newreply.host +": "+$NewReply.reply.Exception.InnerException.InnerException.ErrorCode+": "+$NewReply.reply.Exception.InnerException.InnerException.Message)})
                            }
                        elseif ($Output -eq 'NoDeviceName'){
                            $null = $results.$qcnt.add([PSCustomObject]@{Status = "{0,-11}" -f 'Error'; IPv4Address = "{0,-23}" -f ($newreply.host +": "+$NewReply.reply.Exception.InnerException.InnerException.ErrorCode+": "+$NewReply.reply.Exception.InnerException.InnerException.Message); Time = ''})
                            }
                        elseif ($Output -eq 'NoTime'){
                            $null = $results.$qcnt.add([PSCustomObject]@{Status = "{0,-11}" -f 'Error'; IPv4Address = "{0,-23}" -f ($newreply.host +": "+$NewReply.reply.Exception.InnerException.InnerException.ErrorCode+": "+$NewReply.reply.Exception.InnerException.InnerException.Message); DeviceName = ''; Time = ''})
                            }
                        }
                    Else{
                        $null = $results.$qcnt.Add([PSCustomObject]@{Status = 'Error'; IPv4Address = "{0,-23}" -f ($newreply.host +": "+$NewReply.reply.Exception.InnerException.InnerException.ErrorCode+": "+$NewReply.reply.Exception.InnerException.InnerException.Message); DeviceName = ''; Time = ''})
                        }
                    }
                }
            Else{
                $pque.Enqueue($newreply)
                }
            }
        while ($dque.Count -gt 0){
            $hostname = $dque.Dequeue()
            if ($hostname.Name.IsCompleted -eq $true){
                if (($hostname.Name.result.hostname)){
                    $dcnt = $hostname.queue
                    $results.$dcnt = [PSCustomObject]@{Status = ($results.$dcnt).Status; IPv4Address = ($results.$dcnt).IPv4Address; DeviceName = $hostname.name.result.hostname; Time = ($results.$dcnt).Time}
                    if ($DevName){
                        $DevName | ForEach-Object {
                            if ($hostname.Name.Result.HostName -match $_){
                                $null = $cfnd.Add([PSCustomObject]@{Status = ($results.$dcnt).Status; IPv4Address = ($results.$dcnt).IPv4Address; DeviceName = $hostname.name.result.hostname; Time = ($results.$dcnt).Time})
                                }
                            }
                        }
                    }
                }
            Else{
                $dque.Enqueue($hostname)
                }
            }
        if ($endad){
            $adrl.clear()
            }
        }
    }
}
Catch{
    $results = $null
    if (!($Mant)){
        Throw $_.Exception.ErrorRecord
        }
    }
Finally{
    if (!($AddressOnly) -and !($CIDcalc) -and $Site -ne 'NewSite' -and !($Mant)){
        $pque.Clear()
        $dque.Clear()
        $adrl.Clear()
        }
    [System.GC]::Collect()
    }
}
end {
##If Devname filter was used, 'cfnd' list is provided, containing any successful pings to devices matching the input provided for Devname. Allows for matching groups of devices with similar naming##
Try{
    If (!($AddressOnly) -and !($CIDcalc) -and $Site -ne 'NewSite'){
        $rng = 0..($results.count -1)
        if (!($DevName)){
            $rng | ForEach-Object {$results.$_}
            }
        Else{
            if ($cfnd.count -gt 0){
                $cfnd
                }
            Else{
                Write-Output 'No Devices found matching ComputerName(s)'
                }
            $cfnd = $null
            }
        }
    }
Catch{
    Throw $_.Exception.ErrorRecord
    }
Finally{
    $results = $null; $npopt = $null; $ping = $null; $newping = $null; $time = $null; $newdev = $null;$newreply = $null;$pbuffer = $null;$ad = $null; $pque = $null; $dque = $null; $adrl = $null
    if ($Target){
        $Target.Clear()
        }
    if ($DevName){
        $DevName.Clear()
        }
    if ($CIDR){
        $CIDR.Clear()
        }
    [System.GC]::Collect()
    }
}
}