Getting a List of NT Printers

How do I get a list of printers that are installed on an NT Server using Perl code?
paulcaAsked:
Who is Participating?
 
cadabraConnect With a Mentor Commented:
Hi paulca,

The following code works on my system (windows NT4 server) with activestate perl 522. It relies on the Win32-API package.

Code starts bellow:
================================================================================

#!perl -w
use Win32::API;

# BOOL EnumPrinters(
#   DWORD Flags,         // printer object types
#   LPTSTR Name,         // name of printer object
#   DWORD Level,         // information level
#   LPBYTE pPrinterEnum, // printer information buffer
#   DWORD cbBuf,         // size of printer information buffer
#   LPDWORD pcbNeeded,   // bytes received or required
#   LPDWORD pcReturned   // number of printers enumerated
# );

my $EnumPrinters = new Win32::API("winspool.drv", "EnumPrinters",
['N','P','N','P','N','P','P'], 'I') || die;

my $Flags = 2;  # request local printers.
my $Level = 4; # _PRINTER_INFO_4
my $BuffSize = 0; # initial setting.
my $PinfoBuffer = "\0"; # initial setting
my $PrinterName = "\0";
my $pack_pcbNeeded= pack("L", 0);
my $pack_pcReturned = pack("L", 0);

# First find out how many bytes are going to be returned.
if (defined $EnumPrinters->Call($Flags,$PrinterName,$Level,$PinfoBuffer,
                              $BuffSize,$pack_pcbNeeded,$pack_pcReturned))
{
   $BuffSize = unpack("L", $pack_pcbNeeded);
   $PinfoBuffer = "\0" x $BuffSize;
}
else {
   die $^E;
}

# Now get our printer information.
if ($EnumPrinters->Call($Flags,$PrinterName,$Level,$PinfoBuffer,
                              $BuffSize,$pack_pcbNeeded,$pack_pcReturned))
{

   my $pcReturned = unpack("L", $pack_pcReturned);
   print "NumPrinters=$pcReturned\n";
#   print "RawBuffer=[[$PinfoBuffer]]\n";

   #typedef struct _PRINTER_INFO_4 {
   #    LPTSTR  pPrinterName;
   #    LPTSTR  pServerName;
   #    DWORD  Attributes;
   #} PRINTER_INFO_4;

   # get the length of a /strpointer, strpointer, dword/
   my $atom = length(pack("PPL", 0, 0, 0));

   my $offset = 0;
   for (1..$pcReturned) {
      my $info = substr($PinfoBuffer, $offset, $atom);
      my ($name, $server, $attr) = unpack("P128P128L", $info);
      $offset = $_ * $atom;

        $name =~ s/\0.*$//;
      print "$name";
        $_ = $name;
        if ( /^\\/ )  {  print "\n"; }
        else { print "   <-- local\n"; }
   }
}
else {
   die $^E;
}




0
 
cadabraCommented:
Check out the following package:

Win32::NetResource - manage network resources in perl

This module offers control over the network resources of Win32.Disks, printers etc can be shared over a network. The documentation (NetResource.html) shows how to print a list of shared disks and printers.


The package can be found at http://www.activestate.com/packages/zips/libwin32.zip


Another possible alternative to this would be to try to use Win32-API package to access the enumprinters win32 API calls
0
 
paulcaAuthor Commented:
I am only looking for the printers that are installed on the actual server, not the entire list of printers on the network.  Some of the printers that were listed, were not installed on my server but were shared printers on the network.  Also, I am getting an Access Denied error.  Any insight on that?
0
Get expert help—faster!

Need expert help—fast? Use the Help Bell for personalized assistance getting answers to your important questions.

 
cadabraCommented:
My best bet would be to use the Win32-API package and call the printer enumeration functions, which return structures called PRINTER_INFOn if I remember correctly (I have no access to the documentation right now).
I hope someone else has an easier solution.

What say you ?
Cadabra.
0
 
cadabraCommented:
Code fixed up a bit:
===================================================================================

#!perl -w
use Win32::API;

