#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 | |
} | |
} |
Works like a charm! Thank you !
BUG: fails for objects inside containers i.e. DN like "CN=name, CN=name,..."
'CN=HealthMailbox355a526d6d2d4e988242cf58345f188d,CN=Monitoring Mailboxes,CN=Microsoft Exchange System Objects,DC=domain,DC=com' | ConvertFrom-DN
a version of script that works for objects inside Containers:
function ConvertFromDN {
param (
[Parameter(Mandatory, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[string]$dn
)
process {
if ($dn) {
$d = ""; $p = "";
$dn -split '(?<!\\),' | ForEach-Object { if ($_ -match '^DC=') { $d += $_.Substring(3) + '.' } else { $p = $_.Substring(3) + '\' + $p } }
Write-Output ($d.Trim('.') + '\' + $p.TrimEnd('\'))
}
}
}
'CN=HealthMailbox355a526d6d2d4e988242cf58345f188d,CN=Monitoring Mailboxes,CN=Microsoft Exchange System Objects,DC=domain,DC=com' | ConvertFromDN
a version of script that works for objects inside Containers:
Thnx, works fine! :) Tested with the Users Container.
A year later I noticed a BUG in my version of the function - it does not correctly handle the cases when comma is a part of the name.
E.g. 'CN=Sequeira\, Marcello,OU=Users,OU=2021-09,OU=Disabled,DC=domain,DC=com'
Publishing an updated version of my function that now works with commas as well.
function ConvertFromDN {
param (
[Parameter(Mandatory, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[string]$dn
)
process {
if ($dn) {
$d = ''; $p = '';
$dn -split '(?<!\\),' | ForEach-Object { if ($_ -match '^DC=') { $d += $_.Substring(3) + '.' } else { $p = $_.Substring(3) + '\' + $p } }
Write-Output ($d.Trim('.') + '\' + ($p.TrimEnd('\') -replace '\\,',','))
}
}
}
'CN=Sequeira\, Marcello,OU=Users,OU=2021-09,OU=Disabled,DC=domain,DC=com' | ConvertFromDN
'CN=HealthMailbox355a526d6d2d4e988242cf58345f188d,CN=Monitoring Mailboxes,CN=Microsoft Exchange System Objects,DC=domain,DC=com' | ConvertFromDN
Handy functions guys. Saved my bacon when Get-ADComputer decided to return the CanonicalName as null for one specific AD entry !
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
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.
$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
Brilliant, this helped like a charm.