Home > Blockchain >  How can I parse distinguished names from Active Directory using Powershell to determine parent OUs?
How can I parse distinguished names from Active Directory using Powershell to determine parent OUs?

Time:04-09

I'm using Microsoft's ActiveDirectory module to retrieve and manipulate our domain users, and I need to easily determine the parent OU of the objects I'm retrieving. I've tried using -split ',' or .Split(','), but I keep running into issues with certain objects that have commas in them.

CodePudding user response:

There is no public exposed DN parser method or class built in to the .Net libraries. It does exist because it has to be there for how some of the DirectoryServices classes seem to work, but I don't know how to call it from Powershell and it's not documented.

There is the fairly popular DNParser library on NuGet, which is a .Net library for parsing and manipulating distinguished names.

First, download the package file from NuGet. The package will be called "dnparser.1.3.3.nupkg" for example, but it's just a ZIP file. Extract the contents to a folder. The package is a single library, so all we need is .\dnparser.1.3.3\lib\net5.0\CPI.DirectoryServices.dll for Powershell v5 or .\dnparser.1.3.3\lib\netstandard1.1\CPI.DirectoryServices.dll for Powershell v6 . You only need that library. Nothing else in the package is strictly necessary.

# Load the library
Add-Type -Path 'C:\Path\To\dnparser.1.3.3\lib\netstandard1.1\CPI.DirectoryServices.dll'

Get-ADUser -Filter 'Enabled -eq "True"' |
    Select-Object -First 10 |
    ForEach-Object {
        $DN = [CPI.DirectoryServices.DN]::new($_.DistinguishedName)
        [PSCustomObject]@{
            DistinguishedName = $DN.ToString()
            ParentOU = $DN.Parent.ToString()
        }
    } |
    Format-List *

You can also create the object with New-Object if you prefer that.

$DN = New-Object -TypeName CPI.DirectoryServices.DN -ArgumentList $_.DistinguishedName

There are other methods and properties in the class, but this is enough for what I need.


Warning: I have learned that DNParser, designed around RFC 2253, uses UTF-8 for encoding hex characters, while I think at least some instances of Active Directory use ISO-8859-1 (Western Latin). In short, you may have hex-escaped characters in Active Directory like ü which are escaped as \FC. These may translate to the UTF-8 unprintable character in DNParser or \EF\BF\BD because they're in an invalid range in UTF-8. The UTF-8 equivalent would be \C3\BC, but that's ó in ISO-8859-1. There does not appear to be a way to force disable this behavior.

CodePudding user response:

You could use this small helper function to parse out the RelativeDistinguishedName components in order:

function Parse-DistinghuishedName {
    # See https://docs.microsoft.com/en-us/previous-versions/windows/desktop/ldap/distinguished-names
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [string[]]$DistinguishedName
    )
    begin {
        function _UnescapeSpecial([string]$value) {
            # replace all special characters formatted as BackSlash-TwoDigitHexCode
            $match = ([regex]'(?i)\\([0-9a-f]{2})').Match($value)
            while ($match.Success) {
                $value = $value -replace "\\$($match.Groups[1].Value)", [char][convert]::ToUInt16($match.Groups[1].Value, 16)
                $match = $match.NextMatch()
            } 
            # finally, replace all backslash escaped characters
            $value -replace '\\(.)', '$1'
        }
    }
    process {
        foreach ($dn in $DistinguishedName) {
            $hash = [ordered]@{}
            # split the string into separate RDN (RelativeDistinguishedName) components
            $dn -split ',\s*(?<!\\,\s*)' | ForEach-Object {
                $name, $value = ($_ -split '=', 2).Trim()
                if (![string]::IsNullOrWhiteSpace($value)) {
                    $value = _UnescapeSpecial $value

                    switch ($name) {
                        'O'       { $hash['Organization']       = $value }
                        'L'       { $hash['City']               = $value }
                        'S'       { $hash['State']              = $value }
                        'C'       { $hash['Country']            = $value }
                        'ST'      { $hash['StateOrProvince']    = $value }
                        'UID'     { $hash['UserId']             = $value }
                        'STREET'  { $hash['Street']             = $value }
                        # these RDN's can occur multiple times, so add as arrays
                        'CN'      { $hash['Name']                = @($value) } 
                        'OU'      { $hash['OrganizationalUnit']  = @($value) }
                        'DC'      { $hash['DomainComponent']     = @($value) }
                    }
                }
            }
            $hash
        }
    }
}

Usage:

$dnHash = Parse-DistinghuishedName 'CN=R\fchmann\, Heinz ,OU=Test,OU=SubOU,DC=North America,DC=Fabrikam,DC=COM'

would result in an ordered Hashtable:

Name                           Value                                                                                                                                                
----                           -----                                                                                                                                                
Name                           {Rühmann, Heinz}                                                                                                                                     
OrganizationalUnit             {Test, SubOU}                                                                                                                                        
DomainComponent                {North America, Fabrikam, COM}  

To get the parent OU name, you just index into the .OrganizationalUnit element:

$dnHash.OrganizationalUnit[0]   # --> 'Test'  (top parent OU)
$dnHash.OrganizationalUnit[-1]  # --> 'SubOU' (direct OU)
  • Related