Windows下Python3获取USB设备完整信息的问题求助
Windows下Python获取USB读卡器完整参数的问题
背景
我正在自学Python3,刚入门,第一个小项目就卡壳了,代码写得比较粗糙,见谅。
目标
在Windows环境下获取所有可移动USB设备(SD卡读卡器)的完整参数,包括盘符、物理地址、文件系统、VID、PID等(具体看下面代码里的drive_dict)。
遇到的问题
- VendorID(VID)和ProductID(PID)不正确或者返回
None - 没法在Windows下查询SD卡的写保护状态(侧边滑块),求替代Linux中
os.statvfs(drive_path)的方案 - 连接多个USB读卡器时,无法返回VID或PID
相关代码
import ctypes import libusb import os import psutil import wmi # --------------------------------------------------------------------- windows equivalent of linux 'lsusb' def get_usb_detail(all_usb_dev, level, usb_addr): desc = libusb.device_descriptor() libusb.get_device_descriptor(all_usb_dev, ctypes.byref(desc)) description = "{:04X}:{:04X}".format(desc.idVendor, desc.idProduct) # print(f'\\n... description (vend, prod ID) : {description}') # ----------------------------------------------------- bus bus = libusb.get_bus_number(all_usb_dev) # print(f'... get_usb_detail | bus ; {bus}') # ----------------------------------------------------- address address = libusb.get_device_address(all_usb_dev) # print(f'... get_usb_detail | address ; {address}') # ----------------------------------------------------- path buffer = (ctypes.c_ubyte * 7)() pointer = ctypes.cast(ctypes.addressof(buffer), ctypes.POINTER(ctypes.c_ubyte)) n = libusb.get_port_numbers(all_usb_dev, pointer, 7) path = '-'.join([str(i) for i in buffer[:n]]) # print(f'... get_usb_detail | path ; {path}') # ----------------------------------------------------- output like Linux 'lsusb' print("Bus {:d},\\tDevice {:d},\\tID {},\\tPath {}".format(bus, address, description, path)) # ----------------------------------------------------- idVendor and idProduct # >>>> is following compare correct?? - I don't think so # result of Nirsoft USBDriveLog: Loacation: Integrated : Bus 0 : Device 0 : Function 8 : Adapter 0 : Port 0 # DiskNumber: 3, Vendor ID: 0bda, Product ID: 0169 print(f'\\n... compare address = {address} with usb_addr = {usb_addr}') if address == usb_addr: idVendor = "0x%04X" % desc.idVendor idProduct = "0x%04X" % desc.idProduct return idVendor, idProduct return 'None', 'None' def get_usb_list(usb_addr): # ------------------------------------------------------------------------- check libusb for windows r = libusb.init(None) if r < 0: print(f'*** error {r}: can\\'t initalize libusb') return r try: devs = ctypes.POINTER(ctypes.POINTER(libusb.device))() cnt = libusb.get_device_list(None, ctypes.byref(devs)) if cnt < 0: print(f'*** error {cnt}: no USB devices found') return cnt for i in range(cnt): idVendor, idProduct = get_usb_detail(devs[i], 0, usb_addr) # Calls function: get_usb_detail if idVendor != 'None' and idProduct != 'None': return idVendor, idProduct libusb.free_device_list(devs, 1) finally: libusb.exit(None) return idVendor, idProduct # --------------------------------------------------------- get_removable_drives def get_removable_drives(): usb_devices = [] c = wmi.WMI() for drive in c.Win32_DiskDrive(): if drive.MediaType and 'Removable' in drive.MediaType: # print(f'::: drive : {drive}') for partition in drive.associators("Win32_DiskDriveToDiskPartition"): # print('+++ get_removable_drives|partition ', partition) for logical_disk in partition.associators("Win32_LogicalDiskToPartition"): # print(f'... get_removable_drives|logical disk = {logical_disk}') volume_label = logical_disk.VolumeName if logical_disk.VolumeName else None try: sd_serial = os.stat(logical_disk.DeviceID).st_dev except PermissionError: sd_serial = None # --------------------------------------------------------- drive info drive_info = psutil.disk_usage(logical_disk.DeviceID) # --- get DiskIndex instead of physical_drive usb_addr = partition.DiskIndex disk_index = str(partition.DiskIndex).zfill(4) physical_drive = drive.Name drive_letter = logical_disk.Caption + '\\\\' print(f'\\n... usb_addr = {usb_addr}') print(f'... diskIndex = {disk_index}') print(f'--- phys drive = {physical_drive}') print(f'::: drive letter = {drive_letter}') usb_disk = partition.Caption.split()[1][:-1].zfill(4) usb_part = partition.Caption.split()[3].zfill(4) print(f'... partition.Caption : {partition.Caption}') print(f'... usb_disk = {usb_disk}') print(f'... usb_part = {usb_part}\\n') # --------------------------------------------------------- idVendor and idProduct using usb_addr idVendor, idProduct = get_usb_list(usb_addr) print(f'... dev_vendor_id = {idVendor}') print(f'... dev_product_id = {idProduct}') # --------------------------------------------------------- unresolved / troubles | start # there is only a solution for Linux using 'attributes = os.statvfs(drive_path)' is_write_protected = False # dummy value for system satisfaction # --------------------------------------------------------- unresolved / troubles | end # ------------------------------------- create dict drive_dict = {'dev_vendor_id': idVendor, 'dev_product_id': idProduct, 'dev_removable': True, 'is_write_procetcted': is_write_protected, 'disk_index': partition.DiskIndex, 'volume_label': volume_label, 'volume_serial_number': logical_disk.VolumeSerialNumber[ 0:4] + '-' + logical_disk.VolumeSerialNumber[ 4:8], 'sd_size': drive.Size, 'sd_serial': sd_serial, 'drive_letter': logical_disk.Caption, 'physical_drive': physical_drive, 'file_system': logical_disk.FileSystem, 'total_size': drive_info.total, 'used_space': drive_info.used, 'free_space': drive_info.free, 'percent_free': drive_info.percent} usb_devices.append(drive_dict) return usb_devices if __name__ == '__main__': # vendor_id = 0x0bda, product_id = 0x0169 NoName CardReader # Bus 1, Device 3, ID 0BDA:0169, Path 7 # vendor_id = 0x11b0, product_id = 0x6368 Kingston CardReader usb_devices = get_removable_drives() print(f'\\n\\n... list of result dict(s) : {usb_devices}') print(f'... # of usb devices found : {len(usb_devices)}') if len(usb_devices) > 0: print(f'... VendorID : {usb_devices[0]['dev_vendor_id']} | expected: 0x0bda')
补充说明
可能我的实现思路完全错了,因为对Python3的API不太熟,希望能得到解决方案,甚至更简洁的实现方式。
环境
Python 3.12.2、Windows 10、PyUSB、libusb-1.0.27.7z Windows二进制文件
解决方案
问题1&3:正确获取VID/PID(适配多设备)
你当前用partition.DiskIndex和libusb的设备地址对比是错误的——DiskIndex是磁盘编号,和USB设备地址不是同一个概念。正确做法是通过WMI直接关联USB设备和磁盘,无需依赖libusb:
修改get_removable_drives函数,直接从Win32_DiskDrive的关联属性中提取VID/PID:
def get_removable_drives(): usb_devices = [] c = wmi.WMI() for drive in c.Win32_DiskDrive(): if drive.MediaType and 'Removable' in drive.MediaType: # 从USB控制器关联设备中提取VID/PID vid = pid = None usb_controllers = drive.associators("Win32_DiskDriveToUSBController") for controller in usb_controllers: if hasattr(controller, 'PNPDeviceID'): pnp_id = controller.PNPDeviceID if 'VID_' in pnp_id and 'PID_' in pnp_id: vid = '0x' + pnp_id.split('VID_')[1].split('&')[0] pid = '0x' + pnp_id.split('PID_')[1].split('&')[0] break # 处理分区与逻辑磁盘 for partition in drive.associators("Win32_DiskDriveToDiskPartition"): for logical_disk in partition.associators("Win32_LogicalDiskToPartition"): volume_label = logical_disk.VolumeName if logical_disk.VolumeName else None try: sd_serial = os.stat(logical_disk.DeviceID).st_dev except PermissionError: sd_serial = None drive_info = psutil.disk_usage(logical_disk.DeviceID) # 获取写保护状态 is_write_protected = get_write_protection(logical_disk.Caption) # 构建结果字典 drive_dict = { 'dev_vendor_id': vid, 'dev_product_id': pid, 'dev_removable': True, 'is_write_protected': is_write_protected, 'disk_index': partition.DiskIndex, 'volume_label': volume_label, 'volume_serial_number': logical_disk.VolumeSerialNumber[:4] + '-' + logical_disk.VolumeSerialNumber[4:8] if logical_disk.VolumeSerialNumber else None, 'sd_size': drive.Size, 'sd_serial': sd_serial, 'drive_letter': logical_disk.Caption, 'physical_drive': drive.Name, 'file_system': logical_disk.FileSystem, 'total_size': drive_info.total, 'used_space': drive_info.used, 'free_space': drive_info.free, 'percent_free': drive_info.percent } usb_devices.append(drive_dict) return usb_devices
这种方式直接通过WMI关联关系获取每个读卡器的VID/PID,多设备场景下也能正常匹配。
问题2:查询SD卡写保护状态
Windows下通过调用DeviceIoControl内核API可以直接获取硬件级的写保护状态,实现如下:
import ctypes from ctypes import wintypes def get_write_protection(drive_letter): # 转换盘符为设备路径(如"C:" -> "\\\\.\\C:") device_path = f"\\\\.\\{drive_letter.strip(':')}:" # 打开设备(仅需查询权限) handle = ctypes.windll.kernel32.CreateFileW( device_path, wintypes.DWORD(0), wintypes.DWORD(0x00000001 | 0x00000002), # 共享读写 None, wintypes.DWORD(3), # OPEN_EXISTING wintypes.DWORD(0), None ) if handle == wintypes.HANDLE(-1).value: return False try: # 定义查询结构体 class STORAGE_PROPERTY_QUERY(ctypes.Structure): _fields_ = [ ("PropertyId", wintypes.DWORD), ("QueryType", wintypes.DWORD), ("AdditionalParameters", ctypes.c_byte * 1) ] # 查询写保护属性 query = STORAGE_PROPERTY_QUERY() query.PropertyId = 6 # StorageDeviceWriteProtectProperty query.QueryType = 0 # PropertyStandardQuery buffer = ctypes.create_string_buffer(4) bytes_returned = wintypes.DWORD(0) success = ctypes.windll.kernel32.DeviceIoControl( handle, 0x002D1400, # IOCTL_STORAGE_QUERY_PROPERTY ctypes.byref(query), ctypes.sizeof(query), buffer, 4, ctypes.byref(bytes_returned), None ) if success: return ctypes.cast(buffer, ctypes.POINTER(wintypes.BOOL)).contents.value return False finally: ctypes.windll.kernel32.CloseHandle(handle)
该函数直接读取设备硬件状态,能准确反映SD卡侧边滑块的写保护状态。
简化实现思路
完全可以抛弃libusb,仅用WMI+Windows API实现所有需求,代码更简洁且避免设备地址匹配错误。直接调用修改后的get_removable_drives()即可获取所有目标参数。
内容的提问来源于stack exchange,提问作者diewin
相关产品推荐
相关产品推荐

