Microsoft KB Archive/202480

= HOW TO: Determine Printer Status and Print Job Status from Visual Basic =

Article ID: 202480

Article Last Modified on 7/16/2004

-

APPLIES TO


 * Microsoft Visual Basic 5.0 Enterprise Edition
 * Microsoft Visual Basic 6.0 Enterprise Edition
 * Microsoft Visual Basic 5.0 Professional Edition
 * Microsoft Visual Basic 6.0 Professional Edition
 * Microsoft Visual Basic 4.0 Standard Edition
 * Microsoft Visual Basic 4.0 32-Bit Enterprise Edition
 * Microsoft Visual Basic 4.0 Professional Edition

-



This article was previously published under Q202480



IN THIS TASK
SUMMARY
 * Description of the Technique
 * Step-by-Step Sample
 * Troubleshooting

REFERENCES



SUMMARY
This article describes how to use Windows application programming interface (API) functions in Visual Basic to determine the printer status or the print job status programmatically. Although an application does not typically have to check the status of a printer before printing, it may be useful to determine the status of a printer or print job programmatically.

back to the top

Description of the Technique
The term "printer" can refer to a hardware device, a queue, a driver, or even a port. In this article, the term "printer" is defined as a local print queue. The code sample in this article returns the statuses that the operating system reports. This is the same status that the spooler reports, which you can check by watching the local print queue.

For example, to view the queue on a computer that is running Microsoft Windows 98, click Start, point to Settings, click Printers, and then double-click the icon for the printer whose queue you want to view.

NOTE: You cannot communicate directly with the physical printer. You should not have to do this because the operating system must arbitrate access to the hardware.

The sample code in this article examines the local print queue, which obtains information from the port monitor, which in turn communicates with the physical device. For a more detailed description of how this works, see the articles that are listed in the "References" section of this article.

This sample reports the status from the printer and from the jobs, but note that the job status information is generally more reliable for applications. Ideally, you should examine the jobs and the printer statuses separately, and the code should infer the "meta" status of the queue. However, for most uses, your code can rely on the job statuses. Also, this example loops through the jobs and checks and reports the status of each job. This is because your job may follow another job that is reporting a problem, such as being out of paper or getting jammed.

NOTE: The system only checks the status when the system has a job to spool. Otherwise, the queue is considered "ready" because the queue can accept jobs, even if the hardware is in an error state. For example, if the last job that was printed used the last piece of paper, the operating system does not know this until the system tries to print again.

Additionally, although there are many statuses that may be reported, many are not supported in practice. The printer hardware and the port monitor determine which statuses to report. For example, if the printer is out of paper and offline, the status may be reported as "Printing" because that is what the job is trying to do. Therefore, a queue that displays "Ready" does not guarantee that your print job will complete successfully.

This code sample examines only the local queue, which should suffice for most applications. However, connecting to remote printers can become fairly complex. You can have chained queues, in which the port for the local queue is actually another queue. You can have printer pooling, in which multiple printers work from a common super queue. When the architecture becomes more complex, the code to retrieve a meaningful status also becomes more complex, and the usefulness of the status is reduced.

On Microsoft Windows 95, Microsoft Windows 98, and Microsoft Windows Millennium Edition only, you can also examine the PrinterInfo.Attributes field for the bit PRINTER_ATTRIBUTE_WORK_OFFLINE. This state is something that typically occurs because of user action (for example, if the user right-clicks a printer icon and then clicks Work Offline). This state does not occur because of the state change of a print job, although the operating system can force state if the printer cannot be connected to despool a job. In that case, the status is typically reported as USER_INERVENTION_REQUIRED.

back to the top

Step-by-Step Example
 Create a new Standard EXE project in Visual Basic. By default, Form1 is created.  On the Project menu, click Add Module, and then add the following code: Option Explicit

Public Declare Function lstrcpy Lib "kernel32" _ Alias "lstrcpyA" _ (ByVal lpString1 As String, _  ByVal lpString2 As String) _ As Long

Public Declare Function OpenPrinter Lib "winspool.drv" _ Alias "OpenPrinterA" _ (ByVal pPrinterName As String, _  phPrinter As Long, _   pDefault As PRINTER_DEFAULTS) _ As Long

