3

How do I get from a drive letter to a device instance ID?

My process starts with a device arrival message. I have been successful in getting the drive letter from the arrival message and in opening the dvd tray.

I have searched the various Setup API items; but I haven't found anything that gets me from a drive letter to a device instance ID.

A solution in C# or VB.NET would be ideal, but I'm willing to figure it out from any other language as long as I can see the API calls.

Thanks in advance...

gog
  • 881
  • 9
  • 25
SteveS48
  • 69
  • 3
  • 6

3 Answers3

6

You cannot do it directly.

The link is to use STORAGE_DEVICE_NUMBER. You can use DeviceIoControl with IOCTL_STORAGE_GET_DEVICE_NUMBER on your device name to populate this structure. Put this value to one side.
You then need to get device infomation on your system using SetupDiGetClassDevs setting the GUIDS as approriate, indicicating the drives your are insterested in. Then enumerate through the devices using SetupDiEnumDeviceInfo. Then enumerate the interfaces using SetupDiEnumDeviceInterfaces and finally get the information using SetupDiGetDeviceInterfaceDetail. In this structure returned you can get a DevicePath you can use to get the STORAGE_DEVICE_NUMBER as above. Match this with the STORAGE_DEVICE_NUMBER from your drive letter, and you have now linked a driver letter to your structure. Phew! Inside this structure is a DevInst.

DanDan
  • 10,037
  • 8
  • 49
  • 68
1

i know it's late for you now but not for everybody ^^

I had the same need and this is main line of how I did it:

-You need a window to receive device arrival and removal (as you said)

-Then you create a DeviceNotificationFilter initiated to dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE

-Then in the message loop of your window you look for VM_DEVICECHANGE

-When u receive it if wParam == DBT_DEVICEARRIVAL, use the lParam to check if it is a DBT_DEVTYPE_VOLUME (i was getting the letter and the type of the drive here) or a DBT_DEVTYPE_DEVICEINTERFACE ( there you can use your wellcasted lParam to get the InstanceId from the input structure).

When you connect a drive your receive DEVINTERFACE first then the other. I only give the main line beacause i did this long time ago and i don't have the code here, and also I had found a lot of code pieces on the net (long time ago so there should be more now ^^^) maybe msdn give a full code example to do that now.

If you read this and need more informations, i'll reply or make a full documented answer if many need it.

Hope it will help some of you.

Delatour
  • 45
  • 5
1

I know it's years later but I had to do this and searching brought me here and @DanDan 's answer worked. In order to save future people a lot of work, I thought I'd give back a little and present the technique a bit more explicitly. You'll still have to write a bit of code, but the part I found difficult is below as code:

As DanDan mentioned, the idea is to use CreateFile and DeviceIoControl to get the Windows STORAGE_DEVICE_NUMBER for the disk associated with a file path, and then use the Setup API to enumerate disk devices until we find one whose device instance equals the SDN.

