Python's win32api only printing to default printer - python

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?

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.

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)

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.

The best way I found is:
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}")
Print file
win32api.ShellExecute(0, "print", "{document}", '/d:"{name of printer}"', ".", 0)
Restore old printer as the default
time.sleep(3)
os.system(f"RUNDLL32 PRINTUI.DLL,PrintUIEntry /y /n {current_printer}")

Related

Python - Printing file on Windows w/ Printer Name

I am making a program which has a functionality to print things. I have managed to put together this snippet which fetches the name of the default printer (if there is one):
import ctypes
buffer = ctypes.create_unicode_buffer(1024)
ctypes.WinDLL("winspool.drv").GetDefaultPrinterW(buffer, ctypes.byref(ctypes.c_ulong(1024)))
printerName = buffer.value
However I cannot figure out how to actually print the file with this. Using notepad.exe with the -P argument (or running a text file with the print verb) it can print but it opens a notepad window and such, which I want to be silent.
The print command does not allow direct printer names, it requires you to set an LPT port and use that.
If you have any clue how to print the file, now that I have the printer name (It's a txt file if that matters) I would greatly appreciate it!

Can't open Microsoft Teams with python (3.8) script using any method

I am trying to make a script to automate the login into Microsoft Teams and all of my code works except the part where the application has to be opened. The weird thing is that this is capable of opening any other application except MS Teams (Chrome, Notepad, Firefox, Edge etc.)
Here's the relevant code:
def openfile():
if os.stat("stor.txt").st_size == 0:
name = filedialog.askopenfilename()
newfile = open("stor.txt", "w")
newfile.write(name)
else:
name = (open("stor.txt", "r").read())
os.startfile(name)
sleep(5)
keyboard.write(open("user.txt", "r").read())
keyboard.press("enter")
sleep(3)
keyboard.write(open("pass.txt", "r").read())
keyboard.press("enter")
I tried this with os.startfile, os.system(start..) and every other method on the web. Doesn't work.
The value I'm passing in to os.startfile() when I try to run Teams is C:/Users/Raghav/AppData/Local/Microsoft/Teams/Update.exe.
First of all, I don't recommend storing your password in plain text like that. It's not very secure, and if another program takes focus at the right time your code will even type your password somewhere else!
Teams should remember your credentials after the first time you log in. I suggest letting it handle that part.
In any case, running os.startfile("foo.exe") is like double-clicking on foo.exe. The file name that you're passing in is C:/Users/Raghav/AppData/Local/Microsoft/Teams/Update.exe, and Update.exe doesn't look like something that should launch Teams to me.
Inspecting the Teams shortcut in my own Start menu, I see that things are a bit more complicated. This shortcut runs Update.exe and passes it some arguments:
C:\...\Update.exe --processStart "Teams.exe"
There is no way to pass arguments to a program with os.startfile(). Try os.system() instead:
os.system('C:/Users/Raghav/AppData/Local/Microsoft/Teams/Update.exe --processStart "Teams.exe"')
There are lots of other ways to run external commands in Python, but this is likely simplest since you don't need Teams' output streams. This command should return 0 if it succeeds and some other value if it fails.
import os
os.system("C:\\Users\\Lenovo\\AppData\\Local\\Discord\\Update.exe --processStart Discord.exe")
For applications that have an address like above, there are also some tips:
Sometimes Discord.exe name of the file in the address have "Discord.exe" (with double-quotes). Remove it.
Instead of single \ use double \\ in the address.
It will definitely work GO AHEAD ✔

How to open <del>named pipe</del>character device special file for reading and writing in Python

I have a service running on a Linux box that creates a named pipe character device-special file, and I want to write a Python3 program that communicates with the service by writing text commands and reading text replies from the pipe device. I don't have source code for the service.
I can use os.open(named_pipe_pathname, os.O_RDWR), and I can use os.read(...) and os.write(...) to read and write it, but that's a pain because I have to write my own code to convert between bytes and strings, I have to write my own readline(...) function, etc.
I would much rather use a Python3 io object to read and write the pipe device, but every way I can think to create one returns the same error:
io.UnsupportedOperation: File or stream is not seekable.
For example, I get that message if I try open(pathname, "r+"), and I get that same message if I try fd=os.open(...) followed by os.fdopen(fd, "r+", ...).
Q: What is the preferred way for a Python3 program to write and read text to and from a named pipe character device?
Edit:
Oops! I assumed that I was dealing with a named pipe because documentation for the service describes it as a "pipe" and, because it doesn't appear in the file system until the user-mode service runs. But, the Linux file utility says it is in fact, a character device special file.
The problem occurs because attempting to use io.open in read-write mode implicitly tries to wrap the underlying file in io.BufferedRandom (which is then wrapped in io.TextIOWrapper if in text mode), which assumes the underlying file is not only read/write, but random access, and it takes liberties (seeking implicitly) based on this. There is a separate class, io.BufferedRWPair, intended for use with read/write pipes (the docstring specifically mentions it being used for sockets and two way pipes).
You can mimic the effects of io.open by manually wrapping layer by layer to produce the same end result. Specifically, for a text mode wrapper, you'd do something like:
rawf = io.FileIO(named_pipe_pathname, mode="rb+")
with io.TextIOWrapper(io.BufferedRWPair(rawf, rawf), encoding='utf-8', write_through=True) as txtf:
del rawf # Remove separate reference to rawf; txtf manages lifetime now
# Example use that works (but is terrible form, since communicating with
# oneself without threading, select module, etc., is highly likely to deadlock)
# It works for this super-simple case; presumably you have some parallel real code
txtf.write("abcé\n")
txtf.flush()
print(txtf.readline(), flush=True)
I believe this will close rawf twice when txtf is closed, but luckily, double-close is harmless here (the second close does nothing, realizing it's already closed).
Solution
You can use pexpect. Here is an example using two python modules:
caller.py
import pexpect
proc = pexpect.spawn('python3 backwards.py')
proc.expect(' > ')
while True:
n = proc.sendline(input('Feed me - '))
proc.expect(' > ')
print(proc.before[n+1:].decode())
backwards.py
x = ''
while True:
x = input(x[::-1] + ' > ')
Explanation
caller.py is using a "Pseudo-TTY device" to talk to backwards.py. We are providing input with sendline and capturing input with expect (and the before attribute).
It looks like you need to create separate handles for reading and for writing: to open read/write just requires a seek method. I couldn't figure out how to timeout reading, so it's nice to add an opener (see the docstring for io.open) that opens the reader in non-blocking mode. I set up a simple echo service on a named pipe called /tmp/test_pipe:
In [1]: import io
In [2]: import os
In [3]: nonblockingOpener = lambda name, flags:os.open(name, flags|os.O_NONBLOCK)
In [4]: reader = io.open('/tmp/test_pipe', 'r', opener = nonblockingOpener)
In [5]: writer = io.open('/tmp/test_pipe', 'w')
In [6]: writer.write('Hi have a line\n')
In [7]: writer.flush()
In [8]: reader.readline()
Out[8]: 'You said: Hi have a line\n'
In [9]: reader.readline()
''

Writing COIN-OR CBC Log File

I'm using COIN-OR's CBC solver to solve some numerical optimization problems. I'm structuring the optimization problem in Python via PuLP.
I've noticed that solvers like GUROBI and CPLEX create log files, but I can't seem to figure out how to get CBC to create a log file (as opposed to printing the optimizer's progress to the screen).
Does anybody know of an option in CBC to set a log file? Re-directing all stdout to a file doesn't work for me, since I'm solving a bunch of problems in parallel and want to keep their log files separate.
Here's an example of how I'm calling the solver. This works great and prints progress to the terminal.
prob.solve(pulp.COIN_CMD(msg=1, options=['DivingVectorlength on','DivingSome on']))
Here's how I think a solution should be structured (though obviously LogFileName isn't a valid CBC option).
prob.solve(pulp.COIN_CMD(msg=1, options=['DivingVectorlength on', 'DivingSome on', 'LogFileName stats.log']))
Any help on this would be greatly appreciated. I've been going through the internet, docs, and the CBC interactive session for hours trying to figure this out.
Reusing #Mike's answer, PuLP (since 2.2) now includes the possibility to write the log to a file by passing the logPath argument with the path to the file to write.
So you can now do:
prob.solve(pulp.COIN_CMD(msg=1, logPath="stats.log", options=['DivingVectorlength on', 'DivingSome on']))
The only caveat is that you cannot longer see it "on screen", since it redirects the output to the file. You are not required to give msg=1, just logPath in this case.
The logPath argument is consistent (in PuLP >= 2.2) among several solvers: PULP_CBC_CMD, COIN_CMD, PULP_COIN_CMD, GUROBI, CPLEX, CPLEX_CMD, GUROBI_CMD.
For a solution requiring only a few lines of code in your script that invokes PuLP and CBC, see the solution by James Vogel (https://github.com/voglster, maybe) at https://groups.google.com/forum/#!topic/pulp-or-discuss/itbmTC7uNCQ, based on os.dup() and os.dup2().
I hope it isn't inappropriate to copy it here to guard against linkrot, but see the original post for line-by-line explanation and some sophisticated things I don't understand from the tempfile package. My own usage is less sophisticated, using an actual permanent filename:
from os import dup, dup2, close
f = open('capture.txt', 'w')
orig_std_out = dup(1)
dup2(f.fileno(), 1)
status = prob.solve (PULP_CBC_CMD(maxSeconds = i_max_sec, fracGap = d_opt_gap, msg=1)) # CBC time limit and relative optimality gap tolerance
print('Completion code: %d; Solution status: %s; Best obj value found: %s' % (status, LpStatus[prob.status], value(prob.objective)))
dup2(orig_std_out, 1)
close(orig_std_out)
f.close()
This leaves you with useful information in capture.txt in the current directory.
I was unable to find an answer without changing the pulp source code, but if that does not bother you, then take the following route:
navigate to the directory of your pulp install library and look at the solvers.py file.
The function of interest is solve_CBC in the COIN_CMD class. In that method, the arguments are formed into a single command to pass to the cbc-64 solver program, it is then called using the subprocess.Popen method. The stdout argument for this method is either set to None or os.devnull neither of which is very useful for us. You can see the process call on line 1340 (for PuLP 1.5.6).
cbc = subprocess.Popen((self.path + cmds).split(), stdout = pipe,
stderr = pipe)
This source also reveals that the problem (mps) and solution (sol) files are written to the /tmp directory (on UNIX machines) and that the file names include the pid of the interpreter calling it. I open a file using this id and pass it to that argument. like this:
logFilename = os.path.join(self.tmpDir, "%d-cbc.log" % pid)
logFile = open(logFilename, 'a')
cbc = subprocess.Popen((self.path + cmds).split(), stdout = logFile,
stderr = pipe)
Sure enough, in the /tmp directory I see my log files after running. You can set the verbosity with log N see the cbc help for more documentation there. Since this creates a different file for each process id, I think it will solve your problem of running multiple solvers in parallel.

python - open wav file in default program (Linux)

I want to open .wav file in default program. But it doesn´t work. This is my code:
audiofile=(myFile[index]+".wav") # I have all files in array (without ".wav")
try:
try:
os.system('xdg-open audiofile')
except:
os.system('start audiofile')
except:
print "error"
I don´t get any error, but it doesn´t work. How can I solve it? Thank you.
You aren't substituting the name of the audio file into your OS commands, so it can't possibly work.
You'd need something like:
os.system('xdg-open ' + audiofile)
This assumes that you have a default application associated with .wav files, which of course you can test by trying your command manually.
You might also want to check the return value of os.system for an error code, rather than relying on exceptions.
First of all, you should fill the variable audiofile into the command, not the string 'audiofile' itself
os.system('xdg-open %s' % audiofile)
Second,
os.system will NOT throw an exception when xdg-open or start doesn't exist in system.
Determine the type of system first by platform.system
>>> import platform
>>> platform.system()
'Linux'

Categories

Resources