Document Your Domain Groups

The final report will please you, your security department, and your auditors

Downloads
103358.zip

Let me start by saying that this isn't your typical group-listing script. This admin script produces a very thorough single-domain listing of all your Active Directory (AD) groups in a nicely formatted, easy-to-read layout. The report it produces provides you with an excellent point-in-time group history document and supplies a great deal of information to you, your security department, and your auditors.

Here's a list of the script's major features:

  • enumerates all domain groups within a single domain
  • lists and enumerates all nested groups
  • presents information in one Excel document with each group on its own worksheet
  • lists users whose primary group is the group being enumerated (these members don't normally show up in group listings unless you take care to account for this situation as this script does; for example, if you as a domain admin set your primary group to Domain Admins and do a simple group listing of that group, you won't see yourself as a member in that listing)
  • colors nested group names in red and recurring groups in purple
  • avoids possible endless loops should one group contain another group that contains the first group
  • sorts and indents by group
  • provides a domain group summary list with hyperlinks to all groups that have members
  • provides a no members summary list if there are any groups without members, including hyperlinks to the groups
  • colors worksheet tabs in red for easy identification of groups with no members
  • uses blue text for disabled user accounts within groups
  • provides total group counts for both groups with and without members

Script Overview

The script cycles through a collection of groups within a single AD domain and writes each group's members and nested group members into its own specific Excel worksheet. (You can download the script by clicking the Download the Code Here button.) In the end, all the individual domain group worksheets make up the overall domain group listing spreadsheet. In addition to the individual group worksheets, a summary worksheet is created and, if any groups have no members, an additional worksheet is created listing all the groups that have no members.

During the process of evaluating individual groups, disabled accounts are highlighted in blue; any nested groups are indented and highlighted in red and members of those nested groups are also indented and listed under their specific parent group.

If a specific nested group happens to occur more than once in any specific listing, it's not enumerated again but rather highlighted in purple; you'll find that the fully enumerated group already exists elsewhere within that group listing. A summary worksheet is created at the end of the process, providing group total information and easy-to-use hyperlinks to all your groups for quick and easy analysis.

If your domain has any groups that do not contain members, a separate groups with no members worksheet is created, providing the names and hyperlinks to those groups that have no members. The resulting spreadsheet with its color coding and nested group indentation scheme provides a very thorough and easy-to-comprehend picture of your domain's group infrastructure.

How It Works

Aside from setting up some variables, constants, and an instance of Excel, the first process the script undertakes is to create a dictionary that contains a list of all disabled accounts. This is done by querying AD. The dictionary is used at a couple of key points within the script while enumerating group member users to determine if the user account is disabled or not. You'll see this later on in the script. If an account is disabled, that userid is formatted in the Excel output with a blue font, making it easy to spot within the group listings.

Next, a collection of all domain groups is gathered using the simple AD query shown in Listing 1. Stepping though the group collection constitutes the main loop of the script, where all groups are ultimately enumerated. Within this main loop a call is made to the GetGroupMembers subroutine, which is really the workhorse of this whole process. It's within the GetGroupMembers subroutine that each group's membership is individually evaluated and written to the Excel report.

Inside the Subroutine

Once a specific group object is passed to the GetGroupMembers subroutine the process of enumerating the members of that group begins.

The first section of code shown in Listing 2 performs an often-overlooked process: checking for user accounts that have a Primary Group assignment that belongs to the group being evaluated. As strange as it may sound, user accounts whose Primary Group is the group being evaluated will not be part of the collection returned with an AD group query. To account for this condition, you must approach it from another angle, which is to look for accounts whose PrimaryGroupToken is that of the Group you're evaluating.

The chunk of code in Listing 2 ensures that you capture these members. You'll see that I also trigger a flag within this section of code to indicate that this group "Has Members" whenever a user or group exists within the group being evaluated. By triggering I mean I change the value of the HasMember variable from False to True. If this flag isn't triggered while evaluating the group, I can capture the group name to a dictionary called NoMembersList, which is used near the end of the program to list all the groups with no members. If the flag is triggered (set to True), the group is stored to a dictionary called YesMembersList, which is used to list all the groups that do have members. You'll also notice that within this section of code I check to see if the user item is found within the DisabledAccounts dictionary and highlight the output in blue if the userid exists within the dictionary.

