Created
September 2, 2020 16:57
-
-
Save SQLJana/5738429c394990f6c1ca7f1f07487263 to your computer and use it in GitHub Desktop.
Examples that show how to do recursion in PowerShell to find loops in a hierarchy, especially with Active Directory style Groups/Users and Nested Groups.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Author: Jana Sattainathan - https://sqljana.wordpress.com - September 1, 2020 | |
#Gets all the loops (ie., at some point members of groups are groups already traversed in hierarchy) | |
function Get-ADGroupLoops | |
{ | |
param ( | |
[Parameter()] | |
[object]$Identity, | |
[Parameter()] | |
[System.Collections.Specialized.OrderedDictionary][ref] $TempWorkHashTable = (New-Object System.Collections.Specialized.OrderedDictionary), | |
[Parameter()] | |
[System.Collections.Specialized.OrderedDictionary][ref] $GroupsReturnHashTable = (New-Object System.Collections.Specialized.OrderedDictionary), | |
[Parameter()] | |
[System.Collections.Specialized.OrderedDictionary][ref] $MembersReturnHashTable = (New-Object System.Collections.Specialized.OrderedDictionary), | |
[Parameter()] | |
[System.Collections.Specialized.OrderedDictionary][ref] $LoopsReturnHashTable = (New-Object System.Collections.Specialized.OrderedDictionary) | |
) | |
[bool] $loopBeginning = $false | |
[string] $fullHierarchyString = "" | |
[string] $loopString = "" | |
$adObject = Get-ADObject -Identity $Identity | |
#If this is group and not an individual | |
if ($adObject.ObjectClass -eq 'group') | |
{ | |
Write-Verbose "Working on: $Identity" | |
if (!$GroupsReturnHashTable.Contains($adObject.DistinguishedName)) | |
{ | |
$GroupsReturnHashTable.Add($adObject.DistinguishedName, $adObject) | |
} | |
#Get the members of the group | |
$members = Get-ADGroupMember -Identity $Identity | |
#If this we have not seen this group before | |
if (!$TempWorkHashTable.Contains($adObject.DistinguishedName)) | |
{ | |
$TempWorkHashTable.Add($adObject.DistinguishedName, $adObject) | Out-Null | |
$members | foreach { | |
#This is the recursive call to itself | |
$members = Get-ADGroupLoops ` | |
-Identity (Get-ADObject -Identity $_) ` | |
-TempWorkHashTable ([ref]$TempWorkHashTable) ` | |
-GroupsReturnHashTable ([ref]$GroupsReturnHashTable) ` | |
-MembersReturnHashTable ([ref]$MembersReturnHashTable) ` | |
-LoopsReturnHashTable ([ref]$LoopsReturnHashTable) | |
} | |
} | |
else | |
{ | |
#We have already seen this group before. That is the starting point of the loop | |
# and we have to print and remove all elements in the Ordered dictionary from that element that form the loop | |
[HashTable] $keysToRemove = @{} | |
foreach($key in $TempWorkHashTable.Keys) | |
{ | |
if ($key -eq $adObject.DistinguishedName) | |
{ | |
$loopBeginning = $true | |
} | |
$fullHierarchyString = $fullHierarchyString + "->" + $TempWorkHashTable[$key].Name | |
if ($loopBeginning -eq $true) | |
{ | |
$keysToRemove.Add($key, $key) | |
$loopString = $loopString + "->" + $TempWorkHashTable[$key].Name | |
} | |
} | |
#The full hierarchy and loop | |
$fullHierarchyString = $fullHierarchyString + "->" + $adObject.Name | |
$loopString = $loopString + "->" + $adObject.Name | |
foreach($key in $keysToRemove.Keys) | |
{ | |
$TempWorkHashTable.Remove($key) | |
} | |
#DN,oGroup | |
$TempWorkHashTable.Add($adObject.DistinguishedName, $adObject) | |
Write-Verbose "Hierarchy: $fullHierarchyString" | |
Write-Verbose "Loop: $loopString" | |
$LoopsReturnHashTable.Add($loopString, $fullHierarchyString) | Out-Null | |
$fullHierarchyString = "" | |
$loopString = "" | |
} | |
} | |
else | |
{ | |
## It is not a group..just a regular user | |
if (!$MembersReturnHashTable.Contains($adObject.DistinguishedName)) | |
{ | |
$MembersReturnHashTable.Add($adObject.DistinguishedName, $adObject) | |
} | |
} | |
} | |
#---------------------------------------- | |
#Sample code to test the function above | |
#---------------------------------------- | |
$startGroup = Get-ADGroup 'YOUR-TOP-LEVEL-AD-GROUP_NAME' | |
[System.Collections.Specialized.OrderedDictionary] $tempWorkHashTable = (New-Object System.Collections.Specialized.OrderedDictionary) | |
[System.Collections.Specialized.OrderedDictionary] $groupsReturnHashTable = (New-Object System.Collections.Specialized.OrderedDictionary) | |
[System.Collections.Specialized.OrderedDictionary] $loopsReturnHashTable = (New-Object System.Collections.Specialized.OrderedDictionary) | |
[System.Collections.Specialized.OrderedDictionary] $membersReturnHashTable = (New-Object System.Collections.Specialized.OrderedDictionary) | |
Get-ADGroupLoops ` | |
-Identity $startGroup ` | |
-TempWorkHashTable ([ref]$tempWorkHashTable) ` | |
-GroupsReturnHashTable ([ref]$groupsReturnHashTable) ` | |
-LoopsReturnHashTable ([ref]$loopsReturnHashTable) ` | |
-MembersReturnHashTable ([ref]$membersReturnHashTable) ` | |
-Verbose | |
"----Groups-------" | |
$groupsReturnHashTable | |
"----Members-------" | |
$membersReturnHashTable | |
"----Loops-------" | |
$loopsReturnHashTable |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Author: Jana Sattainathan - https://sqljana.wordpress.com - September 1, 2020 | |
#Returns all the groups | |
function Get-Group | |
{ | |
#Notice the loops | |
# Group1 > Group3 > Group1 | |
# Group1 > Group8 > Group5 > Group15 > Group1. | |
@{ | |
"Group1" = "User1,User2,Group2,Group8"; | |
"Group2" = "User1,User2,User3,Group3" | |
"Group3" = "User3,User8,Group1" | |
"Group4" = "Group8,User1" | |
"Group5" = "User10,Group15" | |
"Group6" = "User10, User11" | |
"Group7" = "User11" | |
"Group8" = "Group5" | |
"Group15" = "Group1" | |
} | |
} | |
#Returns all the members of a give group as an array | |
function Get-GroupMember | |
{ | |
param ( | |
[Parameter()] | |
[string]$Identity = 'Group1' | |
) | |
$groups = Get-Group | |
if ($groups.ContainsKey($Identity) -eq $true) | |
{ | |
$groups[$Identity].Split(",") | |
} | |
else | |
{ | |
@() | |
} | |
} | |
#Returns true if if parameter is a Group, else false | |
function Test-Group | |
{ | |
param ( | |
[Parameter()] | |
[string]$Identity = 'Group1' | |
) | |
$groups = Get-Group | |
$groups.ContainsKey($Identity) | |
} | |
#Gets all the loops (ie., at some point members of groups are groups already traversed in hierarchy) | |
function Get-GroupLoops | |
{ | |
param ( | |
[Parameter()] | |
[string]$Identity = 'Group1', | |
[Parameter()] | |
[System.Collections.Specialized.OrderedDictionary][ref] $TempWorkHashTable = (New-Object System.Collections.Specialized.OrderedDictionary), | |
[Parameter()] | |
[System.Collections.Specialized.OrderedDictionary][ref] $GroupsReturnHashTable = (New-Object System.Collections.Specialized.OrderedDictionary), | |
[Parameter()] | |
[System.Collections.Specialized.OrderedDictionary][ref] $MembersReturnHashTable = (New-Object System.Collections.Specialized.OrderedDictionary), | |
[Parameter()] | |
[System.Collections.Specialized.OrderedDictionary][ref] $LoopsReturnHashTable = (New-Object System.Collections.Specialized.OrderedDictionary) | |
) | |
[bool] $loopBeginning = $false | |
[string] $fullHierarchyString = "" | |
[string] $loopString = "" | |
#If this is group and not an individual | |
if (Test-Group -Identity $Identity) | |
{ | |
Write-Verbose "Working on: $Identity" | |
if (!$GroupsReturnHashTable.Contains($Identity)) | |
{ | |
$GroupsReturnHashTable.Add($Identity, $Identity) | |
} | |
#Get the members of the group | |
$members = Get-GroupMember -Identity $Identity | |
#If this we have not seen this group before | |
if (!$TempWorkHashTable.Contains($Identity)) | |
{ | |
$TempWorkHashTable.Add($Identity, $Identity) | Out-Null | |
$members | foreach { | |
#This is the recursive call to itself | |
$members = Get-GroupLoops ` | |
-Identity $_ ` | |
-TempWorkHashTable ([ref]$TempWorkHashTable) ` | |
-GroupsReturnHashTable ([ref]$GroupsReturnHashTable) ` | |
-MembersReturnHashTable ([ref]$MembersReturnHashTable) ` | |
-LoopsReturnHashTable ([ref]$LoopsReturnHashTable) | |
} | |
} | |
else | |
{ | |
#We have already seen this group before. That is the starting point of the loop | |
# and we have to print and remove all elements in the Ordered dictionary from that element that form the loop | |
[HashTable] $keysToRemove = @{} | |
foreach($key in $TempWorkHashTable.Keys) | |
{ | |
if ($key -eq $Identity) | |
{ | |
$loopBeginning = $true | |
} | |
$fullHierarchyString = $fullHierarchyString + "->" + $key | |
if ($loopBeginning -eq $true) | |
{ | |
$keysToRemove.Add($key, $key) | |
$loopString = $loopString + "->" + $key | |
} | |
} | |
#The full hierarchy and loop | |
$fullHierarchyString = $fullHierarchyString + "->" + $Identity | |
$loopString = $loopString + "->" + $Identity | |
foreach($key in $keysToRemove.Keys) | |
{ | |
$TempWorkHashTable.Remove($key) | |
} | |
#DN,oGroup | |
$TempWorkHashTable.Add($Identity, $Identity) | |
Write-Verbose "Hierarchy: $fullHierarchyString" | |
Write-Verbose "Loop: $loopString" | |
$LoopsReturnHashTable.Add($loopString, $fullHierarchyString) | Out-Null | |
$fullHierarchyString = "" | |
$loopString = "" | |
} | |
} | |
else | |
{ | |
## It is not a group..just a regular user | |
if (!$MembersReturnHashTable.Contains($Identity)) | |
{ | |
$MembersReturnHashTable.Add($Identity, $Identity) | |
} | |
} | |
} | |
#---------------------------------------- | |
#Sample code to test the functions above | |
#---------------------------------------- | |
$startGroup = 'Group1' | |
[System.Collections.Specialized.OrderedDictionary] $tempWorkHashTable = (New-Object System.Collections.Specialized.OrderedDictionary) | |
[System.Collections.Specialized.OrderedDictionary] $groupsReturnHashTable = (New-Object System.Collections.Specialized.OrderedDictionary) | |
[System.Collections.Specialized.OrderedDictionary] $loopsReturnHashTable = (New-Object System.Collections.Specialized.OrderedDictionary) | |
[System.Collections.Specialized.OrderedDictionary] $membersReturnHashTable = (New-Object System.Collections.Specialized.OrderedDictionary) | |
Get-GroupLoops ` | |
-Identity $startGroup ` | |
-TempWorkHashTable ([ref]$tempWorkHashTable) ` | |
-GroupsReturnHashTable ([ref]$groupsReturnHashTable) ` | |
-LoopsReturnHashTable ([ref]$loopsReturnHashTable) ` | |
-MembersReturnHashTable ([ref]$membersReturnHashTable) ` | |
-Verbose | |
"----Groups-------" | |
$groupsReturnHashTable | |
"----Members-------" | |
$membersReturnHashTable | |
"----Loops-------" | |
$loopsReturnHashTable |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment