6

Machine-Specific
Scripting

So far, we have demonstrated the use of scripts that will run unattended on any number of workstations, quietly managing, reporting, and configuring as required by the administrator. These scripts even maintain themselves, noting when they become obsolete and replacing themselves when changes on a remote server suggest that they should do so. While there are many circumstances in which this functionality is more than sufficient, there is one major limitation: none of the techniques or scripts we have described is able to differentiate its behavior depending on the machine on which it is being run. This may not pose a problem if the tasks being carried out are all routine and generally applicable, but it does impose severe restrictions if the purpose of scripting is to automate reconfiguration of a machine or if you require certain tasks to be carried out only on specific machines.

In this chapter, we explain how to enhance the techniques already described by writing scripts whose actions are partially determined by the specific workstation on which they are running. First, we demonstrate the extraction and comparison of an identifier that can be found trivially within the registry. We then describe types of situations in which such an identifier falls short; the bulk of the chapter is devoted to describing various ways of extracting information that unambiguously and uniquely identifies a workstation. It is this latter type that we use exclusively in our scripts, for reasons which should soon become self-evident.

Extracting the Machine Identifier

Every Windows NT workstation has a name, an identity that is assigned to it when the operating system is installed. This name must be unique on a local area network because it is used for Windows networking; it normally (but not always) has the same value as the hostname if IP networking is installed.* As far as we are concerned at the moment, the useful thing about this name is that it is incredibly easy to extract from a workstation. This means that it can provide us with a simple way of specifying script behavior that varies depending upon the individual workstation on which it is being run.

There are several methods available for retrieving the machine name from a workstation. Four obvious ones spring to mind:

  • Get it through the Network Control Panel.
  • Retrieve it from the registry.
  • Use network command-line utilities.
  • Read an environment variable.

A brief description of each of these methods follows.

From the Network Control Panel

If you are sitting at a workstation, the quickest way to obtain the computer name is probably through the Network Control Panel. Simply open the panel (Start → Settings → Control Panel → Network) and you will be presented with a dialog looking very much like the one in Figure 6-1. The computer name is presented clearly and unambiguously.

Clearly, although this extraction method is trivial for someone sitting at a workstation, it would not be of much use to a script. For this we have to turn to one of the other options.

From the Registry

It should come as no surprise to find that a workstation's computer name is stored in the registry. A few minutes browsing with Regedt32 would reveal that the information is stored in the HKEY_LOCAL_MACHINE hive under the string value HKLM\SYSTEM\CurrentControlSet\Control\ComputerName\ComputerName. The following short Perl scriptlet, computername.pl extracts the computer name of a workstation from the registry and prints it to standard output:

#computername.pl

use Win32::Registry;

$key = 'SYSTEM\CurrentControlSet\Control\ComputerName\ComputerName';

images

Figure 6-1. Network Control Panel

Win32::Registry::RegOpenKeyEx
    (&HKEY_LOCAL_MACHINE,$key,NULL,&KEY_ALL_ACCESS,$RegHandle);
Win32::Registry::RegQueryValueEx
    ($RegHandle, 'ComputerName', NULL, $type, $answer);
Win32::Registry::RegCloseKey($RegHandle);

print "The name of this NT workstation is $answer";

Running this scriptlet from the command line on a workstation called albatross will produce the following results:

C:>computername.pl
The name of this NT workstation is ALBATROSS

Using net.exe

Another straightforward way of finding out the name of a computer is to use a net command. Here, we will concentrate on one incantation of the command, net config workstation, which displays information about the workstation service. Typing this command at the command line produces a list of parameters, separated into four logical groups, which can loosely be described as concerning:

  • Identity
  • Networking hardware
  • Domain information
  • Communication time-outs

The section we are interested in is the first one. The information displayed here looks very much like this:

C:\>net config workstation
Computer name                        \\ALBATROSS
User name                            home

As you can see, the computer name takes pride of place. The extraction of just the computer name from this output can be accomplished with the following fragment of Perl:

@output = ‘net config workstation‘;
$output[0] =~ s/.*\\\\(.*)/$1/;
print "The computer name is: $output[0]";

The code may look a little obfuscated thanks to the regular expression, but it is actually incredibly simple. The first line calls net config workstation and stores the output in an array. A regular expression then replaces the first line of the array with the value of the computer name (“find any number of any characters, followed by two backslashes, followed by any number of any more characters; replace everything with the bit after the backslashes”). Finally we prove the point by printing the computer name to standard output.

