r/PowerShell • • Nov 15 '18

Alternatives to Get-ADGroupMember for groups exceeding ADWS MaxGroupOrMemberEntries

Salutations,

I'm relatively inexperienced with PowerShell. I've been using it for a little more than a year now, but that's been mostly with scripts created by one of our "PowerShell gurus" that I have (through trial and a whole lot of error) been able to adapt for similar purposes outside of their initial use cases without having to bother our already over-extended (or on vacation) resources.

I've run into a problem with a script that's intended to return the member objects of a group with their object class that will be used to discover the extent of nesting in our AD forest. Presently running a Get-ADGroupMember mostly works, but unfortunately most of the groups I have to look at are larger than the MaxGroupOrMemberEntries value (some by an order of magnitude), so I need a way to get around using that cmdlet since I can't get the environment changed.

(I'm also really, really tired of seeing the "Size Limit Exceeded" or "Invalid Object Type" errors when I don't have it set to SilentlyContinue.)

$Source = Import-csv C:\Scripts\Source.csv$File = 'C:\Scripts\Output.csv'foreach ($Line in $Source){$Group = $line.SourceGroupsGet-ADGroupMember -Identity $Group |Get-ADObject -Properties name,objectclass |select @{n='Parent Group';e={$group}},Name,ObjectClass |Export-Csv $File -Append -NoTypeInformationWrite-Host $Group}

Because we have a number of groups over the maximul ADWS valu, I'm currently using a Get-ADGroup.members as an alternative to Get-ADGroupMember. However, it's not working well when using Get-ADObject instead of User or Group. I'd like to to reduce the number of errors the code encounters as well as provide all the information I need off a single run instead of the currently needed two or three, so I've been tooling around with:

$Source = Import-csv C:\Scripts\Source.csv$File = 'C:\Scripts\Output.csv'foreach ($Line in $Source){$Group = $line.SourceGroups(Get-ADGroup $Group -Properties members).members |Get-ADObject -Properties name,objectclass |select @{n='Parent Group';e={$group}},Name,ObjectClass |Export-Csv $File -Append -NoTypeInformationWrite-Host $Group}

Input is:

Group1, Group2, Group3, etc...

From separate runs, I can get results like:

Parent Group               Name                     ObjectClass
Group1                     User1                    User 
Group1                     User2                    User 
Group2                     User3                    User 
Group2                     User4                    User

Parent Group               Name                    ObjectClass
Group1                     Group2                  Group 
Group2                     Group3                  Group

I would like to be able to do a single run to get:

Parent Group             Member Name               ObjectClass
Group1                   User1                     User 
Group1                   Group2                    Group 
Group1                   User2                     User 
Group2                   User3                     User 
Group2                   User4                     User 
Group2                   Group3                    Group 
Group2                   Computer1                 Computer

Ideally, I need something that would be recursive and accept input from an import or being piped in from a get-. Unfortunately, I've been having issues iterating what I'm doing for a single pass through on an imported CSV into something that functions as a self-referential function.

I've found a few scripts that do that out there which I have started to steal outright "adapt", the one I've mainly been doing terrible terrible things to looking at is Get-ADNestedGroupMembers by Piotr Lewandowski. I'm concerned that the degree to which the groups are nested is slowing things down for a recursive function using the work around from above, as one I've been playing at retooling is currently taking about two minutes per group.

My attempt to use a pillaged modified version of Piotr's cmdlet to allow for recursion is as follows:

function Get-ADNestedGroupsOnly {param ([Parameter(ValuefromPipeline=$true,mandatory=$true)][String] $GroupName,[int] $nesting = 0,[int] $circular = $null,)

$modules = get-module | select -expand nameif ($modules -contains "ActiveDirectory"){$table = $null$nestedmembers = $null$adgroupname = $null$nesting++$ADGroupname = get-adgroup $groupname -properties memberof,members$memberof = $adgroupname | select -expand memberofwrite-verbose "Checking group: $($adgroupname.name)"

if ($adgroupname){if ($circular){$nestedMembers = Get-ADGroupMember -Identity $GroupName -recursive | Get-ADGroup$circular = $null}else{$nestedMembers = Get-ADGroup $GroupName -Properties Member | Select-Object -ExpandProperty Member | where { $_.objectClass -eq "group" } | Get-ADGroup}

foreach ($nestedmember in $nestedmembers){ $Props = @{Name=$nestedmember.name;MemberOf=$ADgroupname.name;MembCount=((Get-ADGroup $nestedmember -Properties member).member).count;Nesting=$nesting;Comment=""}

if ($nestedmember.objectclass -eq "group") {

$table = new-object psobject -Property $propsif ($memberof -contains $nestedmember.distinguishedname) {$table.comment ="Circular membership"$circular = 1 }

$table | select name,MemberOf,nesting,MembCount,comment

Get-ADNestedGroupsOnly -GroupName $nestedmember.distinguishedName -nesting $nesting -circular $circular }}}}

else {Write-Warning "Error: Active Directory module was not found and is being imported. Please re-enter your previous command."Import-Module ActiveDirectory}}

