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() } } } |