0

I need a little help with a program I'm writing. All I need it to do is launch a file in its default program and track the PID of that program (so it can be closed later). I found the following code from here:

import psutil
import subprocess
import os

file = os.getcwd() + "\\" + input()
open = subprocess.Popen(["start", "/WAIT", file], shell=True)
psutil.Process(open.pid).get_children()[0].kill()

This does open the file in the correct program, but it crashes with a

IndexError: list index out of range  error on the 

psutil.Process(open.pid).get_children()[0].kill() line.

If anyone has any clue how to acheive what I'm asking, some help would be greatly appreciated! Thanks

Community
  • 1
  • 1

1 Answers1

3

Well, first, try breaking it down into pieces instead of trying to guess where in that big expression something went wrong. Print out open.pid, Process(open.pid), Process(open.pid).get_children(), and Process(open.pid).get_children()[0] and it should be pretty obvious what the problem is: the start process has no children.

Second, start doesn't always launch a new process. It runs the Open command for a process—which can be a command string, or it can be an OLE thingy or something called via a shell extension DLL, which may end up launching a new process, but it could do something else, most commonly opening the file in an existing process for that app. If, say, you give it a .docx file, and Word is already open, it asks the existing copy of Word to deal with the file.*

* Although in modern versions of Word, I think it's actually word.exe that does that, rather than relying on the old OLE mechanisms, in which case that was a bad example…

And even if it does have to start a new process, it doesn't necessarily do so as a controlled child; it may launch a new detached process, then act the same as if it had found one via OLE. I'm pretty sure that detail isn't documented, but it makes sense (imagine how you'd write start if you were them), and you can test it pretty easily:

  • Using the pskill tool, or psutil from within Python, kill the start process; the app doesn't even notice.
  • Use your favorite third-party command-line tool, or just pop up the process explorer GUI thingy, and see who the parent of the app is.

At any rate, if the app isn't a child of start, you can't find it by looking at the children of start.

Also, if you look at the Win32 API behind start (and Python's os.startfile, ShellExecute, notice that in modern Windows, it doesn't even return a handle to the process.


So, how can you do this?

Well, as the start docs explain:

You can run nonexecutable files through their file association by typing the name of the file as a command. For more information about creating these associations in a command script by using assoc and ftype

So, look at assoc and ftype. You can run assoc to get the file type association for your file, then use ftype to get the command strings for the Open command for that file type, then fill in the %0 etc. variables yourself, then run it. (If the command isn't a command string, then it's an old OLE-type program, and you can't do this—but then that's exactly the case where you might not be getting a new process in the first place…)


Alternatively, you may have noticed that the ShellExecute docs say:

To obtain information about the application that is launched as a result of calling ShellExecute, use ShellExecuteEx.

If you do that, you can set the SEE_MASK_NOCLOSEPROCESS in the fMask flags, and the hProcess member will be an HINSTANCE for the process (but only if a new process was launched—if a shell extension found an existing process or whatever, you'll still get NULL here, of course). You can then use the normal Win32 process APIs on it, or just get its pid and use it with psutil. This is all doable, but probably painful, with ctypes; you'd probably want to use the pywin32 wrappers.

abarnert
  • 354,177
  • 51
  • 601
  • 671