Python 和 GetVolumeInformationW 和 GetDriveTypeW:列出所有磁盘并获取磁盘信息和文件系统标志

Python and GetVolumeInformationW and GetDriveTypeW: list all disks and get disk info and filesystem flags

在 python3 使用 windows API: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getvolumeinformationw

如何获取这些值(file_system_flags、max_component_length 和 serial_number),通过此函数 GetVolumeInformationW,无需安装任何其他外部模块?

import ctypes

kernel32 = ctypes.windll.kernel32
volumeNameBuffer = ctypes.create_unicode_buffer(1024)
fileSystemNameBuffer = ctypes.create_unicode_buffer(1024)
serial_number = None
max_component_length = None
file_system_flags = None

target_disk = 'C:\'
rc = kernel32.GetVolumeInformationW(
    ctypes.c_wchar_p(target_disk),
    volumeNameBuffer, ctypes.sizeof(volumeNameBuffer),
    serial_number,
    max_component_length,
    file_system_flags, 
    fileSystemNameBuffer, ctypes.sizeof(fileSystemNameBuffer)
)

mount_point = target_disk[:-1]
disk_label = volumeNameBuffer.value
fs_type = fileSystemNameBuffer.value
max_length = max_component_length
flags = file_system_flags
serial = serial_number

print(mount_point, disk_label, fs_type, max_length, flags, serial)

补充: 以及如何将文件系统标志转换为人类可读的格式?

my_disk_flag = 0x3e706ff

hex_flags = [0x00000002, 0x00000001, 0x20000000, 0x00000010, 0x00040000, 0x00000008, 0x00080000, 0x00100000, 0x00020000, 0x00800000, 0x00400000, 0x00010000, 0x01000000, 0x00000080, 0x00000040, 0x00200000, 0x02000000, 0x00000004, 0x00008000, 0x00000020, 0x08000000]

str_flags = ['FILE_CASE_PRESERVED_NAMES', 'FILE_CASE_SENSITIVE_SEARCH', 'FILE_DAX_VOLUME', 'FILE_FILE_COMPRESSION', 'FILE_NAMED_STREAMS', 'FILE_PERSISTENT_ACLS', 'FILE_READ_ONLY_VOLUME', 'FILE_SEQUENTIAL_WRITE_ONCE', 'FILE_SUPPORTS_ENCRYPTION', 'FILE_SUPPORTS_EXTENDED_ATTRIBUTES', 'FILE_SUPPORTS_HARD_LINKS', 'FILE_SUPPORTS_OBJECT_IDS', 'FILE_SUPPORTS_OPEN_BY_FILE_ID', 'FILE_SUPPORTS_REPARSE_POINTS', 'FILE_SUPPORTS_SPARSE_FILES', 'FILE_SUPPORTS_TRANSACTIONS', 'FILE_SUPPORTS_USN_JOURNAL', 'FILE_UNICODE_ON_DISK', 'FILE_VOLUME_IS_COMPRESSED', 'FILE_VOLUME_QUOTAS', 'FILE_SUPPORTS_BLOCK_REFCOUNTING']

创建该类型的一个实例并通过引用传递它。它相当于在 C 中声明一个局部变量并传递其地址,例如DWORD flags; 并作为 &flags 作为输出参数传递。

它还通过声明函数调用的 .argtypes.restype 来帮助 ctypes 错误检查,这类似于声明 C 原型。 ctypes.wintypes 有很多 Windows 的预定义类型。

我还添加了一个 .errcheck 属性,它会自动检查 return 值并根据 GetLastError() 代码引发 Windows 异常,以及一个用于处理标志的枚举转换为可读格式:

import ctypes as ct
from ctypes import wintypes as w
from enum import IntFlag

class FSFlags(IntFlag):
    FILE_CASE_SENSITIVE_SEARCH        = 0x00000001
    FILE_CASE_PRESERVED_NAMES         = 0x00000002
    FILE_UNICODE_ON_DISK              = 0x00000004
    FILE_PERSISTENT_ACLS              = 0x00000008
    FILE_FILE_COMPRESSION             = 0x00000010
    FILE_VOLUME_QUOTAS                = 0x00000020
    FILE_SUPPORTS_SPARSE_FILES        = 0x00000040
    FILE_SUPPORTS_REPARSE_POINTS      = 0x00000080
    FILE_VOLUME_IS_COMPRESSED         = 0x00008000
    FILE_SUPPORTS_OBJECT_IDS          = 0x00010000
    FILE_SUPPORTS_ENCRYPTION          = 0x00020000
    FILE_NAMED_STREAMS                = 0x00040000
    FILE_READ_ONLY_VOLUME             = 0x00080000
    FILE_SEQUENTIAL_WRITE_ONCE        = 0x00100000
    FILE_SUPPORTS_TRANSACTIONS        = 0x00200000
    FILE_SUPPORTS_HARD_LINKS          = 0x00400000
    FILE_SUPPORTS_EXTENDED_ATTRIBUTES = 0x00800000
    FILE_SUPPORTS_OPEN_BY_FILE_ID     = 0x01000000
    FILE_SUPPORTS_USN_JOURNAL         = 0x02000000
    FILE_SUPPORTS_BLOCK_REFCOUNTING   = 0x08000000
    FILE_DAX_VOLUME                   = 0x20000000

