How to group Active Directory objects by Organizational Unit (OU) using PowerShell.

Grouping objects by Organizational Unit might be helpful for preparing reports or stats for our directory. Let's learn how we can do it using PowerShell!

Getting objects

First, let's grab all the objects we'd like to group. Some example cmdlets we can use:

# Gets all the computers
$objects = Get-AdComputer -Filter *

# Gets all the users
$objects = Get-AdUser -Filter *

Finding the OU

Now we need to find the value of the organizational unit for each object in the array.

Distinguished name

We can first check if there's any property we could use out-of-the-box.

$objects[0].OU
$objects[0].OrganizationalUnit
# Return nothing

Apparently, such property doesn't exist. However, we could use

DistinguishedName
property.

The format of

DistinguishedName
property is similar to the below:

CN=ObjectName,OU=Unit1A,OU=Unit1,DC=domain,DC=com

The values represent:

  • CN=ObjectName
    is the name of the particular object
  • OU=Unit
    is for OU structure, starting from the bottom. Top-level OUs are listed last
  • DC=domain,DC=com
    is for Active Directory domain name

Removing CN part

Based on the values from

DistinguishedName
the easiest way to find the OU would be to strip the first part
CN=xxxxx,
and group by that value. We could use calculated property to achieve this.

Let's try to replace the first part with a regular expression:

$objects | Select-Object @{n="OU";e={$_.DistinguishedName -replace "CN=.*,",""}}

The output will be a very long list of something like:

DC=internal
DC=internal
DC=internal
# and so on

Why is that? Let's start from the very beginning. PowerShell is based on .NET. Therefore, regular expressions use the .NET implementation.

In this implementation, regex quantifiers are greedy by default. In contrary to lazy quantifiers, the greedy ones try to match as many characters as possible. See the image below for comparison

In our example,

CN=
is matched based on character matching. Next, the
*
quantifier matches as many other characters as it can, provided they are followed by
,
.

So, how to match a minimal number of characters? You can probably guess - we need to change the quantifier to be lazy by adding a

?
character! We want our
*
quantifier to be lazy. We don't need to apply the same mechanism for the characters we use. It'd not have any effect anyway.

Tip

If you want to learn more about quantifiers check Quantifiers in Regular Expressions article provided by Microsoft.

Our code will now be:

$objects | Select-Object @{n="OU";e={$_.DistinguishedName -replace "CN=.*?,",""}}

And the output will be:

OU=Unit1A,OU=Unit1,DC=domain,DC=com
OU=Unit1B,OU=Unit1,DC=domain,DC=com
OU=Unit2A,OU=Unit2,DC=domain,DC=com
OU=Unit1B,OU=Unit1,DC=domain,DC=com
# and so on

Grouping objects

Now we only need to group (and possibly sort) the OU names we got from the previous step. Let's save the output to a variable and use Group-Object and Sort-Object:

# Saving output from previous step
$ous = $objects | Select-Object @{n="OU";e={$_.DistinguishedName -replace "CN=.*?,",""}}
# Groupping
$groupped = $ous | Group-Object OU -NoElement
# And sorting
$groupped | Sort-Object Count -Descending

The output will be:

Count Name
----- ----
  100 OU=Unit1B,OU=Unit1,DC=domain,DC=com
   21 OU=Unit1A,OU=Unit1,DC=domain,DC=com
   15 OU=Unit2A,OU=Unit2,DC=domain,DC=com
# and so on

Now we have stats and we can export or work on them further. Have fun 👍