Fiddle Freak
Fiddle Freak

Reputation: 2041

pass list of properties in ldap filter for directorysearcher

So currently, I know you can grab an account using the ldap filter for something like $adSearchFilter = "(&(objectCategory=User)(samAccountType:1.2.840.113556.1.4.803:=805306368)(SamAccountName='Account1')). Is there a way the ldap filter will allow you to pass through a list of names, like instead of using = I can use something like -contains?

Below is the code, and as you can see, it searches one user at a time for the whole search process in a foreach loop...

Function GetUsersInfoFromDomain
{
    Param ([String]$searchPropertyName, [String[]]$searchPropertyValues, [String[]]$DcWithCred,[String]$domainShortName, [String[]]$userProperties)

    $queryTable = @()
    ForEach ($searchPropertyValue in $searchPropertyValues)
    {
        $adSearchFilter = "(&(objectCategory=User)(samAccountType:1.2.840.113556.1.4.803:=805306368)($searchPropertyName=$searchPropertyValue))"
        Write-Host "Searching domain $domainShortName with $searchPropertyName $searchPropertyValue"
        $searchDomainResultsTable = powershell -command {
            Param ([String]$adSearchFilter, [String[]]$userProperties,[String[]]$DcWithCred, [String]$domainShortName)
            [string]$DC = $DcWithCred[0]
            [string]$Username = $DcWithCred[1]
            [string]$Password = $DcWithCred[2]
            [string]$domain = "LDAP://$DC"

            $adDomain = New-Object System.DirectoryServices.DirectoryEntry($domain, $Username, $Password)
            $adSearcher = New-Object System.DirectoryServices.DirectorySearcher($adDomain)
            $adSearcher.Filter = $adSearchFilter
            $adSearcher.PageSize=1000
            $adSearcher.PropertiesToLoad.AddRange($userProperties) | out-Null
            $userRecords = $adSearcher.FindAll()
            $adSearcher.Dispose() | Out-Null
            [System.GC]::Collect() | Out-Null

            # The AD results are converted to an array of hashtables.
            $userPropertiesTable = @()
            foreach($record in $userRecords) {
                $hashUserProperty = @{}
                foreach($userProperty in $userProperties){
                    if (($userProperty -eq 'objectGUID') -or ($userProperty -eq 'objectSid') -or ($userProperty -eq 'msExchMasterAccountSid')) {
                        if ($record.Properties[$userProperty]) {
                            $hashUserProperty.$userProperty = $record.Properties[$userProperty][0]
                        } else {
                        $hashUserProperty.$userProperty = $null
                        }
                    } Else {
                        if ($record.Properties[$userProperty]) {
                            $hashUserProperty.$userProperty = ($record.Properties[$userProperty] -join '; ').trim('; ')
                        } else {
                        $hashUserProperty.$userProperty = $null
                        }
                    } #end Else
                } #end ForEach
                $userPropertiesTable += New-Object PSObject -Property $hashUserProperty
            } #end ForEach
            [System.GC]::Collect() | Out-Null

            # Fixes the property values to be a readable format before exporting to csv file
            $listOfBadDateValues = '9223372036854775807', '9223372036854770000', '0'
            $maxDateValue = '12/31/1600 5:00 PM'
            $valuesToFix = @('lastLogonTimestamp', 'AccountExpires', 'LastLogon', 'pwdLastSet', 'objectGUID', 'objectSid', 'msExchMasterAccountSid')
            $extraPropertyValues = @('Domain Name')
            $valuesToFixCounter = 0
            $extraPropertyValuesCounter = 0
            $valuesToFixFound = @($false, $false, $false, $false, $false, $false, $false)
            $extraPropertyValuesFound = @($false)

            ForEach ($valueToFix in $valuesToFix)
            {
                if ($userProperties -contains $valueToFix)
                {
                    $valuesToFixFound[$valuesToFixCounter] = $true
                }
                $valuesToFixCounter++
            }

            ForEach ($extraPropertyValue in $extraPropertyValues)
            {
                if ($userProperties -contains $extraPropertyValue)
                {
                    $extraPropertyValuesFound[$extraPropertyValuesCounter] = $true
                }
                $extraPropertyValuesCounter++
            }

            $tableFixedValues = $userPropertiesTable | % { 
                if ($valuesToFixFound[0]) {
                    if ($_.lastLogonTimestamp) {
                        $_.lastLogonTimestamp = ([datetime]::FromFileTime($_.lastLogonTimestamp)).ToString('g')
                    }
                }; if ($valuesToFixFound[1]) {
                    if (($_.AccountExpires) -and ($listOfBadDateValues -contains $_.AccountExpires)) {
                        $_.AccountExpires = ""
                    } else {
                        if (([datetime]::FromFileTime($_.AccountExpires)).ToString('g') -eq $maxDateValue) {
                            $_.AccountExpires = ""
                        } Else {
                            $_.AccountExpires = ([datetime]::FromFileTime($_.AccountExpires)).ToString('g')
                        }
                    }
                }; if ($valuesToFixFound[2]) {
                    if (($_.LastLogon) -and ($listOfBadDateValues -contains $_.LastLogon)) {
                        $_.LastLogon = ""
                    } else {
                        if (([datetime]::FromFileTime($_.LastLogon)).ToString('g') -eq $maxDateValue) {
                            $_.LastLogon = ""
                        } Else {
                            $_.LastLogon = ([datetime]::FromFileTime($_.LastLogon)).ToString('g')
                        }
                    }
                }; if ($valuesToFixFound[3]) {
                    if (($_.pwdLastSet) -and ($listOfBadDateValues -contains $_.pwdLastSet)) {
                        $_.pwdLastSet = ""
                    } else {
                        if (([datetime]::FromFileTime($_.pwdLastSet)).ToString('g') -eq $maxDateValue) {
                            $_.pwdLastSet = ""
                        } Else {
                            $_.pwdLastSet = ([datetime]::FromFileTime($_.pwdLastSet)).ToString('g')
                        }
                    }
                }; if ($valuesToFixFound[4]) {
                    if ($_.objectGUID) {
                        $_.objectGUID = ([guid]$_.objectGUID).Guid
                    } Else {
                        $_.objectGUID = ""
                    }
                }; if ($valuesToFixFound[5]) {
                    if ($_.objectSid) {
                        $_.objectSid  = (New-Object Security.Principal.SecurityIdentifier($_.objectSid, 0)).Value
                    } Else {
                        $_.objectSid = ""
                    }
                }; if ($valuesToFixFound[6]) {
                    if ($_.msExchMasterAccountSid) {
                        $_.msExchMasterAccountSid  = (New-Object Security.Principal.SecurityIdentifier($_.msExchMasterAccountSid, 0)).Value
                    } Else {
                        $_.msExchMasterAccountSid = ""
                    }
                }; If ($extraPropertyValuesFound[0]) {
                    If (!($_.'Domain Name')) {
                        $_.'Domain Name' = $domainShortName
                    }
                };$_}
                [System.GC]::Collect() | Out-Null

                $sortedTableColumns = $tableFixedValues | Select-Object $userProperties
                [System.GC]::Collect() | Out-Null

                return $sortedTableColumns
        } -args $adSearchFilter, $userProperties, $DcWithCred, $domainShortName
        [System.GC]::Collect() | Out-Null
        Write-Host "Search Complete."
        Write-Host ""

        if ($searchDomainResultsTable)
        {
            $queryTable += $searchDomainResultsTable
        }
    } # End ForEach Loop
    Write-Host 'Exporting domain search results to table...'
    Write-Output $queryTable
}

