Active Directory (AD) administrators are well aware that there are multiple names for individual AD objects. For example, users log on to the system using a logon name (username or domain\username) or user principal name (UPN—username@domainname), but there are other names for the account object, such as its distinguished name (DN—e.g., CN=Joe User,OU=Users,DC=fabrikam,DC=com) or canonical name (e.g., fabrikam.com/Users/Joe User). Because AD objects have multiple names, it's helpful to be able to translate between them. To meet this need, Microsoft created the NameTranslate COM object, which quickly translates AD object names between different name formats.

Using the NameTranslate Object in VBScript

The NameTranslate object is fairly straightforward to use in VBScript. Listing 1 shows a bare-bones example of how you would use the object to translate a name in NT4 format (i.e., domain\username) into DN format.

Listing 1: Using the NameTranslate Object in VBScript
Const ADS_NAME_INITTYPE_GC = 3
Const ADS_NAME_TYPE_NT4 = 3
Const ADS_NAME_TYPE_1779 = 1

Dim NameTranslate
Set NameTranslate = CreateObject("NameTranslate")

NameTranslate.Init ADS_NAME_INITTYPE_GC, ""

Dim NT4Name, DN

NT4Name = "fabrikam\pflynn"

NameTranslate.Set ADS_NAME_TYPE_NT4, NT4Name

DN = NameTranslate.Get(ADS_NAME_TYPE_1779)

' CN=Phineas Flynn,OU=Engineers,DC=fabrikam,DC=com
WScript.Echo DN

The code in Listing 1 initializes the NameTranslate object to locate a global catalog (GC), sets the NT4 name (NT4Name), and gets the object's DN. The NameTranslate documentation describes the object's methods and numeric constants used in this code.

Using the NameTranslate Object in PowerShell

Unfortunately, using the NameTranslate object isn't straightforward in Windows PowerShell because the object lacks a type library that tells PowerShell what its properties and methods are. The PowerShell session in Figure 1 illustrates the problem. You can create the object, but you can't call its Init method directly like you can in VBScript. (If the NameTranslate COM object had a type library, the code in Figure 1 would work as expected.)

Figure 1: Problem Encountered When Using the NameTranslate Object in PowerShell

Fortunately, the Microsoft .NET Framework provides an alternative way to call a COM object's methods using the InvokeMember method. The InvokeMember method allows PowerShell to call a COM object's methods indirectly, even if the COM object lacks a type library. Listing 2 shows the PowerShell equivalent of the VBScript code in Listing 1.

Listing 2: Using the NameTranslate Object in PowerShell
$ADS_NAME_INITTYPE_GC = 3
$ADS_NAME_TYPE_NT4 = 3
$ADS_NAME_TYPE_1779 = 1

$NameTranslate = New-Object -ComObject "NameTranslate"

$NameTranslate.GetType().InvokeMember("Init",
  "InvokeMethod", $NULL, $NameTranslate,
  ($ADS_NAME_INITTYPE_GC, "")) | Out-Null

$NT4Name = "fabrikam\pflynn"

$NameTranslate.GetType().InvokeMember("Set",
  "InvokeMethod", $NULL, $NameTranslate,
  ($ADS_NAME_TYPE_NT4, $NT4Name)) | Out-Null

$NameTranslate.GetType().InvokeMember("Get",
  "InvokeMethod", $NULL, $NameTranslate,
  $ADS_NAME_TYPE_1779)

As you can see, the code in Listing 2 is harder to read than its VBScript equivalent because of the call to the .NET InvokeMember method. You can make this code somewhat easier to read by using a function that implements the InvokeMember method. Listing 3 is the equivalent of Listing 2, except that it creates the Invoke-Method function to handle the calls to the InvokeMember method.

Listing 3: Using a Function to Implement the InvokeMember Method in PowerShell
$ADS_NAME_INITTYPE_GC = 3
$ADS_NAME_TYPE_NT4 = 3
$ADS_NAME_TYPE_1779 = 1

function Invoke-Method([__ComObject] $object,
  [String] $method, $parameters) {
  $output = $object.GetType().InvokeMember($method,
    "InvokeMethod", $NULL, $object, $parameters)
  if ( $output ) { $output }
}

$NameTranslate = New-Object -ComObject "NameTranslate"

Invoke-Method $NameTranslate "Init" ($ADS_NAME_INITTYPE_GC,
  "")

$NT4Name = "fabrikam\pflynn"

Invoke-Method $NameTranslate "Set" ($ADS_NAME_TYPE_NT4,
  $NT4Name)

Invoke-Method $NameTranslate "Get" ($ADS_NAME_TYPE_1779)

In the Invoke-Method function, you might notice something odd: a seemingly unnecessary if statement at the end. If you run the code in Listing 3 without this if statement, PowerShell will appear to output a string (the translated name), but in fact, the output will be an object of type System.RuntimeType. This if statement prevents PowerShell from including an unnecessary null object in its output stream.

Another thing to note about the code in Listing 3 is that when it the calls the Invoke-Method function, it encloses the function's third parameter in parentheses. The Invoke-Method function's third parameter, which corresponds to the InvokeMember method's fifth parameter, can be an array if the method it's invoking requires more than one parameter. The parentheses are technically not necessary in the final line of code, but I included them to be consistent.

Making Things Easier

As you've seen, it's possible to use the NameTranslate object in PowerShell, but it's a nuisance because you can't call its methods directly. This nuisance is mitigated somewhat by using a function like Invoke-Method, which makes the code slightly easier to read and understand. However, "slightly easier" wasn't good enough for me, so I encapsulated the power of the NameTranslate object in a script. Translate-ADName.ps1 provides easy, cmdlet-like access to the NameTranslate COM object.