# BOOL EnumPrinters(
#   DWORD Flags,         // printer object types
#   LPTSTR Name,         // name of printer object
#   DWORD Level,         // information level
#   LPBYTE pPrinterEnum, // printer information buffer
#   DWORD cbBuf,         // size of printer information buffer
#   LPDWORD pcbNeeded,   // bytes received or required
#   LPDWORD pcReturned   // number of printers enumerated
# );

my $EnumPrinters = new Win32::API("winspool.drv", "EnumPrinters",
['N','P','N','P','N','P','P'], 'I') || die;

my $Flags = 2;  # request local printers.
my $Level = 4; # _PRINTER_INFO_4
my $BuffSize = 0; # initial setting.
my $PinfoBuffer = "\0"; # initial setting
my $PrinterName = "\0";
my $pack_pcbNeeded= pack("L", 0);
my $pack_pcReturned = pack("L", 0);

# First find out how many bytes are going to be returned.
if (defined $EnumPrinters->Call($Flags,$PrinterName,$Level,$PinfoBuffer,
                              $BuffSize,$pack_pcbNeeded,$pack_pcReturned))
{
   $BuffSize = unpack("L", $pack_pcbNeeded);
   $PinfoBuffer = "\0" x $BuffSize;
}
else {
   die $^E;
}

# Now get our printer information.
if ($EnumPrinters->Call($Flags,$PrinterName,$Level,$PinfoBuffer,
                              $BuffSize,$pack_pcbNeeded,$pack_pcReturned))
{

   my $pcReturned = unpack("L", $pack_pcReturned);
   print "NumPrinters=$pcReturned\n";
#   print "RawBuffer=[[$PinfoBuffer]]\n";

   #typedef struct _PRINTER_INFO_4 {
   #    LPTSTR  pPrinterName;
   #    LPTSTR  pServerName;
   #    DWORD  Attributes;
   #} PRINTER_INFO_4;

   # get the length of a /strpointer, strpointer, dword/
   my $atom = length(pack("PPL", 0, 0, 0));

   my $offset = 0;
   for (1..$pcReturned) {
      my $info = substr($PinfoBuffer, $offset, $atom);
      my($name, $server, $attr) = unpack("ppL", $info);
      $offset = $_ * $atom;

      print "$name";
      $_ = $name;
      if ( /^\\/ ) { print "\n"; } else { print "  <-- local\n"; }
   }
}
else {
   die $^E;
}



0
 
paulcaAuthor Commented:
I need to get info from PRINTER_INFO_2.  I completed the revision but I would like to double check.  Do you have the revised code for PRINTER_INFO_2.
0
 
cadabraCommented:
I don't have code for PRINTER_INFO_2. (I didn't have code for PRINTER_INFO_4 before either).

I just had a look at the PRINTER_INFO_2 structure, and PHEW thats one long structure.

If you like, you can post the code you wrote and I will double check it for you.
0
 
paulcaAuthor Commented:
  #typedef struct _PRINTER_INFO_2 {
   # LPTSTR    pServerName;
   # LPTSTR    pPrinterName;
   # LPTSTR    pShareName;
   # LPTSTR    pPortName;
   # LPTSTR    pDriverName;
   # LPTSTR    pComment;
   # LPTSTR    pLocation;
   # LPDEVMODE pDevMode;
   # LPTSTR    pSepFile;
   # LPTSTR    pPrintProcessor;
   # LPTSTR    pDatatype;
   # LPTSTR    pParameters;
   # PSECURITY_DESCRIPTOR   pSecurityDescriptor;
   # DWORD     Attributes;
   # DWORD     Priority;
   # DWORD     DefaultPriority;
   # DWORD     StartTime;
   # DWORD     UntilTime;
   # DWORD     Status;
   # DWORD     cJobs;
   # DWORD     AveragePPM;
   #} PRINTER_INFO_2;

   # get the length of a /strpointer, strpointer, strpointer, dword/
   my $atom = length(pack("PPPPPPPPPPPPPLLLLLLLL", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0));

   my $offset = 0;
   for (1..$pcReturned) {
      my $info = substr($PinfoBuffer, $offset, $atom);
      my($server, $name, $share, $port, $driver, $comment, $location, $devstruct, $sepfile, $printproc, $data, $parameters, $descript, $attr, $priority, $default, $start, $until, $status, $cjobs, $average) = unpack("pppppppppppppLLLLLLL", $info);
      $offset = $_ * $atom;

      print "\nName: $name\nServer: $server\nDriver: $driver\nAttributes: $attr\n";
      $_ = $name;
      if ( /^\\/ ) { print "\n"; } else { print "  <-- local\n"; }
   }
}
else {
   die $^E;
}
0
 