First, here's a summary of how you get the STORAGE_DEVICE_NUMBER from the path (e.g. c:\\users\\bob);

  1. Strip the path to the root (e.g down to C:) and prepend it with \\\\.\\ so you have \\\\.\\C:
  2. Open that path up using CreateFileW with to get metadata
  3. Use DeviceIoControl with IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS to get the extents
  4. Get the DiskNumber member from the first extent returned.
  5. Close the file
  6. Open up \\\\.\\PhysicalDrive<n> where <n> is the that DiskNumber from the first extent
  7. Use DeviceIoControl with code IOCTL_STORAGE_GET_DEVICE_NUMBER to get make it fill out a STORAGE_DEVICE_NUMBER struct as output
  8. Use SetupDiGetClassDevs with arguments &GUID_DEVCLASS_DISKDRIVE and DICGF_PRESENT to get all disks on the system
  9. In a loop, use SetupDiEnumDeviceInfo to get a SP_DEVINFO_DATA repeatedly (on the device list returned by step #8 above) and a call the function below to determine which one, if any, matches the STORAGE_DEVICE_NUMBER for the give path.

(This is edited to remove custom utility classes of mine right on the SO web page so I might have introduced errors/typos)

bool DoesDeviceInstanceEqualStorageDeviceNumber(
    const std::string&      devInstance, 
    STORAGE_DEVICE_NUMBER   sdn)
{   
    // Open up this device instance, specifying that we want the *interfaces*.
    // The interfaces are key key because examining them will let us get a 
    // string we can use the Win32 CreateFile function.  

    const auto hDevInfo = SetupDiGetClassDevsA(
        nullptr,
        devInstance.c_str(), 
        nullptr, 
        DIGCF_DEVICEINTERFACE | DIGCF_ALLCLASSES);

    if (hDevInfo == INVALID_HANDLE_VALUE)
        throws std::runtime_error("Unable to get disk devices");


    DWORD dwSize = 0;
    SP_DEVINFO_DATA did;
    WCHAR buffer[4096];

    did.cbSize = sizeof (did);
    bool foundValidMatch = false;
    int deviceNumber = 0;

    // Iterate through all such devices, looking for one that has a storage device number that matches the given one.

    while ( !foundValidMatch &&  SetupDiEnumDeviceInfo(hDevInfo, deviceNumber, &did))
    {
        deviceNumber++;

        DEVPROPTYPE devPropType;

        // We'll only bother comparing this one if it is fixed.  Determine that.

        const auto getPropResult = SetupDiGetDevicePropertyW (
            hDevInfo, 
            &did, 
            &DEVPKEY_Device_RemovalPolicy,  // Ask for the "removal policy"
            &devPropType, 
            (BYTE*)buffer, 
            sizeof(buffer), 
            &dwSize, 
            0);

        if (!getPropResult)
        {
            std::cerr << "Unable to to get removal policy for disk device: " << ::GetLastError() << std::endl;
            continue;
        }

        /*  This bit *would* skip removable disks, you wanted...
        else if (buffer[0] != 1)
        {
  
            std::cerr << "Skipping removable disk device " << devInstance << std::endl;
            continue;
        }
        */

        // OK this is a fixed disk so it might be the one we'll compare against
        // 1. Get the very first disk interface from this particular disk device
        // 2. Open a file on it
        // 3. Query the resulting file for its device number.
        // 4. Compare the device number to the one we determined above
        // 5. If it matches ours, then we succeed.  If not, continue

        SP_DEVICE_INTERFACE_DATA devIntData;
        devIntData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);

        // Get the disk interfaces
        const auto result = SetupDiEnumDeviceInterfaces(
            hDevInfo,
            &did, //&did,
            &GUID_DEVINTERFACE_DISK, // Get Disk Device Interface (from winioctl.h)
            0,                       // We only need the very FIRST one.  I think...
            &devIntData);

        if (!result)
            continue;

        DWORD dwRequiredSize = 0;

        // Want to get the detail but don't yet know how much space we'll need
        // Do a dummy call to find out

        SetupDiGetDeviceInterfaceDetail(
            hDevInfo, 
            &devIntData, 
            nullptr, 
            0, 
            &dwRequiredSize, 
            nullptr);

        if (ERROR_INSUFFICIENT_BUFFER != ::GetLastError())
        {
            std::cerr << "Unable to get device interface Detail: " << ::GetLastError() << std::endl;;
        }
        else
        {

            // Get the detail data so we can get the device path and open a file.

            std::vector<TCHAR> buf(dwRequiredSize);
            auto pDidd = reinterpret_cast<PSP_DEVICE_INTERFACE_DETAIL_DATA>(buf.data());

            // WARNING: HARD CODED HACK
            // ------------------------
            // https://stackoverflow.com/questions/10405193/vb-net-hid-setupdigetdeviceinterfacedetail-getlasterror-shows-1784-error-inv
            //
            // Don't ask.  Just do what they tell you. 
            // -----------------------------------------------------------------
#ifdef BUILD_64
            pDidd->cbSize = 8;
#else
            pDidd->cbSize = 6;
#endif
            // -----------------------------------------------------------------

            if (!SetupDiGetDeviceInterfaceDetail(
                hDevInfo, 
                &devIntData, 
                pDidd, 
                dwRequiredSize, 
                &dwRequiredSize, 
                nullptr))
            {
                std::cerr << "Cannot get interface detail: " << ::GetLastError());
            }
            else
            {
                // FINALLY:  We now have a DevicePath that we can use to open up
                // in a Win32 CreateFile() call.   That will let us get the
                // STORAGE_DEVICE_NUMBER and compare it to the one we were given.

                const auto hFile = ::CreateFileW(pDidd->DevicePath, 0, FILE_SHARE_READ, nullptr, OPEN_EXISTING, 0, NULL);
                if (INVALID_HANDLE_VALUE != hFile)
                {
                    std::cerr << "Unable to open logical volume: " + devicePath << std::endl;
                    continue;
                }
 
                STORAGE_DEVICE_NUMBER sdnTest;
                ZeroMemory(&sdnTest, sizeof(STORAGE_DEVICE_NUMBER));


                if (0 == DeviceIoControl(
                     hDevInfo
                     IOCTL_STORAGE_GET_DEVICE_NUMBER,
                     nullptr,            // output only so not needed
                     0,                  // output only so not needed
                     &sdnTest,
                     sizeof(STORAGE_DEVICE_NUMBER),
                     nullptr, 
                     nullptr))
                {
                    std::cerr << "Unable to determine storage device number: " << ::GetLastError() << std::endl;);
                }
                else
                {
                    // All this for a one-line test...

                    foundValidMatch = sdnTest.DeviceNumber == sdn.DeviceNumber; 
                }
            }
        }
    }
    SetupDiDestroyDeviceInfoList(hDevInfo);
    return foundValidMatch;
}

I hope this saves someone a headache

Joe
  • 2,355
  • 8
  • 24