Classes/LS2Principal.ps1
|
class LS2Principal { [string]$distinguishedName [string]$objectSid [string]$sAMAccountName [string]$objectClass [string]$displayName [string]$NTAccountName [string]$userPrincipalName [string[]]$memberOf [int]$MemberCount [System.DirectoryServices.ActiveDirectorySecurity]$ObjectSecurity # Constructor from SearchResult LS2Principal( [System.DirectoryServices.SearchResult]$SearchResult, [string]$Server, [System.Security.Principal.SecurityIdentifier]$SidKey, [string]$NTAccountName ) { $this.distinguishedName = $SearchResult.Properties['distinguishedName'][0] # Create DirectoryEntry to get ObjectSecurity if (-not $Server) { throw "Server parameter is null or empty" } $objectPath = "LDAP://$Server/$($this.distinguishedName)" Write-Verbose "LS2Principal: Creating DirectoryEntry for $objectPath" $tempEntry = New-AuthenticatedDirectoryEntry -Path $objectPath # Handle case where DirectoryEntry creation fails if (-not $tempEntry) { throw "Failed to create DirectoryEntry for path: $objectPath" } # Set objectSid if ($SearchResult.Properties['objectSid'].Count -gt 0) { $this.objectSid = (New-Object System.Security.Principal.SecurityIdentifier($SearchResult.Properties['objectSid'][0], 0)).Value } # Set sAMAccountName if ($SearchResult.Properties['sAMAccountName'].Count -gt 0) { $this.sAMAccountName = $SearchResult.Properties['sAMAccountName'][0] } # Set objectClass (get the most specific class) if ($SearchResult.Properties['objectClass'].Count -gt 0) { $classes = @($SearchResult.Properties['objectClass']) $this.objectClass = $classes[$classes.Count - 1] } # Set displayName if ($SearchResult.Properties['displayName'].Count -gt 0) { $this.displayName = $SearchResult.Properties['displayName'][0] } # Set NTAccountName - use provided or build from sAMAccountName + domain if ($NTAccountName) { $this.NTAccountName = $NTAccountName } elseif ($this.sAMAccountName) { # Build NTAccount name from sAMAccountName and domain NetBIOS name $domainDN = $this.distinguishedName -replace '^.*?,(?=DC=)', '' if ($script:DomainStore -and $script:DomainStore.ContainsKey($domainDN)) { $domainNetBiosName = $script:DomainStore[$domainDN].nETBIOSName.ToUpper() $this.NTAccountName = "$domainNetBiosName\$($this.sAMAccountName)" } else { # Fallback: extract first DC component from DN if ($domainDN -match 'DC=([^,]+)') { $domainNetBiosName = $Matches[1].ToUpper() $this.NTAccountName = "$domainNetBiosName\$($this.sAMAccountName)" } } } # Set userPrincipalName if ($SearchResult.Properties['userPrincipalName'].Count -gt 0) { $this.userPrincipalName = $SearchResult.Properties['userPrincipalName'][0] } # Set memberOf if ($SearchResult.Properties['memberOf'].Count -gt 0) { $this.memberOf = @($SearchResult.Properties['memberOf']) } else { $this.memberOf = @() } # Set ObjectSecurity (may fail for some objects in PS5.1) try { $this.ObjectSecurity = $tempEntry.ObjectSecurity } catch { Write-Verbose "Could not retrieve ObjectSecurity for '$($this.distinguishedName)': $_" $this.ObjectSecurity = $null } # Dispose only if not null if ($tempEntry) { $tempEntry.Dispose() } # Add nTSecurityDescriptor as an alias for ObjectSecurity $this | Add-Member -MemberType ScriptProperty -Name nTSecurityDescriptor -Value { return $this.ObjectSecurity } } # Constructor for well-known principals that don't exist in AD # Used for BUILTIN groups (S-1-5-32-*) and other machine-local SIDs # Note: Some well-known SIDs like S-1-5-11 exist as foreignSecurityPrincipal objects # and will use the main constructor instead LS2Principal( [string]$ObjectSid, [string]$NTAccountName ) { $this.distinguishedName = $null $this.objectSid = $ObjectSid $this.sAMAccountName = $null $this.objectClass = 'wellKnownPrincipal' $this.displayName = $null $this.NTAccountName = $NTAccountName $this.userPrincipalName = $null $this.memberOf = @() $this.ObjectSecurity = $null } } |