Use the DirectorySearcher .NET Class and PowerShell to Search Active Directory

Use the DirectorySearcher .NET Class and PowerShell to Search Active Directory

  • Comments 3
  • Likes


Summary: Learn how to search Active Directory Domain Services from Windows PowerShell by using the DirectorySearcher .NET class.

 

Hey, Scripting Guy! Question

Hey Scripting Guy! I am curious about searching Active Directory Domain Services (AD DS) from Windows PowerShell. I have seen lots of things on the Internet, but they all seem to rely upon things that are not part of Windows PowerShell. I think such things are great, but in our environment it takes considerable effort to get any software package approved for use; and if something is shareware, freeware, or whatever ware it is impossible. Therefore, I need to know what I can use that is already in the box. In the past, I have used VBScript to query AD, but I am trying to give Windows PowerShell a chance, so in one respect, I am reinventing the wheel, but I would like to avoid reinventing the entire drive train.

-- JG

Hey, Scripting Guy! Answer

Hello JG,

Microsoft Scripting Guy Ed Wilson here, one of the things I especially enjoy is talking to customers about Windows PowerShell. This is because it combines two of my favorite things: Windows PowerShell, and talking. About a month or so ago, I did a presentation for the Charlotte SQL Server users group. As a result of that talk, I am signed up to attend the SQL Saturday in Raleigh, North Carolina, the SQL Saturday in Columbia, South Carolina and even the SQL Saturday in Orlando, Florida. Actually, the one in Florida will be while the female carbon life based form that inhabits my house and I are on holiday in central Florida – but as I said, it is fun. There has been quite a bit of discussion about all of these meetings on Twitter. One cool thing about Twitter is during my presentation in Charlotte, I saw Peter taking pictures with his phone, and when I had completed my presentation, I noticed he had been posting the pictures to Twitter … real time updates are especially cool.

JG, when people talk about getting a list of computers from Active Directory Domain Services (AD DS) they generally think it will involve searching AD DS and at once they begin to panic because they envision a journey as confusing as Gulliver’s Travels but without the advantage of talking horses. In the VBScript days, searching AD involved incorporating ADO, and it could in fact, be a bit confusing. In the Windows PowerShell world, there are specialized cmdlets available from Microsoft, or from partners, and even from the community that make the process much less painful. I have written several dozen Hey Scripting Guy! articles that talk about searching AD DS by using Windows PowerShell, and I am certain I will continue to explore that topic in the future. However, I have come up with a single line of code that will return all of the computers in the current domain. By using the [adsisearcher] type accelerator, and default values, a listing of computers can be produced with a single line of code. This is shown here.

PS C:\> ([adsisearcher]"objectcategory=computer").findall()
Path                                                                             Properties
----                                                                               ----------
LDAP://CN=HYPERV,OU=Domain Controllers,...            {primarygroupid, iscriticalsystemobjec...
LDAP://CN=DC1,OU=Domain Controllers,DC=...           {primarygroupid, iscriticalsystemobjec...
LDAP://CN=WIN2003,CN=Computers,DC=NWTra...     {operatingsystemservicepack, iscritica...
LDAP://CN=WINXP,CN=Computers,DC=NWTrade...      {operatingsystemservicepack, iscritica...
LDAP://CN=HSG_Test,OU=HSG_TestOU1,OU=HS...      {iscriticalsystemobject, samaccountnam...
LDAP://CN=HSGTestComputer0,OU=HSG_Scrip...        {iscriticalsystemobject, samaccountnam...
LDAP://CN=HSGTestComputer1,OU=HSG_Scrip...        {iscriticalsystemobject, samaccountnam...
LDAP://CN=HSGTestComputer2,OU=HSG_Scrip...        {iscriticalsystemobject, samaccountnam...
LDAP://CN=HSGTestComputer3,OU=HSG_Scrip...        {iscriticalsystemobject, samaccountnam...
LDAP://CN=HSGTestComputer4,OU=HSG_Scrip...        {iscriticalsystemobject, samaccountnam...
LDAP://CN=HSGTestComputer5,OU=HSG_Scrip...        {iscriticalsystemobject, samaccountnam...
LDAP://CN=WIN7-PC,CN=Computers,DC=NWTra...      {lastlogontimestamp, iscriticalsystemo...
LDAP://CN=MRED1,CN=Computers,DC=NWTrade...     {primarygroupid, iscriticalsystemobjec...
LDAP://CN=OFFICE,CN=Computers,DC=NWTrad...       {operatingsystemservicepack, iscritica...
LDAP://CN=HYPERV-BOX,CN=Computers,DC=NW...     {primarygroupid, iscriticalsystemobjec...
LDAP://CN=TERESA-KITCHEN,CN=Computers,D...        {primarygroupid, iscriticalsystemobjec...
LDAP://CN=EX1,CN=Computers,DC=NWTraders...       {primarygroupid, iscriticalsystemobjec...
LDAP://CN=SQL1,CN=Computers,DC=NWTrader...       {primarygroupid, iscriticalsystemobjec...
LDAP://CN=WIN7-C1,OU=testou,DC=NWTrader...        {primarygroupid, iscriticalsystemobjec...
LDAP://CN=TERESA-WIN7,CN=Computers,DC=N...      {primarygroupid, iscriticalsystemobjec...
LDAP://CN=bogusComputer,CN=Computers,DC...       {iscriticalsystemobject, samaccountnam...
LDAP://CN=AnotherBogusComputer,CN=Compu...      {iscriticalsystemobject, samaccountnam...
LDAP://CN=WS1,CN=Computers,DC=NWTraders...      {primarygroupid, iscriticalsystemobjec...
LDAP://CN=BIGT-PC,CN=Computers,DC=NWTra...        {lastlogontimestamp, iscriticalsystemo...
PS C:\>

 

