Monday, June 9, 2008

Using a script to add a Windows printer form

A client of mine uses a custom paper size for plotting architectural proofs. The plotters themselves support the paper size, but the client also needs to plot to that size when creating a PDF. Since PDF printers are local devices, they needed to add the form to each workstation.

Since there seems to be very little on the net regarding how to manipulate printer forms, here's how:



1. Download and install the Windows 2003 Resource Kit Tools.

2. Distribute prnadmin.dll from the kit. The default location is C:\Program Files\Windows Resource Kits\Tools. I set up the network logon script to copy the file to C:\bin on each workstation.

3. Install the font. I used the following logon script (forgive the mangled formatting).

'----------------------------
' InstallForm15x21.vbs
' by Craig Putnam
' 20080609
' Installs a custom 15x21 printer form on workstations.
' This script requires prnadmin.dll to be present in the netlogon bin folder.
'----------------------------

option explicit

const FORM_NAME = "15x21"
const FORM_HEIGHT = 21
const FORM_WIDTH = 15

const INCHES = 25400

dim shell : set shell = createObject("Wscript.Shell")
dim master : set master = quietCreateObject("PrintMaster.PrintMaster.1")
if master is nothing then
runProgram("regsvr32 /s c:\bin\prnadmin.dll")
set master = createObject("PrintMaster.PrintMaster.1")
end if

if not formExists(FORM_NAME) then
createForm FORM_NAME, FORM_HEIGHT, FORM_WIDTH, INCHES
end if

'----------------------------
' Creates a printer form on this computer.
'
' Inputs: name: string: The name of the form.
' width: The width of the form.
' height: The height of the form.
' unit: The unit conversion factor. The base unit for form size is
' microns (1/1,000,000 of a meter).
'----------------------------
sub createForm(name,width,height,unit)
dim heightInMicrons, widthInMicrons
heightInMicrons = height * unit
widthInMicrons = width * unit
dim form : set form = createObject("Form.Form.1")
form.name = name
form.setSize heightInMicrons, widthInMicrons
form.setImageableArea 0, 0, heightInMicrons, widthInMicrons
master.formAdd form
set form = nothing
end sub

'----------------------------
' Determines whether a printer form of the given name exists on this computer.
'
' Inputs: name: string: The name of the form.
' Returns: boolean: Whether a form of the give name exists on this computer.
'----------------------------
function formExists(name)
dim form
for each form in master.forms
if form.name = name then
formExists = true
exit function
end if
next
formExists = false
end function

'----------------------------
' Creates an object without generating an error if creation fails.
'
' Inputs: name: string: The name of the object.
' Returns: The object, or the nothing value if creation fails.
'----------------------------
function quietCreateObject(name)
on error resume next

set quietCreateObject = createObject(name)
if err <> 0 then
err.clear
set quietCreateObject = nothing
end if
end function

'----------------------------
' Runs the given program. Directs all output to the log file. Note that if
' the program requires input from stdin, it will hang.
'
' Inputs: program: string: The program to run. This must be an executable
' that Windows can natively execute.
'----------------------------
function runProgram(program)
on error resume next

dim process : set process = shell.exec(program)
while process.status = 0
wscript.echo process.stdout.readAll
wscript.echo process.stderr.readAll
wscript.sleep 2
wend
wscript.echo process.stdout.readAll
wscript.echo process.stderr.readAll

set process = null
end function

'--------
' End of script InstallForm15x21.vbs
'--------

If you want to use the script-as is, replace FORM_NAME, FORM_WIDTH, and FORM_HEIGHT with the appropriate values. Windows measures forms in millionths of a meter, thus the inches conversion factor. If you want to customize the script, the resource kit includes decent documentation in prnadmin.doc.

Saturday, May 3, 2008

The Case of the Mysterious Underscore

Why does this happen?

>dir /b *_*

Foo+.pdf
Some File _Some City_.pdf

>

Highly weird. I'm pretty sure that the second file doesn't have any underscores.

Microsoft's excellent command-line reference provides an explanation.

>dir /x *_*

05/03/2008 09:32 PM 1234 FOO_~1.PDF Foo+.pdf
05/03/2008 09:28 PM 1234 SOMEFI~1.PDF Some File _Some City_.pdf
...


>

Friday, February 8, 2008

Recovering from a corrupt Exchange data store (the easy way)

Say you have an Exchange server go down and, for some reason, you don't have backups of the mail store. Say further that you can't remount the store. There are many articles from Microsoft and others on magic tricks you can do to fix a glocked store, but it turns out that there's a very easy method that should work nicely with smaller organizations.

In this scenario, Exchange has been completely destroyed and must be rebuilt from scratch.

By default, Outlook uses cached mode for Exchange accounts. All of each user's data is stored on their local computer, in (hopefully) perfect condition. With Exchange offline, go to each user's workstation, fire up Outlook, and export the user's mailbox to a PST. Then bring up Exchange and mount an empty private data store. For each user, delete and recreate their email profile, connect to their Exchange account, and use Outlook to re-import their data from the aforementioned PST. Email has now been recovered.

Obviously this doesn't work for public data stores. But most people are just interested in their emails, contacts, and calendar, so taking care of those will give you breathing room to work on fixing the public store.

As to how I came to offer this tip, I don't want to talk about it. :)

