Private/Resolve-Principal.ps1
|
function Resolve-Principal { <# .SYNOPSIS Resolves a DirectoryEntry object for a security principal using UPN, NetBIOS name, or SID. .DESCRIPTION This function accepts a User Principal Name (UPN), NetBIOS name (DOMAIN\username), or Security Identifier (SID) and returns the corresponding DirectoryEntry object from Active Directory. The function automatically determines the input type and performs the appropriate lookup to resolve the principal. The function supports both user accounts and computer accounts, automatically searching both object types when performing the lookup. When provided with any one of these identifiers, the function can derive the other values and return a complete DirectoryEntry object representing the security principal. .PARAMETER Identity The identity of the principal to retrieve. Can be specified as: - UPN: user@domain.com - NetBIOS: DOMAIN\username or username - SID: S-1-5-21-domain-rid or SecurityIdentifier object .PARAMETER Server The domain controller to query. If not specified, uses the default domain controller. .INPUTS System.String or System.Security.Principal.SecurityIdentifier Identity of the principal to retrieve. .OUTPUTS System.DirectoryServices.DirectoryEntry DirectoryEntry object representing the resolved security principal. .EXAMPLE # Resolve using UPN $Principal = Resolve-Principal -Identity "administrator@domain.com" .EXAMPLE # Resolve using NetBIOS name $Principal = Resolve-Principal -Identity "DOMAIN\administrator" $Principal = Resolve-Principal -Identity "administrator" # Current domain assumed .EXAMPLE # Resolve using SID $Principal = Resolve-Principal -Identity "S-1-5-21-1234567890-1234567890-1234567890-500" .EXAMPLE # Use with pipeline "administrator@domain.com", "DOMAIN\user1", "S-1-5-21-1234567890-1234567890-1234567890-1001" | Resolve-Principal .NOTES The function supports the following input formats: - UPN: Contains '@' character (user@domain.com) - NetBIOS: Contains '\' character (DOMAIN\user) or plain username - SID: Starts with 'S-1-' or is a SecurityIdentifier object For NetBIOS names without a domain prefix, the current domain is assumed. .LINK https://docs.microsoft.com/en-us/windows/security/identity-protection/access-control/security-identifiers #> [CmdletBinding()] [OutputType([System.DirectoryServices.DirectoryEntry])] param ( [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [object]$Identity, [Parameter()] [string]$Server ) begin { Write-Verbose "[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] Starting $($MyInvocation.MyCommand) on $env:COMPUTERNAME..." # Get domain information for context try { if ($Server) { $rootDSE = [ADSI]"LDAP://$Server/RootDSE" } else { $rootDSE = [ADSI]"LDAP://RootDSE" } $defaultNC = $rootDSE.defaultNamingContext # Extract domain name from DN $domainDN = $defaultNC $domainName = ($domainDN -replace 'DC=', '' -replace ',', '.').Trim() $netbiosDomain = $domainName.Split('.')[0].ToUpper() Write-Verbose "Domain: $domainName, NetBIOS: $netbiosDomain, DN: $domainDN" } catch { throw "Failed to connect to domain: $($_.Exception.Message)" } } process { try { $identityString = $Identity.ToString() Write-Verbose "Processing identity: $identityString" $directoryEntry = $null $searchFilter = $null # Determine identity type and create appropriate search filter if ($identityString -match '@') { # UPN format (user@domain.com) Write-Verbose "Detected UPN format: $identityString" $searchFilter = "(&(|(objectCategory=person)(objectCategory=computer))(userPrincipalName=$identityString))" } elseif ($identityString -match '^S-1-') { # SID format Write-Verbose "Detected SID format: $identityString" try { $sid = New-Object System.Security.Principal.SecurityIdentifier($identityString) $sidBytes = New-Object byte[] $sid.BinaryLength $sid.GetBinaryForm($sidBytes, 0) # Convert to hex string for LDAP filter $sidHex = ($sidBytes | ForEach-Object { '\' + $_.ToString('X2') }) -join '' $searchFilter = "(&(|(objectCategory=person)(objectCategory=computer))(objectSid=$sidHex))" } catch { throw "Invalid SID format: $identityString" } } elseif ($Identity -is [System.Security.Principal.SecurityIdentifier]) { # SecurityIdentifier object Write-Verbose "Detected SecurityIdentifier object: $($Identity.Value)" $sidBytes = New-Object byte[] $Identity.BinaryLength $Identity.GetBinaryForm($sidBytes, 0) # Convert to hex string for LDAP filter $sidHex = ($sidBytes | ForEach-Object { '\' + $_.ToString('X2') }) -join '' $searchFilter = "(&(|(objectCategory=person)(objectCategory=computer))(objectSid=$sidHex))" } elseif ($identityString -match '\\') { # NetBIOS format (DOMAIN\username) Write-Verbose "Detected NetBIOS format with domain: $identityString" $parts = $identityString.Split('\', 2) $username = $parts[1] # Use sAMAccountName for search (works for both users and computers) $searchFilter = "(&(|(objectCategory=person)(objectCategory=computer))(sAMAccountName=$username))" } else { # Plain username (assume current domain) Write-Verbose "Detected plain username, assuming current domain: $identityString" $searchFilter = "(&(|(objectCategory=person)(objectCategory=computer))(sAMAccountName=$identityString))" } # Perform LDAP search Write-Verbose "Search filter: $searchFilter" if ($Server) { $searcher = [ADSISearcher]"LDAP://$Server/$defaultNC" } else { $searcher = [ADSISearcher]"LDAP://$defaultNC" } $searcher.Filter = $searchFilter $searcher.SearchScope = [System.DirectoryServices.SearchScope]::Subtree $searcher.PropertiesToLoad.AddRange(@( 'distinguishedName', 'sAMAccountName', 'userPrincipalName', 'objectSid', 'objectClass', 'name', 'displayName' )) $searchResult = $searcher.FindOne() if ($searchResult) { $directoryEntry = $searchResult.GetDirectoryEntry() Write-Verbose "Successfully resolved principal: $($directoryEntry.Properties['distinguishedName'].Value)" # Add derived properties for easy access $directoryEntry | Add-Member -MemberType NoteProperty -Name 'ResolvedUPN' -Value $directoryEntry.Properties['userPrincipalName'].Value -Force $directoryEntry | Add-Member -MemberType NoteProperty -Name 'ResolvedNetBIOS' -Value "$netbiosDomain\$($directoryEntry.Properties['sAMAccountName'].Value)" -Force if ($directoryEntry.Properties['objectSid'].Value) { $sidObj = New-Object System.Security.Principal.SecurityIdentifier($directoryEntry.Properties['objectSid'].Value, 0) $directoryEntry | Add-Member -MemberType NoteProperty -Name 'ResolvedSID' -Value $sidObj.Value -Force } return $directoryEntry } else { throw "Principal not found: $identityString" } } catch { Write-Error "Failed to resolve principal '$identityString': $($_.Exception.Message)" return $null } finally { if ($searcher) { $searcher.Dispose() } } } end { Write-Verbose "[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] Completed $($MyInvocation.MyCommand)" } } |