|
29 | 29 | import win32console |
30 | 30 | import winreg |
31 | 31 |
|
32 | | - from pywinauto.win32defines import SEE_MASK_NOCLOSEPROCESS, SW_HIDE |
| 32 | + from pywinauto.win32defines import SEE_MASK_NOCLOSEPROCESS, SW_HIDE, SW_SHOWMINNOACTIVE |
33 | 33 |
|
34 | 34 | API_URLS = [ |
35 | 35 | 'https://api4.my-ip.io/ip', |
|
62 | 62 | "get_win32_error_message", |
63 | 63 | "CloudRotatingFileHandler", |
64 | 64 | "run_elevated", |
65 | | - "is_uac_enabled" |
| 65 | + "is_uac_enabled", |
| 66 | + "start_elevated" |
66 | 67 | ] |
67 | 68 |
|
68 | 69 | logger = logging.getLogger(__name__) |
@@ -388,7 +389,7 @@ def run_elevated(exe_path, cwd, *args): |
388 | 389 | sei.fMask = SEE_MASK_NOCLOSEPROCESS |
389 | 390 | sei.lpVerb = "runas" |
390 | 391 | sei.lpFile = os.path.abspath(exe_path) |
391 | | - sei.lpDirectory = os.path.abspath(cwd) |
| 392 | + sei.lpDirectory = os.path.abspath(cwd) if cwd else os.path.dirname(exe_path) |
392 | 393 | sei.lpParameters = ' '.join(map(str, args)) |
393 | 394 | sei.nShow = SW_HIDE |
394 | 395 |
|
@@ -429,3 +430,44 @@ def is_uac_enabled() -> bool: |
429 | 430 | except (FileNotFoundError, PermissionError): |
430 | 431 | # if not found or permission is denied, fall back to a safe default. |
431 | 432 | return True |
| 433 | + |
| 434 | + |
| 435 | +def start_elevated(exe_path: str, cwd: str, *args) -> Optional[psutil.Process]: |
| 436 | + """ |
| 437 | + Start exe_path as Administrator and return a psutil.Process for the started process (Popen-like). |
| 438 | + Returns None on non-Windows platforms. |
| 439 | +
|
| 440 | + Note: The returned handle refers to the primary process created by ShellExecuteExW. |
| 441 | + """ |
| 442 | + if sys.platform != 'win32': |
| 443 | + return None |
| 444 | + |
| 445 | + sei = SHELLEXECUTEINFO() |
| 446 | + sei.cbSize = ctypes.sizeof(sei) |
| 447 | + sei.fMask = SEE_MASK_NOCLOSEPROCESS |
| 448 | + sei.lpVerb = "runas" |
| 449 | + sei.lpFile = os.path.abspath(exe_path) |
| 450 | + sei.lpDirectory = os.path.abspath(cwd) if cwd else os.path.dirname(exe_path) |
| 451 | + sei.lpParameters = ' '.join(map(str, args)) |
| 452 | + sei.nShow = SW_SHOWMINNOACTIVE |
| 453 | + |
| 454 | + # noinspection PyUnresolvedReferences |
| 455 | + if not ctypes.windll.shell32.ShellExecuteExW(ctypes.byref(sei)): |
| 456 | + raise ctypes.WinError() |
| 457 | + |
| 458 | + hproc = sei.hProcess |
| 459 | + |
| 460 | + # Try to get PID from the handle to wrap in psutil.Process. |
| 461 | + # Kernel32 GetProcessId returns DWORD PID. |
| 462 | + GetProcessId = ctypes.windll.kernel32.GetProcessId # type: ignore[attr-defined] |
| 463 | + GetProcessId.argtypes = [ctypes.c_void_p] |
| 464 | + GetProcessId.restype = ctypes.c_ulong |
| 465 | + pid = GetProcessId(hproc) |
| 466 | + |
| 467 | + if pid: |
| 468 | + try: |
| 469 | + return psutil.Process(pid) |
| 470 | + except psutil.NoSuchProcess: |
| 471 | + return None |
| 472 | + else: |
| 473 | + return None |
0 commit comments