Hey, Scripting Guy! Question

Hey Scripting Guy! It seems that searching Active Directory from within Windows PowerShell is rather easy. But what I do not get is the syntax for the query. In the past I used something that looked more like SQL that the examples you show. What gives?

- RH

SpacerHey, Scripting Guy! Answer

Hi PS,

I was watching the Ninja bear on MSN Video pretty cool actually. I don't think I would want to fight him (or her … never can tell with bears). We are finally digging out from underneath the big Charlotte, North Carolina, USA snow storm and the sun if shining, there are birds singing … more like a normal southern winter … around 60 degrees or so outside (15.5 degrees Celsius using my handy temperature conversion HTA).  So I thought what is there better to do than to watch a Ninja bear and check my scripter@Microsoft.Com email. Let's take a look at the LDAP dialect that is used to query Active Directory. Also since this is sort of a weather holiday here in Charlotte (I mean we are digging out from under a massive snow blizzard) we will not be writing a script. However, with Windows PowerShell there are lots of things that do not require scripts.

This week we are talking about searching Active Directory. The Active Directory Script Center Hub is seen here. It has links to a number of resources related to working with Active Directory. There is a good collection of scripts that illustrate searching Active Directory in this section of the Script Center Script Repository. 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.

There are a couple of options available to us when it comes to querying Active Directory from the PowerShell prompt. One is to use the [ADSISearcher] type accelerator that is available in Windows PowerShell 2.0 (currently in beta). The [ADSISearcher] type accelerator is a shortcut to the System.DirectoryServices.DirectorySearcher class. All the [ADSISearcher] type accelerator does is save us a little bit of typing. We still need to give it the appropriate constructor to actually create an instance of the class.

If we do not use the [ADSISearcher] we need to use the New-Object cmdlet to create the object. We can put the New-Object command inside smooth parentheses to force the creation of the object first, and then call the FindAll method from the DirectorySearcher object. The resulting collection of DirectoryEntry objects is pipelined to the Select-Object cmdlet where the Path property is returned.  This is seen here.

PS C:\> (New-Object DirectoryServices.DirectorySearcher "ObjectClass=user").FindAll() |

Select-object -property path

Path
----
LDAP://CN=Administrator,CN=Users,DC=nwtraders,DC=com
LDAP://CN=Guest,CN=Users,DC=nwtraders,DC=com
LDAP://CN=BERLIN,OU=Domain Controllers,DC=nwtraders,DC=com
LDAP://CN=krbtgt,CN=Users,DC=nwtraders,DC=com
LDAP://CN=VISTA,CN=Computers,DC=nwtraders,DC=com
LDAP://CN=VistaAdmin,OU=Students,DC=nwtraders,DC=com
List Truncated –

To use the [ADSISearcher] type accelerator, we still need to supply it with an appropriate constructor, which in many cases will be the search filter expressed in LDAP Search Filter Syntax. LDAP Search Filter Syntax is defined in the Internet Request For Comment RFC 2254 and is represented by Unicode strings. The search filters allow us to specify search criteria in an efficient and effective manner. Some examples of using the LDAP Search Filter Syntax are seen in Table 1.

LDAP Search Filter ExamplesTable 1

Search Filter

Description

ObjectClass=Computer

All computer objects

ObjectClass=OrganizationalUnit

All Organizational Unit objects

ObjectClass=User

All user objects as well as all computer objects

ObjectCategory=User

All User objects

(&(ObjectCategory=User)(ObjectClass=Person))

All User objects

L=Berlin

All objects with the location of Berlin

Name=*Berlin*

All objects with a name that contains Berlin

(&(L=berlin)(ObjectCategory=OrganizationalUnit))

All Organizational Units with the location of Berlin

(&(ObjectCategory=OrganizationalUnit)(Name=*Berlin*))

All Organizational Units with a name that contains Berlin

(&(ObjectCategory=OrganizationalUnit)(Name=*Berlin*)(!L=Berlin))

All Organizational Units with a name that contains Berlin, but do not have a location of Berlin

(&(ObjectCategory=OrganizationalUnit)(Name=*Berlin*)(!L=*))

All Organizational Units with a name that contains Berlin, but do not have any location specified

(&(ObjectCategory=OrganizationalUnit)(|(L=Berlin)(L=Charlotte)))

All Organizational Units with a location of either Berlin or Charlotte

 

As seen in the examples in Table 1 there are two ways in which the search filter can be specified. The first method is a straight forward assignment filter. The attribute, the operator, and the value make up the filter. This is seen here.

PS C:\> ([ADSISearcher]"Name=Charlotte").FindAll() | Select Path

Path
----
LDAP://OU=Charlotte,DC=nwtraders,DC=com

If we were going to do the same thing without using the [ADSISearcher] the code we would need to use would first need to create the DirectoryServices.DirectorySearcher object and store it in a variable. We will call the variable $adsiSearcher. We then assign our filter to the filter property and call then we call the FindAll method. It would look like the following:

