From 445c68537c10678a8766296677316eddf86955f8 Mon Sep 17 00:00:00 2001 From: lucasg Date: Mon, 13 Apr 2020 14:31:28 +0200 Subject: [PATCH] [Device] Implement apis to retrieve the list of devices under each class --- samples/device/device_manager.py | 7 +- windows/generated_def/winfuncs.py | 18 +++++ windows/generated_def/winstructs.py | 9 +++ windows/winobject/device.py | 91 ++++++++++++++++------ windows/winproxy/apis/setupapi.py | 117 +++++++++++++++++++++++----- 5 files changed, 199 insertions(+), 43 deletions(-) diff --git a/samples/device/device_manager.py b/samples/device/device_manager.py index d4d1ed1..c5c5733 100644 --- a/samples/device/device_manager.py +++ b/samples/device/device_manager.py @@ -4,10 +4,11 @@ from windows.winobject.device import DeviceManager def main(): for device_class in DeviceManager.enumerate_active_class(): - print("-[%s] Class %s" % (device_class.guid, device_class.name)) + print("-Class %s %s [%s]" % (device_class.name, " "*(60 - min(60,len(device_class.name))), device_class.guid)) - # for device in device_class.devices: - # print(" -Device : %s" % (device.name)) + for device in device_class.devices: + if device.name != "N/A": + print(" -Device : %s" % (device.name)) # for resource in device.resources: # print(' -%s : [0x%08x - 0x%08x] (0x%04x)' % (resource.type, resource.start, resource.end, resource.flags)) diff --git a/windows/generated_def/winfuncs.py b/windows/generated_def/winfuncs.py index a9eb61a..203dbeb 100644 --- a/windows/generated_def/winfuncs.py +++ b/windows/generated_def/winfuncs.py @@ -3166,3 +3166,21 @@ SetupDiClassNameFromGuidAParams = ((1, 'ClassGuid'),(1, 'ClassName'),(1, 'ClassN SetupDiClassNameFromGuidWPrototype = WINFUNCTYPE(BOOL, POINTER(GUID), LPCWSTR, DWORD , POINTER(DWORD)) SetupDiClassNameFromGuidWParams = ((1, 'ClassGuid'),(1, 'ClassName'),(1, 'ClassNameSize'),(1, 'RequiredSize')) + +SetupDiGetClassDevsAPrototype = WINFUNCTYPE(HANDLE, POINTER(GUID), LPCSTR, HANDLE , DWORD) +SetupDiGetClassDevsAParams = ((1, 'ClassGuid'), (1, 'Enumerator'), (1, 'hwndParent'), (1, 'Flags'),) + +SetupDiGetClassDevsWPrototype = WINFUNCTYPE(HANDLE, POINTER(GUID), LPCWSTR, HANDLE , DWORD) +SetupDiGetClassDevsWParams = ((1, 'ClassGuid'), (1, 'Enumerator'), (1, 'hwndParent'), (1, 'Flags'),) + +SetupDiDestroyDeviceInfoListPrototype = WINFUNCTYPE(BOOL, HANDLE) +SetupDiDestroyDeviceInfoListParams = ((1, 'DeviceInfoSet'),) + +SetupDiEnumDeviceInfoPrototype = WINFUNCTYPE(BOOL, HANDLE, DWORD, PSP_DEVINFO_DATA) +SetupDiEnumDeviceInfoParams = ((1, 'DeviceInfoSet'),(1, 'MemberIndex'),(1, 'DeviceInfoData'),) + +SetupDiGetDeviceRegistryPropertyAPrototype = WINFUNCTYPE(BOOL, HANDLE, PSP_DEVINFO_DATA, DWORD, PDWORD, PBYTE, DWORD, PDWORD) +SetupDiGetDeviceRegistryPropertyAParams = ((1, 'DeviceInfoSet'),(1, 'DeviceInfoData'),(1, 'Property'),(1, 'PropertyRegDataType'),(1, 'PropertyBuffer'),(1, 'PropertyBufferSize'),(1, 'RequiredSize'),) + +SetupDiGetDeviceRegistryPropertyWPrototype = WINFUNCTYPE(BOOL, HANDLE, PSP_DEVINFO_DATA, DWORD, PDWORD, PBYTE, DWORD, PDWORD) +SetupDiGetDeviceRegistryPropertyWParams = ((1, 'DeviceInfoSet'),(1, 'DeviceInfoData'),(1, 'Property'),(1, 'PropertyRegDataType'),(1, 'PropertyBuffer'),(1, 'PropertyBufferSize'),(1, 'RequiredSize'),) \ No newline at end of file diff --git a/windows/generated_def/winstructs.py b/windows/generated_def/winstructs.py index fbe1556..ab4adc3 100644 --- a/windows/generated_def/winstructs.py +++ b/windows/generated_def/winstructs.py @@ -9828,3 +9828,12 @@ PWIN32_FIND_DATAW = POINTER(_WIN32_FIND_DATAW) WIN32_FIND_DATAW = _WIN32_FIND_DATAW LPWIN32_FIND_DATAW = POINTER(_WIN32_FIND_DATAW) +class _SP_DEVINFO_DATA(Structure): + _fields_ = [ + ("cbSize", DWORD), + ("ClassGuid", GUID), + ("DevInst", DWORD), + ("Reserved1", ULONG_PTR), + ] +PSP_DEVINFO_DATA = POINTER(_SP_DEVINFO_DATA) +SP_DEVINFO_DATA = _SP_DEVINFO_DATA \ No newline at end of file diff --git a/windows/winobject/device.py b/windows/winobject/device.py index fd271da..70ecd74 100644 --- a/windows/winobject/device.py +++ b/windows/winobject/device.py @@ -1,19 +1,61 @@ + import windows from windows import winproxy from windows.generated_def import windef import windows.generated_def as gdef +SPDRP_DEVICEDESC = 0x0000000 +SPDRP_FRIENDLYNAME = 0x000000C + + +class DeviceObject(object): + + + def __init__(self, dev_data, device_name): + + self.dev_data = dev_data + self._name = device_name + + @property + def name(self): + return self._name + + @classmethod + def from_device_info(cls, h_devs, device_data): + + + # DO NOT STORE h_devs since it may be invalidated in the future + try: + device_name = winproxy.SetupDiGetDeviceRegistryPropertyW(h_devs, device_data, SPDRP_FRIENDLYNAME, None) + if device_name: + device_name = device_name.decode('utf-16-le').rstrip('\x00') + else: + device_name = winproxy.SetupDiGetDeviceRegistryPropertyW(h_devs, device_data, SPDRP_DEVICEDESC, None) + if device_name: + device_name = device_name.decode('utf-16-le').rstrip('\x00') + else: + device_name = "N/A" + + except WindowsError as e: + if e.winerror == gdef.ERROR_INVALID_DATA: + return cls(device_data, "N/A") + raise + + + return cls(device_data, device_name) class DeviceClass(object): def __init__(self, guid): + # class GUID self._guid = guid + # associated class name self._name = None - - # self._devices = None - # self._h_devs = None + + # List of devices registered under the class + self._devices = None @property def name(self): @@ -24,33 +66,38 @@ class DeviceClass(object): @property def guid(self): - return self._guid + return self._guid - # @property - # def devices(self): - # if not self._devices: + @property + def devices(self): + if not self._devices: - # with OpenDeviceClass(self): - # self._devices = list(do for do in self._get_devices()) + try: + h_devs = winproxy.SetupDiGetClassDevsA(self.guid) + self._devices = list(do for do in self._get_devices(h_devs)) + finally: + winproxy.SetupDiDestroyDeviceInfoList(h_devs) - # return self._devices + return self._devices - # def _get_devices(self): + def _get_devices(self, h_devs): - # if not self._h_devs: - # return + if not h_devs: + return - # device_index = 0 + device_index = 0 - # while True: + while True: - # device_data = winproxy.SetupDiEnumDeviceInfo(self._h_devs, device_index) - # if not device_data: - # return # stop iterating + try: + device_data = winproxy.SetupDiEnumDeviceInfo(h_devs, device_index) + except WindowsError as e: + if e.winerror == gdef.ERROR_NO_MORE_ITEMS: + return # stop iterating + raise - - # device_index+=1 - # yield DeviceObject.from_device_info(self._h_devs, device_data) + device_index+=1 + yield DeviceObject.from_device_info(h_devs, device_data) def _get_class_name(self): @@ -61,7 +108,7 @@ class DeviceManager(object): @staticmethod def enumerate_active_class(): - return list(DeviceManager.enumerate_class()) + return list(DeviceManager.enumerate_class()) # return list(filter(lambda dc: len(dc.devices) > 0, DeviceManager.enumerate_class())) @staticmethod diff --git a/windows/winproxy/apis/setupapi.py b/windows/winproxy/apis/setupapi.py index f7fb039..f24e6ba 100644 --- a/windows/winproxy/apis/setupapi.py +++ b/windows/winproxy/apis/setupapi.py @@ -3,14 +3,16 @@ from ctypes import wintypes import windows.generated_def as gdef from ..apiproxy import ApiProxy, NeededParameter, is_implemented -from ..error import succeed_on_zero, fail_on_zero +from ..error import succeed_on_zero, fail_on_zero, result_is_handle, no_error_check MAX_CLASS_NAME_LEN = 32 +MAX_DEV_LEN = 1000 + class SetupApiProxy(ApiProxy): APIDLL = "SetupApi" - default_error_check = staticmethod(succeed_on_zero) + default_error_check = staticmethod(fail_on_zero) @SetupApiProxy() @@ -31,7 +33,7 @@ def SetupDiClassNameFromGuidA(Guid): raw_class_name = bytes(class_name) return raw_class_name.decode("utf-8").rstrip("\x00") -@SetupApiProxy(error_check=fail_on_zero) +@SetupApiProxy() def SetupDiClassNameFromGuidW(Guid): """ @@ -50,24 +52,103 @@ def SetupDiClassNameFromGuidW(Guid): raw_class_name = bytes(class_name) return raw_class_name.decode("utf-16-le").rstrip("\x00") -# @SetupApiProxy() -# def SetupDiGetClassDevsA(Guid): -# return SetupDiGetClassDevsA.ctypes_function() +@SetupApiProxy(error_check=result_is_handle) +def SetupDiGetClassDevsA(Guid, Enumerator = None, hwndParent = None, Flags=0): + """ + Given a class GUID, return a HANDLE to the device's information set or raise an Exception + """ -# @SetupApiProxy() -# def SetupDiEnumDeviceInfo(hDevInfo, NumDevice): -# return SetupDiEnumDeviceInfo.ctypes_function() + return SetupDiGetClassDevsA.ctypes_function( + ctypes.byref(Guid), + Enumerator, + hwndParent, + Flags + ) -# @SetupApiProxy() -# def SetupDiGetDeviceRegistryPropertyA(hDevInfo, DevData, Property, PropertyType): -# return SetupDiGetDeviceRegistryPropertyA.ctypes_function() +@SetupApiProxy(error_check=result_is_handle) +def SetupDiGetClassDevsW(Guid, Enumerator = None, hwndParent = None, Flags=0): + """ + Given a class GUID, return a HANDLE to the device's information set or raise an Exception + """ + + return SetupDiGetClassDevsW.ctypes_function( + ctypes.byref(Guid), + Enumerator, + hwndParent, + Flags + ) + +@SetupApiProxy() +def SetupDiEnumDeviceInfo(hDevInfo, MemberIndex): + """ + Given a device information set, return the info associated with the index + or raise ERROR_NO_MORE_ITEMS if there is none anymore. + """ + + data = gdef.winstructs.SP_DEVINFO_DATA() + data.cbSize = ctypes.sizeof(gdef.winstructs.SP_DEVINFO_DATA) + + success = SetupDiEnumDeviceInfo.ctypes_function( + hDevInfo, + MemberIndex, + ctypes.byref(data) + ) + + return data + +@SetupApiProxy() +def SetupDiGetDeviceRegistryPropertyA(hDevInfo, DevData, Property, PropertyType, PropertySize = MAX_DEV_LEN*ctypes.sizeof(wintypes.CHAR)): + + property_buffer = ctypes.create_string_buffer(PropertySize) + bytes_written = wintypes.DWORD(0) + + success = SetupDiGetDeviceRegistryPropertyA.ctypes_function( + hDevInfo, + ctypes.byref(DevData), + Property, + PropertyType, + ctypes.cast(ctypes.byref(property_buffer), gdef.winstructs.PBYTE), + wintypes.DWORD(PropertySize), + ctypes.byref(bytes_written), + ) + + if not success: + return None + + # Truncate read data + registry_data = bytes(property_buffer) + registry_data = registry_data[0:bytes_written.value] + + return registry_data -# @SetupApiProxy() -# def SetupDiGetDeviceRegistryPropertyW(hDevInfo, DevData, Property, PropertyType): -# return SetupDiGetDeviceRegistryPropertyW.ctypes_function() +@SetupApiProxy() +def SetupDiGetDeviceRegistryPropertyW(hDevInfo, DevData, Property, PropertyType, PropertySize = MAX_DEV_LEN*ctypes.sizeof(wintypes.WCHAR)): + + property_buffer = ctypes.create_unicode_buffer(PropertySize) + bytes_written = wintypes.DWORD(0) + + success = SetupDiGetDeviceRegistryPropertyW.ctypes_function( + hDevInfo, + ctypes.byref(DevData), + Property, + PropertyType, + ctypes.cast(ctypes.byref(property_buffer), gdef.winstructs.PBYTE), + wintypes.DWORD(PropertySize), + ctypes.byref(bytes_written), + ) + + if not success: + return None + + # Truncate read data + registry_data = bytes(property_buffer) + registry_data = registry_data[0:bytes_written.value] + + return registry_data -# @SetupApiProxy() -# def SetupDiDestroyDeviceInfoList(hDevInfo): -# return SetupDiDestroyDeviceInfoList.ctypes_function(hDevInfo) \ No newline at end of file + +@SetupApiProxy() +def SetupDiDestroyDeviceInfoList(hDevInfo): + return SetupDiDestroyDeviceInfoList.ctypes_function(hDevInfo) \ No newline at end of file