functions/Resolve-Principal.ps1

function Resolve-Principal
{
<#
    .SYNOPSIS
        Resolves the principals specified.
     
    .DESCRIPTION
        Resolves the principals specified.
     
        This command can accept a variety of inputs and resolve them into its identity.
        Using registered / cached domains, this resolution will try to automatically determine the correct domain to scan and resolve the identity.
        Then this information can be provided in multiple formats, as would be useful for the user.
     
    .PARAMETER Name
        The name to resolve.
        This can be any of the following formats:
        - SamAccountName (will resolve against targeted server/domain)
        - SID
        - UserPrincipalName (Domain may need to be pre-registered, when using a UPN-Suffix that is not equal to the domain's DNS name)
        - NT Account (May encounter issues if multiple domains share the same NetBIOS Name)
     
    .PARAMETER OutputType
        How the output should be formatted:
        ADObject: Return the AD object of the principal
        NTAccount: Return an NT Account object (e.g: 'contoso\max.mustermann')
        SID: Return the SecurityIdentifier uniquele representing the identity. It is generally a good idea to use this, if you want to compare identities.
        DataSet: Return the full dataset contained, including the user AD object and the domain information of the hosting domain.
        UPN: Return the UserPrincipalName
        SamAccountName: Return the SAMAccountName
     
    .PARAMETER Server
        The server / domain to work with.
     
    .PARAMETER Credential
        The credentials to use for this operation.
     
    .EXAMPLE
        PS C:\> Resolve-Principal -Name 'mm'
     
        Resolves the user mm against the local domain.
     
    .EXAMPLE
        PS C:\> Resolve-Principal -Name 'contoso\maria'
     
        Resolves the user maria against the contoso domain.
     
    .EXAMPLE
        PS C:\> Resolve-Principal -Name 'murat@fabrikam.org'
     
        Resolves the user murat against all known domains that support the UPN suffix fabrikam.org.
        Only a domain with the DNS name of fabrikam.org will be detected if not pre-registered or previously already discovered.
     
    .EXAMPLE
        PS C:\> Resolve-Principal -Name 'S-1-5-21-584015949-955715703-1113067636-1105'
     
        Resolves the user with the RID 1105 against the domain with the domain sid 'S-1-5-21-584015949-955715703-1113067636'
#>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingEmptyCatchBlock", "")]
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [string]
        $Name,
        
        [ValidateSet('ADObject', 'NTAccount', 'SID', 'DataSet', 'UPN', 'SamAccountName')]
        [string]
        $OutputType = 'ADObject',
        
        [PSFComputer]
        $Server,
        
        [PSCredential]
        $Credential
    )
    
    begin
    {
        $parameters = $PSBoundParameters | ConvertTo-PSFHashtable -Include Server, Credential
        
        $principalProperties = 'ObjectSID', 'SamAccountName', 'UserPrincipalName', 'Name', 'ObjectClass'
        
        #region Utility functions
        function Convert-ADPrincipal
        {
            [CmdletBinding()]
            param (
                $DomainInfo,
                
                [Parameter(ValueFromPipeline = $true)]
                $ADObject
            )
            
            process
            {
                foreach ($inputItem in $ADObject)
                {
                    $data = [pscustomobject]@{
                        SID = $inputItem.ObjectSID
                        SamAccountName = $inputItem.SamAccountName
                        UserPrincipalName = $inputItem.UserPrincipalName
                        Name = $inputItem.Name
                        ObjectClass = $inputItem.ObjectClass
                        Domain = $DomainInfo
                        ADObject = $inputItem
                    }
                    $script:principals.SID["$($inputItem.ObjectSID)"] = $data
                    if ($inputItem.UserPrincipalName) { $script:principals.UserPrincipalName[$inputItem.UserPrincipalName] = $data }
                    $script:principals.NTAccount[('{0}\{1}' -f $DomainInfo.NetBIOSName, $inputItem.SamAccountName)] = $data
                    
                    if (-not $script:principals.Domains[$DomainInfo.FQDN])
                    {
                        $script:principals.Domains[$DomainInfo.FQDN] = @{ }
                    }
                    $script:principals.Domains[$DomainInfo.FQDN][$data.SamAccountName] = $data
                    
                    $data
                }
            }
        }
        
        function ConvertTo-Output
        {
            [CmdletBinding()]
            param (
                [string]
                $OutputType,
                
                [Parameter(ValueFromPipeline = $true)]
                $InputObject
            )
            
            process
            {
                foreach ($item in $InputObject)
                {
                    switch ($OutputType)
                    {
                        'ADObject' { $item.ADObject }
                        'NTAccount' { ('{0}\{1}' -f $item.Domain.NetBIOSName, $item.SamAccountName) -as [System.Security.Principal.NTAccount] }
                        'SID' { $item.SID }
                        'DataSet' { $item }
                        'UPN' { $item.UserPrincipalName }
                        'SamAccountName' { $item.SamAccountName }
                    }
                }
            }
        }
        
        function Get-DomainInfo
        {
            [CmdletBinding()]
            param (
                [string]
                $Name,
                
                [Hashtable]
                $Parameters
            )
            
            $data = [pscustomobject]@{
                UserName = $Name
                Domain   = $Null
                Type     = 'Default'
            }
            
            #region Name notation Cases - resolve domain name and username
            try { $domainName = (Get-Domain @Parameters).DNSRoot }
            catch { $domainName = $env:USERDNSDOMAIN }
            if ($Name -like '*@*')
            {
                $data.UserName = @($Name -split '@')[0]
                $data.Type = 'UPN'
                $domainName = @($Name -split '@')[-1]
            }
            elseif ($Name -like '*\*')
            {
                $data.UserName = @($Name -split '\\')[-1]
                $domainName = @($Name -split '\\')[0]
            }
            elseif ($Name -as [System.Security.Principal.SecurityIdentifier])
            {
                $data.UserName = $Name
                $data.Type = 'SID'
                if (($Name -as [System.Security.Principal.SecurityIdentifier]).AccountDomainSid) { $domainName = $Name }
            }
            #endregion Name notation Cases - resolve domain name and username
            
            if ($data.Type -eq 'UPN' -and $script:domains.UPN[$domainName])
            {
                $data.Domain = $script:domains.UPN[$domainName]
                return $data
            }
            
            try { $domainObject = Resolve-Domain -Name $domainName -OutputType DataSet @Parameters }
            catch { }
            if ($domainObject)
            {
                $data.Domain = $domainObject
                return $data
            }
            
            # Didn't find anything and not a UPN? Return nothing
            if ($Name -notlike '*@*') { return }
            
            # UPN Suffix Resolution
            $domains = $script:domains.UPN[$domainName]
            
            if (-not $domains) { return }
            
            $data.Domain = $domains
            $data
        }
        
        function Get-ADObject2
        {
            [CmdletBinding()]
            param (
                $DomainInfoObject,
                
                [string]
                $LdapFilter,
                
                [string[]]
                $Properties,
                
                [System.Collections.Hashtable]
                $Parameters
            )
            
            Write-PSFMessage -Level Debug -String 'Resolve-Principal.Resolving.Query' -StringValues $LdapFilter -FunctionName 'Resolve-Principal'
            $adObject = $null
            
            foreach ($domainInfo in $DomainInfoObject)
            {
                $paramClone = $Parameters.Clone()
                $paramClone += @{
                    LDAPFilter = $LdapFilter
                    ErrorAction = 'Stop'
                    Properties = $Properties
                }
                $paramClone.Server = $domainInfo.FQDN
                
                try { $adObject = Get-ADObject @paramClone }
                catch
                {
                    $paramClone.Remove('Credential')
                    if ($domainInfo.Credential) { $paramClone.Credential = $domainInfo.Credential }
                    
                    try { $adObject = Get-ADObject @paramClone }
                    catch { Write-PSFMessage -Level Warning -String 'Resolve-Principal.AccessError' -StringValues $domainInfo.FQDN -ErrorRecord $_ -FunctionName 'Resolve-Principal' }
                }
                
                if ($adObject) { return $adObject }
            }
        }
        #endregion Utility functions
    }
    process
    {
        if ($script:principals.SID[$Name]) { return $script:principals.SID[$Name] | ConvertTo-Output -OutputType $OutputType }
        if ($script:principals.UserPrincipalName[$Name]) { return $script:principals.UserPrincipalName[$Name] | ConvertTo-Output -OutputType $OutputType }
        if ($script:principals.NTAccount[$Name]) { return $script:principals.NTAccount[$Name] | ConvertTo-Output -OutputType $OutputType }
        
        if (-not ($Name -as [System.Security.Principal.SecurityIdentifier]))
        {
            try { $Name = ([System.Security.Principal.NTAccount]$Name).Translate([System.Security.Principal.SecurityIdentifier]) }
            catch { }
        }
        
        if ($OutputType -eq 'SID' -and $Name -as [System.Security.Principal.SecurityIdentifier])
        {
            return $Name -as [System.Security.Principal.SecurityIdentifier]
        }
        
        $domainInfo = Get-DomainInfo -Name $Name -Parameters $parameters
        if (-not $domainInfo)
        {
            Write-PSFMessage -Level Warning -String 'Resolve-Principal.Resolve.Domain.Error' -StringValues $Name
            Write-Error "Unable to resolve domain for $Name"
            return
        }
        
        switch ($domainInfo.Type)
        {
            #region UPN Based Resolution
            'UPN'
            {
                $adObject = $null
                $adObject = Get-ADObject2 -DomainInfoObject $domainInfo.Domain -LdapFilter "(userPrincipalName=$Name)" -Properties $principalProperties -Parameters $parameters
                
                if (-not $adObject)
                {
                    Write-PSFMessage -Level Warning -String 'Resolve-Principal.Resolve.Principal.Error' -StringValues $Name
                    Write-Error "Unable to resolve principal $Name"
                    return
                }
                $adObject | Convert-ADPrincipal -DomainInfo $domainInfo | ConvertTo-Output -OutputType $OutputType
            }
            #endregion UPN Based Resolution
            
            #region SID Based Resolution
            'SID'
            {
                $adObject = $null
                $adObject = Get-ADObject2 -DomainInfoObject $domainInfo.Domain -LdapFilter "(objectSID=$($domainInfo.UserName))" -Properties $principalProperties -Parameters $parameters
                
                if (-not $adObject)
                {
                    Write-PSFMessage -Level Warning -String 'Resolve-Principal.Resolve.Principal.Error' -StringValues $Name
                    Write-Error "Unable to resolve principal $Name"
                    return
                }
                $adObject | Convert-ADPrincipal -DomainInfo $domainInfo.Domain | ConvertTo-Output -OutputType $OutputType
            }
            #endregion SID Based Resolution
            
            #region Default Workflow
            default
            {
                if ($script:principals.Domains[$domainInfo.Domain.FQDN].SamAccountName.$($domainInfo.UserName))
                {
                    return $script:principals.Domains[$domainInfo.Domain.FQDN].SamAccountName.$($domainInfo.UserName) | ConvertTo-Output -OutputType $OutputType
                }
                
                $adObject = $null
                $adObject = Get-ADObject2 -DomainInfoObject $domainInfo.Domain -LdapFilter "(samAccountName=$($domainInfo.UserName))" -Properties $principalProperties -Parameters $parameters
                
                if (-not $adObject)
                {
                    Write-PSFMessage -Level Warning -String 'Resolve-Principal.Resolve.Principal.Error' -StringValues $Name
                    Write-Error "Unable to resolve principal $Name"
                    return
                }
                $adObject | Convert-ADPrincipal -DomainInfo $domainInfo.Domain | ConvertTo-Output -OutputType $OutputType
            }
            #endregion Default Workflow
        }
    }
}