Public Declare Function GetPrinter Lib "winspool.drv" Alias "GetPrinterA" _ (ByVal hPrinter As Long, _  ByVal Level As Long, _   pPrinter As Byte, _   ByVal cbBuf As Long, _   pcbNeeded As Long) _ As Long

Public Declare Function ClosePrinter Lib "winspool.drv" _ (ByVal hPrinter As Long) _ As Long

Public Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" _ (Destination As Any, _  Source As Any, _   ByVal Length As Long)

Public Declare Function EnumJobs Lib "winspool.drv" Alias "EnumJobsA" _ (ByVal hPrinter As Long, _  ByVal FirstJob As Long, _   ByVal NoJobs As Long, _   ByVal Level As Long, _   pJob As Byte, _   ByVal cdBuf As Long, _   pcbNeeded As Long, _   pcReturned As Long) _ As Long ' constants for PRINTER_DEFAULTS structure Public Const PRINTER_ACCESS_USE = &H8 Public Const PRINTER_ACCESS_ADMINISTER = &H4

' constants for DEVMODE structure Public Const CCHDEVICENAME = 32 Public Const CCHFORMNAME = 32

Public Type PRINTER_DEFAULTS pDatatype As String pDevMode As Long DesiredAccess As Long End Type

Public Type DEVMODE dmDeviceName As String * CCHDEVICENAME dmSpecVersion As Integer dmDriverVersion As Integer dmSize As Integer dmDriverExtra As Integer dmFields As Long dmOrientation As Integer dmPaperSize As Integer dmPaperLength As Integer dmPaperWidth As Integer dmScale As Integer dmCopies As Integer dmDefaultSource As Integer dmPrintQuality As Integer dmColor As Integer dmDuplex As Integer dmYResolution As Integer dmTTOption As Integer dmCollate As Integer dmFormName As String * CCHFORMNAME dmLogPixels As Integer dmBitsPerPel As Long dmPelsWidth As Long dmPelsHeight As Long dmDisplayFlags As Long dmDisplayFrequency As Long End Type

Type SYSTEMTIME wYear As Integer wMonth As Integer wDayOfWeek As Integer wDay As Integer wHour As Integer wMinute As Integer wSecond As Integer wMilliseconds As Integer End Type

Type JOB_INFO_2 JobId As Long pPrinterName As Long pMachineName As Long pUserName As Long pDocument As Long pNotifyName As Long pDatatype As Long pPrintProcessor As Long pParameters As Long pDriverName As Long pDevMode As Long pStatus As Long pSecurityDescriptor As Long Status As Long Priority As Long Position As Long StartTime As Long UntilTime As Long TotalPages As Long Size As Long Submitted As SYSTEMTIME time As Long PagesPrinted As Long End Type

Type PRINTER_INFO_2 pServerName As Long pPrinterName As Long pShareName As Long pPortName As Long pDriverName As Long pComment As Long pLocation As Long pDevMode As Long pSepFile As Long pPrintProcessor As Long pDatatype As Long pParameters As Long pSecurityDescriptor As Long Attributes As Long Priority As Long DefaultPriority As Long StartTime As Long UntilTime As Long Status As Long cJobs As Long AveragePPM As Long End Type

