4

The printer has a print queue where are documents prepared for printing that were sent to the printer from a few computers.

I'd like to write a code in Python that runs on the server and checks for printer events. Especially when the document has finished successfully printing, I'd like to catch this event and get information about the document

  • document name
  • number of pages
  • format (A4, A3, etc.)
  • was it colorful or black and white
  • time of finish printing

Could you help me bounce off?

I already studied this question but I can't figure out what I need from it.

I tried this code but ends with error message:

Traceback (most recent call last):
  File "...recipe-305690-1.py", line 195, in <module>
    prt.EnumJobs(pJob, prt.pcbNeeded)
  File "...recipe-305690-1.py", line 164, in EnumJobs
    ret = ws.EnumJobsA(self.OpenPrinter(),
ctypes.ArgumentError: argument 5: <class 'OverflowError'>: int too long to convert
xralf
  • 3,312
  • 45
  • 129
  • 200
  • 1
    From that link it looks like you can get the document name, and the printing start and end time at least. But I think that if there's not an existing easy solution out there to get the other info, and you still insist that it has to be written in Python (as opposed to an external program that works in tandem with a Python script) you'll likely have to use the `ctypes` library and dig into the [relevant Windows SDK](https://docs.microsoft.com/en-us/troubleshoot/windows/win32/printer-print-job-status). Did you at least try the solution in your link and debug it to see if more info was available? – Random Davis Nov 24 '21 at 22:11
  • @RandomDavis I tried to debug it, but not in depth yet, because I had more priority things these days. – xralf Nov 25 '21 at 05:04
  • 2
    Okay, it's just that you're supposed to have done as much as you possibly can on your own before posting here: https://meta.stackoverflow.com/q/261592/6273251 – Random Davis Nov 26 '21 at 16:03
  • So far, the program catches only print events from the computer it is running on. I don't want to send documents to print queue of the printer, because it would confuse print service workers and the minor problem is the waste of paper (I know I could send empty paper). So, difficult to debug. – xralf Nov 27 '21 at 16:46
  • Are you aware of `win32print.EnumJobs(hPrinter, FirstJob , NoJobs , Level )`? [JOB_INFO_2](https://learn.microsoft.com/en-us/windows/win32/printdocs/job-info-2) comes at least close to your wishlist. – Thingamabobs Nov 27 '21 at 20:40
  • @Thingamabobs It seems that I could get the number of pages from it and only that were sent from the computer the program is running on. I'd like to get all finished jobs (sent from all the computers to the printer) from the particular printer. – xralf Nov 27 '21 at 20:57
  • @xralf I guess not, the description of [EnumJobs](https://learn.microsoft.com/en-us/windows/win32/printdocs/enumjobs) let me beliefe otherwise. *The EnumJobs function retrieves information about a specified set of print jobs for a specified printer.* [Also see](https://learn.microsoft.com/en-us/windows/win32/printdocs/enumjobs#remarks) *How quickly this function returns depends on run-time factors such as network status, print server configuration* – Thingamabobs Nov 27 '21 at 21:05
  • For the seak of minimalistic in a checking coroutine you can and should call [GetPrinter](https://learn.microsoft.com/en-us/windows/win32/printdocs/getprinter) and the [PRINTER_INFO_6](https://learn.microsoft.com/en-us/windows/win32/printdocs/printer-info-6). For more see the [win32print](http://timgolden.me.uk/pywin32-docs/win32print.html) modul. – Thingamabobs Nov 27 '21 at 21:08
  • Is the printer connected to a MS server or just one shared on the network? Also what brand/model of printer is it? – Life is complex Nov 28 '21 at 06:12
  • @Lifeiscomplex It's not a shared printer on the network, each PC has it's own driver. – xralf Nov 29 '21 at 09:21
  • @Lifeiscomplex It's Oce VarioPrint 4120. – xralf Nov 29 '21 at 11:56
  • 1
    @xralf every computer system would have its own printer driver(s), but the printer is on the network, because it is a Oce VarioPrint 4120. So is you plan to deploy a python script to every computer that could print to the printer in question? – Life is complex Nov 29 '21 at 11:59
  • Here are all the manuals for the [Oce VarioPrint 4120](https://downloads.cpp.canon/ProductDownloads/Index/330). It is worth looking at these to see if there is a way to query this printer for the information that you seek. – Life is complex Nov 29 '21 at 12:10
  • Does the "Job tickets" function of the Oce VarioPrint 4120 gave you the information that you seek? – Life is complex Nov 29 '21 at 12:14
  • FYI this printer has a web management interface and can be queried via SNMP. Have you looked at these items? – Life is complex Nov 29 '21 at 12:24
  • @Lifeiscomplex We have actually more printers (more brand/models). So, there isn't unified way to query them with pywin32? Where did you find "job tickets" and possibility to query it via SNMP? Maybe the other printers would have something similar. Do you think that we could finally get the desired information (5 items of the list in the question)? – xralf Nov 29 '21 at 14:11
  • 2
    @xralf Try [this exampel](https://github.com/ActiveState/code/blob/master/recipes/Python/305690_Enumerate_printer_job/recipe-305690.py) and see if it satisfy your needs. – Thingamabobs Nov 29 '21 at 16:46
  • @Thingamabobs I already tried it about a week ago and it ended with an error. I can post the error tommorrow (at home I'm on Linux machine), but maybe the error is for another question. – xralf Nov 29 '21 at 16:52
  • @xralf IMHO the error is for this question. Also any code that you tried is for this question. We need to know everything that you have tried that has either failed or partially worked. It would also be nice to know if you plan to deploy the module to each system in the network or do you have another plan. Details are important to solving your use case. – Life is complex Nov 29 '21 at 17:15
  • @Lifeiscomplex I would like to run the program on the server (I will make windows service from it) that catches print events (finished printing jobs with the information we need) from all printers. I will filter the jobs according to document name (matching some patterns), but that's easy and not part of this question. This would be best case scenario. We need mainly the information about the color and format to have precise reports about what was printed colorfully, what was printed A4 and black and white in certain time period etc. – xralf Nov 29 '21 at 17:37
  • @Lifeiscomplex So far the reports are created little unprecisely - the document is sent to the printer and we count it as finished printing, no matter it could fail and the information we need we get from the tables of document prefixes that signalize for example that it is A4 or it is black and white etc. So the tables of prefixes must be updated frequently by print service workers, but this could be automated in the future. – xralf Nov 29 '21 at 17:37
  • 1
    @xralf I dont think you get the informations **after** the job was sent to the printer. Rather you would need to write a programm that sets the job for you, but that task would take much more time as you may wish to spent on it. Based on my understanding to the documentation you only have access to the [SpoolerApi](https://learn.microsoft.com/en-us/windows/win32/printdocs/print-spooler-api) and the information you can **get** there is limited. Why dont you use the filename to consider what happens? It may appear that a standard filename will be enough. – Thingamabobs Nov 29 '21 at 18:34
  • @Thingamabobs We get a bunch of information from the filename as well, but we don't have information about the format and colorfulness in the filename. – xralf Nov 30 '21 at 06:28
  • 1
    check out these [questions](https://www.google.com/search?q=types.ArgumentError:+argument+5:+%3Cclass+%27OverflowError%27%3E:+site:stackoverflow.com) to fix that error message, which seems to be related to Window 64-bit systems instead of Window 32-bit systems. – Life is complex Nov 30 '21 at 14:15
  • In the [faulty code](https://github.com/ActiveState/code/blob/master/recipes/Python/305690_Enumerate_printer_job/recipe-305690.py), line 141 reads `self.handle = c_ulong()` which is suspect IMHO (a handle is 64-bit on Win64, and `ulong` is always 32 bits on Windows.) – AntoineL Dec 01 '21 at 08:50
  • @xralf the code is supposed to fail. But I'm not quite sure why there is nothing to adress this in the code. You could write a try and except for it, but Im actually not sure if its needed. Anyway [see](https://learn.microsoft.com/en-us/windows/win32/printdocs/enumjobs#remarks) *To determine the required buffer size, call EnumJobs with cbBuf set to zero. EnumJobs fails, GetLastError returns ERROR_INSUFFICIENT_BUFFER, and the pcbNeeded parameter returns the size, in bytes, of the buffer required to hold the array of structures and their data.* – Thingamabobs Dec 01 '21 at 21:02
  • Please print the `cbBuf` before executing `ws.EnumJobsA` and see this [answer](https://stackoverflow.com/a/384672/13629335) and this [solutions](https://www.pythonpool.com/overflowerror-python-int-too-large-to-convert-to-c-long-solved/) – Thingamabobs Dec 01 '21 at 21:36
  • @Thingamabobs Sorry, I used some print statements, so the line numbers do not correspond to line numbers in the code. The problem is here `pJob = addressof(pJobInfo)`, pJob has some large address and `ret = ws.EnumJobsA(self.OpenPrinter(), FirstJob, self.NoJobs, Level, pJob, cbBuf, byref(self.pcbNeeded), byref(self.nReturned))` fails – xralf Dec 01 '21 at 22:11

2 Answers2

2

[GitHub]: ActiveState/code - (master) code/recipes/Python/305690_Enumerate_printer_job/recipe-305690.py (that raises ctypes.ArgumentError in your case) is ancient, some parts of it only worked by luck, and some of them never worked (as the flow didn't reach them).
I submitted [GitHub]: ActiveState/code - Fixes and updates which addresses the major problems (there are some left, but the code is working).

Starting from that, I tried to tailor it for this question, and address the items (partially at least). Since the question is tagged for [GitHub]: mhammond/pywin32 - pywin32, I used it in order to have (much) shorter (and Python friendlier) code.

code00.py:

#!/usr/bin/env python

import sys
import time
import win32con as wcon
import win32print as wprn
from pprint import pprint as pp


JOB_INFO_RAW_KEYS_DICT = {  # Can comment uninteresting ones
    "JobId": "Id",
    "Position": "Index",
    "pPrinterName": "Printer",
    "pUserName": "User",
    "pDocument": "Document",
    "TotalPages": "Pages",
    "Submitted": "Created",
}


def generate_constant_strings(header, mod=None):
    header_len = len(header)
    ret = {}
    for k, v in (globals() if mod is None else mod.__dict__).items():
        if k.startswith(header):
            ret[v] = k[header_len:].capitalize()
    return ret


JOBSTATUS_DICT = generate_constant_strings("JOB_STATUS_", mod=wcon)
DMCOLOR_DICT = generate_constant_strings("DMCOLOR_", mod=wcon)
DMPAPER_DICT = generate_constant_strings("DMPAPER_", mod=wcon)


def printer_jobs(name, level=2):
    p = wprn.OpenPrinter(wprn.GetDefaultPrinter() if name is None else name, None)
    jobs = wprn.EnumJobs(p, 0, -1, level)
    wprn.ClosePrinter(p)
    return jobs


def job_data(job, raw_keys_dict=JOB_INFO_RAW_KEYS_DICT):
    ret = {}
    for k, v in job.items():
        if k in raw_keys_dict:
            ret[raw_keys_dict[k]] = v
    ret["Format"] = DMPAPER_DICT.get(job["pDevMode"].PaperSize)
    ret["Color"] = DMCOLOR_DICT.get(job["pDevMode"].Color)
    ret["Status"] = JOBSTATUS_DICT.get(job["Status"])
    return ret


def main(*argv):
    printer_name = None
    interval = 3
    while 1:
        try:
            jobs = printer_jobs(printer_name)
            for job in jobs:
                data = job_data(job)
                pp(data, indent=2, sort_dicts=0)
            time.sleep(interval)
        except KeyboardInterrupt:
            break


if __name__ == "__main__":
    print("Python {:s} {:03d}bit on {:s}\n".format(" ".join(elem.strip() for elem in sys.version.split("\n")),
                                                   64 if sys.maxsize > 0x100000000 else 32, sys.platform))
    rc = main(*sys.argv[1:])
    print("\nDone.")
    sys.exit(rc)

Output:

[cfati@CFATI-5510-0:e:\Work\Dev\StackOverflow\q070103258]> "e:\Work\Dev\VEnvs\py_pc064_03.08.07_test0\Scripts\python.exe" code00.py
Python 3.8.7 (tags/v3.8.7:6503f05, Dec 21 2020, 17:59:51) [MSC v.1928 64 bit (AMD64)] 064bit on win32

{ 'Id': 9,
  'Printer': 'WF-7610 Series(Network)',
  'User': 'cfati',
  'Document': '*Untitled - Notepad',
  'Index': 1,
  'Pages': 1,
  'Created': pywintypes.datetime(2021, 12, 3, 22, 52, 22, 923000, tzinfo=TimeZoneInfo('GMT Standard Time', True)),
  'Format': 'A4',
  'Color': 'Color',
  'Status': None}
{ 'Id': 13,
  'Printer': 'WF-7610 Series(Network)',
  'User': 'cfati',
  'Document': 'e:\\Work\\Dev\\StackOverflow\\q070103258\\code00.py',
  'Index': 2,
  'Pages': 4,
  'Created': pywintypes.datetime(2021, 12, 3, 23, 10, 40, 430000, tzinfo=TimeZoneInfo('GMT Standard Time', True)),
  'Format': 'A3',
  'Color': 'Monochrome',
  'Status': None}

Done.

Grapically:

Img0

Notes:

CristiFati
  • 38,250
  • 9
  • 50
  • 87
  • *I'm not sure how (or whether it's possible) to get all of them.* There is a method to [get all](http://timgolden.me.uk/pywin32-docs/win32print__EnumPrinters_meth.html) of the printers. [MSDN](https://learn.microsoft.com/en-us/windows/win32/printdocs/enumprinters) – Thingamabobs Dec 04 '21 at 09:07
  • @Thingamabobs: this way, I'm pretty sure it's not possible. I have a *Win* machine (that I try this on from), and another one *OSX*. Connected to the same printer their queues are pretty separate. But "the print server" card has not been played yet. – CristiFati Dec 04 '21 at 09:18
  • @Thingamabobs: I just realized what your comment is all about: it's not about (and it never was about) ***all*** the printers registered on a system. That's a different story. For the record, I have 20+ printer drivers installed (and none of them corresponds to an **active** printer). – CristiFati Dec 04 '21 at 09:22
  • As I dont have the enviorment to work something out here, I really do just assume that it should be possible to use [EnumPrinters](https://learn.microsoft.com/en-us/windows/win32/printdocs/enumprinters) and work with [GetPrinter](https://learn.microsoft.com/en-us/windows/win32/printdocs/getprinter) and [PRINTER_INFO_6](https://learn.microsoft.com/en-us/windows/win32/printdocs/printer-info-6). Of course the printer needs to be available for the PC that requests these information in the first place, but that wasnt part of the question anyway. – Thingamabobs Dec 04 '21 at 09:41
  • Yes it is, but that's a different level. I've worked wit those as well. https://stackoverflow.com/questions/44109985/how-can-i-use-setjob-in-win32print/44115745#44115745. Each record returned by *EnumPrinters* can have its own queues. I was working with the default printer, that "by coincidence" had some jobs in its queue. – CristiFati Dec 04 '21 at 10:50
  • @CristiFati I appreciate your answer, so I awarded bounty not to expire, but I will wait with accepting til I test it in workweek on Windows. – xralf Dec 04 '21 at 16:19
  • @xralf: Thank you very much for the gift (especially since the answer only addresses your question partially)! Let me know how it works! – CristiFati Dec 05 '21 at 13:16
  • @CristiFati You're welcome. I will let you know on monday. Your work deserves bounty no matter if it's partial, but I will accept only if it's complete. – xralf Dec 05 '21 at 13:20
  • @CristiFati I tried it, but it catched no events (even those sent from the computer the script is running). I tried to modify the main as follows `while 1: rc = main(*sys.argv[1:]) time.sleep(3)` but it prints nothing. Am I doing something wrong? – xralf Dec 06 '21 at 11:01
  • Wow that's strange! Did it run while there were jobs in the print queue? – CristiFati Dec 06 '21 at 14:28
  • @CristiFati Yes, I printed a few documents. – xralf Dec 07 '21 at 05:52
  • Ok, as you noticed this code does not get notified on events. It just queries the print queue for existing print jobs (I modified it to keep running till Ctrl+C). But again It's strange that it doesn't show jobs from the local print queue. So it simply doesn't display anything (not even errors)? – CristiFati Dec 07 '21 at 11:44
  • @xralf: Do you have any updates? Does it show some jobs? – CristiFati Dec 09 '21 at 17:35
  • @CristiFati Sorry, SO didn't notified me about your preprevious comment. I will try your modification. – xralf Dec 09 '21 at 19:30
  • The modification works. It seems that the solution is impossible (to see jobs sent from all computers)? – xralf Dec 15 '21 at 06:11
  • That part was a long shot from the beginning (from my *PoV*, in the last bullet I listed how would I try to go further, but I don't know the outcome, as I have neither the knowledge nor the environment). The modification works in the sense that it doesn't crash or it actually gets useful data? – CristiFati Dec 15 '21 at 13:22
  • Another thing that comes into my mind (and I think it has greater chances of success) is querying the printer directly. But the drawback is that different printers might have different *API*s (some might not expose the functionality at all). – CristiFati Dec 15 '21 at 13:25
1

The problem with the code sample is that it is expecting

#164: FirstJob = c_ulong(0) #Start from this job

But the verion of the ws = WinDLL("winspool.drv") you are using expects an Int per the error message.

My recommendation is to use something like C# as handling this kind of thing is easier and better documentented.

https://www.codeproject.com/Articles/51085/Monitor-jobs-in-a-printer-queue-NET

ZeusT
  • 515
  • 2
  • 8