AD Group History Mystery: PowerShell v3 REPADMIN

I remember back in high school the janitor had this massive ring of keys on his belt.  The keys would jingle with each step as he pushed the broom down the hall.  It was like his own percussion section accompanying the tune he whistled.  So what does this have to do with PowerShell?

The Scenario

After speaking about SID history and token size at PowerShell Saturday last month an attendee approached me with a common concern.  I was so excited to code the answer that I did it in the airport on the way home.

Joe User has been with the company for 23 years and has accumulated more group memberships than the entire desktop support team.  Joe has rotated through five different departments during his career and managed to survive all of the layoffs.  As a result he has access to every share in the company.  Even worse his access token is so big that it won’t fit through the door.

imageWe would love to clean up his group memberships, but we have no way of knowing when he was added to all these groups.  If we could see the dates he joined those groups it would give us a clue about removing just the older group memberships.  Without this information his token will continue to bloat… just like that overloaded key ring swinging on the janitor's hip.

Where can we find group membership details?

When you look into the member attribute of an AD group you’ll find a list of all members in distinguished name format.  But that’s it.  There is no smoking gun or finger prints that tell you how they got there.  However, there is a little-known piece of data called replication metadata that can tell us exactly what we need.  This data is quite special for groups, because it shows us the date individual members were added and removed.  Awesome!  But if you try to view it in the GUI it looks like ugly hex.

REPADMIN is so last decade

imageThat’s where REPADMIN helps with the handy showObjMeta parameter.  While this command will show us the data, it wraps and scrolls so much in the console that it is difficult to read.  Also it is extremely painful to parse with any kind of script.

Try it for yourself:

repadmin.exe /showObjMeta DCNAME “CN=GroupName,OU=SomeOU,DC=contoso,DC=com”

This is a cool command that I’ve used for forensic investigations in the past to see when an attribute was last modified and which DC originated the change.  Then you may be able to trace it down in the logs on that DC to find the account that made the change.  You can read more about this here and here.

Can I do it with PowerShell?  Please say yes.

Way back in PowerShell v1 MVP Brandon Shell wrote a script called Get-ADObjectReplicationMetadata to do this.  The AD cmdlets in PowerShell v2 had little parity with REPADMIN.  Now in PowerShell v3 the AD cmdlets have made good progress.  We still have a ways to go, but you can see in the chart below that PowerShell is catching up with REPADMIN.  This is an unofficial comparison chart that I created based on my own observations.  Any corrections or additions are welcome.

Notice now we have one of my new favorite cmdlets Get-ADReplicationAttributeMetadata.  When I found this in the Windows Server 2012 beta it was like Christmas morning!



2012 Cmdlets
/FailCache Get-ADReplicationFailure
/Queue Get-ADReplicationQueueOperation
/ReplSingleObj Sync-ADObject
/ShowConn Get-ADReplicationConnection
/ShowObjMeta Get-ADReplicationAttributeMetadata
/ShowRepl  /ReplSum Get-ADReplicationPartnerMetadata
/ShowUTDVec Get-ADReplicationUpToDatenessVectorTable
/SiteOptions Set-ADReplicationSite
2008 R2 Cmdlets
/ShowAttr Get-ADObject
/SetAttr Set-ADObject
/PRP Get-ADDomainControllerPasswordReplicationPolicy

The Script

Here is the PowerShell goodness we’ve been awaiting (also attached at the bottom of the post):

Import-Module ActiveDirectory            
$username = "janitor"            
$userobj  = Get-ADUser $username            
Get-ADUser $userobj.DistinguishedName -Properties memberOf |            
 Select-Object -ExpandProperty memberOf |            
 ForEach-Object {            
    Get-ADReplicationAttributeMetadata $_ -Server localhost -ShowAllLinkedValues |             
      Where-Object {$_.AttributeName -eq 'member' -and             
      $_.AttributeValue -eq $userobj.DistinguishedName} |            
      Select-Object FirstOriginatingCreateTime, Object, AttributeValue            
    } | Sort-Object FirstOriginatingCreateTime -Descending | Out-GridView


