Skip to content

Commit

Permalink
Merge pull request #3 from PoshAJ/main
Browse files Browse the repository at this point in the history
looking good. thank you for your contribution @PoshAJ
  • Loading branch information
santisq authored Aug 14, 2023
2 parents 5f40862 + 9c52b5e commit 5a97cc6
Show file tree
Hide file tree
Showing 2 changed files with 158 additions and 129 deletions.
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

0 comments on commit 5a97cc6

Please sign in to comment.