Public/Schema/Get-ADSchemaRelatedClass.ps1
|
<#
.SYNOPSIS List every class an Active Directory class inherits from or is extended by. .DESCRIPTION Returns the class itself, every parent class up the subClassOf chain until 'top', and every auxiliary class attached along the way, including the system auxiliary classes. This is the set of classes that decide which attributes an object of that class can actually hold. An attribute rarely sits on the class you are looking at: on an extended schema it usually comes from a parent or from an auxiliary class added by a product such as Exchange. Use Get-ADSchemaClassAttribute when you want the attributes themselves and where each one comes from. This function answers the narrower question of which classes are involved. .PARAMETER ClassName LDAP display name of the class, such as 'user', 'group' or 'computer'. .PARAMETER Server Domain controller or domain to query. Defaults to the one selected by the ActiveDirectory module. .EXAMPLE Get-ADSchemaRelatedClass -ClassName 'user' Returns user, organizationalPerson, person, top and the auxiliary classes attached to any of them. .EXAMPLE Get-ADSchemaRelatedClass -ClassName 'user' | ForEach-Object { Get-ADSchemaClassAttribute -ClassName $_ } Walks the whole inheritance tree of the user class and lists the attributes of each class in it. .OUTPUTS System.String, one per class, without duplicates. .NOTES Version : 2.0 - August 2026. Migrated from the ActiveDirectory-Toolbox repository. The original was a modified version of https://www.neroblanco.co.uk/2017/09/get-possible-ad-attributes-user-group/ and never ran: it was renamed to Get-ADSchemaRelatedClass but kept calling itself under its former name, Get-RelatedClass, which existed nowhere. It failed as soon as a class had a parent, so always except on 'top'. Fixed here, along with the missing systemAuxiliaryClass, the duplicates and the absence of any protection against a loop between two auxiliary classes. Author : Bastien Perez - ITPro-Tips (https://itpro-tips.com) .LINK https://itpro-tips.com #> function Get-ADSchemaRelatedClass { [CmdletBinding()] [OutputType([string])] param ( [Parameter(Mandatory, Position = 0)] [string]$ClassName, [string]$Server ) $ErrorActionPreference = 'Stop' # Common parameters forwarded to every ActiveDirectory cmdlet call $adParameters = @{ ErrorAction = 'Stop' } if ($PSBoundParameters.ContainsKey('Server')) { $adParameters.Add('Server', $Server) } try { $schemaNamingContext = (Get-ADRootDSE @adParameters).schemaNamingContext } catch { Write-Error "Unable to read the RootDSE: $($_.Exception.Message)" return } # Ordered, so the class asked for comes first and its parents follow in inheritance order $relatedClasses = [System.Collections.Generic.List[string]]@() # A class is reachable through several paths, and two auxiliary classes can reference each other. Without this # the recursion never ends. $visitedClasses = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase) function Resolve-RelatedClass { param ( [string]$Name ) if ([string]::IsNullOrWhiteSpace($Name)) { return } if (-not $visitedClasses.Add($Name)) { return } $getADObjectParams = $adParameters.Clone() $getADObjectParams.SearchBase = $schemaNamingContext $getADObjectParams.LDAPFilter = "(&(objectClass=classSchema)(lDAPDisplayName=$Name))" $getADObjectParams.Properties = 'lDAPDisplayName', 'subClassOf', 'auxiliaryClass', 'systemAuxiliaryClass' # Wrapped in an array, an ADObject implements IDictionary so 'Count' on a single object would return its # number of properties rather than 1 $classesFound = @() try { $classesFound = @(Get-ADObject @getADObjectParams) } catch { Write-Warning "Unable to read the class '$Name': $($_.Exception.Message)" return } if ($classesFound.Count -eq 0) { Write-Warning "Class '$Name' not found in the schema" return } $classFound = $classesFound[0] $relatedClasses.Add($classFound.lDAPDisplayName) # 'top' is its own parent, which is where the chain stops if (($null -ne $classFound.subClassOf) -and ($classFound.subClassOf -ne $classFound.lDAPDisplayName)) { Resolve-RelatedClass -Name $classFound.subClassOf } foreach ($property in @('auxiliaryClass', 'systemAuxiliaryClass')) { if ($null -eq $classFound.$property) { continue } foreach ($auxiliaryClass in @($classFound.$property)) { Resolve-RelatedClass -Name $auxiliaryClass } } } Resolve-RelatedClass -Name $ClassName if ($relatedClasses.Count -eq 0) { Write-Warning "No class found for '$ClassName'" } return $relatedClasses } |