Windows

通过 GetLogicalDrives API 可以拿到所有的磁盘列表,API 返回 DWORD 值, 为当前可用磁盘驱动的位掩码,比特位 0 表示 A 盘,比特位 1 表示 B 盘 …

Python 示例代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
from ctypes import windll
import string
from typing import List

def get_drives_path() -> List[str]:
    bitmask = windll.kernel32.GetLogicalDrives()

    drivers = []
    for path in (f"{letter}:" for letter in string.ascii_uppercase):
        if bitmask & 1:
            drivers.append(path)
        bitmask >>= 1
    return drivers

print(get_drivers_path())

拿到盘符后通过 GetDriveTypeW API 获取驱动器的类型。

如果返回值是 DRIVE_REMOVABLE ,那就一定是可移动设备。对于外接拓展坞上面插的键盘或鼠标也会被识别为移动设备并且有盘符, 但是它的大小为 0 ,同时 USB 设备文件格式一般为 NTFS, FAT32, exFAT 。我们可以通过检查容量和格式过滤掉。

对于移动硬盘或者容量比较大 NTFS 格式的 U 盘,返回值可能是 DRIVE_FIXED ,这时需要通过 STORAGE_BUS_TYPE 来判断是否是 USB 设备。

Python 代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import struct
import win32file
import winioctlcon

IOCTL_STORAGE_QUERY_PROPERTY = winioctlcon.CTL_CODE(
    winioctlcon.IOCTL_STORAGE_BASE,
    0x500,
    winioctlcon.METHOD_BUFFERED,
    winioctlcon.FILE_ANY_ACCESS,
)


def is_usb_bus_type(label: str) -> bool:
    path = f"\\\\.\\{label}"
    handle = win32file.CreateFileW(
        path,
        0,
        win32file.FILE_SHARE_READ | win32file.FILE_SHARE_WRITE,
        None,
        win32file.OPEN_EXISTING,
        0,
        None,
    )
    bus_type = winioctlcon.BusTypeUnknown
    try:
        query = struct.pack("iii", 0, 0, 0)
        devd = win32file.DeviceIoControl(
            handle, IOCTL_STORAGE_QUERY_PROPERTY, query, struct.calcsize("LLcc??LLLLiLi"), None
        )
        bus_type = struct.unpack("LLcc??LLLLiLi", devd)[10]
    finally:
        handle.Close()
    return bus_type == winioctlcon.BusTypeUsb

通过 GetDiskFreeSpaceExW API 可以拿到 U 盘的空间信息,GetVolumeInformationW 可以获取名字和格式。

完整代码:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

from ctypes import windll
import string
import struct
from typing import Dict, List, Tuple

import win32file
import winioctlcon
import win32api


def get_drives_path() -> List[str]:
    bitmask = windll.kernel32.GetLogicalDrives()

    drivers = []
    for path in (f"{letter}:" for letter in string.ascii_uppercase):
        if bitmask & 1:
            drivers.append(path)
        bitmask >>= 1
    return drivers


def get_drive_total_size(label: str) -> int:
    try:
        _, total, _ = win32file.GetDiskFreeSpaceEx(label)
        return total
    except:
        return 0


def get_drive_name_type(label: str) -> Tuple[str, str]:
    try:
        name, _, _, _, type = win32api.GetVolumeInformation(label)
    except:
        return ("", "")
    return (name, type)


IOCTL_STORAGE_QUERY_PROPERTY = winioctlcon.CTL_CODE(
    winioctlcon.IOCTL_STORAGE_BASE,
    0x500,
    winioctlcon.METHOD_BUFFERED,
    winioctlcon.FILE_ANY_ACCESS,
)


def is_usb_bus_type(label: str) -> bool:
    path = f"\\\\.\\{label}"
    handle = win32file.CreateFileW(
        path,
        0,
        win32file.FILE_SHARE_READ | win32file.FILE_SHARE_WRITE,
        None,
        win32file.OPEN_EXISTING,
        0,
        None,
    )
    bus_type = winioctlcon.BusTypeUnknown
    try:
        query = struct.pack("iii", 0, 0, 0)
        devd = win32file.DeviceIoControl(
            handle,
            IOCTL_STORAGE_QUERY_PROPERTY,
            query,
            struct.calcsize("LLcc??LLLLiLi"),
            None,
        )
        bus_type = struct.unpack("LLcc??LLLLiLi", devd)[10]
    finally:
        handle.Close()
    return bus_type == winioctlcon.BusTypeUsb


def is_usb_drive(label: str) -> bool:
    type = windll.kernel32.GetDriveTypeW(label)
    if type == win32file.DRIVE_REMOVABLE:
        return True
    elif type != win32file.DRIVE_FIXED:
        return False
    elif not is_usb_bus_type(label):
        return False
    return True


def get_usb_list() -> List:
    usb_drive_paths = [
        (path, get_drive_total_size(path), *get_drive_name_type(path))
        for path in get_drives_path()
        if is_usb_drive(path)
    ]
    return [
        usb
        for usb in usb_drive_paths
        if usb[1] > 0 and usb[3] in ["NTFS", "FAT", "exFAT", "FAT32"]
    ]


print(get_usb_list())