Hey, Scripting Guy! I am just getting started using Windows PowerShell, and while it is pretty cool, I really need to be able to search Active Directory. I tried to adapt some old VBScripts I found laying around on the Internet, but it gets pretty long and convoluted. Is there an easier way to do this? What I am interested in, to start with, is a listing of the computers in Active Directory and the values of their associated attributes. Can you do such a thing?
– SL
Hi SL,
That silence you hear is the absence of music in my office. I was so excited when I got your question, I immediately got down to work. So fast in fact that I skipped my afternoon snack, and did not bother turning on my Zune. Well let’s get to work—I am starting to get hungry.
Luckily, using Windows PowerShell to search Active Directory is a piece of cake, maybe a nice big piece of Black Forest cake like I got in Freiburg, Germany, last year. Of course, Black Forest cake isn’t as good without a nice big cup of rich German coffee. Anyway, SL, querying Active Directory using Windows PowerShell is not a problem. The script I put together is called the SearchAllComputersInDomain.ps1.
This week we are talking about searching Active Directory. The Active Directory Script Center Hub has links to a number of resources related to working with Active Directory. You will also find in this section of the Script Center Script Repository a good collection of scripts that illustrate searching Active Directory. There are several scripts in the Community-Submitted Scripts Center that also illustrate searching Active Directory. The “Hey, Scripting Guy!” Active Directory Archive is also an excellent source of information for searching Active Directory. Not to be outdone, the Scripting Guide has an ADSI Scripting Primer. |
We can use the SearchAllComputersInDomain.ps1 script to retrieve a listing of all the attributes and their associated values for all the computers in the domain:
$Filter = “ObjectCategory=computer” $Searcher = New-Object System.DirectoryServices.DirectorySearcher($Filter) $Searcher.Findall() | Foreach-Object ` -Begin { “Results of $Filter query: ” } ` -Process { $_.properties ; “`r”} ` -End { [string]$Searcher.FindAll().Count + ” $Filter results were found” }
The first thing we need to do in the script is create our LDAP dialect search filter. The search filter syntax is documented on MSDN. In our script the filter follows this simple pattern:
LDAPAttribute=value
We are interested in the ObjectCategory attribute with a value of computer. ObjectCategory is the name of an attribute in Active Directory, and we are supplying the value of computer for that attribute. Please note that, while it may look a little strange not having a space on either side of the ObjectCategory=computer filter, if you include a space on either side the query will fail, but an error will not be generated. We simply do not see any results returned. This can lead to some extremely costly experimentation sessions late at night as one tries to hone in on the correct syntax for the query. We store the search filter in the variable $Filter as seen here:
$Filter = “ObjectCategory=computer”
The LDAP attribute we are interested in is called ObjectCategory. The schema for the LDAP attribute ObjectCategory tells us that the syntax is a DistinguishedName value. We can see this by using the Active Directory Schema snap-in. The properties of the ObjectCategory attribute are seen here:
The Active Directory Schema snap-in is not available by default. To use the tool you need to first register the schmmgmt.dll file by using RegSvr32. The command is seen here:
Regsvr32 schmmgmt.dll
After you have registered the schmmgmt.dll file, the dialog box appears that you see here:
In our query, however, we are not supplying a DistinguishedName value. Is the documentation in error? So how does our filter actually work? In this case, the Active Directory search engine simplifies the task for us a little and allows the use of the LDAP display name in the filter. If we use only the DisplayName the Active Directory search engine goes to the definition for the class and picks up its defaultObjectCategory value. Every classSchema object has an attribute called defaultObjectCategory, which is the object category of an instance of the class if none is specified by the user. For most of the classes, the defaultObjectCategory value is the class itself. In the search filter, if we specify objectCategory = x and x is the ldapDisplayName of the class, LDAP will automatically expand the filter to and convert the value for the objectCategory to the distinguished name format. (Thanks to my friend Luis in Lisbon for helping me out with this.)
So what about using the objectClass attribute instead of objectCategory? Because of the way the classes are structured in the hierarchy in the schema of Active Directory, every object in Active Directory is a member of many classes. Most objects are actually members of four or five classes on average. Because of this, the index of the objectClass attribute is extremely large. Also, objectClass has poor selectivity for many possible class values. In addition to possible performance issues, the results can be misleading as well. The query seen here returns both computer objects and user objects:
PS C:\> $Filter = “ObjectClass=user” PS C:\> $Searcher = New-Object System.DirectoryServices.DirectorySearcher($Filter) PS C:\> $Searcher.FindAll()
Next we need to create an instance of the System.DirectoryServices.DirectorySearcher class. The DirectorySearcher class is documented on MSDN. There are many different ways in which the DirectorySearcher class can be created. Brandon, a Microsoft MVP, recommends supplying the filter to the constructor because it is much faster on medium-to-large networks. We store the returned System.DirectoryServices.DirectorySearcher object in the $Searcher variable as seen here:
$Searcher = New-Object System.DirectoryServices.DirectorySearcher($Filter)
Now we use the Findall method to return all objects that meet our search filter criteria:
$Searcher.Findall() |
We receive all of the objects from the Findall method and send them across the pipeline to the ForEach-Object cmdlet. We use the backtick (`) character for line continuation here. This is because I want the –Begin, –Process, and –End parameters to line up with one another. To do this, we need to use line continuation. This is the same as the space underscore used in VBScript. The ForEach-Object command is seen here:
Foreach-Object `
We want to use the –Begin parameter from the ForEach-Object cmdlet. We use the –Begin parameter to do something once before we get the objects coming across the pipeline. The commands in the scriptblock associated with the –Begin parameter occur once for the all the objects that come across the pipeline. This is a perfect place to put a header—something that will be printed out at the top of a report or at the top of the screen. We use this to print out a confirmation message that these are the results of the filter query. This is seen here (note that at the end of the line we also need to use the line continuation character):
-Begin { “Results of $Filter query: ” } `
The –Process parameter works on each object that comes across the pipeline. Inside the scriptblock we use the semicolon to indicate a separate logical line of code. We print out the properties and values of each computer object and then use the backtick r character (“`r”) to print out a blank line between each computer. At the end of the script block, we use the backtick character to continue to the next line:
-Process { $_.properties ; “`r”} `
The last thing we want to do is to print out how many computer accounts were found. We convert the number of the computer accounts into a string so we can concatenate with the remainder of our ending message. This line of code is seen here:
-End { [string]$Searcher.FindAll().Count + ” $Filter results were found” }
When we run the script at the end of the last record, we see the summary message shown here:
Well, SL, this concludes our discussion about searching Active Directory for a listing of all computers in the domain. Join us tomorrow as Searching Active Directory Week continues. Until then, peace.
Ed Wilson and Craig Liebendorfer, Scripting Guys
0 comments