GroupMembership

I have some groups. For example:

Group1. Group2, Group3 are members of Group1 and Group4 is a member of Group2.

and few users are also members of Group1 and Group2.

I want to generate a report of all nested groups inside group1, I don’t want the users.

For example:

Group1 Group2 Group4
Group3
How can I achieve this, so far I have this script:
$allgroups=Get-ADGroup -Filter { Name -like "Group*"} -server "lab.local
$res=foreach ( $g in $allgroups)
{
$members = Get-ADGroupMember -Identity $g -Server lab.local
}
foreach ($member in $members)

{

if ($member.objectClass -eq 'group')

{

[pscustomobject]@{
Group = $g
Member = $member.Name}
}
}

$res

I’ve been working on a similar process for Exchange Distribution Groups. I created another advanced function for Nested AD Groups and made it available. Below is a link to the GitHub repository if you like to try it out. I will add if you don’t have the permissions to view a specific group it will return an error stating object not found on the server.

Get-NestedADGroupMember -Identity 'Domain Admins' -ListGroups

https://github.com/JasonRobertson/PowerShell/blob/master/Get-NestedADGroupMember/Get-NestedADGroupMember.ps1

$members = Get-ADGroupMember -Identity $g -Server lab.local | where-object {$_.objectClass -eq "Group"}

If you do this, you won’t need the second foreach loop…

Okay. I tried this however it is listing the groups in a list, I want to know the hierarchy of the group membership. Probably export to a csv file. Like if

Group2, Group3 are members of Group1 and Group4 is a member of Group2 and few users are also members of Group1 and Group2. I don’t want to the users in the output file.

Group1 Group2 Group4
Group3
Group5 Group6 Group8
Group7

Also I want to be able to query the group in another domain in the same forest also and I want the script to be able to find the groups in other domains as well.

If you use the Get-NestedDistributionGroupList you can use those outputs to create a custom object and export to a csv file. Below is an example code

$Results = @()
Foreach ($ADGroup in (Get-ADGroup -Filter *)){
$Object= [PSCustomObject]@{
GroupName=$adgroup.Name
NestedGroups=Get-NestedADGroupMember-Identity $ADGroup-ListGroups |Select-Object-ExpandProperty Name
}
$Results+=$Object
}
$Results
GroupName      NestedGroups
---------      ------------
Administrators {Enterprise Admins, Domain Admins}

$Results | Export-CSV -Path $env:Path\ADGroup_Nested.csv -NoTypeInformation

