Test-PowerPing.psm1

class PowerPingResult {
    [string]$Status
    [string]$IPv4Address
    [string]$DeviceName
    [string]$Time
    PowerPingResult(
    [PSCustomObject]$response
    ){
        $this.Status = $response.Status
        $this.IPv4Address = $response.ipv4address
        $this.DeviceName = $response.DeviceName
        $this.Time = $response.Time
        }
    }
##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 (ICMP) asynchronously to devices to determine network accessibility.
Returns simple ping results (Status,IPv4Address,DeviceName,Time) with status codes.
Additional features: Address list generation, CIDR calculator, Range to CIDR conversion.
.DESCRIPTION
Test-PowerPing pings (ICMP) 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.
'ToCidr' switch can be used to convert range to CIDR or split CIDR entry into sub-blocks.
'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.
-CIDR format supported for range, use 'AssignOnly' switch for assignable addresses.
-'CIDcalc','ToCidr' & 'AddressOnly' switches available for CIDR input
[Command Example: Test-PowerPing -Target 'website.com,www.anothersite.com,10.0.0.223']
[Command Example: Test-Powerping '192.168.20.0/24' -CIDcalc]
 
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,Target,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,Target,Sites
.PARAMETER ToCidr
Converts provided Start/EndIP to CIDR
-Entry must be value 0 - 31
-Enter 0 for minimum CIDR block(s) value for provided IP Range
-Enter 1 - 31 to split provided range into blocks of provided factor
-Can provide CIDR block or Start/End IP as input
[Command Example: Test-PowerPing -StartIP '192.168.20.0' -EndIP '192.168.25.255' -ToCidr 24]
 
Parameter Set(s): CIDR,Address,Target,Sites
.PARAMETER FullCIDR
Provides full CIDR conversion list when splitting CIDR blocks - used with 'ToCidr'
-if Switch is not used, only blocks matching the factor provided in 'ToCidr' will be returned
-Used specifically with Start/End IP
[Command Example: Test-PowerPing -StartIP '192.168.20.1' -EndIP '192.168.20.255' -ToCidr 26 -FullCIDR]
 
Parameter Set(s): CIDR,Address,Target,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 matches (partial or full) 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 **
.EXAMPLE
.
Test-Powerping -StartIP '192.168.12.0' -EndIP '192.168.12.255' -ToCidr 0
192.168.12.0/24
 
Test-Powerping '192.168.12.0/24' -ToCidr 26
192.168.12.0/26
192.168.12.64/26
192.168.12.128/26
192.168.12.192/26
 
Test-Powerping -StartIP '192.168.12.1' -EndIP '192.168.12.255' -ToCidr 26 -FullCIDR
192.168.12.1/32
192.168.12.2/31
192.168.12.4/30
192.168.12.8/29
192.168.12.16/28
192.168.12.32/27
192.168.12.64/26
192.168.12.128/26
192.168.12.192/26
 
____________________________________________________________________
Top command converts provided range to CIDR
- providing '0' for ToCidr parameter will convert range to CIDR without splitting
Second command splits provided CIDR block into subnet blocks matching /26
- providing '1 to 31' for ToCidr parameter will attempt to split provided range/CIDR into subnet blocks
- can attempt split on CIDR or Start/End range
Third command converts provided range to CIDR and splits into subnet blocks matching /26
- FullCIDR switch used to show complete CIDR conversion for provided Range - used with Start/EndIP Only
- omitting FullCIDR switch will only return entries where block matches provided factor /26
.EXAMPLE
.
Using Pipeline
 