cadabraCommented:
Hi paulca

There was a problem on the line with the unpack command:

Bad:
  unpack("pppppppppppppLLLLLLL", $info)
Good:
  unpack("pppppppppppppLLLLLLLL", $info)


Here is the full code. I also added a check for $server being defined.

I sure am glad you don't have to analyze the devmode structure ...


Cadabra.
=============================================================================================
#!perl -w
use Win32::API;

# BOOL EnumPrinters(
#   DWORD Flags,         // printer object types
#   LPTSTR Name,         // name of printer object
#   DWORD Level,         // information level
#   LPBYTE pPrinterEnum, // printer information buffer
#   DWORD cbBuf,         // size of printer information buffer
#   LPDWORD pcbNeeded,   // bytes received or required
#   LPDWORD pcReturned   // number of printers enumerated
# );

my $EnumPrinters = new Win32::API("winspool.drv", "EnumPrinters",
['N','P','N','P','N','P','P'], 'I') || die;

my $Flags = 2;  # request local printers.
my $Level = 2; # _PRINTER_INFO_2
my $BuffSize = 0; # initial setting.
my $PinfoBuffer = "\0"; # initial setting
my $PrinterName = "\0";
my $pack_pcbNeeded= pack("L", 0);
my $pack_pcReturned = pack("L", 0);

# First find out how many bytes are going to be returned.
if (defined $EnumPrinters->Call($Flags,$PrinterName,$Level,$PinfoBuffer,
                              $BuffSize,$pack_pcbNeeded,$pack_pcReturned))
{
   $BuffSize = unpack("L", $pack_pcbNeeded);
   $PinfoBuffer = "\0" x $BuffSize;
}
else {
   die $^E;
}


# Now get our printer information.
if ($EnumPrinters->Call($Flags,$PrinterName,$Level,$PinfoBuffer,
                              $BuffSize,$pack_pcbNeeded,$pack_pcReturned))
{

   my $pcReturned = unpack("L", $pack_pcReturned);
   print "NumPrinters=$pcReturned\n";
#   print "RawBuffer=[[$PinfoBuffer]]\n";

   #typedef struct _PRINTER_INFO_4 {
   #    LPTSTR  pPrinterName;
   #    LPTSTR  pServerName;
   #    DWORD  Attributes;
   #} PRINTER_INFO_4;


   #typedef struct _PRINTER_INFO_2 {
   # LPTSTR    pServerName;
   # LPTSTR    pPrinterName;
   # LPTSTR    pShareName;
   # LPTSTR    pPortName;
   # LPTSTR    pDriverName;
   # LPTSTR    pComment;
   # LPTSTR    pLocation;
   # LPDEVMODE pDevMode;
   # LPTSTR    pSepFile;
   # LPTSTR    pPrintProcessor;
   # LPTSTR    pDatatype;
   # LPTSTR    pParameters;
   # PSECURITY_DESCRIPTOR   pSecurityDescriptor;
   # DWORD     Attributes;
   # DWORD     Priority;
   # DWORD     DefaultPriority;
   # DWORD     StartTime;
   # DWORD     UntilTime;
   # DWORD     Status;
   # DWORD     cJobs;
   # DWORD     AveragePPM;
   #} PRINTER_INFO_2;

   # get the length of a /13 strpointers, 8 dwords/
   my $atom = length(pack("PPPPPPPPPPPPPLLLLLLLL", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0));

   my $offset = 0;
   for (1..$pcReturned) {
      my $info = substr($PinfoBuffer, $offset, $atom);
      my($server, $name, $share, $port, $driver, $comment, $location, $devstruct, $sepfile, $printproc, $data, $parameters, $descript, $attr, $priority, $default, $start, $until, $status, $cjobs, $average) = unpack("pppppppppppppLLLLLLLL", $info);
      $offset = $_ * $atom;
      if (!defined($server)) { $server = ""; }
      print "\nName: $name\nServer: $server\nDriver: $driver\nAttributes: $attr\n";
      $_ = $name;
      if ( /^\\/ ) { print "\n"; } else { print "  <-- local\n"; }
   }
}
else {
   die $^E;
}  

