Click here to Skip to main content
15,880,392 members
Articles / Desktop Programming / Win32

Monitor jobs in a printer queue (.NET)

Rate me:
Please Sign up or sign in to vote.
4.06/5 (23 votes)
7 Jan 2010CPOL2 min read 285.2K   17.3K   57   82
This article demonstrates monitoring a printer queue for job status changes.

Introduction

This article demonstrates the ability to monitor a print queue on the local machine.

Background

There is a lot of code available in the internet (CodeProject as well as other sites) about the API calls to do print spool monitoring. However, I was not able to find any code that would help the user to enable monitoring one or more print queues with minimal code changes. So, I created a class (PrintQueueMonitor) that would allow the user to monitor the print queue for job changes and raise an event as and when jobs get added to the queue or job status gets changed in the queue.

Using the code

The attached zip file contains two projects:

  1. PrintQueueMonitor
  2. PrintSpooler

The PrintQueueMonitor project has two main files:

  • PrintSpoolAPI.cs
  • PrintQueueMonitor.cs

PrintSpoolAPI.cs contains the data types and structures used by PrintQueueMonitor. This API was not created by me, and I pulled this from the internet (not sure of the location):

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using System.Globalization;
namespace Atom8.API.PrintSpool
{
    public enum JOBCONTROL
    ......
    ......
}

PrintQueueMonitor.cs contains two classes - PrintJobChangeEventArgs and PrintQueueMonitor, and a delegate PrintJobStatusChanged.

The PrintJobChangeEventArgs is inherited from EventArgs. This object is passed while raising the event for job status addition or change in status or deletion.

C#
public class PrintJobChangeEventArgs : EventArgs
{
    #region private variables
    private int _jobID=0;
    private string _jobName = "";
    private JOBSTATUS _jobStatus = new JOBSTATUS();
    private PrintSystemJobInfo _jobInfo = null;
    #endregion
    public int JobID { get { return _jobID;}  }
    public string JobName { get { return _jobName; } }
    public JOBSTATUS JobStatus { get { return _jobStatus; } }
    public PrintSystemJobInfo JobInfo { get { return _jobInfo; } }
    public PrintJobChangeEventArgs(int intJobID, string strJobName, 
           JOBSTATUS jStatus, PrintSystemJobInfo objJobInfo )
        : base()
    {
        _jobID = intJobID;
        _jobName = strJobName;
        _jobStatus = jStatus;
        _jobInfo = objJobInfo;
    }
}

The delegate PrintJobStatusChanged is the type of event raised when the PrintQueueMonitor class needs to send job status change notifications to the owner.

C#
public delegate void PrintJobStatusChanged(object Sender, 
                     PrintJobChangeEventArgs e);

The PrintQueueMonitor class imports Win32 API functions, registers with the Win32 Spooler for change notifications, and waits for notificaitons. Upon receipt of notification, it checks to see if the notification is relevant to a job, and if so, raises the OnJobStatusChange() event with the appropriate parameters:

C#
public class PrintQueueMonitor  
{
    #region DLL Import Functions
    [DllImport("winspool.drv", 
      EntryPoint = "OpenPrinterA",SetLastError = true, 
      CharSet = CharSet.Ansi,ExactSpelling = true, 
      CallingConvention = CallingConvention.StdCall)]
    public static extern bool OpenPrinter(String pPrinterName,
    out IntPtr phPrinter,
    Int32 pDefault);
    [DllImport("winspool.drv", 
        EntryPoint = "ClosePrinter",
        SetLastError = true,
        ExactSpelling = true,
        CallingConvention = CallingConvention.StdCall)]
    public static extern bool ClosePrinter
    (Int32 hPrinter);
    [DllImport("winspool.drv",
    EntryPoint = "FindFirstPrinterChangeNotification",
    SetLastError = true, CharSet = CharSet.Ansi,
    ExactSpelling = true,
    CallingConvention = CallingConvention.StdCall)]
    public static extern IntPtr FindFirstPrinterChangeNotification
                        ([InAttribute()] IntPtr hPrinter,
                        [InAttribute()] Int32 fwFlags,
                        [InAttribute()] Int32 fwOptions,
                        [InAttribute(), 
                         MarshalAs(UnmanagedType.LPStruct)] 
                         PRINTER_NOTIFY_OPTIONS pPrinterNotifyOptions);
    [DllImport("winspool.drv", EntryPoint = 
               "FindNextPrinterChangeNotification",
    SetLastError = true, CharSet = CharSet.Ansi,
    ExactSpelling = false,
    CallingConvention = CallingConvention.StdCall)]
    public static extern bool FindNextPrinterChangeNotification
                        ([InAttribute()] IntPtr hChangeObject,
                         [OutAttribute()] out Int32 pdwChange,
                         [InAttribute(), 
                          MarshalAs(UnmanagedType.LPStruct)] 
                          PRINTER_NOTIFY_OPTIONS pPrinterNotifyOptions,
                        [OutAttribute()] out IntPtr lppPrinterNotifyInfo
                             );
        #endregion
        #region Constants
        const int PRINTER_NOTIFY_OPTIONS_REFRESH = 1;
        #endregion
        #region Events
        public event PrintJobStatusChanged OnJobStatusChange;
        #endregion
        #region private variables
        private IntPtr _printerHandle = IntPtr.Zero;
        private string _spoolerName = "";
        private ManualResetEvent _mrEvent = new ManualResetEvent(false);
        private RegisteredWaitHandle _waitHandle = null;
        private IntPtr _changeHandle = IntPtr.Zero;
        private PRINTER_NOTIFY_OPTIONS _notifyOptions = 
                new PRINTER_NOTIFY_OPTIONS();
        private Dictionary<int, string> objJobDict = 
                new Dictionary<int, string>();
        private PrintQueue _spooler = null;
        #endregion
        #region constructor
        public PrintQueueMonitor(string strSpoolName) 
        {
            // Let us open the printer and get the printer handle.
            _spoolerName = strSpoolName;
            //Start Monitoring
            Start();
        }
        #endregion
        #region destructor
        ~PrintQueueMonitor() 
        {
            Stop();
        }
        #endregion
        #region StartMonitoring
        public void Start()
        {
            OpenPrinter(_spoolerName, out _printerHandle, 0);
            if (_printerHandle != IntPtr.Zero)
            {
                //We got a valid Printer handle. 
                //Let us register for change notification....
                _changeHandle = FindFirstPrinterChangeNotification(
                  _printerHandle, (int)PRINTER_CHANGES.PRINTER_CHANGE_JOB, 0, 
                  _notifyOptions);
                // We have successfully registered for change
                // notification. Let us capture the handle...
                _mrEvent.Handle = _changeHandle;
                //Now, let us wait for change notification from the printer queue....
                _waitHandle = ThreadPool.RegisterWaitForSingleObject(_mrEvent, 
                  new WaitOrTimerCallback(PrinterNotifyWaitCallback), 
                  _mrEvent, -1, true);
            }
            _spooler = new PrintQueue(new PrintServer(), _spoolerName);
            foreach (PrintSystemJobInfo psi in _spooler.GetPrintJobInfoCollection())
            {
                objJobDict[psi.JobIdentifier] = psi.Name;
            }
        }
        #endregion
        #region StopMonitoring
        public void Stop()
        {
            if (_printerHandle != IntPtr.Zero)
            {
                ClosePrinter((int)_printerHandle);
                _printerHandle = IntPtr.Zero;
            }
        }
        #endregion
        #region Callback Function
        public void PrinterNotifyWaitCallback(Object state,bool timedOut)
        {
            if (_printerHandle == IntPtr.Zero) return;
            #region read notification details
            _notifyOptions.Count = 1;
            int pdwChange = 0;
            IntPtr pNotifyInfo = IntPtr.Zero;
            bool bResult = FindNextPrinterChangeNotification(_changeHandle, 
                           out pdwChange, _notifyOptions, out pNotifyInfo);
            //If the Printer Change Notification Call did not give data, exit code
            if ((bResult == false) || (((int)pNotifyInfo) == 0)) return;
            //If the Change Notification was not relgated to job, exit code
            bool bJobRelatedChange = 
              ((pdwChange & PRINTER_CHANGES.PRINTER_CHANGE_ADD_JOB) == 
                PRINTER_CHANGES.PRINTER_CHANGE_ADD_JOB) || 
              ((pdwChange & PRINTER_CHANGES.PRINTER_CHANGE_SET_JOB) == 
                PRINTER_CHANGES.PRINTER_CHANGE_SET_JOB) ||
              ((pdwChange & PRINTER_CHANGES.PRINTER_CHANGE_DELETE_JOB) == 
                PRINTER_CHANGES.PRINTER_CHANGE_DELETE_JOB) ||
              ((pdwChange & PRINTER_CHANGES.PRINTER_CHANGE_WRITE_JOB) == 
                PRINTER_CHANGES.PRINTER_CHANGE_WRITE_JOB);
            if (!bJobRelatedChange) return;
            #endregion 
            #region populate Notification Information
            //Now, let us initialize and populate the Notify Info data
            PRINTER_NOTIFY_INFO info = 
              (PRINTER_NOTIFY_INFO)Marshal.PtrToStructure(pNotifyInfo, 
               typeof(PRINTER_NOTIFY_INFO));                        
            int pData = (int)pNotifyInfo + 
                         Marshal.SizeOf(typeof(PRINTER_NOTIFY_INFO));
            PRINTER_NOTIFY_INFO_DATA[] data = 
                    new PRINTER_NOTIFY_INFO_DATA[info.Count];
            for (uint i = 0; i < info.Count; i++)
            {
                data[i] = (PRINTER_NOTIFY_INFO_DATA)Marshal.PtrToStructure(
                          (IntPtr)pData, typeof(PRINTER_NOTIFY_INFO_DATA));
                pData += Marshal.SizeOf(typeof(PRINTER_NOTIFY_INFO_DATA));
            }
            #endregion 
            
            #region iterate through all elements in the data array
            for (int i = 0; i < data.Count(); i++)
            {
                if ( (data[i].Field == 
                       (ushort)PRINTERJOBNOTIFICATIONTYPES.JOB_NOTIFY_FIELD_STATUS) &&
                     (data[i].Type == (ushort)PRINTERNOTIFICATIONTYPES.JOB_NOTIFY_TYPE)
                    )
                {
                   JOBSTATUS jStatus  = (JOBSTATUS)Enum.Parse(typeof(JOBSTATUS), 
                                         data[i].NotifyData.Data.cbBuf.ToString());
                    int intJobID = (int)data[i].Id;
                    string strJobName = ""; 
                    PrintSystemJobInfo pji = null;
                    try
                    {
                        _spooler = new PrintQueue(new PrintServer(), _spoolerName);
                        pji = _spooler.GetJob(intJobID);
                        if (!objJobDict.ContainsKey(intJobID))
                            objJobDict[intJobID] = pji.Name;
                        strJobName = pji.Name;
                    }
                    catch
                    {
                        pji = null;
                        objJobDict.TryGetValue(intJobID, out strJobName);
                        if (strJobName == null) strJobName = "";
                    }
                    if (OnJobStatusChange != null)
                    {
                        //Let us raise the event
                        OnJobStatusChange(this, 
                          new PrintJobChangeEventArgs(intJobID,strJobName,jStatus,pji));
                    }
                }
            }
            #endregion
            #region reset the Event and wait for the next event
            _mrEvent.Reset();
            _waitHandle = ThreadPool.RegisterWaitForSingleObject(_mrEvent, 
              new WaitOrTimerCallback(PrinterNotifyWaitCallback), _mrEvent, -1, true);
            #endregion 
        }
        #endregion
    }