$mytargetlist | test-powerping
$mycidrlist | test-powerping -CIDcalc
$mycidrlist | test-powerping -ToCidr 24 | test-powerping -CIDcalc | select-object -property BaseIP,BroadcastIP
____________________________________________________________________
All commands provide input via pipeline for actions described below
+ Top example will send pings to targets provided in variable '$mytargetlist'
-- 'Targets' parameter set available for ping/output options. CIDR block format supported for target range.
-- Filtering is not supported via pipeline / Targets parameter set
--- Use standard parameter sets to use filters, or save filtered list as site / assign to variable
+ Middle example will provide CIDR calculator information for blocks provided in variable '$mycidrlist'
+ Bottom example attempts to convert CIDR list to block size /24 and return Base/BroadcastIP for new blocks.
** Available switches for CIDR input: -AdressOnly (-AssignOnly optional), -CIDCalc, -ToCIDR <value>
.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.1.0
---------------
Date: 7/28/2024
---------------
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],[PowerPingResult])]
##If/Else ValidateScript method used to allow for custom error messaging.
    Param(
          [Parameter(Mandatory,ParameterSetName='Address',HelpMessage='Enter Valid IPv4 Address (Ex: "10.0.0.0")')]
          [Parameter(Mandatory,ParameterSetName='Mask',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",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(ParameterSetName='Targets')]
          [Parameter(ParameterSetName='Address')]
          [Parameter(ParameterSetName='Sites')]
          [Parameter(ParameterSetName='CIDR')]
          [ValidateScript({
            if (([uint32]$_ -lt 32 -and [uint32]$_ -ge 0) -and !$AddressOnly -and !$CIDcalc){
                $true
                }
            Else{
                Throw Write-Output 'Entry must be digit 0-31. Enter "0" for minimum block list. Cannot be used with "AddressOnly" or "CIDCalc" Parameters.'
                }
            })]
          [string]
          $ToCidr,
          [Parameter(Mandatory,ParameterSetName="Mask",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',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(ParameterSetName='Targets')]
          [Parameter(ParameterSetName='Sites')]
          [Parameter(ParameterSetName='CIDR')]
          [Parameter(ParameterSetName='Mask')]
          [ValidateScript({
            if (!$AssignOnly -and !$ToCidr){
                $true
                }
            Else{
                Throw Write-Output 'Cannot be used with "AssignOnly" or "ToCidr" Parameters.'
                }
            })]
          [switch]
          $CIDcalc,
          [Parameter(ParameterSetName='Targets')]
          [Parameter(ParameterSetName='CIDR')]
          [Parameter(ParameterSetName='Mask')]
          [Parameter(ParameterSetName='Sites')]
          [ValidateScript({
            if (!$CIDcalc -and !$ToCidr){
                $true
                }
            Else{
                Throw Write-Output 'Cannot be used with "CIDcalc" or "ToCidr" Parameters.'
                }
            })]
          [switch]
          $AssignOnly,
          [Parameter(ParameterSetName='Sites')]
          [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')]
          [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')]
          [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')]
          [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')]
          [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')]
          [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,ValueFromPipeline,ParameterSetName='Targets',Position=0,HelpMessage='Enter Device Name, Web Address, or IPv4 address.')]
          [ValidateScript({
            if ($_ | foreach-object {$_ -ne $null -and $_ -notmatch '["<>^`{|}]'}){
                $true
                }
            Else{
                Throw Write-Output 'Cannot be null. Excluded characters: "<>^`{|}'
                }
            })]
          [string[]]
          $Target,
          [Parameter(ParameterSetName='Sites')]
          [Parameter(ParameterSetName='Address')]
          [Parameter(ParameterSetName='CIDR')]
          [Parameter(ParameterSetName='Mask')]
          [Parameter(ParameterSetName='Targets')]
          [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(ParameterSetName='Targets')]
          [Parameter(ParameterSetName='Sites')]
          [Parameter(ParameterSetName='Address')]
          [Parameter(ParameterSetName='CIDR')]
          [Parameter(ParameterSetName='Mask')]
          [switch]
          $AddressOnly,
          [Parameter(ParameterSetName='Targets')]
          [Parameter(ParameterSetName='Address')]
          [Parameter(ParameterSetName='Sites')]
          [Parameter(ParameterSetName='CIDR')]
          [switch]
          $FullCIDR,
          [Parameter(ParameterSetName='Sites')]
          [Parameter(ParameterSetName='Address')]
          [Parameter(ParameterSetName='CIDR')]
          [Parameter(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(ParameterSetName='Sites')]
          [Parameter(ParameterSetName='Address')]
          [Parameter(ParameterSetName='Targets')]
          [Parameter(ParameterSetName='CIDR')]
          [Parameter(ParameterSetName='Mask')]
          [ValidateScript({[int]$_ -le '255'})]
          [int]
          $TTL,
          [Parameter(ParameterSetName='Sites')]
          [Parameter(ParameterSetName='Address')]
          [Parameter(ParameterSetName='Targets')]
          [Parameter(ParameterSetName='CIDR')]
          [Parameter(ParameterSetName='Mask')]
          [ValidateScript({[int]$_ -le '60000'})]
          [int]
          $Timeout,
          [Parameter(ParameterSetName='Sites')]
          [Parameter(ParameterSetName='Address')]
          [Parameter(ParameterSetName='Targets')]
          [Parameter(ParameterSetName='CIDR')]
          [Parameter(ParameterSetName='Mask')]
          [ValidateScript({[int]$_ -le '65500'})]
          [int]
          $Buffer,
          [Parameter(ParameterSetName='Sites')]
          [Parameter(ParameterSetName='Address')]
          [Parameter(ParameterSetName='Targets')]
          [Parameter(ParameterSetName='CIDR')]
          [Parameter(ParameterSetName='Mask')]
          [switch]
          $OnlineOnly,
          [Parameter(ParameterSetName='Sites')]
          [Parameter(ParameterSetName='Address')]
          [Parameter(ParameterSetName='Targets')]
          [Parameter(ParameterSetName='CIDR')]
          [Parameter(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.
          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'
            $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{
    $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
            }
        $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"'}
            }
## Saved Site Management (Add,Remove,Import,Delete,List)
        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
                }
            }
## Sorting sites by type (Range,CIDR,or Target) for selected site and preparing to process
        Else{
            $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 ($ToCidr){
                        $tocidrparam = @{
                            Begin = $rngparam.Start
                            Finish = $rngparam.End
                            Factor = $ToCidr
                            }
                        if ($FullCIDR){
                            $tocidrparam.Add('full',$true)
                            }
                        Get-PowerIP2CIDR @tocidrparam
                        }
                    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' -or $AssignOnly){
                            $cidparam.Add('AssignO',$true)
                            if ($nfilter){
                                $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
                        }
                    Elseif($ToCidr){
                        if ([uint32](($cidparam.Range).split('/')[1]) -le [uint32]$ToCidr){
                            $startend = Get-PowerIPCIDR -Range ($cidparam.Range) -Calculator | Select-Object -Property BaseIP,BroadcastIP
                            $tocidrparam = @{
                                Begin = $startend.BaseIP
                                Finish = $startend.BroadcastIP
                                Factor = $ToCidr
                                }
                            Get-PowerIP2CIDR @tocidrparam
                            }
                        Else{
                            Throw 'Error: ToCidr factor cannot be greater than provided CIDR Range'
                            }
                        }
                    Else{
                        $null = $adrl.AddRange((Get-PowerIPCIDR @cidparam))
                        }
                    }
                Elseif ($nsite -notmatch '[<>^`{|}]'){
                    if ($AddressOnly){
                        $nsite
                        }
                    Elseif (!$CIDcalc -and !$ToCidr){
                        $null = $adrl.Add($nsite)
                        }
                    }
                }
            }
        }
    if ($iPFilter){
        $iPFilter = $iPFilter.Split(',')
        }
#Range processing/prep for pings or list generation
    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
                return
                }
            Elseif ($ToCidr){
                $tocidrparam = @{
                    Begin = $rngparam.Start
                    Finish = $rngparam.End
                    Factor = $ToCidr
                    }
                if ($FullCIDR){
                    $tocidrparam.Add('full',$true)
                    }
                Get-PowerIP2CIDR @tocidrparam
                return
                }
            Else{
                $null = $adrl.AddRange((Get-PowerIPRange @rngparam))
                }
            }
        }
## Netmask processing/prep for ping / address list generation / CIDR caclulator
    if ($Netmask){
        $CIDR = $StartIP + '/' + ($csnl.keys | Where-Object {$csnl[$_] -eq $Netmask})
        }
## CIDR processing/prep for ping / address list generation / CIDR calculator
    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
                    }
                Elseif ($ToCidr){
                    if ([uint32](($cidparam.Range).split('/')[1]) -le [uint32]$ToCidr){
                        $startend = Get-PowerIPCIDR -Range ($cidparam.Range) -Calculator | Select-Object -Property BaseIP,BroadcastIP
                        $tocidrparam = @{
                            Begin = $startend.BaseIP
                            Finish = $startend.BroadcastIP
                            Factor = $ToCidr
                            }
                        Get-PowerIP2CIDR @tocidrparam
                        $cfilter = $null
                        $cblock = $null
                        $cidparam = $null
                        $startend = $null
                        $tocidrparam = $null
                        return
                        }
                    Else{
                         Throw 'Error: ToCidr factor cannot be greater than provided CIDR Range'
                        }
                    }
                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
            }
        }
