Добавьте сценарий Get-ADPathname.ps1 в свой арсенал инструментов

Всем администраторам Active Directory (AD) приходится иметь дело с путями к объектам в AD. Обычно эти пути представляют собой различающиеся имена (DN) объектов в каталоге (например, «CN=Ken Dyer,CN=Users,DC=fabrikam,DC=com»). По разным причинам фрагменты этих имен иногда требуется извлекать. Например, синтаксический разбор строк часто выполняют для того, чтобы получить родительский контейнер именованного объекта AD. Один из способов это сделать — найти первую запятую в DN и извлечь остаток строки после запятой, как показано на экране 1.

 

Анализ строки с целью получения родительского контейнера именованного объекта AD
Экран 1. Анализ строки с целью получения родительского контейнера именованного объекта AD

Однако у простого разбора строки есть свои ограничения. В частности, запятая не является незаконным символом в DN, но ее необходимо экранировать с помощью символа обратной косой черты. То есть если имя объекта «Dyer, Ken» вместо «Ken Dyer», то DN — «CN=Dyer, Ken,CN=Users,DC=fabrikam,DC=com». Результатом разбора строки по методу, показанному на экране 1, будет «Ken,CN=Users,DC=fabrikam,DC=com» (с ведущим пробелом) из-за запятой в первом элементе DN («CN=Dyer, Ken»).

Запятые — не единственные символы, нуждающиеся в экранировании. Также необходимо экранировать косую черту (/) при задании DN для использования с Active Directory Service Interfaces (ADSI). Например, рассмотрим команду:

$user = [ADSI] «LDAP://CN=HRUser,OU=H/R,DC=fabrikam,DC=com»

Попытка выполнить эту команду завершится неудачей, так как косая черта в имени организационной единицы (OU) («CN=Jeff Smith,OU=H/R,DC=fabrikam,DC=com») не экранирована. Чтобы команда стала работоспособной, необходимо экранировать косую черту обратной косой чертой:

$user = [ADSI] «LDAP://CN=HRUser,OU=H/R,DC=fabrikam,DC=com»

Если ни одно из имен объектов не содержит запятых, символов косой черты или других символов, требующих экранирования, синтаксический разбор строк принесет желаемые результаты (пока в AD не встретится объект, содержащий неожиданный символ). Однако эффективность сценариев не должна зависеть от конкретных соглашений об именовании в AD. Сценарии должны выполняться со всеми допустимыми именами AD, независимо от требования экранирования символов.

Надлежащим образом экранировать пути AD сложнее, чем кажется на первый взгляд, поэтому компания Microsoft предоставила COM-объект Pathname. Применить объект Pathname в сценарии VBScript довольно просто, но использовать его в Windows PowerShell не столь удобно из-за отсутствия библиотеки типов. У COM-объекта NameTranslate — такая же проблема с типами. Для взаимодействия с объектом Pathname необходимо использовать программный код, аналогичный коду для объекта NameTranslate. В частности, нужно вызвать метод InvokeMember базового объекта Pathname.

Знакомство с Get-ADPathname.ps1

Вместо того чтобы постоянно иметь дело с неуклюжим синтаксисом метода InvokeMember, я подготовил сценарий PowerShell, Get-ADPathname.ps1, который предоставляет простой способ взаимодействия с объектом Pathname. Сценарий может выполнять семь типов действий на основе указанных пользователем параметров. Эти параметры действий перечислены в таблице 1. Всем параметрам действий в таблице 1 (кроме -GetEscapedElement) также требуется параметр –Path, указывающий пути AD, которые нужно разобрать.

 

Параметры действия Get-ADPathname.ps1

Помимо параметров действия, перечисленных в таблице 1, существует набор параметров-модификаторов, влияющих на входные (-Path и -Type) и выходные (-Format, -EscapedMode и -ValuesOnly) данные сценария. Эти параметры перечислены в таблице 2. Каждый параметр действия в таблице 1 (кроме -GetEscapedElement) использует один или несколько параметров-модификаторов из таблицы 2.

 

Параметры-модификаторы Get-ADPathname.ps1

Параметр –Format, приведенный в таблице 2, имеет девять возможных значений, указывающих Get-ADPathname.ps1, как выводить пути AD. Эти значения перечислены в таблице 3.

 

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

Теперь, после знакомства с параметрами и доступными значениями, я покажу, как следует их использовать. Лучший способ это сделать — продемонстрировать способы применения каждого из семи параметров в таблице 1. Сценарий Get-ADPathname.ps1 приведен в листинге 1.

Использование параметра -Retrieve

Параметр -Retrieve извлекает пути AD в различных форматах. Это параметр по умолчанию. Его синтаксис следующий:

Get-ADPathname.ps1 [-Path ] [-Type ]
[-Retrieve] [-Format ] [-EscapedMode ]
[-ValuesOnly]

Имя параметра -Retrieve вводить не обязательно, но его можно указать для ясности. Имя параметра -Path также указывать не обязательно. В приведенных в статье командах я иногда ввожу имя параметра -Path, а в других случаях пропускаю, чтобы читатель привык к обоим способам употребления. Помимо строки, параметр -Path также принимает входные данные конвейера.

Получить пути AD полезно в нескольких случаях. Например, если имеется список имен DN, каждое из которых нужно передать в акселератор типа [ADSI]. Если имена DN находятся в файле с именем DNs.txt, можно использовать следующую команду:

Get-Content DNs.txt | Get-ADPathname -Type DN -Retrieve `
-Format X500 -EscapedMode On | ForEach-Object {
([ADSI] $_).displayName)
}

Акселератор типа [ADSI] требует полного пути LDAP для каждого имени DN в списке (-Format X500) и соответствующего экранирования (-EscapedMode On). В результате работы команды выводится атрибут displayName для каждого DN в файле DNs.txt. Обратите внимание, что можно было бы пропустить omitted -Type DN, -Retrieve и -Format X500, так как все эти параметры имеют значения по умолчанию, но указаны для ясности.

Другой пример: имеется список полных путей LDAP (то есть LDAP://...) с escape-символами в файле с именем FullPaths.txt. Из этого списка путей LDAP нужно создать список имен DN без escape-символов. Сделать это можно с помощью следующей команды:

Get-Content FullPaths.txt |
Get-ADPathname -Type Full -Retrieve -Format X500DN `
-EscapedMode Off

Аргумент параметра -Type (Full) указывает сценарию Get-ADPathname.ps1, что каждый входной путь (то есть каждая строка в файле FullPaths.txt) представляет собой полный путь LDAP. В этом случае параметры -Format и -EscapedMode не обязательны, так как -Format X500DN и -EscapedMode Off действуют по умолчанию (указаны для ясности).

И, наконец, пример команды, которая получает имя объекта:

Get-ADPathname «CN=Ken Dyer,CN=Users,DC=fabrikam,DC=com» `
-Type DN -Retrieve -Format Leaf -ValuesOnly

Эта команда выводит строку «Ken Dyer» (без кавычек). Если в данной команде пропустить параметр -ValuesOnly, то будет выдана строка «CN=Ken Dyer» (без кавычек).

Использование параметра -AddLeafElement

Параметр –AddLeafElement добавляет конечные элементы к пути AD. Синтаксис следующий:

Get-ADPathname.ps1 [-Path] [-Type ]
-AddLeafElement [-Format ]
[-EscapedMode ] [-ValuesOnly]

Например, добавляем к пути AD два конечных элемента с использованием -AddLeafElement:

Get-ADPathname «CN=Users,DC=fabrikam,DC=com» `
-AddLeafElement «CN=Ken Dyer»,«CN=Jeff Smith»

На экране 2 показаны результаты. В большинстве случаев параметр -AddLeafElement не столь полезен, поскольку тех же целей можно достичь путем объединения строк в PowerShell, но я указал его для полноты.

 

Использование параметра -AddLeafElement
Экран 2. Использование параметра -AddLeafElement

Использование параметра -RemoveLeafElement

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

Get-ADPathname.ps1 [-Path] [-Type ]
-RemoveLeafElement [-Format ]
[-EscapedMode ] [-ValuesOnly]

Ниже приводится пример использования параметра -RemoveLeafElement для вывода имен родительского контейнера двух путей AD с включенным экранированием:

