Active Directory and PowerShell – Speed Up Your Filtering

With the release of Windows Server 2008 R2, Microsoft released a Windows PowerShell module for managing Active Directory. To be able to use the PowerShell module you must have at least one domain controller running Windows Server 2008 R2, or at least one domain controller running Windows Server 2003 or Windows Server 2008 with the Active Directory Management Gateway Service installed. The PowerShell module is also available in Windows 7 as part of the Remote Server Administration Tools (RSAT).

In this article we`re going to focus on filtering performance when using the Microsoft Active Directory PowerShell module.

Filtering search results

We start by looking at a common cmdlet, Get-ADUser. This cmdlet has two parameter sets, each with one mandatory parameter; -Filter and -LDAPFilter.

The following table shows frequently used search filter operators.

The left column lists operators valid for the Filter parameter, while the right column lists the equivalent for the LDAPFilter parameter.

Operator Description LDAP Equivalent
-eq Equal to. This will not support wild card search. =
-ne Not equal to. This will not support wild card search. !x = y
-approx Approximately equal to ~=
-le Lexicographically less than or equal to <=
-lt Lexicographically less than !x >= y
-ge Lexicographically greater than or equal to >=
-and AND &
-or OR |
-not NOT !
-bor Bitwise OR :1.2.840.113556.1.4.804:=
-band Bitwise AND :1.2.840.113556.1.4.803:=
-recursivematch Use LDAP_MATCHING_RULE_IN_CHAIN (Note: This control only works with Windows 2008 and later.) :1.2.840.113556.1.4.1941:=
-like Similar to -eq and supports wildcard comparison. The only wildcard character supported is: * =
-notlike Not like. Supports wild card comparison. !x = y


If we take a look at Get-Help Get-ADUser –Parameter Filter we can see the following description:

Specifies a query string that retrieves Active Directory objects. This string uses the PowerShell Expression Language syntax. The PowerShell Expression Language syntax provides rich type-conversion support for value types received by the Filter parameter. The syntax uses an in-order representation, which means that the operator is placed between the operand and the value.

Examples on using the Filter parameter

Retrieve all users objects:

[code]Get-ADUser -Filter *  [/code]

Retrieve all user with a name beginning with “test”:

[code]Get-ADUser -Filter {name -like "test*"}  [/code]

Retrieve all users objects with an e-mail address:

[code]Get-ADUser -Filter {EmailAddress -like "*"}  [/code]If we also take a look at Get-Help Get-ADUser –Parameter LDAPFilter we can see the following description:  Specifies an LDAP query string that is used to filter Active Directory objects. You can use this parameter to run your existing LDAP queries. The Filter parameter syntax supports the same functionality as the LDAP syntax.

Examples on using the LDAPFilter parameter

Retrieve all users objects:

[code]Get-ADUser -LDAPFilter "(&(objectCategory=Person)(objectClass=User))" [/code]Retrieve all user with a name beginning with “test”:

[code]Get-ADUser -LDAPFilter "(mail=*)" [/code]Retrieve all users objects with an e-mail address:

[code]Get-ADUser -LDAPFilter "(name=test*)" [/code]For more information, see Get-Help about_ActiveDirectory_Filter.

For new PowerShell users which has got comfortable with the common core PowerShell cmdlets, a natural approach to filter information is to retrieve all users and filter the results using Where-Object. There is nothing wrong using this approach, which works fine, but it will be slower and also consume more resources especially in larger environments.

PowerShell has a builtin cmdlet called Measure-Command which we can use to compare filtering performance. The basic syntax is Measure-Command -Expression {command-to-measure}

To show the impact the size of the Active Directory database has, the following test is performed both in a small environment with ~100 users (Environment A) and in a larger environment with ~20 000 users (Environment B).

Environment A

[code]Measure-Command -Expression {Get-ADUser -Filter * | Where-Object {$ -like "test*"}} | Format-List TotalSeconds [/code]

TotalSeconds : 0,2395828

[code]Measure-Command -Expression {Get-ADUser -Filter {name -like "test*"}} | Format-List TotalSeconds [/code]

TotalSeconds : 0,0207963

[code]Measure-Command -Expression {Get-ADUser -LDAPFilter "(name=test*)"} | Format-List TotalSeconds [/code]

TotalSeconds : 0,0128277

Environment B

[code]Measure-Command -Expression {Get-ADUser -Filter * | Where-Object {$ -like "test*"}} | Format-List TotalSeconds [/code]

TotalSeconds : 64,485127

[code]Measure-Command -Expression {Get-ADUser -Filter {name -like "test*"}} | Format-List TotalSeconds [/code]

TotalSeconds : 0,0788577

[code]Measure-Command -Expression {Get-ADUser -LDAPFilter "(name=test*)"} | Format-List TotalSeconds [/code]

TotalSeconds : 0,0418277

We can see that performing the filtering on the server side by using the -Filter and -LDAPFilter parameters is much faster than filtering on the client side. We also see that in the smaller environment, the difference isn`t noticeable to the user running the command.

