You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 11:18:16