winprocess.py 7.2 KB

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