mirror of
https://github.com/hakril/PythonForWindows
synced 2026-06-08 14:31:45 +00:00
POC of new high-level API for device manager adapted from lucasg work
This commit is contained in:
@@ -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())
|
||||
@@ -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,
|
||||
})
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user