2

How to get the drive letter of an SD Card connected to a PC, from a C# .NET Framework application?

I have looked at suggested questions on this topic, including this, this & this, but none of them give me the solution I need.

Using System.IO.DriveInfo.GetDrives() or System.Management.ManagementObjectSearcher() with query "Win32_LogicalDisk", I can get the drive letters of all devices, but I can't tell which device(s) is the SD card. Using System.Management.ManagementObjectSearcher() with query "CIM_LogicalDevice", "Caption = 'SDHC Card'", I get 2 devices with the "SDHC Card" caption property, but no drive letters.

How can I get the drive letter of the SD Card or card reader?

Here is what I have tried so far:

using System;
using System.Management;

namespace Code3
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("\tfrom: 'ManagementObjectSearcher()' with query \"Win32_LogicalDisk\"");
            var searcher1 = new ManagementObjectSearcher(@"\root\cimv2", "SELECT * FROM Win32_LogicalDisk");
            foreach (ManagementBaseObject disk in searcher1.Get())
            {
                string diskID = disk.GetPropertyValue("DeviceID").ToString();
                int driveType = Convert.ToInt32(disk.GetPropertyValue("DriveType"));
                string diskCaption = disk.GetPropertyValue("Caption").ToString();
                string diskDescription = disk.GetPropertyValue("Description").ToString();
                string diskName = disk.GetPropertyValue("Name").ToString();
                int diskMediaType = Convert.ToInt32(disk.GetPropertyValue("MediaType"));
                Console.WriteLine($"{diskName} - ID: {diskID},  Caption: {diskCaption},  Desc.: {diskDescription,-16},  Drive Type: {driveType},  Media Type: {diskMediaType}.");
            }
            Console.WriteLine();

            Console.WriteLine("\tfrom: 'ManagementObjectSearcher()' with query SelectQuery(\"CIM_LogicalDevice\", \"Caption = 'SDHC Card'\")");
            ManagementScope mgmtScope = new ManagementScope(@"\root\cimv2");
            SelectQuery devQuery = new SelectQuery("CIM_LogicalDevice", "Caption = 'SDHC Card'");
            var searcher2 = new ManagementObjectSearcher(mgmtScope, devQuery);

            foreach (ManagementBaseObject device in searcher2.Get())
            {
                Console.WriteLine($"{device.GetPropertyValue("Name"),-15} - Caption: {device.GetPropertyValue("Caption")},  Device ID: {device.GetPropertyValue("DeviceID")}.");
                continue; // ... to skip property display

                if (!string.IsNullOrEmpty(device.GetPropertyValue("Name").ToString()))
                {
                    PropertyDataCollection props = device.Properties;
                    Console.WriteLine($"\n\t\tProperties of {device.GetPropertyValue("DeviceID")} Drive: \n");
                    foreach (var prop in device.Properties)
                    {
                        if (prop.Value != null)
                            Console.WriteLine($"{prop.Name,-20} - {prop.Type,-8} - {prop.Value ?? "(null)"}");
                    }
                    Console.WriteLine();
                }
            }

            Console.ReadKey();
        }
    }
}

Thank you for any help you can give me.

EDIT: From "CIM_LogicalDisk", I can see that "F:" drive is my SD-Card. (from 'VolumeName' property.)

From "CIM_LogicalDevice", I can see the "\.\PHYSICALDRIVE1" and "PCISTOR\DISK&VEN_RSPER&PROD_RTS5208LUN0&REV_1.00\0000" is my SD-Card. (from 'Name', 'Caption', and/or 'Model' properties.)

But my app can't see this! Note that 'drive letter' and 'PHYSICALDRIVE number' do not remain correlated, and can change as different removable devices are inserted and removed.

How can I get my code to make the connection between logical and physical drives?

JNygrenLT
  • 173
  • 12

2 Answers2

0

If you are really sure that your SD card's volume label is always "SDHC Card" (which I am not), then you can use the following approach:

The DriveInfo class contains a static method GetDrives which returns an array of DriveInfo instances. Each instance itself represents on logical drive. You can use the VolumeLabel property to check the name of the volume.

