_process_win32_controller.py
574 lines
| 20.9 KiB
| text/x-python
|
PythonLexer
Mark Wiebe
|
r6118 | """Windows-specific implementation of process utilities with direct WinAPI. | ||
This file is meant to be used by process.py | ||||
""" | ||||
#----------------------------------------------------------------------------- | ||||
# Copyright (C) 2010-2011 The IPython Development Team | ||||
# | ||||
# Distributed under the terms of the BSD License. The full license is in | ||||
# the file COPYING, distributed as part of this software. | ||||
#----------------------------------------------------------------------------- | ||||
from __future__ import print_function | ||||
# stdlib | ||||
Thomas Kluyver
|
r11127 | import os, sys, threading | ||
Mark Wiebe
|
r6118 | import ctypes, msvcrt | ||
# Win32 API types needed for the API calls | ||||
from ctypes import POINTER | ||||
from ctypes.wintypes import HANDLE, HLOCAL, LPVOID, WORD, DWORD, BOOL, \ | ||||
ULONG, LPCWSTR | ||||
LPDWORD = POINTER(DWORD) | ||||
LPHANDLE = POINTER(HANDLE) | ||||
ULONG_PTR = POINTER(ULONG) | ||||
class SECURITY_ATTRIBUTES(ctypes.Structure): | ||||
_fields_ = [("nLength", DWORD), | ||||
("lpSecurityDescriptor", LPVOID), | ||||
("bInheritHandle", BOOL)] | ||||
LPSECURITY_ATTRIBUTES = POINTER(SECURITY_ATTRIBUTES) | ||||
class STARTUPINFO(ctypes.Structure): | ||||
_fields_ = [("cb", DWORD), | ||||
("lpReserved", LPCWSTR), | ||||
("lpDesktop", LPCWSTR), | ||||
("lpTitle", LPCWSTR), | ||||
("dwX", DWORD), | ||||
("dwY", DWORD), | ||||
("dwXSize", DWORD), | ||||
("dwYSize", DWORD), | ||||
("dwXCountChars", DWORD), | ||||
("dwYCountChars", DWORD), | ||||
("dwFillAttribute", DWORD), | ||||
("dwFlags", DWORD), | ||||
("wShowWindow", WORD), | ||||
("cbReserved2", WORD), | ||||
("lpReserved2", LPVOID), | ||||
("hStdInput", HANDLE), | ||||
("hStdOutput", HANDLE), | ||||
("hStdError", HANDLE)] | ||||
LPSTARTUPINFO = POINTER(STARTUPINFO) | ||||
class PROCESS_INFORMATION(ctypes.Structure): | ||||
_fields_ = [("hProcess", HANDLE), | ||||
("hThread", HANDLE), | ||||
("dwProcessId", DWORD), | ||||
("dwThreadId", DWORD)] | ||||
LPPROCESS_INFORMATION = POINTER(PROCESS_INFORMATION) | ||||
# Win32 API constants needed | ||||
ERROR_HANDLE_EOF = 38 | ||||
ERROR_BROKEN_PIPE = 109 | ||||
Mark Wiebe
|
r6130 | ERROR_NO_DATA = 232 | ||
Mark Wiebe
|
r6118 | HANDLE_FLAG_INHERIT = 0x0001 | ||
STARTF_USESTDHANDLES = 0x0100 | ||||
CREATE_SUSPENDED = 0x0004 | ||||
CREATE_NEW_CONSOLE = 0x0010 | ||||
Mark Wiebe
|
r6130 | CREATE_NO_WINDOW = 0x08000000 | ||
Mark Wiebe
|
r6118 | STILL_ACTIVE = 259 | ||
WAIT_TIMEOUT = 0x0102 | ||||
WAIT_FAILED = 0xFFFFFFFF | ||||
INFINITE = 0xFFFFFFFF | ||||
DUPLICATE_SAME_ACCESS = 0x00000002 | ||||
Mark Wiebe
|
r6130 | ENABLE_ECHO_INPUT = 0x0004 | ||
ENABLE_LINE_INPUT = 0x0002 | ||||
ENABLE_PROCESSED_INPUT = 0x0001 | ||||
Mark Wiebe
|
r6118 | |||
# Win32 API functions needed | ||||
GetLastError = ctypes.windll.kernel32.GetLastError | ||||
GetLastError.argtypes = [] | ||||
GetLastError.restype = DWORD | ||||
CreateFile = ctypes.windll.kernel32.CreateFileW | ||||
CreateFile.argtypes = [LPCWSTR, DWORD, DWORD, LPVOID, DWORD, DWORD, HANDLE] | ||||
CreateFile.restype = HANDLE | ||||
CreatePipe = ctypes.windll.kernel32.CreatePipe | ||||
CreatePipe.argtypes = [POINTER(HANDLE), POINTER(HANDLE), | ||||
LPSECURITY_ATTRIBUTES, DWORD] | ||||
CreatePipe.restype = BOOL | ||||
CreateProcess = ctypes.windll.kernel32.CreateProcessW | ||||
CreateProcess.argtypes = [LPCWSTR, LPCWSTR, LPSECURITY_ATTRIBUTES, | ||||
LPSECURITY_ATTRIBUTES, BOOL, DWORD, LPVOID, LPCWSTR, LPSTARTUPINFO, | ||||
LPPROCESS_INFORMATION] | ||||
CreateProcess.restype = BOOL | ||||
GetExitCodeProcess = ctypes.windll.kernel32.GetExitCodeProcess | ||||
GetExitCodeProcess.argtypes = [HANDLE, LPDWORD] | ||||
GetExitCodeProcess.restype = BOOL | ||||
GetCurrentProcess = ctypes.windll.kernel32.GetCurrentProcess | ||||
GetCurrentProcess.argtypes = [] | ||||
GetCurrentProcess.restype = HANDLE | ||||
ResumeThread = ctypes.windll.kernel32.ResumeThread | ||||
ResumeThread.argtypes = [HANDLE] | ||||
ResumeThread.restype = DWORD | ||||
ReadFile = ctypes.windll.kernel32.ReadFile | ||||
ReadFile.argtypes = [HANDLE, LPVOID, DWORD, LPDWORD, LPVOID] | ||||
ReadFile.restype = BOOL | ||||
WriteFile = ctypes.windll.kernel32.WriteFile | ||||
WriteFile.argtypes = [HANDLE, LPVOID, DWORD, LPDWORD, LPVOID] | ||||
WriteFile.restype = BOOL | ||||
Mark Wiebe
|
r6130 | GetConsoleMode = ctypes.windll.kernel32.GetConsoleMode | ||
GetConsoleMode.argtypes = [HANDLE, LPDWORD] | ||||
GetConsoleMode.restype = BOOL | ||||
SetConsoleMode = ctypes.windll.kernel32.SetConsoleMode | ||||
SetConsoleMode.argtypes = [HANDLE, DWORD] | ||||
SetConsoleMode.restype = BOOL | ||||
FlushConsoleInputBuffer = ctypes.windll.kernel32.FlushConsoleInputBuffer | ||||
FlushConsoleInputBuffer.argtypes = [HANDLE] | ||||
FlushConsoleInputBuffer.restype = BOOL | ||||
Mark Wiebe
|
r6118 | WaitForSingleObject = ctypes.windll.kernel32.WaitForSingleObject | ||
WaitForSingleObject.argtypes = [HANDLE, DWORD] | ||||
WaitForSingleObject.restype = DWORD | ||||
DuplicateHandle = ctypes.windll.kernel32.DuplicateHandle | ||||
DuplicateHandle.argtypes = [HANDLE, HANDLE, HANDLE, LPHANDLE, | ||||
DWORD, BOOL, DWORD] | ||||
DuplicateHandle.restype = BOOL | ||||
SetHandleInformation = ctypes.windll.kernel32.SetHandleInformation | ||||
SetHandleInformation.argtypes = [HANDLE, DWORD, DWORD] | ||||
SetHandleInformation.restype = BOOL | ||||
CloseHandle = ctypes.windll.kernel32.CloseHandle | ||||
CloseHandle.argtypes = [HANDLE] | ||||
CloseHandle.restype = BOOL | ||||
CommandLineToArgvW = ctypes.windll.shell32.CommandLineToArgvW | ||||
CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(ctypes.c_int)] | ||||
CommandLineToArgvW.restype = POINTER(LPCWSTR) | ||||
LocalFree = ctypes.windll.kernel32.LocalFree | ||||
LocalFree.argtypes = [HLOCAL] | ||||
LocalFree.restype = HLOCAL | ||||
class AvoidUNCPath(object): | ||||
"""A context manager to protect command execution from UNC paths. | ||||
In the Win32 API, commands can't be invoked with the cwd being a UNC path. | ||||
This context manager temporarily changes directory to the 'C:' drive on | ||||
entering, and restores the original working directory on exit. | ||||
The context manager returns the starting working directory *if* it made a | ||||
change and None otherwise, so that users can apply the necessary adjustment | ||||
to their system calls in the event of a change. | ||||
Thomas Kluyver
|
r12553 | Examples | ||
-------- | ||||
Mark Wiebe
|
r6118 | :: | ||
cmd = 'dir' | ||||
with AvoidUNCPath() as path: | ||||
if path is not None: | ||||
cmd = '"pushd %s &&"%s' % (path, cmd) | ||||
os.system(cmd) | ||||
""" | ||||
def __enter__(self): | ||||
self.path = os.getcwdu() | ||||
self.is_unc_path = self.path.startswith(r"\\") | ||||
if self.is_unc_path: | ||||
# change to c drive (as cmd.exe cannot handle UNC addresses) | ||||
os.chdir("C:") | ||||
return self.path | ||||
else: | ||||
# We return None to signal that there was no change in the working | ||||
# directory | ||||
return None | ||||
def __exit__(self, exc_type, exc_value, traceback): | ||||
if self.is_unc_path: | ||||
os.chdir(self.path) | ||||
class Win32ShellCommandController(object): | ||||
"""Runs a shell command in a 'with' context. | ||||
This implementation is Win32-specific. | ||||
Example: | ||||
# Runs the command interactively with default console stdin/stdout | ||||
with ShellCommandController('python -i') as scc: | ||||
scc.run() | ||||
# Runs the command using the provided functions for stdin/stdout | ||||
def my_stdout_func(s): | ||||
# print or save the string 's' | ||||
write_to_stdout(s) | ||||
def my_stdin_func(): | ||||
# If input is available, return it as a string. | ||||
if input_available(): | ||||
return get_input() | ||||
# If no input available, return None after a short delay to | ||||
# keep from blocking. | ||||
else: | ||||
time.sleep(0.01) | ||||
return None | ||||
with ShellCommandController('python -i') as scc: | ||||
scc.run(my_stdout_func, my_stdin_func) | ||||
""" | ||||
def __init__(self, cmd, mergeout = True): | ||||
"""Initializes the shell command controller. | ||||
The cmd is the program to execute, and mergeout is | ||||
whether to blend stdout and stderr into one output | ||||
in stdout. Merging them together in this fashion more | ||||
reliably keeps stdout and stderr in the correct order | ||||
especially for interactive shell usage. | ||||
""" | ||||
self.cmd = cmd | ||||
self.mergeout = mergeout | ||||
def __enter__(self): | ||||
cmd = self.cmd | ||||
mergeout = self.mergeout | ||||
self.hstdout, self.hstdin, self.hstderr = None, None, None | ||||
self.piProcInfo = None | ||||
try: | ||||
p_hstdout, c_hstdout, p_hstderr, \ | ||||
c_hstderr, p_hstdin, c_hstdin = [None]*6 | ||||
# SECURITY_ATTRIBUTES with inherit handle set to True | ||||
saAttr = SECURITY_ATTRIBUTES() | ||||
saAttr.nLength = ctypes.sizeof(saAttr) | ||||
saAttr.bInheritHandle = True | ||||
saAttr.lpSecurityDescriptor = None | ||||
def create_pipe(uninherit): | ||||
"""Creates a Windows pipe, which consists of two handles. | ||||
The 'uninherit' parameter controls which handle is not | ||||
inherited by the child process. | ||||
""" | ||||
handles = HANDLE(), HANDLE() | ||||
if not CreatePipe(ctypes.byref(handles[0]), | ||||
ctypes.byref(handles[1]), ctypes.byref(saAttr), 0): | ||||
raise ctypes.WinError() | ||||
if not SetHandleInformation(handles[uninherit], | ||||
HANDLE_FLAG_INHERIT, 0): | ||||
raise ctypes.WinError() | ||||
return handles[0].value, handles[1].value | ||||
p_hstdout, c_hstdout = create_pipe(uninherit=0) | ||||
# 'mergeout' signals that stdout and stderr should be merged. | ||||
# We do that by using one pipe for both of them. | ||||
if mergeout: | ||||
c_hstderr = HANDLE() | ||||
if not DuplicateHandle(GetCurrentProcess(), c_hstdout, | ||||
GetCurrentProcess(), ctypes.byref(c_hstderr), | ||||
0, True, DUPLICATE_SAME_ACCESS): | ||||
raise ctypes.WinError() | ||||
else: | ||||
p_hstderr, c_hstderr = create_pipe(uninherit=0) | ||||
c_hstdin, p_hstdin = create_pipe(uninherit=1) | ||||
# Create the process object | ||||
piProcInfo = PROCESS_INFORMATION() | ||||
siStartInfo = STARTUPINFO() | ||||
siStartInfo.cb = ctypes.sizeof(siStartInfo) | ||||
siStartInfo.hStdInput = c_hstdin | ||||
siStartInfo.hStdOutput = c_hstdout | ||||
siStartInfo.hStdError = c_hstderr | ||||
siStartInfo.dwFlags = STARTF_USESTDHANDLES | ||||
Mark Wiebe
|
r6130 | dwCreationFlags = CREATE_SUSPENDED | CREATE_NO_WINDOW # | CREATE_NEW_CONSOLE | ||
Mark Wiebe
|
r6118 | |||
if not CreateProcess(None, | ||||
u"cmd.exe /c " + cmd, | ||||
None, None, True, dwCreationFlags, | ||||
None, None, ctypes.byref(siStartInfo), | ||||
ctypes.byref(piProcInfo)): | ||||
raise ctypes.WinError() | ||||
# Close this process's versions of the child handles | ||||
CloseHandle(c_hstdin) | ||||
c_hstdin = None | ||||
CloseHandle(c_hstdout) | ||||
c_hstdout = None | ||||
if c_hstderr != None: | ||||
CloseHandle(c_hstderr) | ||||
c_hstderr = None | ||||
# Transfer ownership of the parent handles to the object | ||||
self.hstdin = p_hstdin | ||||
p_hstdin = None | ||||
self.hstdout = p_hstdout | ||||
p_hstdout = None | ||||
if not mergeout: | ||||
self.hstderr = p_hstderr | ||||
p_hstderr = None | ||||
self.piProcInfo = piProcInfo | ||||
finally: | ||||
if p_hstdin: | ||||
CloseHandle(p_hstdin) | ||||
if c_hstdin: | ||||
CloseHandle(c_hstdin) | ||||
if p_hstdout: | ||||
CloseHandle(p_hstdout) | ||||
if c_hstdout: | ||||
CloseHandle(c_hstdout) | ||||
if p_hstderr: | ||||
CloseHandle(p_hstderr) | ||||
if c_hstderr: | ||||
CloseHandle(c_hstderr) | ||||
return self | ||||
def _stdin_thread(self, handle, hprocess, func, stdout_func): | ||||
exitCode = DWORD() | ||||
bytesWritten = DWORD(0) | ||||
while True: | ||||
#print("stdin thread loop start") | ||||
# Get the input string (may be bytes or unicode) | ||||
data = func() | ||||
# None signals to poll whether the process has exited | ||||
if data is None: | ||||
#print("checking for process completion") | ||||
if not GetExitCodeProcess(hprocess, ctypes.byref(exitCode)): | ||||
raise ctypes.WinError() | ||||
if exitCode.value != STILL_ACTIVE: | ||||
return | ||||
# TESTING: Does zero-sized writefile help? | ||||
if not WriteFile(handle, "", 0, | ||||
ctypes.byref(bytesWritten), None): | ||||
raise ctypes.WinError() | ||||
continue | ||||
#print("\nGot str %s\n" % repr(data), file=sys.stderr) | ||||
# Encode the string to the console encoding | ||||
if isinstance(data, unicode): #FIXME: Python3 | ||||
data = data.encode('utf_8') | ||||
# What we have now must be a string of bytes | ||||
if not isinstance(data, str): #FIXME: Python3 | ||||
raise RuntimeError("internal stdin function string error") | ||||
# An empty string signals EOF | ||||
if len(data) == 0: | ||||
return | ||||
# In a windows console, sometimes the input is echoed, | ||||
# but sometimes not. How do we determine when to do this? | ||||
stdout_func(data) | ||||
# WriteFile may not accept all the data at once. | ||||
# Loop until everything is processed | ||||
while len(data) != 0: | ||||
#print("Calling writefile") | ||||
if not WriteFile(handle, data, len(data), | ||||
ctypes.byref(bytesWritten), None): | ||||
Mark Wiebe
|
r6130 | # This occurs at exit | ||
if GetLastError() == ERROR_NO_DATA: | ||||
return | ||||
Mark Wiebe
|
r6118 | raise ctypes.WinError() | ||
#print("Called writefile") | ||||
data = data[bytesWritten.value:] | ||||
def _stdout_thread(self, handle, func): | ||||
# Allocate the output buffer | ||||
data = ctypes.create_string_buffer(4096) | ||||
while True: | ||||
bytesRead = DWORD(0) | ||||
if not ReadFile(handle, data, 4096, | ||||
ctypes.byref(bytesRead), None): | ||||
le = GetLastError() | ||||
if le == ERROR_BROKEN_PIPE: | ||||
return | ||||
else: | ||||
raise ctypes.WinError() | ||||
# FIXME: Python3 | ||||
s = data.value[0:bytesRead.value] | ||||
#print("\nv: %s" % repr(s), file=sys.stderr) | ||||
func(s.decode('utf_8', 'replace')) | ||||
def run(self, stdout_func = None, stdin_func = None, stderr_func = None): | ||||
"""Runs the process, using the provided functions for I/O. | ||||
The function stdin_func should return strings whenever a | ||||
character or characters become available. | ||||
The functions stdout_func and stderr_func are called whenever | ||||
something is printed to stdout or stderr, respectively. | ||||
These functions are called from different threads (but not | ||||
concurrently, because of the GIL). | ||||
""" | ||||
if stdout_func == None and stdin_func == None and stderr_func == None: | ||||
return self._run_stdio() | ||||
Mark Wiebe
|
r6130 | if stderr_func != None and self.mergeout: | ||
Mark Wiebe
|
r6118 | raise RuntimeError("Shell command was initiated with " | ||
"merged stdin/stdout, but a separate stderr_func " | ||||
"was provided to the run() method") | ||||
# Create a thread for each input/output handle | ||||
Mark Wiebe
|
r6130 | stdin_thread = None | ||
Mark Wiebe
|
r6118 | threads = [] | ||
if stdin_func: | ||||
Mark Wiebe
|
r6130 | stdin_thread = threading.Thread(target=self._stdin_thread, | ||
Mark Wiebe
|
r6118 | args=(self.hstdin, self.piProcInfo.hProcess, | ||
Mark Wiebe
|
r6130 | stdin_func, stdout_func)) | ||
Mark Wiebe
|
r6118 | threads.append(threading.Thread(target=self._stdout_thread, | ||
args=(self.hstdout, stdout_func))) | ||||
Mark Wiebe
|
r6130 | if not self.mergeout: | ||
Mark Wiebe
|
r6118 | if stderr_func == None: | ||
stderr_func = stdout_func | ||||
threads.append(threading.Thread(target=self._stdout_thread, | ||||
args=(self.hstderr, stderr_func))) | ||||
# Start the I/O threads and the process | ||||
if ResumeThread(self.piProcInfo.hThread) == 0xFFFFFFFF: | ||||
raise ctypes.WinError() | ||||
Mark Wiebe
|
r6130 | if stdin_thread is not None: | ||
stdin_thread.start() | ||||
Mark Wiebe
|
r6118 | for thread in threads: | ||
thread.start() | ||||
# Wait for the process to complete | ||||
if WaitForSingleObject(self.piProcInfo.hProcess, INFINITE) == \ | ||||
WAIT_FAILED: | ||||
raise ctypes.WinError() | ||||
# Wait for the I/O threads to complete | ||||
for thread in threads: | ||||
thread.join() | ||||
Mark Wiebe
|
r6130 | # Wait for the stdin thread to complete | ||
if stdin_thread is not None: | ||||
stdin_thread.join() | ||||
def _stdin_raw_nonblock(self): | ||||
"""Use the raw Win32 handle of sys.stdin to do non-blocking reads""" | ||||
# WARNING: This is experimental, and produces inconsistent results. | ||||
# It's possible for the handle not to be appropriate for use | ||||
# with WaitForSingleObject, among other things. | ||||
handle = msvcrt.get_osfhandle(sys.stdin.fileno()) | ||||
result = WaitForSingleObject(handle, 100) | ||||
if result == WAIT_FAILED: | ||||
raise ctypes.WinError() | ||||
elif result == WAIT_TIMEOUT: | ||||
print(".", end='') | ||||
Mark Wiebe
|
r6118 | return None | ||
Mark Wiebe
|
r6130 | else: | ||
data = ctypes.create_string_buffer(256) | ||||
bytesRead = DWORD(0) | ||||
print('?', end='') | ||||
if not ReadFile(handle, data, 256, | ||||
ctypes.byref(bytesRead), None): | ||||
raise ctypes.WinError() | ||||
# This ensures the non-blocking works with an actual console | ||||
# Not checking the error, so the processing will still work with | ||||
# other handle types | ||||
FlushConsoleInputBuffer(handle) | ||||
data = data.value | ||||
data = data.replace('\r\n', '\n') | ||||
data = data.replace('\r', '\n') | ||||
print(repr(data) + " ", end='') | ||||
return data | ||||
def _stdin_raw_block(self): | ||||
"""Use a blocking stdin read""" | ||||
# The big problem with the blocking read is that it doesn't | ||||
# exit when it's supposed to in all contexts. An extra | ||||
# key-press may be required to trigger the exit. | ||||
try: | ||||
data = sys.stdin.read(1) | ||||
data = data.replace('\r', '\n') | ||||
return data | ||||
except WindowsError as we: | ||||
if we.winerror == ERROR_NO_DATA: | ||||
# This error occurs when the pipe is closed | ||||
return None | ||||
else: | ||||
# Otherwise let the error propagate | ||||
raise we | ||||
Mark Wiebe
|
r6118 | |||
def _stdout_raw(self, s): | ||||
"""Writes the string to stdout""" | ||||
print(s, end='', file=sys.stdout) | ||||
Mark Wiebe
|
r6130 | sys.stdout.flush() | ||
Mark Wiebe
|
r6118 | |||
def _stderr_raw(self, s): | ||||
"""Writes the string to stdout""" | ||||
print(s, end='', file=sys.stderr) | ||||
Mark Wiebe
|
r6130 | sys.stderr.flush() | ||
Mark Wiebe
|
r6118 | |||
def _run_stdio(self): | ||||
"""Runs the process using the system standard I/O. | ||||
IMPORTANT: stdin needs to be asynchronous, so the Python | ||||
sys.stdin object is not used. Instead, | ||||
msvcrt.kbhit/getwch are used asynchronously. | ||||
""" | ||||
Mark Wiebe
|
r6130 | # Disable Line and Echo mode | ||
#lpMode = DWORD() | ||||
#handle = msvcrt.get_osfhandle(sys.stdin.fileno()) | ||||
#if GetConsoleMode(handle, ctypes.byref(lpMode)): | ||||
# set_console_mode = True | ||||
# if not SetConsoleMode(handle, lpMode.value & | ||||
# ~(ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT)): | ||||
# raise ctypes.WinError() | ||||
if self.mergeout: | ||||
Mark Wiebe
|
r6118 | return self.run(stdout_func = self._stdout_raw, | ||
Mark Wiebe
|
r6130 | stdin_func = self._stdin_raw_block) | ||
Mark Wiebe
|
r6118 | else: | ||
return self.run(stdout_func = self._stdout_raw, | ||||
Mark Wiebe
|
r6130 | stdin_func = self._stdin_raw_block, | ||
stderr_func = self._stderr_raw) | ||||
# Restore the previous console mode | ||||
#if set_console_mode: | ||||
# if not SetConsoleMode(handle, lpMode.value): | ||||
# raise ctypes.WinError() | ||||
Mark Wiebe
|
r6118 | |||
def __exit__(self, exc_type, exc_value, traceback): | ||||
if self.hstdin: | ||||
CloseHandle(self.hstdin) | ||||
self.hstdin = None | ||||
if self.hstdout: | ||||
CloseHandle(self.hstdout) | ||||
self.hstdout = None | ||||
if self.hstderr: | ||||
CloseHandle(self.hstderr) | ||||
self.hstderr = None | ||||
if self.piProcInfo != None: | ||||
CloseHandle(self.piProcInfo.hProcess) | ||||
CloseHandle(self.piProcInfo.hThread) | ||||
self.piProcInfo = None | ||||
def system(cmd): | ||||
"""Win32 version of os.system() that works with network shares. | ||||
Note that this implementation returns None, as meant for use in IPython. | ||||
Parameters | ||||
---------- | ||||
cmd : str | ||||
A command to be executed in the system shell. | ||||
Returns | ||||
------- | ||||
None : we explicitly do NOT return the subprocess status code, as this | ||||
utility is meant to be used extensively in IPython, where any return value | ||||
would trigger :func:`sys.displayhook` calls. | ||||
""" | ||||
with AvoidUNCPath() as path: | ||||
if path is not None: | ||||
cmd = '"pushd %s &&"%s' % (path, cmd) | ||||
with Win32ShellCommandController(cmd) as scc: | ||||
scc.run() | ||||
if __name__ == "__main__": | ||||
print("Test starting!") | ||||
#system("cmd") | ||||
system("python -i") | ||||
print("Test finished!") | ||||