What’s that machine called?

Mamood had a problem! He needed to get some information on a bunch of machines he manages and like a good follower of the church of PowerShell, he fired up his favourite blue and white command line tool (which he for some reason changes to be black with green text) and set about trying to obtain the information required.

Somewhere along the way he got stuck, so somehow he managed to find his Office Communicator window in amongst what must have been at least 50 others including several terminal server client sessions (each containing at least another 20 windows) and contacted me. He needed to perform a nslookup on an IP Address and grep the resolved host name for his information.

I sent him back the following three functions (cobbled together on the spur of the moment):

function Ping-Host([string]$hostName) {
$pinger = new-object system.Net.NetworkInformation.Ping;
$pinger.Send($hostName);
}

function Resolve-HostByAddress([string]$ipAddress) {
return [System.Net.Dns]::GetHostByAddress($ipAddress);
}

function Resolve-HostByName([string]$hostName) {
return [System.Net.Dns]::GetHostByName($hostName);
}

I then told him to save them in a script called networkTools.ps1 and then Dot-source them into his session and have a play to see if they do what he wanted:

[D:\PsScripts]
1> . .\networkTools.ps1

[D:\PsScripts]
2> resolve-hostByName “localhost”
HostName Aliases AddressList
——– ——- ———–
machine1.cpatinliteral.net {} {127.0.0.1}

[D:\PsScripts]
3> resolve-hostByAddress “192.168.8.5″
HostName Aliases AddressList
——– ——- ———–
machine1.cpatinliteral.net {} {192.168.8.5}

[D:\PsScripts]
4> “machine1″, “machine2″, “machine3″ | % { resolve-hostByName $_ }
HostName Aliases AddressList
——– ——- ———–
machine1.captainliteral.net {} {192.168.8.5}
machine2.captainliteral.net {} {192.168.8.3}
machine3.captainliteral.net {} {192.168.8.8}

He said they were spot on and went away happy – until a little while later. He was struggling to tie in all the information he had obtained into a single report (formatted table) and wondered if I had any ideas. Of cause I did, but I didn’t know if they would work! I spend a lot of time piping objects to Get-Member and see a lot of extras provided by the Extendible Type System that PowerShell provides and wondered if I could use this to make a struct of sorts…

[D:\PsScripts]
5> $machine = New-Object System.Object

[D:\PsScripts]
6> Add-Member -InputObject $machine -MemberType NoteProperty -name “HostName” -Value “”

[D:\PsScripts]
7> $machine | gm

TypeName: System.Object

Name MemberType Definition
—- ———- ———-
Equals Method System.Boolean Equals(Object obj)
GetHashCode Method System.Int32 GetHashCode()
GetType Method System.Type GetType()
ToString Method System.String ToString()
HostName NoteProperty System.String HostName=

[D:\PsScripts]
8> $machine.hostName = “machine2″

[D:\PsScripts]
9> $machine

HostName
——–
machine2

Looking good – I’ve managed to create a basic object and add an extra property (HostName) to it, assign to this new property and then output is back to the console – and I’ve done all this without going near the C# compiler. This is new for me!

Lets add some more properties:

[D:\PsScripts]
10> Add-Member -InputObject $machine -MemberType NoteProperty -name “IpAddress” -Value “”

[D:\PsScripts]
11> Add-Member -InputObject $machine -MemberType NoteProperty -name “MacAddress” -Value “”

[D:\PsScripts]
12> $machine | gm

TypeName: System.Object

Name MemberType Definition
—- ———- ———-
Equals Method System.Boolean Equals(Object obj)
GetHashCode Method System.Int32 GetHashCode()
GetType Method System.Type GetType()
ToString Method System.String ToString()
HostName NoteProperty System.String HostName=machine2
IpAddress NoteProperty System.String IpAddress=
MacAddress NoteProperty System.String MacAddress=

[D:\PsScripts]
13> $machine.IpAddress = $(resolve-hostbyname $machine.HostName).AddressList[0]

[D:\PsScripts]
14> $machine
HostName IpAddress MacAddress
——– ——— ———-
machine2 192.168.8.3

This might just work! Lets wrap it in another function that we’ll append to the end of networkTools.ps1:

function New-MachineObject($hostName = “”, $ipAddress = “”, $macAddress = “”, $alive = “”) {
    $machine = New-Object System.Object;
    Add-Member -InputObject $machine -MemberType NoteProperty -name “HostName” -Value $hostName;
    Add-Member -InputObject $machine -MemberType NoteProperty -name “IpAddress” -Value $ipAddress;
    Add-Member -InputObject $machine -MemberType NoteProperty -name “MacAddress” -Value $macAddress;
    Add-Member -InputObject $machine -MemberType NoteProperty -name “Alive” -Value $alive;
    return $machine;
}

