
close
close
You might have encountered the problem of finding all of the empty and unused organizational units (OUs) in your Active Directory domain. Depending on the size and complexity of your domain, this could be a thankless job. But let’s see how PowerShell can help. If you missed them, you might take a few minutes to read the previous articles I wrote about managing OUs with PowerShell and the Active Directory module, as we will be using some of the same cmdlets and concepts.
I suppose we might have a differing opinion about what constitutes an empty OU. At the very least, I would define an empty OU as one that doesn’t contain any users, group, computers or other AD objects anywhere in the OU hierarchy. It’s possible that a top-level OU is empty except for a child OU, which contains several user accounts. I wouldn’t want to identify that top-level OU as empty, but I would want to catch another child OU that had nothing in it.
I’m going to start with an OU that I know is completely empty.
Get-ADOrganizationalUnit -filter "Name -like 'Empty*'"
Unfortunately, there’s no property that would help indicate if it contains any child objects. This means I’ll have to manually search the OU for objects. I can use Get-ADObject and specify the OU as the SearchBase.
advertisment
$ou = Get-ADOrganizationalUnit -filter "Name -like 'Empty*'" Get-ADObject -filter * -SearchBase $ou
Getting an OU (Image Credit: Jeff Hicks)
$ou = Get-ADOrganizationalUnit -filter "Name -eq 'Test1'" Get-ADObject -filter * -SearchBase $ou
Getting child objects (Image Credit: Jeff Hicks)
Get-ADObject -filter * -SearchBase $ou | Where {$_.distinguishedname -ne $ou.distinguishedname}
Filtering out the OU itself (Image Credit: Jeff Hicks)
Get-ADObject -filter * -SearchBase $ou | Where { ($_.distinguishedname -ne $ou.distinguishedname) -AND ($_.objectclass -ne "organizationalunit")}
Filtering out all OUs (Image Credit: Jeff Hicks)
advertisment
$ou = Get-ADOrganizationalUnit -filter "Name -like 'Empty*'" Get-ADObject -filter * -SearchBase $ou | Where { ($_.distinguishedname -ne $ou.distinguishedname) -AND ($_.objectclass -ne "organizationalunit")} | Measure-Object
Measuing and empty OU (Image Credit: Jeff HIcks)
Get-ADOrganizationalUnit -filter "Name -like 'test*'" -Properties Description -PipelineVariable pv | Select DistinguishedName,Name,Description, @{Name="Children"; Expression = { Get-ADObject -filter * -SearchBase $pv.distinguishedname | Where {$_.objectclass -ne "organizationalunit"} | Measure-Object | Select -ExpandProperty Count }}
This looks complicated, but it really isn’t. In this example I’m looking for all OUs that start with ‘Test’. The results will be stored temporarily in a pipeline variable, pv. Next, I’ll select a few properties and define a new one called ‘Children’. The value will the count of the number of objects found in the OU by Get-ADObject. The result looks like this:
Measuring OUs (Image Credit: Jeff Hicks)
Get-ADOrganizationalUnit -filter "Name -like 'test*'" -Properties Description -PipelineVariable pv | Select DistinguishedName,Name,Description, @{Name="Children"; Expression = { Get-ADObject -filter * -SearchBase $pv.distinguishedname | Where { $_.objectclass -ne "organizationalunit"} | Measure-Object | Select -ExpandProperty Count }} | Where {$_.children -eq 0} | foreach { Remove-ADOrganizationalUnit -Identity $_.distinguishedname -Recursive -WhatIf }
WhatIf Removal of empty OUs (Image Credit: Jeff Hicks)
Errors removing the OU (Image Credit: Jeff Hicks)
Get-ADOrganizationalUnit -filter "Name -like 'test*'" -Properties Description -PipelineVariable pv | Select DistinguishedName,Name,Description, @{Name="Children"; Expression = { Get-ADObject -filter * -SearchBase $pv.distinguishedname | Where { $_.objectclass -ne "organizationalunit"} | Measure-Object | Select -ExpandProperty Count }} | Where {$_.children -eq 0} | foreach { Set-ADOrganizationalUnit -Identity $_.distinguishedname -ProtectedFromAccidentalDeletion $False -PassThru | Remove-ADOrganizationalUnit -Recursive }
Now, Set-ADOrganizationalUnit is writing the OU object to the pipeline, so I can send that directly to Remove-ADOrganizationalUnit. If I rerun the command to list the OUs, my 0 children entries are gone.
Verifying OUs (Image Credit: Jeff Hicks)
advertisment
Get-ADOrganizationalUnit -filter * -Properties Description -PipelineVariable pv | Select DistinguishedName,Name,Description, @{Name="Children"; Expression = { Get-ADObject -filter * -SearchBase $pv.distinguishedname | Where { $_.objectclass -ne "organizationalunit"} | Measure-Object | Select -ExpandProperty Count }} | Where {$_.children -eq 0} | foreach { Set-ADOrganizationalUnit -Identity $_.distinguishedname -ProtectedFromAccidentalDeletion $False -PassThru -whatif | Remove-ADOrganizationalUnit -Recursive -whatif }
I’ve used WhatIf for the the last part because I don’t really want to delete them. But at least I can see which ones are empty.
Test removal of all empty OUs in the domain (Image Credit: Jeff HIcks)
More from Jeff Hicks
advertisment
Petri Newsletters
Whether it’s Security or Cloud Computing, we have the know-how for you. Sign up for our newsletters here.
advertisment
More in PowerShell
Microsoft’s New PowerShell Crescendo Tool Facilitates Native Command-Line Wraps
Mar 21, 2022 | Rabia Noureen
Most popular on petri
Log in to save content to your profile.
Article saved!
Access saved content from your profile page. View Saved
Join The Conversation
Create a free account today to participate in forum conversations, comment on posts and more.
Copyright ©2019 BWW Media Group