Thursday, February 23, 2006

simplifying printer installs

Installing printers is terribly boring. As is often the case with Windows, it is possible to automate/script tasks like this, but it is usually complicated and awkward.

There is an arcane rundll32 command which can be used, preceded by some vbscript to create the TCP/IP ports for network printers.

Another possibility is a Microsoft tool that has to be downloaded separately, called Print Migrator. This is what I will try next. I tried this, and indeed, it makes it very easy to replicate printer settings to other machines in the same network. The drawback is that it saves and restores all printers. They cannot be selected individually. The advantage is that it works across operating systems, so it doesn't matter if the machines are Windows 2000, XP, or even NT4. And it couldn't be any easier to use.

For the complicated rundll32 version, an excellent page with many examples, is here. That's where I also found the link to Print Migrator. There may be much more good stuff on that site, but the terrible layout (it even uses frames!) discourages me at the moment, so I will just have to return there later...

To create the TCP/IP ports, I use a VBS script like this one, which takes an IP address as argument:

Set args = WScript.Arguments
If args.Count > 0 then
ip = args.Item(0)
ip = "" 'provide a default IP
End If

reply = MsgBox ("Create JetDirect port on IP " & ip & "?", vbYesNo + vbQuestion, "Creating JetDirect TCP/IP port")

If reply = vbYes Then
Set objWMIService = GetObject("winmgmts:")
Set objNewPort = objWMIService.Get("Win32_TCPIPPrinterPort").SpawnInstance_
objNewPort.Name = "IP_" & ip
objNewPort.Protocol = 1
objNewPort.HostAddress = ip
objNewPort.PortNumber = "9100"
objNewPort.SNMPEnabled = False
MsgBox "Done"
End If

A search on "Win32_TCPIPPrinterPort" will find many other examples. Unfortunately, that doesn't work on Windows 2000, only on XP. For Win2K, you have to create the port(s) manually.

To use rundll32 to add an HP Laserjet 5M printer on a command like this could be used:
rundll32 printui.dll,PrintUIEntry /y /b "HP Laserjet 5M" /z /if /f %windir%\inf\ntprint.inf /r "IP_192.168.1.5" /m "HP Laserjet 5M" /u

However, while it works sometimes, it will give errors at other times when used this way in a single command. Finally, my script which now seems to work reliably uses separate commands:
@rem the first 2 lines might be combined into one, but I don't feel like trying yet another variation
rundll32 printui.dll,PrintUIEntry /m "HP LaserJet 5M" /ia /f %windir%\inf\ntprint.inf /u
rundll32 printui.dll,PrintUIEntry /b "HP LaserJet 5M" /z /if /f %windir%\inf\ntprint.inf /r "IP_192.168.1.5" /m "HP LaserJet 5M" /u
@echo Wait for install to finish, then press any key to set this printer as the default printer
@echo Or press Ctrl-C to leave the default printer as is
@rem This step definitely needs to be done separately
rundll32 printui.dll,PrintUIEntry /y /n "HP LaserJet 5M"

To get a list of all the options, enter rundll32 printui.dll,PrintUIEntry /?

Finally, there is the problem of setting the paper size: I prefer English systems but their print drivers default to a paper size of "Letter". Since I work in Europe, I need A4 paper instead.

First, I quickly hacked together a litle Perl solution. This script changes the default paper size from "Letter" to "A4" in all .GPD driver files it finds:

use File::Find;

find(\&found, $ENV{WINDIR});

sub found {
if (/\.GPD$/i) {
print "Processing $File::Find::name ($_)\n";
undef $/;
open(F,$_) or warn "Cannot open $_\n";
my $filetext = <F>;
close F;
if ( $filetext =~ s/^(\s*\*DefaultOption:\s*)LETTER(.*)/$1.'A4'.$2/gem) {
print "Changing file $_\n";
rename($_, "$_.orig") or warn "Could not rename '$_' to '$_.orig' : $!\n";
open(F, ">$_") or die "Could not open $File::Find::name for writing : $!\n";
print F $filetext;
close F;

However, while this Perl script has worked on one machine, it has not on another. The GPD file was corrected, but there are also registry settings involved, and it sometimes just doesn't work.

After long searches to solve this problem, here is what I found: the paper size is stored in the registry, in a binary structure which is dependent on the printer driver. This means it cannot be edited directly in the registry. This driver structure is partly documented, and can be accessed through the Windows API, but not through VBScript and the WSH.

There is no easy way to change the default paper size!

The only workaround which I found is to use rundll32 again, to save and restore a specific printer's settings. The following command saves my LaserJet 5M settings to the file laserjet-5M-settings.dat:
rundll32 printui.dll,PrintUIEntry /n "HP LaserJet 5M" /Ss /a laserjet-5M-settings.dat

And this command restores the settings:
rundll32 printui.dll,PrintUIEntry /n "HP LaserJet 5M" /Sr /a laserjet-5M-settings.dat d g u