## Function to process Target variable/pipeline input
    function Get-ppingtargets {
        if ($Target.count -eq '1' -and $Target -match ','){
                $Target = $Target.Split(',')
            }
        $Target | 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]))$'){
                $cidparam = @{
                    Range = $_
                    }
                if (!$CIDcalc -and !$ToCidr){
                    if ($AssignOnly){
                        $cidparam.Add('AssignO',$true)
                        }
                    if (!$AddressOnly){
                        $null = $adrl.AddRange((Get-PowerIPCIDR @cidparam))
                        }
                    Else{
                        Get-PowerIPCIDR @cidparam
                        $cidparam = $null
                        return
                        }
                    }
                Elseif ($ToCidr){
                    if ([uint32](($cidparam.Range).split('/')[1]) -le [uint32]$ToCidr){
                        $startend = Get-PowerIPCIDR -Range ($cidparam.Range) -Calculator | Select-Object -Property BaseIP,BroadcastIP
                        $tocidrparam = @{
                            Begin = $startend.BaseIP
                            Finish = $startend.BroadcastIP
                            Factor = $ToCidr
                            }
                        Get-PowerIP2CIDR @tocidrparam
                        $tocidrparam = $null
                        $cidparam = $null
                        return
                        }
                    Else{
                         Throw 'Error: ToCidr factor cannot be greater than provided CIDR Range'
                        }
                    }
                Else{
                    $cidparam.Add('Calculator',$true)
                    Get-PowerIPCIDR @cidparam
                    $cidparam = $null
                    return
                    }
                $cidparam = $null
                }
            Else{
                $null = $adrl.Add($_)
                }
            }
        }
    if ($Target){
        Get-ppingtargets
        }
