How do I enumerate remembered connections that are not currently connected?


Harry Johnston wanted to know how to get a list of remembered (but not currently connected) drive mappings.

The idea here is to make a tweak to the Little Program. Start with what we had and make these changes:

int __cdecl main(int, char **)
{
 HANDLE hEnum;
 WNetOpenEnum(RESOURCE_REMEMBERED,
              RESOURCETYPE_DISK,
              0,
              NULL,
              &hEnum);

 ...
}

This changes the program from enumerating connected resources to enumerating remembered resources.

The last step is to skip the remembered resources that are also connected. But this part is not Win32 programming; it's just programming, For each remembered resource, check if the lpLocal­Name is non-null and matches an lpLocal­Name that came out of an enumeration of connected resources.

So let's do it. We start with the header files:

#define UNICODE
#define _UNICODE
#define STRICT
#include <windows.h>
#include <stdio.h> // horrors! Mixing C and C++ I/O!
#include <string>
#include <set>
#include <memory>
#include <winnetwk.h>

Since we are using classes like std::set which throw exceptions, we need to wrap our resources inside RAII classes. Here's one for network resource enumeration:

class CNetEnumerator
{
public:
 CNetEnumerator() = default;
 ~CNetEnumerator() { if (m_hEnum) WNetCloseEnum(m_hEnum); }
 operator HANDLE() { return m_hEnum; }
 HANDLE* operator&() { return &m_hEnum; }
private:
 HANDLE m_hEnum = nullptr;
};

Here is our function to enumerate all network resources. It uses a callback because arghhhhhhhhhhh wishes it were so.

template<typename Callback>
void for_each_network_resource(
    DWORD dwScope,
    DWORD dwType,
    DWORD dwUsage,
    LPNETRESOURCE pnrIn,
    Callback callback)
{
 CNetEnumerator hEnum;
 WNetOpenEnum(dwScope, dwType, dwUsage, pnrIn, &hEnum);

 const DWORD elements = 65536 / sizeof(NETRESOURCE);
 static_assert(elements > 1, "Must have room for data");
 std::unique_ptr<NETRESOURCE> buffer(new NETRESOURCE[elements]);

 DWORD err;
 do {
  DWORD cEntries = INFINITE;
  DWORD cb = elements * sizeof(NETRESOURCE);
  err = WNetEnumResource(hEnum, &cEntries, buffer.get(), &cb);
  if (err == NO_ERROR || err == ERROR_MORE_DATA) {
   for (DWORD i = 0; i < cEntries; i++) {
    callback(&buffer[i]);
   }
  }
 } while (err == ERROR_MORE_DATA);
}

There is a bit of trickery to get the enumeration buffer into a form that C++ likes. We had previously used Local­Alloc, which is guaranteed to return memory suitably aligned for NETRESOURCE. However, we can't do it for new BYTE[], since that returns only byte-aligned data. We solve this problem by explicitly allocating NETRESOURCE objects, but choosing a number so that the result is close to our desired buffer size.¹

We need another helper class so we can create a case-insensitive set.

struct CaseInsensitiveWstring
{
 bool operator()(const std::wstring& a, const std::wstring& b) const {
  return CompareStringOrdinal(a.c_str(), a.length(),
                              b.c_str(), b.length(), TRUE) == CSTR_LESS_THAN;
 }
};

Okay, now we can start doing actual work:

void report(PCWSTR pszLabel, PCWSTR pszValue)
{
 printf("%ls = %ls\n", pszLabel, pszValue ? pszValue : L"(null)");
}

int __cdecl wmain(int, wchar_t **)
{
 std::set<std::wstring, CaseInsensitiveWstring> connected;

 // Collect the local resources which are already connected.
 for_each_network_resource(RESOURCE_CONNECTED,
  RESOURCETYPE_DISK, 0, nullptr, [&](LPNETRESOURCE pnr) {
   if (pnr->lpLocalName != nullptr) {
    connected.emplace(pnr->lpLocalName);
   }
  });

 // Now look for remembered resources that are not connected.
 for_each_network_resource(RESOURCE_REMEMBERED,
  RESOURCETYPE_DISK, 0, nullptr, [&](LPNETRESOURCE pnr) {
   if (pnr->lpLocalName == nullptr ||
       connected.find(pnr->lpLocalName) == connected.end()) {
    report(L"localName", pnr->lpLocalName);
    report(L"remoteName", pnr->lpRemoteName);
    report(L"provider", pnr->lpProvider);
    printf("\n");
   }
  });

 return 0;
}

Not exciting. Mostly consists of boring typing. But hey, that's what programming is like most of the time.

¹ If we were being super-weenies about the buffer size, we could have written

 union EnumBuffer {
  BYTE bytes[65536];
  NETRESOURCE nr;
 };

 std::unique_ptr<EnumBuffer> buffer(new EnumBuffer());
 LPNETRESOURCE pnr = &buffer->nr;
 ...
  DWORD cb = sizeof(EnumBuffer);
Comments (6)
  1. skSdnW says:

    The super-weenies method is ugly (IMHO), requires inventing/naming a type and might waste a couple of bytes but I still appreciate you adding it to your post.

    I'm not a fan of callbacks that lack a caller defined void* parameter but I guess it does not matter when using fancy pants c++11. I'm sure the Windows team already knows this because the few enum functions in Win95 that lacked the parameter got a Ex version later on…

  2. pmbAustin says:

    So, I have a related question on this topic that has always irked me.  I had a "remembered network drive mapping" to a share on a USB drive.  The USB drive died suddenly.  I could find no way through the Windows UI anywhere to make it "forget" that mapping… it insisted that it couldn't forget it unless it could access it first.  Chicken-and-egg problem.  I don't even remember how I managed to get it to finally forget it (I think I managed to get the drive limping along just long enough to plug it in and forget the mapping before it crapped out again).  Very frustrating.  Surely I'm missing something?

  3. James says:

    There is a memory leak in the sample; the unique_ptr to the buffer should be templated on an array of NETRESOURCE structs:

    std::unique_ptr<NETRESOURCE> buffer(new NETRESOURCE[elements]);

    should be:

    std::unique_ptr<NETRESOURCE[]> buffer(new NETRESOURCE[elements]);

  4. NoP says:

    @pmbAustin: have a look at fsmgmt.msc!

  5. Myria says:

    I just reported this via Windows Feedback, but disconnected drives in Windows 10 have a bug.  If a disconnected network connection is associated with a drive letter, and you mount an .iso file, the .iso file might be assigned the same drive letter.  This confuses Explorer – it shows Disconnect instead of Eject on the context menu, and Disconnect does nothing.  Unless you know Windows well enough to use alternate means, you can't unmount the .iso.

  6. Harry Johnston says:

    Woo hoo! – famous for a day.  Well an hour.  Ten minutes at least. :-)

    Plus the code will be useful.  Thanks.  (Though I'm embarrassed to realize that if I'd just thought to look up the documentation for the function you were using in the linked post, I'd have figured it out myself!)

Comments are closed.

Skip to main content