def validate(result,func,args):
    if not result:
        raise ct.WinError(ct.get_last_error())
    return None

dll = ct.WinDLL('kernel32',use_last_error=True)
dll.GetVolumeInformationW.argtypes = w.LPCWSTR,w.LPWSTR,w.DWORD,w.LPDWORD,w.LPDWORD,w.LPDWORD,w.LPWSTR,w.DWORD
dll.GetVolumeInformationW.restype = w.BOOL
dll.GetVolumeInformationW.errcheck = validate

volumeNameBuffer = ct.create_unicode_buffer(w.MAX_PATH + 1)
fileSystemNameBuffer = ct.create_unicode_buffer(w.MAX_PATH + 1)
serial_number = w.DWORD()
max_component_length = w.DWORD() 
file_system_flags = w.DWORD()

target_disk = 'c:\'

dll.GetVolumeInformationW(target_disk,
                          volumeNameBuffer, ct.sizeof(volumeNameBuffer),
                          ct.byref(serial_number),
                          ct.byref(max_component_length),
                          ct.byref(file_system_flags),
                          fileSystemNameBuffer, ct.sizeof(fileSystemNameBuffer))

mount_point = target_disk[:-1]
disk_label = volumeNameBuffer.value
fs_type = fileSystemNameBuffer.value
max_length = max_component_length.value
flags = FSFlags(file_system_flags.value)
serial = serial_number.value

print(f'{mount_point=}\n{disk_label=}\n{fs_type=}\n{max_length=}\n{flags=}\n{serial=}')

输出示例:

mount_point='c:'
disk_label=''
fs_type='NTFS'
max_length=255
flags=<FSFlags.FILE_SUPPORTS_USN_JOURNAL|FILE_SUPPORTS_OPEN_BY_FILE_ID|FILE_SUPPORTS_EXTENDED_ATTRIBUTES|FILE_SUPPORTS_HARD_LINKS|FILE_SUPPORTS_TRANSACTIONS|FILE_NAMED_STREAMS|FILE_SUPPORTS_ENCRYPTION|FILE_SUPPORTS_OBJECT_IDS|1024|512|FILE_SUPPORTS_REPARSE_POINTS|FILE_SUPPORTS_SPARSE_FILES|FILE_VOLUME_QUOTAS|FILE_FILE_COMPRESSION|FILE_PERSISTENT_ACLS|FILE_UNICODE_ON_DISK|FILE_CASE_PRESERVED_NAMES|FILE_CASE_SENSITIVE_SEARCH: 65472255>
serial=3465270344

为了将来参考,我将在此处保留有关如何列出所有磁盘并获取其类似 fstab 的信息的完整代码

import ctypes as ct
import string
from ctypes import wintypes as w
from enum import IntFlag
from pathlib import Path

class FSFlags(IntFlag):
    FILE_CASE_SENSITIVE_SEARCH        = 0x00000001
    FILE_CASE_PRESERVED_NAMES         = 0x00000002
    FILE_UNICODE_ON_DISK              = 0x00000004
    FILE_PERSISTENT_ACLS              = 0x00000008
    FILE_FILE_COMPRESSION             = 0x00000010
    FILE_VOLUME_QUOTAS                = 0x00000020
    FILE_SUPPORTS_SPARSE_FILES        = 0x00000040
    FILE_SUPPORTS_REPARSE_POINTS      = 0x00000080
    FILE_VOLUME_IS_COMPRESSED         = 0x00008000
    FILE_SUPPORTS_OBJECT_IDS          = 0x00010000
    FILE_SUPPORTS_ENCRYPTION          = 0x00020000
    FILE_NAMED_STREAMS                = 0x00040000
    FILE_READ_ONLY_VOLUME             = 0x00080000
    FILE_SEQUENTIAL_WRITE_ONCE        = 0x00100000
    FILE_SUPPORTS_TRANSACTIONS        = 0x00200000
    FILE_SUPPORTS_HARD_LINKS          = 0x00400000
    FILE_SUPPORTS_EXTENDED_ATTRIBUTES = 0x00800000
    FILE_SUPPORTS_OPEN_BY_FILE_ID     = 0x01000000
    FILE_SUPPORTS_USN_JOURNAL         = 0x02000000
    FILE_SUPPORTS_BLOCK_REFCOUNTING   = 0x08000000
    FILE_DAX_VOLUME                   = 0x20000000

