Monday, January 2, 2012

How to list all users / groups of domain programmatically (Windows)

 To list all users of domain in command line, use command "net users /domain". The command  "net users" without /domain switch returns all users of the local workstation.

 To list all groups of domain in command line, use command "net groups /domain". Again, the same command  without /domain switch returns all groups of the local workstation.

How to get the same reports programmatically?

Use the function NetQueryDisplayInformation in both cases. MSDN provides example code of how to use it. Depending on parameter the function returns array of NET_DISPLAY_USER or NET_DISPLAY_GROUP structures. Pass NULL for the first parameter, this will list all users / groups from local computer.

How to deal with the domain? It seems that we may use the function NetServerEnum with SV_TYPE_DOMAIN_CTRL as servertype parameter (see MSDN for the sample). Note that you need to run the program as administrator. This returns domain controller, which can be used as the first parameter for NetQueryDisplayInformation.

Don't be surprised: depending on the configuration of your network, domain controller name may be equal or not equal to the domain name.

But wait! The command "net users /domain" does not require to be run under administrator account, while our program does. So it seems there could be a better solution.

For this case you may find the DsEnumerateDomainTrusts function more useful. Pass NULL as the first parameter and DS_DOMAIN_PRIMARY as a flag. This function exists since Windows 2000, so in case of old good Windows NT you may still choose NetServerEnum... but I don't find this information useful nowadays.

Now lets put it all together. NetQueryDisplayInformation works with Unicode strings only. DsEnumerateDomainTrusts exists in both versions. To make the things easier I assume that Unicode is set. So the sample code looks like:

#include <Dsgetdc.h>
#include <lm.h>

DWORD GetAllUsersOfPrimaryDomain()
{
  PNET_DISPLAY_USER pBuff, p;
  DWORD res, dwRec, i = 0;
  ULONG domainsCount;
  PDS_DOMAIN_TRUSTS domain;

  res = DsEnumerateDomainTrusts(NULL, DS_DOMAIN_PRIMARY, &domain, &domainsCount);

  if (res != ERROR_SUCCESS)
  {
    printf("DsEnumerateDomainTrusts failed with error %ld\n", res);
    return res;
  }

  printf("DsEnumerateDomainTrusts returns %ld domain, this test will print users for the first one (%S) ",
domainsCount, domain->DnsDomainName);

  do // begin do
  {
      //
      // Call the NetQueryDisplayInformation function;
      //   specify information level 3 (group account information).
      //
      res = NetQueryDisplayInformation(domain->DnsDomainName, 1, i, 1000, MAX_PREFERRED_LENGTH, &dwRec, (PVOID*)&pBuff);
      //
      // If the call succeeds,
      //
      if((res==ERROR_SUCCESS) || (res==ERROR_MORE_DATA))
      {
         p = pBuff;
         for(;dwRec>0;dwRec--)
         {
            //
            // Print the retrieved group information.
            //
            printf("Name:      %S\n"
                  "Comment:   %S\n"
                  "Full name:  %u\n"
                  "--------------------------------\n",
                  p->usri1_name,
                  p->usri1_comment,
                  p->usri1_full_name);
            //
            // If there is more data, set the index.
            //
            i = p->usri1_next_index;
            p++;
         }
         //
         // Free the allocated memory.
         //
         NetApiBufferFree(pBuff);
      }
      else
         printf("Error: %u\n", res);
   //
   // Continue while there is more data.
   //
  } while (res==ERROR_MORE_DATA); // end do

    NetApiBufferFree(domain);

  return ERROR_SUCCESS;
}



(NetQueryDisplayInformation part of this sample was copied from MSDN).

One more question is left. Suppose you have a user with fullname "DOMAINNAME\USERNAME" and you want to list all users of DOMAINNAME. As I mentioned before domain controller name may be equal or not equal to domain name. So if you pass DOMAINNAME as an argument into NetQueryDisplayInformation you may get error. How to get domain controller of DOMAINNAME?

In this case use the function NetGetAnyDCName like in the following sample:

LPWSTR  controller = NULL;
NetGetAnyDCName(null, L“DOMAINNAME”, &controller);
/* ...now pass controller to  NetQueryDisplayInformation as above... */
NetApiBufferFree(domain_controller);

No comments:

Post a Comment