So far we can clearly see that using the filter parameters is important in larger environments. The next question we`ll ask, what is the recommended filter parameter? The answer is: It depends.

Which of the following is the easiest to read and understand?

[code] Get-ADObject -LDAPFilter "(&(|(objectclass=user)(objectclass=computer))(memberOf=CN=Test Group,OU=Groups,DC=domain,DC=local))" [/code]

[code] Get-ADObject -Filter 'objectclass -eq "user" -or objectclass -eq "computer" -and memberof -eq "CN=Test Group,OU=Groups,DC=domain,DC=local"' [/code]

Most people will find the syntax for the Filter parameter be the easiest to understand. In the previous example we used Get-ADObject rather than Get-ADUser, and this makes filtering even more important since all object classes is retrieved.

Let`s compare the performance of the filtering in the above example:

[code]Measure-Command -Expression {Get-ADObject -LDAPFilter "(&(|(objectclass=user)(objectclass=computer))(memberOf=CN=Test Group,OU=Groups,DC=domain,DC=local))"} | Format-List TotalSeconds [/code]

TotalSeconds      : 0,4034246

[code] Measure-Command -Expression {Get-ADObject -Filter 'objectclass -eq "user" -or objectclass -eq "computer" -and memberof -eq "CN=Test Group,OU=Groups,DC=domain,DC=local"'} | Format-List TotalSeconds [/code]

TotalSeconds      : 25,0171279

We can see that LDAP-filtering is more than 60 times faster.

Generally, the –Filter parameter is  recommended because it`s easy to use and has an acceptable performance. In some situations, especially when working with Get-ADObject, LDAP filtering is by far the fastest.

If using Where-Object rather than LDAP filtering when using Get-ADObject, the performance difference might be several hours (of course depending on the size of the environment).

If you got questions related to working with Active Directory from PowerShell I would like to encourage you to post your question in the Windows PowerShell forum on Microsoft TechNet.

Jan Egil Ring

Jan Egil Ring works as a Senior Consultant on the Infrastructure Team at Crayon, Norway. He works mainly with Microsoft-products, and his favorite server product is Microsoft Exchange. In addition to being a consultant, he`s also a Microsoft Certified Trainer. He`s achieved several certifications such as MCITP: Enterprise Administrator and MCITP: Enterprise Messaging Administrator. In January 2011, he was awarded the Microsoft Most Valuable Professional Award for his contributions in the Windows PowerShell technical community.

More Posts - Website - Twitter - LinkedIn

  • Mike Vayser

    For some reason i’m getting results different from yours while trying to time some commands. I have not checked on domain controller if the following commands use different LDAP filter while requesting data. Results with AD Filter are several times faster. This is contradicting what i was expecting – faster results with LDAP filter.

    Get total number of mail-enabled groups using measure-command. Result is 7115 objects

    Get-ADGroup -Filter {mailNickName -like ‘*’} | ForEach-Object {$i++}
    ~5.8-6 seconds

    Get-ADGroup -LDAPFilter “mailNickName=*” | ForEach-Object {$i++}
    32-33 sec

    Exchange Get-DistributionGroup cmdlet:
    Get-DistributionGroup -ResultSize unlimited | ForEach-Object {$i++}
    ~45 sec

    Quest Get-QADGroup without progress indicator
    Get-QADGroup -SizeLimit 0 -DontUseDefaultIncludedProperties -LdapFilter ‘(mailNickName=*)’ -ShowProgress:$False | ForEach-Object {$i++}
    9-11 sec

    Quest Get-QADGroup with progress indicator
    Get-QADGroup -SizeLimit 0 -DontUseDefaultIncludedProperties -LdapFilter ‘(mailNickName=*)’ -ShowProgress:$True | ForEach-Object {$i++}
    28-41 sec

  • GO

    Wow, I have searched for two hours for this and here you have it so easy to understand and use. Thanks