Funktionierender Prototyp des Serious Games zur Vermittlung von Wissen zu Software-Engineering-Arbeitsmodellen.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

winprocess.py 7.2KB

1 year ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  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 msvcrt
  15. import os
  16. import win32api
  17. import win32con
  18. import win32event
  19. import win32gui
  20. import win32process
  21. import win32security
  22. def logonUser(loginString):
  23. """
  24. Login as specified user and return handle.
  25. loginString: 'Domain\nUser\nPassword'; for local
  26. login use . or empty string as domain
  27. e.g. '.\nadministrator\nsecret_password'
  28. """
  29. domain, user, passwd = loginString.split("\n")
  30. return win32security.LogonUser(
  31. user,
  32. domain,
  33. passwd,
  34. win32con.LOGON32_LOGON_INTERACTIVE,
  35. win32con.LOGON32_PROVIDER_DEFAULT,
  36. )
  37. class Process:
  38. """
  39. A Windows process.
  40. """
  41. def __init__(
  42. self,
  43. cmd,
  44. login=None,
  45. hStdin=None,
  46. hStdout=None,
  47. hStderr=None,
  48. show=1,
  49. xy=None,
  50. xySize=None,
  51. desktop=None,
  52. ):
  53. """
  54. Create a Windows process.
  55. cmd: command to run
  56. login: run as user 'Domain\nUser\nPassword'
  57. hStdin, hStdout, hStderr:
  58. handles for process I/O; default is caller's stdin,
  59. stdout & stderr
  60. show: wShowWindow (0=SW_HIDE, 1=SW_NORMAL, ...)
  61. xy: window offset (x, y) of upper left corner in pixels
  62. xySize: window size (width, height) in pixels
  63. desktop: lpDesktop - name of desktop e.g. 'winsta0\\default'
  64. None = inherit current desktop
  65. '' = create new desktop if necessary
  66. User calling login requires additional privileges:
  67. Act as part of the operating system [not needed on Windows XP]
  68. Increase quotas
  69. Replace a process level token
  70. Login string must EITHER be an administrator's account
  71. (ordinary user can't access current desktop - see Microsoft
  72. Q165194) OR use desktop='' to run another desktop invisibly
  73. (may be very slow to startup & finalize).
  74. """
  75. si = win32process.STARTUPINFO()
  76. si.dwFlags = win32con.STARTF_USESTDHANDLES ^ win32con.STARTF_USESHOWWINDOW
  77. if hStdin is None:
  78. si.hStdInput = win32api.GetStdHandle(win32api.STD_INPUT_HANDLE)
  79. else:
  80. si.hStdInput = hStdin
  81. if hStdout is None:
  82. si.hStdOutput = win32api.GetStdHandle(win32api.STD_OUTPUT_HANDLE)
  83. else:
  84. si.hStdOutput = hStdout
  85. if hStderr is None:
  86. si.hStdError = win32api.GetStdHandle(win32api.STD_ERROR_HANDLE)
  87. else:
  88. si.hStdError = hStderr
  89. si.wShowWindow = show
  90. if xy is not None:
  91. si.dwX, si.dwY = xy
  92. si.dwFlags ^= win32con.STARTF_USEPOSITION
  93. if xySize is not None:
  94. si.dwXSize, si.dwYSize = xySize
  95. si.dwFlags ^= win32con.STARTF_USESIZE
  96. if desktop is not None:
  97. si.lpDesktop = desktop
  98. procArgs = (
  99. None, # appName
  100. cmd, # commandLine
  101. None, # processAttributes
  102. None, # threadAttributes
  103. 1, # bInheritHandles
  104. win32process.CREATE_NEW_CONSOLE, # dwCreationFlags
  105. None, # newEnvironment
  106. None, # currentDirectory
  107. si,
  108. ) # startupinfo
  109. if login is not None:
  110. hUser = logonUser(login)
  111. win32security.ImpersonateLoggedOnUser(hUser)
  112. procHandles = win32process.CreateProcessAsUser(hUser, *procArgs)
  113. win32security.RevertToSelf()
  114. else:
  115. procHandles = win32process.CreateProcess(*procArgs)
  116. self.hProcess, self.hThread, self.PId, self.TId = procHandles
  117. def wait(self, mSec=None):
  118. """
  119. Wait for process to finish or for specified number of
  120. milliseconds to elapse.
  121. """
  122. if mSec is None:
  123. mSec = win32event.INFINITE
  124. return win32event.WaitForSingleObject(self.hProcess, mSec)
  125. def kill(self, gracePeriod=5000):
  126. """
  127. Kill process. Try for an orderly shutdown via WM_CLOSE. If
  128. still running after gracePeriod (5 sec. default), terminate.
  129. """
  130. win32gui.EnumWindows(self.__close__, 0)
  131. if self.wait(gracePeriod) != win32event.WAIT_OBJECT_0:
  132. win32process.TerminateProcess(self.hProcess, 0)
  133. win32api.Sleep(100) # wait for resources to be released
  134. def __close__(self, hwnd, dummy):
  135. """
  136. EnumWindows callback - sends WM_CLOSE to any window
  137. owned by this process.
  138. """
  139. TId, PId = win32process.GetWindowThreadProcessId(hwnd)
  140. if PId == self.PId:
  141. win32gui.PostMessage(hwnd, win32con.WM_CLOSE, 0, 0)
  142. def exitCode(self):
  143. """
  144. Return process exit code.
  145. """
  146. return win32process.GetExitCodeProcess(self.hProcess)
  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. if __name__ == "__main__":
  169. # Pipe commands to a shell and display the output in notepad
  170. print("Testing winprocess.py...")
  171. import tempfile
  172. timeoutSeconds = 15
  173. cmdString = (
  174. """\
  175. REM Test of winprocess.py piping commands to a shell.\r
  176. REM This 'notepad' process will terminate in %d seconds.\r
  177. vol\r
  178. net user\r
  179. _this_is_a_test_of_stderr_\r
  180. """
  181. % timeoutSeconds
  182. )
  183. cmd_name = tempfile.mktemp()
  184. out_name = cmd_name + ".txt"
  185. try:
  186. cmd = open(cmd_name, "w+b")
  187. out = open(out_name, "w+b")
  188. cmd.write(cmdString.encode("mbcs"))
  189. cmd.seek(0)
  190. print(
  191. "CMD.EXE exit code:",
  192. run("cmd.exe", show=0, stdin=cmd, stdout=out, stderr=out),
  193. )
  194. cmd.close()
  195. print(
  196. "NOTEPAD exit code:",
  197. run(
  198. "notepad.exe %s" % out.name,
  199. show=win32con.SW_MAXIMIZE,
  200. mSec=timeoutSeconds * 1000,
  201. ),
  202. )
  203. out.close()
  204. finally:
  205. for n in (cmd_name, out_name):
  206. try:
  207. os.unlink(cmd_name)
  208. except os.error:
  209. pass