I realize that it looks complicated, but it is practically a one-liner.  Notice the highlighted pieces:

  • You’ll need to provide a username in the appropriate variable.  This can be a short user ID or a distinguished name.
  • The metadata cmdlet needs the switch ShowAllLinkedValues in order to return all of the group membership metadata.  You only need this parameter with AD objects containing linked values.
  • Replace localhost with the FQDN of your nearest DC containing the user account in question.

Note that you will need a Windows Server 2012 domain controller and optionally the AD PowerShell module installed from the Windows 8 RSAT.

When you run this script you’ll get a clean grid view full of dated group memberships.  If any groups are missing in the list, then they have likely not been converted to Linked Value Replication (LVR).


It would be easy to wrap this code into a function or module where you could reuse it for processing a large number of accounts.  You could pipe a list of users into it, and then send the results to a CSV file.  To scale it more efficiently you could simply dump the member metadata for every group in the domain instead of retrieving it multiple times for multiple users.

Do Your Part: Reduce Token Bloat Today

Armed with this code you can now begin the process of reviewing token bloat users and their group memberships.  Hopefully the date information will empower you to remove them from some of those stale groups.  Who knows, you might even be able to get by with a smaller key ring.

get user group membership history.p-s-1.txt

Comments (29)
  1. Excellent post!  I'll be sharing it 🙂



  2. anonymouscommenter says:

    Great article! Even for me who doesn't understand the full capability of PowerShell yet, it made sense!


  3. LA Richards says:

    If you are using Powershell v3 you shouldn't need to import the active directory module manually.  Powershell will import the module the first time you run a cmdlet from a particular module.

  4. LA Richards says:

    And, do you know if Get-ADReplicationAttributeMetadata works in Powershell v3 on a Windows 7/2008R2 system?

  5. sgrinker says:

    @LA Richards

    I did a quick check on my Win7 box running PowerShell v3, and it doesn't look like it.  Granted I wasn't surprised to find that given the ActiveDirectory module on that machine didn't get upgraded.  I'm not even sure there are plans to create RSAT tools for Win7 that support Server 2012 unfortunately.  Granted it would be a huge plus if they did release it, but based on past RSAT history I'm not holding my breath.  I guess the alternative would be to remote to a machine that has the updated module.

  6. Hi LA,

    Thanks for the feedback.

    You're right that it is not necessary to load the module in PS v3, but I consider it good form.  That way the script is slightly more efficient without searching for the module to load.

    You are also correct that you must have the Windows 8 version of the RSAT to use the new AD module cmdlets (or a Windows Server 2012 box).  I am not aware of any effort to back-port those to Windows 7.

    Keep scripting,


  7. anonymouscommenter says:

    Do you really need a 2012 DC? Wouldn't a 2012 member server or any server with the new powershell cmdlets installed be sufficient? Or do the 2012 AD cmdlets have to run against a 2012 DC?

  8. Hi Fred,

    Great questions. Regarding the AD cmdlets for 2012 please see this article where I discuss the various ways to get them from Windows 7:…/how-to-use-the-2012-active-directory-cmdlets-from-windows-7.aspx

    It is true that you cannot install the AD cmdlets for 2012 on Windows 7, but you can remotely import them.  And this link explains how to do it.



    1. JohnM says:

      Hi Ashley –

      AWESOME – AWESOME Script. Thanks for sharing it with the rest of the world!

      Quick questions –
      1. How long is the metadata kept for the user that have been added to a group? Until the user is deleted from AD?
      2. Is there a way to have this script run the information for a group? Meaning, identify all of the users that were added from the group perspective rather than from the user – Is this possible?

      Thanks in Advanced.

  9. anonymouscommenter says:

    I have often told customers…

    “Most companies clean up stale users, a few companies clean up stale computers, but no one cleans up stale groups.”

    Generally it is easy enough to tell if a computer or user account is stale

  10. anonymouscommenter says:

    Welcome! Today’s post includes demo scripts and links from the Microsoft Virtual Academy event: Using PowerShell for Active Directory . We had a great time creating this for you, and I hope you will share it with anyone needing to ramp up their

  11. anonymouscommenter says:

    Learn about the nuances involved in reporting group memberships with Active Directory PowerShell.

  12. Johnny-mcldaz says:

    Great article Ashley. I am trying to turn this into a function, and it works for one user. But when I want to add the param value [string[]]$name, it fails. From a get-member for the get-aduser command, I see the property name and its value is a string.
    What am I doing wrong?
    Function Get-ADUserGroupMembershipDetail

    Param (
    HelpMessage="Enter the user ID to query.",

    $userobj = Get-ADUser $Name
    #Trap {Return "error"}

    Get-ADUser $userobj.DistinguishedName -Properties memberOf |
    Select-Object -ExpandProperty memberOf |
    ForEach-Object {
    Get-ADReplicationAttributeMetadata $_ -Server DC2 -ShowAllLinkedValues |
    Where-Object {$_.AttributeName -eq ‘member’ -and
    $_.AttributeValue -eq $userobj.DistinguishedName} |
    Select-Object FirstOriginatingCreateTime, Object, AttributeValue
    } | Sort-Object FirstOriginatingCreateTime -Descending | Out-GridView
    Any help would be appreciative since I’m still learning.

  13. Hi Johnny,

    Can you provide more detail? How are you calling the function? Is "DC2" a valid DC name in your environment? What is the exact error message?


  14. anonymouscommenter says:

    Someone just now added Jimmy to the Domain Admins group! How do I know? Because I used PowerShell to check. Let me show you how.

    Some of the best customers that I visit get email pages when high value group memberships change. Obviously this is strongly

  15. anonymouscommenter says:

    Pls give the complete powershell command to get object changes in AD.

  16. NolanC says:

    Works beautifully. Love it

  17. Leo Mengmeng Li says:

    Hi Ashley,
    Thanks for the fantastic live on MVA. Do you still have the script to retrieve the metadata of locked out user?



  18. anonymouscommenter says:

    FirstOriginatingCreateTime is always blank and its not retrieving all of the groups only one. I am running windows 2012R2 but not in 2008R2 mode. Could that be it?

  19. Hi Tory,
    Sounds like you may have some legacy groups from pre-2003. Check this article for guidance:
    Hope this helps,

  20. Nice job educating the community here Ashley

  21. anonymouscommenter says:

    Hi Ashley. The return from your script is not the date the user account became a member, but the date the group was created.

  22. Hi Dave,
    Please see my reply to Tory in the comments above. Does that help?

  23. Shane Adkins says:

    Great post and right on time for my customer today. Thanks for sharing and providing impact!

  24. Manoj Chaudhari says:

    Awesome Script. However We are not getting group information for cross domain groups so could you please suggest what modification is required here ?

    1. aatif kungle says:

      Awesome Script. However We are not getting group information for cross domain groups so could you please suggest what modification is required here ?

      Hello Manoj,

      Have you got the fix for above if so then could post it?


  25. KK_80 says:

    I tried the above mentioned code however, it does not work for disabled user accounts. Thereby cannot get historical data regard group assignments for disabled users. Appreciate any help on this.

  26. Roger says:

    Hi Ashley, great post but I miss a detail. Who performed the Action?
    How do I get out this?

    Kind regards

  27. Martin says:

    I had a strange problem with a script similar to this one where -ShowAllLinkedValues didn’t actually show all members in the group. It seemed to be random, everything was working just fine and I could see 1000+ members, then when more members where added I could see only 300 members something.
    After some investigation it turns out that “&” was used in the name of some OU’s. When a member was located in one of those OU’s there was an error and only members listed before that user was shown.

Comments are closed.

Skip to main content