voussoirkit/voussoirkit/windrives.py

184 lines
5.6 KiB
Python
Raw Normal View History

2020-08-20 18:39:49 +00:00
'''
2020-08-26 01:43:39 +00:00
This module provides functions for getting information about logical drives
on Windows.
2020-08-20 18:39:49 +00:00
'''
import ctypes
2020-08-26 01:43:39 +00:00
import re
2020-08-20 18:39:49 +00:00
import string
2021-08-25 05:34:09 +00:00
import ctypes.wintypes
kernel32 = ctypes.WinDLL('kernel32')
from voussoirkit import vlogging
log = vlogging.getLogger(__name__, 'windrives')
2020-08-20 18:39:49 +00:00
2020-08-26 01:43:39 +00:00
def get_all_volumes():
2020-08-20 18:39:49 +00:00
'''
2020-08-26 01:43:39 +00:00
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
2021-08-25 05:34:09 +00:00
Thank you Mark Tolonen.
https://stackoverflow.com/a/66976493
2020-08-26 01:43:39 +00:00
'''
2021-08-25 05:34:09 +00:00
type_unicode = ctypes.wintypes.LPCWSTR
type_dword = ctypes.wintypes.DWORD
type_handle = ctypes.wintypes.HANDLE
kernel32.FindFirstVolumeW.argtypes = (type_unicode, type_dword)
kernel32.FindFirstVolumeW.restype = type_handle
kernel32.FindNextVolumeW.argtypes = (type_handle, type_unicode, type_dword)
kernel32.FindNextVolumeW.restype = type_handle
kernel32.FindVolumeClose.argtypes = (type_handle,)
kernel32.FindVolumeClose.restype = type_handle
buffer = ctypes.create_unicode_buffer(1024)
buffer_size = ctypes.sizeof(buffer)
handle = kernel32.FindFirstVolumeW(buffer, buffer_size)
2020-08-26 01:43:39 +00:00
volumes = []
if handle:
2021-08-25 05:34:09 +00:00
volumes.append(buffer.value)
while kernel32.FindNextVolumeW(handle, buffer, buffer_size):
volumes.append(buffer.value)
2020-08-26 01:43:39 +00:00
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.
2020-08-20 18:39:49 +00:00
Thanks Nicholas Orlowski
http://stackoverflow.com/a/12056414
'''
2020-08-26 01:43:39 +00:00
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
2020-08-20 18:39:49 +00:00
name_buffer = ctypes.create_unicode_buffer(1024)
filesystem_buffer = ctypes.create_unicode_buffer(1024)
serial_number = None
max_component_length = None
file_system_flags = None
drive_active = kernel32.GetVolumeInformationW(
2020-08-26 01:43:39 +00:00
ctypes.c_wchar_p(path),
2020-08-20 18:39:49 +00:00
name_buffer,
ctypes.sizeof(name_buffer),
serial_number,
max_component_length,
file_system_flags,
filesystem_buffer,
ctypes.sizeof(filesystem_buffer),
)
info = {
'active': bool(drive_active),
'filesystem': filesystem_buffer.value,
'name': name_buffer.value,
2020-08-26 01:43:39 +00:00
'mount': mount,
2020-08-20 18:39:49 +00:00
}
return info
def get_drive_letters():
'''
2020-08-26 01:43:39 +00:00
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.
2020-08-20 18:39:49 +00:00
Thanks RichieHindle
http://stackoverflow.com/a/827398
'''
2020-08-26 01:43:39 +00:00
# "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
2020-08-20 18:39:49 +00:00
letters = []
bitmask = ctypes.windll.kernel32.GetLogicalDrives()
for letter in string.ascii_uppercase:
if bitmask & 1:
letters.append(letter)
bitmask >>= 1
return letters
2020-08-26 01:43:39 +00:00
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):
2020-08-20 18:39:49 +00:00
'''
Given the name of a drive (the user-customizable name seen in Explorer),
2020-08-26 01:43:39 +00:00
return the letter or mount path of that drive.
2020-08-20 18:39:49 +00:00
Raises KeyError if it is not found.
'''
drives = get_drive_map()
2020-08-26 01:43:39 +00:00
for (mount, info) in drives.items():
2020-08-20 18:39:49 +00:00
if info['name'] == name:
2020-08-26 01:43:39 +00:00
return mount
2020-08-20 18:39:49 +00:00
raise KeyError(name)
2020-08-26 01:43:39 +00:00
def get_volume_mount(volume):
2020-08-20 18:39:49 +00:00
'''
2020-08-26 01:43:39 +00:00
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.
2020-08-20 18:39:49 +00:00
'''
2021-08-25 05:34:09 +00:00
buffer = ctypes.create_unicode_buffer(1024)
2020-08-26 01:43:39 +00:00
length = ctypes.c_int32()
kernel32.GetVolumePathNamesForVolumeNameW(
ctypes.c_wchar_p(volume),
2021-08-25 05:34:09 +00:00
buffer,
ctypes.sizeof(buffer),
2020-08-26 01:43:39 +00:00
ctypes.pointer(length),
)
2021-08-25 05:34:09 +00:00
return buffer.value