Managing AD in Bulk Using PowerShell

The Active Directory module makes it easy

Regardless of how you manage Active Directory (AD) day-to-day, every so often you'll probably need to perform bulks operations against AD. Whether you're searching for objects that meet particular criteria so you can take some action on them or modifying objects in bulk, the Active Directory module for Windows PowerShell is proving to be a worthy tool.

In "PowerShell and Active Directory," I discussed how to load and access the Active Directory module, which is new to Windows Server 2008 R2 and Windows 7. Now I'll show you two examples of how to use the module to automate bulk operations in your environment.

Searching High and Low

One of the most common bulk operations is searching for AD objects that meet particular criteria—say the value of an attribute or the status of a computer account. Let's start exploring the Active Directory module by creating such a scenario.

Suppose I want to find all the disabled computer accounts in three organizational units (OUs) in my AD domain and move them to a temporary holding OU so I can dispose of them later. One way to do this is to create a list of OUs to search, find all the disabled computer accounts in those OUs, and move them as they're found. You might think it would be easier to search the entire domain, but providing a list of OUs reduces the number of objects to search and lets you experiment with providing input to PowerShell commands, which is useful to know how to do.

So, I first need a way to feed the OU names to PowerShell. One approach is to put the names in a comma-separated value (CSV) file, then use PowerShell's Import-CSV cmdlet to read in the names, storing them in a variable. This approach is good if you have a long list of items to feed to a command. However, since I want to search only three OUs, I'll use an array to store their names.

The OU names give PowerShell a place to start its search from, which is called the search base. The search base typically takes the form of an LDAP distinguished name (DN)—for example, OU=Marketing, DC=cpandl,DC=com—so that's what I'll store in my array. The following command creates my array of OUs to search:

$searchBase =

