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 through this collection to get the entries which are of type System.DirectoryServices.SearchResult

Isn’t that easy? Oh…you still think this is too much to type. Granted but what about putting this all together in a function so you can easily use it any time you like. Maybe even put it in your profile to have it around when you need it. You’re right, I already did this and you can copy and paste the function from here.

function Get-MyADObject {

param(

[Parameter(Position=0, Mandatory=$true)]

[ValidateNotNull()]

[System.String]

$ADFilter,

[Parameter(Position=1)]

[System.String]

$ADRoot = ([System.DirectoryServices.DirectoryEntry]"LDAP://RootDSE").defaultNamingContext,

[Parameter(Position=2)]

[ValidateNotNull()]

[System.String]

$ADScope = "SUBTREE",

[Parameter(Position=3)]

[ValidateNotNull()]

[System.Int32]

$ADPageSize = 1000,

[Parameter(Position=4)]

[System.String[]]

$ADPropertyList,

[Switch]$GCSearch,

[Switch]$FindOne,

[Switch]$GetObjects

)

if ($GCSearch){$Mode = 'GC'}

else {$Mode = 'LDAP'}

$ADSearchRoot = New-Object System.DirectoryServices.DirectoryEntry("$($Mode)://$($ADRoot)")

$ADSearcher = New-Object System.DirectoryServices.DirectorySearcher

$ADSearcher.SearchRoot = $ADSearchRoot

$ADSearcher.PageSize = $ADPageSize

$ADSearcher.Filter = $ADFilter

$ADSearcher.SearchScope = $ADScope

if ($ADPropertyList){foreach ($ADProperty in $ADPropertyList){[Void]$ADSearcher.PropertiesToLoad.Add($ADProperty)}}

if ($FindOne) {$ADResults = $ADSearcher.FindOne()}

else {$ADResults = $ADSearcher.FindAll()}

if ($GetObjects){

   $SearchResult=@()

   foreach ($ADResult in $ADResults){

     $ADObject = New-Object System.DirectoryServices.DirectoryEntry("$($ADResult.Path)")

     $SearchResult += $ADObject

}

return $SearchResult

}

else {return $ADResults}

}

Now that wasn’t difficult, was it? Wondering why I put in that many parameters into the function? That’s to make the function more versatile. Most of the parameters have default values so you can omit them for standard queries. Pretty cool and pretty easy, don’t you think .

Now stop it, what is that $ADProperty parameter used for? I‘m glad you asked!
One of the most useful things of the AD searcher is the ability to tell it which properties it should retrieve from the objects it finds. If you are searching for all the servers in your AD and just want to know which operating system they have installed, you can set up a list of these properties (“OperatingSystemVersion”, “OperatingSystem”, “OperatingSystemServicePack”) and the searcher will get those. This helps keep network traffic and memory consumption of your scripts at bay.

Want to see some examples on how to use your new function? Ok here you go (and you get a Pause function for free as well )

function Pause ($Message="Press any key to continue...")

{

Write-Host -NoNewLine $Message

$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")

Write-Host ""

}

Clear-Host

$rootDSE = ([ADSI]"LDAP://RootDSE")

Write-Host "============================================================================="

Write-Host "Getting Userobjects using ANR and only loading properties cn, samAccountName and displayname apart from adspath which is loaded by default"

$userName = Read-Host "Username:"

$props =@("cn","samAccountName","DisplayName")

$colResults = Get-MyADObject -ADFilter "(&(objectCategory=person)(objectClass=user)(anr=$userName))" -ADPropertyList $props

$userList = @()

foreach ($objResult in $colResults){

if($objResult -is [System.DirectoryServices.SearchResult]){

try {

$tempObj = New-Object PSObject -Property $objResult.Properties

$userList += $tempObj

}

finally{}

}

}

$userList | ft *

Write-Host "============================================================================="

Pause

Write-Host "============================================================================="

#Get-MyADObject -ADFilter "(&(objectCategory=computer)(operatingSystem=*server*))" -FindOne -GetObjects

Write-Host "============================================================================="

Pause

Write-Host "============================================================================="

Write-Host "Getting DC NTDSobjects for forest from config NC"

Get-MyADObject -ADFilter "(objectClass=nTDSDSA)" -ADRoot $rootDSE.configurationNamingContext

Write-Host "============================================================================="

Pause

Write-Host "============================================================================="

Write-Host "Getting userobjects that have a SID History"

Get-MyADObject -ADFilter "(&(objectCategory=person)(objectClass=user)(sIDHistory=*))"

Write-Host "============================================================================="

Pause

Write-Host "============================================================================="

Write-Host "Getting domains in forest"

Get-MyADObject -ADFilter "(objectCategory=domain)" -ADRoot $rootDSE.rootDomainNamingContext -GCSearch

Write-Host "============================================================================="

Pause

Write-Host "============================================================================="

Write-Host "Getting one random site in forest"

Get-MyADObject -ADFilter "(objectClass=site)" -ADRoot $rootDSE.configurationNamingContext

Write-Host "============================================================================="

That should get you started and give you some ideas on what you can do with the System.DirectoryServices.DiretorySearcher.

I hope you have not only copied the code but also understood the concepts and can use it for your own projects.

And just in case you wondered if this script works against 2003 AD only, no it works against all AD versions.

Some reference for you to help playing around with System.DirectoryService:
System.DirectoryServices Namespace
DirectorySearcher Class
DirectoryEntry Class