Introducing Translate-ADName.ps1

Translate-ADName.ps1, which you can download by clicking the zip file at the top of the article, requires PowerShell 2.0 or later. The script's syntax is as follows:

Translate-ADName.ps1 [-OutputType] <String>
  [-Name] <String[]> [-InputType <String>]
  [-InitType <String>] [-InitName <String>]
  [-ChaseReferrals] [-Credential <PSCredential>]

The -OutputType parameter is required and specifies the name format for the script's output. It's the first positional parameter, so its parameter name (-OutputType) isn't required. Table 1 lists the possible values for the -OutputType parameter.

Table 1: Possible Values for the -OutputType and -InputType Parameters

The -Name parameter is also required and specifies the name to translate. You can't use wildcard characters, but you can specify more than one name if you separate them with commas. If a name contains spaces, you must enclose it in single quotes (') or double quotes ("). The -Name parameter is the second positional parameter, so its parameter name (-Name) isn't required. The -Name parameter also supports pipeline input.

The -InputType parameter specifies the format of the names used with the -Name parameter. Table 1 lists the possible values for the -InputType parameter. Note that -InputType supports two additional values compared to -OutputType. The -InputType parameter's default value is unknown (i.e., the system will estimate the format).

The -InitType parameter specifies how the script will initialize the NameTranslate object. This parameter can be one of three possible string values:

  • GC. The script will locate a GC server and use it to translate names. This is the default value.
  • domain. The script will use the domain name specified by the -InitName parameter to translate names.
  • server. The script will use the server name specified by the -InitName parameter to translate names.

The -InitName parameter specifies the name of the domain or server to use to translate names. The -InitName parameter is ignored if -InitType is GC, but it's required if -InitType is either domain or server.

If you specify the -ChaseReferrals parameter, the script enables referral chasing. See the TechNet article "LDAP Referrals" for more information about referrals. The default is to not chase referrals.

The -Credential parameter specifies the credentials to use when translating names. This is useful when the account running the script can't access the domain, such as when you're logged on using a computer's local account rather than a domain account. This parameter uses a PSCredential object, which securely stores the credential's password. Even though the password is stored securely in the PSCredential object, the Translate-ADName.ps1 script has to temporarily decode the password in memory before using it, so there's a remote possibility of password exposure if this portion of memory gets written to disk during a hibernation operation or system crash.

Using Translate-ADName.ps1

Let's look at a few examples of how you use Translate-ADName.ps1 to translate AD object names between different name formats. If you want to translate a single canonical name into a DN, you'd use:

Translate-ADName -OutputType DN `
  -Name "fabrikam.com/Engineers/Phineas Flynn"

Note that you can omit the -OutputType and -Name parameter names because they're the script's first two positional parameters, like this:

Translate-ADName DN "fabrikam.com/Engineers/Phineas Flynn"

Because you didn't specify the -InputType parameter, the script uses the default value unknown (i.e., the NameTranslate object will estimate the input name format). Also, since you didn't specify -InitType, the script uses GC as the default value.

This command is rather useful when you've opened an object in the Microsoft Management Console (MMC) Active Directory Users and Computers snap-in and you want its DN. When Advanced Features is enabled, the Object tab displays the canonical name of the object. You can copy the canonical name to the clipboard and use Translate-ADName.ps1 to easily translate it into a DN.

If you need to translate a name using a specific server, you'd use a command such as:

Translate-ADName canonical FABRIKAM\pflynn `
  -InitType server -InitName fabdc1

In this case, the script will use the server named fabdc1 to get the canonical name of the specified user object.

If you have a lot of names to translate, you can put them into a text file (one name per line) and use the Get-Content cmdlet to retrieve each name. For example, if you want to use specific credentials to translate a list of DNs into canonical names, you'd use a command like:

Get-Content Names.txt | Translate-ADName `
  -OutputType canonical -InputType DN `
  -Credential (Get-Credential)

When you run this command, PowerShell will first prompt you for the credentials it should use because the Get-Credential cmdlet runs first. After you enter the credentials, the script will retrieve the contents of the file Names.txt, which you're saying contains a list of DNs (-InputType DN). The parameter -OutputType canonical tells the script to output canonical names.

Suppose that you need to list the names of all the user accounts in your domain and they need to be sorted by container. The AD cmdlet Get-ADUser makes it easy to retrieve a list of the DNs of user objects in the current domain:

Get-ADUser -Filter 'Name -like "*"' |
  Select-Object -ExpandProperty DistinguishedName

But what if you want to sort this list by container? You can't, because the CN attribute appears first in a DN. Consider the following DNs:

   CN=Phineas Flynn,OU=Engineers,DC=fabrikam,DC=com
   CN=Perry the Platypus,OU=Secret Agents,DC=fabrikam,DC=com

If you sort these two DNs, Perry the Platypus will get sorted before Phineas Flynn. In order to sort the names by container, you need these names in canonical format, which will now sort properly:

   fabrikam.com/Engineers/Phineas Flynn
   fabrikam.com/Secret Agents/Perry the Platypus

By using the Translate-ADName.ps1 script to translate the names provided by Get-ADUser cmdlet, then using Sort-Object cmdlet to sort them, you can get the desired result:

Get-ADUser -Filter 'Name -like "*"' |
  Select-Object -ExpandProperty DistinguishedName |
  Translate-ADName canonical | Sort-Object

Name Translation Made Easy

The NameTranslate object is a really useful tool when you need to translate AD object names between different formats. Unfortunately, this object is awkward to use from PowerShell because it lacks a type library. The Translate-ADName.ps1 script eliminates this awkwardness and makes it simple to take advantage of this useful object in PowerShell.