Powershell – Get the top level OU from a AD user via Powershell

active-directorypowershell

I am asking this question mainly to ask if there is a better way to do what I have working. I would also like to know if anyone sees any issues with getting this information this way.

I am trying to get the top level OU that a user is in, and any lower level OUs. The main problem is that we have multiple sites, some of which have multiple layers of OUs for user accounts (ou=doctors,ou=Users,ou=Site,dc=example,dc=com), and some sites that just have a single OU (ou=Users,ou=Site,dc=example,dc=com). I used the script below to get the DN path, split it, and rebuild it backwards with the last three pieces. Can anyone see any issues with doing it this way. Something about it just feels wrong….

$user = Get-ADUser CKnutson
$user.DistinguishedName
# Returns: CN=Cory Knutson,OU=IT,OU=Users,OU=Site,DC=example,DC=com

$split = $user.DistinguishedName.Split(',')
$path = "$($split[-3]),$($split[-2]),$($split[-1])"

Write-Host $path
# Returns: OU=Site,DC=example,DC=com

Just to state, the end goal was for me to get the path to the "Disabled" OU that we have just inside of each of the "Site" OUs. So my scripting could move the object when disabling the account to the proper place, in that site's top level OU (OU=Disabled,OU=Site,DC=example,DC=com).

Best Answer

Can anyone see any issues with doing it this way.

Yes, I see two immediate problems that might arise from your current approach.


1. Escaped commas

Consider an OU with a comma in its name, like: OU=Users\, Admin,DC=corp,DC=example

Your use of string.Split() won't care about the escape sequence and you end up with:

 Admin,DC=corp,DC=example

Use the -split regex operator with a lookbehind to make sure you ignore escaped commas:

$parts = $user.DistinguishedName -split '(?<!\\),'

2. Portability

Your code assumes that the NC part of the DN (eg. DC=example,DC=com), will always be just 2 labels wide. This means your code will fail if you use it in scripts you might want to reuse in other domains/environments.

I would grab each part, from right-to-left until I find one without the DC RDN prefix:

$topParts = foreach($part in $parts[-1..-$parts.Length]){
    $part
    if($part -notlike 'DC=*'){
        break
    }
}
# Remember to reverse the RDNs again
$path = $topParts[-1..-$topParts.Length] -join ','