Public Const ERROR_INSUFFICIENT_BUFFER = 122 Public Const PRINTER_STATUS_BUSY = &H200 Public Const PRINTER_STATUS_DOOR_OPEN = &H400000 Public Const PRINTER_STATUS_ERROR = &H2 Public Const PRINTER_STATUS_INITIALIZING = &H8000 Public Const PRINTER_STATUS_IO_ACTIVE = &H100 Public Const PRINTER_STATUS_MANUAL_FEED = &H20 Public Const PRINTER_STATUS_NO_TONER = &H40000 Public Const PRINTER_STATUS_NOT_AVAILABLE = &H1000 Public Const PRINTER_STATUS_OFFLINE = &H80 Public Const PRINTER_STATUS_OUT_OF_MEMORY = &H200000 Public Const PRINTER_STATUS_OUTPUT_BIN_FULL = &H800 Public Const PRINTER_STATUS_PAGE_PUNT = &H80000 Public Const PRINTER_STATUS_PAPER_JAM = &H8 Public Const PRINTER_STATUS_PAPER_OUT = &H10 Public Const PRINTER_STATUS_PAPER_PROBLEM = &H40 Public Const PRINTER_STATUS_PAUSED = &H1 Public Const PRINTER_STATUS_PENDING_DELETION = &H4 Public Const PRINTER_STATUS_PRINTING = &H400 Public Const PRINTER_STATUS_PROCESSING = &H4000 Public Const PRINTER_STATUS_TONER_LOW = &H20000 Public Const PRINTER_STATUS_USER_INTERVENTION = &H100000 Public Const PRINTER_STATUS_WAITING = &H2000 Public Const PRINTER_STATUS_WARMING_UP = &H10000 Public Const JOB_STATUS_PAUSED = &H1 Public Const JOB_STATUS_ERROR = &H2 Public Const JOB_STATUS_DELETING = &H4 Public Const JOB_STATUS_SPOOLING = &H8 Public Const JOB_STATUS_PRINTING = &H10 Public Const JOB_STATUS_OFFLINE = &H20 Public Const JOB_STATUS_PAPEROUT = &H40 Public Const JOB_STATUS_PRINTED = &H80 Public Const JOB_STATUS_DELETED = &H100 Public Const JOB_STATUS_BLOCKED_DEVQ = &H200 Public Const JOB_STATUS_USER_INTERVENTION = &H400 Public Const JOB_STATUS_RESTART = &H800

Public Function GetString(ByVal PtrStr As Long) As String Dim StrBuff As String * 256 'Check for zero address If PtrStr = 0 Then GetString = " " Exit Function End If  'Copy data from PtrStr to buffer. CopyMemory ByVal StrBuff, ByVal PtrStr, 256 'Strip any trailing nulls from string. GetString = StripNulls(StrBuff) End Function

Public Function StripNulls(OriginalStr As String) As String 'Strip any trailing nulls from input string. If (InStr(OriginalStr, Chr(0)) > 0) Then OriginalStr = Left(OriginalStr, InStr(OriginalStr, Chr(0)) - 1) End If

'Return modified string. StripNulls = OriginalStr End Function

Public Function PtrCtoVbString(Add As Long) As String Dim sTemp As String * 512 Dim x As Long

x = lstrcpy(sTemp, Add) If (InStr(1, sTemp, Chr(0)) = 0) Then PtrCtoVbString = "" Else PtrCtoVbString = Left(sTemp, InStr(1, sTemp, Chr(0)) - 1) End If End Function

Public Function CheckPrinterStatus(PI2Status As Long) As String Dim tempStr As String If PI2Status = 0 Then  ' Return "Ready" CheckPrinterStatus = "Printer Status = Ready" & vbCrLf Else tempStr = ""  ' Clear If (PI2Status And PRINTER_STATUS_BUSY) Then tempStr = tempStr & "Busy " End If     If (PI2Status And PRINTER_STATUS_DOOR_OPEN) Then tempStr = tempStr & "Printer Door Open " End If     If (PI2Status And PRINTER_STATUS_ERROR) Then tempStr = tempStr & "Printer Error " End If     If (PI2Status And PRINTER_STATUS_INITIALIZING) Then tempStr = tempStr & "Initializing " End If     If (PI2Status And PRINTER_STATUS_IO_ACTIVE) Then tempStr = tempStr & "I/O Active " End If

If (PI2Status And PRINTER_STATUS_MANUAL_FEED) Then tempStr = tempStr & "Manual Feed " End If     If (PI2Status And PRINTER_STATUS_NO_TONER) Then tempStr = tempStr & "No Toner " End If     If (PI2Status And PRINTER_STATUS_NOT_AVAILABLE) Then tempStr = tempStr & "Not Available " End If     If (PI2Status And PRINTER_STATUS_OFFLINE) Then tempStr = tempStr & "Off Line " End If     If (PI2Status And PRINTER_STATUS_OUT_OF_MEMORY) Then tempStr = tempStr & "Out of Memory " End If     If (PI2Status And PRINTER_STATUS_OUTPUT_BIN_FULL) Then tempStr = tempStr & "Output Bin Full " End If     If (PI2Status And PRINTER_STATUS_PAGE_PUNT) Then tempStr = tempStr & "Page Punt " End If     If (PI2Status And PRINTER_STATUS_PAPER_JAM) Then tempStr = tempStr & "Paper Jam " End If

