Win32 Volume、分区号与硬盘型号
Windows 下经常需要完成以下映射关系:
盘符
↓
Volume GUID
↓
Disk Number + Partition Number
↓
PhysicalDrive
↓
硬盘型号 / SSD-HDD 类型本文使用纯 Win32 API + Python ctypes 实现整个查询链路。
需要注意的是,IOCTL_STORAGE_GET_DEVICE_NUMBER 等接口通常需要较高权限访问设备对象。
如果希望采用无需管理员权限的实现方式,可以参考:
- PowerShell方式:win32-drive-letters-deviceid-harddisk-model
- listdisk-rs
- MSDN 中的 WMI 类(Win32_DiskDrive、Win32_DiskPartition、Win32_LogicalDisk 等)
Win32 Prototype
首先定义需要使用的结构体、常量以及 Win32 API 签名。
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 =("kernel32", use_last_error=True)
INVALID_HANDLE_VALUE =(-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 =(
,
,
|,
None,
,
0,
None,
)
if h == INVALID_HANDLE_VALUE:
raise(())
return h枚举 Volume
def enum_volumes():
"""
Enumerate all volume GUIDs in the system.
"""
buf =()
h =(,)
if h == INVALID_HANDLE_VALUE:
raise(())
try:
yield buf.value
while(,,):
yield buf.value
finally:
()Volume 获取挂载点
def get_mount_points(volume_guid):
"""
Get all mount points (drive letters / folders) for a volume.
"""
needed =()
# First call to get required buffer size
(
,
None,
0,
(),
)
if needed.value == 0:
return []
buf =(.)
# Second call to actually retrieve mount points
if not(
,
,
.,
(),
):
raise(())
result = []
offset = 0
# Multi-string parsing (double-null terminated string list)
while True:
s =(() + *())
if not s:
break
result.()
offset += len() + 1
return resultVolume 获取磁盘号与分区号
def volume_to_disk_info(volume_guid):
"""
Map a volume GUID to physical disk number and partition number.
"""
h =(.("\\"))
try:
result =()
returned =()
ok =(
,
,
None,
0,
(),
(),
(),
None,
)
if not ok:
raise(())
return result
finally:
()获取硬盘型号
def get_disk_model(disk_number):
"""
Query physical disk model string via StorageDeviceProperty.
"""
h =(rf".\PhysicalDrive{}")
try:
query =()
query.PropertyId = StorageDeviceProperty
query.QueryType = PropertyStandardQuery
header =()
returned =()
# First call: get required buffer size
(
,
,
(),
(),
(),
(),
(),
None,
)
buf =(.)
# Second call: retrieve full descriptor
ok =(
,
,
(),
(),
,
len(),
(),
None,
)
if not ok:
raise(())
desc =(
,
(),
).contents
if desc.ProductIdOffset == 0:
return "<unknown>"
return (
(() +.)
.("ascii", errors="ignore")
.()
)
finally:
()判断 SSD 或 HDD
def get_disk_kind(disk_number):
"""
Determine disk type using seek penalty property.
"""
h =(rf".\PhysicalDrive{}")
try:
query =()
query.PropertyId = StorageDeviceSeekPenaltyProperty
query.QueryType = PropertyStandardQuery
result =()
returned =()
ok =(
,
,
(),
(),
(),
(),
(),
None,
)
if not ok:
return "Unknown"
return "HDD" if result.IncursSeekPenalty else "SSD"
finally:
()综合输出
def main():
"""
Full mapping pipeline:
Volume → Disk → Partition → Model → Type
"""
cache = {}
for volume in():
try:
disk_info =()
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:
[] = {
"model":(),
"kind":(),
}
info =[]
print()
print("Volume :",)
for mount in():
print("Mount :",)
print("Disk :",)
print("Partition :",)
print("Model :",["model"])
print("Type :",["kind"])
except Exception as e:
print()
print()
print("ERROR:",)运行示例:
Volume : \\?\Volume{...}\
Mount : C:\
Disk : 0
Partition : 3
Model : Samsung SSD 990 EVO
Type : SSD可通过 gist 获取完整示例代码。