6

I'm trying to use win32api to output a PDF document to a particular printer.

win32api.ShellExecute(0, "print", filename, '/d:"%s"' % printername, ".", 0)

filename is a full pathname to the file, and printname is the name of the target printer I get by going through the output of win32api.EnumPrinters(6).

The file is sent to the Windows default printer even if printername is the name of a different target (my expectation is that passing a specific printer would send the named file to that printer, rather than the default).

Any hints as to what I'm doing wrong? Is there a different way of generically printing a PDF file to a specific printer? Barring all else, is there a way of temporarily changing the default printer from my program?

Inaimathi
  • 13,853
  • 9
  • 49
  • 93
  • Ive found that it depends on the filetype... a *.html will prompt for the printer of your choice, a *.pdf just goes to default ... none of the "workarounds" I have seen actually work ... and we use this in a software package that is used by lots of researchers/farmers ... – Joran Beasley Sep 27 '12 at 17:30
  • @JoranBeasley - Huh. Any other approaches you know of then, or am I SOL as far as you know? – Inaimathi Sep 27 '12 at 17:31
  • SOL i think ... It gets worse it depends on your default PDF handler (PDFComplete doesnt work at all for example) ... the alternative is to open it in their default pdf viewer and let them print from there – Joran Beasley Sep 27 '12 at 17:32
  • If you were to save it as HTML instead of PDF you can get teh printer selection window ... we spent lots of engineering hours to come to that conclusion ... – Joran Beasley Sep 27 '12 at 17:33
  • @JoranBeasley - That doesn't help. The point of the program would be to route print jobs without supervision; having to pick the printer each time defeats the purpose of using `ShellExecute` in the first place. – Inaimathi Sep 27 '12 at 17:48
  • yeah it wont work with this method at all :/ ... I haave not found a viable alternative ... – Joran Beasley Sep 27 '12 at 18:20
  • THIS IS THE BEST ANSWER, THIS SOLVES IT!! https://stackoverflow.com/a/66873366/14000156 – Michael Rodas Sep 05 '22 at 20:57

4 Answers4

5

MikeHunter's answer was a decent starting point.

The proposed solution is calling out to Acrobat or Acrobat Reader to do the actual printing, rather than going through the win32api. For my purposes, this is sufficient:

from subprocess import call

acrobat = "C:\Program Files\Adobe\Acrobat 7.0\Acrobat.exe" ## Acrobat reader would also work, apparently
file = "C:\path\to\my\file.pdf"
printer = "Printer Name Goes Here"

call([acrobat, "/T", file, printer])

That starts up Acrobat, and prints the given file to the named printer even if it's not the Windows default. The first print job processed this way takes a few seconds (I'm assuming this is the Acrobat service being started and cached in memory), subsequent jobs print instantly. I have not done any kind of load testing on this, but I assume the call is less than trivial, so don't trust it for massive throughput.

Inaimathi
  • 13,853
  • 9
  • 49
  • 93
3

I'm trying to print any old file to a specific printer, so these answers did not help me. However, I did find the perfect solution. Windows has a canonical verb called printto that does not show up in the context menu. It is used as a way for users to drag and drop a document onto a printer to enable printing in that manner. We can use that feature; the second argument is the name of the printer. I could never get the /d: parameter to work correctly in conjunction with the print canonical verb, but this solution solved it for me. I put the printername in quotes in case there are spaces in it.

win32api.ShellExecute(0, "printto", filename, f'"{printername}"', ".", 0)
Bobort
  • 3,085
  • 32
  • 43
  • this is not working on 2 printers it will always print to the default printer – Fathy Jun 20 '22 at 12:38
  • 1
    I seem to still be using this method to print to various printers. Verify the syntax and the printer name. It must be a valid printer name; sometimes those can be tricky to get just right, especially if they're shared or networked printers. – Bobort Jun 21 '22 at 13:30
  • thank you the name was the issue this approach working great but the printer name shouldn't have spaces. but can you help me I need to print text not a file? I tried `io.BytesIO` but can't get it to work – Fathy Jun 23 '22 at 06:59
  • 1
    Without doing a bunch of research and assuming your project has few users, I would just write to a file first. Then print that file. – Bobort Jun 23 '22 at 15:11
  • thank you I already did this and it's working fine – Fathy Jun 25 '22 at 13:26
1

I use SumatraPDF to achieve a similar solution (Python 3) as user Inaimathi posted:

import time
from subprocess import call

start = time.perf_counter()
sumatra = "C:\\Program Files\\SumatraPDF\\SumatraPDF.exe"
file = "C:\\Users\\spiderman\\Desktop\\report.pdf"

call([sumatra, '-print-to-default', '-silent', file])
end = time.perf_counter()
print("PDF printing took %5.9f seconds" % (end - start))

The list of command-line arguments you can pass to SumatraPDF is here.

Miguel Rentes
  • 994
  • 1
  • 10
  • 27
1

The best way I found is:

  1. set the default printer to the printer you need

    current_printer = win32print.GetDefaultPrinter()

    os.system(f"RUNDLL32 PRINTUI.DLL,PrintUIEntry /y /n {name of needed printer}")

  2. Print file

    win32api.ShellExecute(0, "print", "{document}", '/d:"{name of printer}"', ".", 0)

  3. Restore old printer as the default

    time.sleep(3)

    os.system(f"RUNDLL32 PRINTUI.DLL,PrintUIEntry /y /n {current_printer}")