If (PI2Status And PRINTER_STATUS_PAPER_OUT) Then tempStr = tempStr & "Paper Out " End If     If (PI2Status And PRINTER_STATUS_OUTPUT_BIN_FULL) Then tempStr = tempStr & "Output Bin Full " End If     If (PI2Status And PRINTER_STATUS_PAPER_PROBLEM) Then tempStr = tempStr & "Page Problem " End If     If (PI2Status And PRINTER_STATUS_PAUSED) Then tempStr = tempStr & "Paused " End If

If (PI2Status And PRINTER_STATUS_PENDING_DELETION) Then tempStr = tempStr & "Pending Deletion " End If     If (PI2Status And PRINTER_STATUS_PRINTING) Then tempStr = tempStr & "Printing " End If     If (PI2Status And PRINTER_STATUS_PROCESSING) Then tempStr = tempStr & "Processing " End If     If (PI2Status And PRINTER_STATUS_TONER_LOW) Then tempStr = tempStr & "Toner Low " End If

If (PI2Status And PRINTER_STATUS_USER_INTERVENTION) Then tempStr = tempStr & "User Intervention " End If     If (PI2Status And PRINTER_STATUS_WAITING) Then tempStr = tempStr & "Waiting " End If     If (PI2Status And PRINTER_STATUS_WARMING_UP) Then tempStr = tempStr & "Warming Up " End If     'Did you find a known status? If Len(tempStr) = 0 Then tempStr = "Unknown Status of " & PI2Status End If     'Return the Status CheckPrinterStatus = "Printer Status = " & tempStr & vbCrLf End If End Function  Add three CommandButton controls. Add three TextBox controls to Form1, and then configure the TextBox controls as follows:  Set the MultiLine property of each TextBox control to True. Size each TextBox to approximately five inches wide and three inches long. Set the ScrollBars property of each TextBox control to 2 - Vertical.</li></ol> </li> Add a Timer control to Form1.</li>  Add the following code to the Form's module: Option Explicit

Private Sub Command1_Click 'Enable the timer to begin printer status checks. Timer1.Enabled = True 'Enable and disable start and stop buttons. Command1.Enabled = False Command2.Enabled = True Command3.Enabled = True End Sub

Private Sub Command2_Click 'Disable timer to stop further printer checks. Timer1.Enabled = False 'Enable and disable start and stop buttons. Command1.Enabled = True Command2.Enabled = False Command3.Enabled = True End Sub

Private Sub Command3_Click 'Clear the status info. Text1.Text = "" Text2.Text = "" Text3.Text = "" End Sub

Private Sub Form_Load 'Initialize captions for control buttons. Command1.Caption = "Start" Command2.Caption = "Stop" Command3.Caption = "Clear" 'Clear the status info. Text1.Text = "" Text2.Text = "" Text3.Text = "" Command1.Enabled = True 'Disable stop and clear buttons. Command2.Enabled = False Command3.Enabled = False 'Set interval for printer status checking to 1/2 second. Timer1.Enabled = False Timer1.Interval = 500 End Sub

Private Sub Timer1_Timer Dim PrinterStatus As String Dim JobStatus As String Dim ErrorInfo As String 'Clear the status info for new info/status. Text1.Text = "" Text2.Text = "" Text3.Text = "" 'Call sub to perform check. Text1.Text = CheckPrinter(PrinterStatus, JobStatus) Text2.Text = PrinterStatus Text3.Text = JobStatus End Sub

