Last Update: Sep 04, 2024 | Published: Jun 02, 2017
Last time, we started looking at the Active Directory Searcher object and how to find a single user object. If you missed it, take a moment to get caught up.
If you recall, I created a simple searcher.
$searcher = New-Object system.DirectoryServices.DirectorySearcher $searcher.filter = "samaccountname=jeff"
My Active Directory (AD) domain is not especially large, so the query does not take long to run. However, you will want to fine tune your search to be as specific and limited as possible. We will look at some filtering techniques later. Right now, I want to focus on search scope by discussing how much AD you will need to search.
When you create a search object, it defaults to the domain root for the current logged on user.
When I invoked the FindOne() method, it searched the entire domain structure. That may be perfectly acceptable if you have no idea where the object is located. In my case, I know that all active user accounts are under the Employees organizational unit. I will need to know the distinguished name, and assuming that I do, I can reposition my search.
$searcher.SearchRoot = "LDAP://ou=employees,dc=globomantics,dc=local"
Now the search runs very quickly.
This search took 27ms and it took 35ms when searching the entire domain. Sure, that is hardly earth shattering but this will make a difference when searching for many objects. If you have a large domain, it will make a bigger difference.
Another tweak you can make is to modify the set of object properties that get returned. If you recall from last time, the user account result came back with 75 properties. What if I know I only need a small subset? I can configure the searcher to only retrieve those properties.
This takes a little juggling to get the property names loaded.
$props = "distinguishedname","name","samaccountname","title","department","directreports" foreach ($item in $props) { $searcher.PropertiesToLoad.Add($item) | out-null }
The result is now limited to these properties and ran a little quicker at 22ms.
You can use the techniques from the previous article to clean this up.
You can also get the complete object with the GetDirectoryEntry() method. If you recall, the FindOne() and FindAll() methods write a result object to the pipeline, not the actual object.
Here is another way to display properties cleanly:
$entry | Select @{Name="DN";Expression={$_.DistinguishedName.value}}, @{Name="SAM";Expression={$_.samAccountname.value}}, @{Name="Name";Expression={$_.name.value}}, @{Name="Title";Expression={$_.title.value}}, @{Name="Dept";Expression={$_.department.value}}, @{Name="DirectReports";Expression = {$_.directreports.value}}
I could have easily retrieved these properties alone in the searcher without having to get the full object. If you want to do a full directory entry, you do not have to resort to 100 Select-Object expressions.
$entry.Properties.GetEnumerator()| Foreach -begin { $h = @{} } -process { $h.add($_.PropertyName,$_.value) } -end { new-object psobject -Property $h }
Let’s put everything we have looked at into a function that will get the full user object from AD with nothing but the SamAccountname.
Function Get-MyADUserObject { [cmdletbinding()] Param( [Parameter(Position = 0, Mandatory)] [string]$SamAccountname, [ValidatePattern("^LDAP://")] [string]$SearchRoot ) $searcher = New-Object system.DirectoryServices.DirectorySearcher $searcher.filter = "samaccountname=$SamAccountName" #limit search properties since we're going to get the complete user object $searcher.PropertiesToLoad.Add("distinguishedname") | out-null if ($SearchRoot) { $searcher.SearchRoot = $SearchRoot } $user = $searcher.FindOne() if ($user.Path) { $entry = $user.GetDirectoryEntry() $entry.Properties.GetEnumerator()| Foreach -begin { $h = @{} } -process { $h.add($_.propertyName,$_.value) } -end { new-object psobject -Property $h } } else { Write-Warning "Could not find user $samaccountname under $($searcher.SearchRoot.Path)" } } #end function
Feel free to expand upon this. Now, I have a tool to get a user from AD that writes an object to the pipeline. I can work with this.
Everything we have been doing for a single object, we can also do for many. We will start down that path next time.