1

I'm working on a tool that can quickly run through a csv and search AD for the relevant information provided I have made a gui that will let the user align the header of a CSV with the AD search method

My main issue at the moment is when searching AD for EmailAddress variable i am often getting the error "A connection to the directory on which to process the request was unavailable. This is likely a transient condition."

and its erroring out on select entries Limiting the number of powershell jobs running at any one time appears to assist with the issue but does not eliminate it entirely

Here is what I'm working with at the moment

$maxConcurrentJobs=15
$CheckBlock = {
            param ($User)
            Try { Get-ADUser -Filter { EmailAddress -eq $User } -Properties SamAccountName, EmployeeID, EmailAddress }
            Catch { Return "$User - $_" }
        }
        $Array.($listbox_Columns.SelectedItem) | ForEach-Object{
            $Check = $false 
            while ($Check -eq $false)
            {
                if ((Get-Job -State 'Running').Count -lt $maxConcurrentJobs)
                {
                    Write-Host "Processing EmailAddress $_"
                    Start-Job -ScriptBlock $CheckBlock -ArgumentList $_
                    $Check = $true
                }
            }
        }
Mark Wragg
  • 22,105
  • 7
  • 39
  • 68
Luke Hagar
  • 13
  • 3
  • As an aside: While seductively convenient, the [use of script blocks (`{ ... }`) as `-Filter` arguments](https://stackoverflow.com/a/44184818/45375) is conceptually problematic and can lead to misconceptions. – mklement0 Dec 23 '20 at 22:47

2 Answers2

3

I'm sorry this won't fit in a comment, but I'd been watching this discussion with the intent of chiming in.

While I'd be hard-pressed to cite references it's well known to me that the -Filter argument is interpreted/translated into an LDAP query by the AD cmdlets, on the client-side and then forwarded to the server as an LDAP query string.

This can be proven by upping the NTDS logging on a DC executing a -Filter query to see what's logged, my bet is it'll be the translated LDAP query. Moreover, it stands to reason because AFAIK AD LDAP (un-interfaced) can't answer any other type of query. My intent to double-check using this approach is the reason for my delayed participation.

Over the years, I've repeatedly tested performance between -Filter & -LDAPFilter and repeatedly came up with extremely narrow differences swinging in either direction. Given general performance variability, my conclusion is there's little to no difference! While we can assume there's some overhead involved in the interpretation of the -Filter argument it's probably minimal to the point of being undetectable. Included in that overhead there's an ability to query on calculated properties like "Enabled". That property as returned by Get-ADUser is likely a bitwise interpretation of UserAccountControl, it nevertheless can be queried with -Filter. There's been some debate about that and other properties/attributes, but I can personally attest to its reliability and have mentioned it in other SO discussions.

Note: It's possible these results could be different with more complex queries. However, increasing complexity might lead one to use -LDAPQuery for other more straightforward reasons.

I can't find those discussions at the moment but will try to update this note when I do. I know I reached a similar conclusion and commented on one of @mklement0's answers to which he directed me to one of @tomolak's where I logged a similar comment.

I've also long recognized that broad singular queries for broad needs are much faster than re-running a Get-* many times. Within reason, this appears to be universal, not specific to AD cmdlets. If I need to check that a user exists thousands of times it's much faster to load a list of all users first, then check the list than it is to run Get-ADUser that many times. Of course, if I only need to check a few users the formula might swing in the other direction. This firm observation is likely the reason the code sped up, not any difference between -Filter and -LDAPFilter.

In my experience, the real use case for -LDAPFilter is when certain attributes are not queryable with -Filter. This is likely due to a lack of Filter > LDAPFilter translation for the given property. So, the best advice I can give and that which I employ is to use -Filter until you can't, and switch to -LDAPFilter when you need. I can't rule out other use cases, perhaps portability of query strings between different AD cmdlets? Despite all this, if you're comfortable with LDAP Queries generally or specifically, there's certainly no functional harm in using them.

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Steven
  • 6,817
  • 1
  • 14
  • 14
  • Very accurate, the biggest time save was running one Get-ADUser per user vs running one properly formatted query for the whole list Thank you so much for the in-depth analysis! it was a great read – Luke Hagar Jan 01 '21 at 13:20
2

I'd suggest moving to -LDAPFilter, like this:

Get-ADUser -LDAPFilter "(mail=$User)" -Properties SamAccountName, EmployeeID, EmailAddress

And building from that, the optimum would be a single search that gets all users in one go. This can also be done with -LDAPFilter, but it requires a bit more doing.

$mails = $listbox_Columns.SelectedItem  # this should be an array of email addresses
$filter = $mails -join ')(mail='

Get-ADUser -LDAPFilter "(|(mail=$filter))" -Properties SamAccountName, EmployeeID, EmailAddress

Chances are high that you don't need to distribute this across many jobs anymore at this point, this is as efficient as it gets.

What's happening in the second code sample:

$mails -join ')(mail=' together with (|(mail=$filter)) creates an LDAP search expression in the form of (|(mail=A)(mail=B)(mail=C)) and so on, which would give you all the matching objects (but nothing else) in one server round-trip.

Of course you need to familiarize yourself with the LDAP search filter syntax and take a look at the raw LDAP property values in your AD to use it effectively, but this is a small price to pay for the performance gains it provides.

Tomalak
  • 332,285
  • 67
  • 532
  • 628
  • 1
    WOW "this is as efficient as it gets." You can say that 7 more times this is near instant! – Luke Hagar Dec 23 '20 at 19:26
  • @LukeHagar there is the possibility that the LDAP filter string gets too long, but the upper limit is very comfortable. The filter string can be multiple megabytes in size before the server chokes, so you should be good for most practical purposes. Doesn't hurt to try how far you can push it before you get errors, just so you get a feeling for it. In general, it's always more efficient to save round trips and let the server sort out an however complex query, than to send many simple queries in parallel. – Tomalak Dec 23 '20 at 19:30
  • Hmm well I don’t have a precise answer to offer but the absolute truth is LDAPFilter *is* much faster. I’ll delete my comments to not distract from that. Take care! – Doug Maurer Dec 23 '20 at 19:37
  • @DougMaurer Yeah, I've learned that the hard way. I've not used `-Filter` since. Also, native LDAP filters at least equally expressive, and quite a bit more focused, so I'm not missing any functionality. There is a certain inconvenience that comes with having to encode values manually (things like parenthesis must be escaped to keep the filter string from becoming syntactically invalid) but that's not much of a concern for email addresses like in the OP's case. – Tomalak Dec 23 '20 at 19:41
  • Thank you all for your help, this has made this function so much smaller and has sped it up significantly – Luke Hagar Dec 23 '20 at 19:50
  • While `-LDAPFilter` may be faster - and it would be good to understand why - I don't think `-Filter` is evaluated on the client side, because all `-Filter` parameters by design filter _at the source_. – mklement0 Dec 23 '20 at 22:51
  • As for LDAP syntax: The appeal of the `-Filter` parameter is not having to learn a different domain's syntax and to instead use PowerShell's syntax - though that promise isn't fulfilled - see [this answer](https://stackoverflow.com/a/44184818/45375). – mklement0 Dec 23 '20 at 22:58
  • 1
    @mklement0 That's what I thought but I didn't have anything to back up my claim, which is why I deleted it. – Doug Maurer Dec 23 '20 at 23:05
  • @DougMaurer, I guess the way to verify the true behavior would be to monitor network traffic (I don't have access to any Active Directory instance). In the abstract, the fact that `-Filter` is a `[string]`-typed parameter indicates that it isn't PowerShell (on the client side) that's evaluating the filter. – mklement0 Dec 23 '20 at 23:12
  • I did tests with multiple conditions repeated 1000 times in the lab. Both performed about the same. – Doug Maurer Dec 23 '20 at 23:14
  • @mklement0 Is the `-Filter` syntax strictly limited to expressions that can be translated into an LDAP filter? My experience with `-LDAPFilter` was generally a lot more positive than with `-Filter`, but I might either run into confirmation bias here, or there is another factor to it. – Tomalak Dec 23 '20 at 23:53
  • @Tomalak: Translating to an LDAP filter behind the scenes sounds likely to me, but I don't know for sure; there's only a legacy help topic available, and it states "The Filter parameter has been implemented to replace the function of the LDAP Filter and adds support for PowerShell variables, rich data types, improved error checking and an Active Directory extended form of the PowerShell Expression Language." - https://learn.microsoft.com/en-us/previous-versions/windows/server/hh531527(v=ws.10) – mklement0 Dec 24 '20 at 14:08
  • 1
    @mklement0 Yeah, it seems to not be an issue for this simple use case. But I'm not sure if it's universally true, I've not been deterred from using `-Filter` for no reason, although I can't find a good cross-test right now. – Tomalak Dec 28 '20 at 17:46