5

I've been looking around for an answer to my original issue.. how do i determine (programmatically) that my win32api.ShellExecute statement executed successfully, and if a successful execution occurs, execute an os.remove() statement.

Researching I found out that the ShellExecute() call returns the HINSTANCE. Further digging I found that ShellExecute() will return an HINSTANCE > 32 if it was successful. My problem/question now is, how do i use it to control the rest of my program's flow? I tried using an if HINSTANCE> 32: statement to control the next part, but I get a NameError: name 'hinstance' is not defined message. Normally this wouldn't confuse me because it means i need to define the variable 'hinstance' before referencing it; however, because i thought ShellExecute is supposed to return HINSTANCE, i thought that makes it available for use?

Here is my full code where i am trying to implement this. Note that in my print_file() def i am assigning hinstance to the full win32api.ShellExecute() command in attempt to capture the hinstance along with explicitly returning it at the end of the function.. this isn't working either.

import win32print
import win32api
from os.path import isfile, join
import glob
import os
import time

source_path = "c:\\temp\\source\\"

def main():
    printer_name = win32print.GetDefaultPrinter()
    while True:
        file_queue = [f for f in glob.glob("%s\\*.txt" % source_path) if isfile(f)]
        if len(file_queue) > 0:
            for i in file_queue:
                print_file(i, printer_name)
                if hinstance > 32:
                    time.sleep(.25)
                    delete_file(i)
                print "Filename: %r has printed" % i
                print
                time.sleep(.25)
                print                
        else:
            print "No files to print. Will retry in 15 seconds"
        time.sleep(15)


def print_file(pfile, printer):
    hinstance = win32api.ShellExecute(
        0,
        "print",
        '%s' % pfile,
        '/d:"%s"' % printer,
        ".",
        0
        )
    return hinstance


def delete_file(f):
    os.remove(f)
    print f, "was deleted!"

def alert(email):
    pass

main()
CyberSamurai
  • 179
  • 1
  • 10
  • 2
    `ShellExecute` returns a value of type `HINSTANCE`, which has nothing to do with any variable names. You still need to do `hinstance = print_file(...)`. – Drew McGowen Aug 02 '13 at 20:52
  • 1
    Aha! That makese sense.. Seems like a silly oversight on my part now. Thank you. I adjusted the code so that print_file just runs the `win32api.ShellExecute(...)` without any of the hinstance bits and changed the part in main() to be like you referenced `hinstance = print_file(...)` but now print_file() returns None instead of 42L like it was before. Any ideas on that? – CyberSamurai Aug 02 '13 at 21:01
  • 1
    The caller can only get the value if print_file returns it – David Heffernan Aug 02 '13 at 21:20
  • Ok, I see what i did wrong. I adjusted my `print_file()` function to return hinstance. So now the for loop looks like: http://pastebin.com/yTX461JT How does that look to you? – CyberSamurai Aug 02 '13 at 21:27
  • That pastebin is what Drew suggested in his comment, and what I wrote in my answer. I don't think there's any more to add, – David Heffernan Aug 02 '13 at 22:10

2 Answers2

6

With ShellExecute, you will never know when the printing is complete, it depends on the size of the file and whether the printer driver buffers the contents (the printer might be waiting for you to fill the paper tray, for example).

According to this SO answer, it looks like subprocess.call() is a better solution, since it waits for the command to complete, only in this case you would need to read the registry to obtain the exe associated with the file.

ShellExecuteEx is available from pywin32, you can do something like:

import win32com.shell.shell as shell
param = '/d:"%s"' % printer
shell.ShellExecuteEx(fmask = win32com.shell.shellcon.SEE_MASK_NOASYNC, lpVerb='print', lpFile=pfile, lpParameters=param)

EDIT: code for waiting on the handle from ShellExecuteEx()

import win32com.shell.shell as shell
import win32event
#fMask = SEE_MASK_NOASYNC(0x00000100) = 256 + SEE_MASK_NOCLOSEPROCESS(0x00000040) = 64
dict = shell.ShellExecuteEx(fMask = 256 + 64, lpFile='Notepad.exe', lpParameters='Notes.txt')
hh = dict['hProcess']
print hh
ret = win32event.WaitForSingleObject(hh, -1)
print ret
Community
  • 1
  • 1
