4
votes

I'm writing a Windows library using C++. This library should be able to check if the device driver of a specific device is installed on the system. So I am searching for a way to check if a driver is installed for a known Device ID.

So far, I found this information:

SetupDiBuildDriverInfoList lists available drivers for given devices. However, I have to supply more than just a Device ID.

SetupDiGetClassDevs seems to return exactly what I need for calling SetupDiBuildDriverInfoList, but it still doesn't take a Device ID as input. It may take a GUID of a device setup/interface class, but if I understand it correctly, a vendor-specific driver does not have such a GUID. It can also take a PnP enumerator, which I don't know enough about to tell whether I can use that somehow. Or finally, it may take a Device Instance ID - but not a Device ID.

Obviously, I want to check for any device of the same kind, so querying by Device Instance ID is not feasible. So, the question is: How do I check whether the driver for a given Device ID (or any other information that can identify the device; I assume Device ID is the right thing here) is installed, using the API functions I have listed (or any other way)?

2
So enumerate all devices, find their instance ID and check on that (if it matches your device ID)?Mats Petersson
@MatsPetersson Um, if there isn't an easier way to do it… but does that work if the device isn't connected?flyx
You mean like I've got a scanner sitting on the floor, not connected to the machine, and I want to know if the driver already is installed on the machine, before I plug it in? Probably not... You have to actually plug in the device (at least once)Mats Petersson
Hmm, okay. Then I just need to get the device when it's plugged in. Are you saying that there is no other way to get a connected device from its ID other than looping through all devices?flyx
Perhaps CM_Locate_DevNode?Harry Johnston

2 Answers

4
votes

Harry Johnston's answer brought me close, but I had to add a bit more to make it work. The magic bit that was missing was that I had to call both SetupDiEnumDeviceInfo and SetupDiBuildDriverInfoList before SetupDiEnumDriverInfoW actually did something useful.

Here is a complete (modulo cleanup) example, replace the string passed to SetupDiGetClassDevsW to match your own device. For my specific device it prints

Driver found: description: USBXpress Device, MfgName: Silicon Labs, ProviderName: Silicon Laboratories Inc.

on a PC with the driver installed and

No driver found

on a PC (actually VM) with no driver installed.

#include <Windows.h>
#include <SetupAPI.h>
#include <stdio.h>
#include <stdlib.h>

#pragma comment(lib, "setupapi.lib")

int main(int argc, char ** argv)
{
    HDEVINFO hdevinfo = SetupDiGetClassDevsW(NULL, LR"(USB\VID_10C4&PID_EA61)",
                                             NULL, DIGCF_ALLCLASSES);
    if (hdevinfo == INVALID_HANDLE_VALUE)
    {
        DWORD err = GetLastError();
        printf("SetupDiGetClassDevs: %u\n", err);
        return 1;
    }

    SP_DEVINFO_DATA devinfo;
    devinfo.cbSize = sizeof(devinfo);
    if (!SetupDiEnumDeviceInfo(hdevinfo, 0, &devinfo))
    {
        DWORD err = GetLastError();
        printf("SetupDiEnumDeviceInfo: %u %d\n", err, 0);
        return 1;
    }

    if (!SetupDiBuildDriverInfoList(hdevinfo, &devinfo, SPDIT_COMPATDRIVER)) {
        printf("error %d\n", GetLastError());
        return 1;
    }

    SP_DRVINFO_DATA_W drvdata;
    drvdata.cbSize = sizeof(SP_DRVINFO_DATA_W);
    BOOL worked = SetupDiEnumDriverInfoW(hdevinfo, &devinfo, SPDIT_COMPATDRIVER,
                                         0, &drvdata);
    if (worked) {
        printf("Driver found: description: %ws, MfgName: %ws, ProviderName: %ws\n",
               drvdata.Description, drvdata.MfgName, drvdata.ProviderName);
    }
    else {
        DWORD err = GetLastError();
        if (err == ERROR_NO_MORE_ITEMS)
            printf("No driver found\n");
        else {
            printf("SetupDiEnumDriverInfoW: %d", err);
            return 1;
        }
    }
    return 0;
}
3
votes

You can convert a device ID to a list of device instance IDs like this:

#include <Windows.h>
#include <Cfgmgr32.h>
#include <SetupAPI.h>
#include <stdio.h>

#pragma comment(lib, "setupapi.lib")

int main(int argc, char ** argv)
{
    static wchar_t buffer[1024 * 1024];
    wchar_t * ptr;
    CONFIGRET result;

    result = CM_Get_Device_ID_ListW(L"usb\\vid_0461&pid_4d15", buffer,
               _countof(buffer), CM_GETIDLIST_FILTER_ENUMERATOR);

    if (result != CR_SUCCESS)
    {
        printf("CM_Get_Device_ID_ListW: %u\n", result);
        return 1;
    }

    ptr = buffer;

    while (*ptr)
    {
        printf("%ws\n", ptr);
        ptr += wcslen(ptr) + 1;
    }

    printf("Done\n");
    return 0;
}

Or like this:

int main(int argc, char ** argv)
{
    HDEVINFO hdevinfo;

    SP_DEVINFO_DATA devinfo;
    wchar_t instance_id[4096];
    DWORD n;

    hdevinfo = SetupDiGetClassDevs(NULL, L"usb\\vid_0461&pid_4d15", 
                 NULL, DIGCF_ALLCLASSES);

    if (hdevinfo == INVALID_HANDLE_VALUE)
    {
        DWORD err = GetLastError();
        printf("SetupDiGetClassDevs: %u\n", err);
        return 1;
    }

    for (n = 0;; n++)
    {
        devinfo.cbSize = sizeof(devinfo);
        if (!SetupDiEnumDeviceInfo(hdevinfo, n, &devinfo))
        {
            DWORD err = GetLastError();
            printf("SetupDiEnumDeviceInfo: %u\n", err);
            break;
        }

        if (!SetupDiGetDeviceInstanceId(hdevinfo, &devinfo, 
               instance_id, _countof(instance_id), NULL))
        {
            DWORD err = GetLastError();
            printf("SetupDiGetDeviceInstanceId: %u\n", err);
        }
        else
        {
            printf("DevicePath: %ws\n", instance_id);
        }
    }

    return 0;
}

The first code sample uses the older API, CM_Get_Device_ID_List.

The second code sample uses the newer API, SetupDiGetClassDevs, but note that the ability to use a device ID instead of just an enumerator isn't documented. It works, but it isn't documented.

In either case, the device does not have to be currently present, but it must have been installed at some point in the past.

If you aren't sure of the correct format for the device ID you want, you can list all device instances. (The device ID is just the device instance ID with the instance-specific ID removed, i.e., everything up to but excluding the last backslash.)

In the first code sample, use CM_GETIDLIST_FILTER_NONE to get a list of all device instances.

In the second code sample, pass NULL instead of the device ID string to get a list of all device instances.