After checking for the Primary Group the script starts to enumerate the group's members, as seen in the section of code in Listing 3. The process here is mainly concerned with whether the member is a regular account or whether it's a group member. If it's a regular account, the member goes through the same process it did when checking the Primary Group members: The group member is checked against the disabled accounts dictionary and colored if it is a disabled account and the HasMembers flag is set to True.

If the member is another group, the group is evaluated against the group dictionary. If the group doesn't exist, it's added to the group dictionary and a recursive call is made to the GetGroupMembers subroutine to gather the members of that particular nested group.

If the nested group exists in the group dictionary, the recursion process is not undertaken; the group name is simply written to the worksheet, colored purple, and the script moves on to the next group member. Keeping track of groups in the group dictionary in this fashion prevents the possibility of the code going into an endless loop if a group happens to contain another group that contains it as a member.

Without going into a complicated explanation involving the indenting process let me summarize what I do within the code to make sure the indentation stays accurate and the group members and nested groups are indented and sorted properly.

Typically, formatting wouldn't be a problem for groups that just had users as members; you could easily sort on column A. But it's a different story when nested groups are involved. If a nested group is encountered when enumerating a group, I write the nested group under its parent group and then increase the column number by one so that the nested group's members appear indented by one column to the right. I also keep track of the group names whenever I enter a recursive call that sends the script off to enumerate a nested group. So when the recursive call is finished and the current group name is no longer the same as the one stored, I decrease the indent by subtracting one from the current column number, thus putting me back into the correct column of the previous group.

Even though the indentations might seem to be sufficient for having all the group information sorted properly, it is not sufficient for groups that contain other groups. Under normal circumstances, when a group with users and nested groups is enumerated you'll see that your first item in the collection might be a user, the second might be a group, and the third might be another user, and so on. Therefore your users would not necessarily all fall under each other but would rather be separated by groups in between them. Technically, the listing would still be correct; however, visually this could make analyzing the data a bit confusing.

The piece to this process that helps keep things sorted properly involves writing the current overall nested group hierarchy level of each group member to the 256th column (which has column heading IV). To help understand this, a picture is truly worth a thousand words. So let's take a look at Figure 1 and Figure 2. Here you'll see an example of a fairly complex nested group listing. In Figure 1 you'll see that the group being processed is named RTC Local User Administrators. It contains one user—Mark Hassall—and two nested groups—RTCDomainUserAdmins and VPNUsers (note the red font, indicating they are groups). These three members fall directly under RTC Local User Administrators in column A. You'll see that the RTCDomainUserAdmins group has only one member—Michael Holms—and that it is indented to indicate that it belongs to RTCDomainUserAdmins group. The VPNUsers group contains one user—Craig Playstead—and two nested groups—Domain Admins and RTCDomainUserAdmins—and they are indented one level to indicate that they belong to the VPNUsers group. The Domain Admins group members are indented as well to show they belong to the Domain Admins group. And finally, you'll see that the VPNUsers member RTCDomainUserAdmins is colored with a purple font and also has a notation indicating it is a recurring group and, therefore, you will not see indented members listed under it. You can, however, find the RTCDomainUserAdmins group in the listing and ascertain who the members of that group are.

Sorting It Out

To ensure that I all my group listings would be sorted properly, I needed a single column to sort on that was structured in a fashion that would guarantee everything fell into its proper place.

As you can see in Figure 2, by simply keeping track of the complete path of each group member's hierarchical structure and writing that to column 256 I could sort the worksheet on column IV and everything would fall right into place. The asterisks are used as a delimiter to mark the change in the hierarchy.

Wrapping It Up

The process of evaluating my groups continues until every group in the domain is evaluated. The process then exits the main loop and the script begins to wrap things up by providing group summary information and hyperlink listings to all groups with and without members. Accessing any particular group is simply a matter of clicking on the group name hyperlink. Note that if all your groups do contain members there will not be any references to "No Members."