Sample source code and usage

The PrintSpooler project is a sample (with code) that uses the PrintQueueMonitor class and appends a list box with job change notifications received from the PrintQueueMonitor class.

C#
pqm = new PrintQueueMonitor(cmbPrinters.Text.Trim());
pqm.OnJobStatusChange += 
    new PrintJobStatusChanged(pqm_OnJobStatusChange);
........
........
........

void pqm_OnJobStatusChange(object Sender, PrintJobChangeEventArgs e)
{

    MethodInvoker invoker = () => {
        lbSpoolChanges.Items.Add(e.JobID + " - " + 
        e.JobName + " - " + e.JobStatus);
    };

    if (lbSpoolChanges.InvokeRequired)
        Invoke(invoker);
    else
        invoker();
}

History

To build this class and sample, I have used pieces of code from various posts from CodeProject and other sites. Many thanks to the individuals who have contributed to this.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect Atom8 IT Solutions (P) Ltd
India India
I have been programming since 1991. I consider programming as my passion. I founded Atom8 IT Solutions (P) Ltd., in April 2009 and have been developing applications in Microsoft Technologies (mainly C#, Silverlight).

My specialization has been on the US healthcare domain (Medical Transcription, Patient Financial Services, EMRs).

Comments and Discussions

 
QuestionPRINTED / COMPLETED JOB Status is not showing exceptions Happening Pin
dileep Perumbavoor1-Feb-23 20:27
dileep Perumbavoor1-Feb-23 20:27 
QuestionPrintSystemJobInfo property JobStream always comes as null Pin
KumVivek18-Apr-21 4:59
KumVivek18-Apr-21 4:59 
QuestionGet Copy Count Pin
dharmeshpatt28-Oct-20 3:23
dharmeshpatt28-Oct-20 3:23 
QuestionNetwork Printer Pin
partha249-Aug-20 0:50
partha249-Aug-20 0:50 
Questionerro?? Pin
Bessax23-Dec-19 5:24
Bessax23-Dec-19 5:24 
QuestionModify Content before printing Pin
jude_aj28-May-18 0:21
jude_aj28-May-18 0:21 
AnswerRe: Modify Content before printing Pin
Member 1397428610-Sep-18 9:38
Member 1397428610-Sep-18 9:38 
QuestionPrinter not found Pin
admin rawat27-Mar-17 1:28
admin rawat27-Mar-17 1:28 
QuestionWindows 10 PinPopular
us471114-Dec-16 21:15
us471114-Dec-16 21:15 
AnswerRe: Windows 10 Pin
sziebe11-Mar-20 6:58
sziebe11-Mar-20 6:58 
QuestionNeed to add one time password and wifi control to this code -- Any suggestions pls Pin
Ty Ademosu30-Sep-16 0:46
Ty Ademosu30-Sep-16 0:46 
AnswerRe: Need to add one time password and wifi control to this code -- Any suggestions pls Pin
Ty Ademosu1-Oct-16 3:27
Ty Ademosu1-Oct-16 3:27 
QuestionNot working on Windows 10. Pin
Er. Manoj Sethi6-Sep-16 2:15
Er. Manoj Sethi6-Sep-16 2:15 
AnswerRe: Not working on Windows 10. Pin
percival fernandez3-Mar-19 2:14
percival fernandez3-Mar-19 2:14 
QuestionOnJobStatusChange doesn't fire after a printjob status change Pin
Member 1141660823-Sep-15 9:46
Member 1141660823-Sep-15 9:46 
AnswerRe: OnJobStatusChange doesn't fire after a printjob status change Pin
DI Christian Schuller12-Mar-17 2:20
professionalDI Christian Schuller12-Mar-17 2:20 
GeneralRe: OnJobStatusChange doesn't fire after a printjob status change Pin
Marc Sparks 202115-Jul-21 16:51
Marc Sparks 202115-Jul-21 16:51 
Questionhow do I capture the printing page number reading the job info. Pin
ranjeetha venkatesh1-Mar-15 21:37
ranjeetha venkatesh1-Mar-15 21:37 
Questionno of page count in windows 64 bit machine Pin
Member 104897922-Feb-15 3:13
professionalMember 104897922-Feb-15 3:13 
QuestionSome example in vb.net? Pin
orak762-Jan-15 13:17
orak762-Jan-15 13:17 
Questionhow to get the spl file data Pin
ibmsoft4-Oct-14 17:35
ibmsoft4-Oct-14 17:35 
QuestionGet Job Info Pin
Mohammad Al-Akhras12-Apr-14 12:05
Mohammad Al-Akhras12-Apr-14 12:05 
AnswerRe: Get Job Info Pin
bsopik14-Jul-14 23:11
bsopik14-Jul-14 23:11 
GeneralRe: Get Job Info Pin
Mohammad Al-Akhras15-Jul-14 14:56
Mohammad Al-Akhras15-Jul-14 14:56 
GeneralRe: Get Job Info Pin
bish0926-Sep-17 5:48
bish0926-Sep-17 5:48 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.