Работать с Active Directory из PowerShell гораздо удобнее с использованием командлетов из одноименного модуля. Однако, если на каком-либо компьютере этот модуль не установлен и PowerShell Remoting нам в данный момент по каким-то причинам недоступен, мы можем использовать ADSI — Active Directory Service Interfaces.
Когда мы обращаемся к Active Directory при помощи ADSI, например, так
[adsi]"LDAP://CN=UserName,CN=Users,DC=domain,DC=com" | Format-List -Property *
большинство атрибутов выглядят так же, как если бы мы использовали командлет Get-ADUser из модуля ActiveDirectory. Но есть и несколько исключений. Одним из них является атрибут ObjectSID.
Если мы введем следующий код, то увидим, что представлен он будет в виде массива байтов.
$user = [adsi]"LDAP://CN=UserName,CN=Users,DC=domain,DC=com" $user.ObjectSID
Стоит сказать, что это тот формат, в котором он хранится в Active Directory. Однако нам будет гораздо удобнее, если он будет представлен в виде привычной нам строки SID.
Давайте напишем функцию для его конвертации из массива байтов в формат строки. Назовем ее ConvertTo-sthSID.
function ConvertTo-sthSID { }
В качестве параметра укажем $ByteArray. Сделаем его обязательным, так как в случае его отсутствия функции просто нечего будет делать. Кроме того, предоставим возможность передавать его значение функции по конвейеру.
function ConvertTo-sthSID { Param( [Parameter(Mandatory=$true,ValueFromPipeline=$true)] $ByteArray ) }
Так как мы собираемся реализовывать конвейерную обработку, нам пригодится использование блоков begin, process и end.
Начнем с begin. Внутри этого блока мы инициализируем переменную $Stream, в которой будет находиться весь массив байтов, вне зависимости от того, как именно он был передан функции — с использованием параметра -ByteArray или по конвейеру.
function ConvertTo-sthSID { Param( [Parameter(Mandatory=$true,ValueFromPipeline=$true)] $ByteArray ) begin { $Stream = @() } }
Отличие тут состоит в следующем. При передаче массива посредством параметра, переменная $ByteArray будет содержать весь массив целиком. То есть, в этом случае он будет готов к дальнейшей обработке без каких-либо дополнительных действий.
С другой стороны, если для передачи массива мы использовали конвейер, то в данном случае каждый элемент массива будет передаваться по очереди. Таким образом, перед тем как приступить к его обработке, нам потребуется получить все его элементы и собрать их вместе в одной переменной.
Для этого нам понадобится блок process.
function ConvertTo-sthSID { Param( [Parameter(Mandatory=$true,ValueFromPipeline=$true)] $ByteArray ) begin { $Stream = @() } process { } }
Так как блок process выполняется для каждого поступившего по конвейеру элемента, сборку отдельных байтов в один массив мы будем производить в нем.
Однако строку, отвечающую за сборку массива мы поместим в выражение foreach. Сделаем мы это для того, чтобы когда мы используем не конвейер, а параметр -ByteArray, наш входящий массив байтов так же копировался бы в переменную $Stream.
function ConvertTo-sthSID { Param( [Parameter(Mandatory=$true,ValueFromPipeline=$true)] $ByteArray ) begin { $Stream = @() } process { foreach ($Byte in $ByteArray) { $Stream += $Byte } } }
Основная обработка у нас будет происходить в блоке end.
function ConvertTo-sthSID { Param( [Parameter(Mandatory=$true,ValueFromPipeline=$true)] $ByteArray ) begin { $Stream = @() } process { foreach ($Byte in $ByteArray) { $Stream += $Byte } } end { } }
Но перед этим нам бы пригодилось описание формата SID. Найти мы его можем на сайте MSDN — https://msdn.microsoft.com/en-us/library/cc230371.aspx.
Мы знаем, что строка начинается с символов S-. Далее расположено значение Revision, что представлено первым байтом и, в соответствии с документацией, должно равняться 0x01.
Значение второго байта не отображается в строке напрямую и обозначает количество четырехбайтовых блоков, представляющих из себя секцию SubAuthority.
Байты с третьего по восьмой обозначают IdentifierAuthority, хотя обычно первые пять из них представлены нулями. Таким образом и мы будем использовать только последний.
function ConvertTo-sthSID { Param( [Parameter(Mandatory=$true,ValueFromPipeline=$true)] $ByteArray ) begin { $Stream = @() } process { foreach ($Byte in $ByteArray) { $Stream += $Byte } } end { # Revision and IdentifierAuthority $Result = "S-{0}-{1}" -f $Stream[0], $Stream[7] } }
Далее расположены четырехбайтовые блоки SubAuthority в количестве, указанном во втором байте массива. В идентификаторах безопасности пользователей и компьютеров их пять. В так называемых well-known идентификаторах возможны варианты.
Младшим является первый байт в блоке, старшим, соответственно, последний.
Для того, чтобы обработать все имеющиеся в массиве блоки воспользуемся конструкцией for.
function ConvertTo-sthSID { Param( [Parameter(Mandatory=$true,ValueFromPipeline=$true)] $ByteArray ) begin { $Stream = @() } process { foreach ($Byte in $ByteArray) { $Stream += $Byte } } end { # Revision and IdentifierAuthority $Result = "S-{0}-{1}" -f $Stream[0], $Stream[7] # SubAuthority for ($i = 0; $i -lt $Stream[1]; $i++) { } } }
Внутри мы определим переменную $off, указывающую смещение для каждого обрабатываемого блока.
Во второй строке мы вычисляем значение, представленное четыремя байтами и добавляем его к создаваемой строке. Причем второй байт мы сдвигаем влево на 8 бит, третий — на 16, а четвертый — на 24, получая таким образом 32-битовое число, которое затем представляем в десятичном виде. Для того, чтобы байты при конвертации не превращались в отрицательные числа, в качестве типа данных мы указываем [uint32].
function ConvertTo-sthSID { Param( [Parameter(Mandatory=$true,ValueFromPipeline=$true)] $ByteArray ) begin { $Stream = @() } process { foreach ($Byte in $ByteArray) { $Stream += $Byte } } end { # Revision and IdentifierAuthority $Result = "S-{0}-{1}" -f $Stream[0], $Stream[7] # SubAuthority for ($i = 0; $i -lt $Stream[1]; $i++) { $off = $i * 4 $Result = "$Result-{0}" -f $([uint32]$Stream[8 + $off] -bor ([uint32]$Stream[9 + $off] -shl 8) -bor ([uint32]$Stream[10 + $off] -shl 16) -bor ([uint32]$Stream[11 + $off] -shl 24)) } return $Result } }
Результат преобразования, расположенный в переменной в $Result, мы возвращаем при помощи ключевого слова return.
Полный текст функции выглядит следующим образом.
function ConvertTo-sthSID { Param( [Parameter(Mandatory=$true,ValueFromPipeline=$true)] $ByteArray ) begin { $Stream = @() } process { foreach ($Byte in $ByteArray) { $Stream += $Byte } } end { # Revision and IdentifierAuthority $Result = "S-{0}-{1}" -f $Stream[0], $Stream[7] # SubAuthority for ($i = 0; $i -lt $Stream[1]; $i++) { $off = $i * 4 $Result = "$Result-{0}" -f $([uint32]$Stream[8 + $off] -bor ([uint32]$Stream[9 + $off] -shl 8) -bor ([uint32]$Stream[10 + $off] -shl 16) -bor ([uint32]$Stream[11 + $off] -shl 24)) } return $Result } }
Теперь мы можем ее использовать следующим образом:
$user = [adsi]"LDAP://CN=UserName,CN=Users,DC=domain,DC=com" ConvertTo-sthSID -ByteArray $user.ObjectSID
или же так:
$user = [adsi]"LDAP://CN=UserName,CN=Users,DC=domain,DC=com" $user.ObjectSID | ConvertTo-sthSID
Эта функция является частью модуля sthLDAPTools, который вы можете установить из PowerShell Gallery при помощи следующей команды:
Install-Module -Name sthLDAPTools
Все модули доступны по следующей ссылке:
https://sergeyvasin.net/modules/
Страницы в социальных сетях:
Twitter: https://twitter.com/vsseth
Facebook: https://fb.com/inpowershell
VKontakte: https://vk.com/inpowershell