Hello There, Guest!
View New Posts  |  View Today's Posts
[C#] Printer Monitor

  • 0 Vote(s) - 0 Average


12-29-2013, 09:34 PM #1
Lee Stevens
Senior Member
***
Posts: 345 Threads:54 Joined: Feb 2012 Reputation: 12

Printer Monitor
Hi All,

I've been making a printer monitor for work. I'm using the Win32_PrintJob WMI to get all the details it currently looks like this.



You'll notice i get two entries per job, i'm not sure why this is happening. Also i have problems reporting the correct number of Prints and Copies.

Here's the Monitor section of the Code:
Code:
private void Monitor()
        {
            try
            {
                while (blnStillRunning)
                {
                    using (ManagementObjectSearcher jobQuery = new ManagementObjectSearcher("SELECT * FROM Win32_PrintJob"))
                    {
                        using (ManagementObjectCollection jobs = jobQuery.Get())
                        {
                            foreach (ManagementObject job in jobs)
                            {
                                string jobId = job["JobId"].ToString();

                                int pages = ((int.Parse(job["TotalPages"].ToString()).Equals("0") ? 1 : int.Parse(job["TotalPages"].ToString())));
                                int PagesPrinted = ((int.Parse(job["PagesPrinted"].ToString()).Equals("0") ? 1 : int.Parse(job["PagesPrinted"].ToString())));
                                int printedNumberToUse = (pages > PagesPrinted) ? pages : PagesPrinted;
                                
                                double cost = (job["Color"].ToString().Equals("Color")) ? (printedNumberToUse * double.Parse(txtColor.Text)) : (printedNumberToUse * double.Parse(txtBlackAndWhite.Text));

                                ListViewItem oSearchItem = lvHistory.FindItemWithText(jobId);

                                if (oSearchItem != null)
                                {
                                    if ((!oSearchItem.SubItems[6].Text.Equals(printedNumberToUse.ToString())))
                                    {
                                        // Update Pages/Cost SubItem.
                                        oSearchItem.SubItems[6].Text = pages.ToString();
                                        oSearchItem.SubItems[9].Text = cost.ToString();
                                    }
                                    else if ((!oSearchItem.SubItems[1].Text.Equals(job["Status"].ToString())))
                                    {
                                        // Update Status SubItem.
                                        oSearchItem.SubItems[1].Text = job["Status"].ToString();
                                    }
                                }
                                else
                                {
                                    if (!job["Document"].ToString().Equals("Local Downlevel Document"))
                                    {
                                        ListViewItem oItem = new ListViewItem(jobId);

                                        oItem.SubItems.Add(job["Status"].ToString()); // Print Job Status
                                        oItem.SubItems.Add(job["DriverName"].ToString()); // Printer Name
                                        oItem.SubItems.Add(job["Owner"].ToString()); // Username
                                        oItem.SubItems.Add(job["HostPrintQueue"].ToString()); // Computer
                                        oItem.SubItems.Add(job["Document"].ToString()); // Document
                                        oItem.SubItems.Add(printedNumberToUse.ToString()); // Pages
                                        oItem.SubItems.Add(""); // Copies
                                        oItem.SubItems.Add(""); // Total
                                        oItem.SubItems.Add(cost.ToString()); // Cost
                                        oItem.SubItems.Add(job["Color"].ToString()); // Type [Color / Black and White]
                                        oItem.SubItems.Add(ManagementDateTimeConverter.ToDateTime(job["TimeSubmitted"].ToString()).ToString("dddd dd MMMM - hh:mm:ss")); // Date
                                        oItem.SubItems.Add(job["PaperSize"].ToString()); // Size

                                        lvHistory.Items.Add(oItem);
                                    }
                                }

                                jobId = null;
                                pages = 0;
                                PagesPrinted = 0;
                                printedNumberToUse = 0;
                                cost = 0.00;
                                oSearchItem = null;
                            }
                        }
                    }
                    Application.DoEvents();
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }

This is a hard topic to find online, wonder if anyone can help me here??

ISSUES:
1). 2 Jobs created for 1 job (only on my machine once i put the exe on the Server it only lists one job, but not the correct number of pages.
2). Can't get number of Copies of a print.
This post was last modified: 12-29-2013, 09:36 PM by Lee Stevens.

12-29-2013, 09:49 PM #2
AceInfinity
Developer
*******
Administrators
Posts: 9,733 Threads:1,026 Joined: Jun 2011 Reputation: 76

RE: Printer Monitor
I've not used any printer monitoring code before, but that looks like a nested mess lol. Have you considered event driven WMI methods instead? http://msdn.microsoft.com/en-us/library/...s.90).aspx

IMHO, a structure like this shouldn't be implemented. You're essentially blocking, and disrupting the whole point of the message loop with Windows form applications.

WMI itself is already slow an expensive computationally, and you combine that with constant queries after each is executed, and use Application.DoEvents().. And waste time setting variables that get destroyed anyways because they leave their scope:
Code:
jobId = null;
pages = 0;
PagesPrinted = 0;
printedNumberToUse = 0;
cost = 0.00;
oSearchItem = null;

You shouldn't have to do that. Although:
Code:
oItem.SubItems.Add ...

The ListViewItem class provides a constructor that allows you to set all of the subitems for each column without having to continuously call the Add() method over and over, you may want to use that instead. It takes in a string array.

The point is that you're doing a ton of computational stuff in a loop that just keeps going, and doesn't care whether it should be checking that something was printed or not. If you're absolutely forced to do something like this, you need to try to keep the repetative task as minimal as possible. In this case I'd suggest subscribing to the WMI event instead and make it event based, which will help lower the CPU usage of such a program, but give you a bit more flexibility as well.

Really cool though :) It's nice to see you applying C# to real-world implementations. Knowing a programming language or languages, is really beneficial for problem solving. I write code all the time to solve real world problems lol.

edit: You may also want to read this article: http://msdn.microsoft.com/en-us/magazine/cc301317.aspx

cheers
This post was last modified: 12-29-2013, 10:58 PM by AceInfinity.


Microsoft MVP .NET Programming - (2012 - Present)
®Crestron DMC-T Certified Automation Programmer

Development Site: aceinfinity.net

 ▲
 ▲ ▲

12-29-2013, 11:04 PM #3
Lee Stevens
Senior Member
***
Posts: 345 Threads:54 Joined: Feb 2012 Reputation: 12

RE: Printer Monitor
Thank you Ace, i'm going to look into the Event driven stuff instead, i'll update once i have something.

12-29-2013, 11:06 PM #4
AceInfinity
Developer
*******
Administrators
Posts: 9,733 Threads:1,026 Joined: Jun 2011 Reputation: 76

RE: Printer Monitor
This is pretty awesome though, I could see it as a small but trivial thing to see who printed what at times. :)

