ADPoSh: Create Unused Group Age Report


Update 10/19/2017:  the script has been updated to remove groups with RIDS less than 1000,

Visit github repo for a copy of the latest script. 


Today’s topic is going to be getting rid of unused groups in the forest.  Over the last few years I’m seeing a growing trend were administrators or identity teams are creating groups that are never used.  Once these groups are created it seems impossible to get these groups removed due to the unknown.  These unknowns are several things like; it might be used just empty, or we have no idea what is using it.  As part of my continue discussions I started focusing on low hanging fruit (groups).  To solve this I started out focusing on if memberof and members are empty and the when change was older than x it must be unused.  This seems logical but turns out it was not, I then expanded my search to include if msDS-ReplValueMetaData is null and if the whencreated is older than x.  There are several topics around leveraging msDS-ReplValueMetaData and what is it used for, in short this is an attribute that contains all of the replication metadata around linked values (Use PowerShell and Repadmin to Check for Updates to High Privileged Groups). If the attribute is empty more than likely the group has never had any direct members added to it.  I have found that at least a fourth of all groups in most Active Directory environments fall into this category.

Here is the script I threw together.

 $_default_log = $env:userprofile + '\Documents\never_used_ad_group_age.csv'
 If ($(Try { Test-Path $_default_log} Catch { $false })){Remove-Item $_default_log -force}
 (get-adforest).domains | foreach {$_domain = $_
     get-adgroup -LDAPFilter "(&(!(member=*))(!(memberof=*)))" -Properties "msDS-ReplValueMetaData",whencreated,groupscope,groupcategory -server $_domain | `
         where {(!($_."msDS-ReplValueMetaData"))} | select `
         @{name='AgeinDays';expression={(new-TimeSpan($($_.whencreated)) $(Get-Date)).days}},isCriticalSystemObject,distinguishedname,`
         @{name='ParentOU';expression={$($_.distinguishedname -split '(?<![\\]),')[1..$($($_.distinguishedname -split '(?<![\\]),').Count-1)] -join ','}} | `
         export-csv $_default_log -Append -NoTypeInformation
 write-host "Report Can be found here $_default_log"

This will run through all the domains in a forest.

Link to github

link to Technet gallery *Old location

Once the script is complete open it in excel and pivot on the data of your choice.  Here is some examples on how I would

Select the data


select Insert Pivot Table, make the pivot table fields look like the following:


Should have a nice little view like this


*Consider filtering out OU’s or Containers like Builtin and Users.

Make the Pivot Table Fields look like this


A nice view of each ou and the age and count of groups should be displayed.



Now that you have this data hopefully it will be enough to clean out some of the bloat in Active Directory. In a next blog post we will tackle getting the last change from the msDS-ReplValueMetaData to figure out the last time an object was added or removed.  Hope you find this useful and have a good day.