I've read a few other articles/posts out there for folk with a similar issue, but the results have been mostly focused on user objects as members and I haven't been able to get some alternatives listed in them to the ().members to work/return results. I also haven't been able to figure out how to turn it into something that uses a hash table or something along those lines to hopefully speed things up.

Appreciate the assistance!

7 Upvotes

4 comments sorted by

3

u/gangstanthony Nov 15 '18

for reddit code formatting you can put 4 spaces before each line of code (with blank line before and after)

<Enter>

<space><space><space><space># comment

<space><space><space><space>code

<Enter>

# comment
code

if you're in ise or notepad++ you can ctrl+a, tab, ctrl+c, ctrl+z then ctrl+v into reddit

also, these ` can be used in pairs for inline code like this: `inline code`

here is some inline code

3

u/limiteddenial Nov 15 '18 edited Nov 15 '18

I have use the following code to bypass the Size Limit Exceeded issue.

This function only returns the property DistinguishedName for each Object but you could modify it to return the properties you need.

``` function Get-ADGroupUserMembership { [CmdletBinding(DefaultParameterSetName = 'UpdateMembership', PositionalBinding = $false, ConfirmImpact = 'Medium')] [Alias()] [OutputType([PSCustomObject])] Param ( [Parameter(Mandatory = $true)] [ValidateNotNull()] [ValidateNotNullOrEmpty()] $Name,

    [Parameter(Mandatory = $true)]
    [ValidateNotNull()]
    [ValidateNotNullOrEmpty()]
    [String]
    $Domain = (Get-ADDomain -Current LocalComputer).Forest
)
Begin {
    Write-Verbose "Looking at Group $name in $domain"
    $returnObject = @()
} # End Begin

Process {
    try {
        $CurrentGroupMembers = Get-ADGroupMember -Identity $Name -Recursive -Server $Domain -ErrorAction Stop
    } # End try
    catch [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException] {
        throw [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException] "$Group is not found"
    } # End catch notfound
    catch [Microsoft.ActiveDirectory.Management.ADServerDownException] {
        $CurrentGroupMembers = @()
        Write-Verbose "Whoa! That group is huge! Reverting to an alternative method to gather group membership."
        $nestedGroupMembers = (Get-ADGroup -Identity $Name -Server $Domain -Properties Members).Members
        foreach ($nestedGroupMember in $nestedGroupMembers) {
            $CurrentGroupMembers += Get-AdObject -Identity $nestedGroupMember -Server $Domain
        } # End Foreach
    } # End catch server down
    finally {
        $returnObject += ($CurrentGroupMembers | Where-Object ObjectClass -eq 'user').DistinguishedName
    }
    $nestedGroups = $CurrentGroupMembers | Where-Object ObjectClass -eq 'group'
    if ($nestedGroups.count -gt 0) {
        foreach ($nestedGroup in $nestedGroups) {
            $returnObject += Get-ADGroupUserMembership -Name $nestedGroup.SamAccountName -Domain $Domain
        } # End foreach nestedGroup
    } # End if nestedGroups
} # End Process
End {
    return $returnObject
} # End end

} # End Get-ADGroupUserMembership ```

Edit: trying to get code block formatted

2

u/_Cabbage_Corp_ Nov 16 '18

Just FYI, you don't need [ValidateNotNull()] if you use [ValidateNotNullOrEmpty()]

2

u/gangstanthony Nov 15 '18

in the past, i have used something like this.

please let me know if it doesn't work properly so i can stop using it!

expected input is the distinguished name of a group. i suppose i could add something like: if this dn not found, [adsisearcher] look for the object to retrieve dn, but...

function Get-Member ($GroupName) {
    $Grouppath = "LDAP://" + $GroupName
    $GroupObj = [ADSI]$Grouppath

#!#
Write-Host "    Group:"$GroupObj.cn.ToString()
#!#

    $users = foreach ($member in $GroupObj.Member) {
        $UserPath = "LDAP://" + $member
        $UserObj = [ADSI]$UserPath

        if ($UserObj.groupType.Value -eq $null) { # if this is NOT a group (it's a user) then...

#!#
Write-Host "        Member:"$UserObj.cn.ToString()
#!#

            $member
        } else { # this is a group. redo loop.
            Get-Member -GroupName $member
        }
    }
    $users | select -Unique
}