61

Is there a way in Python to list all the currently in-use drive letters in a Windows system?

(My Google-fu seems to have let me down on this one)

A C++ equivalent: Enumerating all available drive letters in Windows

Tomerikoo
  • 18,379
  • 16
  • 47
  • 61
Electrons_Ahoy
  • 36,743
  • 36
  • 104
  • 127

19 Answers19

76

Without using any external libraries, if that matters to you:

import string
from ctypes import windll

def get_drives():
    drives = []
    bitmask = windll.kernel32.GetLogicalDrives()
    for letter in string.uppercase:
        if bitmask & 1:
            drives.append(letter)
        bitmask >>= 1

    return drives

if __name__ == '__main__':
    print get_drives()     # On my PC, this prints ['A', 'C', 'D', 'F', 'H']
RichieHindle
  • 272,464
  • 47
  • 358
  • 399
  • Any reason not to use string.lowercase or string.ascii_lowercase instead of string.letters[len(string.letters)/2:] ? – John Fouhy May 06 '09 at 02:02
  • @John: No reason - thanks for the suggestion, now changed to string.uppercase (because for drive letters I prefer caps, don't know why 8-) – RichieHindle May 06 '09 at 08:03
  • 1
    [c+':\\' for c in string.lowercase if os.path.isdir(c+':\\')] – Berry Tsakala Jun 05 '09 at 18:45
  • 3
    Berry: that will pop up nasty Windows dialogs if you have removable media drives without media in them... – Ted Mielczarek Jun 30 '10 at 18:12
  • This question is 4 years old at the time of this post, so presumably this answer is meant to be compatible with an older version of Python. I'd like to achieve what OP is asking with 2.7, in particular without an external library if possible, but GetLogicalDrives() doesn't seem to exist, at least for the version I'm using... – RTF Sep 01 '13 at 21:11
  • @RTF: It works for me with Python 2.7 (32 bit) - what error are you getting when you try to run it? – RichieHindle Sep 01 '13 at 21:17
  • Oops, I'm actually developing in Linux at the moment, and according to Eclipse, the GetLogicalDrives (class?) doesn't exist. Also, windll causes an import error in interactive python, but not Eclipse. My app is multi-platform, but I haven't started testing in Windows yet. Will windll only be available with Pythons Windows dist, and if so, why does Eclipse recognize the import? – RTF Sep 01 '13 at 21:23
  • Apologies, it's working just fine for me too in Windows with 2.7 – RTF Sep 01 '13 at 21:46
  • Thanks. This is a great solution. I wrapped it with: "#import platform; if platform.uname[0] == 'Windows': " – SoloPilot Nov 13 '14 at 12:50
  • what if I want to also get the name of the Drive? – PNC Oct 16 '17 at 19:06
  • I am getting error in above script as: "AttributeError: module 'string' has no attribute 'uppercase'" So I replace it with string.ascii_uppercase – Nitish Dhapodkar Jan 30 '23 at 02:28
75
import win32api

drives = win32api.GetLogicalDriveStrings()
drives = drives.split('\000')[:-1]
print drives

Adapted from: http://www.faqts.com/knowledge_base/view.phtml/aid/4670

Claudiu
  • 224,032
  • 165
  • 485
  • 680
Ayman Hourieh
  • 132,184
  • 23
  • 144
  • 116
  • I just tried it in 2.6, and got an extra empty string at the end. Still a good answer. – Mark Ransom Jun 05 '09 at 19:47
  • 3
    Just to make sure you don't discard any non-empty strings, consider using `drives = [drivestr in drives.split('\000') if drivestr]` – Wesley Mar 27 '10 at 04:11
  • This also returns virtual CD Drive as a result. Could be a good idea to check if path is valid or not I suppose. – kittenparry Jul 30 '20 at 12:03
  • I had to run `pip install pypiwin32` as administrator before I could use `import win32api`. – user42723 Aug 10 '20 at 21:07
  • I think it wouldn't hurt to follow @Wesley advice, but they made a typo in the command. It should actually read `drives = [drivestr for drivestr in drives.split('\000') if drivestr]`. – Bastian Sep 28 '22 at 21:29
25

Found this solution on Google, slightly modified from original. Seem pretty pythonic and does not need any "exotic" imports

import os, string
available_drives = ['%s:' % d for d in string.ascii_uppercase if os.path.exists('%s:' % d)]
Barmaley
  • 1,232
  • 20
  • 27
19

I wrote this piece of code:

import os
drives = [ chr(x) + ":" for x in range(65,91) if os.path.exists(chr(x) + ":") ]

It's based on @Barmaley's answer, but has the advantage of not using the string module, in case you don't want to use it. It also works on my system, unlike @SingleNegationElimination's answer.

  • 1
    A Python noob here! Why "x in range(65,90)"? How does this work exactly? Some explanation would be useful for people like myself. Thank you in advance! – Amar Aug 18 '20 at 23:41
  • 2
    The ASCII codes 65 to 90 correspond to the letters A-Z. The script checks all possible drive designations (from A: to Z:) and if they exist, they are added to the list. – Sebastian Hietsch Aug 24 '20 at 20:35
  • 2
    I changed it to range(65,91) because it didn't include Z: in the original version. – Sebastian Hietsch Aug 24 '20 at 20:36
14

Those look like better answers. Here's my hackish cruft

import os, re
re.findall(r"[A-Z]+:.*$",os.popen("mountvol /").read(),re.MULTILINE)

Riffing a bit on RichieHindle's answer; it's not really better, but you can get windows to do the work of coming up with actual letters of the alphabet

>>> import ctypes
>>> buff_size = ctypes.windll.kernel32.GetLogicalDriveStringsW(0,None)
>>> buff = ctypes.create_string_buffer(buff_size*2)
>>> ctypes.windll.kernel32.GetLogicalDriveStringsW(buff_size,buff)
8
>>> filter(None, buff.raw.decode('utf-16-le').split(u'\0'))
[u'C:\\', u'D:\\']
Community
  • 1
  • 1
SingleNegationElimination
  • 151,563
  • 33
  • 264
  • 304
10

The Microsoft Script Repository includes this recipe which might help. I don't have a windows machine to test it, though, so I'm not sure if you want "Name", "System Name", "Volume Name", or maybe something else.

import win32com.client 
strComputer = "." 
objWMIService = win32com.client.Dispatch("WbemScripting.SWbemLocator") 
objSWbemServices = objWMIService.ConnectServer(strComputer,"root\cimv2") 
colItems = objSWbemServices.ExecQuery("Select * from Win32_LogicalDisk") 
for objItem in colItems: 
    print "Access: ", objItem.Access 
    print "Availability: ", objItem.Availability 
    print "Block Size: ", objItem.BlockSize 
    print "Caption: ", objItem.Caption 
    print "Compressed: ", objItem.Compressed 
    print "Config Manager Error Code: ", objItem.ConfigManagerErrorCode 
    print "Config Manager User Config: ", objItem.ConfigManagerUserConfig 
    print "Creation Class Name: ", objItem.CreationClassName 
    print "Description: ", objItem.Description 
    print "Device ID: ", objItem.DeviceID 
    print "Drive Type: ", objItem.DriveType 
    print "Error Cleared: ", objItem.ErrorCleared 
    print "Error Description: ", objItem.ErrorDescription 
    print "Error Methodology: ", objItem.ErrorMethodology 
    print "File System: ", objItem.FileSystem 
    print "Free Space: ", objItem.FreeSpace 
    print "Install Date: ", objItem.InstallDate 
    print "Last Error Code: ", objItem.LastErrorCode 
    print "Maximum Component Length: ", objItem.MaximumComponentLength 
    print "Media Type: ", objItem.MediaType 
    print "Name: ", objItem.Name 
    print "Number Of Blocks: ", objItem.NumberOfBlocks 
    print "PNP Device ID: ", objItem.PNPDeviceID 
    z = objItem.PowerManagementCapabilities 
    if z is None: 
        a = 1 
    else: 
        for x in z: 
            print "Power Management Capabilities: ", x 
    print "Power Management Supported: ", objItem.PowerManagementSupported 
    print "Provider Name: ", objItem.ProviderName 
    print "Purpose: ", objItem.Purpose 
    print "Quotas Disabled: ", objItem.QuotasDisabled 
    print "Quotas Incomplete: ", objItem.QuotasIncomplete 
    print "Quotas Rebuilding: ", objItem.QuotasRebuilding 
    print "Size: ", objItem.Size 
    print "Status: ", objItem.Status 
    print "Status Info: ", objItem.StatusInfo 
    print "Supports Disk Quotas: ", objItem.SupportsDiskQuotas 
    print "Supports File-Based Compression: ", objItem.SupportsFileBasedCompression 
    print "System Creation Class Name: ", objItem.SystemCreationClassName 
    print "System Name: ", objItem.SystemName 
    print "Volume Dirty: ", objItem.VolumeDirty 
    print "Volume Name: ", objItem.VolumeName 
    print "Volume Serial Number: ", objItem.VolumeSerialNumber 
John Fouhy
  • 41,203
  • 19
  • 62
  • 77
  • 3
    Thank you for link to Microsoft Script Repository. – Konstantin Tenzin May 15 '09 at 09:18
  • 2
    I've always felt that it is an excellent resource for Windows progarmmers that is not widely-enough known :-) – John Fouhy May 17 '09 at 22:53
  • 2
    Another +1 for the link to the Microsoft Script Repository, I'd never heard of it before. – Mark Ransom Jun 05 '09 at 19:54
  • Link is dead, so here's a saved version of it from WayBackMachine: https://web.archive.org/web/20090626062333/http://www.microsoft.com/technet/scriptcenter/scripts/python/storage/disks/drives/stdvpy05.mspx – Enkouyami Mar 09 '19 at 22:10
  • Thanks @Enkouyami, looks like Microsoft didn't kill the content, only moved it. I hunted around and found a working link. – John Fouhy Mar 11 '19 at 09:09
7

Here is another great solution if you want to list only drives on your disc and not mapped network drives. If you want to filter by different attributes just print drps.

import psutil
drps = psutil.disk_partitions()
drives = [dp.device for dp in drps if dp.fstype == 'NTFS']
PythonMan
  • 787
  • 10
  • 18
4

On Windows you can do a os.popen

import os
print os.popen("fsutil fsinfo drives").readlines()
user2015144
  • 555
  • 2
  • 4
  • 13
3

More optimal solution based on @RichieHindle

def get_drives():
    drives = []
    bitmask = windll.kernel32.GetLogicalDrives()
    letter = ord('A')
    while bitmask > 0:
        if bitmask & 1:
            drives.append(chr(letter) + ':\\')
        bitmask >>= 1
        letter += 1

    return drives
tiredgin
  • 68
  • 6
3

here's a simpler version, without installing any additional modules or any functions. Since drive letters can't go beyond A and Z, you can search if there is path available for each alphabet, like below:

>>> import os
>>> for drive_letter in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ':
        if os.path.exists(f'{drive_letter}:'):
            print(f'{drive_letter}:')
        else:
            pass

the one-liner:

>>> import os
>>> [f'{d}:' for d in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' if os.path.exists(f'{d}:')]
['C:', 'D:']
Naveen Reddy Marthala
  • 2,622
  • 4
  • 35
  • 67
  • A Python noob here! I am guessing the '%s:' is something like a regular expression, but I am not sure. Some explanation regarding how this code really works, would be nice to improve my understanding. Thank you in advance! – Amar Aug 18 '20 at 23:35
  • 1
    no, %s is not regular expressions. it is a string formatter. check my new edit using f-strings. – Naveen Reddy Marthala Aug 19 '20 at 12:36
1

As part of a similar task I also needed to grab a free drive letter. I decided I wanted the highest available letter. I first wrote it out more idiomatically, then crunched it to a 1-liner to see if it still made sense. As awesome as list comprehensions are I love sets for this: unused=set(alphabet)-set(used) instead of having to do unused = [a for a in aphabet if a not in used]. Cool stuff!

def get_used_drive_letters():
    drives = win32api.GetLogicalDriveStrings()
    drives = drives.split('\000')[:-1]
    letters = [d[0] for d in drives]
    return letters

def get_unused_drive_letters():
    alphabet = map(chr, range(ord('A'), ord('Z')+1))
    used = get_used_drive_letters()
    unused = list(set(alphabet)-set(used))
    return unused

def get_highest_unused_drive_letter():
    unused = get_unused_drive_letters()
    highest = list(reversed(sorted(unused)))[0]
    return highest

The one liner:

def get_drive():
    highest = sorted(list(set(map(chr, range(ord('A'), ord('Z')+1))) -
                          set(win32api.GetLogicalDriveStrings().split(':\\\000')[:-1])))[-1]

I also chose the alphabet using map/range/ord/chr over using string since parts of string are deprecated.

flutefreak7
  • 2,321
  • 5
  • 29
  • 39
1

Here's my higher-performance approach (could probably be higher):

>>> from string import ascii_uppercase
>>> reverse_alphabet = ascii_uppercase[::-1]
>>> from ctypes import windll # Windows only
>>> GLD = windll.kernel32.GetLogicalDisk
>>> drives = ['%s:/'%reverse_alphabet[i] for i,v in enumerate(bin(GLD())[2:]) if v=='1']

Nobody really uses python's performative featurability...

Yes, I'm not following Windows standard path conventions ('\\')...
In all my years of using python, I've had no problems with '/' anywhere paths are used, and have made it standard in my programs.

Tcll
  • 7,140
  • 1
  • 20
  • 23
1

This code will return of list of drivenames and letters, for example:

['Gateway(C:)', 'EOS_DIGITAL(L:)', 'Music Archive(O:)']

It only uses the standard library. It builds on a few ideas I found above. windll.kernel32.GetVolumeInformationW() returns 0 if the disk drive is empty, a CD rom without a disk for example. This code does not list these empty drives.

These 2 lines capture the letters of all of the drives:

bitmask = (bin(windll.kernel32.GetLogicalDrives())[2:])[::-1]  # strip off leading 0b and reverse
drive_letters = [ascii_uppercase[i] + ':/' for i, v in enumerate(bitmask) if v == '1']

Here is the full routine:

from ctypes import windll, create_unicode_buffer, c_wchar_p, sizeof
from string import ascii_uppercase

def get_win_drive_names():
    volumeNameBuffer = create_unicode_buffer(1024)
    fileSystemNameBuffer = create_unicode_buffer(1024)
    serial_number = None
    max_component_length = None
    file_system_flags = None
    drive_names = []
    #  Get the drive letters, then use the letters to get the drive names
    bitmask = (bin(windll.kernel32.GetLogicalDrives())[2:])[::-1]  # strip off leading 0b and reverse
    drive_letters = [ascii_uppercase[i] + ':/' for i, v in enumerate(bitmask) if v == '1']

    for d in drive_letters:
        rc = windll.kernel32.GetVolumeInformationW(c_wchar_p(d), volumeNameBuffer, sizeof(volumeNameBuffer),
                                                   serial_number, max_component_length, file_system_flags,
                                                   fileSystemNameBuffer, sizeof(fileSystemNameBuffer))
        if rc:
            drive_names.append(f'{volumeNameBuffer.value}({d[:2]})')  # disk_name(C:)
    return drive_names
EGarbus
  • 136
  • 4
1

This will help to find valid drives in windows os

import os
import string
drive = string.ascii_uppercase
valid_drives = []
for each_drive in drive:
    if os.path.exist(each_drive+":\\"):
       print(each_drive)
       valid_drives.append(each_drive+":\\")
print(valid_drives)

The output will be

C
D
E
['C:\\','D:\\','E:\\']
Jobin James
  • 916
  • 10
  • 13
1

If you want only the letters for each drive, you can just:

from win32.win32api import GetLogicalDriveStrings


drives = [drive for drive in GetLogicalDriveStrings()[0]]
1schware
  • 23
  • 3
0

As I don't have win32api installed on my field of notebooks I used this solution using wmic:

import subprocess
import string

#define alphabet
alphabet = []
for i in string.ascii_uppercase:
    alphabet.append(i + ':')

#get letters that are mounted somewhere
mounted_letters = subprocess.Popen("wmic logicaldisk get name", shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
#erase mounted letters from alphabet in nested loop
for line in mounted_letters.stdout.readlines():
    if "Name" in line:
        continue
    for letter in alphabet:
        if letter in line:
            print 'Deleting letter %s from free alphabet %s' % letter
            alphabet.pop(alphabet.index(letter))

print alphabet

alternatively you can get the difference from both list like this simpler solution (after launching wmic subprocess as mounted_letters):

#get output to list
mounted_letters_list = []
for line in mounted_letters.stdout.readlines():
    if "Name" in line:
        continue
    mounted_letters_list.append(line.strip())

rest = list(set(alphabet) - set(mounted_letters_list))
rest.sort()
print rest

both solutions are similiarly fast, yet I guess set list is better for some reason, right?

Pulec
  • 1
  • 3
0

if you don't want to worry about cross platform issues, including those across python platforms such as Pypy, and want something decently performative to be used when drives are updated during runtime:

>>> from os.path import exists
>>> from sys import platform
>>> drives = ''.join( l for l in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' if exists('%s:/'%l) ) if platform=='win32' else ''
>>> drives
'CZ'

here's my performance test of this code:

4000 iterations; threshold of min + 250ns:
__________________________________________________________________________________________________________code___|_______min______|_______max______|_______avg______|_efficiency
⡇⠀⠀⢀⠀⠀⠀⠀⠀⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⣷⣷⣶⣼⣶⣴⣴⣤⣤⣧⣤⣤⣠⣠⣤⣤⣶⣤⣤⣄⣠⣦⣤⣠⣤⣤⣤⣤⣄⣠⣤⣠⣤⣤⣠⣤⣤⣤⣤⣤⣤⣄⣤⣤⣄⣤⣄⣤⣠⣀⣀⣤⣄⣤⢀⣀⢀⣠⣠⣀⣀⣤⣀⣠
    drives = ''.join( l for l in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' if exists('%s:/'%l) ) if platform=='win32' else '' |      290.049ns |     1975.975ns |      349.911ns |  82.892%
Tcll
  • 7,140
  • 1
  • 20
  • 23
0

List hard drives using command prompt windows in python

Similarly, you can do it for Linux with a few changes

import os,re
regex = r"([^\s]*:)"
driver = os.popen("wmic logicaldisk get name").read()

print(re.findall(regex, driver))

sample output:

['A:', 'C:', 'D:', 'E:', 'F:', 'G:', 'H:', 'I:', 'J:', 'L:']
Milad Shaker
  • 111
  • 1
  • 6
0

From python 3.12(Yet to release the final version) onwards, You can use os.listdrives() which will return a list containing the names of drives on a Windows system. This function uses GetLogicalDriveStringsW windows API under the hood.

>>> import os
>>> help(os.listdrives)
Help on built-in function listdrives in module nt:

listdrives()
    Return a list containing the names of drives in the system.

    A drive name typically looks like 'C:\\'.

>>> os.listdrives()
['C:\\']
Abdul Niyas P M
  • 18,035
  • 2
  • 25
  • 46