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.
We 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
That’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!
REPADMIN | PowerShell |
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 |
Add-ADDomainControllerPasswordReplicationPolicy | |
Remove-ADDomainControllerPasswordReplicationPolicy | |
Get-ADAccountResultantPasswordReplicationPolicy | |
Get-ADDomainControllerPasswordReplicationPolicyUsage |
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
Anonymous
October 17, 2012
Excellent post! I'll be sharing it :) Cheers, RhoderickAnonymous
October 18, 2012
Great article! Even for me who doesn't understand the full capability of PowerShell yet, it made sense! SatishAnonymous
November 02, 2012
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.Anonymous
November 02, 2012
And, do you know if Get-ADReplicationAttributeMetadata works in Powershell v3 on a Windows 7/2008R2 system?Anonymous
November 02, 2012
The comment has been removedAnonymous
November 06, 2012
The comment has been removedAnonymous
July 31, 2013
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?Anonymous
August 02, 2013
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: blogs.technet.com/.../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. Thanks, Ashley- Anonymous
April 07, 2016
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.
- Anonymous
Anonymous
March 14, 2014
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 staleAnonymous
October 29, 2014
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 theirAnonymous
November 25, 2014
Learn about the nuances involved in reporting group memberships with Active Directory PowerShell.Anonymous
December 01, 2014
The comment has been removedAnonymous
December 02, 2014
The comment has been removedAnonymous
December 17, 2014
The comment has been removedAnonymous
December 18, 2014
Pls give the complete powershell command to get object changes in AD.Anonymous
December 21, 2014
Works beautifully. Love itAnonymous
February 04, 2015
Hi Ashley,
Thanks for the fantastic live on MVA. Do you still have the script to retrieve the metadata of locked out user?
Regards,
LeoAnonymous
April 07, 2015
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?Anonymous
April 08, 2015
Hi Tory,
Sounds like you may have some legacy groups from pre-2003. Check this article for guidance:
http://blogs.technet.com/b/heyscriptingguy/archive/2014/04/22/remediate-active-directory-members-that-don-39-t-support-lvr.aspx
Hope this helps,
AshleyAnonymous
August 06, 2015
Nice job educating the community here AshleyAnonymous
November 11, 2015
Hi Ashley. The return from your script is not the date the user account became a member, but the date the group was created.Anonymous
November 16, 2015
Hi Dave,
Please see my reply to Tory in the comments above. Does that help?
GoateePFE
AshleyAnonymous
March 11, 2016
Great post and right on time for my customer today. Thanks for sharing and providing impact!Anonymous
March 20, 2016
Awesome Script. However We are not getting group information for cross domain groups so could you please suggest what modification is required here ?- Anonymous
April 10, 2017
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?Regards,Aatif
- Anonymous
Anonymous
May 15, 2016
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.Anonymous
May 19, 2016
The comment has been removedAnonymous
September 13, 2016
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.