PS C:\> $adsiSearcher = New-Object DirectoryServices.DirectorySearcher

PS C:\> $adsiSearcher.Filter = "Name=Charlotte"

PS C:\> $adsiSearcher.FindAll() | Select path

 

Path

----

LDAP://OU=Charlotte,DC=nwtraders,DC=com

The second way to use the LDAP Search Filter is to combine multiple filters. The operator goes first. We will have the operator, then filter A followed by filter B. We can combine multiple filters and operators as seen in the syntax examples in Table 1. An example of a compound filter is seen here.

PS C:\> ([ADSISearcher]"(|(Name=Charlotte)(Name=Atlanta))").FindAll() | Select Path

Path
----
LDAP://OU=Atlanta,DC=nwtraders,DC=com
LDAP://OU=Charlotte,DC=nwtraders,DC=com

If we were to do this without using the [ADSISearcher] it might look like the following:

PS C:\> $adsiSearcher.Filter = "(|(Name=Charlotte)(Name=Atlanta))"

PS C:\> $adsiSearcher.FindAll() | Select path

 

Path

----

LDAP://OU=Atlanta,DC=nwtraders,DC=com

LDAP://OU=Charlotte,DC=nwtraders,DC=com

You may be wondering … where did the code go for creating the DirectorySearcher object? Since we are working interactively from the Windows PowerShell console, we did not need to create the object a second time.

The operators that we can use for either straight forward assignment filters, or compound search filters are listed in Table 2.

LDAP Search Filter Logic OperatorsTable 2

Logical operator

Description

=

Equal to

~=

Approximately equal to

<=

Lexicographically less than or equal to

>=

Lexicographically greater than or equal to

&

AND

|

OR

!

NOT

Table 3 lists special characters. If any of these special characters must appear in a search filter as a literal character, it must be replaced by the escape sequence.

Table 3

ASCII character

Escape sequence substitute

*

\2a

(

\28

)

\29

\

\5c

NUL

 \00

/

\2f

Special characters are allowed in Organizational Unit names in Active Directory.

 There is an Organizational Unit with the name *Atlanta. To retrieve this particular organizational unit we would need to use the \2a character as seen here.

PS C:\> ([ADSISearcher]"name=\2aAtlanta").FindAll() | Select Path

Path
----
LDAP://OU=*Atlanta,DC=nwtraders,DC=com

Without using the [ADSISearcher] the command would look like the one seen here.

PS C:\> $adsiSearcher.Filter = "Name=\2aAtlanta"

PS C:\> $adsiSearcher.FindAll() | Select path

 

Path

----

LDAP://OU=*Atlanta,DC=nwtraders,DC=com

To retrieve the organizational unit named (Berlin) we will need to use the \28 and the \29 escape sequences as documented in Table 3. This is seen here.

PS C:\> ([ADSISearcher]"name=\28Berlin\29").FindAll() | Select Path

Path
----
LDAP://OU=(Berlin),DC=nwtraders,DC=com

Here is the code that does not use the [ADSISearcher].

PS C:\> $adsiSearcher.Filter = "name=\28Berlin\29"

PS C:\> $adsiSearcher.FindAll() | Select path

 

Path

----

LDAP://OU=(Berlin),DC=nwtraders,DC=com

There is also an organizational unit named /Charlotte\. The escape sequence substitute for the forward slash is \2f. The escape sequence substitute for the backward slash is \5c. To retrieve the organizational unit named /Charlotte\ using the LDAP Search Filter and the [ADSISearcher] type accelerator, we can use a query that looks like the following.

PS C:\> ([ADSISearcher]"name=\2fCharlotte\5c").FindAll() | Select Path

Path
----
LDAP://OU=\/Charlotte\\,DC=nwtraders,DC=com

The non [ADSISearcher] version of this command would look like the following code.

PS C:\> $adsiSearcher.Filter = "Name=\2fCharlotte\5c"

PS C:\> $adsiSearcher.FindAll() | Select path

 

Path

----

LDAP://OU=\/Charlotte\\,DC=nwtraders,DC=com

I generally try to avoid using special characters in Organizational Unit names, user names, group names, computer names and the like. One reason is that I suspect not all applications know how to handle special characters, and I am always afraid that one might not work. Another reason is that while we can escape the characters in searches, the process is never intuitive, and it simply costs time trying to figure out how to escape the special character. When we add in the fact the problem will normally occur at 2:00 AM on Saturday Morning (all network problems seem to occur at 2:00 AM on Saturday Morning) when we are likely to forget about escaping the special character we have a recipe ripe for disaster. Just because something is permitted, does not mean it is advisable.

Well, PS this draws another article to a close. Join us tomorrow as we continue to look at querying Active Directory using Windows PowerShell. Until then, take care.

Ed Wilson and Craig Liebendorfer, Scripting Guys