locked
Using Powershell to locate Branches of empty OU's RRS feed

  • Question

  • Hi all

    Anybody got any ideas on cracking this puzzler:??

    I'm trying to write a script to locate and list all OU's that are empty. In this instance the definition of an empty OU includes any OU that has child OU's where all the child OU's down the subtree only contain OU's that themselves are emprty.

    For example the following tree is a candidate for deletion:

    OU

     OU

      OU

    Whilst this tree isn't

    OU

     OU

      User1

      User2

      OU

       Group1

    The 2nd one is not a candidate because it contains OU's which contain child objects other than OU's

    Anyone got any ideas?

    • Moved by Bill_Stewart Thursday, June 18, 2015 6:23 PM Abandoned
    Wednesday, March 18, 2015 11:50 AM

Answers

  • Hi,

    Here's something you can play with:

    Push-Location
    
    Set-Location "AD:\$((Get-ADDomain).DistinguishedName)"
    
    Get-ChildItem | Where { $_.ObjectClass -eq 'organizationalUnit' } | ForEach {
    
        $name = $_.Name
    
        $count = Get-ChildItem "AD:\$($_.DistinguishedName)" -Recurse |
            Where { $_.ObjectClass -ne 'organizationalUnit' }
    
        If (!($count.Count -gt 0)) {
    
            Write-Output "Check on the $name OU"
    
        }
    
    }
    
    Pop-Location


    Don't retire TechNet! - (Don't give up yet - 13,225+ strong and growing)

    • Proposed as answer by jrv Thursday, March 19, 2015 10:30 AM
    • Marked as answer by Just Karl Wednesday, July 8, 2015 8:09 PM
    Wednesday, March 18, 2015 1:37 PM
  • As noted above.  DO a recursive search through all OUs and report any that are empty except for OUs.

    # get all OUs in domain
    $searcher=[adsisearcher]'objectClass=organizationalUnit'
    $allOUs=$searcher.FindAll()
    
    # set to filter out anthing that is an OU
    $searcher=[adsisearcher]'(!(objectClass=organizationalUnit))'
    
    # enumerate all ous and sub-OUs and report all that are empty
    $allOUs|
       ForEach-Object{
          $searcher.SearchRoot=$_.Path
          if(($searcher.FindAll()).Count -eq 0){
              $_
          }
        }

    This is fast and works every time. Any object except for an OU will cause the path to be considered not empty.

    I like Mike's solution which is similar but not as fast.  It also requires AD support where this can be run from anywhere in the domain.


    ¯\_(ツ)_/¯


    • Edited by jrv Thursday, March 19, 2015 10:30 AM
    • Proposed as answer by jrv Thursday, March 19, 2015 10:30 AM
    • Marked as answer by Just Karl Wednesday, July 8, 2015 8:09 PM
    Thursday, March 19, 2015 10:24 AM

