Subscribe to Windows IT Pro
February 22, 2012 09:00 AM

4 Challenges of Auditing AD Users and Groups

This Windows PowerShell script makes fast work of a tricky task
Windows IT Pro
InstantDoc ID #141463
Rating: (0)
Downloads
141463.zip

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
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.

 

Related Content:

ARTICLE TOOLS

Comments
  • Lorenzo0o0
    2 months ago
    Mar 08, 2012

    This script is helpful unless you need real solid reporting and filtering capabilities, which is necessary to appease most auditors. If you only need AD auditing for peace of mind or internal security, this script can be useful. In my opinion, its probably not enough to appease auditors who demand reporting and audit trail offered by tools like NetWrix AD Change Reporter (freeware) or Quest ChangeAuditor.

  • Jamie1
    2 months ago
    Mar 02, 2012

    Bill, this was an interesting article. I have a similar script that pulls group membership for all users in the domain and writes the entries to a csv. It also dates the csv file and appends the date to each entry to be used as a reference when restoring accidentally deleted accounts.

    ###start###
    connect-QADService -service 'domain.contoso.com'

    $objGroup = get-qadgroup -sizelimit 0 -empty $false -grouptype 'security'

    $result = $objGroup | foreach-object {

    $group=$_.name
    get-qadgroupmember $_ -ea SilentlyContinue -sizelimit 0 | select-object SamAccountName,displayname,@{n="GroupName";e={$group}},@{n="Date";e={$date}}

    }
    $filename = "allDomaingroupmembers{0:yyyyMMdd}.csv" -f (Get-Date)
    $result | export-csv -path D:\\scripts\\Allgroupmembershipbackup\\$filename
    ###end###

    This does take a couple hours to run so any feedback would be much appreciated.

You must log on before posting a comment.

Are you a new visitor? Register Here

advertisement

advertisement

Windows is a trademark of the Microsoft group of companies. Windows IT Pro is used by Penton Media Inc. under license from owner.