Today's Little Program is a command line version of the Shared Folders MMC snap-in. Why? Because it illustrates the usage pattern for the Net­Xxx family of functions. (It's also a clone of the networking portion of the openfiles tool.)

The Net­Xxx family of functions generally work like this:

  • You pass in some parameters that describe what you want. Server name, that sort of thing.
  • You pass a "level" parameter that describes what information you want.
  • The function allocates memory to hold the results you requested, and it returns a pointer to that memory through a bufptr parameter.
  • If the function returns an array, then
    • You can tell the function the maximum number of results you want.
    • The function tells you how much information it returned.
    • If the function did not retrieve all the results (because it exceeded your maximum), it tells you how to get the rest of them.
  • When you are finished, you free the memory with Net­Api­Buffer­Free.

We'll start with the non-array case, since that is much simpler. Suppose you want to get the level 123 information for a Thing.

THING_INFO_123 *pinfo123;
if (NetThingGetInfo(pszThing,
                    123, (LPBYTE*)&pinfo123) == NERR_Success)
{
    DoSomethingWith(pinfo123);
    NetApiBufferFree(pinfo123);
}

You call the function, passing the desired information level and a pointer to the variable you want to receive the results. You then use the results, and then free them. Let's try it with a simple function to get information about a user.

#define UNICODE
#define _UNICODE
#define STRICT
#include <windows.h>
#include <lm.h>
#include <stdio.h>

void PrintProperty(PCWSTR pszProperty, PCWSTR pszValue)
{
 wprintf(L"%ls: %ls\n", pszProperty,
                        pszValue ? pszValue : L"<none>");
}

int __cdecl wmain(int argc, wchar_t **argv)
{
 USER_INFO_10 *pinfo10;
 if (NetUserGetInfo(NULL, L"Administrator", 10,
                    (LPBYTE*)&pinfo10) == NERR_Success) {
  PrintProperty(L"Name", pinfo10->usri10_name);
  PrintProperty(L"Comment", pinfo10->usri10_comment);
  PrintProperty(L"User comment", pinfo10->usri10_usr_comment);
  PrintProperty(L"Full name", pinfo10->usri10_full_name);
  NetApiBufferFree(pinfo10);
 }
 return 0;
}

The trickier case is the functions that return arrays of data. In that case, you need to call the functions in a loop, similar to Find­Next­File, in order to read all the data. But unlike Find­Next­File, the functions return chunks of data rather than just one entry at a time.

The general pattern goes like this:

THING_INFO_123 *pinfo123;
NET_API_STATUS status;
DWORD_PTR resumeHandle = 0;
do {
 DWORD actual, estimatedTotal;
 status = NetThingEnum(pszThing, 123,
                       (LPBYTE*)&pinfo123,
                       MAX_PREFERRED_LENGTH,
                       &actual,
                       &estimatedTotal,
                       &resumeHandle);
 if (status == NERR_Success ||
     status == ERROR_MORE_DATA) {
  for (DWORD i = 0; i < actual; i++) {
   DoSomethingWith(&pinfo123[i]);
  }
  NetApiBufferFree(pinfo123);
 }
} while (status == ERROR_MORE_DATA);

The general pattern is to start by calling the data retrieval function. If the function returns with NERR_Success, then it means that it was able to get all the information you requested. If the function returns with ERROR_MORE_DATA, then it means that it was able to get some of the information you requested. In either of those two cases, it returns the actual number of items retrieved in the actual parameter, which you use to read the values out of the results. (It also returns an estimate of the total number of items remaining in the estimated­Total variable, but very few people use that.)

If the return value was ERROR_MORE_DATA, then you go back and call the function again to get the next batch of results.

The way the functions can tell whether you're starting a new operation or continuing an old one is via the resume­Handle parameter, which must be a pointer to a DWORD_PTR variable which the function updates. On the first call, set the DWORD_PTR to zero. If the function returns partial results, then it puts an opaque value into the resume­Handle so it can remember where it needs to continue. (By comparison, the Find­First­File passes the resume handle as its return value.)

Note that there is no equivalent to Find­Close when you are finished with the function. If you don't want to retrieve all the results, you just abandon the handle.

int __cdecl wmain(int argc, wchar_t **argv)
{
 FILE_INFO_3 *pinfo3;
 NET_API_STATUS status;
 DWORD_PTR resumeHandle = 0;
 do {
  DWORD actual, estimatedTotal;
  status = NetFileEnum(NULL, NULL, NULL, 3,
                       (LPBYTE*)&pinfo3,
                       MAX_PREFERRED_LENGTH,
                       &actual,
                       &estimatedTotal,
                       &resumeHandle);
  if (status == NERR_Success ||
      status == ERROR_MORE_DATA) {
   for (DWORD i = 0; i < actual; i++) {
    PrintProperty(L"Path", pinfo3[i].fi3_pathname);
    PrintProperty(L"User", pinfo3[i].fi3_username);
    if (pinfo3[i].fi3_permissions & PERM_FILE_READ) {
     PrintProperty(L"Access", L"READ");
    }
    if (pinfo3[i].fi3_permissions & PERM_FILE_WRITE) {
     PrintProperty(L"Access", L"WRITE");
    }
    if (pinfo3[i].fi3_permissions & PERM_FILE_CREATE) {
     PrintProperty(L"Access", L"CREATE");
    }
   }
   NetApiBufferFree(pinfo3);
  }
 } while (status == ERROR_MORE_DATA);
 return 0;
}

I've been ignoring the parameter known as prefmaxlen because you pretty much always pass MAX_PREFERRED_LENGTH. The parameter lets you limit how much information is returned at a time, but you nearly always want as much as possible (which is why you nearly always pass MAX_PREFERRED_LENGTH). If, for some reason, you want to retrieve only a little bit at a time, you can pass a smaller value as the prefmaxlen. Note that prefmaxlen is in bytes, not elements, and the size in bytes needs to include the auxiliary data (like the strings), not just the structures. If you pass a custom prefmaxlen, then you also have to be prepared to handle the NERR_Buf­Too­Small error code, which means "The value you passed in prefmaxlen wasn't big enough to hold even one item. You'll have to try again with a bigger buffer size." If you're advanced enough to use a custom buffer size, then you're advanced enough to figure out how to tweak the algorithm to handle it properly.

Note that I have no special knowledge of the Net­Xxxx family of functions. I figured this out by reading the documentation.