All replies

  • What have you tried?

    ¯\_(ツ)_/¯

    Wednesday, March 18, 2015 12:17 PM
  • import-moduleactivedirectory



    $emptyoubrroots

    =Get-ADOrganizationalUnit-filter*-ResultSetSize$null|


    where-object

    {(Get-ADOrganizationalUnit-filter*-SearchBase$_.DistinguishedName -SearchScopesubtree-ResultSetSize$null).count -ne0}



    &

    {(Get-ADObject  -filter*  -SearchBase$_.DistinguishedName -SearchScopeSubtree-ResultSetSize$null).count -eq0}



    $emptyoubrroots

    Wednesday, March 18, 2015 12:50 PM
  • Try this, I think it should work:

    Get-ADOrganizationalUnit -SearchScope Onelevel -filter * | % {if(!(Get-adobject -SearchBase $_.DistinguishedName -LDAPFilter "(ObjectClass=User)" -SearchScope Subtree)){$_.distinguishedname}}

    Wednesday, March 18, 2015 12:54 PM
  • Thanks Braham20

    I think that will put me on the right track..

    I need to change the logic so the ldapfilter or filter is not OU rather than user

    Wednesday, March 18, 2015 1:24 PM
  • Hi,

    Here's something you can play with:

    Push-Location
    
    Set-Location "AD:\$((Get-ADDomain).DistinguishedName)"
    
    Get-ChildItem | Where { $_.ObjectClass -eq 'organizationalUnit' } | ForEach {
    
        $name = $_.Name
    
        $count = Get-ChildItem "AD:\$($_.DistinguishedName)" -Recurse |
            Where { $_.ObjectClass -ne 'organizationalUnit' }
    
        If (!($count.Count -gt 0)) {
    
            Write-Output "Check on the $name OU"
    
        }
    
    }
    
    Pop-Location


    Don't retire TechNet! - (Don't give up yet - 13,225+ strong and growing)

    • Proposed as answer by jrv Thursday, March 19, 2015 10:30 AM
    • Marked as answer by Just Karl Wednesday, July 8, 2015 8:09 PM
    Wednesday, March 18, 2015 1:37 PM
  • Thanks Braham20

    I think that will put me on the right track..

    I need to change the logic so the ldapfilter or filter is not OU rather than user

    I may have misunderstood the question, are you trying to get OU's which are entirely empty from the root of the domain or are you wanting a list of every empty OU? The code I posted will search each root OU and respective subtree and only report back ones that are entirely empty of users from top to bottom. 
    Wednesday, March 18, 2015 2:16 PM
  • It is a compound recursion problem.   You must search and report every OU that has no children at any level. Gaatther all OUs and then recursively search each on for any object EXCEPT an OU object.

    ¯\_(ツ)_/¯

    Wednesday, March 18, 2015 4:04 PM
  • Hmmm, it seems like a lot of effort to go to if the end goal is just deleting empty OU's. Why not just grab every empty OU and delete it? There's no real need to care about structure as far as I can tell.
    Thursday, March 19, 2015 8:57 AM
  • As noted above.  DO a recursive search through all OUs and report any that are empty except for OUs.

    # get all OUs in domain
    $searcher=[adsisearcher]'objectClass=organizationalUnit'
    $allOUs=$searcher.FindAll()
    
    # set to filter out anthing that is an OU
    $searcher=[adsisearcher]'(!(objectClass=organizationalUnit))'
    
    # enumerate all ous and sub-OUs and report all that are empty
    $allOUs|
       ForEach-Object{
          $searcher.SearchRoot=$_.Path
          if(($searcher.FindAll()).Count -eq 0){
              $_
          }
        }

    This is fast and works every time. Any object except for an OU will cause the path to be considered not empty.

    I like Mike's solution which is similar but not as fast.  It also requires AD support where this can be run from anywhere in the domain.


    ¯\_(ツ)_/¯


    • Edited by jrv Thursday, March 19, 2015 10:30 AM
    • Proposed as answer by jrv Thursday, March 19, 2015 10:30 AM
    • Marked as answer by Just Karl Wednesday, July 8, 2015 8:09 PM
    Thursday, March 19, 2015 10:24 AM
  • Out of curiosity I redid Mike's solution with a pipeline to see how well it works:

    #'

    $root='AD:\'+(get-addomain).distinguishedname Get-ChildItem $root -filter "(objectclass=organizationalUnit)" -rec| ForEach{ if(Get-Childitem "AD:\$($_.DistinguishedName)" -filter '(!(objectclass=organizationalUnit))' -recurse){ # children found }else{ $_ } }

    I also discovered that the performance is much better on WS2008R2 if I do not do a Set-Location to "AD:"


    ¯\_(ツ)_/¯



    • Edited by jrv Thursday, March 19, 2015 11:07 AM
    Thursday, March 19, 2015 11:06 AM
  • This also works:

    Get-ADOrganizationalUnit  -filter * | % {if(!(Get-adobject -SearchBase $_.DistinguishedName -LDAPFilter "(!ObjectClass=OrganizationalUnit)")){$_.Distinguishedname}}
    

    Thursday, March 19, 2015 11:17 AM