(Although this command wraps here, you'd enter it all on one line. The same holds true for the other commands that wrap.) This command assigns the string array to the $searchBase variable. If you want to make sure that an array is created, run the command


PowerShell should return the first element in the array (OU=Test,DC=cpandl,DC=com).

Now I need a command that does the searching. For this particular task, I've chosen the Search-ADAccount cmdlet. The great thing about this cmdlet is that it has all the parameters I need to search for inactive computer accounts, so I don't have to build a complex LDAP filter. For example, to find all inactive computer accounts in a domain, I can simply run

   -AccountDisabled -ComputersOnly

Figure 1 shows an example of what the results will look like.

Retrieving disabled computer accounts with Search-ADAccount
Figure 1: Retrieving disabled computer accounts with Search-ADAccount

Finally, I need a command that moves the disabled computers to a holding OU. To perform the move, I'll use the Move-ADObject cmdlet, which lets you easily move an object or container from one place to another in AD. For example, the following command uses this cmdlet to move a disabled computer account to an OU called BitBucket:

Move-ADObject -Identity

In this case, the -Identity parameter specifies the DN of the object I want to move (a workstation named NT4) and the TargetPath parameter specifies the DN of the OU I want to move the object to. It's as simple as that.

I now have all the pieces to perform the bulk search-and-move operation: I have the list of OUs, the command to find disabled computer accounts, and the command to move them. There are two ways to put them all together, depending on how comfortable you are with PowerShell.

The simplest approach is to search for inactive computer accounts in a single OU, then use PowerShell's built-in pipelining capability to pipe the results to the command that will move those accounts. This can be done with code such as

Search-ADAccount -AccountDisabled
  "OU=SDM,DC=cpandl,DC=com" | 
  Move-ADObject -TargetPath

In this code, I use the Search-ADAccount cmdlet to search for disabled computer accounts in the SDM OU. I pipe the resulting list of disabled computer accounts to the Move-ADObject cmdlet, which will move those accounts to the BitBucket OU. Note that I didn't need to include the -Identity parameter for Move-ADObject because the pipeline takes care of passing the name of each disabled computer account without me explicitly stating it.

The limitation of this approach is that I have to type this command for each OU on my list. This is where my $searchBase array comes in handy. Using that array, I can write code to iterate through my OU list:

foreach ($ou in $searchBase)
  {Search-ADAccount -AccountDisabled
  -ComputersOnly -SearchBase $ou |
  Move-ADObject -TargetPath

Let's look at what this code is doing. First, I use the ForEach-Object cmdlet (aliased to foreach) to iterate through my list of OU names, which is stored in $searchBase. For each OU in $searchBase, I call the Search-ADAccount cmdlet and tell it to look for disabled computer accounts in that OU. I then pipe the output to the Move-Object cmdlet, which moves them. The end result is that all disabled computer accounts in the three OUs are moved to the BitBucket OU.

Making Mass Modifications

The second scenario I want to cover is making mass changes to AD objects. For example, you might need to modify a particular attribute on a large number of objects, based on some other criteria. Let's create a scenario, then look at how to accomplish it.

Suppose I want to find all of the users who are a member of the Marketing Employees group. For each of these users, I want to write the string FTE to his or her employeeType attribute.

First, I need to find the best way to determine a user's group memberships. I have a couple of options:

  • I could read each user's memberOf attribute to determine whether that user is a direct member of a particular group. That doesn't necessarily give me any indirect memberships (groups that are a member of other groups), but it does get me part of the way there. If I need indirect memberships, I could read a user's tokenGroups attribute, which is a special constructed attribute that represents both direct and indirect group memberships. It exists in Windows Server 2003 AD and later.
  • I could search each group, looking at its members attribute to find out that group's direct members. I would also need to search through the membership of each group that's nested within another group. Fortunately, the Active Directory module makes this task easy. Namely, the Get-ADGroupMember cmdlet provides the -Recursive parameter, which will chase down any indirect group members.

The second option is a more scalable solution for my scenario, so that's the approach I'll use.

Next, I need to find the best way to modify attributes on user objects. To do this, I'll use Set-ADUser. This cmdlet lets you modify properties on user accounts. It comes with a set of named parameters that include commonly modified properties. However, you can also modify many other user account properties by using the generic -Add, -Replace, -Clear, and -Remove parameters.

The employeeType attribute isn't one of Set-ADUser's named parameters, so I'll need to use the generic parameters. When using these parameters, it's important to know that they view an attribute that's not set differently than an attribute that already has a value. So, you need to use the -Add parameter when an attribute isn't set and the -Replace parameter when a value already exists.

Now that I have all the pieces in place to make the bulk change to the employeeType attribute based on user group membership, let's put it all together. Once again, I can leverage the PowerShell pipeline, as follows:

  -Identity "Marketing Employees"
  -Recursive |
  where { $_.employeeType -eq $null } |
  Set-ADUser -Add @{employeeType = "FTE"}
  -Identity "Marketing Employees"
  -Recursive |
  where { $_.employeeType -ne $null } |
  Set-ADUser -Replace @{employeeType = "FTE"}

Let's look at what these two commands are doing. In the first command, I use the Get-ADGroupMember cmdlet with the -Recursive parameter to get all the direct and indirect members of the Marketing Employees group in my domain. I then use the PowerShell pipeline to send the output to the Where-Object cmdlet (aliased as where), which checks whether the group member's employeeType attribute is equal to null (i.e., a value isn't set). If it's null, I pass the member to the Set-ADUser cmdlet to populate that attribute. Because employeeType isn't one of Set-ADUser's named parameters and it doesn't contain a value, I use the -Add parameter to populate it with "FTE". The tricky thing about using a generic parameter is that you have to pass in a hashtable that contains the name of the attribute and its value. A hashtable is simply a key-value pair that you can define by delimiting it with the @{ } construct, as I've done with @{employeeType = "FTE"}.

The second command is basically the same as the first, except that I change the where clause to check whether employeeType is not equal to null (i.e., already has a value). If it's not null, I pass the member to the Set-ADUser cmdlet. This time, I use the generic -Replace parameter to change the existing value to "FTE". Once again, I use a hashtable to provide the new value.

Get the Job Done Quickly and Easily

As the two examples show, searching for AD objects is simple with Search-ADAccount and moving them is effortless with Move-ADObject. Get-ADGroupMember makes it easy to recursively root out all direct and nondirect members of a group—a task that used to take quite a bit of work in the pre-PowerShell days. And when you need to modify those group members, Set-ADUser quickly gets the job done.

All in all, when it comes to performing bulk operations against AD, the Active Directory module delivers. Its cmdlets provide a powerful built-in mechanism to perform most automation tasks. I encourage you to explore the other cmdlets in the module, which you can enumerate by typing

Get-Command -Module ActiveDirectory

from the PowerShell console.

Hide comments


  • Allowed HTML tags: <em> <strong> <blockquote> <br> <p>

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.