From 1077541ec4b3fc392fc485c985ec39e05e9a4742 Mon Sep 17 00:00:00 2001 From: hakril Date: Tue, 21 Apr 2020 21:06:44 +0200 Subject: [PATCH] POC of new high-level API for device manager adapted from lucasg work --- windows/winobject/device.py | 541 ---------------------------- windows/winobject/device_manager.py | 366 +++++++++++++++++++ windows/winobject/system.py | 9 + 3 files changed, 375 insertions(+), 541 deletions(-) delete mode 100644 windows/winobject/device.py create mode 100644 windows/winobject/device_manager.py diff --git a/windows/winobject/device.py b/windows/winobject/device.py deleted file mode 100644 index 9fd4934..0000000 --- a/windows/winobject/device.py +++ /dev/null @@ -1,541 +0,0 @@ -import struct -import ctypes -from ctypes import wintypes - -import windows -from windows import winproxy -from windows.generated_def import windef -import windows.generated_def as gdef - -SPDRP_DEVICEDESC = 0x00000000 -SPDRP_HARDWAREID = 0x00000001 -SPDRP_COMPATIBLEIDS = 0x00000002 -SPDRP_UNUSED0 = 0x00000003 -SPDRP_SERVICE = 0x00000004 -SPDRP_UNUSED1 = 0x00000005 -SPDRP_UNUSED2 = 0x00000006 -SPDRP_CLASS = 0x00000007 -SPDRP_CLASSGUID = 0x00000008 -SPDRP_DRIVER = 0x00000009 -SPDRP_CONFIGFLAGS = 0x0000000A -SPDRP_MFG = 0x0000000B -SPDRP_FRIENDLYNAME = 0x0000000C -SPDRP_LOCATION_INFORMATION = 0x0000000D -SPDRP_PHYSICAL_DEVICE_OBJECT_NAME = 0x0000000E -SPDRP_CAPABILITIES = 0x0000000F -SPDRP_UI_NUMBER = 0x00000010 -SPDRP_UPPERFILTERS = 0x00000011 -SPDRP_LOWERFILTERS = 0x00000012 -SPDRP_BUSTYPEGUID = 0x00000013 -SPDRP_LEGACYBUSTYPE = 0x00000014 -SPDRP_BUSNUMBER = 0x00000015 -SPDRP_ENUMERATOR_NAME = 0x00000016 -SPDRP_SECURITY = 0x00000017 -SPDRP_SECURITY_SDS = 0x00000018 -SPDRP_DEVTYPE = 0x00000019 -SPDRP_EXCLUSIVE = 0x0000001a -SPDRP_CHARACTERISTICS = 0x0000001b -SPDRP_ADDRESS = 0x0000001c -SPDRP_UI_NUMBER_DESC_FORMAT = 0x0000001d -SPDRP_DEVICE_POWER_DATA = 0x0000001e -SPDRP_REMOVAL_POLICY = 0x0000001f -SPDRP_REMOVAL_POLICY_HW_DEFAULT = 0x00000020 -SPDRP_REMOVAL_POLICY_OVERRIDE = 0x00000021 -SPDRP_INSTALL_STATE = 0x00000022 -SPDRP_LOCATION_PATHS = 0x00000023 -SPDRP_BASE_CONTAINERID = 0x00000024 -SPDRP_MAXIMUM_PROPERTY = 0x00000025 - - -# Resource types -# source : -ResourceType_All = 0x00000000 # query every resource available -ResourceType_Mem = 0x00000001 # Physical address resource -ResourceType_IO = 0x00000002 # Physical I/O address resource -ResourceType_DMA = 0x00000003 # DMA channels resource -ResourceType_IRQ = 0x00000004 # IRQ resource -ResourceType_DoNotUse = 0x00000005 # Do not use it, idiot -ResourceType_BusNumber = 0x00000006 # (PCI) bus number -ResourceType_MAX = 0x00000007 - -# Log conf -# source : https://docs.microsoft.com/en-us/windows/win32/api/cfgmgr32/nf-cfgmgr32-cm_get_first_log_conf -BASIC_LOG_CONF = 0x00000000 # basic configuration information. -FILTERED_LOG_CONF = 0x00000001 # filtered configuration information. -ALLOC_LOG_CONF = 0x00000002 # allocated configuration information. -BOOT_LOG_CONF = 0x00000003 # boot configuration information. -FORCED_LOG_CONF = 0x00000004 # forced configuration information. -OVERRIDE_LOG_CONF = 0x00000005 # override configuration information. - -class _IO_DES(ctypes.Structure): - """ - The IO_DES structure is used for specifying either a resource list or a resource requirements list that describes I/O port usage for a device instance. - Source : https://docs.microsoft.com/en-gb/windows/win32/api/cfgmgr32/ns-cfgmgr32-io_des - """ - - _fields_ = [ - ('IOD_Count', gdef.DWORD), - ('IOD_Type', gdef.DWORD), - ('IOD_Alloc_Base', gdef.UINT64), - ('IOD_Alloc_End', gdef.UINT64), - ('IOD_DesFlags', gdef.DWORD), - ] -IO_DES = _IO_DES -PIO_DES = ctypes.POINTER(_IO_DES) - -class _IO_RANGE(ctypes.Structure): - """ - The IO_RANGE structure specifies a resource requirements list that describes I/O port usage for a device instance. - Source : https://docs.microsoft.com/en-gb/windows/win32/api/cfgmgr32/ns-cfgmgr32-io_range - """ - - _fields_ = [ - ('IOR_Align', gdef.UINT64), - ('IOR_nPorts', gdef.DWORD), - ('IOR_Min', gdef.UINT64), - ('IOR_Max', gdef.UINT64), - ('IOR_RangeFlags', gdef.DWORD), - ('IOR_Alias', gdef.UINT64), - ] -IO_RANGE = _IO_RANGE -PIO_RANGE = ctypes.POINTER(_IO_RANGE) - -class _MEM_DES(ctypes.Structure): - """ - The MEM_DES structure is used for specifying either a resource list or a resource requirements list that describes memory usage for a device instance. - Source : https://docs.microsoft.com/en-gb/windows/win32/api/cfgmgr32/ns-cfgmgr32-mem_des - """ - - _fields_ = [ - ('MD_Count', gdef.DWORD), - ('MD_Type', gdef.DWORD), - ('MD_Alloc_Base', gdef.UINT64), - ('MD_Alloc_End', gdef.UINT64), - ('MD_DesFlags', gdef.DWORD), - ] -MEM_DES = _MEM_DES -PMEM_DES = ctypes.POINTER(_MEM_DES) - - -class _MEM_RANGE(ctypes.Structure): - """ - The MEM_RANGE structure specifies a resource requirements list that describes memory usage for a device instance. - Source : https://docs.microsoft.com/en-gb/windows/win32/api/cfgmgr32/ns-cfgmgr32-mem_range - """ - - _fields_ = [ - ('MR_Align', gdef.UINT64), - ('MR_nBytes', gdef.ULONG), - ('MR_Min', gdef.UINT64), - ('MR_Max', gdef.UINT64), - ('MR_Flags', gdef.DWORD), - ('MR_Reserved', gdef.DWORD), - ] - -MEM_RANGE = _MEM_RANGE -PMEM_RANGE = ctypes.POINTER(_MEM_RANGE) - -class _DMA_DES(ctypes.Structure): - """ - The DMA_DES structure is used for specifying either a resource list or a resource requirements list that describes direct memory access (DMA) channel usage for a device instance. - Source : https://docs.microsoft.com/en-gb/windows/win32/api/cfgmgr32/ns-cfgmgr32-dma_des - """ - - _fields_ = [ - ('DD_Count', gdef.DWORD), - ('DD_Type', gdef.DWORD), - ('DD_Flags', gdef.DWORD), - ('DD_Alloc_Chan', gdef.ULONG), - ] -DMA_DES = _DMA_DES -PDMA_DES = ctypes.POINTER(_DMA_DES) - - -class _DMA_RANGE(ctypes.Structure): - """ - The MEM_RANGE structure specifies a resource requirements list that describes memory usage for a device instance. - Source : https://docs.microsoft.com/en-gb/windows/win32/api/cfgmgr32/ns-cfgmgr32-dma_range - """ - - _fields_ = [ - ('DR_Min', gdef.ULONG), - ('DR_Max', gdef.ULONG), - ('DR_Flags', gdef.ULONG), - ] - -DMA_RANGE = _DMA_RANGE -PDMA_RANGE = ctypes.POINTER(_DMA_RANGE) - -class _IRQ_DES_64(ctypes.Structure): - """ - The IRQ_DES structure is used for specifying either a resource list or a resource requirements list that describes IRQ line usage for a device instance. - Source : https://docs.microsoft.com/en-gb/windows/win32/api/cfgmgr32/ns-cfgmgr32-irq_des_64 - """ - - _fields_ = [ - ('IRQD_Count', gdef.DWORD), - ('IRQD_Type', gdef.DWORD), - ('IRQD_Flags', gdef.DWORD), - ('IRQD_Alloc_Num', gdef.ULONG), - ('IRQD_Affinity', gdef.ULONG64), - ] -IRQ_DES_64 = _IRQ_DES_64 -PIRQ_DES_64 = ctypes.POINTER(_IRQ_DES_64) - -class _IRQ_DES_32(ctypes.Structure): - """ - The IRQ_DES structure is used for specifying either a resource list or a resource requirements list that describes IRQ line usage for a device instance. - Source : https://docs.microsoft.com/en-gb/windows/win32/api/cfgmgr32/ns-cfgmgr32-irq_des_32 - """ - - _fields_ = [ - ('IRQD_Count', gdef.DWORD), - ('IRQD_Type', gdef.DWORD), - ('IRQD_Flags', gdef.DWORD), - ('IRQD_Alloc_Num', gdef.ULONG), - ('IRQD_Affinity', gdef.DWORD), - ] -IRQ_DES_32 = _IRQ_DES_32 -PIRQ_DES_32 = ctypes.POINTER(_IRQ_DES_32) - - -class _IRQ_RANGE(ctypes.Structure): - """ - The IRQ_RANGE structure specifies a resource requirements list that describes IRQ line usage for a device instance. - Source : https://docs.microsoft.com/en-gb/windows/win32/api/cfgmgr32/ns-cfgmgr32-irq_range - """ - - _fields_ = [ - ('IRQR_Min', gdef.ULONG), - ('IRQR_Max', gdef.ULONG), - ('IRQR_Flags', gdef.ULONG), - ] - -IRQ_RANGE = _IRQ_RANGE -PIRQ_RANGE = ctypes.POINTER(_IRQ_RANGE) - -class AbstractDeviceResource(object): - """ An abstract Python object representing a setup device resource. """ - pass - -class MmioDeviceResource(AbstractDeviceResource): - """ A Python object representing a setup device MMIO resource. """ - - def __init__(self, data): - - # check before casting into MEM_DES - assert (len(data) >= ctypes.sizeof(MEM_DES)) - - self.header = MEM_DES() - ctypes.memmove(ctypes.byref(self.header), data, ctypes.sizeof(MEM_DES)) - - # check before casting into MEM_RANGES - assert (len(data) >= ctypes.sizeof(MEM_DES) + self.header.MD_Count*ctypes.sizeof(MEM_RANGE)) - - # only for requirements list (not our case) - self.ranges = () - for i in range(self.header.MD_Count): - mem_range = MEM_RANGE() - ctypes.memmove(ctypes.byref(mem_range), data + ctypes.sizeof(MEM_DES) + i*ctypes.sizeof(MEM_RANGE), ctypes.sizeof(MEM_RANGE)) - self.ranges.append(mem_range) - - def __str__(self): - return 'MmioDeviceResource : [%016x-%016x]' % (self.header.MD_Alloc_Base, self.header.MD_Alloc_End) - - -class IoDeviceResource(AbstractDeviceResource): - """ A Python object representing a setup device IO port resource. """ - - def __init__(self, data): - - # check before casting into IO_DES - assert (len(data) >= ctypes.sizeof(IO_DES)) - - self.header = IO_DES() - ctypes.memmove(ctypes.byref(self.header), data, ctypes.sizeof(IO_DES)) - - # check before casting into IO_RANGES - assert (len(data) >= ctypes.sizeof(IO_DES) + self.header.IOD_Count*ctypes.sizeof(IO_RANGE)) - - # only for requirements list (not our case) - self.ranges = () - for i in range(self.header.IOD_Count): - io_range = IO_RANGE() - ctypes.memmove(ctypes.byref(io_range), data + ctypes.sizeof(IO_DES) + i*ctypes.sizeof(IO_RANGE), ctypes.sizeof(IO_RANGE)) - self.ranges.append(io_range) - - - def __str__(self): - return 'IoDeviceResource : [%016x-%016x]' % (self.header.IOD_Alloc_Base, self.header.IOD_Alloc_End) - - -class DmaDeviceResource(AbstractDeviceResource): - """ A Python object representing a setup device DMA resource. """ - - def __init__(self, data): - - # check before casting into DMA_DES - assert (len(data) >= ctypes.sizeof(DMA_DES)) - - self.header = DMA_DES() - ctypes.memmove(ctypes.byref(self.header), data, ctypes.sizeof(DMA_DES)) - - # check before casting into MEM_RANGES - assert (len(data) >= ctypes.sizeof(DMA_DES) + self.header.DD_Count*ctypes.sizeof(DMA_RANGE)) - - # only for requirements list (not our case) - self.ranges = () - for i in range(self.header.DD_Count): - dma_range = DMA_RANGE() - ctypes.memmove(ctypes.byref(mem_range), data + ctypes.sizeof(DMA_DES) + i*ctypes.sizeof(DMA_RANGE), ctypes.sizeof(DMA_RANGE)) - self.ranges.append(dma_range) - - def __str__(self): - return 'DmaDeviceResource : [%016x]' % (self.header.DD_Alloc_Chan) - -class IrqDeviceResource(AbstractDeviceResource): - """ A Python object representing a setup device Irq resource. """ - - def __init__(self, data): - - # TODO : check 32/64 bitness before casting - - # check before casting into IRQ_DES_64 - assert (len(data) >= ctypes.sizeof(IRQ_DES_64)) - - self.header = IRQ_DES_64() - ctypes.memmove(ctypes.byref(self.header), data, ctypes.sizeof(IRQ_DES_64)) - - # check before casting into MEM_RANGES - assert (len(data) >= ctypes.sizeof(IRQ_DES_64) + self.header.IRQD_Count*ctypes.sizeof(IRQ_RANGE)) - - # only for requirements list (not our case) - self.ranges = () - for i in range(self.header.IRQD_Count): - irq_range = IRQ_RANGE() - ctypes.memmove(ctypes.byref(mem_range), data + ctypes.sizeof(IRQ_DES_64) + i*ctypes.sizeof(IRQ_RANGE), ctypes.sizeof(DMA_RANGE)) - self.ranges.append(irq_range) - - def __str__(self): - return 'IrqDeviceResource : [%016x]' % (self.header.IRQD_Alloc_Num) - -class DeviceResource(object): - """ A Python object representing a setup device resource. """ - - @classmethod - def parse(cls, resource_type, data): - - subclasses = { - ResourceType_Mem : MmioDeviceResource, - ResourceType_IO : IoDeviceResource, - ResourceType_DMA : DmaDeviceResource, - ResourceType_IRQ : IrqDeviceResource, - } - - return subclasses[resource_type](data) - - -class DeviceObject(object): - """ A Python object representing a setup device instance. """ - - def __init__(self, dev_data, device_name, physical_object_name = None): - - # gdef.winstructs.SP_DEVINFO_DATA: associated device info - self.dev_data = dev_data - - # str: Device instance name (optional) - self._name = device_name - - # list(AbstractDeviceResource): list of resources registered by the device - self._resources = None - - # str: Physical object name (optional) - self._device_object = physical_object_name - - - @property - def name(self): - return self._name - - @property - def device_object(self): - return self._device_object - - @property - def resources(self): - if not self._resources: - self._resources = list(res for res in self.get_resources()) - - return self._resources - - - @classmethod - def from_device_info(cls, h_devs, device_data): - - # TODO : check if we can store h_devs in the class, and use it later - - # Try to retrieve some name associated with the device - device_name = None - for name_params in [SPDRP_FRIENDLYNAME, SPDRP_DEVICEDESC]: - - try: - device_name = winproxy.SetupDiGetDeviceRegistryPropertyW(h_devs, device_data, name_params, None) - if device_name: - device_name = device_name.decode('utf-16-le').rstrip('\x00') - break - - except WindowsError as e: - if e.winerror != gdef.ERROR_INVALID_DATA: - raise - - # Read other useful infos such as PDO, BusParam, etc. - ERROR_NO_SUCH_DEVINST = 0xe000020b - pdo = None - try: - pdo = winproxy.SetupDiGetDeviceRegistryPropertyW(h_devs, device_data, SPDRP_PHYSICAL_DEVICE_OBJECT_NAME, None) - if pdo: - pdo = pdo.decode('utf-16-le').rstrip('\x00') - except WindowsError as e: - if e.winerror not in [gdef.ERROR_INVALID_DATA, ERROR_NO_SUCH_DEVINST]: - raise - - - return cls(device_data, device_name, pdo) - - def get_resources(self, resource_type = None): - - conf = self._get_first_log_conf() - if conf is None: - return - - if resource_type: - yield from self._get_resources_by_type(conf, resource_type) - else: - yield from self._get_resources_by_type(conf, ResourceType_Mem) - yield from self._get_resources_by_type(conf, ResourceType_IO) - yield from self._get_resources_by_type(conf, ResourceType_DMA) - yield from self._get_resources_by_type(conf, ResourceType_IRQ) - - # TODO : free "conf" automagically - - def _get_first_log_conf(self): - """ Try to retrieve the first log conf by using several flags """ - - conf = winproxy.CM_Get_First_Log_Conf(self.dev_data.DevInst, ALLOC_LOG_CONF ) - if conf != None: - return conf - - conf = winproxy.CM_Get_First_Log_Conf(self.dev_data.DevInst, BOOT_LOG_CONF ) - if conf != None: - return conf - - - - - def _get_resources_by_type(self, conf, resource_type): - - h_res = winproxy.CM_Get_Next_Res_Des(conf, resource_type) - - while h_res != None: - - # open resource - resource_size = winproxy.CM_Get_Res_Des_Data_Size(h_res) - if resource_size: - resource_data = winproxy.CM_Get_Res_Des_Data(h_res, resource_size) - yield DeviceResource.parse(resource_type, resource_data) - - # goto next resource - h_res = winproxy.CM_Get_Next_Res_Des(h_res, resource_type) - - # TODO : free "h_res" automagically - -class DeviceClass(object): - """ A Python object representing a setup device class. """ - - def __init__(self, guid): - - # gdef.winstructs.GUID: class GUID - self._guid = guid - - # str: associated class name - self._name = None - - # list(DeviceObject): list of devices registered under the class - self._devices = None - - @property - def name(self): - if not self._name: - self._name = self._get_class_name() - - return self._name - - @property - def guid(self): - return self._guid - - @property - def devices(self): - if not self._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 - - def _get_devices(self, h_devs): - - if not h_devs: - return - - device_index = 0 - - while True: - - 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(h_devs, device_data) - - - def _get_class_name(self): - return winproxy.SetupDiClassNameFromGuidW(self.guid) - - -class DeviceManager(object): - - @staticmethod - def enumerate_active_class(): - return list(DeviceManager.enumerate_class()) - # return list(filter(lambda dc: len(dc.devices) > 0, DeviceManager.enumerate_class())) - - @staticmethod - def enumerate_class(): - - def _gen_all_class_guid(): - """ - Return every registered device classes in the system, generator-style - """ - - class_index = 0 - - while True: - class_guid = winproxy.CM_Enumerate_Classes(class_index, 0) - class_index+=1 - - if not class_guid: - return # stop iterating - - yield DeviceClass(class_guid) - - - - return (dc for dc in _gen_all_class_guid()) \ No newline at end of file diff --git a/windows/winobject/device_manager.py b/windows/winobject/device_manager.py new file mode 100644 index 0000000..3144f23 --- /dev/null +++ b/windows/winobject/device_manager.py @@ -0,0 +1,366 @@ +import ctypes +import itertools + +import windows +from windows import winproxy +import windows.generated_def as gdef +from windows.security import SecurityDescriptor +from windows.utils import fixedproperty + + +class DeviceManager(object): + @property + def classes(self): + for index in itertools.count(): + try: + yield self._enumerate_classes(index, 0) + except WindowsError as e: + if e.winerror == gdef.CR_NO_SUCH_VALUE: + break + # Some index values might represent list entries containing invalid class data, + # in which case the function returns CR_INVALID_DATA. + # This return value can be ignored. + if e.winerror == gdef.CR_INVALID_DATA: + continue + raise + + + def _enumerate_classes(self, index, flags=0): + res = DeviceClass() + x = winproxy.CM_Enumerate_Classes(index, res, flags) + return res + + +class DeviceClass(gdef.GUID): + def __init__(self): + # Bypass GUID __init__ that is not revelant here + pass + + @fixedproperty + def name(self): + return self._get_device_class_name() + + @property + def devices(self): + return self.enumerate_devices() + + def enumerate_devices(self, flags=0): + handle = winproxy.SetupDiGetClassDevsA(self, Flags=flags) + return DeviceInformationSet(handle) + + def _get_device_class_name(self): + name = ctypes.create_string_buffer(gdef.MAX_CLASS_NAME_LEN) + winproxy.SetupDiClassNameFromGuidA(self, name) + return name.value + + def __repr__(self): + guid_cls = self.to_string() + return """<{0} {2} name="{1}">""".format(type(self).__name__, self.name, guid_cls) + + __str__ = __repr__ # Overwrite default GUID str + +class DeviceInformationSet(gdef.HDEVINFO): + def all_device_infos(self): + for index in itertools.count(): + try: + yield self.enum_device_info(index) + except WindowsError as e: + if e.winerror == gdef.ERROR_NO_MORE_ITEMS: + return + raise + + __iter__ = all_device_infos + + def enum_device_info(self, index): + res = DeviceInstance(self) + res.cbSize = ctypes.sizeof(res) + winproxy.SetupDiEnumDeviceInfo(self, index, res) + return res + + def enum_device_interface(self, index): + raise NotImplementedError("enum_device_interface") + + +class DeviceInstance(gdef.SP_DEVINFO_DATA): + def __init__(self, information_set=None): + self.information_set = information_set + + # make a .device_class ? that return the DeviceClass ased in ClassGuid ? + def get_property(self, property): + datatype = gdef.DWORD() + buffer_size = 0x1000 + buffer = windows.utils.BUFFER(gdef.BYTE, nbelt=buffer_size)() + required_size = gdef.DWORD() + # Registry parsing code expect W stuff, so use W function + try: + winproxy.SetupDiGetDeviceRegistryPropertyW(self.information_set, self, property, datatype, buffer.cast(gdef.LPBYTE), buffer_size, required_size) + except WindowsError as e: + if e.winerror == gdef.ERROR_INVALID_DATA: + return None + raise + # PropertyRegDataType + # A pointer to a variable that receives the data type of the property + # that is being retrieved. + # This is one of the standard registry data types + # Look like its registry based, so use the registry decoders :) + return windows.winobject.registry.decode_registry_buffer(datatype.value, buffer, required_size.value) + + + def _generate_property_getter(prop): + def getter(self): + return self.get_property(prop) + return property(getter) + + name = _generate_property_getter(gdef.SPDRP_FRIENDLYNAME) + description = _generate_property_getter(gdef.SPDRP_DEVICEDESC) + hardware_id = _generate_property_getter(gdef.SPDRP_HARDWAREID) + enumerator_name = _generate_property_getter(gdef.SPDRP_ENUMERATOR_NAME) + driver = _generate_property_getter(gdef.SPDRP_DRIVER) + # Map on Device type ? + # https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/specifying-device-types + type = _generate_property_getter(gdef.SPDRP_DEVTYPE) + upper_filters = _generate_property_getter(gdef.SPDRP_UPPERFILTERS) + lower_filters = _generate_property_getter(gdef.SPDRP_LOWERFILTERS) + raw_security_descriptor = _generate_property_getter(gdef.SPDRP_SECURITY) + # I would prefer to use the security_descriptor sddl + # ssdl = _generate_property_getter(gdef.SPDRP_SECURITY_SDS) + service_name = _generate_property_getter(gdef.SPDRP_SERVICE) + manufacturer = _generate_property_getter(gdef.SPDRP_MFG) + location_information = _generate_property_getter(gdef.SPDRP_LOCATION_INFORMATION) + location_paths = _generate_property_getter(gdef.SPDRP_LOCATION_PATHS) + # Looks like it can raise ERROR_NO_SUCH_DEVINST + # install_date = _generate_property_getter(gdef.SPDRP_INSTALL_STATE) + capabilites = _generate_property_getter(gdef.SPDRP_CAPABILITIES) + bus_type = _generate_property_getter(gdef.SPDRP_BUSTYPEGUID) + bus_number = _generate_property_getter(gdef.SPDRP_BUSNUMBER) + address = _generate_property_getter(gdef.SPDRP_ADDRESS) + ui_number = _generate_property_getter(gdef.SPDRP_UI_NUMBER) + ui_number_desc_format = _generate_property_getter(gdef.SPDRP_UI_NUMBER_DESC_FORMAT) + + # Getter with special error handling + + @property + def device_object_name(self): + try: + return self.get_property(gdef.SPDRP_PHYSICAL_DEVICE_OBJECT_NAME) + except WindowsError as e: + if e.winerror not in (gdef.ERROR_INVALID_DATA, gdef.ERROR_NO_SUCH_DEVINST): + raise + + + def get_first_logical_configuration(self, type): + res = LogicalConfiguration() + try: + winproxy.CM_Get_First_Log_Conf(res, self.DevInst, type) + except WindowsError as e: + if e.winerror == gdef.CR_CALL_NOT_IMPLEMENTED: + e.strerror += " (Cannot be called from Wow64 process since Win8)" + raise + return res + + def get_next_logical_configuration(self, logconf): + res = gdef.HANDLE(0) + winproxy.CM_Get_Next_Log_Conf(res, logconf) + return res + + + # 'advanced' attributes extrapolated from properties + @property + def security_descriptor(self): + return SecurityDescriptor.from_binary(self.raw_security_descriptor) + + def __repr__(self): + return """<{0} {1}>""".format(type(self).__name__, self.DevInst) + + +class LogicalConfiguration(gdef.HANDLE): + """Logical Configuration of a Device instance""" + + def get_next_resource_descriptor(self, resource, resdes=None): + if resdes is None: + # Using logical-conf as resdes will retrieve the first one + # https://docs.microsoft.com/en-us/windows/win32/api/cfgmgr32/nf-cfgmgr32-cm_get_next_res_des#remarks + resdes = self + resid = None + if resource == gdef.ResType_All: + resid = gdef.RESOURCEID() + res = gdef.HANDLE() + winproxy.CM_Get_Next_Res_Des(res, resdes, resource, resid, 0) + resdes_type = resid.value if resid is not None else resource + return ResourceDescriptor.from_handle_and_type(res.value, resdes_type) + + def get_resources_for_type(self, type): + try: + current = self.get_next_resource_descriptor(type) + yield current + while True: + current = self.get_next_resource_descriptor(type, current) + yield current + except WindowsError as e: + if e.winerror == gdef.CR_NO_MORE_RES_DES: + return + raise + + @property + def resources(self): + return list(self.get_resources_for_type(gdef.ResType_All)) + + def __repr__(self): + return "<{0}>".format(type(self).__name__) + + +ResType_Mapper = gdef.FlagMapper( + gdef.ResType_None, + gdef.ResType_Mem, + gdef.ResType_IO, + gdef.ResType_DMA, + gdef.ResType_IRQ, + gdef.ResType_BusNumber, + gdef.ResType_MemLarge, + gdef.ResType_ClassSpecific, + gdef.ResType_DevicePrivate, + gdef.ResType_MfCardConfig, + gdef.ResType_PcCardConfig, + +) + +class ResourceDescriptor(gdef.HANDLE): + SUBCLASSES = {} + + def __init__(self, handle, type): + super(ResourceDescriptor, self).__init__(handle) + self.type = ResType_Mapper[type] + + @classmethod + def from_handle_and_type(cls, handle, type): + ecls = cls.SUBCLASSES[type] + return ecls(handle, type) + + @property + def rawdata(self): + data_size = gdef.ULONG() + winproxy.CM_Get_Res_Des_Data_Size(data_size, self) + if not self: + return None + data_size = data_size.value + buffer = ctypes.create_string_buffer(data_size) + winproxy.CM_Get_Res_Des_Data(self, buffer, data_size) + return bytearray(buffer[:data_size]) + + def __repr__(self): + return "<{0} type={1!r}>".format(type(self).__name__, self.type) + + +class ResourceDescriptorWithHeader(ResourceDescriptor): + # Assert the header is the first field + @property + def header_type(self): + # Type of first field + return self.DATA_TYPE._fields_[0][1] + + @property + def header(self): + return self.header_type.from_buffer(self.rawdata) + + @property + def data(self): + return None + +class ResourceDescriptorWithHeaderAndRanges(ResourceDescriptorWithHeader): + def count_field_name(self): + # Assert (manyally checked) that the first field of the + # header is a field containing the size of the data array + # Return name of the first field of the header + return self.header_type._fields_[0][0] + + @property + def data(self): + count_field_name = self.count_field_name() + count = getattr(self.header, count_field_name) + # No entry: + if not count: + return [] + raise NotImplementedError("Resource descriptor with non-zero entry in range array") + + +class ResourceNoType(ResourceDescriptor): + @property + def data(self): + return self.rawdata + +class MemoryResource(ResourceDescriptorWithHeaderAndRanges): + # Assert hea + DATA_TYPE = gdef.MEM_RESOURCE + + def __str__(self): + return "<{0} : [{1:#016x}-{2:#016x}]>".format(type(self).__name__, self.header.MD_Alloc_Base, self.header.MD_Alloc_End) + +class IoResource(ResourceDescriptorWithHeaderAndRanges): + DATA_TYPE = gdef.IO_RESOURCE + + def __str__(self): + return "<{0} : [{1:#016x}-{2:#016x}]>".format(type(self).__name__, self.header.IOD_Alloc_Base, self.header.IOD_Alloc_End) + +class DmaResource(ResourceDescriptorWithHeaderAndRanges): + DATA_TYPE = gdef.DMA_RESOURCE + + def __str__(self): + import pdb;pdb.set_trace() + return "<{0} : [{1:#016x}]>".format(type(self).__name__, self.header.DD_Alloc_Chan) + + +class IrqResource(ResourceDescriptorWithHeaderAndRanges): + # 32/64 based on current process bitness + # Cross bitness cannot be implemented as >=Win8 block it + DATA_TYPE = gdef.IRQ_RESOURCE + + def __str__(self): + return "<{0} : [{1:#016x}]>".format(type(self).__name__, self.header.IRQD_Alloc_Num) + + +class BusNumberResource(ResourceDescriptorWithHeaderAndRanges): + DATA_TYPE = gdef.BUSNUMBER_RESOURCE + + def __str__(self): + return "<{0} : [{1:#016x}-{2:#016x}]>".format(type(self).__name__, self.header.BUSD_Alloc_Base, self.header.BUSD_Alloc_End) + + +class MemLargeResource(ResourceDescriptor): + DATA_TYPE = gdef.MEM_LARGE_RESOURCE + + def __str__(self): + return "<{0} : [{1:#016x}-{2:#016x}]>".format(type(self).__name__, self.header.MLD_Alloc_Base, self.header.MLD_Alloc_End) + +class ClassSpecificResource(ResourceDescriptorWithHeader): + DATA_TYPE = gdef.CS_RESOURCE + # Any idea for __str__ ? + +class DevicePrivateResource(ResourceDescriptor): + @property + def header(self): + return None + + # Any idea for __str__ ? + +class MfCardConfigResource(ResourceDescriptorWithHeader): + DATA_TYPE = gdef.MFCARD_RESOURCE + # Any idea for __str__ ? + +class PcCardConfigResource(ResourceDescriptorWithHeader): + DATA_TYPE = gdef.PCCARD_RESOURCE + # Any idea for __str__ ? + +# Flemme de faire une meta-classe pour ca.. +ResourceDescriptor.SUBCLASSES.update({ + gdef.ResType_None: ResourceNoType, + gdef.ResType_Mem: MemoryResource, + gdef.ResType_IO: IoResource, + gdef.ResType_DMA: DmaResource, + gdef.ResType_IRQ: IrqResource, + gdef.ResType_BusNumber: BusNumberResource, + gdef.ResType_MemLarge: MemLargeResource, + gdef.ResType_ClassSpecific: ClassSpecificResource, + gdef.ResType_DevicePrivate: DevicePrivateResource, + gdef.ResType_MfCardConfig: MfCardConfigResource, + gdef.ResType_PcCardConfig: PcCardConfigResource, +}) \ No newline at end of file diff --git a/windows/winobject/system.py b/windows/winobject/system.py index bc8db6b..2019844 100644 --- a/windows/winobject/system.py +++ b/windows/winobject/system.py @@ -19,6 +19,7 @@ from windows.winobject import service from windows.winobject import volume from windows.winobject import wmi from windows.winobject import object_manager +from windows.winobject import device_manager from windows.winobject import handle from windows.winobject import event_log from windows.winobject import event_trace @@ -135,6 +136,14 @@ class System(object): """ return windows.winobject.object_manager.ObjectManager() + @utils.fixedpropety + def device_manager(self): + """An object to query the device&driver configured on the computer. + + :type: :class:`~windows.winobject.device_manager.DeviceManager` + """ + return windows.winobject.device_manager.DeviceManager() + @utils.fixedpropety def bits(self): return bits.create_manager()