Dot-source this into the current session again and we’re up and running.

[D:\PsScripts]
15> . .\networkTools.ps1

[D:\PsScripts]
16> New-MachineObject “localhost” “127.0.0.1″ “0×00000000″ $false

HostName IpAddress MacAddress Alive
——– ——— ———- —–
localhost 127.0.0.1 0×00000000 False

Mamood’s total scenario was that he had a list of machine names (in a .csv file), and needed to get the fully-qualified domain name for the host along with the IP address, corresponding MAC Address and then test to see if it was responding to ping requests. Actually, I added in the test part as the only way I have of getting the MAC Address is WMI and there’s no point trying to connect to the device if it’s down is there? I had a quick look at the System.Net namespace, but couldn’t see how to get a remote machines MAC address – please, if you know how to do it within .NET, let me know.

Before we solve the complete problem however, yet another function (or two) is required for networkTools.ps1 (and it needs dot-sourcing into the current session again):

function Get-MacAddressForHost($hostName, $ipAddress) {
    $networkAdapters = get-wmiobject Win32_NetworkAdapterConfiguration -computername $hostName -Filter “IPenabled = ‘True’”;
foreach ($adapter in $networkAdapters) {
        if ($adapter.IPAddress -eq $ipAddress) {
    return $adapter.MACAddress;
        }
}
}

function Get-AliveStatus($hostName) {
    $response = Ping-Host $hostName;
    if ($response.Status -eq [System.Net.NetworkInformation.IPStatus]::Success) {
        return $true;
    }
    else {
        return $false;
    }
}

A little crude perhaps, but works and gets around the issue of virtual network adapter from programs like VMWare and Windows Mobile 5 devices J

It’s worth noting that you may need some credentials to connect to the remote machine with WMI – Mamood certainly did! If so, just append –credential $creds the first line of Get-MacAddressForHost and then type the following into your session to get PowerShell to prompt you for the username and password that you’ll use to connect to remote machines. Don’t worry, your password is stored in a Secure String, so it shouldn’t be possible to read it by typing $creds – doesn’t stop a call into the .NET classes to get the clear-text version though. So when you’re finished, set it to $null ($creds = $null;)

[D:\PsScripts]
48> $creds = Get-Credential

And now the final solution…

[D:\PsScripts]
36> . .\networkTools.ps1

# Create a CSV file, just like Mamood has
[D:\PsScripts]
38> “MachineName”, “machine1″, ” machine2″, ” machine3″, ” machine4″ > machineList.csv

# Get machine information from DNS
[D:\PsScripts]
40> $machines = @();

[D:\PsScripts]
41> import-csv machineList.csv | % { $machineInfo = resolve-hostByName $_.MachineName; $machines += New-MachineObject $machineInfo.Hostname $machineInfo.AddressList[0] }

# Ping each host to get alive status
[D:\PsScripts]
43> $machines | % { $_.Alive = Get-AliveStatus $_.HostName }

# Get MAC Address for each alive host
[D:\PsScripts]
45> $machines | ? { $_.Alive } | % { $_.MacAddress = Get-MacAddressForHost $_.HostName $_.IpAddress }

# Finally, output as a table (or you could export to CSV if you prefer)
[D:\PsScripts]
47> $machines | ft

HostName IpAddress MacAddress Alive
——– ——— ———- —–
machine1.captainliteral.net 192.168.8.3 00:04:FF:0B:23:B2 True
machine2.captainliteral.net 192.168.8.8 False
machine3.captainliteral.net 192.168.7.1 False
machine4.captainliteral.net 192.168.8.5 00:16:17:0E:23:B2 True

And that’s that.

This is an interesting procedure because it required multiple passes the get all the required data. Sure, I could have put this into one (long) line and there are probably other, more concise ways of achieving the same task (please post comments if you have one), but what I wanted to show here is that by using the Extensible Type System, you can create simple struct like objects to store information in a typed fashion.

I for one had not played much with the type extensions possible in PowerShell, but I’m going to go a read all about them now (and find out what I did wrong in the process J) Jeffrey Snover recently blogged about this feature of PowerShell and points to you Jim Truher’s blog post explaining it all a lot further and a lot better then I.

If you want my original version of networkTools.ps1, download it here.

For more information on dot-sourcing and running scripts, read my previous entry on the subject here.

The earlier functions in networktools.ps1 use the .NET Framework, read more about what the syntax means here.

In the end Mamood got what he needed and the next time he has to produce the same report (probably tomorrow, or at least by next week), he’ll have it done In a couple of seconds.

Leave a Reply