I've lost track of the number of times that someone has asked in an online forum: "Does someone know how to list all users and their group memberships
in an Active Directory domain?" Third-party auditors or security consultants also ask this question when trying to assess an organization's Active
Directory (AD) environment. Because the question is so common, I decided to write a Windows PowerShell script to address the task.
I initially thought that writing such a script would be simple, but four challenges caused the task to take a little longer than I first expected. I'll
describe these issues, but first I need to explain a bit about the basics of using Microsoft .NET in PowerShell to search AD. (I won't discuss
PowerShell's ActiveDirectory module here, as I like to maintain backward compatibility with earlier OS versions and domains.)
Using .NET to Search AD
When using .NET to search AD, you can use the type accelerator in PowerShell to search for objects. (A type accelerator is a shortcut
name for a .NET class.) For example, enter the following commands at a PowerShell prompt to output a list of all users in the current domain:
PS C:\> $searcher = "(&(objectCategory=user)(objectClass=user))"
PS C:\> $searcher.FindAll()
[ ADSISearcher] is a type accelerator for the .NET System.DirectoryServices.DirectorySearcher object. The string following this type accelerator sets
the object's SearchFilter property to find all user objects, and the FindAll method starts the search. The output is a list of
System.DirectoryServices.SearchResult objects.
So far, so good. Next, we want to determine a user's group memberships. To do so, we can use the Properties collection from a SearchResult object and
retrieve the object's memberof attribute. Using the $searcher variable from the previous example, we can use the FindOne method (instead of FindAll) to
retrieve one result and output the user's group memberships:
PS C:\> $result = $searcher.FindOne()
PS C:\> $result.Properties["memberof"] | sort-object
The first command finds the first user that matches the search filter, and the second command outputs a list of the groups of which that user is a
member.
However, if you look carefully at this list, you'll notice that something is missing: The user's primary group is not included in the memberof
attribute. I wanted the complete list of groups (including the primary group) -- which leads us to the first challenge.
Challenge #1: Finding a User's Primary Group
There is a workaround for the exclusion of the primary group from the memberof attribute. This workaround is described in the Microsoft article "How to Use Native ADSI Components to Find the Primary Group." The workaround uses these steps:
1. Connect to the user object by using the WinNT provider (instead of the LDAP provider).
2. Retrieve the user's primaryGroupID attribute.
3. Retrieve the names of the user's groups by using the WinNT provider, which includes the primary group.
4. Search AD for these groups by using their sAMAccountName attributes.
5. Find the group in which the primaryGroupToken attribute matches the user's primaryGroupID.
The problem with this workaround is that it requires the script to use the WinNT provider to connect to the user object. That is, I needed the script
to translate a user's distinguished name (DN; e.g., CN=Ken Myer,OU=Marketing,DC=fabrikam,DC=com) into a format that the WinNT provider could use (e.g.,
WinNT://FABRIKAM/kenmyer,User).
Challenge #2: Translating Between Naming Formats
The NameTranslate object is a COM (ActiveX) object that implements the IADsNameTranslate interface, which translates the names of AD objects into
alternate formats. You can use the NameTranslate object by creating the object and then calling its Init method to initialize it. For example, Listing
1 shows VBScript code that creates and initializes the NameTranslate object.
However, the NameTranslate object does not work as expected in PowerShell, as Figure 1 shows. The problem is that the NameTranslate object does not
have a type library, which .NET (and thus PowerShell) uses to provide easier access to COM objects. Fortunately, there is a workaround for this problem
as well: The .NET InvokeMember method allows PowerShell to get or set a property or execute a method from a COM object that's missing a type library.
Listing 2 shows the PowerShell equivalent of the VBScript code in Listing 1.

Figure 1: Unexpected behavior of the NameTranslate object in PowerShell
I wanted the script to handle one other name-related problem. The memberof attribute for an AD user contains a list of DNs of which a user is a member,
but I wanted the samaccountname attribute for each group instead. (This is called the Pre-Windows 2000 name in the Active Directory Users and Computers
GUI.) The script uses the NameTranslate object to handle this issue also.