Public Function CheckPrinter(PrinterStr As String, JobStr As String) As String Dim hPrinter As Long Dim ByteBuf As Long Dim BytesNeeded As Long Dim PI2 As PRINTER_INFO_2 Dim JI2 As JOB_INFO_2 Dim PrinterInfo As Byte Dim JobInfo As Byte Dim result As Long Dim LastError As Long Dim PrinterName As String Dim tempStr As String Dim NumJI2 As Long Dim pDefaults As PRINTER_DEFAULTS Dim I As Integer 'Set a default return value if no errors occur. CheckPrinter = "Printer info retrieved" 'NOTE: You can pick a printer from the Printers Collection 'or use the EnumPrinters API to select a printer name. 'Use the default printer of Printers collection. 'This is typically, but not always, the system default printer. PrinterName = Printer.DeviceName 'Set desired access security setting. pDefaults.DesiredAccess = PRINTER_ACCESS_USE 'Call API to get a handle to the printer. result = OpenPrinter(PrinterName, hPrinter, pDefaults) If result = 0 Then 'If an error occurred, display an error and exit sub. CheckPrinter = "Cannot open printer " & PrinterName & _ ", Error: " & Err.LastDllError Exit Function End If

'Init BytesNeeded BytesNeeded = 0

'Clear the error object of any errors. Err.Clear

'Determine the buffer size that is needed to get printer info. result = GetPrinter(hPrinter, 2, 0&, 0&, BytesNeeded) 'Check for error calling GetPrinter. If Err.LastDllError <> ERROR_INSUFFICIENT_BUFFER Then 'Display an error message, close printer, and exit sub. CheckPrinter = " > GetPrinter Failed on initial call! <" ClosePrinter hPrinter Exit Function End If  'Note that in Charles Petzold's book "Programming Windows 95," he   'states that because of a problem with GetPrinter on Windows 95 only, you 'must allocate a buffer as much as three times larger than the value 'returned by the initial call to GetPrinter. This is not done here. ReDim PrinterInfo(1 To BytesNeeded) ByteBuf = BytesNeeded 'Call GetPrinter to get the status. result = GetPrinter(hPrinter, 2, PrinterInfo(1), ByteBuf, _    BytesNeeded) 'Check for errors. If result = 0 Then 'Determine the error that occurred. LastError = Err.LastDllError 'Display error message, close printer, and exit sub. CheckPrinter = "Couldn't get Printer Status! Error = " _ & LastError ClosePrinter hPrinter Exit Function End If