From the Environment

Short of using the GUI-based Network Control Panel, by far the simplest method for extracting the computer name from a workstation is to read the environment table. In common with virtually all operating systems in the world, Windows NT provides a set of so-called environment variables that store information about the machine and current user. These variables are listed if the set command is typed at the command prompt without any parameters. If you type set and read the list of variables, you will find that one of them is called computername; its value, unsurprisingly, is set to the name of the workstation. Extracting the value from an environment variable is totally trivial: on the Windows NT command line, any word sandwiched between two % symbols is treated as an environment variable and automatically “expanded” to the value of that variable. Therefore, on a workstation called albatross, the command:

C:>echo This computer is called %computername%

will produce the following output:

This computer is called ALBATROSS

Perl provides the special variable %ENV to read environment variables. The following scriplet reads the computer name from the environment and prints it to standard output:

#computername.pl
print "The name of this NT workstation is $ENV{computername}";

We leave it to your imagination to work out what the output of this scriptlet may look like!

Clearly this is a much neater, simpler way of retrieving that computer name than using the registry or parsing the output of a command-line network utility; it would be very difficult to justify using any other method unless the circumstances were rather exceptional.

Given that it is so easy for a script to identify the name of the workstation on which it is running, it seems rather perverse to suggest—as we are just about to— that this is actually a rather unsatisfactory identifier. Although it and the extraction techniques discussed earlier are perfectly adequate for many maintenance and configuration tasks that require a script to behave differentially depending on the workstation on which it is running, there are certain situations in which reliance on computer name is not at all satisfactory. The reason for this is that while computer names are usually unique on a LAN, there is no guarantee that they are unique. In fact, in some situations, you can be extremely confident that they will not be unique, for example, if a set of workstations are disk-imaged clones or if the operating system has been installed automatically with a default set of parameters. Furthermore, there may be situations in which a workstation regularly changes its name or location, perhaps depending on who is using it. In any of these cases, a script that really needs to know on which workstation it is running will have to find a more robust method of identifying machines. Ideally, this method should be completely impervious to any amount of configuration and tweaking.

Thankfully, every computer that contains an Ethernet card does have exactly what we need—a unique identifier encoded in hardware—which goes by the name of MAC address. Furthermore, virtually every operating system that supports networking provides a means of extracting this information relatively easily. Or at least for those that don't, someone (the OS vendors themselves in the case of NT) provides utilities to do it for you. The rest of this chapter is dedicated to describing various ways of retrieving a workstation's MAC address; in the next chapter, we give an extended example of putting it to use by writing a configuration script that can totally change a workstation's identity. Although working with MAC addresses is significantly more involved than working with computer names, by the time you have read the next two chapters, we hope you will agree that the extra work is easily worth it for the flexibility and power that MAC addresses provide.

What Is a MAC Address?

The Media Access Control (MAC) address of a computer is the physical address of its Ethernet card, a 48-bit number actually encoded in the hardware of the circuitry. This address is part of the so-called Data Link Control (DLC) layer of an IEEE 802-compliant network; it is these 48 bits that are actually used by computers to talk to one another on a local subnet. For example, imagine a computer called Bill wanting a conversation with another, called Scott. Both are on the same subnet, so we can ignore the business of routers. Bill knows Scott's IP address, so it broadcasts an Address Resolution Protocol (ARP) request encapsulating that IP address; Scott obliges the request by returning an ARP reply, encapsulating its MAC address. Bill can now communicate with Scott directly, using the MAC address. As a final touch, Bill can add Scott's MAC address to its ARP cache (sometimes known as an ARP table), so that it doesn't have to send an ARP request again.*

