Skip to content

Instantly share code, notes, and snippets.

@SQLJana
Created September 2, 2020 16:57
Show Gist options
  • Save SQLJana/5738429c394990f6c1ca7f1f07487263 to your computer and use it in GitHub Desktop.
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.
# 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
# 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