Unused/Convert-SidToDirectoryEntry.ps1
|
function Convert-SidToDirectoryEntry { <# .SYNOPSIS Converts a SecurityIdentifier (SID) to a DirectoryEntry. .DESCRIPTION Takes a System.Security.Principal.SecurityIdentifier object and performs an LDAP query to return the corresponding DirectoryEntry object. On domain-joined computers, uses the built-in Translate() method. On non-domain joined computers, performs an LDAP query using provided credentials to resolve the SID to an NTAccount. Supports forest-wide searches using Global Catalog when RootDSE is provided, enabling resolution of principals from child domains and trusted domains within the forest. .PARAMETER SecurityIdentifier The SecurityIdentifier object to convert. Typically from SID strings or ACL entries. .PARAMETER Credential PSCredential for authenticating to Active Directory. Required for LDAP queries. .PARAMETER RootDSE A DirectoryEntry object for the RootDSE. Used to determine the domain context for LDAP queries. If not specified, attempts to query without specific domain context. .INPUTS System.Security.Principal.SecurityIdentifier Accepts SecurityIdentifier objects via the pipeline. .OUTPUTS System.DirectoryServices.DirectoryEntry Returns the DirectoryEntry object for the principal with the specified SID. .EXAMPLE $sid = [System.Security.Principal.SecurityIdentifier]::new('S-1-5-21-...') $sid | Convert-SidToDirectoryEntry -Credential $cred -RootDSE $rootDSE Converts a SID to DirectoryEntry using credentials and RootDSE. .EXAMPLE $ace.IdentityReference | Convert-SidToDirectoryEntry -Credential $cred -RootDSE $rootDSE Converts SID IdentityReferences from an ACL to DirectoryEntry objects. .NOTES Requires Credential and RootDSE parameters for LDAP queries. Uses Global Catalog for forest-wide searches to support child domain resolution. #> [CmdletBinding()] [OutputType([System.DirectoryServices.DirectoryEntry])] param( [Parameter(Mandatory, ValueFromPipeline)] [System.Security.Principal.SecurityIdentifier] $SecurityIdentifier, [Parameter(Mandatory)] [System.Management.Automation.PSCredential] $Credential, [Parameter(Mandatory)] [System.DirectoryServices.DirectoryEntry] $RootDSE ) begin { # Cache NetBIOS name lookups to avoid repeated queries $script:netBiosCache = @{} # Pre-load all NetBIOS names if we have credentials if ($Credential -and $RootDSE) { try { $configNC = $RootDSE.configurationNamingContext.Value if ($RootDSE.Path -match 'LDAP://([^/]+)') { $server = $Matches[1] $partitionsPath = "LDAP://$server/CN=Partitions,$configNC" $partitionsEntry = New-Object System.DirectoryServices.DirectoryEntry( $partitionsPath, $Credential.UserName, $Credential.GetNetworkCredential().Password ) $partitionsSearcher = New-Object System.DirectoryServices.DirectorySearcher $partitionsSearcher.SearchRoot = $partitionsEntry $partitionsSearcher.Filter = "(objectClass=crossRef)" $partitionsSearcher.PropertiesToLoad.AddRange(@('nCName', 'nETBIOSName')) | Out-Null $partitionsSearcher.SearchScope = [System.DirectoryServices.SearchScope]::OneLevel $allPartitions = $partitionsSearcher.FindAll() foreach ($partition in $allPartitions) { if ($partition.Properties['nCName'].Count -gt 0 -and $partition.Properties['nETBIOSName'].Count -gt 0) { $domainDN = $partition.Properties['nCName'][0] $netBiosName = $partition.Properties['nETBIOSName'][0] $script:netBiosCache[$domainDN] = $netBiosName Write-Verbose "Pre-cached NetBIOS name '$netBiosName' for '$domainDN'" } } $allPartitions.Dispose() $partitionsSearcher.Dispose() $partitionsEntry.Dispose() } } catch { Write-Verbose "Could not pre-load NetBIOS names: $_" } } } process { try { # Get the SID string $sidString = $SecurityIdentifier.Value # Extract server from RootDSE if ($RootDSE) { $rootDomainDN = $RootDSE.rootDomainNamingContext.Value if ($RootDSE.Path -match 'LDAP://([^/]+)') { $server = $Matches[1] } else { Write-Warning "Could not extract server from RootDSE path." return $null } } else { Write-Warning "RootDSE parameter required for SID resolution." return $null } # First try Global Catalog search for forest-wide lookup if ($rootDomainDN) { Write-Verbose "Attempting Global Catalog search for SID '$sidString'" $gcSearcher = New-Object System.DirectoryServices.DirectorySearcher $gcPath = "GC://$server/$rootDomainDN" $gcEntry = New-Object System.DirectoryServices.DirectoryEntry( $gcPath, $Credential.UserName, $Credential.GetNetworkCredential().Password ) $gcSearcher.SearchRoot = $gcEntry $gcSearcher.Filter = "(objectSid=$sidString)" $gcSearcher.PropertiesToLoad.AddRange(@('distinguishedName')) | Out-Null $gcSearcher.SearchScope = [System.DirectoryServices.SearchScope]::Subtree $gcSearcher.PageSize = 1000 try { $gcResult = $gcSearcher.FindOne() if ($gcResult) { $distinguishedName = $gcResult.Properties['distinguishedName'][0] Write-Verbose "Found SID in GC at: $distinguishedName" # Return DirectoryEntry for the found object $objectPath = "LDAP://$server/$distinguishedName" $objectEntry = New-Object System.DirectoryServices.DirectoryEntry( $objectPath, $Credential.UserName, $Credential.GetNetworkCredential().Password ) Write-Verbose "Resolved SID '$sidString' to '$distinguishedName' via Global Catalog" return $objectEntry } } catch { Write-Verbose "Global Catalog search failed, falling back to domain search: $_" } finally { if ($gcSearcher) { $gcSearcher.Dispose() } if ($gcEntry) { $gcEntry.Dispose() } } } # Fallback to direct LDAP search in default domain Write-Verbose "Attempting direct LDAP search for SID '$sidString'" $domainDN = if ($RootDSE) { $RootDSE.defaultNamingContext.Value } else { $null } if (-not $domainDN) { Write-Warning "Could not determine domain DN for SID resolution." return $null } # Create LDAP searcher with credentials $searcher = New-Object System.DirectoryServices.DirectorySearcher $ldapPath = "LDAP://$server/$domainDN" $directoryEntry = New-Object System.DirectoryServices.DirectoryEntry( $ldapPath, $Credential.UserName, $Credential.GetNetworkCredential().Password ) $searcher.SearchRoot = $directoryEntry $searcher.Filter = "(objectSid=$sidString)" $searcher.PropertiesToLoad.AddRange(@('distinguishedName')) | Out-Null $searcher.SearchScope = [System.DirectoryServices.SearchScope]::Subtree $searcher.PageSize = 1000 $result = $searcher.FindOne() if ($result) { $distinguishedName = $result.Properties['distinguishedName'][0] # Return DirectoryEntry for the found object $objectPath = "LDAP://$server/$distinguishedName" $objectEntry = New-Object System.DirectoryServices.DirectoryEntry( $objectPath, $Credential.UserName, $Credential.GetNetworkCredential().Password ) Write-Verbose "Resolved SID '$sidString' to '$distinguishedName' via LDAP" return $objectEntry } else { Write-Warning "Could not find SID '$sidString' in Active Directory via LDAP query." return $null } } catch { Write-Warning "LDAP query failed for SID '$sidString': $_" return $null } finally { if ($searcher) { $searcher.Dispose() } if ($directoryEntry) { $directoryEntry.Dispose() } } } } |