Администраторам Active Directory (AD) хорошо известно, что у отдельных объектов AD существует много имен. Например, пользователи регистрируются в системе с именем регистрации (username или domainusername) или полным именем пользователя (UPN — username@domainname), но существуют и другие имена для объекта учетной записи, скажем, различающееся имя (DN — например, CN=Joe User,OU=Users,DC=fabrikam,DC=com) или каноническое имя (например, fabrikam.com/Users/Joe User). Поскольку объекты AD имеют несколько имен, полезно иметь возможность выполнять преобразования между ними. Для этой цели компания Microsoft подготовила COM-объект NameTranslate, который быстро преобразует имена объектов AD между различными форматами.

Использование объекта NameTranslate в VBScript

Применение объекта NameTranslate в VBScript не составляет особого труда. В листинге 1 показан простой пример использования объекта преобразования имени в формате NT4 (domainusername) в формат DN.

Программный код в листинге 1 инициализирует объект NameTranslate для обнаружения глобального каталога (GC), назначает имя NT4 (NT4Name) и получает DN объекта. В документации NameTranslate содержится описание методов объекта и числовых констант, используемых в этом исходном тексте.

Использование объекта NameTranslate в PowerShell

К сожалению, использовать объект NameTranslate Windows PowerShell довольно сложно, так как у него нет библиотеки типов, из которой PowerShell могло бы стать известно о свойствах и методах. Сеанс PowerShell на приведенном экране иллюстрирует эту проблему. Можно создать объект, но нельзя вызвать метод Init напрямую, как в VBScript. Если COM-объект NameTranslate имеет библиотеку типов, то исходный текст на экране будет работать, как ожидается.

 

Проблема при использовании NameTranslate Object в PowerShell
Экран. Проблема при использовании NameTranslate Object в PowerShell

Однако Microsoft. NET Framework обеспечивает альтернативный способ вызова методов COM-объекта с использованием метода InvokeMember. Благодаря методу InvokeMember становится возможным непрямой вызов методов COM-объекта в PowerShell, даже если COM-объект не располагает библиотекой типов. В листинге 2 показан эквивалент PowerShell для исходного текста VBScript, приведенного в листинге 1.

Как можно заметить, исходный текст в листинге 2 труднее читать, чем эквивалентный исходный текст VBScript, из-за вызова метода. NET InvokeMember. Исходный текст можно сделать более удобным для чтения, воспользовавшись функцией, которая реализует метод InvokeMember. Листинг 3 эквивалентен листингу 2; единственное отличие — создание функции Invoke-Method для обработки вызовов метода InvokeMember.

В функции Invoke-Method можно заметить нечто необычное: на первый взгляд необязательную инструкцию if в конце. Если запустить программный код в листинге 3 без инструкции if, то будет казаться, что PowerShell выдает строку (преобразованное имя), но в действительности вывод будет представлять собой объект типа System.RuntimeType. Инструкция if мешает PowerShell включить лишний объект NULL в выходной поток.

Еще одна заслуживающая внимания особенность исходного текста в листинге 3 заключается в том, что при вызове функции Invoke-Method третий параметр функции заключается в скобки. Третий параметр функции Invoke-Method, соответствующий пятому параметру метода InvokeMember, может быть массивом, если вызываемый метод требует более одного параметра. В скобках в последней строке необходимости нет, но я поставил их ради единообразия.

Можно сделать проще

Как мы видим, использовать объект NameTranslate в PowerShell можно, но затруднительно, из-за необходимости непрямого вызова его методов. Отчасти положение можно исправить с помощью такой функции, как Invoke-Method, благодаря которой исходный текст проще читать и понимать. Однако меня «немного проще» не совсем устраивало, поэтому я заключил возможности объекта в сценарий. Translate-ADName.ps1 обеспечивает удобный доступ к COM-объекту NameTranslate, напоминающий доступ с использованием команд, см. листинг 4.

Знакомство с Translate-ADName.ps1

Сценарий Translate-ADName.ps1 работает с PowerShell 2.0 и более новыми версиями. Синтаксис сценария следующий:

Translate-ADName.ps1 [-OutputType]
[-Name] [-InputType ]
[-InitType ] [-InitName ]
[-ChaseReferrals] [-Credential ]

Параметр -OutputType необходим и указывает имя формата для вывода сценария. Это первый позиционный параметр, поэтому имя параметра (-OutputType) указывать не обязательно. В таблице показаны возможные значения параметра -OutputType.

 

Возможные значения параметров -OutputType и -InputType

