2

When going to Windows' "Device manager" and clicking on (almost) any device in the list one piece of information in the "General" tab is called "Location". That is a string that is either:

  • human-readable, like "on NVIDIA GeForce GTX 1080"
  • semi-useful, like "Location 0 (Internal High Definition Audio Bus)" or "PCI bus 9, device 0, function 0"
  • a USB location, like "Port_#0004.Hub_#0015" or even a "0009.0000.0000.004.000.000.000.000.000"

That info is available through Windows' Unified device property model APIs.

What I'm looking for is to get that information from a given IMFActivate object.

Is there a way to do so? I can't find how to get "device" info from that activation object. The only piece of data I have is it's "symbolic link" (in my case, this string: \\?\usb#vid_04b4&pid_8888&mi_00#9&4fe28be&0&0000#{e5323777-f976-4f5b-9b55-b94699c46e44}\global) but that link's format is nothing like the string I see in "Location".

Thus my question: how to get device's "Location" string, given its IMFActivate object?

UPDATE

Here's the code I'm using to "convert" the symbolic link, provided by IMFActivate to a device id string, recognizable by setup-api functions and then extracting the "location string":

CString symLink2Location(const CString & _symLink)
{
    DEVINST di;
    CString devId = _symLink;
    devId = devId.Left( devId.Find(L"#{") );
    devId.Replace(L"\\\\?\\", L"");
    devId.Replace(L"#", L"\\");
    const auto rc = CM_Locate_DevNodeW(&di, devId.GetBuffer(), CM_LOCATE_DEVNODE_NORMAL);
    if(rc == CR_SUCCESS){
        DEVPROPTYPE dpt;
        ULONG sz = MAX_PATH;
        WCHAR prop[MAX_PATH];
        if(CM_Get_DevNode_PropertyW(di, &DEVPKEY_Device_LocationInfo, &dpt, (PBYTE)&prop, &sz, 0) == CR_SUCCESS){
            if(dpt == DEVPROP_TYPE_STRING){
                return prop;
            }
        }
    }
    return L"";
}

UPDATE 2

Here are the 3 audio input devices as seen in devmgmt.msc under "Sound, video and game controllers":

  • MS LifeCam Cinema (TM), location: 0000.0014.0000.013.003.000.000.000.000 (symlink: \\?\SWD#MMDEVAPI#{0.0.1.00000000}.{751fe058-cef2-4d28-bbeb-e438981938d7}#{2eef81be-33fa-4800-9670-1cd474972c3f})
  • MS LifeCam Studio (TM), location: 0000.0014.0000.013.004.004.000.000.000 (symlink: \\?\SWD#MMDEVAPI#{0.0.1.00000000}.{59267d2e-940b-45f5-8655-45372787bd85}#{2eef81be-33fa-4800-9670-1cd474972c3f})
  • SUB2r USB 3.0 HD Webcam, location: 0009.0000.0000.004.000.000.000.000.000 (symlink: \\?\SWD#MMDEVAPI#{0.0.1.00000000}.{26a4f608-cbd8-4206-b958-d57ee6847153}#{2eef81be-33fa-4800-9670-1cd474972c3f})

All 3 are USB devices, all 3 are listed when calling MFEnumDeviceSources but their "symbolic link" doesn't resolve into a hardware device.

YePhIcK
  • 5,816
  • 2
  • 27
  • 52
  • if you have symbolic link( *sz*) - call `CM_Locate_DevNodeW(&dnDevInst, sz, CM_LOCATE_DEVNODE_NORMAL)` and then `CM_Get_DevNode_PropertyW(dnDevInst, &DEVPKEY_Device_LocationInfo, &PropertyType, pb, &rcb, 0)` and you got location – RbMm Jul 14 '17 at 23:52
  • The call to `CM_Locate_DevNodeW()` returns `CR_INVALID_DEVICE_ID`. I suppose the "symbolic link" provided by the `IMFActivation` object is not what the setup-api expects :( (see my original post for a sample of what that link looks like) – YePhIcK Jul 15 '17 at 00:33
  • yes, my mistake. really need device id here, which in your case will be `usb\vid_04b4&pid_8888&mi_00\9&4fe28be&0&0000` exactly – RbMm Jul 15 '17 at 00:45
  • here problem how correct get device id from this string. yes , need replace *#* to \, trancate but...also this string look like is device interface(not sure are exactly) - are `CM_Get_Device_Interface_PropertyW(L"\\\\?\\usb#vid_04b4&pid_8888&mi_00#9&4fe28be&0&0000#{e5323777-f976-4f5b-9b55-b94699c46e44}\\global", &DEVPKEY_Device_LocationInfo,..)` work for you ? here `{e5323777-f976-4f5b-9b55-b94699c46e44}` - this is interface giud (`STATIC_KSCATEGORY_VIDEO_CAMERA`) if you call `CM_Get_Device_Interface_ListW` for this guid - what exactly string returned ? – RbMm Jul 15 '17 at 09:33
  • I've updated the question to show how I'm converting the symbolic link into device ID string and getting the needed "location" string – YePhIcK Jul 15 '17 at 09:37
  • 1
    i know internal (undocumented) rules how symbolic link ins generated from device id (append \\?\, convert \ to #, append class guid in your case this is `{e5323777-f976-4f5b-9b55-b94699c46e44}` (STATIC_KSCATEGORY_VIDEO_CAMERA) but how do this in documented way not sure – RbMm Jul 15 '17 at 09:41
  • I see what you mean. Still your solution works for me so (with the caveat of not knowing the exact documented way) I would still like to mark your answer as useful :) – YePhIcK Jul 15 '17 at 09:43
  • are `CM_Get_Device_Interface_PropertyW(L"\\\\?\\usb#vid_04b4&pid_‌​8888&mi_00#9&4fe28be‌​&0&0000#{e5323777-f9‌​76-4f5b-9b55-b94699c‌​46e44}\\global", &DEVPKEY_Device_LocationInfo,..)` work for you ? so use `CM_Get_Device_Interface_PropertyW` instead `CM_Get_DevNode_PropertyW` – RbMm Jul 15 '17 at 09:53
  • That one returns `CR_NO_SUCH_VALUE` or, if I replace '#' with '\\' - `CR_NO_SUCH_DEVICE_INTERFACE` – YePhIcK Jul 15 '17 at 10:07
  • under key `HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\DeviceClasses\{E5323777-F976-4f5b-9B55-B94699C46E44}\##?#USB#vid_04b4&pid_8888&mi_00#9&4fe28be&0&0000#{E5323777-F976-4f5b-9B55-B94699C46E44}\#GLOBAL\Device Parameters` no location.. however if truncate by #GLOBAL - in key `HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\DeviceClasses\{E5323777-F976-4f5b-9B55-B94699C46E44}\##?#USB#vid_04b4&pid_8888&mi_00#9&4fe28be&0&0000#{E5323777-F976-4f5b-9B55-B94699C46E44}` exist value `DeviceInstance` - `usb\vid_04b4&pid_8888&mi_00\9&4fe28be&0&0000` – RbMm Jul 15 '17 at 10:21
  • 1
    found correct way - need first call `CM_Get_Device_Interface_PropertyW(pszDeviceInterface, &DEVPKEY_Device_InstanceId` with string returned by `IMFAttributes::Get[Allocated]String` and then already use returned device id in call `CM_Locate_DevNodeW` + `CM_Get_DevNode_PropertyW` – RbMm Jul 15 '17 at 11:39
  • JUST FYI: "under key `HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\DeviceCl‌​asses\{E5323777-F976‌​-4f5b-9B55-B94699C46‌​E44}\##?#USB#vid_04b‌​4&pid_8888&mi_00#9&4‌​fe28be&0&0000#{E5323‌​777-F976-4f5b-9B55-B‌​94699C46E44}\#GLOBAL‌​\Device Parameters` no location" - that is because that device is our own pro-sumer 4K@30FPS webcam, so no, you won't have that on your system. Not yet ;-) ......... You can learn more at http://www.sub2r.com/ – YePhIcK Jul 15 '17 at 15:13

1 Answers1

2

the string returned from IMFAttributes::Get[Allocated]String with MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK or MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_AUDCAP_SYMBOLIC_LINK this is device interface string which we can use as input to CM_Get_Device_Interface_PropertyW. for get location information (if it present) need do 3 steps:

  1. call CM_Get_Device_Interface_PropertyW with DEVPKEY_Device_InstanceId - as result we got device instance identifier of a device
  2. use returned string in call CM_Locate_DevNodeW
  3. and finally call CM_Get_DevNode_PropertyW with DEVPKEY_Device_LocationInfo

code example:

CONFIGRET PrintLocation(PCWSTR pszDeviceInterface)
{
    ULONG cb = 0, rcb = 64;

    static volatile UCHAR guz;

    PVOID stack = alloca(guz);
    DEVPROPTYPE PropertyType;

    CONFIGRET err;

    union {
        PVOID pv;
        PWSTR sz;
        PBYTE pb;
    };

    do 
    {
        if (cb < rcb)
        {
            rcb = cb = RtlPointerToOffset(pv = alloca(rcb - cb), stack);
        }

        if (!(err = CM_Get_Device_Interface_PropertyW(pszDeviceInterface, &DEVPKEY_Device_InstanceId, &PropertyType, pb, &rcb, 0)))
        {
            if (PropertyType == DEVPROP_TYPE_STRING)
            {
                DbgPrint("InstanceId=%S\n", sz);

                DEVINST dnDevInst;

                if (!(err = CM_Locate_DevNodeW(&dnDevInst, sz, CM_LOCATE_DEVNODE_NORMAL)))
                {
                    do 
                    {
                        if (cb < rcb)
                        {
                            rcb = cb = RtlPointerToOffset(pv = alloca(rcb - cb), stack);
                        }

                        if (!(err = CM_Get_DevNode_PropertyW(dnDevInst, &DEVPKEY_Device_LocationInfo, &PropertyType, pb, &rcb, 0)))
                        {
                            if (PropertyType == DEVPROP_TYPE_STRING)
                            {
                                DbgPrint("Location=%S\n", sz);
                            }
                            else
                            {
                                err = CR_WRONG_TYPE;
                            }
                        }

                    } while (err == CR_BUFFER_SMALL);
                }
            }
            else
            {
                err = CR_WRONG_TYPE;
            }

            break;
        }

    } while (err == CR_BUFFER_SMALL);

    return err;
}

of course if hardcode buffer size, function can make more simply

CONFIGRET PrintLocationSimp(PCWSTR pszDeviceInterface)
{
    WCHAR buf[1024];

    DEVPROPTYPE PropertyType;

    ULONG BufferSize = sizeof(buf);

    CONFIGRET err;

    if (!(err = CM_Get_Device_Interface_PropertyW(pszDeviceInterface, &DEVPKEY_Device_InstanceId, &PropertyType, (PBYTE)buf, &BufferSize, 0)))
    {
        if (PropertyType == DEVPROP_TYPE_STRING)
        {
            DbgPrint("InstanceId=%S\n", buf);

            DEVINST dnDevInst;

            if (!(err = CM_Locate_DevNodeW(&dnDevInst, buf, CM_LOCATE_DEVNODE_NORMAL)))
            {
                BufferSize = sizeof(buf);

                if (!(err = CM_Get_DevNode_PropertyW(dnDevInst, &DEVPKEY_Device_LocationInfo, &PropertyType, (PBYTE)buf, &BufferSize, 0)))
                {
                    if (PropertyType == DEVPROP_TYPE_STRING)
                    {
                        DbgPrint("Location=%S\n", buf);
                    }
                    else
                    {
                        err = CR_WRONG_TYPE;
                    }
                }
            }
        }
        else
        {
            err = CR_WRONG_TYPE;
        }
    }

    return err;
}

and for IMFActivate we can use next code:

void mftest()
{
    IMFAttributes *pAttributes;

    if (SUCCEEDED(MFCreateAttributes(&pAttributes, 1)))
    {
        UINT32 count, cchLength;
        IMFActivate **ppDevices, *pDevice;

        if (SUCCEEDED(pAttributes->SetGUID(
            MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE, MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID)) && 
            SUCCEEDED(MFEnumDeviceSources(pAttributes, &ppDevices, &count)) &&
            count)
        {
            PVOID pv = ppDevices;

            do 
            {
                pDevice = *ppDevices++;

                PWSTR pszDeviceInterface;

                if (SUCCEEDED(pDevice->GetAllocatedString(
                    MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK, &pszDeviceInterface, &cchLength)))
                {
                    DbgPrint("%S\n", pszDeviceInterface);

                    PrintLocation(pszDeviceInterface);

                    CoTaskMemFree(pszDeviceInterface);
                }

            } while (--count);

            CoTaskMemFree(pv);
        }

        pAttributes->Release();
    }
}
RbMm
  • 31,280
  • 3
  • 35
  • 56
  • I couldn't find if the max length of the "location" is `MAX_PATH` or `1024`. Or something else. It "feels" like `MAX_PATH` should be it, but I can't confirm that. – YePhIcK Jul 15 '17 at 15:09
  • 1
    @YePhIcK - i usually prefer not hardcode buffer size but allocate it dynamic in loop - begin from some reasonable size and if `CR_BUFFER_SMALL` allocate additional space - like in `PrintLocation` – RbMm Jul 15 '17 at 15:26
  • That method works great for video capturing devices. Fails (`CM_Get_DevNode_PropertyW()` returns `CR_NO_SUCH_VALUE`) for audio capturing devices. Just making a note here – YePhIcK Jul 15 '17 at 18:09
  • @YePhIcK - yes, for audio not return location. but this is not error. not every device have location information. this is depend from every concrete device - some have and some not. look in `devmgmt.msc` - you got the same result. and some properties have sense for interface (`CM_Get_Device_Interface_PropertyW`) and some only for device instance (`CM_Get_DevNode_PropertyW`) - note that the same device instance can have multiple interfaces. because this location exist only for devnode but not for interface. also can try look for another keys defined in `devpkey.h` – RbMm Jul 15 '17 at 18:14
  • I have 5 devices listed in "Sound, video and game controllers" and every one of them has a "location" information (non-empty). I suspect that the list returned by `MFEnumDeviceSources` (which in my case only shows 3 out of 5 - properly skips 2 audio **out** devices) somehow doesn't correspond to what I see in `devmgmt.msc` – YePhIcK Jul 15 '17 at 18:21
  • @YePhIcK - look better in `Audio inputs and outputs` - here device. may be microphone – RbMm Jul 15 '17 at 18:25
  • Ha, you are right! Those have their location set as "on Microsoft® LifeCam Cinema(TM)" and such :) Though not empty, as reported by the code in this solution. – YePhIcK Jul 15 '17 at 18:28
  • 1
    you can for example change `DEVPKEY_Device_LocationInfo` to `DEVPKEY_Device_FriendlyName` query. i for `MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_AUDCAP_SYMBOLIC_LINK` got `Microphone (High Definition Audio Device)` device id is `SWD\MMDEVAPI\{0.0.1.00000000}.{6630cade-5c94-4e5f-8a6e-ee02877a4f3f}` – RbMm Jul 15 '17 at 18:30
  • and in devmgmt.msc - microphone also have not location property. so this is not error. can be several device id - need be sure that device id exactly match symbol to symbol – RbMm Jul 15 '17 at 18:31