So, how does the single line of code work you might ask? The [adsisearcher] is a type accelerator, or a shortcut, that is designed to save you some typing. It is exactly the same thing as the system.directoryservices.directorysearcher .NET framework class. This is shown here.

PS C:\> [system.directoryservices.directorysearcher]

IsPublic
                  IsSerial                  Name                                     BaseType
----------                   ---------                    -------                                       ------------
True
                        False                      DirectorySearcher               System.ComponentModel....

PS
 C:\> [adsisearcher]

IsPublic
                  IsSerial                  Name                                     BaseType
--------
                     --------                     ----                                           --------
True
                        False                      DirectorySearcher               System.ComponentModel....

PS
 C:\>

The two commands are exactly the same; you can type 45 characters, or you can type 15 characters, it is entirely up to you because there is no difference in the object that is returned. Because the [adsisearcher] command is an alias for the system.directoryservices.directorysearcher .NET Framework class, it means the class will need a constructor in order to work. Constructors for .NET Framework classes are documented in MSDN, and that includes the constructor for the system.directoryservices.directorysearcher.

One problem with constructors is they can, at times, be rather confusing. This is also the case with the directorysearcher because there are eight different ways the class can be constructed. If nothing is supplied for a constructor, the directorysearcher is created with default values. It connects to the current domain with an empty search root, a filter to return all classes of objects, no properties and a recursive search.

The New-Object cmdlet is normally used to create new objects (that was the idea for the name). Using the New-Object cmdlet to create a new default directorysearcher class one would use code such as that seen here.

New-Object system.directoryservices.directorysearcher

To be useful, one needs to store the directorysearcher in a variable. Examining the contents of the $adsisearcher variable reveals a lot of cool stuff. This is seen here.

PS C:\> $adsisearcher = New-Object system.directoryservices.directorysearcher
PS C:\> $adsisearcher
CacheResults : True
ClientTimeout : -00:00:01
PropertyNamesOnly : False
Filter : (objectClass=*)
PageSize : 0
PropertiesToLoad : {}
ReferralChasing : External
SearchScope : Subtree
ServerPageTimeLimit : -00:00:01
ServerTimeLimit : -00:00:01
SizeLimit : 0
SearchRoot : System.DirectoryServices.DirectoryEntry
Sort : System.DirectoryServices.SortOption
Asynchronous : False
Tombstone : False
AttributeScopeQuery :
DerefAlias : Never
SecurityMasks : None
ExtendedDN : None
DirectorySynchronization :
VirtualListView :
Site :
Container :
PS C:\>

The information returned from the $adsiSearcher variable reveals how the DirectorySearcher was created. The ones I am most interested in are the filter and the searchRoot. The previous output stated the SearchRoot was a DirectoryEntry object. By querying it directly, it is revealed that the searchroot is the root of my current domain. The Filter, is the same as was documented in MSDN, objectClass=* and is seen here.

PS C:\> $adsisearcher.SearchRoot
distinguishedName : {DC=NWTraders,DC=Com}
Path : LDAP://DC=NWTraders,DC=Com
PS C:\> $adsisearcher.Filter
(objectClass=*)
PS C:\>

The next question, is “How do I make it go?” Once the directorysearcher has been created, the filter specified, and the searchroot configured, you have a choice of two methods. The first is the findone method, and the second one is the findall method. That is it. There is no findtwo or findthree method. I use the findone method for two purposes. The first purpose is to test the results of a query to see if it is returning the kind of information, I am interested in obtaining. The second purpose is when the query will return only a single instance of an object. I might do this, when for example, I know the name of a user, but I do not know where in Active Directory the user is to be found. In this situation, I would specify a filter that causes the command to return only that object. A filter that returns an object with the name of bob is “name=bob”. When you specify a single string with the New-Object cmdlet the string is interpreted as a filter. At the most rudimentary level, the command would look like the following.

PS C:\> $adsiBob = New-Object system.directoryservices.directorysearcher "name=bob"
PS C:\> $adsiBob.FindOne()

One thing to keep in mind, is that if you place a space between the characters as in “name = bob” the command will fail. You can specify the filter in many different ways as shown in the following image.

The findall method returns everything that matches the search filter. If the default constructor is used the filter is every object in Active Directory (objectclass=*). This is shown in the following image.

 

JG that is all there is to using Windows PowerShell to search AD DS by using the DirectorySearcher .NET Framework class. Searching and working with AD DS computers week will continue tomorrow when we will talk about using the [adsiSearcher] type accelerator. We would love for you to follow us on Twitter or Facebook. If you have any questions, send email to us at scripter@microsoft.com or post them on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

 

Ed Wilson and Craig Liebendorfer, Scripting Guys


Your comment has been posted.   Close
Thank you, your comment requires moderation so it may take a while to appear.   Close
Leave a Comment
  • I'll be honest I dont know exactly how powershell works with .NET but I will assume its the exact same .NET code that ends up getting run as if you were to write a native .NET application, so I just thought I would point out something that has caught me out in the past when working with the DirectorySearcher class.

    If you don't set the PageSize property (or if you explicitly set it to 0) then you will probably find that your search never returns more than 1000 results even if it should have. To resolve this you just need to set the PageSize property to 1000 and then the DirectorySearcher class will perform a paged search. The end result being that when it hits its 1000 item page limit it will request the next "page" automatically and your search will therefore get the full results list.

  • @Chris128 you are absolutely correct. By default, it returns 1000 objects. To move past 1000 requires one to set the pagesize property as you indicated. By the way, this is the same way it worked in VBScript as well.

  • thank you