How do I enumerate drives the same way that the NET USE command does?


If you use the Remote Desktop Connection client to connect to another computer, you have the option of making your local drives available to the remote computer.

A customer wanted to know how to enumerate all the drives on the local machine. The were able to get the volumes mapped to drive letters, but they also wanted to get the redirected drives injected by Terminal Services. (Mind you, these aren't volumes that are assigned drive letters, so it's not clear why they are interested in them, but whatever.)

With the NET USE command, they see the Terminal Services volumes in Explorer, and they can be browsed via \\tsclient\d:

Status       Local     Remote                    Network

-------------------------------------------------------------------------------
                       \\TSCLIENT\D              Microsoft Terminal Services
The command completed successfully.

The customer wanted to enumerate these Terminal Services client volumes. "How does the NET USE command enumerate these volumes?"

Let's write that program. Remember, Little Programs do little to no error checking.

#define UNICODE
#define _UNICODE
#include <windows.h>
#include <winnetwk.h>
#include <stdio.h>

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

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

 DWORD cbBuffer = 65536;
 void *buffer = LocalAlloc(LMEM_FIXED, cbBuffer);
 LPNETRESOURCE pnr = (LPNETRESOURCE)buffer;

 DWORD err;
 do {
  DWORD cEntries = INFINITE;
  DWORD cb = cbBuffer;
  err = WNetEnumResource(hEnum, &cEntries, buffer, &cb);
  if (err == NO_ERROR || err == ERROR_MORE_DATA) {
   for (DWORD i = 0; i < cEntries; i++) {
    report(L"localName", pnr[i].lpLocalName);
    report(L"remoteName", pnr[i].lpRemoteName);
    report(L"provider", pnr[i].lpProvider);
    printf("\n");
   }
  }
 } while (err == ERROR_MORE_DATA);

 LocalFree(buffer);
 WNetCloseEnum(hEnum);
 return 0;
}

We open an enumeration for connected disks and then start enumerating out of it. The usage pattern for WNet­Enum­Resources is kind of messy, with a bunch of in/out parameters that need to get reset each time. Each time, we say "Enumerate as much as you can into this 64KB buffer" and then print what we got. If we were told, "There's still more," then we go back and ask for more.

That's all. Nothing particularly fancy.

Comments (14)
  1. acq says:

    Anybody knows how I can disable on the computer to which the client is being connected to see any smartcards, ports, drives or PnP devices from the clients?

  2. acq says:

    The answer to my previous question:

    technet.microsoft.com/…/cc770631.aspx

  3. Joshua says:

    Gee looks like readdir. See man 2 readdir. Most of the time you expect to see this handled for you in system libraries but oh well.

    [Most enumeration problems look like readdir. Not because readdir is primordial, but because most enumeration problems look the same. -Raymond]
  4. Joshua says:

    FindFirstFile/FindNextFile/FindClose do not require 2 loops, nor does the library call as seen in man 3 readdir. Shrug.

    [Wait, you said man 2 readdir, and man 2 readdir does require two loops. (Since it can return multiple dirents.) -Raymond]
  5. Harald van Dijk says:

    > [Wait, you said man 2 readdir, and man 2 readdir does require two loops. (Since it can return multiple dirents.) -Raymond]

    The readdir(2) page reads "The argument count is ignored; at most one old_linux_dirent structure is read." The API appears as if it has been different in the past. If it was different in the past, then the API suggests that an inner loop could have been avoided, even for old kernels, by setting count to 1, which would ensure that at most one entry got returned.

    The same applies to WNetEnumResource: the inner loop should be avoidable by setting lpcCount to 1. That would probably be a subobtimal use of the function, but if someone (such as Joshua) is more comfortable calling it like that, there does not appear to be anything stopping such use.

  6. Joshua says:

    > The API appears as if it has been different in the past.

    And so it was. Wow I didn't notice it had been reduced to assuming 1 now. Been so long since I called it directly.

  7. gdalsnes says:

    I wish all enums used callbacks, like EnumWindows.

  8. Rats.  I was hoping to find out how to get a list of remembered (but not currently connected) drive mappings.

  9. Joker_vD says:

    That's kind of weird to see a function that takes an array of NETRESOURCE to fill as LPVOID instead of LPNETRESOURCE, and the size is counted in bytes, not in NETRESOURCEs. It's not that NETRESOURCE has variable length, so why is this the way it is?

  10. Agares says:

    Why the hell would ERROR_MORE_DATA status have word "error" in it's name?

  11. Harald van Dijk says:

    > Why the hell would ERROR_MORE_DATA status have word "error" in it's name?

    Because ERROR_MORE_DATA indicates that the function was unable to fulfill your request. You requested all items. You didn't get all items. The reason why you didn't get all items is because there is more data that could not be written into the provided buffer.

  12. Neil says:

    In the extreme case you can ask how big the buffer should be and the provider will say "dunno… you need at least N to fit in the array itself" so you ask it "will a buffer of size N do" and it says "no, you need this much for all the strings too".

  13. Graham says:

    @Joker_vD

    The docs say that the buffer stores not only the NETRESOURCE structures, but also the strings pointed to by those structures, so the size of the buffer is greater than simply sizeof(NETRESOURCE) * cEntries. I would guess it's done that way to avoid needing a separate "free the strings in the returned NETRESOURCE" structures.

    The function could simply allocate the strings, keep track of the allocations and return an array of just NETRESOURCEs as you suggest, and then free all the strings during the call to WNetCloseEnum, but I suspect this function dates from the Windows 3.1 times when memory was really precious, and keeping a copy of everything the application enumerated (if you're enumerating all the computers in a domain, it can be a *lot* of data) was a bad idea.

  14. Marc K says:

    > Little Programs do little to no error checking.

    I've found "Enterprise" programs tend to be the same way as well.

Comments are closed.

Skip to main content