Friday, February 1, 2008

Replication on a StoreVault S500

I purchased a pair of NetApp StoreVault S500 SAN units for a client recently. The client has a fairly large volume of data for a small business - about a terabyte's worth of images, which they expect to double in a year. One of the things they wanted to do is make an offsite backup of the data. I tried for about two years to do ad-hoc backup over the Internet, but it never worked well. NetApp offers closely-integrated replication as part of the StoreVault package, and the price is about half what you'd pay for EMC/Dell, so we went for it.

Detail the first: The S500 comes with a dozen 500GB hard drives, giving you 6 trillion bytes of raw storage. That's 5.457 terabytes. (If you're replicating the entire unit, you need a second S500, giving a total of 12,000,000,000,000 raw bytes, but moving on...) It uses a dual-parity variant of RAID 4, which eats a trillion bytes. That's now 4.547 TB. (I chose not to reserve a hot spare given the fact that the entire system is going to be replicated.) It uses a checksum scheme that stores parity on every ninth sector, which uses up, um, 1/9th of the storage. That's now 4.04TB. NetApp packs breakfast food into their file system, called WAFL, which apparently reduces fragmentation and has 10% overhead. That's now 3.64TB. One of the big features of StoreVaults is file system snapshots, which are a prerequisite for replication. Snapshot overhead is supposedly variable, but I haven't worked out how to use anything other than the default 20%. That's now 2.91TB. Oh, there's about 5% overhead for managing RAID. That's now 2.76TB.

All of this is documented in a hard-to-find whitepaper (I had to log into the reseller site to get it). What isn't documented is the massive 700GB or so you have to set aside in order to successfully set up replication. Why? Just in case you need the room, as far as I can tell. Actual space that you can see from an initiator is about 2TB. That's two extremely well-protected, massively redundant terabytes.

By way of disclaimer, I'm bad at math, but not as bad as NetApp's marketers.

Detail the second: The StoreVault Manager software is a "simplified" user interface for the StoreVault. By simplified, NetApp means buggy and unresponsive. The UI appears to be based on some kind of Web front-end pretending to be a Windows program, so there are little oddities such as not being able to click on things that should be clickable, and response times measured in seconds for almost every action. I don't know why anyone thinks that HTML / JavaScript / Ajax / whatever make good general-purpose UI's. As far as the simplification goes, I found myself repeatedly telnetting into the unit to fix problems that I couldn't resolve with the UI.

Detail the third: Initial configuration is easy, as is setting up LUNs. It takes a little fiddling to associate specific LUNs with specific initiators, but it's fairly well documented. Here is what isn't documented: in order to successfully set up LUN replication, your DNS configuration needs to be perfect. If there is a configuration problem, StoreVault Manager will offer highly detailed error messages like, "Permission denied."

When you set up a replica (and presumably whenever a replication occurs) the target unit verifies that the source unit is who it says it is by performing a reverse DNS lookup. If the source unit's PTR record doesn't match, the replication fails. Fair enough. Weirdly, the PC on which StoreVault Manager is running must be able to resolve the target unit's unqualified host name in order to perform any operations on a replica once it has been established. That could mean editing the PC's hosts file.

Oh, yes, StoreVault tech support loves to edit the /etc/hosts files on the S500's.

Detail the fourth: If you ever have to reset the StoreVault to factory defaults, be aware that you may need to manually delete the LUNs. Oh, and have a null modem cable handy, since in my case the StoreVault wasn't accessible over the network until I applied some initial settings through the serial console. Port settings are 9600-8N1-None, since that isn't documented either.

Despite the above, the StoreVault S500 seems to work.

Thursday, January 24, 2008

WMI through firewalls

Don't you love undocumented details? Take, for instance, the Windows Management Instrumentation service. This lets you query and control nearly any aspect of a Windows system. For instance, this morning I wanted to query the registry from VBScript. The line to get an object to query the registry goes something like this:

set reg = getobject("winmgmts:\\" & computerName & "\root\default:StdRegProv")

In most examples on the Web, computerName ends up being ".", the current computer. This works very well. But I didn't really want to query the registry on my own computer. I wanted to query the registry on 50 of a client's computers to audit the installation of a particular software package. Every workstation that I queried came back with error 462: "The remote server machine does not exist or is unavailable." Oddly, I could successfully connect to the registries on Windows 2003 and Windows 2000 Server systems.

Windows XP includes a command-line utility called reg which can query the registry on remote computers. It worked on all of the workstations. Why did it work when the line of script does not?

The reg utility does not use WMI. It opens a pipe called \\computerName\IPC$\winreg, then gets a query object from that pipe. All of the workstations run Windows XP Pro SP2 or Windows Vista Business with the firewall enabled. I have the firewall set to enable file sharing on all of the workstations, so the reg utility worked fine.

After a few hours of research, I found an MSDN Technet article entitled Enable or Disable the Remote Administration Exception which lists the following command:


netsh firewall set service type = remoteadmin mode = enable

Run this on each workstation, and it allows WMI to work through the firewall. You can also set a group policy. Open the appropriate policy and go to Computer Configuration/Administrative Templates/Network/Network Connections/Windows Firewall/Domain Profile. Open the properties for Windows Firewall: Allow remote administration exception and choose Enabled.

And that is today's implementation detail.