0
 
paulcaAuthor Commented:
Actually I have may have to analyze the DEVMODE struct.  I might have to pass it over a C++ program.  I'm really looking forward to that.
0
 
cadabraCommented:
I am interested in what context you are using this code, and why you chose perl specifically to this task, if you intend to pass the info to a C++ program.

Care to share ?
Cadabra.
0
 
paulcaAuthor Commented:
I have a web based app in which a client can choose a printer from the server.  I want to pass it the printer properties so that that it can check to make sure that the printer driver supports the selections made in the browser. If it does, it will go ahead and print the document according to the users specifications.
0
 
cadabraCommented:
Does your app currently support printing from the server ?

I once wrote and maintained an app that did batch printing from an NT server (in Delphi / C++) using the win32 api, where users submitted requests for server generated reports through Oracle database pipes. Things to be aware of are (forgive inaccuracies, I am doing this from memory):

1. When printers are offline, especially remote printers, and you try to query them with API (especially queries trying to get the printer state) you may sometimes get hangs (for minutes).
2. Processes trying to print to a problematic / not responding printer could accumulate and burden the system. Some mechanism could be written to queue print requests, and dequeue / print them in backgound so that the process requesting the print does not stall while waiting for acknowledge from printer.
4. Problematic print queues could impact other applications running on the same server, I have seen renegade print queues gobbling up 100% of a servers CPU, bringing all activity to a standstill.
5. If possible, I recommend having a different server handling the printing issues, so if things get stuck there, at least the online application is not stalled.
6. If you are getting all sorts of problems with certain print queues, try to define them in a different manner, TCP/IP printing etc. The technet has a lot of articles about these problems .

I have a bunch of technet articles I used when faced with these problems lying about somewhere. If you like, I can look for them, and send you the article numbers or documents.

Other nice features you can have in a server generated print system, is defining multiple NT queues on the same physical port, with each queue representing a different paper tray, different document defaults etc. Your application can define printer groups, and control printers; pause them, resume them via win32 api etc.
One of my clients requested this feature, and it works well.

Cadabra.
0
 
paulcaAuthor Commented:
Those are some very good points you made and I'll definately look into them.  I would appreciate it if you could send me the article number.  Thanks.
0
 
cadabraCommented:
Hi,

Here are the technet articles which helped solve severe printing
problems at one of my clients. There were around 300 printer queues
setup on the printing machine (NT server), most were remote printers
(over WAN). The most problematic queues were shared printers on user's
win95 workstations (steer clear of this if possible),
next came print-server based printers, and the most reliable printers
were those with network cards installed in them.

Hope this helps, and that you won't need to use the information
bellow :)

Cadabra.






SESSTIMEOUT Information
PSS ID Number: Q102067


HP JetDirect Firmware Versions and Windows NT Protocol Support
PSS ID Number: Q124293


Text of RFC1179 Standard for Windows NT TCP/IP Printing
PSS ID Number: Q124734


Print Job to LPR Printer Does Not Print
PSS ID Number: Q128352


Windows NT LPR Does Not Support Job Removal
PSS ID Number: Q134854


Printing to HP Laserjet Printer Results in Event 2004
PSS ID Number: Q142370


TCP/IP Printing Causes File Cache to Grow
PSS ID Number: Q149658


LPR Printers Show Status Unknown
PSS ID Number: Q163241


Troubleshooting Printing Problems in Windows NT 4.0
PSS ID Number: Q163551


Print Queue May Stop Responding When Running LPR on Server
PSS ID Number: Q167035


Configuring Individual Printers to Passthrough LPR Print Jobs
PSS ID Number: Q168457


How to Modify the TCP/IP Maximum Retransmission Timeout
PSS ID Number: Q170359


Updated TCP/IP Printing Options for Windows NT 4.0 SP3 and Later
PSS ID Number: Q179156

0
All Courses

From novice to tech pro — start learning today.