Searching Active Directory with PowerShell

Being quite the lazy admin that I am, whenever I’m scripting for AD I like to use short names when referring to objects. As an example, if I want to add the user BellA into the Domain Admins group I don’t want to provide the distinguishedName for both objects .

The easy solution to this is to have enough information to hand, to allow a quick rummage through the Directory, and let the script
retrieve the details it needs. In fact being able to extract information from an AD search is pretty much the corner stone of any AD scripting tool box, as you’ll see over the next couple of posts.

I’ll show you a couple of ways of searching and then mention a few points to be aware of regarding performance, and your impact on the servers whose workload you’re adding to.

# —————————————————————————————————
function Find-DN
# —————————————————————————————————
{
Param (
$sADobjectName,
$sADobjectType
)
$query = new-object system.directoryservices.directorysearcher
$root = [adsi]“”

$query.SearchRoot = $root
$query.filter = “(&(ObjectClass=$sADobjectType)(cn=$sADobjectName))”
$query.SearchScope = “subtree”
$result = $query.findOne()

if ($result -eq $null)
{
return $null
}
else
{
$ADobject = $result.GetDirectoryEntry()
return $ADobject.distinguishedName
}
}

There isn’t a lot of specific PowerShell code in the function above. Its pretty LDAP orientated. We have a simple function that returns you the distinguishedName of an object when you provide the objectClass and cn.
So to find the DN of my test user account:

Find-DN “BellA” “user”

The $result variable is a System.DirectoryServices.SearchResult which we cast into System.DirectoryServices.DirectoryEntry with the GetDirectoryEntry() method. I like this as it gives us a much nicer way of retrieving the objects properties.

The other method of searching AD I have seen, is similar, but with a PS slant to it:

# —————————————————————————————————
function Find-DN2
# —————————————————————————————————
{
Param (
$sADobjectName,
$sADobjectType
)
$query = new-object system.directoryservices.directorysearcher
$root = [adsi]“”

$query.SearchRoot = $root
$query.SearchScope = “subtree”

$ADobject = $query.findAll() | where-object {$_.properties.objectclass -eq $sADobjectType `
-and $_.properties.cn -eq $sADobjectName}
$props = $ADobject.Properties

if ($ADobject -eq $null)
{
return $null
}
else
{
foreach ($prop in $props)
{
return $prop.distinguishedname
}
}
}

Straight away you can see some PowerShell in the function above. Rather then use the $query.filter, we pipe the results into a where-object and then filter for our search criteria. We also handle the properties returned differently.

This function would be called exactly the same as our previous example:

Find-DN2 “BellA” “user”

Now I’ve had problems with this function. I’ve found it worked fine for “some” objects, but I have had trouble retrieving others. Despite the SearchScope being set to “SubTree” I’ve had it fail to retrieve objects that are a few OU’s deep. I suspect this is a flaw with my function as apposed to anything flawed in PS ;)

Search Tips
So now we’ve seen the basics, I thought it worth mentioning a couple of ways to improve on performance, and speed:

[1] Scope: If you can, narrow your SearchScope. There’s three options: “Base”, “OneLevel”, and “SubTree”. If you’re not sure, there’s a good explanation on MSDN here.

[2] Filter: Always try and provide a filter to reduce how much data you’re trying to retrieve. There’s obviously not much point retrieving every user account in your domain and enumerating through them all just for the one you want. Think about the Filter() method, and use the relevant FindOne() FindAll() methods.

I’m not sure how our second examples works with this. My hunch is that it retrieves ALL the objects and trawls through each with the where-object. I could be wrong here of course ;)

[3] Properties: If you’re only after say, the distinguishedName it would be a good idea to reduce the properties returned by using the PropertiesToLoad method.

The Microsoft Scripting Guy’s, have an example here

Managing group membership in Active Directory with PowerShell (Part 2)

Carrying on from where we left off yesterday; today I want to look at adding and removing users from groups.

To get us started we’ll bind to the two objects we’re going to be working with:

# Serverless bind to the domain, and define the root DN
$root = [adsi]“”
$rootdn = $root.distinguishedName

# Bind to our User and Group objects
$user = [adsi](“LDAP://cn=bella, ou=Test OU,”+$root.distinguishedName)
$group = [adsi](“LDAP://cn=Domain Admins, cn=Users,”+$rootdn)

If you have a VBScript/ADSI background then the example below will be familiar. We’ve basically just called the add method against the group object. The important thing to be aware of using this approach is that add method has to be constructed using the ADspath, which is structured by specifying the provider (“LDAP://”) and the distinguishedName of the object we’re adding (user or group).

# The classical VBS/ADSi approach
$group.add(“LDAP://”+$user.distinguishedName)
$group.SetInfo()

Alternatively we could use a PowerShell approach:

# PowerShell approach
$members = $group.member
$group.member = $members+$user.distinguishedName
$group.setinfo()

Here we basically retrieve the group members into the $members variable, and then add the full DN of the object we want to include. Then it’s just a case of writing the collection back to our group object.

If you’re making a lot of changes to an objects attributes, you’ll want to make sure that you rebind to the object after a SetInfo() call, otherwise your data will not be up to date, and you wont see the changes that have been made.

When it comes to removing members from a group it can become a little more complex.

So using the classic ADSI approach:

# Classic VBS/ADSI
$group.remove(“LDAP://”+$user.distinguishedName)
$group.SetInfo()

That’s not too difficult!

If we look at the PowerShell approach, we would take similar steps as adding to the group, only this time we’ll enumerate through the group membership, and for every membership that isn’t our user account will get added to the $newList variable which we’ll then write back to the attribute when we’ve finished.

# PS – harder
foreach ($member in $group.member)
{
# As long as it’s not our user DN add the existing member
# to the list of users we’re going to add back into the group.
if ($user.distinguishedname -ne $member)
{
$newlist = $newList+$member
}
}
$group.member = $newlist
$group.SetInfo()

On the flipside if you want to be a little more selective you could do:

# PS – Simple
$group.member = $group.member[0..3]
$group.SetInfo()

This would just put back the first 4 members into the group.

The thing that I find so powerful about PowerShell, apart from the simplicity of doing stuff (when you can work out the right syntax!), is that it is so flexible. We can perform the same actions in this example, two different ways, and as Marc (MOW) pointed out recently, you can also use the .Net framework, so that’s three different ways to perform the task!

Managing group membership in Active Directory with PowerShell (Part 1)

Adding users into groups, or groups into groups (if your functionality is set sufficiently) is relatively straight forwards. A good explanation of what changes when the domain/forest functionality is raised can be found here.

The Active Directory attributes that are responsible for group membership are MemberOf and Member. These two attributes are quite special because they are linked attributes. This is the mechanism behind how when you add a user to a group, that group automatically appears in the users MemberOf attribute. This works because the attributes use linkID’s. A much better description of which can be found here

There are a couple of things we need to be aware of when manipulating group membership programmatically:

If the account is only a member of it’s primary group, the memberOf attribute will be empty. This is documented in Microsoft KB article 275523.

So if the account has not had any other group membership associated with it, you would only really be able to query the Primary Group on the account for group related information. This is stored in the PrimaryGroupID attribute. As all users are, by default, a member of the same Primary Group, retrieving this would be more of an academic exercise than a practical one.

By default all users in the domain, have a PrimaryGroupID of 513. This number ties into the PrimaryGroupToken attribute on the group object, which for 513 is the Domain Users group. I don’t believe it’s any coincidence that this relates to the Well known SID for this group ;)

Another potential issue is highlighted in KB article 273272 which pertains to a rights issue when retrieving the attribute.

As an example, below we retrieve the group membership for Bella in the Test OU:

$rootDN = ([adsi]“”).distinguishedName

$user = [adsi](“LDAP://cn=bella, ou=Test OU,”+$rootDN)
$user.memberOf

By default this will return nothing, as the property is not set. However if membership to other groups has been added to the account you will get a System.DirectoryServices.PropertyValueCollection returned, which contains the full DN of each group that the account is a member of.

You can enumerate this collection via:

$groups = $user.memberOf

foreach ($group in $groups)
{
write-host $group
}

Which will write out one line per group membership.

You can also access the information directly:

$groups = $user.memberOf

write-host $groups[1]

Which will display just the second group, the account is a member of.

As this is getting a bit long, I’ll split it into two parts. In the second installment I’ll show you how to add and remove members of a group.