Active Directory Reporting: Unused Group Age Report.


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 Technet gallery

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.


Comments (3)

  1. Thank you for this usefull post.
    However, I would suggest an improvement to your code.
    The default delimiter (a comma) of the Export-CSV cmdlet is in this case a bit tricky to handle because Distinguished Names also contains commas as separators.
    The -Delimiter ‘;’ parameter fixes this problem and Excel converts directly text to columns at the opening of the document.

    1. Chad Cox says:

      I usually don’t have problems with excel not putting DN’s into a single column.

Skip to main content