Edward Clements
  • 5,040
  • 2
  • 21
  • 27
  • 1
    subprocess.call won't handle the shell verb print. ShellExecuteEx will return a process handle on which you can wait. There's no need to wait until the printer has finished. Just until the app has queued the print job. – David Heffernan Aug 03 '13 at 14:30
  • 1
    @DavidHeffernan: I did mention that with `subprocess.call` you will need to get the exe name from the registry -- re `ShellExecuteEx`, you're right, you need to call `WaitForSingleObject` with the handle from the return dict value (ref [SO answer](http://stackoverflow.com/a/15625968/1850797)) – Edward Clements Aug 03 '13 at 15:24
  • Thanks for the extra info. I will look into using ShellExecuteEx instead. If i understand correctly, it returns a process handle that i can use to wait using `WaitForSingleObject` before moving onto the `delete_file()` part of my program ? – CyberSamurai Aug 05 '13 at 14:59
  • 1
    @CyberSamurai Indeed so. Do be aware that you may not necessarily get a process handle. Not all association/verb combinations will yield a process handle on which you can wait. If notepad is doing the printing then I think you will get a process handle. But, if it's an image that you want to print, and the association is with the default system image previewer, then don't expect a process handle. – David Heffernan Aug 05 '13 at 15:10
  • The main purpose of the program is to print text files, so it should be notepad.exe getting called to print. Eventually when i get more experienced with programming I'd like to beef it up to be format and platform independent. – CyberSamurai Aug 05 '13 at 15:51
  • 1
    @EdwardClements I was playing around with your ShellExecuteEx code snippet, and i'm having issues with the `win32com.shell.shellcon.SEE_MASK_NOASYNC` bit. It's not liking the SEE_MASK_NOASYNC part. I've tried importing win32com, win32com.shell, win32com.shell.shellcon, and even `from win32com.shell.shellcon import *` but I get `AttributeError: 'module' object has no attribute 'SEE_MASK_NOASYNC'` each time i try to use that bit. Looking at [MSDN](http://tinyurl.com/kwqy3mx) i maybe just need to put `fmask = 'SEE_MASK_NOASYNC'`. Sorry if this is a total noob mistake, just not sure how to pro – CyberSamurai Aug 05 '13 at 18:47
  • according to the MSDN link, SEE_MASK_NOASYNC = 0x00000100, so you should be able to use the value `256` instead of `win32com.shell.shellcon.SEE_MASK_NOASYNC` – Edward Clements Aug 05 '13 at 18:56
  • Ok, that along with changing the paramenter to just be `printer` got it working. Now i'm struggling with the whole part of getting the pyHANDLE to `WaitForSingleObject`. It tells me `pywintypes.error: (6, 'WaitForSingleObject', 'The handle is invalid.')` Tried `phandle = shell.ShellExecuteEx(fMask = 256, lpVerb = "open", lpFile = pfile, lpParameters = 'IS26')` then grabbing just the value i want from the dict with `handle = phandle.values()[-1]` and passing that to `WaitForSingleObject`. After that failed i tried passing the whole dict as the handle arg which obviously isn't a pyHANDLE – CyberSamurai Aug 05 '13 at 20:31
  • 1
    I found out that `ShellExecuteEx` requires an extra mask value `SEE_MASK_NOCLOSEPROCESS (0x00000040)` to return the handle, I've add code (above) which works for me -- you might not want to wait infinitely for the process to complete (in case it hangs) but to place a reasonable value to wait and prompt whether to continue or not... – Edward Clements Aug 06 '13 at 08:29
  • Ah, good find @EdwardClements. I've added that and cleaned up my code quite a bit. What do you think about [the updated code](http://pastebin.com/qeeF58EC)? It seems to be working pretty good now. Code is a lot cleaner too. Using the `ShellExecuteEx` with the `WaitForSingleObject` return codes will make it easier to add additional functionality in the future for sending out some sort of alerts/notices if something doesn't print. Thanks a million to everyone for the help – CyberSamurai Aug 09 '13 at 04:15
1

The return value of ShellExecute is what you need to test. You return that from print_file, but you then ignore it. You need to capture it and check that.

hinstance = print_file(i, printer_name)
if hinstance > 32:
    ....

However, having your print_file function leak implementation detail like an HINSTANCE seems bad. I think you would be better to check the return value of ShellExecute directly at the point of use. So try to move the > 32 check inside print_file.

Note that ShellExecute has very weak error reporting. If you want proper error reporting then you should use ShellExecuteEx instead.

Your delete/sleep loop is very brittle indeed. I'm not quite sure I can recommend anything better since I'm not sure what you are trying to achieve. However, expect to run into trouble with that part of your program.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • 1
    Thanks for the comments. I have seen a few recommends on the web for using ShellExecuteEx, but I do not see an equivalent in the Python world. I'm using pywin32 to access the win32 API, and it only has ShellExecute. So you recommend moving the `if hinstance > 32` inside of the `print_file()` ? My purpose for that is to verify the file printed successfully before moving onto the `delete_file()`. The only reason i have the `time.sleep` is to avoid deleting the file before ShellExecute() has finished with it. I got notepad.exe errors until i added the delay. Goal: print files then delete them – CyberSamurai Aug 02 '13 at 21:20