In the Active Directory PowerShell module, you have two commands to your disposal that help display group membership. Those are Get-ADGroup and Get-ADGroupMember. The first command contains property Members, which gives you DistinguishedName of all members, and Get-ADGroupMember can provide you either direct members or with Recursive switch all members recursively (skipping groups). Till a few weeks ago, I was a happy user of those commands until I noticed two things. Member property for Get-ADGroup sometimes misses elements for whatever reason.
Get-ADGroupMember doesn't like some groups for some reason.
Get-ADGroupMember : An operations error occurred
At line:1 char:1
+ Get-ADGroupMember -Identity AD ADmins'
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (AD ADmins:ADGroup) [Get-ADGroupMember], ADException
+ FullyQualifiedErrorId : ActiveDirectoryServer:8224,Microsoft.ActiveDirectory.Management.Commands.GetADGroupMember
Another problem is you either get a top-level view or all members in that group, which for the most part, is all you need. But sometimes you need a bit more than that, and that's what you're going to get today.
I wrote this little function called Get-WinADGroupMember. When you use it with a single parameter group it is basically a replacement for Get-ADGroupMember -Recursive.
Get-WinADGroupMember -Group 'Domain Admins' | ft
As you notice, it shows Nesting and whether the account is in another forest, and which group is the user in. It's important to understand that Nesting may be a bit misleading. This is because if the user is added directly to a group, and then somewhere else is nested, we would remove the duplicate objects. It's the entirely possible one user is nested on the 2nd level, at the same time being nested on the 5th and 7th level, but we don't show that here. This is why we have All switch.
Get-WinADGroupMember -Group 'Domain Admins' -All | ft
Of course, there are more columns that aren't shown without a wildcard, but they hardly fit the screen anyways.
As you notice this time you get multiple new options. You see groups and nested groups and their members. This is where it's possible to see that one or more users are in the group multiple times on different levels of nesting. You also get to see the Parent Group of the nested object along with some stats such as Direct Members, Direct Groups, Indirect Members, and Total Members. But that's not all. I've also added another switch AddSelf which adds the group we're targeting with its summary stats for a quick look.
Get-WinADGroupMember -Group 'Domain Admins' -all -AddSelf | ft *
So this little function does have a few more tricks than your default functions, but the title promises something else right?
While working with nested groups, it's often impossible to quickly tell what's going on. Who is a member of which group, how many members there are? The bigger the AD, the higher probability of being lost. Also, telling someone that there are 300 group members doesn't have the same impact as showing it to them on a diagram, right? So le's see what Show-ADGroupMember can do, shall we?
# It also works with Show-ADGroupMember for easier muscle memory Show-WinADGroupMember -GroupName 'Domain Admins' -FilePath $PSScriptRoot\Reports\GroupMembership.html
By default, when given a group name, it will show one top tab with the group name, and three-second level tabs. Table with summary (basically a copy of what you saw with Get-WinADGroupMember). A basic diagram that doesn't care for nesting where it cares for connections between members. If there are duplicates, those should be displayed only once; however, their relationships will multiply. The third diagram is a hierarchical diagram that shows nested levels and how they are connected. Each graph can be saved to disk with right-click as an image. You may need a bigger monitor to fit it all, or you can zoom-in or zoom-out as required, but more extensive diagrams will be problematic.
Show-WinADGroupMember (alias: Show-ADGroupMember) can take multiple groups at the same time. What it does it creates a new top-level tab for each group given, and the rest stays the same.
Show-ADGroupMember -GroupName 'Domain Admins', 'Enterprise Admins', 'Administrators', 'Account Operators', 'Backup Operators' -FilePath $PSScriptRoot\Reports\GroupMembership.html
All groups without members are skipped. This means you can query multiple groups at the same time and get pretty diagrams for easy viewing. Unfortunately, there's one drawback to this – it works fine for small groups, but when you deal with a domain of 80000 users in size, things get complicated. When you try to display ten groups, each having 400 nested members, which means there are at least 8000 objects across 20 diagrams to be drawn, and that makes it a bit unusable. If you are going to work with large groups, I suggest doing it, one group at a time, or use additional switches HideUsers, HideComputers, HideAppliesTo. What those switches do is hide respective objects from the diagram, one or both, leaving only Groups and their connections.
Show-ADGroupMember -GroupName 'Domain Admins', 'Enterprise Admins', 'Administrators', 'Account Operators', 'Backup Operators' -FilePath $PSScriptRoot\Reports\GroupMembership.html -HideUsers
After we removed users, the diagrams are more readable, and we still get additional details on how it's all connected and how many members there are. You need to pick your battles. But that's not all.
The command has two more parameters, called Summary and SummaryOnly. In the case of Summary, it displays all tabs as before, and it adds a Summary tab on which all groups you requested are matched on one diagram. This gets you an interesting view of the interconnectivity of groups you asked for.
Show-WinADGroupMember -GroupName 'Test-Group', 'Domain Admins','Enterprise Admins', 'Administrators' -FilePath $PSScriptRoot\Reports\GroupMembership.html -Summary -HideUsers
Similarly, SummaryOnly does the same thing, but it skips individual reports for groups. Again, I want to reiterate that depending on groups you're investigating and it's member count, you may need to pick what you want to request and what you want to see at the same time. It's a bit try, see and adjust. It may work well or it may stutter a bit or not open at all. I would like your feedback so make sure to open issues with problems/feature requests on GitHub. I made an effort for the diagrams/tables to have proper data, but I do make mistakes so maybe I missed something.
Another command I added to ADEssentials is Get-WinADGroupMemberOf. Sometimes instead of going down you want to go up and see how things are looking.
You get similar parameters such as AddSelf and it displays similar datasets consisting only from groups but with details about group types, nesting, parent groups, and so on.
(Get-WinADGroupMemberOf -Identity 'przemyslaw.klys' -AddSelf)[1]
What I wasn't able to yet reliably figure out is following trusted forests as it would seem to get that I would need to query other domains for Foreign Security Principals explicitly. Even that doesn't give me built-in local groups. This would mean for larger environments with lots of forest trusts, I would need to query each forest separately for both FSP SID and also scan group membership of built-in groups. That may have a high penalty – to be investigated.
Show-WinADGroupMemberOf does what Get-WinADGroupMemberOf with added diagrams and basically is a copy of Show-WinADGroupMember just in reverse. It also consists of Summary/SummaryOnly parameters.
Show-WinADGroupMemberOf -Identity 'przemyslaw.klys', 'adm.pklys'
It's a great way to see how deep nesting can be dangerous and how often we're added into a group that's nested so deep our permissions exceed what we need. From my tests, this cmdlet is even more impacted with performance than the other one. I scanned one not so big admin account, and it displayed 4000 groups. Diagrams were struggling even to show up. For this type of case, I need to think about how to display data without killing the browser reliably.
There is a switch added called ClearCache. You will need it if you want to check for group changes dynamically. I am heavily caching AD requests as they go to speed up recursive calls as long multiple queries one after another. For example, for a group of 300 members with some nested groups, the initial call takes 7 seconds and 195 milliseconds. Same call 2 minutes later, just 2 seconds and 793 milliseconds. The more you work on the same objects, the lower time it will take. So if you want to refresh the cache, you either have to use the ClearCache switch or Import-Module ADEssentials -Force (which clears cache by design). It's not a speed daemon, but it does a bit more to what Get-ADGroupMember is giving. Additionally, the FilePath parameter is optional for Show cmdlets. When you don't provide a file path, it will use a temporary path to create the file and display it.
Those cmdlets are part of the ADEssentials module that I've been enhancing for some time now. All you need to do, to install it is:
Install-Module ADEssentials
While most of the commands in the ADEssentials module require RSAT (ActiveDirectory/GroupPolicy) to be present to work, actually commands described in this article don't. Cmdlets itself are ADSI based, so they don't need RSAT to work, but other cmdlets from ADEssentials do.
When you install-module, it will also install PSWriteHTML and PSEventViewer. The first one is required for displaying output in HTML (tables/diagrams). The other one is a wrapper around Get-WinEvent and is used by some of the commands available within ADEssentials. Install-Module will do all that installation for you without doing anything except for the RSAT requirement, but if you're AD Admin, you should already have those up and ready to use. If you don't have admin rights on your workstation it's still possible to use this module.
Install-Module ADEssentials -Scope CurrentUser
As those cmdlets described above are read-only, they don't require any rights in AD. I've been digesting my production environments using my standard ID. For sources, reporting issues, or feature requests, as always, visit GitHub. All my projects are hosted on it, and it's preferred method of providing support.
What you see on screenshots is possible thanks to the PSWriteHTML module that I wrote. It's trivially easy to build HTML with it as it offloads you from doing any HTML manipulation yourself. Having said that, you need to be aware of one thing. All cmdlets provide a switch called Online. What this switch does it tells PSWriteHTML to link to CDN for CSS style or JavaScript outside, making output HTML much smaller and more readable. Otherwise, it will push all JavaScript and all styles that it uses into HTML. Originally I didn't plan to expose Online switch in my commands, but after initial beta testing, some people are heavily restricted to their internet access. By default PSWriteHTML will generate HTML in Offline mode. You have to specifically enable Online mode to get pretty icons, as shown on the screenshots above. Otherwise, you will get icons that are embedded in code (or should be at least – I never really tested it in full offline mode). The difference between Offline and Online mode also impacts file size. While smaller groups in online mode are about 70KB in size, the default Offline mode gets it to 2MB per file. I prefer Online mode myself, but you may not have a choice.