edit: I should write a console version sometime quick for our office, just for fun haha.
This post was last modified: 12-29-2013, 11:09 PM by AceInfinity.


Microsoft MVP .NET Programming - (2012 - Present)
®Crestron DMC-T Certified Automation Programmer

Development Site: aceinfinity.net

 ▲
 ▲ ▲

12-30-2013, 12:31 AM #5
Lee Stevens
Senior Member
***
Posts: 345 Threads:54 Joined: Feb 2012 Reputation: 12

RE: Printer Monitor
Yeah it's an interesting idea, the accountant at work asked me if we could monitor the amount of prints are going on because the bill it's getting higher and higher. Although our printer has a web service it only lets you see the last 20 printed documents. So i decided to have ago! :)

So I've looked at event driven instead and made this:

Code:
public delegate void PrintJobReceivedEventHandler(object sender, PropertyDataCollection e);

    public class PrintMonitor
    {
        public event PrintJobReceivedEventHandler JobReceived;

        protected virtual void OnJobReceived(PropertyDataCollection e)
        {
            if (JobReceived != null)
                JobReceived(this, e);
        }

        public PrintMonitor()
        {
            //
            // TODO: Add constructor logic here
            //
        }

        private string _PrinterName = "";
        private bool _IsMonitoring;
        private string _Error = "";
        private int _LastJobId;

        public string Error
        {
            get { return _Error; }
        }

        public int LastJobId
        {
            get { return _LastJobId; }
            set { _LastJobId = value; }
        }

        public bool IsMonitoring
        {
            get { return _IsMonitoring; }
        }

        public string PrinterName
        {
            get { return _PrinterName; }
            set
            {
                if (value != null)
                {
                    _PrinterName = value;
                    if (_PrinterName.Length > 0)
                        StartMonitor();
                }
            }
        }

        ManagementEventWatcher eventWatcher = null;

        private void StartMonitor()
        {
            ConnectionOptions opt = new ConnectionOptions();
            opt.EnablePrivileges = true;

            //The computer running this must have the printer installed locally
            ManagementScope scope = new ManagementScope("root\\CIMV2", opt);
            scope.Connect();

            try
            {
                WqlEventQuery eventQuery = new WqlEventQuery();
                eventQuery.EventClassName = "__InstanceOperationEvent";
                eventQuery.Condition = @"TargetInstance ISA 'Win32_PrintJob'";
                eventQuery.WithinInterval = new TimeSpan(0, 0, 0, 0, 5); //check every half second

                eventWatcher = new ManagementEventWatcher(scope, eventQuery);
                eventWatcher.EventArrived += new EventArrivedEventHandler(PrJobEventArrived);
                eventWatcher.Start();
                _IsMonitoring = true;
            }
            catch (Exception e)
            {
                _Error = e.Message;
                throw;
            }
        }

        public void StopMonitor()
        {
            eventWatcher.Stop();
        }

        internal void PrJobEventArrived(object sender, EventArrivedEventArgs e)
        {
            if (e != null)
            {
                try
                {
                    foreach (PropertyData pd in e.NewEvent.Properties)
                    {
                        ManagementBaseObject mbo = null;
                        if ((mbo = pd.Value as ManagementBaseObject) != null)
                        {
                            if (int.Parse(mbo.Properties["JobId"].Value.ToString(), System.Globalization.CultureInfo.CurrentCulture) != LastJobId)
                            {
                                if (mbo.Properties["Name"] != null)
                                {
                                    if (mbo.Properties["Name"].Value.ToString().ToLower(System.Globalization.CultureInfo.CurrentCulture).IndexOf(PrinterName.ToLower(System.Globalization.CultureInfo.CurrentCulture)) > -1)
                                    {
                                        LastJobId = int.Parse(mbo.Properties["JobId"].Value.ToString(), System.Globalization.CultureInfo.CurrentCulture);

                                        OnJobReceived(mbo.Properties);
                                    }
                                }
                            }
                            break;
                        }
                    }
                }
                catch (Exception ex)
                {
                    _Error = ex.Message;
                    throw;
                }
            }
        }
    }

