Freigeben über


Identifying Defunct Organizations (Empty OUs)

I have quite a nice little script for you today.  A customer asked me yesterday how he might identify hosted business organizations which contain no subscribed users so he can remove the empty organizations from HMC.  If you’re not familiar with HMC, that involves locating OUs that don’t contain any users other than a couple of default admin accounts.  Once I finished licking my chops I dove right in and whipped this one up. 

The reason I was so stoked about writing this is that it involves one of my favorite things.  Forget raindrops on roses—I love writing a recursive script.  For those not educated in the ways of the geek, a recursive script calls itself.  It’s like a video of a person in standing in front of a TV playing the live video of that person—or parallel mirrors where you see 50 reflections of yourself.  (Oh, I’m getting excited just explaining the concept!)  Generally you use recursion when working with a hierarchy of some sort.  So you inspect a root object and get a list of it’s children, then you would call the same procedure (script, function, whatever) again, this time a child node becomes the root.

A Word of Warning

As you might guess, recursion can be somewhat dangerous.  They can lead to stack overflows or memory exhaustion.  For this reason, PowerShell limits the call depth to 1000.  So once your script is 1001 levels deep into itself you’ll get an error:

The script failed due to call depth overflow. The call depth reached 1001 and the maximum is 1000.
At line:0 char:0

Still you should be careful because if your script consumes enough memory, you just might run out of resources well before you get 1001 levels deep. 

Arguments

-RootNode <LDAP Path of Root Node>

Specifies the container object where you want to begin your search.  If you don’t specify a RootNode, the script returns False, and that’s that.

-OutputFile <Path to file>

If you specify an output file, the DNs of the effectively empty OUs will be written to this file.  Should probably be a txt file, but it doesn't really matter.

-Verbose

This switch if specified will have the script echo to the console every OU that it’s currently checking so you can monitor the progress.

-Quiet

Using this switch means that when the script finds an empty OU, it will still write the DN to the file (if specified) but will not echo it to the console.

* You may use –Verbose and –Quiet together if you feel you must.

** If you’re only interested in a single OU, but you want to know whether it contains any nested users, you can specify only the -RootNode and use the –Quiet switch with no –OutputFile.  This will return only a True or False value indicating whether that single OU is effectively empty.

The Script

Param ( [ADSI] $RootNode ,
[system.io.fileinfo] $OutputFile ,
[switch] $Verbose ,
[switch] $Quiet )

$UsersExist = $False;

If ($Verbose) { write-host "CHECKING: $($RootNode.distinguishedName)" }

$UserObjects = (
$RootNode.PSBase.Children | ?{
$_.objectCategory -like "CN=Person,*" `
-and $_.distinguishedName -notlike "CN=Admin@*" `
-and $_.distinguishedName -notlike "CN=CSRAdmin@*";
}
)

If ($UserObjects) {
$UsersExist = $True;
}

$ChildNodes = ($RootNode.PSBase.Children | ?{

    $_.objectCategory -like "CN=Organizational-Unit,*"

});
$ChildNodes | %{
$ChildNode = $_;
If ($ChildNode){
& $MyInvocation.InvocationName -RootNode $ChildNode `

                                       -out $OutputFile `

                                       -verbose:$Verbose `

                                       -Quiet:$Quiet | %{
If ($_) {
$UsersExist = $True;
} Else {
If (-not $Quiet) {

                    write-host "*** $($ChildNode.distinguishedName) HAS NO USERS";

                }
If ($OutputFile) {
$ChildNode.distinguishedName | `

                        out-file $Outputfile -append -encoding ascii;
}
}
}
}
}

$UsersExist;

Other Uses

For my non-hosting folks out there, this script has a host of other uses.  For instance you want to Find all the OUs that have computer objects in them.  You can change this script up a bit to accomodate that.  You’ll want to change the filter a bit and reverse the logic, but you can do it.  Have fun!

Comments

  • Anonymous
    January 01, 2003
    The comment has been removed