A detailed description of a MAC address and its function in IEEE 802-compliant networking is beyond the scope of this book. For us here, the relevance of a MAC address is that it is guaranteed to be a unique identifier, because every Network Interface Card (NIC) vendor is allocated a unique prefix, and each ensures that every card that it manufactures has a unique suffix. (However, it's worth noting that some cards have a facility for reprogramming the MAC address, so a malevolent hacker could actually violate the uniqueness rule.) In the rest of this chapter, we will demonstrate various ways of extracting the MAC address from all the machines on a subnet, ending up with a Perl script that writes a file you can use for matching machines with identities.

Retrieving a MAC Address

Now that we know what the MAC address is and why it is useful in any context that requires a script to identify the specific machine on which it is running, we will look at the various ways in which we can retrieve it. Several approaches are possible depending on the current configuration in question:

  • NT installed and attached to a network
  • NT installed but not attached to a network
  • NT not installed and not attached to a network
  • Some alien TCP/IP-configured OS attached to a network

In the next several sections, we discuss a series of extraction methods that we have found useful in these various contexts and explain their rationales. We start with simple, manual retrieval methods and work toward complete automation of the process.

Using a DOS Network Driver

Many DOS network drivers display the MAC address of the local NIC when invoked. For instance, the standard NDIS driver that is regularly found on network boot disks displays what it terms the Station Address; this is, in fact, the MAC address. Simple, but rather crude.

Summary of capability

A DOS network driver provides a quick and dirty means for retrieving MAC addresses.

Using ipconfig.exe

ipconfig.exe is a command-line utility that comes with both NT Workstation and Server. It displays the IP configuration information of the machine that it is run on. It can also be used to carry out some limited administration of DHCP. In order for this utility to work, TCP/IP must be installed and configured on your machines. To run the utility, type the following at the command prompt:

C:\>ipconfig

By default, the command displays only the IP address, subnet mask, and default gateway for each adapter bound to TCP/IP. However, there are a number of switches that ipconfig.exe can use that adjust this behavior. The one we are interested in is the /all switch; the output from ipconfig.exe when run from the command line is as follows:

C:\>ipconfig /all

Windows NT IP Configuration

        Host Name . . . . . . . . . : annapurna.jesus.cam.ac.uk
        DNS Servers . . . . . . . . : 131.111.12.20
                                      131.111.8.42
        Node Type . . . . . . . . . : Broadcast
        NetBIOS Scope ID. . . . . . :
        IP Routing Enabled. . . . . : No
        WINS Proxy Enabled. . . . . : No
        NetBIOS Resolution Uses DNS : No

Ethernet adapter PCNTN4M1:
Description . . . . . . . . : AMD PCNET Family Ethernet Adapter
Physical Address. . . . . . : 00-60-B0-C1-E2-82
DHCP Enabled. . . . . . . . : No
IP Address. . . . . . . . . : 131.111.229.20
Subnet Mask . . . . . . . . : 255.255.255.0
Default Gateway . . . . . . : 131.111.229.62

Discussion of the abundant information displayed here is unnecessary in the present context; the only parameter of importance for us is the MAC address, which, as we can see, is 00-60-B0-C1-E2-82 for the machine annapurna. Obviously, we could just note down this address as we did in “Using a DOS Network Driver,” earlier in this chapter. However, a neater solution would be to use a short Perl script to extract the MAC address and add it to a file. The following few lines do just that:

#extmac.pl
open LOGFILE, '>>.\mac.log' or die "cannot open log file $!\n";
@lines = ‘ipconfig /all‘;
foreach $line(@lines) {
    chomp($line);
    if ($line =~ /^\s+Phy.*/) {
        ($description,$macaddress) = split(/:\s/,$line);
        print LOGFILE "$macaddress\n";
    }
}

This script works simply by opening a file ready for appending. It then collects the output from the command-line utility ipconfig.exe (used with the /all switch) into an array, takes each line in turn, and then checks to see if the line starts with a number of spaces followed by the letters “Phy” (the first three letters of Physical Address).* If it does, the line is split in two and the second variable, $macaddress, is written to the file mac.log. If the script was run from a floppy disk and the mac.log file was written to the disk, it would be possible to go to each machine in turn, run the script, and end up with a disk containing all the MAC addresses required.

Summary of capability

The utility ipconfig.exe has the following capabilities:

  • Shows MAC address
  • Facilitates local extraction of MAC address through a script

Using getmac.exe

getmac.exe is a command-line utility that comes with the NT Server Resource Kit. As its name suggests, it extracts the MAC address of the local machine, but not only that, it can also remotely extract the MAC address from any NT machine on your network. This feature makes it an ideal tool to incorporate into a script that collects MAC addresses for a given set of machines on a network without the system administrator having to leave her desk. As it runs over RPC (see “Using the Registry,” later in this chapter), it also has the benefit of being able to use any of the common network protocols such as TCP/IP, NetBEUI, and Nwlink (IPX). getmac.exe has no switches, but it does accept UNC names as well as IP addresses and hostnames as parameters. If it is run without any parameters, it returns the MAC address and transport name (driver) for the local machine. For example:

C:\>getmac
Transport Address  Transport Name
-----------------  --------------
00-60-B0-C1-E2-82  \Device\Nbf_PCNTN4M1
00-60-B0-C1-E2-82  \Device\NwlnkNb
00-60-B0-C1-E2-82  \Device\NetBT_PCNTN4M1

In this case, the result produces three lines, each with the same MAC address but different transport names. This is because the local machine has three protocols bound to the NIC: NetBEUI, Nwlink, and TCP/IP. As you would expect, if no protocols are installed, getmac.exe produces an error and does not display the desired MAC address.

There is one small snag: in order for getmac.exe to be able to extract MAC addresses remotely, you must have a valid account on the machine from which you are trying to obtain the address. This account must also have the right to “access the computer from the network.” If the account is a Domain Admin account and all the machines are registered in the domain, you'll have no problem. However, if this isn't the case, an easy option would be to make sure the guest account on each machine is enabled, as by default the group Everyone has the right to access the computer from the network. Be aware of the security implications.*

The following Perl script makes use of getmac.exe and its network feature. The script takes a text file containing a list of machine names whose MAC addresses we need to obtain. The script is called from the command line in the following way:

C:\>gomac.pl macadds.txt

where macadds.txt is the filename of a text file containing a newline-separated list of hostnames.

$filename = shift @ARGV;
open(NETNAMES,$filename);
open(MACADDS,">addresses.mac");
while ($name = <NETNAMES>) {
    chomp($name);
    $answer = ‘getmac $name‘;
    @components = split(/[\s+^]/, $answer);
    foreach $component(@components) {
        if($component =~ /^\d{2}(-\d{2})+/){
        $macaddress = $component;
            if ($macaddress ne $oldmacaddress){
                print"MAC address for $name\t is:$macaddress\n";
                print MACADDS "$name:$macaddress\n";
                $oldmacaddress = $macaddress;
            }
        }
    }
}
close(NETNAMES);
close(MACADDS);

Once the script has opened the file, it steps through, one line at a time, running getmac.exe, putting the output into $answer, and then splitting the $answer into the array @components. It then just trawls through the components extracting any MAC address using the regular expression /^\d{2}(-\d{2})+/. This expression looks for two digits at the start of the line (array element) followed by a - and two digits. The sequence of the - and two digits is then repeatedly looked for until the end of the line. If a match is found, the component is stored in $macaddress and is then written out to a file. As mentioned earlier, if more than one protocol is installed, the output from getmac.exe repeats the MAC address. The script uses the $oldmacaddress to check that any newly found MAC address is not one we already know about.

Summary of capability

getmac.exe has the following capabilities:

  • Shows MAC address
  • Facilitates local extraction of MAC address through scripts
  • Allows for remote extraction of MAC address through command line or scripts

Using the Registry

Whenever we want to find any system details, our first port of call is invariably the registry. Within its hives is hidden a wealth of useful (and useless) information.

Amazingly, our first attempts to find a reference to a MAC address in this mysterious world proved fruitless, but eventually we found it in the following rather unlikely location: HKLM\SOFTWARE\Description\Microsoft\Rpc\UuidTempo-raryData.

This key is generated when RPC is installed, and as RPC* is required by NT for many of its network functions, this key will always exist if a network protocol has been installed on a workstation.

Using Regedt32 (see Chapter 2, Running a Script Without User Intervention, for details on registry editors), you select the HKEY_LOCAL_MACHINE window and traverse your way through the hierarchy until you reach the key. The value name we are interested in is NetworkAddress and the editor conveniently displays its actual value as six pairs of characters. This then is the MAC address for the local machine.

We can also use Regedt32 to access the registry on a remote machine. To do this, select Registry → Select Computer from the main menu bar and either enter the name of the machine in the dialog box that appears or select it from the accompanying domain/machine list. Having done this, the two primary hives from the remote machine are loaded, one of which is the HKEY_LOCAL_MACHINE. Simply traverse through the registry hive, as before, until you find the MAC address.

There is a faster way to extract remote MAC addresses and that, unsurprisingly, is to use a Perl script. The following script does the same as the preceding but with a lot less hassle. It makes use of the Perl registry module as described in the Appendix, Perl Module Functions. It takes a text file as its only parameter (the file format is the same as the one used with gomac.pl) and outputs its results to a second text file. The script is run from the command line in the following way:

C:\>remotemac.pl macadds.txt

The script is quite straightforward in its operation. It starts by opening the file containing the machine names whose MAC addresses we require as well as the eventual output file addresses.mac. It then steps through the file trying to connect to each machine in turn, returning a pointer to the now open remote hive HKEY_LOCAL_MACHINE.

# remotemac.pl
use Win32::Registry;

$filename = shift @ARGV;
open(NETNAMES,$filename);
open(MACADDS,">addresses.mac");

$filename = shift @ARGV;
open(NETNAMES,$filename);
open(MACADDS,">addresses.mac");

MACHINE: while ($remotemachine = <NETNAMES>)
{
    chomp($remotemachine);
    unless (Win32::Registry::RegConnectRegistry
            ($remotemachine, HKEY_LOCAL_MACHINE, $RemoteHandle))
     {
        print "Cannot connect to $remotemachine\n";
        next MACHINE;
     }
    print "\nRemote Registry accessed on - $remotemachine\n";

Once it has successfully connected to a remote machine, it attempts to open the appropriate key on that machine.

$key = 'SOFTWARE\Description\Microsoft\Rpc\UuidTemporaryData';
unless(Win32::Registry::RegOpenKeyEx
            ($RemoteHandle,$key,NULL,KEY_ALL_ACCESS,$RegHandle))
     {
        Win32::Registry::RegCloseKey ($RemoteHandle);
        print "Unable to retrieve values\n";
        next MACHINE;
     }

If the key on the remote machine is successfully opened, the RegEnumValue function is used to fill up a hash table (%values) with the name, type, and data of the key's values. $index determines which particular value should be retrieved from the key; it is incremented every time RegEnumValue is called. When the value of $index exceeds the number of values in the key, the function returns false and the loop exits. Once this has happened, we close the open keys.

$index=0;
    while(Win32::Registry::RegEnumValue
                 ($RegHandle,$index++,$VName,NULL,$VType,$VData))
    {
    $values {$VName} = [$VName, $VType, $VData];
    }

    Win32::Registry::RegCloseKey($RegHandle);
    Win32::Registry::RegCloseKey($RemoteHandle);

The script now steps through the resulting hash table checking for the hash key NetworkAddress. If it finds it, it grabs the value, which is stored in the third column of the table. Unlike the other ways of extracting a MAC address we describe in this chapter, reading this registry returns a real hex value, rather than a string representing it. If this value were to be interpreted as text, a series of nonsensical characters would be displayed. Therefore, in the following fragment of script, Perl's unpack function is used to convert the hex to the corresponding string. The format control string H12 tells the unpack function that the data being passed to it should be interpreted as 12 hexadecimal values (6 bytes worth) starting with the highest nybble.*

foreach (keys (%values))
     {
        if ($_ eq "NetworkAddress")
        {
            $MACaddress = unpack "H12",$values{$_}[2];
            print "MAC address is: $MACaddress\n";
            print MACADDS "$remotemachine-$MACaddress\n";
        }
     }
}

Finally, both open files are closed.

close(MACADDS);
close(NETNAMES);

Summary of capability

Using the registry technique has the following capabilities:

  • Shows MAC address
  • Facilitates local extraction of MAC address through scripts
  • Allows for remote extraction of MAC address
  • Does not require network protocols to be running for successful extraction
  • Unlike getmac.exe, it's free!

Using ARP Tables

A major disadvantage of all the methods for retrieving MAC addresses we have described so far is that they either require someone actually to visit every computer or else that Windows NT be installed on each one. While this will not be a problem in many circumstances, it would be useful to retrieve a MAC address from a remote network card without relying on OS-specific functionality. On any IP-based network, the obvious answer would be to use an ARP cache (the table described at the start of the chapter that stores IP-to-MAC address mappings). All that is required for this is a method of working out an IP address from a hostname, a way of filling the ARP table with the relevant data, and the means to read the contents. All of these steps are possible using the standard utilities ping.exe and arp.exe.

ping.exe, included as a standard part of Windows NT, is an invaluable utility for sending Internet Control Message Protocol (ICMP) echo requests, thereby checking whether an IP-enabled machine is working. The most basic usage of the utility would be to type something like ping bill.coolbook at the command line. The output of this might look a lot like this:

Pinging bill.coolbook.ora.com [123.345.567.8] with 32 bytes of data:

Reply from 123.345.567.8: bytes=32 time<10ms TTL=127
Reply from 123.345.567.8: bytes=32 time<10ms TTL=127
Reply from 123.345.567.8: bytes=32 time<10ms TTL=127
Reply from 123.345.567.8: bytes=32 time<10ms TTL=127

This is telling us that 32 bytes of data were received in fewer than 10 milliseconds and that the Time To Live is 127 hops. For the present concerns, however, we are interested in only one bit of information—the IP address, which we need to query the ARP table. You will note that ping gives us this information no less than four times! We could have got the same information by using nslookup.exe, a utility that queries a Domain Name System for hostname-to-IP mapping. The command nslookup bill.coolbook would return something like this:

Server:  dns-server.ora.com
Address:  123.345.567.1

Name:    bill.coolbook.ora.com
Address:  123.345.567.8

This is telling us that the resolver on our system (the network software that deals with converting hostnames to IP addresses) is using dns-server.ora.com as its default name server, whose IP address is 123.345.567.1; this server reckons that the IP address associated with the name bill.coolbook.ora.com is 123.345.567.8. The advantage of nslookup over ping is that nslookup does not rely on bill.coolbook being switched on; the information comes from a DNS server, which should always be working. However, to use nslookup instead of ping is to overlook a vital, if not immediately obvious, function of ping: it solves the second of our problems by adding a dynamic entry to the ARP table containing an IP-to-MAC mapping for the host that has been pinged. This means that when we read the contents of the ARP table, we can be confident that the MAC address we need will be displayed.

arp.exe, included with Windows NT, is an implementation of a standard utility for querying the contents of an ARP table. It has several switches that can be used to selectively view or even modify the table.* Here, we are interested in only one format of the command, arp -a, whose output looks something like this:

Interface: 123.345.567.343 on Interface 2
  Internet Address      Physical Address      Type
  123.345.567.20        00-60-b0-c1-e2-81     dynamic
  123.345.567.62        08-00-02-05-cb-95     dynamic
  123.345.567.79        00-20-af-ee-aa-9d     dynamic
  123.345.567.1         00-60-b0-c1-e4-33     static

This is telling us that the ARP table is associated with the physical interface 2 on the local machine, whose associated IP address is 123.345.567.343. There are four entries in the table, each corresponding to a different machine. To look up a MAC address, we simply search through the table for the IP address that we are interested in and read it off. The first three entries in the preceding example are dynamic, meaning that they were cached automatically by the computer when it first contacted the hosts, and the entries will eventually expire. By contrast, the final one is a static entry: it was deliberately placed in the table (with the command arp -s 123.345.567.1 00-60-b0-c1-e4-33) and will remain there until explicitly deleted or until the workstation is rebooted.

As you can see, a combination of ping and arp provides a very powerful way of finding out a machine's MAC address, as it does not matter what OS the computer is running (provided it has a working TCP/IP stack) and uses completely standard protocols to grab the information. There is, however, one important caveat: as we have already mentioned, it can be used only on machines on the same subnet; however many machines you ping from outside the subnet, you will get only one entry in the ARP table, namely the one corresponding to your router!

Summary of capability

A combination of ping and arp when used to extract a MAC address has the following capabilities:

  • Shows MAC address
  • Allows for remote extraction of MAC address
  • Does not rely on Windows NT specifics

images

One final thought: Every implementation of arp that we have come across has a syntax arp hostname which returns the MAC address associated with a particular hostname or IP address. Given the obvious utility of such a feature, and the comparative simplicity of implementing it, why doesn't the NT version of arp support this?

Compiling a Lookup Table

By now you should have gotten the idea that there are plenty of methods and tools available to extract the MAC address from a computer, either at its console or remotely. To end this chapter, we will show you how to employ this knowledge to write a Perl script that compiles a text file containing all the information that is needed to associate a particular piece of hardware with its network identity: each line in this file will consist of a fully qualified hostname (e.g., bill.coolbook.ora.com), an IP address, and, of course, the MAC address. The script will take as input a file containing a list of hostnames. It will use ping.exe to deduce the IP address from hostname and arp.exe to get the MAC address, both of which it will write to a file called address.mac. The script is interspersed with explanations; we include in considerable detail because it is this script that we use to generate the information file used for all of our machine-specific configuration tasks. It is invaluable, particularly for reidentifying disk-image cloned workstations.

First, we get handles to two files, one containing a line-feed-separated list of hosts we want to clone and one we will use to write the output:

#Hostname, IP, and MAC resolver

$filename = "macsource.txt";
$outfilename = "address.mac";
$suffix = ".jesus.cam.ac.uk";
open(SOURCEFILE, $filename);
open(OUTPUTFILE, ">$outfilename");

Following is the main loop of the script. As you can see in this construct, Perl provides a very neat, simple syntax for reading in lines of text files. Every time the script loops around, a new line from SOURCEFILE is assigned to the variable $line; when there are no more lines left (i.e., when we reach the end of the file), the expression evaluates to false, and the loop ends.

while($line = <SOURCEFILE>)
{
    chomp($line);

The preceding line simply gets rid of the carriage-return character. It's later that the fun begins. First, we run ping and grab its output. (The -n 1 isnt strictly necessary—it just says, “Don't bother to send the normal three ICMP echo requests; one will suffice.”)

$res = ‘ping -n 1 $line$suffix‘;

Now we use a regular expression to split the line into chunks, using [ or ] as delimiters, saving each chunk in an array. A quick glance at the earlier sample ping.exe output should tell you that we will get three chunks and that the second one will contain the IP address we're after. But just for fun, we check each chunk against another regular expression to see whether it really is an IP address. If it is, we assign it to $ip_address.

@bits = split(/[\[\]]/, $res);
foreach $bit(@bits)
{
    if($bit =~ /^\d+(\.\d+){3}/)
    {
        $ip_address = $bit;
    }
}

And now that we have an IP address, we can use it to look up a MAC address. Basically the code here is pretty similar to what we used before: we run arp -a and search through it with a couple of regular expressions. However, the goal is slightly different here: whereas before we were checking the output for something that looked like an IP address, here we are testing for a specific IP address (namely the one that we grabbed from the ping output). Because of this, rather than splitting the output of arp.exe with a specific delimiter, we simply allow each line of output to fill one array element. Each line can then be checked to see if it contains $ip_address.

@res = ‘arp -a‘;
    foreach $resline(@res)
    {
        if($resline =~ /.*$ip_address.*/)
        {

Once we've found the line we are looking for, we can split off the MAC address and compile a string ($output) containing a comma-separated list of hostname, IP address, and MAC address. For the sake of variety, we refer to the MAC address by its location in the array this time rather than looping through each element looking for it with a regular expression.

@bits = split(/\s{2,}/, $resline);
$output = "$line,$ip_address,@bits[2]\n";

Finally, we output a line to the output file and print it on screen for good measure! And, as a final touch, we close the file handles.

print OUTPUTFILE $output;
print "line: $output";
}
    }

}
close SOURCEFILE;
close OUTPUTFILE;

