From 318a9fb2f4e09b44db9440b62a718d3d2687eac6 Mon Sep 17 00:00:00 2001 From: Vivian Thiebaut Date: Mon, 1 Jul 2024 16:22:13 -0400 Subject: [PATCH 1/4] Start RDP process using subprocess library instead of _process_helper --- src/ssh/azext_ssh/_process_helper.py | 113 --------------------------- src/ssh/azext_ssh/rdp_utils.py | 4 +- 2 files changed, 2 insertions(+), 115 deletions(-) delete mode 100644 src/ssh/azext_ssh/_process_helper.py diff --git a/src/ssh/azext_ssh/_process_helper.py b/src/ssh/azext_ssh/_process_helper.py deleted file mode 100644 index 11efba70af4..00000000000 --- a/src/ssh/azext_ssh/_process_helper.py +++ /dev/null @@ -1,113 +0,0 @@ -# -------------------------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for license information. -# -------------------------------------------------------------------------------------------- - -# pylint: disable=too-few-public-methods -# pylint: disable=consider-using-with -# pylint: disable=superfluous-parens - -import subprocess -from ctypes import WinDLL, c_int, c_size_t, Structure, WinError, sizeof, pointer -from ctypes.wintypes import BOOL, DWORD, HANDLE, LPVOID, LPCWSTR, LPDWORD -from knack.log import get_logger - -logger = get_logger(__name__) - - -def _errcheck(is_error_result=(lambda result: not result)): - def impl(result, func, args): - # pylint: disable=unused-argument - if is_error_result(result): - raise WinError() - - return result - - return impl - - -# Win32 CreateJobObject -kernel32 = WinDLL("kernel32") -kernel32.CreateJobObjectW.errcheck = _errcheck(lambda result: result == 0) -kernel32.CreateJobObjectW.argtypes = (LPVOID, LPCWSTR) -kernel32.CreateJobObjectW.restype = HANDLE - - -# Win32 OpenProcess -PROCESS_TERMINATE = 0x0001 -PROCESS_SET_QUOTA = 0x0100 -PROCESS_SYNCHRONIZE = 0x00100000 -kernel32.OpenProcess.errcheck = _errcheck(lambda result: result == 0) -kernel32.OpenProcess.restype = HANDLE -kernel32.OpenProcess.argtypes = (DWORD, BOOL, DWORD) - -# Win32 WaitForSingleObject -INFINITE = 0xFFFFFFFF -# kernel32.WaitForSingleObject.errcheck = _errcheck() -kernel32.WaitForSingleObject.argtypes = (HANDLE, DWORD) -kernel32.WaitForSingleObject.restype = DWORD - -# Win32 AssignProcessToJobObject -kernel32.AssignProcessToJobObject.errcheck = _errcheck() -kernel32.AssignProcessToJobObject.argtypes = (HANDLE, HANDLE) -kernel32.AssignProcessToJobObject.restype = BOOL - -# Win32 QueryInformationJobObject -JOBOBJECTCLASS = c_int -JobObjectBasicProcessIdList = JOBOBJECTCLASS(3) - - -class JOBOBJECT_BASIC_PROCESS_ID_LIST(Structure): - _fields_ = [('NumberOfAssignedProcess', DWORD), - ('NumberOfProcessIdsInList', DWORD), - ('ProcessIdList', c_size_t * 1)] - - -kernel32.QueryInformationJobObject.errcheck = _errcheck() -kernel32.QueryInformationJobObject.restype = BOOL -kernel32.QueryInformationJobObject.argtypes = (HANDLE, JOBOBJECTCLASS, LPVOID, DWORD, LPDWORD) - - -def launch_and_wait(command): - """Windows Only: Runs and waits for the command to exit. It creates a new process and - associates it with a job object. It then waits for all the job object child processes - to exit. - """ - try: - job = kernel32.CreateJobObjectW(None, None) - process = subprocess.Popen(command) - - # Terminate and set quota are required to join process to job - process_handle = kernel32.OpenProcess( - PROCESS_TERMINATE | PROCESS_SET_QUOTA, - False, - process.pid, - ) - kernel32.AssignProcessToJobObject(job, process_handle) - - job_info = JOBOBJECT_BASIC_PROCESS_ID_LIST() - job_info_size = DWORD(sizeof(job_info)) - - while True: - kernel32.QueryInformationJobObject( - job, - JobObjectBasicProcessIdList, - pointer(job_info), - job_info_size, - pointer(job_info_size)) - - # Wait for the first running child under the job object - if job_info.NumberOfProcessIdsInList > 0: - logger.debug("Waiting for process %d", job_info.ProcessIdList[0]) - # Synchronize access is required to wait on handle - child_handle = kernel32.OpenProcess( - PROCESS_SYNCHRONIZE, - False, - job_info.ProcessIdList[0], - ) - kernel32.WaitForSingleObject(child_handle, INFINITE) - else: - break - - except OSError as e: - logger.error("Could not run '%s' command. Exception: %s", command, str(e)) diff --git a/src/ssh/azext_ssh/rdp_utils.py b/src/ssh/azext_ssh/rdp_utils.py index cab42670696..aa761745574 100644 --- a/src/ssh/azext_ssh/rdp_utils.py +++ b/src/ssh/azext_ssh/rdp_utils.py @@ -82,12 +82,12 @@ def start_rdp_connection(ssh_info, delete_keys, delete_cert): def call_rdp(local_port): - from . import _process_helper if platform.system() == 'Windows': print_styled_text((Style.SUCCESS, "Launching Remote Desktop Connection")) print_styled_text((Style.IMPORTANT, "To close this session, close the Remote Desktop Connection window.")) command = [_get_rdp_path(), f"/v:localhost:{local_port}"] - _process_helper.launch_and_wait(command) + process = subprocess.Popen(command) + process.wait() def is_local_port_open(local_port): From 33975beeb9a610b3f1cf0b6106572bfcdea72aa9 Mon Sep 17 00:00:00 2001 From: Vivian Thiebaut Date: Mon, 1 Jul 2024 16:23:45 -0400 Subject: [PATCH 2/4] Update version and history --- src/ssh/HISTORY.md | 4 ++++ src/ssh/setup.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/ssh/HISTORY.md b/src/ssh/HISTORY.md index 22788f0f9ea..2d6e3cf4234 100644 --- a/src/ssh/HISTORY.md +++ b/src/ssh/HISTORY.md @@ -1,5 +1,9 @@ Release History =============== +2.0.5 +----- +* Remove ctypes dependency from RDP feature + 2.0.4 ----- * Install Arc SSH Proxy from MAR diff --git a/src/ssh/setup.py b/src/ssh/setup.py index 2403edc17e3..618fdea127b 100644 --- a/src/ssh/setup.py +++ b/src/ssh/setup.py @@ -7,7 +7,7 @@ from setuptools import setup, find_packages -VERSION = "2.0.4" +VERSION = "2.0.5" CLASSIFIERS = [ 'Development Status :: 4 - Beta', From fabb502ab19b9e94480fa1d7ef5e3da02ceb2a77 Mon Sep 17 00:00:00 2001 From: Vivian Thiebaut Date: Thu, 11 Jul 2024 11:51:03 -0400 Subject: [PATCH 3/4] Use communicate() instead of wait() to wait for the RDP process to exit, because when 32bit python is installed on 64bit machines the wait() exits before the process terminates --- src/ssh/azext_ssh/rdp_utils.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/ssh/azext_ssh/rdp_utils.py b/src/ssh/azext_ssh/rdp_utils.py index aa761745574..a785c2fe193 100644 --- a/src/ssh/azext_ssh/rdp_utils.py +++ b/src/ssh/azext_ssh/rdp_utils.py @@ -86,8 +86,10 @@ def call_rdp(local_port): print_styled_text((Style.SUCCESS, "Launching Remote Desktop Connection")) print_styled_text((Style.IMPORTANT, "To close this session, close the Remote Desktop Connection window.")) command = [_get_rdp_path(), f"/v:localhost:{local_port}"] - process = subprocess.Popen(command) - process.wait() + process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + # process.wait() doesn't work correctly when 32bit python is installed on 64bit machines + _= process.communicate() + def is_local_port_open(local_port): From b59bff62e3e0553a97251c0b6082429a0dfb8910 Mon Sep 17 00:00:00 2001 From: Vivian Thiebaut Date: Wed, 17 Jul 2024 14:55:18 -0400 Subject: [PATCH 4/4] Fix style --- src/ssh/azext_ssh/rdp_utils.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/ssh/azext_ssh/rdp_utils.py b/src/ssh/azext_ssh/rdp_utils.py index a785c2fe193..ff0e8cb6cbc 100644 --- a/src/ssh/azext_ssh/rdp_utils.py +++ b/src/ssh/azext_ssh/rdp_utils.py @@ -88,8 +88,7 @@ def call_rdp(local_port): command = [_get_rdp_path(), f"/v:localhost:{local_port}"] process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) # process.wait() doesn't work correctly when 32bit python is installed on 64bit machines - _= process.communicate() - + _ = process.communicate() def is_local_port_open(local_port):