Win32 Volume、分区号与硬盘型号
Windows 硬盘操作,不用 WMI 可以多麻烦。 Windows 下经常需要完成以下映射关系: 本文使用纯 Win32 API + Python 需要注意的是, 如果希望采用无需管理员权限的实现方式,可以参考: 首先定义需要使用的结构体、常量以及 Win32 API 签名。 运行示例: 可通过 gist 获取完整示例代码。盘符
↓
Volume GUID
↓
Disk Number + Partition Number
↓
PhysicalDrive
↓
硬盘型号 / SSD-HDD 类型ctypes 实现整个查询链路。IOCTL_STORAGE_GET_DEVICE_NUMBER 等接口通常需要较高权限访问设备对象。Win32 Prototype
from ctypes import (
addressof,
byref,
c_wchar,
cast,
create_string_buffer,
create_unicode_buffer,
get_last_error,
POINTER,
sizeof,
string_at,
Structure,
WinDLL,
WinError,
wstring_at,
)
from ctypes.wintypes import (
HANDLE,
BOOL,
BOOLEAN,
DWORD,
BYTE,
LPWSTR,
)
# Load kernel32.dll (Win32 API entry point)
kernel32 = WinDLL("kernel32", use_last_error=True)
INVALID_HANDLE_VALUE = HANDLE(-1).value
# Access flags
GENERIC_READ = 0x80000000
# Share modes
FILE_SHARE_READ = 1
FILE_SHARE_WRITE = 2
# Creation disposition
OPEN_EXISTING = 3
# Maximum path length for legacy Win32 APIs
MAX_PATH = 260
# IOCTL control codes
IOCTL_STORAGE_GET_DEVICE_NUMBER = 0x2D1080
IOCTL_STORAGE_QUERY_PROPERTY = 0x2D1400
# Storage property IDs
StorageDeviceProperty = 0
StorageDeviceSeekPenaltyProperty = 7
PropertyStandardQuery = 0Structures
class STORAGE_DEVICE_NUMBER(Structure):
_fields_ = [
("DeviceType", DWORD), # Device type (e.g. disk)
("DeviceNumber", DWORD), # PhysicalDrive number
("PartitionNumber", DWORD), # Partition index
]
class STORAGE_PROPERTY_QUERY(Structure):
_fields_ = [
("PropertyId", DWORD), # Property being queried
("QueryType", DWORD), # Query type (standard query)
("AdditionalParameters", BYTE * 1),
]
class STORAGE_DESCRIPTOR_HEADER(Structure):
_fields_ = [
("Version", DWORD), # Descriptor version
("Size", DWORD), # Total size of returned buffer
]
class STORAGE_DEVICE_DESCRIPTOR(Structure):
_fields_ = [
("Version", DWORD),
("Size", DWORD),
("DeviceType", BYTE),
("DeviceTypeModifier", BYTE),
("RemovableMedia", BOOLEAN),
("CommandQueueing", BOOLEAN),
("VendorIdOffset", DWORD),
("ProductIdOffset", DWORD),
("ProductRevisionOffset", DWORD),
("SerialNumberOffset", DWORD),
("BusType", DWORD),
("RawPropertiesLength", DWORD),
# Variable-length buffer follows this field
("RawDeviceProperties", BYTE * 1),
]
class DEVICE_SEEK_PENALTY_DESCRIPTOR(Structure):
_fields_ = [
("Version", DWORD),
("Size", DWORD),
("IncursSeekPenalty", BOOLEAN), # True = HDD, False = SSD
]Function Prototypes
# Enumerate volumes
FindFirstVolumeW = kernel32.FindFirstVolumeW
FindFirstVolumeW.argtypes = [LPWSTR, DWORD]
FindFirstVolumeW.restype = HANDLE
FindNextVolumeW = kernel32.FindNextVolumeW
FindNextVolumeW.argtypes = [HANDLE, LPWSTR, DWORD]
FindNextVolumeW.restype = BOOL
FindVolumeClose = kernel32.FindVolumeClose
FindVolumeClose.argtypes = [HANDLE]
FindVolumeClose.restype = BOOL
# Volume mount point resolver
GetVolumePathNamesForVolumeNameW = (
kernel32.GetVolumePathNamesForVolumeNameW
)
# Device access
CreateFileW = kernel32.CreateFileW
CreateFileW.restype = HANDLE
DeviceIoControl = kernel32.DeviceIoControl
CloseHandle = kernel32.CloseHandle打开设备对象
def open_device(path):
"""
Open a Win32 device handle (volume or physical drive).
"""
h = CreateFileW(
path,
GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE,
None,
OPEN_EXISTING,
0,
None,
)
if h == INVALID_HANDLE_VALUE:
raise WinError(get_last_error())
return h枚举 Volume
def enum_volumes():
"""
Enumerate all volume GUIDs in the system.
"""
buf = create_unicode_buffer(MAX_PATH)
h = FindFirstVolumeW(buf, MAX_PATH)
if h == INVALID_HANDLE_VALUE:
raise WinError(get_last_error())
try:
yield buf.value
while FindNextVolumeW(h, buf, MAX_PATH):
yield buf.value
finally:
FindVolumeClose(h)Volume 获取挂载点
def get_mount_points(volume_guid):
"""
Get all mount points (drive letters / folders) for a volume.
"""
needed = DWORD()
# First call to get required buffer size
GetVolumePathNamesForVolumeNameW(
volume_guid,
None,
0,
byref(needed),
)
if needed.value == 0:
return []
buf = create_unicode_buffer(needed.value)
# Second call to actually retrieve mount points
if not GetVolumePathNamesForVolumeNameW(
volume_guid,
buf,
needed.value,
byref(needed),
):
raise WinError(get_last_error())
result = []
offset = 0
# Multi-string parsing (double-null terminated string list)
while True:
s = wstring_at(addressof(buf) + offset * sizeof(c_wchar))
if not s:
break
result.append(s)
offset += len(s) + 1
return resultVolume 获取磁盘号与分区号
def volume_to_disk_info(volume_guid):
"""
Map a volume GUID to physical disk number and partition number.
"""
h = open_device(volume_guid.rstrip("\\"))
try:
result = STORAGE_DEVICE_NUMBER()
returned = DWORD()
ok = DeviceIoControl(
h,
IOCTL_STORAGE_GET_DEVICE_NUMBER,
None,
0,
byref(result),
sizeof(result),
byref(returned),
None,
)
if not ok:
raise WinError(get_last_error())
return result
finally:
CloseHandle(h)获取硬盘型号
def get_disk_model(disk_number):
"""
Query physical disk model string via StorageDeviceProperty.
"""
h = open_device(rf"\\.\PhysicalDrive{disk_number}")
try:
query = STORAGE_PROPERTY_QUERY()
query.PropertyId = StorageDeviceProperty
query.QueryType = PropertyStandardQuery
header = STORAGE_DESCRIPTOR_HEADER()
returned = DWORD()
# First call: get required buffer size
DeviceIoControl(
h,
IOCTL_STORAGE_QUERY_PROPERTY,
byref(query),
sizeof(query),
byref(header),
sizeof(header),
byref(returned),
None,
)
buf = create_string_buffer(header.Size)
# Second call: retrieve full descriptor
ok = DeviceIoControl(
h,
IOCTL_STORAGE_QUERY_PROPERTY,
byref(query),
sizeof(query),
buf,
len(buf),
byref(returned),
None,
)
if not ok:
raise WinError(get_last_error())
desc = cast(
buf,
POINTER(STORAGE_DEVICE_DESCRIPTOR),
).contents
if desc.ProductIdOffset == 0:
return "<unknown>"
return (
string_at(addressof(buf) + desc.ProductIdOffset)
.decode("ascii", errors="ignore")
.strip()
)
finally:
CloseHandle(h)判断 SSD 或 HDD
def get_disk_kind(disk_number):
"""
Determine disk type using seek penalty property.
"""
h = open_device(rf"\\.\PhysicalDrive{disk_number}")
try:
query = STORAGE_PROPERTY_QUERY()
query.PropertyId = StorageDeviceSeekPenaltyProperty
query.QueryType = PropertyStandardQuery
result = DEVICE_SEEK_PENALTY_DESCRIPTOR()
returned = DWORD()
ok = DeviceIoControl(
h,
IOCTL_STORAGE_QUERY_PROPERTY,
byref(query),
sizeof(query),
byref(result),
sizeof(result),
byref(returned),
None,
)
if not ok:
return "Unknown"
return "HDD" if result.IncursSeekPenalty else "SSD"
finally:
CloseHandle(h)综合输出
def main():
"""
Full mapping pipeline:
Volume → Disk → Partition → Model → Type
"""
cache = {}
for volume in enum_volumes():
try:
disk_info = volume_to_disk_info(volume)
device_num = disk_info.DeviceNumber
partition_num = disk_info.PartitionNumber
# Cache disk-level info to avoid repeated IOCTL calls
if device_num not in cache:
cache[device_num] = {
"model": get_disk_model(device_num),
"kind": get_disk_kind(device_num),
}
info = cache[device_num]
print()
print("Volume :", volume)
for mount in get_mount_points(volume):
print("Mount :", mount)
print("Disk :", device_num)
print("Partition :", partition_num)
print("Model :", info["model"])
print("Type :", info["kind"])
except Exception as e:
print()
print(volume)
print("ERROR:", e)Volume : \\?\Volume{...}\
Mount : C:\
Disk : 0
Partition : 3
Model : Samsung SSD 990 EVO
Type : SSD