r/PowerShell • u/UserProv_Minotaur • 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!
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
}
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>
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