25 Responses to Find When a User Was Added or Removed to a Domain Group Using PowerShell and Repadmin

  1. rino says:

    i have repadmin. i use a domain admin account. i ran the script on my workstation where i usually ran PS script. i didn’t get any output at all.

  2. Pingback: AD Group Auditing with Powershell :: Craig Porteous

  3. i would like to keep the entire CN of the object, how can i keep that?

  4. I noticed when using this it and grabs the data:
    ModifiedCount : 3
    DomainController : SIDC
    LastModified : 11/9/2015 1:21:31 PM
    Username : test user
    State : PRESENT
    Group : CN=testGroup1,OU=Groups,DC=SID,DC=com

    The username is actually the Displayname that it grabs, any way to change it so that it grabs the SamAccountName?

    • Boe Prox says:

      You would have to add some queries to the existing code so it can pull the account information from AD. This is currently only what is available from the metadata that is replicated.

      • Hi Boe, excellent script! Thanks so much. I don’t get to use PowerShell nearly as much as I’d prefer, but I kludged the following in to return SamAccountName instead of Username, which for us was returning “Lastname\, ” when querying.

        Function Get-ADGroupMemberDate {
        <#
        .SYNOPSIS
        Provides the date that a member was added to a specified Active Directory group.

            .DESCRIPTION
                Provides the date that a member was added to a specified Active Directory group.
        
            .PARAMETER Group
                The group that will be inspected for members and date added. If a distinguished name (dn) is not used,
                an attempt to get the dn before making the query.
        
            .PARAMETER DomainController
                Name of the domain controller to query. Optional parameter.
        
            .NOTES
                Name: Get-ADGroupMemberDate
                Author: Boe Prox
                DateCreated: 17 May 2013
                Version 1.0
        
                The State property will be one of the following:
        
                PRESENT: User currently exists in group and the replicated using Linked Value Replication (LVR).
                ABSENT: User has been removed from group and has not been garbage collected based on Tombstone Lifetime (TSL).
                LEGACY: User currently exists as a member of the group but has no replication data via LVR.
        
            .EXAMPLE
                Get-ADGroupMemberDate -Group "Domain Admins" -DomainController DC3
        
                ModifiedCount    : 2
                DomainController : DC3
                LastModified     : 5/4/2013 6:48:06 PM
                Username         : joesmith
                State            : ABSENT
                Group            : CN=Domain Admins,CN=Users,DC=Domain,DC=Com
        
                ModifiedCount    : 1
                DomainController : DC3
                LastModified     : 1/6/2010 7:36:08 AM
                Username         : adminuser
                State            : PRESENT
                Group            : CN=Domain Admins,CN=Users,DC=Domain,DC=Com
                ...
        
                Description
                -----------
                This lists out all of the members of Domain Admins using DC3 as the Domain Controller.
        
            .EXAMPLE
                Get-ADGroup -Identity "TestGroup" | Get-ADGroupMemberDate
        
                ModifiedCount    : 2
                DomainController : DC1
                LastModified     : 5/4/2013 6:48:06 PM
                Username         : joesmith
                State            : ABSENT
                Group            : CN=TestGroup,OU=Groups,DC=Domain,DC=Com
        
                ModifiedCount    : 1
                DomainController : DC1
                LastModified     : 1/6/2010 7:36:08 AM
                Username         : bobsmith
                State            : PRESENT
                Group            : CN=TestGroup,OU=Groups,DC=Domain,DC=Com
                ...
        
                Description
                -----------
                This lists out all of the members of TestGroup from the output of Get-ADGroup and auto-selecting DC1 as the Domain Controller.
        
        #>
        [OutputType('ActiveDirectory.Group.Info')]
        [cmdletbinding()]
        Param (
            [parameter(ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True,Mandatory=$True)]
            [Alias('DistinguishedName')]
            [string]$Group,
            [parameter()]
            [string]$DomainController = ($env:LOGONSERVER -replace "\\\\")
        )
        Begin {
            #RegEx pattern for output
            [regex]$pattern = '^(?<State>\w+)\s+member(?:\s(?<DateTime>\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2})\s+(?:.*\\)?(?<DC>\w+|(?:(?:\w{8}-(?:\w{4}-){3}\w{12})))\s+(?:\d+)\s+(?:\d+)\s+(?<Modified>\d+))?'
        }
        Process {
            If ($Group -notmatch "^CN=.*") {
                Write-Verbose "Attempting to get distinguished name of $Group"
        
                Try {
                    $distinguishedName = ([adsisearcher]"name=$group").Findone().Properties['distinguishedname'][0]
                    If (-Not $distinguishedName) {Throw "Fail!"}
                } Catch {
                    Write-Warning "Unable to locate $group"
                    Break                
                }
        
            } Else {$distinguishedName = $Group}
        
            Write-Verbose "Distinguished Name is $distinguishedName"
            $data = (repadmin /showobjmeta $DomainController $distinguishedName | Select-String "^\w+\s+member" -Context 2)
        
            ForEach ($rep in $data) {
               If ($rep.line -match $pattern) {
                   # Attempt to extract SamAccountName from separate query at same time other data is being collected
                   $Username = [regex]::Matches($rep.context.postcontext,"CN=(?<Username>.*?),.*") | ForEach {$_.Value}
                   # Replace extraneous text that comes with the repadmin result values via regexp
                   $Username = $Username -replace "{\s+CN=", "CN="
                   $Username = $Username -replace "DC=org,\s\w+\\}", "DC=org"
                   # Get-ADUser can fail if a nested group is found; skip if nested subgroup found. TO DO: Identify subgroups properly and iterate through them?
                   try {
                        # -ExpandProperty returns just the value rather than the key-value pair of @{SamAccountName=JDoe}
                        $Result = Get-ADUser -Identity "$Username" | Select-Object -Property SamAccountName -ExpandProperty SamAccountName
                   } catch { }
        
        
                   $object = New-Object PSObject -Property @{
                        #Username = [regex]::Matches($rep.context.postcontext,"CN=(?<Username>.*?),.*") | ForEach {$_.Groups['Username'].Value} 
                        #Username = [regex]::Matches($rep.context.postcontext,"(?<Username>.*)") | ForEach {$_.Groups['Username'].Value} 
                        DomainController = $matches.dc
                        Group = $distinguishedName
                        State = $matches.state
                        SamAccountName = $Result
                        LastModified = If ($matches.DateTime) {[datetime]$matches.DateTime} Else {$Null}
                        ModifiedCount = $matches.modified
                    }
        
                    $object.pstypenames.insert(0,'ActiveDirectory.Group.Info')
                    $object
                }
            }
        }
        

        }

      • kev000 says:

        Hi Boe, excellent script! I don’t get to use PowerShell nearly as much as I’d like, but I kludged the following which seems to return the same data, just with SamAccountName instead of the “Lastname\, ” my folks were getting before. The only problem is that nested subgroups are not properly traversed or handled, just skipped.

        Function Get-ADGroupMemberDate {
        <#
        .SYNOPSIS
        Provides the date that a member was added to a specified Active Directory group.

            .DESCRIPTION
                Provides the date that a member was added to a specified Active Directory group.
        
            .PARAMETER Group
                The group that will be inspected for members and date added. If a distinguished name (dn) is not used,
                an attempt to get the dn before making the query.
        
            .PARAMETER DomainController
                Name of the domain controller to query. Optional parameter.
        
            .NOTES
                Name: Get-ADGroupMemberDate
                Author: Boe Prox
                DateCreated: 17 May 2013
                Version 1.0
        
                The State property will be one of the following:
        
                PRESENT: User currently exists in group and the replicated using Linked Value Replication (LVR).
                ABSENT: User has been removed from group and has not been garbage collected based on Tombstone Lifetime (TSL).
                LEGACY: User currently exists as a member of the group but has no replication data via LVR.
        
            .EXAMPLE
                Get-ADGroupMemberDate -Group "Domain Admins" -DomainController DC3
        
                ModifiedCount    : 2
                DomainController : DC3
                LastModified     : 5/4/2013 6:48:06 PM
                Username         : joesmith
                State            : ABSENT
                Group            : CN=Domain Admins,CN=Users,DC=Domain,DC=Com
        
                ModifiedCount    : 1
                DomainController : DC3
                LastModified     : 1/6/2010 7:36:08 AM
                Username         : adminuser
                State            : PRESENT
                Group            : CN=Domain Admins,CN=Users,DC=Domain,DC=Com
                ...
        
                Description
                -----------
                This lists out all of the members of Domain Admins using DC3 as the Domain Controller.
        
            .EXAMPLE
                Get-ADGroup -Identity "TestGroup" | Get-ADGroupMemberDate
        
                ModifiedCount    : 2
                DomainController : DC1
                LastModified     : 5/4/2013 6:48:06 PM
                Username         : joesmith
                State            : ABSENT
                Group            : CN=TestGroup,OU=Groups,DC=Domain,DC=Com
        
                ModifiedCount    : 1
                DomainController : DC1
                LastModified     : 1/6/2010 7:36:08 AM
                Username         : bobsmith
                State            : PRESENT
                Group            : CN=TestGroup,OU=Groups,DC=Domain,DC=Com
                ...
        
                Description
                -----------
                This lists out all of the members of TestGroup from the output of Get-ADGroup and auto-selecting DC1 as the Domain Controller.
        
        #>
        [OutputType('ActiveDirectory.Group.Info')]
        [cmdletbinding()]
        Param (
            [parameter(ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True,Mandatory=$True)]
            [Alias('DistinguishedName')]
            [string]$Group,
            [parameter()]
            [string]$DomainController = ($env:LOGONSERVER -replace "\\\\")
        )
        Begin {
            #RegEx pattern for output
            [regex]$pattern = '^(?<State>\w+)\s+member(?:\s(?<DateTime>\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2})\s+(?:.*\\)?(?<DC>\w+|(?:(?:\w{8}-(?:\w{4}-){3}\w{12})))\s+(?:\d+)\s+(?:\d+)\s+(?<Modified>\d+))?'
        }
        Process {
            If ($Group -notmatch "^CN=.*") {
                Write-Verbose "Attempting to get distinguished name of $Group"
        
                Try {
                    $distinguishedName = ([adsisearcher]"name=$group").Findone().Properties['distinguishedname'][0]
                    If (-Not $distinguishedName) {Throw "Fail!"}
                } Catch {
                    Write-Warning "Unable to locate $group"
                    Break                
                }
        
            } Else {$distinguishedName = $Group}
        
            Write-Verbose "Distinguished Name is $distinguishedName"
            $data = (repadmin /showobjmeta $DomainController $distinguishedName | Select-String "^\w+\s+member" -Context 2)
        
            ForEach ($rep in $data) {
               If ($rep.line -match $pattern) {
                   # Attempt to extract SamAccountName from separate query at same time other data is being collected
                   $Username = [regex]::Matches($rep.context.postcontext,"CN=(?<Username>.*?),.*") | ForEach {$_.Value}
                   # Replace junk text that comes with the repadmin result values via regexp
                   $Username = $Username -replace "{\s+CN=", "CN="
                   $Username = $Username -replace "DC=org,\s\w+\\}", "DC=org"
                   # Validate out to PS console what's being formatted for AD search
                   #[string]$Username
                   # Get-ADUser can fail if a nested group is found; skip if nested subgroup found. TO DO: Identify subgroups properly and iterate through them?
                   try {
                        # -ExpandProperty returns just the value rather than the key-value pair of @{SamAccountName=JDoe}
                        $Result = Get-ADUser -Identity "$Username" | Select-Object -Property SamAccountName -ExpandProperty SamAccountName
                   } catch { }
        
        
                   $object = New-Object PSObject -Property @{
                        #Username = [regex]::Matches($rep.context.postcontext,"CN=(?<Username>.*?),.*") | ForEach {$_.Groups['Username'].Value} 
                        #Username = [regex]::Matches($rep.context.postcontext,"(?<Username>.*)") | ForEach {$_.Groups['Username'].Value} 
                        DomainController = $matches.dc
                        Group = $distinguishedName
                        State = $matches.state
                        SamAccountName = $Result
                        LastModified = If ($matches.DateTime) {[datetime]$matches.DateTime} Else {$Null}
                        ModifiedCount = $matches.modified
                    }
        
                    $object.pstypenames.insert(0,'ActiveDirectory.Group.Info')
                    $object
                }
            }
        }
        

        }

        • kev000 says:

          Whoops – and forgot to say thanks for the script!

        • Boe Prox says:

          That is awesome! Thanks for sharing that excellent update. I’ll try to get that updating on technet one of these days. Thanks!

          • John says:

            does this script work? when i run “Get-ADGroupMemberDate” its not a cmdlet that is recognized? what am i missing? If anyone has made this script work, please advise steps…

            thanks JohnM

          • Boe Prox says:

            How are you running the script? Can you provide the exact syntax? Did you make sure to dot source the file first to load the function?

  5. Pradeep says:

    Is there any way to find who added that account into a security group through PowerShell?

  6. Jim says:

    when using this function the Modifiedcount,domaincontoller & lastmodified objects are empty, running repadmin from the command prompt/ps prompt returns the required values but the function doesn’t, running on Windows 2008 R2

  7. mowz says:

    I’m not able to get this to return any information. The repadmin works fine but using the Get-ADGroupMemberDate doesn’t return anything for me. What am I missing here?

  8. Markus says:

    Repadmin only returns 20 members of a group when I run it, running it on “Domain Users” in a real world domain, not a test domain.
    How do I get the rest of the 3600+ members?

  9. andymuch says:

    Amazing function btw! Thanks for sharing… Any way to get the UserName to be the Sam Account Name attribute from AD?

  10. Kurt Falde says:

    Instead of repadmin how about using Get-ADReplicationAttributeMetadata?

  11. jabrasser says:

    Cool script Boe, nice parsing on the Repadmin data. Just downloaded the script and the output is quite nice.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s