I think that you'll find that having this documented within Excel has its advantages. Everything is contained within one file and all the groups are contained within their own individual worksheet. And if you need to know which groups a specific user belongs to you can easily use the "Find All" feature in Excel and locate every occurrence of that user. You could also use the "Find All" feature to locate everywhere a disable account appears or where you had recurring nested groups, which could ultimately help you find inconsistencies that might exist within some of your group structures.

This script is a very useful admin utility that will provide invaluable reports as well as a permanent record of your group structures for any given point in time. It will also save you a lot of time and serve you and your auditors well if you are asked to provide detailed group information during the hectic audit season.

Discuss this Article 13

philobm
on Jan 25, 2010
This might be very useful if the link worked
roaldly
on Jan 31, 2010
Very good article. Now waiting for the script download to work;-)
RealVirtuality
on Feb 2, 2010
Drat! I was looking forward to trying this out...come on guys get the link working please!
donriddell
on Mar 2, 2010
Cannot find Dowload the Code Here button!
WallaceTechUK
on Oct 15, 2010
Fantastic script. Is there anyway to only show DL nested groups and not security groups?
jturnervbs
on Jan 28, 2010
I've let the editor know about the download link problem.
Andres P
on Jan 25, 2010
Code dowload link broken ?
AnneG_editor
on Feb 3, 2010
The link appears to be working now. Let me know if you're still having problems with it. Thanks for your patience and reporting the error. -Anne Grubb, anne.grubb@penton.com
Tallarico
on Feb 15, 2010
First of all thanks for a great article. This type of thing is handy in dealing with compliance auditors. Second thanks for posting a script that's not PowerShell. I'm finding people are adapting PowerShell to things where it doesn't really fit just for the sake of being trendy. One piece of advice: Good AD administrators will have long, descriptive group names. I've had several issues with a similar .vbs bombing out over group names that exceeded the Excel's character limitations. If you're looking for a work-around to this there's one here: http://www.ryanboyer.net/group-membership/. You'll also find solutions for selectively auditing especially sensitive groups rather than every group across your (potentially) huge enterprise. Again - thanks for a terrific article. I keep subscribing to your magazine and interest groups because of useful articles like this one. -Tallarico Also - for
AnneG_editor
on Mar 2, 2010
The problems in the article have been fixed. Thanks for letting us know about them, and thanks for reading! -Anne Grubb, web & developer editor, Windows IT Pro and DevConnections
shapwolf@gmail.com
on Feb 11, 2010
Excel ran out of resources creating worksheets (Excel 2007; 4GB Win7); our AD contains 10's of thousands of users; several forests, hundreds of OUs and thousands of groups. How can I modify the script to start with my OU and only enumerate groups within it? Excel can probably handle that much. I really like the article and concept and can really use the output if I can get it for our situation. thanks.
WallaceTechUK
on Oct 15, 2010
Also , it would be great to link this to Visio and have some kind of visual diagram
jteberle
on Jan 31, 2010
Might be great...Please fix the download link!!

Please or Register to post comments.

IT/Dev Connections

Las Vegas
September 30th - October 4th

Paul ThurottYou'll have the opportunity to experience:
• The Microsoft
Technology Roadmap
• Office 365 Implementation
• Hyper-V Optimizing
• Windows 8 Deployment
and much more!

Come See Paul Thurrott & Rod Trent in Person!

Early Registration Now Open

Upcoming Training

Mastering System Center 2012

During over 6 hours of training you can join John Savill from your computer as he will walk you through the key components and capabilities of System Center 2012, what’s involved in using the components, and the benefit they can bring to your environment.

Register Now

Current Issue

May 2013 - The NameTranslate object is useful when you need to translate Active Directory object names between different formats, but it's awkward to use from PowerShell. Here's a PowerShell script that eliminates the awkwardness.

CURRENT ISSUE / ARCHIVE / SUBSCRIBE

Windows Forums

Get answers to questions, share tips, and engage with the Windows Community in our Forums.