Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add multiple domain support #3

Merged
merged 12 commits into from
Aug 14, 2023
283 changes: 156 additions & 127 deletions Get-Hierarchy.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -43,214 +43,243 @@ function Get-Hierarchy {
[cmdletbinding()]
[alias('gh')]
param(
[parameter(Mandatory,ValueFromPipeline)]
[parameter(Mandatory, ValueFromPipeline)]
[string]$Name,
[validateset('MemberOf','Member')]
[string]$Server,
[validateset('MemberOf', 'Member')]
[string]$RecursionProperty = 'Member'
)

begin {
$txtInfo = (Get-Culture).TextInfo
function GetObject {
param(
[parameter(Mandatory)]
[string]$Name,
[string]$Server
)

try {
if ($PSBoundParameters.ContainsKey('Server')) {
$Entry = [adsi] "LDAP://$Server"
}
}
catch {
$PSCmdlet.ThrowTerminatingError($_)
}

$Searcher = [adsisearcher]::new(
$Entry,
"(|(name=$name)(samAccountName=$Name)(distinguishedName=$Name))")

$Object = $Searcher.FindOne()

if (-not $Object) {
$PSCmdlet.ThrowTerminatingError(
[System.Management.Automation.ErrorRecord]::new(
[System.ArgumentException] ("Cannot find an object: '{0}' under: '{1}'." -f $Name, $Searcher.SearchRoot.distinguishedName.ToString()),
'ObjectNotFound',
[System.Management.Automation.ErrorCategory]::ObjectNotFound,
$Name))
}

$Object
}

function RecHierarchy {
param(
[parameter(mandatory)]
[String]$Name,
[String]$DistinguishedName,
[Int]$Recursion = 0,
[validateset('MemberOf','Member')]
[validateset('MemberOf', 'Member')]
[string]$RecursionProperty = 'Member'
)

$ErrorMessage = {
"`nGroup Name: {0}`nMember: {1}`nError Message: $_`n" -f $Group.Name, $Member.Split(',')[0].Replace('CN=','')
"`nGroup Name: {0}`nMember: {1}`nError Message: $_`n" -f $Group.Name, $Member.Split(',')[0].Replace('CN=', '')
}

$queryObjectSplat = @{
DistinguishedName = $DistinguishedName
RecursionProperty = $RecursionProperty
}

$thisObject = QueryObject -Name $Name -RecursionProperty $RecursionProperty
$thisObject = QueryObject @queryObjectSplat

$Hierarchy = $(
foreach($object in $thisObject.Property) {
try {
QueryObject -Name $object
}
catch {
Write-Warning (& $errorMessage)
$Hierarchy = & {
foreach ($object in $thisObject.Property) {
try {
QueryObject -DistinguishedName $object
}
catch {
Write-Warning (& $errorMessage)
}
}
}) | Sort-Object -Descending ObjectClass
} | Sort-Object -Descending ObjectClass

$thisInput = if($Index[0].Index) {
$thisInput = if ($Index[0].Index) {
$Index[0].Index
}
else {
$thisObject.Name
}

$script:Index.Add(
[pscustomobject]@{
InputParameter = $thisInput
Index = $thisObject.Name
Class = $txtInfo.ToTitleCase($thisObject.ObjectClass)
Recursion = $Recursion
Hierarchy = Indent -String $thisObject.Name -Indent $Recursion
}) > $null
$Index.Add(
[pscustomobject]@{
InputParameter = $thisInput
Index = $thisObject.Name
Class = $thisObject.ObjectClass
Recursion = $Recursion
Domain = $thisObject.Domain
Hierarchy = [Tree]::Indent($thisObject.Name, $Recursion)
})

$Recursion++

foreach($object in $Hierarchy) {
$class = $txtInfo.ToTitleCase($object.ObjectClass)
foreach ($object in $Hierarchy) {
$class = $object.ObjectClass

if($object.Name -in $Index.Index) {
if (($object.Name -in $Index.Index -and ($object.Domain -in $Index.Domain))) {
[int]$i = $Recursion
do {
$i--
$z = $index.where({$_.Recursion -eq $i}).Index | Select-Object -Last 1
if($object.Name -eq $z) {
$z = $index.where({ $_.Recursion -eq $i }).Index | Select-Object -Last 1
if ($object.Name -eq $z) {
$layer = $true
}
} until($i -eq 0 -or $layer -eq $true)

if($layer) {
$string = switch($object.ObjectClass) {
'User' {
if ($layer) {
$string = switch ($object.ObjectClass) {
default {
$object.Name
}
'Group' {
"{0} <=> Circular Nested Group" -f $object.Name
'{0} <=> Circular Nested Group' -f $object.Name
}
}
}
else {
$string = switch($object.ObjectClass) {
'User' {
$string = switch ($object.ObjectClass) {
default {
$object.Name
}
'Group' {
"{0} <=> Skipping // Processed" -f $object.Name
'{0} <=> Skipping // Processed' -f $object.Name
}
}
}

$script:Index.Add(
[pscustomobject]@{
InputParameter = $thisInput
Index = $object.Name
Class = $class
Recursion = $Recursion
Hierarchy = Indent -String $string -Indent $Recursion
}) > $null
$Index.Add(
[pscustomobject]@{
InputParameter = $thisInput
Index = $object.Name
Class = $class
Recursion = $Recursion
Domain = $object.Domain
Hierarchy = [Tree]::Indent($string, $Recursion)
})
}
else {
RecHierarchy -Name $object.Name -Recursion $Recursion -RecursionProperty $RecursionProperty
$recHierarchySplat = @{
DistinguishedName = $object.DistinguishedName
Recursion = $Recursion
RecursionProperty = $RecursionProperty
}

RecHierarchy @recHierarchySplat
}
}
}

function QueryObject {
[cmdletbinding()]
param(
[string]$Name,
[validateset('MemberOf','Member')]
[string]$DistinguishedName,
[validateset('MemberOf', 'Member')]
[string]$RecursionProperty
)

$filter = "(|(distinguishedname=$Name)(samaccountname=$Name)(name=$Name))"
$Object = [adsi] "LDAP://$DistinguishedName"

switch($Name) {
'Administrators' {
if($RecursionProperty) {
$object = Get-ADGroup -LDAPFilter $filter -Properties $RecursionProperty
}
else {
$object = Get-ADGroup -LDAPFilter $filter
}
}

Default {
if($RecursionProperty) {
$object = Get-ADObject -LDAPFilter $filter -Properties $RecursionProperty
}
else {
$object = Get-ADObject -LDAPFilter $filter
}
}
$Properties = [ordered]@{
Name = $Object.name.ToString()
UserPrincipalName = $Object.userPrincipalName.ToString()
DistinguishedName = $Object.distinguishedName.ToString()
Domain = ($Object.distinguishedName.ToString() -isplit ",DC=")[1].ToUpper()
ObjectClass = $Object.SchemaClassName.ToString()
}

if(-not $object) {
$ErrorMessage = "Cannot find an object with identity: '{0}' under: '{1}'." -f $Name,$((Get-ADRootDSE).defaultNamingContext)
throw $ErrorMessage
if ($RecursionProperty) {
$Properties['Property'] = $Object.$RecursionProperty
}

if($object.count -gt 1) {
throw "Multiple objects with Name '$Name' were found. Use DistinguishedName for unique output."
}
return [pscustomobject] $Properties
}

if($RecursionProperty) {
[PScustomObject]@{
Name = $object.Name
UserPrincipalName = $props.UserPrincipalName
ObjectClass = $txtInfo.ToTitleCase($object.ObjectClass)
Property = $object.$RecursionProperty
class Tree {
hidden static [regex] $s_re = [regex]::new(
'└|\S',
[System.Text.RegularExpressions.RegexOptions]::Compiled)

static [string] Indent([string] $inputString, [int] $indentation) {
if ($indentation -eq 0) {
return $inputString
}
return
}

[PScustomObject]@{
Name = $object.Name
UserPrincipalName = $props.UserPrincipalName
ObjectClass = $txtInfo.ToTitleCase($object.ObjectClass)
return [string]::new(' ', (4 * $indentation) - 4) + '└── ' + $inputString
}
}

function Draw-Hierarchy {
param(
[System.Collections.ArrayList]$Array
)
static [object[]] ConvertToTree([object[]] $inputObject) {
for ($i = 0; $i -lt $inputObject.Length; $i++) {
$index = $inputObject[$i].Hierarchy.IndexOf('└')

$Array.Reverse()

for($i=0;$i -lt $Array.Count;$i++) {
if($Array[$i+1] -and $Array[$i].Hierarchy.IndexOf('|_') -lt $Array[$i+1].Hierarchy.IndexOf('|_')) {
$z = $i+1
$ind = $Array[$i].Hierarchy.IndexOf('|_')
while($Array[$z].Hierarchy[$ind] -ne '|') {
$string = ($Array[$z].Hierarchy).ToCharArray()
$string[$ind] = '|'
$string = $string -join ''
$Array[$z].Hierarchy = $string
$z++
if($Array[$z].Hierarchy[$ind] -eq '|') {
break
}
if ($index -lt 0) {
continue
}
}
}

$Array.Reverse()
return $Array
}

function Indent {
param(
[String]$String,
[Int]$Indent
)
$z = $i - 1

$x='_';$y='|';$z=' '
while (-not [Tree]::s_re.IsMatch($inputObject[$z].Hierarchy[$index].ToString())) {
$replace = $inputObject[$z].Hierarchy.ToCharArray()
$replace[$index] = '│'
$inputObject[$z].Hierarchy = [string]::new($replace)
$z--
}

switch($Indent) {
{$_ -eq 0}{
return $String
}
{$_ -gt 0}{
return "$($z*$_)$y$x $string"
if ($inputObject[$z].Hierarchy[$index] -eq '└') {
$replace = $inputObject[$z].Hierarchy.ToCharArray()
$replace[$index] = '├'
$inputObject[$z].Hierarchy = [string]::new($replace)
}
}

return $inputObject
}
}
}

process {
$script:Index = New-Object System.Collections.ArrayList
RecHierarchy -Name $Name -RecursionProperty $RecursionProperty
Draw-Hierarchy -Array $Index
Remove-Variable Index -Scope Global -Force -ErrorAction SilentlyContinue
try {
$getObjectSplat = @{
Name = $Name
}

if ($PSBoundParameters.ContainsKey('Server')) {
$getObjectSplat['Server'] = $Server
}
$Index = [System.Collections.Generic.List[object]]::new()
$Object = GetObject @getObjectSplat

$recHierarchySplat = @{
DistinguishedName = $Object.Properties.distinguishedname
RecursionProperty = $RecursionProperty
}

RecHierarchy @recHierarchySplat
[Tree]::ConvertToTree($Index.ToArray())
}
catch {
$PSCmdlet.ThrowTerminatingError($_)
}
}
}
}
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@
### DESCRIPTION
Gets Group's and User's membership or Group parentship and draws it's hierarchy.
Helps identifying <b>Circular Nested Groups</b>.

### PARAMETER

| Parameter Name | Description |
| --- | --- |
| `-Name <string>` | __Name, SamAccountName or DistinguishedName__ of an AD Object of the class __User__ or __Group__ |
| `[-RecursionProperty <string>]` | Set AD attribute for recursion. __Valid Values__: `Member` / `MemberOf`. __Default Value__: `Member` |
| `[-Server <string>]` | __FQDN or NetBIOS name__ for the instance to connect to. |
| `[<CommonParameters>]` | See [`about_CommonParameters`](https://go.microsoft.com/fwlink/?LinkID=113216) |

### OUTPUTS
Expand All @@ -24,7 +25,6 @@ Helps identifying <b>Circular Nested Groups</b>.
### REQUIREMENTS

- PowerShell v5.1
- [ActiveDirectory Module](https://docs.microsoft.com/en-us/powershell/module/activedirectory/?view=windowsserver2022-ps)</li>


### USAGE EXAMPLES
Expand Down