Summary

In this chapter we have presented several different ways of retrieving MAC addresses from a computer. The final script, which we have discussed in detail, uses standard TCP/IP utilities to compile a lookup table that can be used to identify uniquely a piece of hardware. In the following chapters we discuss how to incorporate the output of the script into a larger mechanism for performing dramatic machine-specific configuration. Namely, we will completely change the network identity and credentials of a workstation.

*The computer name, hostname, and significance for Windows networking (NetBIOS) will be discussed in much more detail in the next chapter.

*We will make use of ARP tables slightly later in the chapter, when we deliberately fill them up and use them to read off a list of MAC addresses for each machine on a subnet.

*For this sort of scripting, regular expressions are invaluable! For the total regular expression experience, read Mastering Regular Expressions by Jeffrey E. F. Friedl, (O'Reilly & Associates, 1997).

*Clearly, in many environments it will not be desirable to allow guest access to your workstations.

*Presumably the programmers who wrote the RPC-related code thought (and they were right) that the myriad of structures required to prize the MAC address from the API were far too much of a hassle to actually use! So they found it out once and wrote to this obscure registry key. But that's just our theory.

*Note that capitalization is extremely important here: the control string “h12” would tell unpack to interpret the data starting with the lowest nybble. The result would be a bogus MAC address.

*For an extensive discussion, see Windows NT TCP/IP Network Administration by Craig Hunt and Robert Bruce Thompson (O'Reilly & Associates, 1998).

If a computer has more than one NIC, a separate ARP cache is kept for each one.

This restriction can be circumvented easily provided you have access to a machine on each subnet that is running a Telnet daemon. As arp is a standard utility, this machine, of course, can be running either NT or Unix.

Get Windows NT Workstation: Configuration and Maintenance now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.