Scaling the PowerShell Active Directory Searcher

powershell problem solver AD CSV
Over the course of the last several articles, I have been demonstrating how to find Active Directory objects using the Active Directory Searcher Object from .NET and PowerShell. I have purposely kept my examples simple and constrained but today we are running free. I need to show you how to manage large environments.

I know that my domain has 6351 user accounts. Let’s see what I get with the searcher object.

$searcher = New-Object system.DirectoryServices.DirectorySearcher
$searcher.filter = "(objectclass=user)"
$all = $searcher.FindAll()

Total users found (Image Credit: Jeff Hicks)
Total Users Found (Image Credit: Jeff Hicks)

Well, that cannot be right. Actually, it is. By default, the searcher object will only return the first 1000 matching objects. Heaven forbid that we ask a server to do any work! The solution is to modify the searcher’s PageSize property. The default value is 0. You can set it to a new value and PowerShell will return matching objects in pages or groups of that number.

$all = $searcher.FindAll()

I have experimented with different page sizes and cannot seem to find any value that is necessarily better than another. I tend to use 100 but you should experiment in your own test environment.
Regardless, $all now has 6447 objects! I have cleared the first hurdle and appear to be getting more than 1000 objects. However, I have even more than expected.

My searcher is defaulting to the domain root, so I know it is not missing anything. Using my Convert-ADSearchResult function from a previous article, I can group the results on the object type.

Grouping results by object type (Image Credit: Jeff Hicks)
Grouping Results by Object Type (Image Credit: Jeff Hicks)

This is interesting, especially for those of you who are not up to speed on LDAP schemas. When we refer to a user account, this is also a Person. Likewise, computer accounts can be counted as Users. The solution is to refine my search filter.

$searcher.filter = "(&(objectcategory=person)(objectclass=user))"
$all = $searcher.FindAll()

If you recall in a previous article, I showed you how to create a compound filter. Let’s check again.

Refined search results (Image Credit: Jeff Hicks)
Refined Search Results (Image Credit: Jeff Hicks)

If you are searching for users or other specifics in a large environment, you might need filters like this:

$searcher.filter = "(&(objectcategory=person)(objectclass=user)(name=mysampleuser3*))"

Limiting search (Image Credit: Jeff Hicks)
Limiting Search (Image Credit: Jeff Hicks)

Or keep extending your filter:

$searcher.filter = "(&(objectcategory=person)(objectclass=user)(name=a*)(title=tester))"

Using a compound search filter (Image Credit: Jeff Hicks)
Using a Compound Search Filter (Image Credit: Jeff Hicks)

That filter may look complicated. I will admit, they can be tricky to write. Let’s look at a trick.
Open Active Directory Users and Computers. Right-click on Saved Queries and select New – Query.
New AD Query (Image Credit: Jeff Hicks)
New Active Directory Query (Image Credit: Jeff Hicks)

If you plan on saving the query, you can enter a name. Otherwise, click the Define Query button. Use the dialogue box to configure whatever query you need.
Defining a custom query (Image Credit: Jeff Hicks)
Defining a Custom Query (Image Credit: Jeff Hicks)

I am going to build a query to find all disabled accounts. Click OK. You should now see the LDAP query string.
Getting the LDAP query string (Image Credit: Jeff Hicks)
Getting the LDAP Query String (Image Credit: Jeff Hicks)

You can select that text and copy it to Clipboard. Use that as your new search filter.

$searcher = New-Object system.DirectoryServices.DirectorySearcher
$searcher.filter = "(&(objectCategory=person)(objectClass=user)(userAccountControl:1.2.840.113556.1.4.803:=2))"
$searcher.PageSize = 100

There is no way I could have figured out that query on my own.

Showing disabled accounts (Image Credit: Jeff Hicks)
Showing Disabled Accounts (Image Credit: Jeff Hicks)

Now, I could pipe $disabled to Where-Object and filter out things like Guest. That would be easier than trying to modify the filter. A better choice would be to restrict my search scope.
Limiting the search (Image Credit: Jeff Hicks)
Limiting the Search (Image Credit: Jeff Hicks)

You could use the Saved Query wizard to get the filter syntax for your common search needs and create a set of PowerShell tools. In fact, I have given you a number of PowerShell functions that could serve as the starting point for your own toolbox. I might put together my own module when I have a little time. Although, I happened to check the PowerShell Gallery and found a very complete module called AdsiPS from fellow MVP Francois-Xavier Cat. He has assembled a complete toolkit that uses ADSI from the .NET Framework to manage Active Directory. This is very similar to what I have been demonstrating. None of it should require the RSAT Active Directory module. Run Install-Module AdsiPS to download and install it in your test network.
I hope you have found this series helpful. As always, comments welcome.