Skip to content

Instantly share code, notes, and snippets.

@joegasper
Last active September 28, 2023 08:17
Show Gist options
  • Star 12 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save joegasper/3fafa5750261d96d5e6edf112414ae18 to your computer and use it in GitHub Desktop.
Save joegasper/3fafa5750261d96d5e6edf112414ae18 to your computer and use it in GitHub Desktop.
Convert between DistinguishedName and CanonicalName
#Updated ConvertFrom-DN to support container objects
function ConvertFrom-DN {
[cmdletbinding()]
param(
[Parameter(Mandatory, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[ValidateNotNullOrEmpty()]
[string[]]$DistinguishedName
)
process {
foreach ($DN in $DistinguishedName) {
Write-Verbose $DN
$CanonNameSlug = ''
$DC = ''
foreach ( $item in ($DN.replace('\,', '~').split(','))) {
if ( $item -notmatch 'DC=') {
$CanonNameSlug = $item.Substring(3) + '/' + $CanonNameSlug
}
else {
$DC += $item.Replace('DC=', ''); $DC += '.'
}
}
$CanonicalName = $DC.Trim('.') + '/' + $CanonNameSlug.Replace('~', '\,').Trim('/')
[PSCustomObject]@{
'CanonicalName' = $CanonicalName;
}
}
}
}
function ConvertFrom-CanonicalUser {
[cmdletbinding()]
param(
[Parameter(Mandatory, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[ValidateNotNullOrEmpty()]
[string]$CanonicalName
)
process {
$obj = $CanonicalName.Split('/')
[string]$DN = 'CN=' + $obj[$obj.count - 1]
for ($i = $obj.count - 2; $i -ge 1; $i--) { $DN += ',OU=' + $obj[$i] }
$obj[0].split('.') | ForEach-Object { $DN += ',DC=' + $_ }
return $DN
}
}
function ConvertFrom-CanonicalOU {
[cmdletbinding()]
param(
[Parameter(Mandatory, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[ValidateNotNullOrEmpty()]
[string]$CanonicalName
)
process {
$obj = $CanonicalName.Split('/')
[string]$DN = 'OU=' + $obj[$obj.count - 1]
for ($i = $obj.count - 2; $i -ge 1; $i--) { $DN += ',OU=' + $obj[$i] }
$obj[0].split('.') | ForEach-Object { $DN += ',DC=' + $_ }
return $DN
}
}
@shivtorov
Copy link

shivtorov commented Nov 11, 2022

Found more bugs in ConvertFromDN - it was not dealing properly with escaping characters in DistinguishedName & CanonicalName.

Publishing here updated version of my ConvertFromDN function

function ConvertFromDN {
    param (
        [Parameter(Mandatory, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
        [string]$DistinguishedName
    )
    process {
        # NB: Escapaing special characters
        # https://social.technet.microsoft.com/wiki/contents/articles/5312.active-directory-characters-to-escape.aspx

        # Characters escaped in DistinguishedName: ',\#+<>;"= as well as leading/trailing spaces
        # Characters escaped in CanonicalName: /\

        # NB: This version of the fuction intentionally not taking care about decoding "\OACNF" part of RDN
        # Example demonstrating incorrect convertion:
        # 'CN=user\0ACNF:bc598594-2dd2-4525-8fd4-4aff689de511,OU=Test,DC=domain,DC=com' | ConvertFromDN

        # NB: This version of the function is not taking care about decoding non-UTF-8 characters

        if ($DistinguishedName) {
            $d = ''; $p = '';
            $DistinguishedName -split '(?<!\\),' <# ignoring escaped commas #> | ForEach-Object {
                if ($_ -match '^DC=') {
                    $d += $_.Substring(3) + '.'
                } else {
                    $escaped = $_.Substring(3)
                    $cleaned = $escaped.Replace('\,', ',').Replace('\\', '\').Replace('\#', '#').Replace('\+', '+').Replace('\<', '<').Replace('\>', '>').Replace('\;', ';').Replace('\"', '"').Replace('\=', '=') -replace '^(\\ )(.+)$', ' $2' -replace '^(.+)(\\ )$', '$1 '
                    $encoded = $cleaned.Replace('\', '\\').Replace('/', '\/')
                    $p = $encoded + '/' + $p
                }
            }
            Write-Output ($d.TrimEnd('.') + '/' + $p.TrimEnd('/'))
        }
    }
}

Local test:

'CN=\   \" 1 \+ 2 \" \= \< \#3 \> \; / \\  \ ,OU=Test,DC=domain,DC=com' | ConvertFromDN

Test against real AD:

# rename a test AD user account to have a weird name with lots of special characters and leading/trailing spaces
Get-ADUser test01 | Rename-ADObject -NewName '   " 1 + 2 " = < #3 > ; / \   '

# check all AD users to see if we fail to calculate CanonicalName correctly for some of them
Get-ADUser -Filter * -Properties CanonicalName | select DistinguishedName,CanonicalName,@{l='Calculated';e={ $_.DistinguishedName | ConvertFromDN }} | where { $_.CanonicalName -ne $_.Calculated } | fl

@joegasper
Copy link
Author

Thank you @shivtorov your latest update sent me down a rabbit hole wondering if there was a way to use what AD does to map names. With Pwsh making .NET calls possible, I came across DsCrackName and namespace calls like [System.DirectoryServices.ActiveDirectory.Domain]::GetComputerDomain() right in Pwsh. This led me to a COM object called NameTranslate that had code examples! I started writing new Pwsh based off the examples, then got smarter and found this gist - seems to work with whatever I toss at it, including your last example.

Convert-ADName.ps1

$myDN = Convert-ADName -Name test01@contoso.com -InputType UPN -OutputType DN
$myCN = Convert-ADName -Name $myDN -InputType DN -OutputType canonical

$myDN
CN=\   \" 1 \+ 2 \" \= \< \#3 \> \; / \\  ~  \ ,OU=ServiceAccounts,OU=IT,OU=Departments,DC=contoso,DC=com

$myCN
contoso.com/Departments/IT/ServiceAccounts/   " 1 + 2 " = < #3 > ; \/ \\  ~   

$myContainer = 'contoso.com/Managed Service Accounts/it-mgdsvc'
Convert-ADName -Name $myContainer -InputType canonical -OutputType DN
CN=it-mgdsvc,CN=Managed Service Accounts,DC=contoso,DC=com

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment