In the Windows NT 4.0 world, group memberships were fairly simple to enumerate because only users or global groups could be members of local groups. Thus, the deepest possible nesting of permissions was having users in a global group and having that global group in a local group. This shallow nesting made it pretty simple to see which users had permissions to a particular resource.
Enter Windows 2000. In addition to letting you nest a global group in a local group, Win2K lets you nest local groups in other local groups. This ability makes identifying which users have permission to a resource much tougher. Although the Microsoft Management Console (MMC) Active Directory Users and Computers snap-in lets you drill down through the groups and visually see which users have permission, you can't see all this information in one display or output the results to a report.
Traditionally, the best command-shell utilities to use for enumerating group memberships have been the Local and Global utilities. Although these utilities do a great job of listing the members of local and global groups, respectively, they don't list the members of any groups that are nested in a local or global group. For quite some time, I had been struggling to write a script that enumerated the members of both first-level and nested groups. After some experimenting, I finally discovered that, by focusing on failures, I could use the Local and Global utilities to drill down through a group and enumerate the members of first-level and nested groups. After further experimenting, I was able to output this information to a comma-separated value (CSV) or tab-separated value (TSV) file for easy viewing in Microsoft Excel.
Script Counts on Command's Failures
By default, scriptwriters typically try to successfully execute commands and successfully capture those commands' output. Failure output is usually less important unless it affects a script's operation. However, in the EnumGroups.bat script, I depend on the Local and Global commands' failures to enumerate the members of first-level and nested groups.
Let's look at some sample failure output from the Local and Global commands. If you run the Local command against a known user account with a command such as
Local Fred domain1
you'll receive the failure message GetUsers - Unknown Error: 1376. If you run the Local command against a known global group with a command such as
Local AcctGG domain1
you'll receive the failure message GetUsers - Unknown Error: 1376. Notice that the failure messages are the same for both wrong inputs.
Now, let's run the Global command against a known user account with the command
Global Fred domain1
In this case, the failure message is 'Fred' group not found. If you run the Global command against a known local group with a command such as
Global SalesLG domain1
the failure message is 'SalesLG' group not found. Notice again that the failure messages are similar.
As an administrator, you can look at a named object and tell whether it's a user account or a group account because of your familiarity with your user and group naming conventions. However, in command-shell scripting, you don't have an easy way to determine whether an object is a user account, local group account, or global group account if your naming convention is domainname\username and domainname\groupname. So, you need to do some creative scripting.