Printers and printer connections have long been a configuration nightmare. Connecting the correct printer requires a complex orchestration of linking each user with their location, and ultimately to a printer and matching driver. You can ease this task somewhat through the use of a centralized print server, a Windows server that houses printer connections along with their associated drivers. Centrally storing those drivers on a single server enables even non-administrator users to attach printers without too much headache.

Yet the ever-increasing mobility of today's workforce means many desktops are being replaced with laptops. Laptops are great for users because they enable them to work anywhere; yet they ramp up the complexity when users are used to an automatically configured printer that's ready for them just after logon.

Logon scripts can accomplish this, although they can be tricky to implement properly. The tricky part lies in the necessary logic that maps user to location to printer. Combining these three elements creates a dizzying range of situations to plan for: A laptop user might have access only to certain printers. That same user might connect from different locations on different days, or even from multiple locations on the same day.

With lots of printers in your directory, automatically mapping one that's physically closest saves time for users. Yet constructing the logic isn't a trivial task. Solving the problem with native Windows tools requires more than just a few clicks of the mouse.

You've got scripting work ahead.

Geolocation by IP Address and Active Directory Site

Before I share some of the script syntax you'll need, its best to consider how the geographic location of people and printers can be logically discovered. One solution leans on Active Directory.

Anyone who's worked with Active Directory knows that AD sites are important for replication and ensuring speedy logins. With an AD domain spanning multiple geographic locations, AD sites and the Domain Controllers within them facilitate authentication and logins for users who are local to that site. Without sites, user logons could get processed on any Domain Controller in the domain or forest, greatly increasing logon time should it occur over the WAN.

Figure 1: Three AD Sites and their IP Address Ranges.

AD sites are centered around ranges of IP addresses, and are linked to specific subnets. In a well-designed infrastructure, those subnets should define a specific geographic location. Figure 1 shows an example of three sites in an AD domain and the three associated subnets ' along with their IP ranges ' that bound each site. Using DHCP, a user's laptop that leaves the Denver site and reconnects in Las Vegas will find its IP address automatically changing to the network.

Problematic here in the non-automated network are the residual printers from that laptop's original site. Someone from Denver who spends a day in Las Vegas surely doesn't want their print jobs sitting on the tray back in Denver. Automation, like what you might get from a logon script, ensures that the print button always maps to a printer that's geographically local.

The linkage between AD site and subnet creates a useful boundary for printers you might want to automatically attach. If, for example, Figure 1's domain hosts printers in each site, it stands to reason that a script or some other automation could connect those printers as users and their laptops move around.

It's important to note that geolocality needn't necessary be this geographic in nature. Rather than defining individual cities, Figure 1's sites could be buildings on a campus or even floors within a building. Your AD site structure is limited only by the imagination of your network design, along with the IP addresses they require for the clients that show up.

Scripting Geolocation

With VBScript, determining what AD site a client has logged into doesn't require a lot of code. To keep things simple, I'll focus exclusively on VBScript in this article. Unfortunately, many logon scripts (and IT pros) still today rely on this aging script language and its cumbersome syntax. You'll see in a minute why the term 'cumbersome' is a fitting descriptor. But first, to determine the current AD site using VBScript, you might use the following:

Set objADSysInfo = CreateObject("ADSystemInfo")
WScript.Echo objADSysInfo.SiteName

The code above delivers that site name to the screen in a dialog box, which is something you wouldn't want to do in a logon script. More useful is using the results you learn from objADSysInfo.SiteName in some kind of logic structure for matching site name to the printer you want to connect.

VBScript is equipped with If-Then as well as Select Case statements, either of which can be used for creating the necessary logic. What's unfortunate is the sheer magnitude of code that becomes necessary as your number of printers grows large. I'll show you that magnitude of code using Select Case structure in what remains of this article. You'll quickly see how unwieldy VBScript gets as that number of printers increases.

To start, here's the basic structure of a Select Case statement that verifies the current site code against the value of objADSysInfo.SiteName:

Select Case objADSysInfo.SiteName
Case "Denver"
Case "Las Vegas"
Case "Ft Lauderdale"
End Select

This is only a framework. Missing are the actions that should occur once the AD site is matched. I introduce the matching part from the action part, because the action can be accomplished using a variety of techniques.

One technique uses the rundll32.exe Windows command. This command is used to invoke an action that's been encoded into a DLL file. The DLL file of interest for connecting printers is printui.dll, and the action to invoke is PrintUIEntry. Both function across Windows XP, Windows Vista, and Windows 7 computers.