Get-ADPathname «CN=Ken Dyer,CN=Users,DC=fabrikam,DC=com»,
«CN=Jeff Smith,OU=H/R,DC=fabrikam,DC=com» `
-RemoveLeafElement -EscapedMode On

Результаты показаны на экране 3. Обратите внимание на вторую OU в выводе.

 

Использование параметра -RemoveLeafElement
Экран 3. Использование параметра -RemoveLeafElement

Использование параметра -Split

Параметр -Split разбивает путь AD и выводит отдельные элементы пути в виде списка. Синтаксис следующий:

Get-ADPathname.ps1 [-Path] –Split
[-EscapedMode ] [-ValuesOnly]

Пример параметра -Split в действии:

$userDN = «CN=Jeff Smith,OU=H/R,DC=fabrikam,DC=com»
$pathElements = Get-ADPathname $userDN -Split –ValuesOnly
«Object name: $($pathElements[0])»
«Container name: $($pathElements[1])»

В этом фрагменте исходного текста в параметре -ValuesOnly пропущены CN= и OU= фрагменты имени. Результаты показаны на экране 4.

 

Использование параметра -Split
Экран 4. Использование параметра -Split

Использование параметра -GetEscapedElement

Параметр -GetEscapedElement выводит элемент пути AD с escape-символами. Этот параметр полезен, так как позволяет не искать символы, которые следует экранировать. Синтаксис следующий:

Get-ADPathname.ps1 -GetEscapedElement

Рассмотрим команду:

Get-ADPathname -GetEscapedElement «CN=Dyer, Ken»

Эта команда выводит строку «CN=Dyer, Ken» (без кавычек).

Использование параметра -GetElement

Параметр -GetElement выводит определенный элемент из одного или нескольких путей AD. Синтаксис следующий:

Get-ADPathname.ps1 [-Path] [-Type ]
[-EscapedMode ] [-ValuesOnly]

Крайний левый элемент пути AD имеет номер 0, следующий имеет номер 1 и т.д. Этот параметр полезен, если нужно получить определенный элемент пути. Например, рассмотрим команду

Get-ADPathname -Path `
«CN=Ken Dyer,CN=Users,DC=fabrikam,DC=com» -GetElement 1

Эта команда выводит второй элемент пути: «CN=Users» (без кавычек).

Использование параметра -GetNumElements

Параметр -GetNumElements выдает число элементов в одном или нескольких путях AD. Синтаксис следующий:

Get-ADPathname.ps1 [-Path] [-Type ]
-GetNumElements

Пример использования параметра -GetNumElements:

Get-ADPathname -Path `
«CN=Ken Dyer,CN=Users,DC=fabrikam,DC=com» -GetNumElements

Эта команда выдает число 4, так как путь AD содержит 4 элемента («CN=Ken Dyer», «CN=Users», «DC=fabrikam» и «DC=com»). Параметр не обязательный, так как параметр -Split выдает число элементов, но он указывается для полноты.

Хватит разбирать пути AD вручную

.

Листинг 1. Сценарий Get-ADPathname.ps1

# Get-ADPathname.ps1
# Written by Bill Stewart (bstewart@iname.com)
# PowerShell wrapper script for the Pathname COM object.
#requires -version 2
[CmdletBinding(DefaultParameterSetName=«Retrieve»)]
param(
[parameter(ParameterSetName=«Retrieve»,Position=0,ValueFromPipeline=$TRUE)]
[parameter(ParameterSetName=«AddLeafElement»,Position=0,Mandatory=$TRUE)]
[parameter(ParameterSetName=«RemoveLeafElement»,Position=0,Mandatory=$TRUE)]
[parameter(ParameterSetName=«GetElement»,Position=0,Mandatory=$TRUE)]
[parameter(ParameterSetName=«GetNumElements»,Position=0,Mandatory=$TRUE)]
[parameter(ParameterSetName=«Split»,Position=0,Mandatory=$TRUE)]
[String[]]
$Path,
[parameter(ParameterSetName=«Retrieve»)]
[parameter(ParameterSetName=«AddLeafElement»)]
[parameter(ParameterSetName=«RemoveLeafElement»)]
[parameter(ParameterSetName=«GetElement»)]
[parameter(ParameterSetName=«GetNumElements»)]
[parameter(ParameterSetName=«Split»)]
[String] [ValidateSet(«DN",»Full«)]
$Type,
[parameter(ParameterSetName=»Retrieve«)]
[Switch]
$Retrieve,
[parameter(ParameterSetName=»AddLeafElement«,Mandatory=$TRUE)]
[String[]]
$AddLeafElement,
[parameter(ParameterSetName=»GetElement«,Mandatory=$TRUE)]
[UInt32]
$GetElement,
[parameter(ParameterSetName=»RemoveLeafElement«,Mandatory=$TRUE)]
[Switch]
$RemoveLeafElement,
[parameter(ParameterSetName=»GetNumElements«,Mandatory=$TRUE)]
[Switch]
$GetNumElements,
[parameter(ParameterSetName=»Split«,Mandatory=$TRUE)]
[Switch]
$Split,
[parameter(ParameterSetName=»Retrieve«)]
[parameter(ParameterSetName=»AddLeafElement«)]
[parameter(ParameterSetName=»RemoveLeafElement«)]
[String] [ValidateSet(»Windows«,"WindowsNoServer»,«WindowsDN»,«WindowsParent»,«X500»,«X500NoServer»,«X500DN»,«X500Parent»,«Server»,«Provider»,«Leaf»)]
$Format,
[parameter(ParameterSetName=«Retrieve»)]
[parameter(ParameterSetName=«AddLeafElement»)]
[parameter(ParameterSetName=«RemoveLeafElement»)]
[parameter(ParameterSetName=«GetElement»)]
[parameter(ParameterSetName=«Split»)]
[String] [ValidateSet(«Default»,«On",»Off«,"OffEx»)]
$EscapedMode,
[parameter(ParameterSetName=«Retrieve»)]
[parameter(ParameterSetName=«AddLeafElement»)]
[parameter(ParameterSetName=«RemoveLeafElement»)]
[parameter(ParameterSetName=«GetElement»)]
[parameter(ParameterSetName=«Split»)]
[Switch]
$ValuesOnly,
[parameter(ParameterSetName=«GetEscapedElement»,Mandatory=$TRUE)]
[String[]]
$GetEscapedElement
)
begin {
$ParamSetName = $PSCMDLET.ParameterSetName
# Determine if we're using pipeline input.
$PipelineInput = $FALSE
if ( $ParamSetName -eq «Retrieve» ) {
$PipelineInput = -not $PSBoundParameters.ContainsKey(«Path»)
}
# These hash tables improve code readability.
$InputTypes = @{
«Full» = 1
«DN" = 4
}
$OutputFormats = @{
»Windows«= 1
»WindowsNoServer«= 2
»WindowsDN«= 3
»WindowsParent«= 4
»X500«= 5
»X500NoServer«= 6
»X500DN«= 7
»X500Parent«= 8
»Server«= 9
»Provider«= 10
»Leaf«= 11
}
$EscapedModes = @{
»Default«= 1
»On«= 2
»Off«= 3
»OffEx«= 4
}
$DisplayTypes = @{
»Full«= 1
»ValuesOnly«= 2
}
# Invokes a method on a COM object that lacks a type library. If the COM
# object uses more than one parameter, specify an array as the $parameters
# parameter. The $outputType parameter coerces the function's output to the
# specified type (default is [String]).
function Invoke-Method {
param(
[__ComObject] $object,
[String] $method,
$parameters,
[System.Type] $outputType =»String«
)
$output = $object.GetType().InvokeMember($method,»InvokeMethod«, $NULL, $object, $parameters)
if ( $output ) { $output -as $outputType }
}
# Sets a property on a COM object that lacks a type library.
function Set-Property {
param(
[__ComObject] $object,
[String] $property,
$parameters
)
[Void] $object.GetType().InvokeMember($property,»SetProperty«, $NULL, $object, $parameters)
}
# Creates the Pathname COM object. It lacks a type library so we use the
# above Invoke-Method and Set-Property functions to interact with it.
$Pathname = new-object -comobject»Pathname«
# Set defaults for -Type and -Format. Use separate variables in case of
# pipeline input.
if ( $Type ) { $InputType = $Type } else { $InputType =»DN«}
if ( $Format ) { $OutputFormat = $Format } else { $OutputFormat =»X500DN«}
# Enable escaped mode if requested.
if ( $EscapedMode ) {
Set-Property $Pathname»EscapedMode«$EscapedModes[$EscapedMode]
}
# Output values only if requested.
if ( $ValuesOnly ) {
Invoke-Method $Pathname»SetDisplayType«$DisplayTypes[»ValuesOnly«]
}
# -Retrieve parameter
function Get-ADPathname-Retrieve {
param(
[String] $path,
[Int] $inputType,
[Int] $outputFormat
)
try {
Invoke-Method $Pathname»Set«($path,$inputType)
Invoke-Method $Pathname»Retrieve«$outputFormat
}
catch [System.Management.Automation.MethodInvocationException] {
write-error -exception $_.Exception.InnerException
}
}
# -AddLeafElement parameter
function Get-ADPathname-AddLeafElement {
param(
[String] $path,
[Int] $inputType,
[String] $element,
[Int] $outputFormat
)
try {
Invoke-Method $Pathname»Set«($path,$inputType)
Invoke-Method $Pathname»AddLeafElement«$element
Invoke-Method $Pathname»Retrieve«$outputFormat
}
catch [System.Management.Automation.MethodInvocationException] {
write-error -exception $_.Exception.InnerException
}
}
# -RemoveLeafElement parameter
function Get-ADPathname-RemoveLeafElement {
param(
[String] $path,
[Int] $inputType,
[Int] $outputFormat
)
try {
Invoke-Method $Pathname»Set«($path,$inputType)
Invoke-Method $Pathname»RemoveLeafElement«
Invoke-Method $Pathname»Retrieve«$outputFormat
}
catch [System.Management.Automation.MethodInvocationException] {
write-error -exception $_.Exception.InnerException
}
}
# -GetElement parameter
function Get-ADPathname-GetElement {
param(
[String] $path,
[Int] $inputType,
[Int] $elementIndex
)
try {
Invoke-Method $Pathname»Set«($path,$inputType)
Invoke-Method $Pathname»GetElement«$elementIndex
}
catch [System.Management.Automation.MethodInvocationException] {
write-error -exception $_.Exception.InnerException
}
}
# -GetNumElements parameter
function Get-ADPathname-GetNumElements {
param(
[String] $path,
[Int] $inputType
)
try {
Invoke-Method $Pathname»Set«($path,$inputType)
Invoke-Method $Pathname»GetNumElements«-outputtype»UInt32«
}
catch [System.Management.Automation.MethodInvocationException] {
write-error -exception $_.Exception.InnerException
}
}
# -Split parameter
function Get-ADPathname-Split {
param(
[String] $path,
[Int] $inputType
)
try {
Invoke-Method $Pathname»Set«($path,$inputType)
$numElements = Invoke-Method $Pathname»GetNumElements«-outputtype»UInt32«
for ( $i = 0; $i -lt $numElements; $i++ ) {
Invoke-Method $Pathname»GetElement«$i
}
}
catch [System.Management.Automation.MethodInvocationException] {
write-error -exception $_.Exception.InnerException
}
}
# -GetEscapedElement parameter
function Get-ADPathname-GetEscapedElement {
param(
[String] $element
)
try {
Invoke-Method $Pathname»GetEscapedElement«(0,$element)
}
catch [System.Management.Automation.MethodInvocationException] {
write-error -exception $_.Exception.InnerException
}
}
}
process {
# The process block uses 'if'/'elseif' instead of 'switch' because 'switch'
# replaces '$_', and we need '$_' in case of pipeline input.
#»Retrieve«is the only parameter set that that accepts pipeline input.
if ( $ParamSetName -eq»Retrieve«) {
if ( $PipelineInput ) {
if ( $_ ) {
Get-ADPathname-Retrieve $_ $InputTypes[$InputType] $OutputFormats[$OutputFormat]
}
else {
write-error»You must provide pipeline input or specify the -Path parameter.«-category SyntaxError
}
}
else {
$Path | foreach-object {
Get-ADPathname-Retrieve $_ $InputTypes[$InputType] $OutputFormats[$OutputFormat]
}
}
}
elseif ( $ParamSetName -eq»AddLeafElement«) {
$AddLeafElement | foreach-object {
Get-ADPathname-AddLeafElement $Path[0] $InputTypes[$InputType] $_ $OutputFormats[$OutputFormat]
}
}
elseif ( $ParamSetName -eq»RemoveLeafElement«) {
$Path | foreach-object {
Get-ADPathname-RemoveLeafElement $_ $InputTypes[$InputType] $OutputFormats[$OutputFormat]
}
}
elseif ( $ParamSetName -eq»GetElement«) {
$Path | foreach-object {
Get-ADPathname-GetElement $_ $InputTypes[$InputType] $GetElement
}
}
elseif ( $ParamSetName -eq»GetNumElements«) {
$Path | foreach-object {
Get-ADPathname-GetNumElements $_ $InputTypes[$InputType]
}
}
elseif ( $ParamSetName -eq»Split«) {
Get-ADPathname-Split $Path[0] $InputTypes[$InputType]
}
elseif ( $ParamSetName -eq»GetEscapedElement" ) {
$GetEscapedElement | foreach-object {
Get-ADPathname-GetEscapedElement $_
}
}
}