Searching 2003-2012 AD

Ever came across an ‘old’ Active Directory at a customer site and missed the AD PowerShell cmdlets? As a matter of fact, this happens to me all the time working as a PFE (In case you don’t know what a Microsoft PFE is have a look here).

So what can you do if you still want to use PowerShell? Use ADO and COM Objects like in VBScript? Yeah sounds like a plan let’s go back to VBScript anyway…NOT!!!

I like the object concepts of PowerShell and the underlying .Net Framework… wait a second how about using .Net for querying the Active Directory. That way we get back objects and can use them as objects in PowerShell. Now that is what I call a plan
Since PowerShell is based on the .Net Framework we can use the .Net Framework classes right away without having to worry about DLLs loaded or things alike (as an example you can use the .Net Mathematics class to do a lot of calculation right in PowerShell. Just type [Math]:: at a PowerShell prompt and use the Tab key to iterate through the different functions the Math class provides. Isn’t that neat?)

Let’s fire up PowerShell ISE get our hands on the keyboard and bring out some code (If you don’t want to use ISE you can use the Script Editor you prefer, but ISE brings some nice IntelliSense features).

$ADSearcher = New-Object System.DirectoryServices.DirectorySearcher

That’s it. You have a wonderful shiny new directory searcher object stored in a variable called $ADSearcher. But wait a second…what the heck do we do with this thing. First of all I encourage you to play around with your new object and the Get-Member cmdlet (Get-Member -InputObject $ADSearcher is a good start ) Examine what the object can do (look at the methods) and what it provides (look at the properties).
As you see we get a lot of interesting properties we can use to customize our search.
Here is some more code to get you started with a search that actually finds something in your AD:

$rootDSE = New-Object System.DirectoryServices.DirectoryEntry("LDAP://RootDSE")

$ADSearchRoot = New-Object System.DirectoryServices.DirectoryEntry("LDAP://$($rootDSE.defaultNamingContext)")

$ADSearcher = New-Object System.DirectoryServices.DirectorySearcher

$ADSearcher.SearchRoot = $ADSearchRoot

$ADSearcher.PageSize = 1000

$ADSearcher.Filter = "(&(objectCategory=person)(objectClass=user)(anr=myusername))"

$ADSearcher.FindAll()

What?? Are you kidding me? 7 Lines of code just to search for a user in AD? That’s not handy and in no way memorable (actually you get used to it…).

Ok, let’s dissect this a little so you know what happens here.
First of all we need to tell the searcher where to search. So what I did in line one is getting the Root Directory Service Entry of the domain we are logged on to.
Second we store the default naming context of the rootDSE in $ADSearchroot. We use a System.DirectoryServices.DirectoryEntry object for this because the Directory Searcher needs it’s search root as (you guessed it already) System.DirectoryServices.DirectoryEntry.
At line three we define our AD searcher object and fill some of it’s properties in the next three lines. As search root we give it the value of the $ADSearchRoot object. Next we define the page size for our LDAP query (because that’s what we do with AD searcher, we fire LDAP questions against AD) so we can do a paged query and get all results, even if we get more results than the LDAP query limit defined in AD.
So time to tell the searcher what it should be looking for. In the above case we are looking for an object that has a category of person and is of class user (right, we are searching a user in AD). anr=myusername tells the searcher to use ambigous name resolution in its search. Keep in mind that by using ANR you can not use wildcards, ANR automatically does a ‘starts with’ search.  (Need more information about ANR? Have a look here: Ambiguous Name Resolution for LDAP in Windows 2000)

Ok, back to our script, the last line starts the searcher and dutifully it brings us the results it has found. There is one thing you should be aware of when using the searcher. The results are always objects of type System.DirectoryServices.SearchResultCollection

TypeName: System.DirectoryServices.SearchResultCollection

 

Name                      MemberType            Definition                                                                         

----                      ----------            ----------                                                                         

Contains                  Method                bool Contains(System.DirectoryServices.SearchResult result)                        

CopyTo                    Method                void CopyTo(System.DirectoryServices.SearchResult[] results, int index), void ICol...

CreateObjRef              Method                System.Runtime.Remoting.ObjRef CreateObjRef(type requestedType)                    

Dispose                   Method                void Dispose(), void IDisposable.Dispose()                                         

Equals                    Method                bool Equals(System.Object obj)                                                     

GetEnumerator             Method                System.Collections.IEnumerator GetEnumerator(), System.Collections.IEnumerator IEn...

GetHashCode               Method                int GetHashCode()                                                                  

GetLifetimeService        Method                System.Object GetLifetimeService()                                                  

GetType                   Method                type GetType()                                                                     

IndexOf                   Method                int IndexOf(System.DirectoryServices.SearchResult result)                           

InitializeLifetimeService Method                System.Object InitializeLifetimeService()                                          

ToString                  Method                string ToString()                                                                   

Item                      ParameterizedProperty System.DirectoryServices.SearchResult Item(int index) {get;}                       

Count                     Property              int Count {get;}                                                                    

Handle                    Property              System.IntPtr Handle {get;}                                                        

IsSynchronized            Property              bool IsSynchronized {get;}                                                          

PropertiesLoaded          Property              string[] PropertiesLoaded {get;}                                                   

SyncRoot                  Property              System.Object SyncRoot {get;}                                                      

So what you were looking for is always in a collection and you need to run