Identifying a Computer Name with a Ping Sweep Tool with PowerShell

I hope you’ve been following along in this series as we build a PowerShell tool to ping a range of IP addresses in a given subnet. In the last article, we left with a pretty decent tool that displayed the IP address for computers that responded. Although that may be all you need, lets try to get a more rich result since PowerShell is already writing an object to the pipeline. It would probably be helpful to see the computer name.
PowerShell Ping Sweep Tool Article Series

  1. Building a Ping Sweep Tool with PowerShell
  2. PowerShell Ping Sweep Tool: Adding Parameter Validation
  3. Adding Trace Information to a PowerShell Ping Tool
  4. Identifying a Computer Name with a PowerShell Ping Sweep Tool
  5. Building a PowerShell Ping Sweep Tool: Adding a Port Check


If you are running Windows 8 or later, you can easily use the Resolve-DnsName cmdlet with an IP address.

Using PowerShell's Resolve-DnsName cmdlet. (Image Credit: Jeff Hicks)
Using PowerShell's Resolve-DnsName cmdlet. (Image Credit: Jeff Hicks)
Excellent. I can see that the NameHost property is what I want. Although I'm not a big fan of using raw .NET when there is a perfectly good cmdlet to use, this can serve as an alternative method:
Using .NET to grab the host name in Windows PowerShell. (Image Credit: Jeff Hicks)
Using .NET to grab the host name in Windows PowerShell. (Image Credit: Jeff Hicks)
This approach provides us with a different property name but gives us the same result. It would be nice to be able to write an object to the pipeline with the IP address and its host name. It's possible that the IP address is not registered with DNS, so I should handle that error. Here's the relevant change to the function.
​
Here's what happens when I run the new version.
Our result after running test-subnet. (Image Credit: Jeff Hicks)
Our result after running test-subnet. (Image Credit: Jeff Hicks)
Another useful entry might the device's MAC address. I can't guarantee that the host will be a Windows host or that I will have permissions to access it, so I can't rely on WMI or CIM cmdlets. However, there is an old-school alternative. When I ping a remote IP address, an entry will be made in the arp table.
Using the ARP command in Windows PowerShell.  (Image Credit: Jeff Hicks)
Using the ARP command in Windows PowerShell. (Image Credit: Jeff Hicks)
I can also get a specific IP address.
Using a specific IP address with the ARP command in Windows PowerShell.  (Image Credit: Jeff Hicks)
Using a specific IP address with the ARP command in Windows PowerShell. (Image Credit: Jeff Hicks)
The physical address is the MAC address. To obtain the MAC address, I will have to parse the ARP output. I'll need to get just the line with the MAC. There are a few ways I could parse the text output. Here's one approach:
Parsing the text output. (Image Credit: Jeff Hicks)
Parsing the text output. (Image Credit: Jeff Hicks)
I can split this line into an array of three elements. I am splitting on the white spaces between the columns using a regular expression pattern.
​
I find it helpful to trim the line first so that there are no extra spaces.
Trimming the first line. (Image Credit: Jeff Hicks)
Trimming the first line. (Image Credit: Jeff Hicks)
The MAC address is the second element.
030915 1338 BuildingaPi8

I can use this code in my function so that if the address can be pinged, I can get the MAC and add it to the output. Here's the new version of Test-Subnet.
​
[cmdletbinding()]
Param(
[Parameter(Position=0,HelpMessage="Enter an IPv4 subnet ending in 0.")]
[ValidatePattern("\d{1,3}\.\d{1,3}\.\d{1,3}\.0")]
[string]$Subnet= ((Get-NetIPAddress -AddressFamily IPv4).Where({$_.InterfaceAlias -notmatch "Bluetooth|Loopback"}).IPAddress -replace "\d{1,3}$","0"),
[ValidateRange(1,255)]
[int]$Start = 1,
[ValidateRange(1,255)]
[int]$End = 254,
[ValidateRange(1,10)]
[Alias("count")]
[int]$Ping = 1
)
Write-Verbose "Pinging $subnet from $start to $end"
Write-Verbose "Testing with $ping pings(s)"
#a hash table of parameter values to splat to Write-Progress
$progHash = @{
 Activity = "Ping Sweep"
 CurrentOperation = "None"
 Status = "Pinging IP Address"
 PercentComplete = 0
}
#How many addresses need to be pinged?
$count = ($end - $start)+1
<#
take the subnet and split it into an array then join the first
3 elements back into a string separated by a period.
This will be used to construct an IP address.
#>
$base = $subnet.split(".")[0..2] -join "."
#Initialize a counter
$i = 0
#loop while the value of $start is <= $end
while ($start -le $end) {
  #increment the counter
  write-Verbose $start
  $i++
  #calculate % processed for Write-Progress
  $progHash.PercentComplete = ($i/$count)*100
  #define the IP address to be pinged by using the current value of $start
  $IP = "$base.$start"
  #Use the value in Write-Progress
  $proghash.currentoperation = $IP
  Write-Progress @proghash
  #get local IP
  $local = (Get-NetIPAddress -AddressFamily IPv4).Where({$_.InterfaceAlias -notmatch "Bluetooth|Loopback"})
  #test the connection
  if (Test-Connection -ComputerName $IP -Count $ping -Quiet) {
    #if the IP is not local get the MAC
    if ($IP -ne $Local.IPAddress) {
        #get MAC entry from ARP table
        Try {
            $arp = (arp -a $IP | where {$_ -match $IP}).trim() -split "\s+"
            $MAC = $arp[1]
        }
        Catch {
            #this should never happen but just in case
            Write-Warning "Failed to resolve MAC for $IP"
            $MAC = "unknown"
        }
    }
    else {
        #get local MAC
        $MAC = ($local | Get-NetAdapter).MACAddress
    }
    #attempt to resolve the hostname
    Try {
        $iphost = (Resolve-DNSName -Name $IP -ErrorAction Stop).Namehost
    }
    Catch {
        Write-Verbose "Failed to resolve host name for $IP"
        #set a value
        $iphost = "unknown"
    }
    Finally {
        #create a custom object
       [pscustomobject]@{
       IPAddress = $IP
       Hostname = $iphost
       MAC = $MAC
       }
    }
  } #if test ping
  #increment the value $start by 1
  $start++
} #close while loop
} #end function

After testing, I realized I needed to take the local IP address into account.

​
The local address won't be in the ARP table. If the address I'm pinging is the same as the local address, I'll need to take other steps to get the MAC.
​
I also included some error handling in the rare situation where the address can be pinged but not retrieved from the ARP cache. I don't think this is even possible, but it is only a few lines of code, so I included it to be safe.
Most of my tests have been with a small subnet subset, but I should really test the entire thing end to end. I'm also a little curious about how long this will take.
Testing our function in Windows PowerShell. (Image Credit: Jeff Hicks)
Testing our function in Windows PowerShell. (Image Credit: Jeff Hicks)
Subtracting the begin and finish times shows this took about 6 minutes.
Our function took six minutes to complete. (Image Credit: Jeff Hicks)
Our function took six minutes to complete. (Image Credit: Jeff Hicks)
You might get different results depending on the quality of your network and especially name resolution. In fact all of the extra bits could be totally optional. You could create the function so that the basic output is the IP address and if you want the additional detail, it is specified by parameter. There are some advanced things we could do to really speed up performance with runspaces or the use of background jobs, but that is definitely advanced material and beyond the scope of this series. However, there is one more feature I think we can add and I'll cover that next time.