I got the below error when I tried the Get-NestedADGroupMember
Cannot compare “CN=Group1,DC=lab,DC=local” because it is not IComparable.
At line:116 char:13

  • If ($Group -gt 0){
  • CategoryInfo : InvalidOperation: (:slight_smile: [], RuntimeException
  • FullyQualifiedErrorId : NotIcomparable

Great news! That has already been taken care of in the script as well. :smiley:

Here is the logic from the script

$DomainList= (Get-ADForest).domains |Get-ADDomain|Select-Object DistinguishedName, DNSRoot |Sort-Object
foreach ($Domain in $DomainList){
   If ($Group[0].DistinguishedName.EndsWith($Domain.DistinguishedName)) {
      $GetADGroup=@{
         Identity=$Group[0].DistinguishedName
         Server=$Domain.DNSRoot}
   }
}

I receive a similar error, when I spoke with my Domain Admin, he advised me this is because I don’t have permissions to view the details of that security group. Remember if this gets you 99% of the way there, it’s better than being 0%. :slight_smile:

Thanks for the reply. It would be helpful if you explain the logic in your script? Also I want the output as below:

Group1 Group2 Group4
Group3
Group5 Group6 Group8
Group7

Any suggestion on the logic behind the script or any other way to obtain the information?

Are you asking p42p0wd3r or in general? The replies are under p42p0wd3r response.

[quote quote=172300]Are you asking p42p0wd3r or in general? The replies are under p42p0wd3r response.

[/quote]
Hi Jason,

Sorry for the confusion. I am asking you. Can you explain the logic of your script. What your script is doing steps by step?

Also I would like the output as below:

Group1 Group2 Group4
Group3
Group5 Group6 Group8
Group7
Can we do something like this, if yes how?

Check the groupmembership of group1 and put only its members which are groups in a variable says members.

Then again loop through groups in the members variable and check if it contains any groups as members.

They problems happens that type of members variable changes to string and it is no longer of the type ADPrincipal.

If this sounds confusing then please explain the logic of your script step by step, that will be really helpful.

Hey Tech,

Sorry for the delay in the reply, busy day today at work. :slight_smile: I’m happy to explain the script further be prepared for a bit of a long post.

If you read the verbose commands in the script, they will explain a bit of what is being done.

    Begin
    {
        Write-Verbose "ENTER - BEGIN BLOCK"
        Write-Verbose "Create User, Group and GroupList variables"
        [System.Collections.ArrayList]$User = @()
        [System.Collections.ArrayList]$Group = @()
        [System.Collections.ArrayList]$GroupList = @()
        Write-Verbose "Collect AD Domains"
        $DomainList = (Get-ADForest).domains | Get-ADDomain | Select-Object DistinguishedName, DNSRoot | Sort-Object
        Write-Verbose "EXIT - BEGIN BLOCK"
    }

I use an [ArrayList] instead of a [Array] for the User, Group and GroupList, because you cannot remove an object from an array because it is a type with a fixed size. An array can increase in size but never decrease. An [ArrayList] on the other hand does have the remove method in its type.

The $DomainList variable is used to obtain the DisinguishedName and DNSRoot properties for each of the domains in the organization. The properties are used later to determine what domain the group is located in. This is important for when the group i.e. Administrators has Enterprise Admins nested from the parent domain, refer to lines 129 - 135.

The process block is quite extensive and handles all of the logic, I’m going to break this down into two pieces.

The first part I will discuss is the foreach loop created on line 100. I use the Get-ADGroupMember command to identify the members of the parent ad group based on what was provided for the Identity parameter. With each of the members, the switch statement identifies if the ObjectClass of the member is Group or not.

Lines 101 - 122.

    Process
    {
        Write-Verbose "ENTER - PROCESS BLOCK"
        Write-Verbose "ENTER - Foreach - $Identity"
        Foreach ($Member in (Get-ADGroupMember -Identity $Identity)){
            switch ($Member.ObjectClass) {
                Group {
                    Write-Verbose "Nested AD Group Identified: $($Member.Name)"
                    If ($Member.DistinguishedName -notin $Group.DistinguishedName) {
                        $Group.Add($Member) | Out-Null
                        IF ($ListGroups) {
                            $GroupList.Add($Member) | Out-Null
                        }
                    }
                    Else {
                        Write-Verbose "$($Member.Name) is already identified, skipping to mitigate duplicate entry"
                    }
                }
                default{
                    If ($Member.DistinguishedName -notin $User.DistinguishedName) {
                        $User.Add($Member) | Out-Null
                    }
                    Else {
                        Write-Verbose "$($Member.Name) is already identified, skipping to mitigate duplicate entry"
                    }
                }
            }
        }
        Write-Verbose "EXIT - Foreach - $Identity"

To identify the Objectclass we can do this with an IF Statement or a Switch as seen below.

If ($member.ObjectClass -eq 'Group'){
   If (Member.DistinguishedName -NotIn$Group.DistinguishedName){
      $Group.Add($member) | Out-Null
   }
}
Else{
   If ($Member.DistinguishedName -notin $User.DistinguishedName){
      $User.Add($Member) | Out-Null
   }
}
The If statement is a viable option; however, the switch statement allows the same and increases the legibility of the code.

As each member is iterated through and the object is added either to the [ArrayList]$Group or [ArrayList]$User. If the -ListGroups parameter is used, groups are added to the [ArrayList]$GroupList.

The second piece of the Process block deals with the desired recursing of the nested groups to find additional nested groups. This is where the “MAGIC” happens. By using the Do-While loop, while [ArrayList] $Group -gt 0, resolves the issue of how many levels of nested groups can be drilled down. The answer is pretty much until the computer/server runs out of RAM.

As previously stated in a prior post, we identify the domain via a ForEach loop with $DomainList. The If statement checks the first group object distinguished name and uses the EndsWith() methods to check wether $Domain.DistinguishedName is at the end of $Group[0].DistinguishedName. If this is true, the $Domain.DNSRoot property is assigned to the Server parameter. DNSRoot is used because the -Server parameter for Get-ADGroupMember does not support DistinguishedName.

Lines 129 - 136

foreach ($Domain in $DomainList){
   If ($Group[0].DistinguishedName.EndsWith($Domain.DistinguishedName)) {
      $GetADGroup = @{
         Identity = $Group[0].DistinguishedName
         Server   = $Domain.DNSRoot
      }
   }
}

The $GetADGroup variable is a hashtable and I use a method in PowerShell known as Splatting.
<p style=“padding-left: 40px;”>“Splatting is a method of passing a collection of parameter values to a command as a unit. PowerShell associates each value in the collection with a command parameter. Splatted parameter values are stored in named splatting variables, which look like standard variables, but begin with an At symbol (@) instead of a dollar sign ($)” - Microsoft About Splatting</p>
I digress, back to the script. :slight_smile: During the Do-While loop, the goal is only to look at one group at a time, this is accomplished by only indexing the first entry in [ArrayList] $Group. This is accomplished with [0] to the right of $Group. By using the distinguished name, I remove almost all ambiguity from the query, but this doesn’t work if the group is in multiple domains i.e. Domain Admins. This issue is resolved by comparing the end of the distinguished name matches the distinguished name of the AD domain.

At this point, the logic is a rinse and repeat as previously done in lines 100 - 123 and continues to repeat while the $Group -gt 0. Once the group has been processed, it is removed from the [Arraylist]$Group with the remove method. The default behavior is to provide all of the users, but if you use -ListGroups instead this will bypass all Object Classes, not equal to Group.

Woohoo! Finally done with that explanation. That is a lot of information to digest.

Going back to the example of the export, why doesn’t this work for you? It will give you the Parent Group and all of the nested groups inside it. If you are looking for a Hierarchial Tree structure like below that will take some development.

Parent Group
<p style=“padding-left: 40px;”>–> ChildGroup1</p>
<p style=“padding-left: 80px;”>-> ChildGroup10
-> ChildGroup11</p>
<p style=“padding-left: 40px;”>–> ChildGroup2</p>
<p style=“padding-left: 80px;”>-> ChildGroup1
-> ChildGroup3</p>

[quote quote=172423]Going back to the example of the export, why doesn’t this work for you? It will give you the Parent Group and all of the nested groups inside it. If you are looking for a Hierarchial Tree structure like below that will take some development.

Parent Group

–> ChildGroup1

-> ChildGroup10

-> ChildGroup11

–> ChildGroup2

-> ChildGroup1

-> ChildGroup3

[/quote]
Hello Jason, Thanks for your explanation and reply. However, I want to extract the group membership three levels down. I don’t want the indentation. Just want the nesting to appear in different columns in a csv file. How can I achieve this?

Is this what you are trying to do?

ParentGroup NestedDGMembers1 NestedDGMembers2 NestedDGMembers3
ParentDG1 DG1

DG2

DG4 DG5

DG6

ParnetDG2 DG2 DG3 DG4

DG1

 

Yes that’s correct.

Techsavy,

That would take a bit of manipulation. Any reason you are trying to only drill down 3 nested layers?