-1

So i'm building an app for a school PC that tracks all inserted devices. I managed to use RegisterDeviceNotification() to get a notification in the main thread whenever i plug in or remove a device. All i can get though, is the LParam, a pointer that is device unique.

I can't find anything about how to get the friendly name of the device using that LParam. The only resource i can find is this CodeProject from 2006 (in C++).

I can't find anything on pinvoke.net and the only thing i found (i don't remember exactly where) is using a ManagementObjectSearcher to get this data, but it finds null data. (here's the code)

ManagementObjectSearcher searcher = new ManagementObjectSearcher("Select Name from Win32_PnpEntity");

            foreach (ManagementObject devices in searcher.Get())
            {
                foreach (var v in devices.Properties)
                {
                    Console.WriteLine(v.Value);
                }
            }

            searcher.Dispose();

Can anyone please help me figure out how to get the friendly name of the device?

Adryzz
  • 33
  • 7
  • when you say connected devices, is this mainly for usbs?, the above code works for me – traveler3468 Sep 10 '20 at 23:37
  • [How do I get information about recently connected USB device?](https://stackoverflow.com/a/54298316/7444103). – Jimi Sep 10 '20 at 23:40
  • Mainly for USB devices because a student isn't likely to carry around a thunderbolt 3 graphics card at school or other non-usb devices, but i want it to work with every device. – Adryzz Sep 10 '20 at 23:40
  • To read (almost) all the information available about the USB device detected, see [Get the serial number of USB storage devices in .Net Core 2.1](https://stackoverflow.com/a/51806262/7444103) (it doesn't return just the Serial Number, of course). Tagged .Net Core, but it's the same thing in .Net Framework – Jimi Sep 10 '20 at 23:47
  • @Jimi 's first answer works really well for disk drives, but how can i make it work for every device type? – Adryzz Sep 10 '20 at 23:49
  • 3
    Which answer? The latter I linked? If you mean that one, that's about USB devices used for file storage. If you need information about another type of device, you have to query its specific class, e.g., `Win32_Printer`. If you just want some basic info, those returned by the event interface may be enough (what's in the first link). You can also query the generic device by ID, for some generic info. It depends on how deep you want to go. – Jimi Sep 10 '20 at 23:54

1 Answers1

1

demo code:

struct DeviceName : public LIST_ENTRY 
{
    ULONG InterfaceHash;
    WCHAR Name[];

    void* operator new(size_t cb, size_t len)
    {
        return LocalAlloc(0, cb + len);
    }

    void operator delete(void* pv)
    {
        LocalFree(pv);
    }
};

volatile const UCHAR guz = 0;
CONFIGRET GetFriendlyNameByDevNode(DeviceName** pp, DEVINST dnDevInst)
{
    CONFIGRET status;

    ULONG cb = 32;

    DEVPROPTYPE PropertyType;

    do 
    {
        if (DeviceName* p = new(cb) DeviceName)
        {
            status = CM_Get_DevNode_PropertyW(
                dnDevInst, &DEVPKEY_DeviceInterface_FriendlyName, &PropertyType, (PBYTE)p->Name, &cb, 0);

            if (status == CR_SUCCESS)
            {
                if (PropertyType == DEVPROP_TYPE_STRING)
                {
                    *pp = p;
                    return CR_SUCCESS;
                }
                else
                {
                    status = CR_WRONG_TYPE;
                }
            }

            delete p;
        }
        else
        {
            status = CR_OUT_OF_MEMORY;
        }

    } while (CR_BUFFER_SMALL == status);

    return status;
}

CONFIGRET GetFriendlyNameByInterface(DeviceName** pp, PCWSTR pszDeviceInterface)
{
    // RTCu must be disabled !
    ULONG cb = 0, rcb = 64;

    PVOID stack = alloca(guz);
    DEVPROPTYPE PropertyType;

    CONFIGRET status;

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

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

    } while (CR_BUFFER_SMALL == (status = CM_Get_Device_Interface_PropertyW(
        pszDeviceInterface, &DEVPKEY_Device_InstanceId, &PropertyType, pb, &rcb, 0)));

    if (status == CR_SUCCESS)
    {
        if (PropertyType == DEVPROP_TYPE_STRING)
        {
            DEVINST dnDevInst;

            status = CM_Locate_DevNodeW(&dnDevInst, DeviceID, CM_LOCATE_DEVNODE_NORMAL);

            return status == CR_SUCCESS ? GetFriendlyNameByDevNode(pp, dnDevInst) : status;
        }
        else
        {
            status = CR_WRONG_TYPE;
        }
    }

    return status;
}

        case WM_DESTROY:
            if (_hDevNot)
            {
                UnregisterDeviceNotification(_hDevNot);

                PLIST_ENTRY head = &_DevListHead, entry = head->Flink;

                while (entry != head)
                {
                    DeviceName* p = static_cast<DeviceName*>(entry);

                    entry = entry->Flink;

                    delete p;
                }
            }
            break;

        case WM_DEVICECHANGE:
            switch (wParam)
            {
            case DBT_DEVICEREMOVECOMPLETE:
            case DBT_DEVICEARRIVAL:
                if (reinterpret_cast<PDEV_BROADCAST_DEVICEINTERFACE>(lParam)->dbcc_devicetype == DBT_DEVTYP_DEVICEINTERFACE)
                {
                    DeviceName* p;
                    ULONG InterfaceHash;
                    UNICODE_STRING dbcc_name;
                    RtlInitUnicodeString(&dbcc_name, reinterpret_cast<PDEV_BROADCAST_DEVICEINTERFACE>(lParam)->dbcc_name);
                    RtlHashUnicodeString(&dbcc_name, FALSE, HASH_STRING_ALGORITHM_DEFAULT, &InterfaceHash);

                    if (wParam == DBT_DEVICEARRIVAL)
                    {
                        if (CR_SUCCESS == GetFriendlyNameByInterface(&p, dbcc_name.Buffer))
                        {
                            p->InterfaceHash = InterfaceHash;
                            InsertHeadList(&_DevListHead, p);
                            DbgPrint("inserted %S ( %wZ )\n", p->Name, &dbcc_name);
                        }
                    }
                    else
                    {

                        PLIST_ENTRY head = &_DevListHead, entry = head;

                        while ((entry = entry->Flink) != head)
                        {
                            if (static_cast<DeviceName*>(entry)->InterfaceHash == InterfaceHash)
                            {
                                DbgPrint("removed %S ( %wZ )\n", 
                                    static_cast<DeviceName*>(entry)->Name, &dbcc_name);

                                RemoveEntryList(entry);
                                delete static_cast<DeviceName*>(entry);
                                break;
                            }
                        }
                    }
                }
                break;
            }
            return 0;

        case WM_CREATE:
            InitializeListHead(&_DevListHead);
            static DEV_BROADCAST_DEVICEINTERFACE dbd = { sizeof(dbd), DBT_DEVTYP_DEVICEINTERFACE };
            _hDevNot = RegisterDeviceNotificationW(hwnd, &dbd, DEVICE_NOTIFY_WINDOW_HANDLE|DEVICE_NOTIFY_ALL_INTERFACE_CLASSES);
            break;
RbMm
  • 31,280
  • 3
  • 35
  • 56
  • [operator new](https://en.cppreference.com/w/cpp/memory/new/operator_new) (as implemented here) is not allowed to return an invalid pointer. – IInspectable Sep 11 '20 at 10:12
  • @IInspectable - i overwrite *operator new*, how you can see. also i think you know about (5) variant with `const std::nothrow_t& tag` - *and returns a null pointer on failure instead of propagating the exception.*. and code which i write anyway comiled with any problem. so what you want ? why you again about my usage of *union* nothing say ? – RbMm Sep 11 '20 at 10:24
  • Sure, if you're up for that. You aren't implementing the `nothrow` overload, so yes, it's illegal for that implementation to return an invalid pointer. Though none of that matters much anyway, since you are potentially leaking resources left and right by avoiding proper RAII wrappers. Though, again, that doesn't matter a whole lot as the code just follows your standard *"if I cannot observe failure the code is correct"* fallacy. There's hardly any valid code here. I'm not going to explain this again since you wouldn't understand. – IInspectable Sep 11 '20 at 10:48
  • @IInspectable - and where in concrete code was resource leak ? can point ? my new illegal why ? this code is compiled without any errors and warning. what is concrete wrong in code ? – RbMm Sep 11 '20 at 11:59
  • A C++ compiler is **explicitly** allowed to produce false positives to the question: "Is this a program?" Search for cases of IFNDR in the language specification, and you might begin to understand that not producing any warnings means next to nothing. – IInspectable Sep 11 '20 at 12:09
  • @IInspectable - not use RAII != potentially leaking resources - in my concrete code no memory leaking or can you show where ? and code with new correct - or again - where concrete error ? what concrete can execute wrong ? – RbMm Sep 11 '20 at 12:24
  • **Every** raw pointer is a potential resource leak. Since you've never worked in a team, it's understandable that the idea of code changing over time, without you noticing is pretty opaque to you. Anyway, since you are begging to learn about concrete issues, here's one: You aren't getting suitably aligned memory from `LocalAlloc`, though on x86 you'll never notice. – IInspectable Sep 11 '20 at 12:46
  • 1
    @IInspectable - *potential resource leak* not mean real. and about `LocalAlloc` *suitably aligned memory* - you wrong. this api return `2*sizeof(PVOID)` aligned memory when i need only `sizeof(ULONG)` align. so all ok here – RbMm Sep 11 '20 at 13:13
  • @IInspectable - `DeviceName` requeire align as `LIST_ENTRY` - sizeof(PVOID) (forget about this at by mistake write `sizeof(DWORD)` ) and this aligment grantee by `LocalAloc`- so you wrong here as usual – RbMm Sep 11 '20 at 14:26
  • @IInspectable - *where does the idea of data having identical size and alignment requirements come from?* - you even never can be concrete. (1) `struct DeviceName` require the same align as `LIST_ENTRY` and same as `__alignof(PVOID)==sizeof(PVOID)` in in this concrete case. (2) LocalAlloc as and HeapAlloc return data aligned on `2*sizeof(PVOID)` or can even say 8 and 16 on 32 and 64 bit/ and so what ? i not say *data having identical size and alignment* i only say what concrete by size align required and returned – RbMm Sep 11 '20 at 14:42
  • @RbMm Your code sample helped me, but I don't understand two things -- first, why use `LocalAlloc()` which is deprecated (`HeapAlloc()` should be used instead), and second, why use loops and `allloca()` with horrible pointer arithmetic when all those `CM_` APIs can accept `nullptr` in `PropertyBuffer` and `0` in `PropertyBufferSize` to return actual size needed? – Igor Levicki Feb 25 '22 at 16:30
  • @IgorLevicki `LocalAlloc(0, dwBytes)` equal to `HeapAlloc(GetProcessHeap(), 0, dwBytes)`. about `alloca` - i not view nothing horrible here, also in some situations data which we request can change between calls. so if we first get required buffer size in first call - it anyway can be small in second call - so need loop. also i preffer use loop always, even if data can not change between calls - in loop we have single api call in code (but it can be called multiple times(can be and single)) instead have multiple(2) call to api in code. if we assume that data size always small - use stack ok – RbMm Feb 25 '22 at 23:07
  • @RbMm The `alloca()` function returns a pointer to the beginning of the allocated space. If the allocation causes stack overflow, program behaviour is undefined -- i.e. you have no way of knowing whether the pointer returned is valid. Also, if you or someone using your code calls any C code that uses `setjmp / longjmp`, the allocation with `alloca()` will be undone. It just adds unnecessary risks. As for `LocalAlloc()`, it has greater call overhead -- `HeapAlloc()` maps directly to `RtlAllocateHeap()`. Finally, `RtlPointerToOffset()` is a macro intended for use in kernel mode drivers. – Igor Levicki Mar 04 '22 at 12:37
  • @IgorLevicki my code have no stackoverflow or UB. and i perfect know what alloca return. and i not use setjmp / longjmp here. dont undertand about "risks". i know wat i doing. *for LocalAlloc(), it has greater call overhead* - this is absolute false. look for it implementation. and about `RtlPointerToOffset` - macro is only macro. it independed from kernel or user mode – RbMm Mar 05 '22 at 13:06
  • @RbMm You obviously never read [this](https://man7.org/linux/man-pages/man3/alloca.3.html) -- check under RETURN VALUE. `LocalAlloc()` definitely has greater overhead, and you can disassemble it to see yourself (I did before making the claim). Word "deprecated" means "do not use in new code" (usually because it will be removed in the future). Macro is defined in Windows Driver Kit, not in Windows SDK so it won't even compile for 99% of Windows developers. – Igor Levicki Mar 21 '22 at 14:47
  • @IgorLevicki - *LocalAlloc* with `LMEM_FIXED == 0` have no any overhead compare `HeapAlloc` this is exactly the same by fact. you bad look in debugger. i know how alloca work and what it return. macro is only macro. you can copy-paste it definition, ifcan not include ntifs.h or use it analog – RbMm Mar 21 '22 at 14:54
  • @RbMm `LocalAlloc` is exported from `kernel32.dll`, and it is just a stub that jumps to `LocalAlloc` in `kernelbase.dll`. If you disassemble `kernelbase.dll`, you will see that `LocalAlloc` has a considerable amount of code for stack unwinding, validating parameters, and testing flags before calling `RtlAllocateHeap` from `ntdll.dll`. On the other hand, `HeapAlloc` export from `kernel32.dll` is directly aliased to `NTDLL.RtlAllocateHeap`, it doesn't even have a stub / jump and a reference to it is resolved directly by the OS loader. So no, you have no idea what you are talking about. – Igor Levicki Mar 21 '22 at 15:24
  • @IgorLevicki - i pefrect know all this, which api from which dll exported, what is stub, forward export, etc. several instructions in `LocalAlloc` body before call `RtlAllocateHeap` - this nothing, compare instructions count inside `RtlAllocateHeap`. – RbMm Mar 21 '22 at 15:31
  • @RbMm `RtlAllocateHeap` is executed in both cases so there is no point in looking inside it. On the other hand, the 100+ bytes of extra code at the start of `LocalAlloc` does not have to be executed at all if you call `HeapAlloc` directly. That is exactly why Microsoft says `LocalAlloc` is deprecated, and why `HeapAlloc` should be used if you are allocating from the heap. – Igor Levicki Mar 24 '22 at 12:18
  • @IgorLevicki +30-40 instructions (and no interlocked from it) - this is nothing. * so there is no point in looking inside it* - this is not true - say 50+35 and 2000+35 - not the same. and if not take to account another code (system calls, etc). this is nothing. *That is exactly why Microsoft says LocalAlloc is deprecated* - this is absolute false. also i have advice to you - never use new/delete or malloc for instance - this is have greater overhead compare `RtlAllocateHeap`. never use api kernel32 dll, if exist such in ntdll - because *greater overhead* – RbMm Mar 24 '22 at 12:58