This article describes a method for administering printer settings from within a C#/.NET console application. The method uses COM to call methods within prnadmin.dll, supplied with the Windows 2003 Server Resource Kit. Using prnadmin.dll, you can add/remove printers, set printer ports, set the default printer, and print a test page. The methods described here can also be embedded in class libraries, Windows Forms applications, or Web applications wherever prnadmin.dll has been installed and registered. The demonstration program supplied with this article will accept a printer name, a file name, and a printer driver name (for new printers) and set the default printer. It shows how the prnadmin.dll can be used for a "Direct to Postscript" printing capability in your C#/.NET applications.
If your system does not already have prnadmin.dll installed, it can be installed as part of the Windows 2003 Server Resource Kit available from Microsoft. By default, it gets installed to the folder C:\Program Files\Windows Resource Kits\Tools. prnadmin.dll exposes standard COM methods. In order to use prnadmin.dll with a COM client, it is required to run regsrv32.exe against it to register it as a local COM server. This program is usually in C:\Windows\System32, so make sure that is in your path.
C:Tools> regsvr32.exe prnadmin.dll
A dialog box will display a brief message letting you know success or failure. If regsvr32.exe is not run against prnadmin.dll, then an attempt to run a COM client application against it will fail with COM error 80040154.
In order to use a COM component from within a .NET application, interop assemblies must be used. An interop assembly contains type metadata needed by .NET to describe COM types. (i.e. You can't just browse for and add the native prnadmin.dll in "Add Reference" in Visual Studio.) prnadmin.dll does not come with interop assemblies, but you can make them yourself using the tlbimp.exe command. If you have Visual Studio 2005 installed, you may find this command in C:\Program Files\Microsoft Visual Studio 8\SDK\v2.0\Bin, so make sure this folder is in your path. (Tlbimp.exe is also distributed with many other .NET related packages and SDKs.)
C:Tools> tlbimp.exe prnadmin.dll
This command will create a file in the current folder called PRNADMINLib.dll containing the interop assembly. This is the DLL file that you can add to a .NET project. (Note: A reference to PRNADMINLib.dll is included in the example code, but it will most likely have to be updated on your system to reflect your installation paths.)
Finally, the Windows 2003 Server Resource Kit comes with very useful documentation for prnadmin.dll. It is targeted for Visual Basic, but the transliteration to C# is straightforward.
Using the Code
The example program given here sets the default printer to one that prints to a file on the local system. It takes three optional arguments: printer name, full file name, and printer driver name; and there are hardcoded defaults for each. If the given printer name does not exist, it is created first and then set as the default. The given driver name only has an effect when creating a new printer, it does not change the driver of an existing printer.
PrinterExample.exe [printerName [filename [drivername]]]
The default printer name is "TESTPRINTER", and the default filename is @"C:\postscript.ps", and the default printer driver is "HP DeskJet 1200C/PS", which produces color postscript. Postscript drivers are fairly common; if you want to print to postscript you should be able to find one on your system by opening the printer properties dialog for an existing printer on your system and picking from among the choices in the driver drop down menus in the "Add a Printer Driver" wizard. (Fortunately, you usually only need to specify a driver by name and Windows takes care of finding the correct driver in the driver cache.)
In the example program, we first obtain a reference to the
PrintMaster interface. This is the main
interface for dealing with printer objects.
PRNADMINLib.PrintMaster pMaster = new PRNADMINLib.PrintMasterClass();
When printing to a file, a special object called a port needs to be created to which a printer can send data. The port can be configured in turn to send its data to a file on the local system. Some checking is done to ensure that a full pathname is used for the port filename, and next, a check is made to see if a port exists on the system corresponding to the given file name. If the port does not exist, it is created. If the port does exist, then it is reconfigured. The method used in the example just loops over an enumeration of ports from the
PRNADMINLib.Port pPort = null;
PRNADMINLib.PortCollection portColl =
IEnumerator portEnumerator = portColl.GetEnumerator();
PRNADMINLib.Port portCurrent = portEnumerator.Current as PRNADMINLib.Port;
if (portCurrent.PortName == portfileName)
pPort = portCurrent;
if (pPort == null)
pPort = new PRNADMINLib.PortClass();
pPort.PortName = portfileName;
pPort.PortType = 3; pMaster.PortAdd(pPort);
The existence of the given printer is checked in the same way.
PRNADMINLib.Printer pPrinter = null;
PRNADMINLib.PrinterCollection prnColl =
IEnumerator prnEnumerator = prnColl.GetEnumerator();
PRNADMINLib.Printer prnCurrent =
prnEnumerator.Current as PRNADMINLib.Printer;
if (prnCurrent.PrinterName == printerName)
pPrinter = prnCurrent;
If the printer does not exist, then it is created with the given driver and the port from above. The three parameters
port are the minimum required. If the printer does exist, then its configuration is altered to reflect the given port file and driver.
if (pPrinter == null)
pPrinter = new PRNADMINLib.PrinterClass();
pPrinter.PrinterName = printerName;
pPrinter.DriverName = newPrinterDriver;
pPrinter.PortName = pPort.PortName;
if (pPrinter.PortName != pPort.PortName)
pPrinter.PortName = pPort.PortName;
if (args.Length == 3 && newPrinterDriver != pPrinter.DriverName)
Console.WriteLine("Warning: Driver remains set to " + pPrinter.DriverName);
This code can throw an exception if the given driver is not found on the system, but it is not checked in the example.
Finally, the printer is set to default and a test page is printed to the configured file.
pMaster.DefaultPrinter = pPrinter.PrinterName;
You can verify this by checking into the Printers and Faxes folder of the Control Panel, and checking the properties on the default printer.
Points of Interest
In the example program, a Postscript printer driver is used. The example code was tested against the color "HP DeskJet 1200C/PS" driver and the black & white "HP LaserJet 2100 Series PS", but any postscript driver should work here. Postscript is a format almost universally understood by printers, but it is dated. To get PDF, one can use the freeware program Ghostscript available from the University of Wisconsin at Madison. The author has successfully chained the output of one of the above postscript drivers to an external process running gsview32c.exe to create PDF in one step. Ghostview is doubly nice because it can also merge PDFs. The command looks like this:
> gswin32c.exe -dNOPAUSE -dQUIET -dBATCH -sDEVICE=pdfwrite
Make sure you keep the capitalizations of the options right. (For those of you running this inside of a spawned process, be aware that messages may be written to stdout or stderr even though the options imply otherwise.)
Alternatively if you can find one, a PDF print driver may be substituted to give your application a direct "Print to PDF" functionality. Unfortunately, the author tried this and failed with two popular "free" PDF writers. In each case, the product installed a "PDF Printer" on the system, and it tried to use the same driver that those printers were configured to use. What happened was that the installed PDF printers were also configured to use special ports, which appear to be responsible for generating pop-up dialogs to adjust PDF generation settings. Without the use of these special ports, PDF generation resulted in output files unreadable by Acrobat reader. There may be a way to use the prnadmin.dll
interface to administer these special ports, but it is beyond the scope of this article.
Care must be taken while synchronizing the print settings with other events in your application. Printing is inherently asynchronous, and often so are the settings. The port attached to the printer is an example of such an asynchronous setting. The obvious implementation strategy for a "Print to Postscript" allowing specification of the output file is to reconfigure the port on demand. Care must be taken to verify the port setting before printing. The author experimented with this and found that if a port file is reconfigured and a test page is printed immediately thereafter, then two output files are generated: one for the previous port in addition to one for the new port setting. It was found that setting the default printer seems to be less prone to synchronization timing issues, but it will still be subject to concurrency issues.
Code similar to that above can be included in .NET applications that process data accessible through other COM objects, like Microsoft Office or Internet Explorer. Command line programs can be created to print Office files or Web sites directly to postscript. Freeware tools such as Ghostscript can then be invoked to convert and/or merge the postscript to PDF.
- 18th March, 2008: Article posted