I thought about doing something like $adSearchFilter += "($searchPropertyName=$searchPropertyValue)". However due to the 10mb limit - What is the LDAP filter string length limit in Active Directory?, I'm not sure if this would be the best method while looking up 200,000++ users.

Does anyone know a way to pass a list instead of 1 string value per search?

Upvotes: 0

Views: 1393

Answers (1)

Mathias R. Jessen
Mathias R. Jessen

Reputation: 174485

LDAP doesn't have a -contains-like statement, but you can use the OR operator (|) to construct a filter expression that matches multiple exact values:

(|(samaccountname=user1)(samaccountname=user2)(samaccountname=user3))

This is how I would build the filter string:

$FilterTemplate = '(&(objectCategory=User)(samAccountType:1.2.840.113556.1.4.803:=805306368){0})'
$ClauseTemplate = "($searchPropertyName={0})"
$AllClauses     = $searchPropertyValues |ForEach-Object { $ClauseTemplate -f $_ }
$adSearchFilter = $FilterTemplate -f $($AllClauses -join '')

That being said, why would you pass 200000 specific values to search for in a single search? LDAP supports wildcard matching (eg. (samaccountname=*)).

In any case, you could calculate the final size of your string, by calling Encoding.GetByteCount on the biggest string in $AllClauses, and then use that to partition the array (let's cap it at 9.5 MB to be on the safe side):

$LongestString = $AllClauses |Sort -Property Length |Select -Last 1
$LongestByteCount = [System.Text.Encoding]::Unicode.GetByteCount($LongestString)
if(($LongestByteCount * $AllClauses.Count) -gt 9.5MB)
{
    $MaxCount = [int](9.5MB / $LongestByteCount)
    for($i = 0; $i -lt $AllClauses.Count; $i += $MaxCount)
    {
        $ClauseSubset = $AllClauses[$i..$($i + $MaxCount - 1)]
        $adSearchFilter = $FilterTemplate -f $($ClauseSubset -join '')
        # Do your search
    }
}

Upvotes: 1

Related Questions