internal/functions/Get-LdapObject.ps1

function Get-LdapObject {
    <#
        .SYNOPSIS
            Use LDAP to search in Active Directory
 
        .DESCRIPTION
            Utilizes LDAP to perform swift and efficient LDAP Queries.
 
        .PARAMETER LdapFilter
            The search filter to use when searching for objects.
            Must be a valid LDAP filter.
 
        .PARAMETER Property
            The properties to retrieve.
            Keep bandwidth in mind and only request what is needed.
 
        .PARAMETER SearchRoot
            The root path to search in.
            This generally expects either the distinguished name of the Organizational unit or the DNS name of the domain.
            Alternatively, any legal LDAP protocol address can be specified.
 
        .PARAMETER Configuration
            Rather than searching in a specified path, switch to the configuration naming context.
 
        .PARAMETER Raw
            Return the raw AD object without processing it for PowerShell convenience.
 
        .PARAMETER PageSize
            Rather than searching in a specified path, switch to the schema naming context.
 
        .PARAMETER MaxSize
            The maximum number of items to return.
 
        .PARAMETER SearchScope
            Whether to search all OUs beneath the target root, only directly beneath it or only the root itself.
     
        .PARAMETER AddProperty
            Add additional properties to the output object.
            Use to optimize performance, avoiding needing to use Add-Member.
 
        .PARAMETER Server
            The server to contact for this query.
 
        .PARAMETER Credential
            The credentials to use for authenticating this query.
     
        .PARAMETER TypeName
            The name to give the output object
 
        .EXAMPLE
            PS C:\> Get-LdapObject -LdapFilter '(PrimaryGroupID=516)'
             
            Searches for all objects with primary group ID 516 (hint: Domain Controllers).
    #>

    [Alias('ldap')]
    [CmdletBinding(DefaultParameterSetName = 'SearchRoot')]
    param (
        [Parameter(Mandatory = $true, Position = 0)]
        [string]
        $LdapFilter,
        
        [Alias('Properties')]
        [string[]]
        $Property = "*",
        
        [Parameter(ParameterSetName = 'SearchRoot')]
        [Alias('SearchBase')]
        [string]
        $SearchRoot,
        
        [Parameter(ParameterSetName = 'Configuration')]
        [switch]
        $Configuration,
        
        [switch]
        $Raw,
        
        [ValidateRange(1, 1000)]
        [int]
        $PageSize = 1000,
        
        [Alias('SizeLimit')]
        [int]
        $MaxSize,
        
        [System.DirectoryServices.SearchScope]
        $SearchScope = 'Subtree',
        
        [System.Collections.Hashtable]
        $AddProperty,
        
        [string]
        $Server,
        
        [PSCredential]
        $Credential,
        
        [Parameter(DontShow = $true)]
        [string]
        $TypeName
    )
    
    begin {
        #region Utility Functions
        function Get-PropertyName {
            [OutputType([string])]
            [CmdletBinding()]
            param (
                [string]
                $Key,
                
                [string[]]
                $Property
            )
            
            if ($hit = @($Property).Where{ $_ -eq $Key }) { return $hit[0] }
            if ($Key -eq 'ObjectClass') { return 'ObjectClass' }
            if ($Key -eq 'ObjectGuid') { return 'ObjectGuid' }
            if ($Key -eq 'ObjectSID') { return 'ObjectSID' }
            if ($Key -eq 'DistinguishedName') { return 'DistinguishedName' }
            if ($Key -eq 'SamAccountName') { return 'SamAccountName' }
            $script:culture.TextInfo.ToTitleCase($Key)
        }
        
        function New-DirectoryEntry {
            <#
        .SYNOPSIS
            Generates a new directoryy entry object.
         
        .DESCRIPTION
            Generates a new directoryy entry object.
         
        .PARAMETER Path
            The LDAP path to bind to.
         
        .PARAMETER Server
            The server to connect to.
         
        .PARAMETER Credential
            The credentials to use for the connection.
         
        .EXAMPLE
            PS C:\> New-DirectoryEntry
 
            Creates a directory entry in the default context.
 
        .EXAMPLE
            PS C:\> New-DirectoryEntry -Server dc1.contoso.com -Credential $cred
 
            Creates a directory entry in the default context of the target server.
            The connection is established to just that server using the specified credentials.
    #>

            [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")]
            [CmdletBinding()]
            param (
                [string]
                $Path,
        
                [AllowEmptyString()]
                [string]
                $Server,
        
                [PSCredential]
                [AllowNull()]
                $Credential
            )
    
            if (-not $Path) { $resolvedPath = '' }
            elseif ($Path -like "LDAP://*") { $resolvedPath = $Path }
            elseif ($Path -notlike "*=*") { $resolvedPath = "LDAP://DC={0}" -f ($Path -split "\." -join ",DC=") }
            else { $resolvedPath = "LDAP://$($Path)" }
    
            if ($Server -and ($resolvedPath -notlike "LDAP://$($Server)/*")) {
                $resolvedPath = ("LDAP://{0}/{1}" -f $Server, $resolvedPath.Replace("LDAP://", "")).Trim("/")
            }
    
            if (($null -eq $Credential) -or ($Credential -eq [PSCredential]::Empty)) {
                if ($resolvedPath) { New-Object System.DirectoryServices.DirectoryEntry($resolvedPath) }
                else {
                    $entry = New-Object System.DirectoryServices.DirectoryEntry
                    New-Object System.DirectoryServices.DirectoryEntry(('LDAP://{0}' -f $entry.distinguishedName[0]))
                }
            }
            else {
                if ($resolvedPath) { New-Object System.DirectoryServices.DirectoryEntry($resolvedPath, $Credential.UserName, $Credential.GetNetworkCredential().Password) }
                else { New-Object System.DirectoryServices.DirectoryEntry(("LDAP://DC={0}" -f ($env:USERDNSDOMAIN -split "\." -join ",DC=")), $Credential.UserName, $Credential.GetNetworkCredential().Password) }
            }
        }
        #endregion Utility Functions
        
        $script:culture = Get-Culture

        #region Prepare Searcher
        $searcher = New-Object system.directoryservices.directorysearcher
        $searcher.PageSize = $PageSize
        $searcher.SearchScope = $SearchScope
        
        if ($MaxSize -gt 0) {
            $Searcher.SizeLimit = $MaxSize
        }
        
        if ($SearchRoot) {
            $searcher.SearchRoot = New-DirectoryEntry -Path $SearchRoot -Server $Server -Credential $Credential
        }
        else {
            $searcher.SearchRoot = New-DirectoryEntry -Server $Server -Credential $Credential
        }
        if ($Configuration) {
            $searcher.SearchRoot = New-DirectoryEntry -Path ("LDAP://CN=Configuration,{0}" -f $searcher.SearchRoot.distinguishedName[0]) -Server $Server -Credential $Credential
        }
        
        Write-PSFMessage -Level InternalComment -String 'Get-LdapObject.Search' -StringValues $SearchScope, $searcher.SearchRoot.Path
        
        if ($Credential) {
            $searcher.SearchRoot = New-Object System.DirectoryServices.DirectoryEntry($searcher.SearchRoot.Path, $Credential.UserName, $Credential.GetNetworkCredential().Password)
        }
        
        $searcher.Filter = $LdapFilter
        
        foreach ($propertyName in $Property) {
            $null = $searcher.PropertiesToLoad.Add($propertyName)
        }
        
        Write-PSFMessage -String 'Get-LdapObject.Filter' -StringValues $ldapFilter
        #endregion Prepare Searcher
    }
    process {
        try {
            $ldapObjects = $searcher.FindAll()
        }
        catch {
            throw
        }
        foreach ($ldapobject in $ldapObjects) {
            if ($Raw) {
                $ldapobject
                continue
            }
            #region Process/Refine Output Object
            $resultHash = @{ }
            foreach ($key in $ldapobject.Properties.Keys) {
                $resultHash[(Get-PropertyName -Key $key -Property $Property)] = switch ($key) {
                    'ObjectClass' { $ldapobject.Properties[$key][@($ldapobject.Properties[$key]).Count - 1] }
                    'ObjectGuid' { [guid]::new(([byte[]]($($ldapobject.Properties[$key])))) }
                    'ObjectSID' { [System.Security.Principal.SecurityIdentifier]::new(([byte[]]$($ldapobject.Properties[$key])), 0) }
                        
                    default { $($ldapobject.Properties[$key]) }
                }
            }
            if ($resultHash.ContainsKey("ObjectClass")) { $resultHash["PSTypeName"] = $resultHash["ObjectClass"] }
            if ($TypeName) { $resultHash["PSTypeName"] = $TypeName }
            if ($AddProperty) { $resultHash += $AddProperty }
            $item = [pscustomobject]$resultHash
            Add-Member -InputObject $item -MemberType ScriptMethod -Name ToString -Value {
                if ($this.DistinguishedName) { $this.DistinguishedName }
                else { $this.AdsPath }
            } -Force -PassThru
            #endregion Process/Refine Output Object
        }
    }
}