Add support for path-mounted drives.

This commit is contained in:
Ethan Dalool 2020-08-25 18:43:39 -07:00
parent 03256e2a79
commit e23bd1131f

View file

@ -1,28 +1,63 @@
''' '''
NOTE: I have not yet figured out how to get a list of volumes that are mounted This module provides functions for getting information about logical drives
to folders instead of drive letters. I think this is the API I need: on Windows.
https://docs.microsoft.com/en-us/windows/win32/fileio/enumerating-volume-mount-points
''' '''
import ctypes import ctypes
import re
import string import string
kernel32 = ctypes.windll.kernel32
def get_drive_info(letter): def get_all_volumes():
''' '''
Given a drive letter, return a dictionary containing its attributes. Return a list of volume paths like \\?\Volume{GUID}\ for all volumes,
whether they are mounted or not.
Note: This will include recovery / EFI partitions, which may not be what
you're looking for. Also see get_drive_letters and get_drive_mounts.
Thank you Duncan.
https://stackoverflow.com/a/3075879
'''
volumes = []
buf = ctypes.create_unicode_buffer(1024)
length = ctypes.c_int32()
handle = kernel32.FindFirstVolumeW(buf, ctypes.sizeof(buf))
if handle:
volumes.append(buf.value)
while kernel32.FindNextVolumeW(handle, buf, ctypes.sizeof(buf)):
volumes.append(buf.value)
kernel32.FindVolumeClose(handle)
return volumes
def get_drive_info(path):
'''
Given a drive path as either:
- a letter like C or C:\, or
- a mount path, or
- a volume path like \\?\Volume{GUID},
return a dictionary containing its attributes.
Thanks Nicholas Orlowski Thanks Nicholas Orlowski
http://stackoverflow.com/a/12056414 http://stackoverflow.com/a/12056414
''' '''
kernel32 = ctypes.windll.kernel32 letter_match = re.match(r'^([A-Z])(|:|:\\)$', path)
if letter_match:
letter = letter_match.group(1)
path = f'{letter}:\\'
if path.startswith('\\\\?\\Volume{'):
mount = get_volume_mount(path)
else:
mount = path
name_buffer = ctypes.create_unicode_buffer(1024) name_buffer = ctypes.create_unicode_buffer(1024)
filesystem_buffer = ctypes.create_unicode_buffer(1024) filesystem_buffer = ctypes.create_unicode_buffer(1024)
serial_number = None serial_number = None
max_component_length = None max_component_length = None
file_system_flags = None file_system_flags = None
letter = letter.rstrip(':\\/')
drive_active = kernel32.GetVolumeInformationW( drive_active = kernel32.GetVolumeInformationW(
ctypes.c_wchar_p(f'{letter}:\\'), ctypes.c_wchar_p(path),
name_buffer, name_buffer,
ctypes.sizeof(name_buffer), ctypes.sizeof(name_buffer),
serial_number, serial_number,
@ -35,21 +70,25 @@ def get_drive_info(letter):
'active': bool(drive_active), 'active': bool(drive_active),
'filesystem': filesystem_buffer.value, 'filesystem': filesystem_buffer.value,
'name': name_buffer.value, 'name': name_buffer.value,
'mount': mount,
} }
return info return info
def get_drive_letters(): def get_drive_letters():
''' '''
Return a list of all connected drive letters. Return a list of all connected drive letters as single-character strings.
Drives which are mounted to paths instead of letters will not be returned.
Use get_drive_mounts instead.
Thanks RichieHindle Thanks RichieHindle
http://stackoverflow.com/a/827398 http://stackoverflow.com/a/827398
"If the function succeeds, the return value is a bitmask representing the
currently available disk drives. Bit position 0 (the least-significant bit)
is drive A, bit position 1 is drive B, bit position 2 is drive C, and so on."
https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getlogicaldrives
''' '''
# "If the function succeeds, the return value is a bitmask representing the
# currently available disk drives. Bit position 0 (the least-significant
# bit) is drive A, bit position 1 is drive B, bit position 2 is drive C,
# and so on."
# https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getlogicaldrives
letters = [] letters = []
bitmask = ctypes.windll.kernel32.GetLogicalDrives() bitmask = ctypes.windll.kernel32.GetLogicalDrives()
for letter in string.ascii_uppercase: for letter in string.ascii_uppercase:
@ -58,22 +97,64 @@ def get_drive_letters():
bitmask >>= 1 bitmask >>= 1
return letters return letters
def get_drive_letter_by_name(name): def get_drive_map():
'''
Return a dict of {mount: info} for all connected drives, where mount is
either a drive letter or mount path, and info is the dict returned
by get_drive_info.
'''
drives = {mount: get_drive_info(mount) for mount in get_drive_mounts()}
return drives
def get_drive_mounts():
'''
Return a list of all connected drives as either:
- a letter like C:\ if the volume has a letter, or
- a mount path if the volume does not have a letter.
'''
mounts = []
for volume in get_all_volumes():
mount = get_volume_mount(volume)
if mount:
mounts.append(mount)
return mounts
def get_drive_mount_by_name(name):
''' '''
Given the name of a drive (the user-customizable name seen in Explorer), Given the name of a drive (the user-customizable name seen in Explorer),
return the letter of that drive. return the letter or mount path of that drive.
Raises KeyError if it is not found. Raises KeyError if it is not found.
''' '''
drives = get_drive_map() drives = get_drive_map()
for (letter, info) in drives.items(): for (mount, info) in drives.items():
if info['name'] == name: if info['name'] == name:
return letter return mount
raise KeyError(name) raise KeyError(name)
def get_drive_map(): def get_volume_mount(volume):
''' '''
Return a dict of {letter: info}. Given a volume path like \\?\Volume{GUID}\, return either:
- a letter like C:\ if the volume has a letter, or
- a mount path if the volume does not have a letter, or
- emptystring if the volume is not mounted at all.
Thank you Duncan.
https://stackoverflow.com/a/3075879
Note: The API function is named "GetVolumePathNames..." in the plural,
and Duncan's original answer uses .split('\0'), but in my testing the
drives always contain only a single name.
If the drive has a letter and mount path, only the letter is returned.
If it has two mount paths, only the first one is returned.
So, I'll just use a single return value until further notice.
''' '''
drives = {letter: get_drive_info(letter) for letter in get_drive_letters()} buf = ctypes.create_unicode_buffer(1024)
return drives length = ctypes.c_int32()
kernel32.GetVolumePathNamesForVolumeNameW(
ctypes.c_wchar_p(volume),
buf,
ctypes.sizeof(buf),
ctypes.pointer(length),
)
return buf.value