Параметр -Name также обязательный и указывает преобразуемое имя. Нельзя использовать универсальные символы, но можно указать более одного имени, разделяя имена запятыми. Если имя содержит пробелы, его необходимо заключить в одиночные (') или двойные («) кавычки. Параметр -Name — второй позиционный параметр, поэтому указывать имя параметра (-Name) не обязательно. Параметр -Name поддерживает конвейерный ввод.

Параметр -InputType указывает формат имен, используемых в параметре -Name. В таблице 1 перечислены возможные значения параметра -InputType. Обратите внимание, что по сравнению с -OutputType возможны два дополнительных значения -InputType. Значение параметра -InputType по умолчанию — unknown (то есть система самостоятельно определит формат).

Параметр -InputType указывает, каким образом сценарий инициализирует объект NameTranslate. Этот параметр может иметь одно из трех возможных строковых значений.

  • GC. Сценарий обнаруживает сервер глобального каталога и использует его для преобразования имен. Это значение по умолчанию.
  • domain. Сценарий использует имя домена, указанное в параметре -InitName для преобразования имен.
  • server. Сценарий использует имя сервера, указанное в параметре -InitName для преобразования имен.

Параметр -InitName указывает имя домена или сервера, используемых для преобразования имен. Параметр -InitName игнорируется, если значение -InitType — GC, но обязателен, если -InitType имеет значение domain или server.

Если указан параметр -ChaseReferrals, то в сценарии включено прослеживание ссылкам. Более подробные сведения о ссылках приведены в статье «LDAP Referrals» на сайте TechNet. По умолчанию прослеживание ссылок отменено.

Параметр -Credential указывает учетные данные, используемые при преобразовании имен. Это полезно, если учетная запись, из которой запущен сценарий, не имеет доступа к домену (например, если пользователь выполнил регистрацию не с доменной, а с локальной учетной записью компьютера). В этом параметре используется объект PSCredential в котором надежно хранится пароль. Хотя пароль надежно хранится в объекте PSCredential, перед его использованием сценарию Translate-ADName.ps1 необходимо временно расшифровать пароль в памяти, поэтому существует небольшая вероятность раскрытия пароля, если эта часть памяти будет записана на диск во время перехода в спящий режим или системного сбоя.

Использование Translate-ADName.ps1

Рассмотрим несколько примеров использования Translate-ADName.ps1 для преобразования имен объектов AD между различными форматами. Если нужно преобразовать одно каноническое имя в DN, выполните команду:

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

Обратите внимание, что не обязательно указывать имена параметров -OutputType и -Name, потому что это два первых позиционных параметра сценария, например:

Translate-ADName DN»fabrikam.com/Engineers/Phineas Flynn«

Поскольку не указан параметр -InputType, сценарий использует значение по умолчанию unknown (то есть объект NameTranslate определит входное имя формата). Кроме того, поскольку не указано -InitType, в сценарии по умолчанию используется значение GC.

Эта команда весьма полезна, если нужно открыть объект в оснастке Active Directory Users and Computers консоли управления Microsoft (MMC) и необходимо получить его DN. Если включен режим Advanced Features, то на вкладке Object отображается каноническое имя объекта. Каноническое имя можно скопировать в буфер обмена и без труда преобразовать его в DN с помощью Translate-ADName.ps1.

Если нужно преобразовать имя с использованием определенного сервера, то введите следующую команду:

Translate-ADName canonical FABRIKAMpflynn `
-InitType server -InitName fabdc1

В этом случае сценарий будет использовать сервер с именем fabdc1, чтобы получить каноническое имя указанного объекта пользователя.

Если предстоит преобразовать много имен, можно поместить их в текстовый файл (по одному имени на строке) и извлечь каждое имя с использованием команды Get-Content. Например, если требуется использовать определенные учетные данные для преобразования списка имен DN в канонические имена, введите следующую команду:

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

После запуска этой команды PowerShell сначала запрашивает учетные данные, потому что первой выполняется команда Get-Credential. После ввода учетных данных сценарий извлекает содержимое из файла Names.txt, который содержит список имен DN (-InputType DN). Параметр -OutputType canonical указывает, что нужно вывести канонические имена.

Предположим, требуется список имен всех учетных записей пользователей в домене, и необходимо рассортировать их по контейнерам. С помощью команды Get-ADUser AD легко извлечь список имен DN объектов пользователя в текущем домене:

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

Но как рассортировать этот список по контейнерам? Сделать этого нельзя, потому что атрибут CN — первый в DN. Рассмотрим следующие имена DN:

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

Если сортировать эти два DN, то Perry the Platypus окажется впереди Phineas Flynn. Чтобы сортировать имена по контейнерам, необходимо, чтобы имена были представлены в каноническом формате, и тогда сортировка будет выполнена верно:

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

Используя сначала сценарий Translate-ADName.ps1 для преобразования имен, предоставленных командой Get-ADUser, а затем командой Sort-Object для сортировки, можно получить желаемый результат:

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

Преобразование имен — это просто

Объект NameTranslate — действительно полезный инструмент для преобразования имен объектов AD между различными форматами. К сожалению, данный объект неудобно использовать из PowerShell, так как в нем нет библиотеки типов. С помощью сценария Translate-ADName.ps1 можно устранить этот недостаток и без труда воспользоваться преимуществами данного полезного объекта в PowerShell.

Листинг 1. Использование объекта NameTranslate в 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 =»fabrikampflynn«
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

Листинг 2. Использование объекта NameTranslate в 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 =»fabrikampflynn«
$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)

Листинг 3. Использование функции для реализации метода InvokeMember в 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 =»fabrikampflynn«
Invoke-Method $NameTranslate»Set«($ADS_NAME_TYPE_NT4,
$NT4Name)
Invoke-Method $NameTranslate»Get«($ADS_NAME_TYPE_1779)

Листинг 4. Translate-ADName.ps1

# Written by Bill Stewart (bstewart@iname.com)
# PowerShell wrapper script for the NameTranslate COM object.
#requires -version 2
<#
. SYNOPSIS

Translates Active Directory names between various formats.

. DESCRIPTION

Translates Active Directory names between various formats using the NameTranslate COM object. Before names can be translated, the NameTranslate object must first be initialized. The default initialization type is 'GC' (see the -InitType parameter). You can use the -Credential parameter to initialize the NameTranslate object using specific credentials.

. PARAMETER OutputType

The output name type, which must be one of the following:

1779 RFC 1779; e.g., 'CN=Phineas Flynn,OU=Engineers,DC=fabrikam,DC=com'
DN short for 'distinguished name'; same as 1779
canonical canonical name; e.g., 'fabrikam.com/Engineers/Phineas Flynn'
NT4 domainusername; e.g., 'fabrikampflynn'
display display name
domainSimple simple domain name format
enterpriseSimple simple enterprise name format
GUID GUID; e.g., '{95ee9fff-3436-11d1-b2b0-d15ae3ac8436}'
UPN user principal name; e.g., 'pflynn@fabrikam.com'
canonicalEx extended canonical name format
SPN service principal name format
. PARAMETER Name

The name to translate. This parameter does not support wildcards.

. PARAMETER InputType

The input name type. Possible values are the same as -OutputType, with the following additions:

unknown unknown name format; the system will estimate the format
SIDorSIDhistory SDDL string for the SID or one from the object's SID history
The default value for this parameter is 'unknown'.
. PARAMETER InitType

The type of initialization to be performed, which must be one of the following:

domain Bind to the domain specified by the -InitName parameter
server Bind to the server specified by the -InitName parameter
GC Locate and bind to a global catalog

The default value for this parameter is 'GC'. When -InitType is not 'GC', you must also specify the -InitName parameter.

. PARAMETER InitName

When -InitType is 'domain' or 'server', this parameter specifies which domain or server to bind to. This parameter is ignored if -InitType is 'GC'.

. PARAMETER ChaseReferrals

This parameter specifies whether to chase referrals. (When a server determines that other servers hold relevant data, in part or as a whole, it may refer the client to another server to obtain the result. Referral chasing is the action taken by a client to contact the referred-to server to continue the directory search.)

. PARAMETER Credential

Uses the specified credentials when initializing the NameTranslate object.

. EXAMPLE
PS C:> Translate-ADName -OutputType dn -Name fabrikampflynn

This command outputs the specified domainusername as a distinguished name.

PS C:> Translate-ADName canonical 'CN=Phineas Flynn,OU=Engineers,DC=fabrikam,DC=com'

This command outputs the specified DN as a canonical name.

PS C:> Translate-ADName dn fabrikampflynn -InitType server -InitName dc1

This command uses the server dc1 to translate the specified name.

PS C:> Translate-ADName display fabrikampflynn -InitType domain -InitName fabrikam

This command uses the fabrikam domain to translate the specified name.

PS C:> Translate-ADName dn 'fabrikam.com/Engineers/Phineas Flynn' -Credential (Get-Credential)

Prompts for credentials, then uses those credentials to translate the specified name.

PS C:> Get-Content DNs.txt | Translate-ADName -OutputType display -InputType dn

Outputs the display names for each of the distinguished names in the file DNs.txt.

#>
[CmdletBinding()]
param(
[parameter(Mandatory=$TRUE,Position=0)]
[String] $OutputType,
[parameter(Mandatory=$TRUE,Position=1,ValueFromPipeline=$TRUE)]
[String[]] $Name,
[String] $InputType=»unknown«,
[String] $InitType=»GC«,
[String] $InitName=»«,
[Switch] $ChaseReferrals,
[System.Management.Automation.PSCredential] $Credential
)
begin {
# Is input coming from the pipeline (-Name parameter not bound)?
$PIPELINEINPUT = -not $PSBOUNDPARAMETERS.ContainsKey(»Name«)
# Hash table to simplify output type names and values
$OutputNameTypes = @{
»1779«= 1;
»DN«= 1;
»canonical«= 2;
»NT4«= 3;
»display«= 4;
»domainSimple«= 5;
»enterpriseSimple«= 6;
»GUID«= 7;
»UPN«= 9;
»canonicalEx«= 10;
»SPN«= 11;
}
# Collect list of output types, and throw an error if -OutputType not in the list
$OutputTypeNames = $OutputNameTypes.Keys | sort-object | foreach-object { $_ }
if ( $OutputTypeNames -notcontains $OutputType ) {
write-error»The -OutputType parameter must be one of the following values: $OutputTypeNames«-category InvalidArgument
exit
}
# Copy output type hash table and add two additional types
$InputNameTypes = $OutputNameTypes.Clone()
$InputNameTypes.Add(»unknown«, 8)
$InputNameTypes.Add(»SIDorSidHistory«, 12)
# Collect list of input types, and throw an error if -InputType not in the list
$InputTypeNames = $InputNameTypes.Keys | sort-object | foreach-object { $_ }
if ( $InputTypeNames -notcontains $InputType ) {
write-error»The -InputType parameter must be one of the following values: $InputTypeNames«-category InvalidArgument
exit
}
# Same as with previous hash tables...
$InitNameTypes = @{
»domain«= 1;
»server«= 2;
»GC«= 3;
}
$InitTypeNames = $InitNameTypes.Keys | sort-object | foreach-object { $_ }
if ( $InitTypeNames -notcontains $InitType ) {
write-error»The -InitType parameter must be one of the following values: $InitTypeNames«-category InvalidArgument
exit
}
if ( ($InitType -ne»GC«) -and ($InitName -eq»«) ) {
write-error»The -InitName parameter cannot be empty.«-category InvalidArgument
exit
}
# Accessor functions to simplify calls to NameTranslate
function invoke-method([__ComObject] $object, [String] $method, $parameters) {
$output = $object.GetType().InvokeMember($method,»InvokeMethod«, $NULL, $object, $parameters)
if ( $output ) { $output }
}
function get-property([__ComObject] $object, [String] $property) {
$object.GetType().InvokeMember($property,»GetProperty«, $NULL, $object, $NULL)
}
function set-property([__ComObject] $object, [String] $property, $parameters) {
[Void] $object.GetType().InvokeMember($property,»SetProperty«, $NULL, $object, $parameters)
}
# Create the NameTranslate COM object
$NameTranslate = new-object -comobject NameTranslate
# If -Credential, use InitEx to initialize it; otherwise, use Init
if ( $Credential ) {
$networkCredential = $Credential.GetNetworkCredential()
try {
invoke-method $NameTranslate»InitEx«(
$InitNameTypes[$InitType],
$InitName,
$networkCredential.UserName,
$networkCredential.Domain,
$networkCredential.Password
)
}
catch [System.Management.Automation.MethodInvocationException] {
write-error $_
exit
}
finally {
remove-variable networkCredential
}
}
else {
try {
invoke-method $NameTranslate»Init«(
$InitNameTypes[$InitType],
$InitName
)
}
catch [System.Management.Automation.MethodInvocationException] {
write-error $_
exit
}
}
# If -ChaseReferrals, set the object's ChaseReferral property to 0x60
if ( $ChaseReferrals ) {
set-property $NameTranslate»ChaseReferral«(0x60)
}
# The NameTranslate object's Set method specifies the name to translate and
# its input format, and the Get method returns the name in the output format
function translate-adname2([String] $name, [Int] $inputType, [Int] $outputType) {
try {
invoke-method $NameTranslate»Set«($inputType, $name)
invoke-method $NameTranslate»Get«($outputType)
}
catch [System.Management.Automation.MethodInvocationException] {
write-error»'$name' — $($_.Exception.InnerException.Message)"
}
}
}
process {
if ( $PIPELINEINPUT ) {
translate-adname2 $_ $InputNameTypes[$InputType] $OutputNameTypes[$OutputType]
}
else {
$Name | foreach-object {
translate-adname2 $_ $InputNameTypes[$InputType] $OutputNameTypes[$OutputType]
}
}
}