I've also changed the subitems thing:

Code:
if (!printJobProperties["Document"].Value.ToString().Equals("Local Downlevel Document"))
                        {
                            ListViewItem oItem = new ListViewItem(jobId);
                            
                            oItem.SubItems.AddRange(new string[] {
                                printJobProperties["Status"].Value.ToString(), // Printer Job Status
                                printJobProperties["DriverName"].Value.ToString(), // Printer Name
                                printJobProperties["Owner"].Value.ToString(), // Username
                                printJobProperties["HostPrintQueue"].Value.ToString(), // Computer
                                printJobProperties["Document"].Value.ToString(), // Docuemnt [Name/Path]
                                printedNumberToUse.ToString(), // Pages
                                "", // Copies
                                "", // Total [Pages * Copies]
                                cost.ToString(), // Cost
                                printJobProperties["Color"].Value.ToString(), // Type [Color / Black and White]
                                ManagementDateTimeConverter.ToDateTime(printJobProperties["TimeSubmitted"].Value.ToString()).ToString("dddd dd MMMM - hh:mm:ss"), // Date
                                printJobProperties["PaperSize"].Value.ToString() // Paper Size
                            });
                            
                            lvHistory.Items.Add(oItem);
                        }

What would be the difference between the Win Form and Console App - is there any advantage?

12-30-2013, 12:58 AM #6
AceInfinity
Developer
*******
Administrators
Posts: 9,733 Threads:1,026 Joined: Jun 2011 Reputation: 76

RE: Printer Monitor
The answer to that is as trivial as whether you like a fancy interface or not. :) Other than that, they are basically one in the same, just one uses Win32 to create a visual.
This post was last modified: 12-30-2013, 12:58 AM by AceInfinity.


Microsoft MVP .NET Programming - (2012 - Present)
®Crestron DMC-T Certified Automation Programmer

Development Site: aceinfinity.net

 ▲
 ▲ ▲

12-30-2013, 01:09 AM #7
Lee Stevens
Senior Member
***
Posts: 345 Threads:54 Joined: Feb 2012 Reputation: 12

RE: Printer Monitor
Thought as much, i prefer having a GUI to look at.

12-30-2013, 03:25 PM #8
Lee Stevens
Senior Member
***
Posts: 345 Threads:54 Joined: Feb 2012 Reputation: 12

RE: Printer Monitor
SO i've let it run for a few hours an here's the outcome - still not positive about the TotalPages VS PagesPrinted



Edit: Hmm, so i've been reading and looks like some people re-query when the job is being deleted to re-get the TotalPages / PagesPrinted to get the correct amount, i'll probably update this when i'm back at work i'll keep you updated.
This post was last modified: 12-30-2013, 03:47 PM by Lee Stevens.

01-27-2014, 11:34 PM #9
Lee Stevens
Senior Member
***
Posts: 345 Threads:54 Joined: Feb 2012 Reputation: 12

RE: Printer Monitor
As an update, i've managed to fix the correct printed pages number, but i have another issue after around 24hrs it stops picking up jobs, does ManagementEventWatcher "expire", do i have to re-establish Watcher every 24hrs?

01-28-2014, 01:10 PM #10
KoBE
¯\_(ツ)_/¯
******
Global Moderators
Posts: 4,862 Threads:494 Joined: Jun 2011 Reputation: 67

RE: Printer Monitor
Are you running the watcher multiple times (or restarting the application multiple times? The reason I ask is because if you don't unregister events sometimes they don't listen properly next time. You might try adding this to your StopMonitor() code to see if it helps. Maybe your form closing event as well if you are restarting the application frequently.
Code:
eventWatcher.EventArrived -= new EventArrivedEventHandler(PrJobEventArrived);
This post was last modified: 01-28-2014, 01:11 PM by KoBE.




Forum Jump:


Possibly Related Threads...
Thread Author Replies Views Last Post
  Wireless Network Monitor KoBE 3 1,784 06-15-2015, 07:40 PM
Last Post: AceInfinity
  Print directly to printer on LX 300 dot matrix t0kneneng 9 6,093 06-12-2013, 05:01 PM
Last Post: AceInfinity
  Sending Raw Data to a printer in C# KoBE 2 3,889 06-06-2013, 10:58 PM
Last Post: KoBE


Users browsing this thread: 1 Guest(s)