def validate(result,func,args):
    if not result:
        raise ct.WinError(ct.get_last_error())
    return None

dll = ct.WinDLL('kernel32',use_last_error=True)
dll.GetVolumeInformationW.argtypes = w.LPCWSTR,w.LPWSTR,w.DWORD,w.LPDWORD,w.LPDWORD,w.LPDWORD,w.LPWSTR,w.DWORD
dll.GetVolumeInformationW.restype = w.BOOL
dll.GetVolumeInformationW.errcheck = validate

volumeNameBuffer = ct.create_unicode_buffer(w.MAX_PATH + 1)
fileSystemNameBuffer = ct.create_unicode_buffer(w.MAX_PATH + 1)
serial_number = w.DWORD()
max_component_length = w.DWORD() 
file_system_flags = w.DWORD()

lst_available_disks = [f'{d}:\' for d in string.ascii_uppercase if Path(f'{d}:\').exists()]
lst_disk_types = ['DRIVE_UNKNOWN', 'DRIVE_NO_ROOT_DIR', 'DRIVE_REMOVABLE', 'DRIVE_FIXED', 'DRIVE_REMOTE', 'DRIVE_CDROM', 'DRIVE_RAMDISK']

for target_disk in lst_available_disks:
    disk_type_index = dll.GetDriveTypeW(target_disk)

    dll.GetVolumeInformationW(target_disk,
                            volumeNameBuffer, ct.sizeof(volumeNameBuffer),
                            ct.byref(serial_number),
                            ct.byref(max_component_length),
                            ct.byref(file_system_flags),
                            fileSystemNameBuffer, ct.sizeof(fileSystemNameBuffer))

    mount_point = target_disk
    disk_label = volumeNameBuffer.value
    fs_type = fileSystemNameBuffer.value
    max_length = max_component_length.value
    flags = FSFlags(file_system_flags.value)
    serial = serial_number.value

    if 'FILE_READ_ONLY_VOLUME' in str(flags):
        read_write_status = 'ro'
    else:
        read_write_status = 'rw'

    extra_tab_label = ''
    if len(disk_label) < 8: extra_tab_label = '\t'
    extra_tab_type = ''
    if len(lst_disk_types[disk_type_index]) < 12: extra_tab_type = '\t'

    print(f'{disk_label}{extra_tab_label}\t{mount_point}\t{fs_type}\t{max_length}\t{serial}\t{read_write_status},{lst_disk_types[disk_type_index]}{extra_tab_type}\t{flags=}\n')

输出(只是用零替换磁盘序列号):

Win10           C:\     NTFS    255     0000000000      rw,DRIVE_FIXED          flags=<FSFlags.FILE_SUPPORTS_USN_JOURNAL|FILE_SUPPORTS_OPEN_BY_FILE_ID|FILE_SUPPORTS_EXTENDED_ATTRIBUTES|FILE_SUPPORTS_HARD_LINKS|FILE_SUPPORTS_TRANSACTIONS|FILE_NAMED_STREAMS|FILE_SUPPORTS_ENCRYPTION|FILE_SUPPORTS_OBJECT_IDS|1024|512|FILE_SUPPORTS_REPARSE_POINTS|FILE_SUPPORTS_SPARSE_FILES|FILE_VOLUME_QUOTAS|FILE_FILE_COMPRESSION|FILE_PERSISTENT_ACLS|FILE_UNICODE_ON_DISK|FILE_CASE_PRESERVED_NAMES|FILE_CASE_SENSITIVE_SEARCH: 65472255>     

                D:\     FAT32   255     0000000000      rw,DRIVE_FIXED          flags=<FSFlags.FILE_SUPPORTS_ENCRYPTION|512|FILE_UNICODE_ON_DISK|FILE_CASE_PRESERVED_NAMES: 131590>

DRV061107       E:\     CDFS    110     0000000000      ro,DRIVE_CDROM          flags=<FSFlags.FILE_SUPPORTS_OPEN_BY_FILE_ID|FILE_READ_ONLY_VOLUME|FILE_UNICODE_ON_DISK|FILE_CASE_SENSITIVE_SEARCH: 17301509>

Ventoy          F:\     exFAT   255     0000000000      rw,DRIVE_REMOVABLE      flags=<FSFlags.FILE_SUPPORTS_ENCRYPTION|512|FILE_UNICODE_ON_DISK|FILE_CASE_PRESERVED_NAMES: 131590>

VTOYEFI         G:\     FAT     255     0000000000      rw,DRIVE_REMOVABLE      flags=<FSFlags.FILE_SUPPORTS_ENCRYPTION|512|FILE_UNICODE_ON_DISK|FILE_CASE_PRESERVED_NAMES: 131590>