## List to collect pipeline input
    $ppinputlist = New-Object System.Collections.ArrayList
## Ping processing setup (pque = queue for ping, dque = queue for dns lookup for device name)
    if (!($AddressOnly) -and !($CIDcalc) -and !($ToCidr) -and $Site -ne 'NewSite' -and !($Mant)){
        $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'
            }
##Function below to process pings##
##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!##
        function get-ppprocessing {
            [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)
                                    }
                                }
                            $null = $results.$qcnt.add([PSCustomObject]@{
                                Status ='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}$'){$newreply.host}Else{$newreply.reply.Result.Address.IPAddressToString}
                                DeviceName = if ($Output -like '*Device*'){''}Else{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 = if ($Output -like '*Time*'){''}Else{$NewReply.reply.Result.RoundtripTime}
                                })
                            }
                        ELSEif ($so -ne '1' -and $NewReply.Reply.Status.value__ -eq '5'){
                            $null = $results.$qcnt.add([PSCustomObject]@{
                                Status =$NewReply.reply.Result.Status
                                IPv4Address =if ($NewReply.reply.Result.Address.IPAddressToString -eq '0.0.0.0'){$newreply.host + ": " + $NewReply.reply.Result.Status.value__}Else{$newreply.host + ": " + $NewReply.reply.Result.Status.value__ + ":" + $NewReply.reply.result.Address.IPAddressToString}
                                DeviceName = ''
                                Time = ''
                                })
                            }
                        ELSEif ($NewReply.Reply.Status.value__ -ne '5'){
                            $null = $results.$qcnt.add([PSCustomObject]@{
                                Status ='Error'
                                IPv4Address = $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 {
    if (!($AddressOnly) -and !($CIDcalc) -and !($ToCidr) -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 -or $ToCidr){
        [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 !$ToCidr -and $Site -ne 'NewSite'){
        $global:Error.Clone() | ForEach-Object {
            if ($_.exception -match 'cannot convert value "256"'){
                $global:Error.Remove($_)
                }
            }
        }
    }
}
##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!##
Process {
Try{
## Pipeline collection ##
if ($input){
    $null = $ppinputlist.Add($_)
    }
## Standard input processing
if ($adrl.Count -gt 0){
    get-ppprocessing
    }
}
Catch{
    $results = $null
    if (!($Mant)){
        Throw $_.Exception.ErrorRecord
        }
    }
Finally{
    if (!($AddressOnly) -and !($CIDcalc) -and !($ToCidr) -and $Site -ne 'NewSite' -and !($Mant)){
        $pque.Clear()
        $dque.Clear()
        $adrl.Clear()
        }
    }
}
end {
##Pipeline Processing##
Try{
    if ($ppinputlist.Count -gt 0){
        [string[]]$Target = $ppinputlist | ForEach-Object {"$_"}
        Get-ppingtargets
        if ($adrl.Count -gt 0 -and !($AddressOnly) -and !($CIDcalc) -and !($ToCidr)){
            get-ppprocessing
            }
        }
    }
Catch{
    $results = $null
    if (!($Mant)){
        Throw $_.Exception.ErrorRecord
        }
    }
Finally{
    if (!($AddressOnly) -and !($CIDcalc) -and !($ToCidr) -and $Site -ne 'NewSite' -and !($Mant)){
        $pque.Clear()
        $dque.Clear()
        $adrl.Clear()
        $ppinputlist = $null
        }
    }
##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 !($ToCidr) -and $Site -ne 'NewSite'){
        $rng = 0..($results.count -1)
        if (!($DevName)){
            $rng | ForEach-Object {if ($results.$_){[PowerPingResult]::new($results.$_)}}
            }
        Else{
            if ($cfnd.count -gt 0){
                $cfnd | ForEach-Object {[PowerPingResult]::new($_)}
                }
            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()
    }
}
}