So something like...

var drives = DriveInfo.GetDrives().Where(drive => drive.VolumeLabel == "SDHC Card");

...returns all drives where the volume is called "SDHC Card". If you want to get the drive letter of the logical drive you can access it by using the RootDirectory property of a concrete instance.

Like:

var drives = DriveInfo.GetDrives().Where(drive => drive.VolumeLabel == "SDHC Card");

foreach (var drive in drives)
    Console.WriteLine(drive.RootDirectory.FullName);
Markus Safar
  • 6,324
  • 5
  • 28
  • 44
  • Markus, you are correct. since my app will be used with new (possibly unformatted?) SD cards from various makers, DriveInfo has no reliable indicator for SD cards. Thanks. – JNygrenLT Jun 23 '21 at 13:09
0

I finally got a solution worked out. Using WMI association classes, I was able to make the connection between logical and physical drives.

This class is my solution:

using System.Collections.Generic;
using System.Management;

namespace GetSDCard
{
    public class GetSDCards
    {
        public Card[] GetCards()
        {
            return FindCards().ToArray();
        }

        private List<Card> FindCards()
        {
            List<Card> cards = new List<Card>();

            //  Get Disk Drives collection (Win32_DiskDrive)
            string queryDD = "SELECT * FROM Win32_DiskDrive WHERE Caption = 'SDHC Card'";
            using (ManagementObjectSearcher searchDD = new ManagementObjectSearcher(queryDD))
            {
                ManagementObjectCollection colDiskDrives = searchDD.Get();
                foreach (ManagementBaseObject objDrive in colDiskDrives)
                {
                    //  Get associated Partitions collection (Win32_DiskDriveToDiskPartition)
                    string queryPart = $"ASSOCIATORS OF {{Win32_DiskDrive.DeviceID='{objDrive["DeviceID"]}'}} WHERE AssocClass = Win32_DiskDriveToDiskPartition";
                    using (ManagementObjectSearcher searchPart = new ManagementObjectSearcher(queryPart))
                    {
                        ManagementObjectCollection colPartitions = searchPart.Get();
                        foreach (ManagementBaseObject objPartition in colPartitions)
                        {
                            //  Get associated Logical Disk collection (Win32_LogicalDiskToPartition)
                            string queryLD = $"ASSOCIATORS OF {{Win32_DiskPartition.DeviceID='{objPartition["DeviceID"]}'}} WHERE AssocClass = Win32_LogicalDiskToPartition";
                            using (ManagementObjectSearcher searchLD = new ManagementObjectSearcher(queryLD))
                            {
                                ManagementObjectCollection colLogicalDisks = searchLD.Get();
                                foreach (ManagementBaseObject objLogicalDisk in colLogicalDisks)
                                    cards.Add(new Card($"{objLogicalDisk["DeviceID"]}", $"{objDrive["Caption"]}", $"{objLogicalDisk["VolumeName"]}"));
                            }
                        }
                    }
                }
            }

            return cards;
        }


        public class Card
        {
            public string Drive { get; set; }
            public string Name { get; set; }
            public string Label { get; set; }

            public Card(string _drive, string _name, string _label)
            {
                Drive = _drive;
                Name = _name;
                Label = _label;
            }
        }
    }
}

Here is a simple console app to demonstrate how to use it.

using GetSDCard;
using System;
using System.IO;

namespace FindSDCard_Demo
{
    class Program
    {
        static void Main(string[] args)
        {
            GetSDCards getter = new GetSDCards();
            GetSDCards.Card[] sdCards = getter.GetCards();
            if (sdCards.Length == 0)
                Console.WriteLine("No SD Cards found.");
            else
            {
                string sdDrive = sdCards[0].Drive;
                Console.WriteLine($"Root folder of SD Card '{sdDrive}':");
                foreach (var folder in Directory.GetDirectories(sdDrive))
                    Console.WriteLine($"\t{folder}");
            }
        }
    }
}

I hope that this can save you the hours of frustration I went through.

JNygrenLT
  • 173
  • 12