Introduction
It’s Friday afternoon. The network guys threw the towel, after trying to replicate the DNS servers on both sides of the firewall. “Klaus” they said, “Would you please give us a list of host names and MAC addresses of the computers in the lab network?”. The network guys decided to just add static host name mappings to the DNS on the office network. This way we would be able to access the machines in the lab by hostname. Temporarily of course!
Great! Pulling up the MAC addresses for about 30 PCs, manually, is not what I call a great start into the weekend! Time to call…
PowerShell to the rescue
I studied a bunch of links on the interweb and came up with my own home brawn recipe. It uses a .NET call and the ARP (address resolution protocol) tool. No need for WMI and actually reading the configuration from each of the machines, which requires valid credentials to be passed in for each remote computer. This was in my case not possible.
Here is how it works. The script would iterate IP addresses from lets say 192.168.2.1 to 192.168.2.255 and call [System.Net.Dns]::GetHostbyAddress($IP).HostName. This call will try to return a hostname of a Windows PC using the address resolution protocol ARP. The address resolution protocol caches also the MAC address for each particular IP address. This mapping can be retrieved using the ARP tool with the –A parameter. Its out put looks like this:
#Interface: 192.168.2.49 --- 0x10003
# Internet Address Physical Address Type
# 192.168.1.1 00-26-88-ed-42-06 dynamic
# 192.168.1.51 00-0c-29-84-ff-af dynamic
# 192.168.2.2 00-00-bc-2f-93-06 dynamic
# 192.168.2.35 00-50-56-bd-26-1a dynamic
# 192.168.2.36 00-50-56-bd-51-2b dynamic
The Regular Expression to parse the ARP –A output
One of the key aspects of my PowerShell script is to parse the output of the ARP tool using a regular expression. The regular expression is here:
$Regex = "^\s+(?<IP>{0}\d+)\s+(?<MAC>[0-9a-f]{2}-[0-9a-f]{2}-[0-9a-f]{2}-[0-9a-f]{2}-[0-9a-f]{2}-[0-9a-f]{2})" -f $SubnetRegex, "{1}", "{2}"
The Script in its simplicity and beauty
Here is the script in its hole beauty. It creates a csv file with IP addresses, MAC addresses and hostnames
$DebugPreference = "continue"
$Subnet = "192.168.2."
$SubnetData = New-Object -TypeName "System.Collections.ArrayList"
1..70 | ForEach-Object `
{
$ClientTable = @{}
$IP = "$Subnet$_";
Write-Debug $IP;
$ClientTable["IP"] = $IP
$ClientTable["MAC"] = "?"
try
{
$Hostname = [System.Net.Dns]::GetHostbyAddress($IP).HostName
Write-Debug $Hostname
$ClientTable["Hostname"] = $Hostname
}
catch
{
Write-Warning "No hostname found for $IP"
$ClientTable["Hostname"] = "?"
}
$null = $SubnetData.add($ClientTable)
}
$SubnetData
#Get ARP Result
$ARPResultLines = ARP -a
$ARPResultLines.count
#Interface: 192.168.2.49 --- 0x10003
# Internet Address Physical Address Type
# 192.168.1.1 00-26-88-ed-42-06 dynamic
# 192.168.1.51 00-0c-29-84-ff-af dynamic
# 192.168.2.2 00-00-bc-2f-93-06 dynamic
# 192.168.2.35 00-50-56-bd-26-1a dynamic
# 192.168.2.36 00-50-56-bd-51-2b dynamic
$SubnetRegex = $Subnet.replace(".", "\.");
$SubnetRegex
$Regex = "^\s+(?<IP>{0}\d+)\s+(?<MAC>[0-9a-f]{2}-[0-9a-f]{2}-[0-9a-f]{2}-[0-9a-f]{2}-[0-9a-f]{2}-[0-9a-f]{2})" -f $SubnetRegex, "{1}", "{2}"
$Regex
for($i=3; $i -lt $ARPResultLines.count; $i++)
{
$Line = $ARPResultLines[$i]
Write-Debug $Line
if($Line.Trim() -ne "")
{
if($Line -match $Regex)
{
$IP = $matches.IP
$MAC = $matches.MAC
foreach($DataSet in $SubnetData)
{
if($DataSet["IP"] -eq $IP)
{
$DataSet["MAC"] = $MAC
}
}
}
}
}
$SubnetData
Set-Content -Encoding "UTF8" -value "IP, Hostname, MAC" -Path "$Home\LabNetwork.csv"
foreach($DataSet in $SubnetData)
{
$Hostname = $DataSet["Hostname"]
$IP = $DataSet["IP"]
$MAC = $DataSet["MAC"]
Add-Content -Encoding "UTF8" -value "$IP, $Hostname, $MAC" -Path "$Home\LabNetwork.csv"
}
. $Home\LabNetwork.csv
Ausblick
Figure 1: I don’t drink Bud Light, but I like the commercials
This is what I am thinking: Bud Light Real Men Of Genius Commercials.
Mr.-PowerShell-Doing-The-Work-For-Network-Guys-On-Friday-Afternoon Guy.