Figure 2: Options available for connecting printers with printui.dll.

Invoking rundll32.exe requires some syntax that may be unfamiliar. The code snippet below instructs printui.dll to invoke its PrintUIEntry action, and then supplies the /? switch to request a dialog box (shown in Figure 2) that displays the command's available options. You can run this command right at the command prompt. If you do, notice that there are no spaces around the comma:

rundll32.exe printui.dll,PrintUIEntry /?

With a significant selection of switches available, you're probably wondering which you'll be most interested in. I find three uses of this structure to be most useful, listed here individually:

rundll32.exe printui.dll,PrintUIEntry /in /q /n\\ServerName\PrinterName
rundll32.exe printui.dll,PrintUIEntry /dn /q /n\\ServerName\PrinterName
rundll32.exe printui.dll,PrintUIEntry /y /n\\ServerName\PrinterName

The first line above installs the referenced printer quietly, without prompting the user. This installation assumes that the necessary drivers are already available on the print server identified in the command. The second line quietly deletes the referenced printer, while the third line sets the printer as the default printer.

Using rundll32.exe at the command prompt requires just what you see above. Running it within a VBScript requires a bit of extra syntax. To invoke this command (or, really, any outside executable) you'll need to first create a WScript.Shell object and then invoke its Run method. Using that syntax with the first command above resembles this (the second line is wrapped):

Set WshShell = WScript.CreateObject("WScript.Shell")
WshShell.Run "rundll32.exe printui.dll,PrintUIEntry /in /q /n\\ServerName\PrinterName"

Although this works, perhaps you don't want to lean on an external command for this task. If you don't, there's a second structure built into VBScript for invoking printers. This second structure requires slightly fewer characters to accomplish what is really the same result. It first requires creating a WScript.Network object and then invoking its AddWindowsPrinterConnection method. The syntax looks like this:

Set objNet = CreateObject("WScript.Network")
objNet.AddWindowsPrinterConnection "\\ServerName\PrinterName"

I show you both of these to give you an idea of the options available. The WScript.Network object, while simpler in form, doesn't come equipped with the variety of switches printui.dll exposes (a portion of which you saw in Figure 2). Depending on your needs, however, either can be acceptable.

Wrapping all of this into a final product that you can deploy as a VBScript logon script requires a bit more code. One structure you might want to add first removes the collection of existing printers before adding those appropriate for the current location. This is done with the WScript.Network object's EnumPrinterConnections and RemovePrinterConnection methods. You'll also want to incorporate a Select Case structure to link the current AD site to the right printer. With all the bells and whistles, that final script might look something like this:

Set objADSysInfo = CreateObject("ADSystemInfo")
Set objNet = CreateObject("Wscript.Network")
Set colPrinters = objNet.EnumPrinterConnections
On Error Resume next
For i = 0 to colPrinters.Count - 1 Step 2
objNet.RemovePrinterConnection colPrinters.Item(i),True,True
On Error Goto 0
Select Case objADSysInfo.SiteName
Case "Denver"
objNet.AddWindowsPrinterConnection "\\DEN\DenverPrn"
Case "Las Vegas"
objNet.AddWindowsPrinterConnection "\\LAS\VegasPrn"
Case "Ft Lauderdale"
objNet.AddWindowsPrinterConnection "\\FLL\LauderPrn"
End Select

This code block's middle section requires a bit of extra explanation. Take a look at how every other item in the collection of printers is enumerated and removed in the For-Next loop. This syntax is required due to how EnumPrinterConnections returns the collection, and isn't something you'd know without testing it yourself first.

The returned collection is in fact an array of item pairs, with even-numbered items representing printer ports and odd-numbered items representing printer UNC names. Only one half of each pair is required for the removal to work properly. A second item of note is the surrounding On Error Resume Next and On Error Goto 0 lines. These are added to ensure the script continues to run just in case a printer removal reports an error.

User Location

This script functions well for our imaginary three-site AD domain. That domain only has to worry about a single printer in each site. As you can imagine, the logic structures grow larger and ultimately very unwieldy as the number of printers and sites goes up. Even more structure is required when users are limited to certain printers, certain times of day, or certain sites. Any of these additional requirements require adding further conditional structures to your logon script.

At the end of the day, it is the potential for error and the demands of ongoing maintenance that should have you shuddering. If your requirements are greater than what can be accomplished through something that's simple, consider looking to holistic solutions ' many of which eliminate scripting entirely ' that rein in complexity through better management tools.