1 """
2 Windows Process Control
3
4 winprocess.run launches a child process and returns the exit code.
5 Optionally, it can:
6 redirect stdin, stdout & stderr to files
7 run the command as another user
8 limit the process's running time
9 control the process window (location, size, window state, desktop)
10 Works on Windows NT, 2000 & XP. Requires Mark Hammond's win32
11 extensions.
12
13 This code is free for any purpose, with no warranty of any kind.
14 -- John B. Dell'Aquila <jbd@alum.mit.edu>
15 """
16
17 import win32api, win32process, win32security
18 import win32event, win32con, msvcrt, win32gui
19
20
22 """
23 Login as specified user and return handle.
24 loginString: 'Domain\nUser\nPassword'; for local
25 login use . or empty string as domain
26 e.g. '.\nadministrator\nsecret_password'
27 """
28 domain, user, passwd = loginString.split('\n')
29 return win32security.LogonUser(
30 user,
31 domain,
32 passwd,
33 win32con.LOGON32_LOGON_INTERACTIVE,
34 win32con.LOGON32_PROVIDER_DEFAULT
35 )
36
37
39 """
40 A Windows process.
41 """
42
43 - def __init__(self, cmd, login=None,
44 hStdin=None, hStdout=None, hStderr=None,
45 show=1, xy=None, xySize=None,
46 desktop=None):
47 """
48 Create a Windows process.
49 cmd: command to run
50 login: run as user 'Domain\nUser\nPassword'
51 hStdin, hStdout, hStderr:
52 handles for process I/O; default is caller's stdin,
53 stdout & stderr
54 show: wShowWindow (0=SW_HIDE, 1=SW_NORMAL, ...)
55 xy: window offset (x, y) of upper left corner in pixels
56 xySize: window size (width, height) in pixels
57 desktop: lpDesktop - name of desktop e.g. 'winsta0\\default'
58 None = inherit current desktop
59 '' = create new desktop if necessary
60
61 User calling login requires additional privileges:
62 Act as part of the operating system [not needed on Windows XP]
63 Increase quotas
64 Replace a process level token
65 Login string must EITHER be an administrator's account
66 (ordinary user can't access current desktop - see Microsoft
67 Q165194) OR use desktop='' to run another desktop invisibly
68 (may be very slow to startup & finalize).
69 """
70 si = win32process.STARTUPINFO()
71 si.dwFlags = (win32con.STARTF_USESTDHANDLES ^
72 win32con.STARTF_USESHOWWINDOW)
73 if hStdin is None:
74 si.hStdInput = win32api.GetStdHandle(win32api.STD_INPUT_HANDLE)
75 else:
76 si.hStdInput = hStdin
77 if hStdout is None:
78 si.hStdOutput = win32api.GetStdHandle(win32api.STD_OUTPUT_HANDLE)
79 else:
80 si.hStdOutput = hStdout
81 if hStderr is None:
82 si.hStdError = win32api.GetStdHandle(win32api.STD_ERROR_HANDLE)
83 else:
84 si.hStdError = hStderr
85 si.wShowWindow = show
86 if xy is not None:
87 si.dwX, si.dwY = xy
88 si.dwFlags ^= win32con.STARTF_USEPOSITION
89 if xySize is not None:
90 si.dwXSize, si.dwYSize = xySize
91 si.dwFlags ^= win32con.STARTF_USESIZE
92 if desktop is not None:
93 si.lpDesktop = desktop
94 procArgs = (None,
95 cmd,
96 None,
97 None,
98 1,
99 win32process.CREATE_NEW_CONSOLE,
100 None,
101 None,
102 si)
103 if login is not None:
104 hUser = logonUser(login)
105 win32security.ImpersonateLoggedOnUser(hUser)
106 procHandles = win32process.CreateProcessAsUser(hUser, *procArgs)
107 win32security.RevertToSelf()
108 else:
109 procHandles = win32process.CreateProcess(*procArgs)
110 self.hProcess, self.hThread, self.PId, self.TId = procHandles
111
112 - def wait(self, mSec=None):
113 """
114 Wait for process to finish or for specified number of
115 milliseconds to elapse.
116 """
117 if mSec is None:
118 mSec = win32event.INFINITE
119 return win32event.WaitForSingleObject(self.hProcess, mSec)
120
121 - def kill(self, gracePeriod=5000):
122 """
123 Kill process. Try for an orderly shutdown via WM_CLOSE. If
124 still running after gracePeriod (5 sec. default), terminate.
125 """
126 win32gui.EnumWindows(self.__close__, 0)
127 if self.wait(gracePeriod) != win32event.WAIT_OBJECT_0:
128 win32process.TerminateProcess(self.hProcess, 0)
129 win32api.Sleep(100)
130
132 """
133 EnumWindows callback - sends WM_CLOSE to any window
134 owned by this process.
135 """
136 TId, PId = win32process.GetWindowThreadProcessId(hwnd)
137 if PId == self.PId:
138 win32gui.PostMessage(hwnd, win32con.WM_CLOSE, 0, 0)
139
141 """
142 Return process exit code.
143 """
144 return win32process.GetExitCodeProcess(self.hProcess)
145
146
147 -def run(cmd, mSec=None, stdin=None, stdout=None, stderr=None, **kw):
148 """
149 Run cmd as a child process and return exit code.
150 mSec: terminate cmd after specified number of milliseconds
151 stdin, stdout, stderr:
152 file objects for child I/O (use hStdin etc. to attach
153 handles instead of files); default is caller's stdin,
154 stdout & stderr;
155 kw: see Process.__init__ for more keyword options
156 """
157 if stdin is not None:
158 kw['hStdin'] = msvcrt.get_osfhandle(stdin.fileno())
159 if stdout is not None:
160 kw['hStdout'] = msvcrt.get_osfhandle(stdout.fileno())
161 if stderr is not None:
162 kw['hStderr'] = msvcrt.get_osfhandle(stderr.fileno())
163 child = Process(cmd, **kw)
164 if child.wait(mSec) != win32event.WAIT_OBJECT_0:
165 child.kill()
166 raise WindowsError, 'process timeout exceeded'
167 return child.exitCode()
168
169
170 if __name__ == '__main__':
171
172
173 print 'Testing winprocess.py...'
174
175 import tempfile
176
177 timeoutSeconds = 15
178 cmdString = """\
179 REM Test of winprocess.py piping commands to a shell.\r
180 REM This window will close in %d seconds.\r
181 vol\r
182 net user\r
183 _this_is_a_test_of_stderr_\r
184 """ % timeoutSeconds
185
186 cmd, out = tempfile.TemporaryFile(), tempfile.TemporaryFile()
187 cmd.write(cmdString)
188 cmd.seek(0)
189
190 print 'CMD.EXE exit code:', run('cmd.exe', show=0, stdin=cmd,
191 stdout=out, stderr=out)
192 cmd.close()
193 print 'NOTEPAD exit code:', run('notepad.exe %s' % out.file.name,
194 show=win32con.SW_MAXIMIZE,
195 mSec=timeoutSeconds*1000)
196 out.close()
197