'Copy contents of printer status byte array into a  'PRINTER_INFO_2 structure to separate the individual elements. CopyMemory PI2, PrinterInfo(1), Len(PI2) 'Check if printer is in ready state. PrinterStr = CheckPrinterStatus(PI2.Status) 'Add printer name, driver, and port to list. PrinterStr = PrinterStr & "Printer Name = " & _ GetString(PI2.pPrinterName) & vbCrLf PrinterStr = PrinterStr & "Printer Driver Name = " & _ GetString(PI2.pDriverName) & vbCrLf PrinterStr = PrinterStr & "Printer Port Name = " & _ GetString(PI2.pPortName) & vbCrLf 'Call API to get size of buffer that is needed. result = EnumJobs(hPrinter, 0&, &HFFFFFFFF, 2, ByVal 0&, 0&, _     BytesNeeded, NumJI2) 'Check if there are no current jobs, and then display appropriate message. If BytesNeeded = 0 Then JobStr = "No Print Jobs!" Else 'Redim byte array to hold info about print job. ReDim JobInfo(0 To BytesNeeded) 'Call API to get print job info. result = EnumJobs(hPrinter, 0&, &HFFFFFFFF, 2, JobInfo(0), _       BytesNeeded, ByteBuf, NumJI2) 'Check for errors. If result = 0 Then 'Get and display error, close printer, and exit sub. LastError = Err.LastDllError CheckPrinter = " > EnumJobs Failed on second call! < Error = " _ & LastError ClosePrinter hPrinter Exit Function End If     'Copy contents of print job info byte array into a      'JOB_INFO_2 structure to separate the individual elements. For I = 0 To NumJI2 - 1  ' Loop through jobs and walk the buffer CopyMemory JI2, JobInfo(I * Len(JI2)), Len(JI2) ' List info available on Jobs. Debug.Print "Job ID" & vbTab & JI2.JobId Debug.Print "Name Of Printer" & vbTab & _ GetString(JI2.pPrinterName) Debug.Print "Name Of Machine That Created Job" & vbTab & _ GetString(JI2.pMachineName) Debug.Print "Print Job Owner's Name" & vbTab & _ GetString(JI2.pUserName) Debug.Print "Name Of Document" & vbTab & GetString(JI2.pDocument) Debug.Print "Name Of User To Notify" & vbTab & _ GetString(JI2.pNotifyName) Debug.Print "Type Of Data" & vbTab & GetString(JI2.pDatatype) Debug.Print "Print Processor" & vbTab & _ GetString(JI2.pPrintProcessor) Debug.Print "Print Processor Parameters" & vbTab & _ GetString(JI2.pParameters) Debug.Print "Print Driver Name" & vbTab & _ GetString(JI2.pDriverName) Debug.Print "Print Job 'P' Status" & vbTab & _ GetString(JI2.pStatus) Debug.Print "Print Job Status" & vbTab & JI2.Status Debug.Print "Print Job Priority" & vbTab & JI2.Priority Debug.Print "Position in Queue" & vbTab & JI2.Position Debug.Print "Earliest Time Job Can Be Printed" & vbTab & _ JI2.StartTime Debug.Print "Latest Time Job Will Be Printed" & vbTab & _ JI2.UntilTime Debug.Print "Total Pages For Entire Job" & vbTab & JI2.TotalPages Debug.Print "Size of Job In Bytes" & vbTab & JI2.Size 'Because of a bug in Windows NT 3.51, the time member is not set correctly. 'Therefore, do not use the time member on Windows NT 3.51. Debug.Print "Elapsed Print Time" & vbTab & JI2.time Debug.Print "Pages Printed So Far" & vbTab & JI2.PagesPrinted 'Display basic job status info. JobStr = JobStr & "Job ID = " & JI2.JobId & _ vbCrLf & "Total Pages = " & JI2.TotalPages & vbCrLf tempStr = ""  'Clear 'Check for a ready state. If JI2.pStatus = 0& Then  ' If pStatus is Null, check Status. If JI2.Status = 0 Then tempStr = tempStr & "Ready! " & vbCrLf Else 'Check for the various print job states. If (JI2.Status And JOB_STATUS_SPOOLING) Then tempStr = tempStr & "Spooling " End If              If (JI2.Status And JOB_STATUS_OFFLINE) Then tempStr = tempStr & "Off line " End If              If (JI2.Status And JOB_STATUS_PAUSED) Then tempStr = tempStr & "Paused " End If              If (JI2.Status And JOB_STATUS_ERROR) Then tempStr = tempStr & "Error " End If              If (JI2.Status And JOB_STATUS_PAPEROUT) Then tempStr = tempStr & "Paper Out " End If              If (JI2.Status And JOB_STATUS_PRINTING) Then tempStr = tempStr & "Printing " End If              If (JI2.Status And JOB_STATUS_USER_INTERVENTION) Then tempStr = tempStr & "User Intervention Needed " End If              If Len(tempStr) = 0 Then tempStr = "Unknown Status of " & JI2.Status End If           End If        Else ' Dereference pStatus. tempStr = PtrCtoVbString(JI2.pStatus) End If         'Report the Job status. JobStr = JobStr & tempStr & vbCrLf Debug.Print JobStr & tempStr Next I  End If   'Close the printer handle. ClosePrinter hPrinter End Function </li> Go to your print queue, and then pause the printer. Note that you may need a local printer instead of a network printer to pause the print queue.</li> Run the sample project, and then click Start.</li> Perform one or more print operations from another application such as Notepad. The printer information is displayed in the text boxes. Notice that job details appear in the Immediate window.</li> If necessary, click Stop on the form to scroll through the text boxes and the Immediate window to view this printer information.</li></ol>

back to the top

Troubleshooting

 * Only a specific device driver can obtain real-time, accurate printer status information. This code obtains the same status that the Windows Spooler reports.
 * The exact status that is reported may vary with different printers and drivers.

back to the top

<div class="references_section">