Windows Server для самых маленьких. Часть 13: Управляем Active Directory из PowerShell
Все части серии «Windows Server для самых маленьких»Модулем ActiveDirectory мы пользуемся с части 4, и до сих пор он был просто набором команд, которые делают то, что написано в их имени. Пора разобрать его как инструмент: через что он говорит с контроллером, как устроены его фильтры, почему -Properties * — плохая привычка, и что делать с теми объектами каталога, для которых отдельного командлета нет. Эта часть — справочник, к которому стоит возвращаться; большинство примеров из следующих частей будут опираться на неё.
ADWS
Модуль не говорит по LDAP. Он обращается к Active Directory Web Services — службе ADWS на контроллере, порт TCP 9389, которая принимает запросы по протоколам WS-Transfer и WS-Enumeration (SOAP поверх транспорта net.tcp из .NET) и уже сама читает и пишет каталог. Служба появилась в Windows Server 2008 R2 вместе с модулем, и на контроллеры старше её ставили отдельно под именем Active Directory Management Gateway Service.
Из этого три следствия. -Server у командлетов модуля должен указывать на контроллер с работающей ADWS; если службы там нет или она остановлена, ответ будет «Unable to contact the server. This may be because this server does not exist, it is currently down, or it does not have the Active Directory Web Services running.», а если -Server не указан и DC Locator не нашёл ни одного контроллера с ADWS — «Unable to find a default server with Active Directory Web Services running». Модуль не умеет работать с LDAP-серверами, которые не Active Directory (OpenLDAP, 389 Directory Server): для них — классы System.DirectoryServices из .NET; экземпляры AD LDS он обслуживает, у них своя ADWS. И запросы модуля не видны в LDAP-захвате: смотреть их нужно либо на порту 9389, где они зашифрованы, либо по событию 1644 на контроллере, как в части 5, — ADWS внутри контроллера всё равно выполняет обычный LDAP-поиск.
PS C:\> Get-Service ADWS | Select-Object Status, StartType # на dc1
Status StartType
------ ---------
Running Automatic
PS C:\> (Get-Command -Module ActiveDirectory).Count
151
PS C:\> Get-Command -Module ActiveDirectory -Noun ADObject, ADServiceAccount, ADAccount, ADUser, ADGroupMember | Group-Object Noun | Sort-Object Count -Descending | Select-Object Count, Name, @{n='Verbs'; e={$_.Group.Verb -join ','}}
Count Name Verbs
----- ---- -----
8 ADObject Get,Move,New,Remove,Rename,Restore,Set,Sync
7 ADServiceAccount Get,Install,New,Remove,Set,Test,Uninstall
4 ADAccount Disable,Enable,Search,Unlock
4 ADUser Get,New,Remove,Set
3 ADGroupMember Add,Get,Remove
Get-Service здесь выполнен на самом контроллере: в PowerShell 7 у него нет параметра -ComputerName, а как выполнять команды на другом сервере — тема следующей части. Командлетов в модуле Windows Server 2025 — 151, и число растёт от версии к версии: у старых систем его меньше.
Модуль ставится вместе с инструментами роли (Install-WindowsFeature RSAT-AD-PowerShell на сервере) или с RSAT на рабочей станции, о чём в части 15. В PowerShell 7 он начиная с Windows Server 2019 и Windows 10 1809 загружается нативно — в его манифесте CompatiblePSEditions перечислены и Desktop, и Core. На более старых системах PowerShell 7 подгружает его через слой совместимости с Windows PowerShell, и объекты приходят десериализованными; включить этот режим явно можно через Import-Module ActiveDirectory -UseWindowsPowerShell.
Identity
У каждого Get-, Set-, Remove- есть параметр -Identity, первый позиционный, и он принимает любое из четырёх имён из части 3:
Get-ADUser alice # sAMAccountName
Get-ADUser 'CN=Alice Smith,OU=Sales,OU=Departments,DC=corp,DC=example,DC=com' # DN
Get-ADUser 9c3f1a2b-7d4e-4f60-8a1b-2c3d4e5f6a7b # objectGUID
Get-ADUser S-1-5-21-3623811015-3361044348-30300820-1104 # objectSid
UPN в -Identity не принимается: Get-ADUser alice@corp.example.com ответит «Cannot find an object with identity: 'alice@corp.example.com' under: 'DC=corp,DC=example,DC=com'.», а Get-ADUser -Filter 'UserPrincipalName -eq "alice@corp.example.com"' найдёт пользователя. У компьютеров -Identity принимает имя и без знака доллара, и с ним: Get-ADComputer ws1 и Get-ADComputer 'ws1$' найдут одну и ту же учётную запись. И -Identity принимает объект по конвейеру, поэтому Get-ADUser alice | Set-ADUser -Title 'Engineer' и Get-ADGroupMember Sales | Disable-ADAccount работают без промежуточных переменных.
Фильтры
-Filter — главный способ найти несколько объектов. Его язык похож на PowerShell, но это не PowerShell: строка разбирается модулем и переводится в LDAP-фильтр перед отправкой.
Get-ADUser -Filter 'Enabled -eq $true -and Department -eq "Sales"'
Get-ADComputer -Filter 'OperatingSystem -like "*Server*" -and Enabled -eq $true'
Get-ADUser -Filter 'PasswordNeverExpires -eq $true'
Get-ADUser -Filter 'LastLogonDate -lt $cutoff' -Properties LastLogonDate
Get-ADGroup -Filter 'GroupScope -eq "Universal"'
Get-ADUser -Filter *
Операторы: -eq, -ne, -like, -notlike (с *, других подстановочных знаков нет), -gt, -ge, -lt, -le, -approx, -and, -or, -not, и ещё -bor, -band для битовых полей, -recursivematch для вложенного членства. Регулярных выражений нет: -Filter 'Name -match "smith"' ответит «Error parsing query: 'Name -match "smith"' Error Message: 'Operator Not supported: -match' at position: '6'.». Три правила, на которых спотыкаются:
Переменные модуль ищет сам, но не все. Фильтр в одинарных кавычках с $cutoff внутри работает, потому что модуль сам ищет переменные в области вызова; свойство объекта самого модуля — $user.SamAccountName у результата Get-ADUser — тоже. А свойство строки из Import-Csv или хеш-таблицы уже нет: -Filter 'SamAccountName -eq $_.Login' в цикле по CSV ответит «Property: 'Login' not found in object of type: 'System.Management.Automation.PSCustomObject'.», а индексы ($list[0]) и выражения ($((Get-Date).AddDays(-90))) не разбираются вовсе. Надёжнее собирать строку: -Filter "Name -like '$prefix*'", или сначала положить значение в простую переменную.
Имена свойств — те, что показывает модуль, не всегда LDAP. Enabled и PasswordNeverExpires вычисляются из userAccountControl, и модуль переводит их в битовые LDAP-правила. Но не каждое вычисленное свойство годится для фильтра: -Filter 'LockedOut -eq $true' ответит «Searching on extended attribute 'LockedOut' is not supported with this cmdlet. Use Search-ADAccount instead.», так же откажет CannotChangePassword. LastLogonDate — это lastLogonTimestamp, переведённый в DateTime; PasswordLastSet — pwdLastSet; Created и Modified — whenCreated и whenChanged. Любой LDAP-атрибут тоже можно писать напрямую: -Filter 'pwdLastSet -eq 0'.
Сложное — в -LDAPFilter. Поиск по дате в FILETIME, цепочка членства, ANR, сравнение битов без именованного свойства — всё это проще написать LDAP-фильтром из части 5, чем подбирать синтаксис модуля:
Get-ADUser -LDAPFilter '(&(objectCategory=person)(memberOf:1.2.840.113556.1.4.1941:=CN=Domain Admins,CN=Users,DC=corp,DC=example,DC=com))'
Get-ADObject -LDAPFilter '(anr=ali)'
Область поиска: -SearchBase (DN, с которого начинать; без него — корень домена) и -SearchScope (Base, OneLevel, Subtree). Постраничный вывод включён всегда, страница — -ResultPageSize, 256 по умолчанию; -ResultSetSize ограничивает общее число результатов, когда нужны «первые десять». ADWS держит открытый перечислитель не дольше 30 минут: очень долгий конвейер, который медленно обрабатывает каждый объект, может оборваться на середине, и тогда результаты сначала собирают в переменную.
Properties
Командлет возвращает не все атрибуты, а небольшой набор по умолчанию — у Get-ADUser десять: DistinguishedName, Enabled, GivenName, Name, ObjectClass, ObjectGUID, SamAccountName, SID, Surname, UserPrincipalName. Всё остальное нужно запрашивать:
PS C:\> Get-ADUser alice | Get-Member -MemberType Property | Measure-Object | Select-Object -ExpandProperty Count
10
PS C:\> (Get-ADUser alice -Properties *).PropertyCount
106
PS C:\> Get-ADUser alice -Properties Mail, Department, PasswordLastSet, LastLogonDate, MemberOf | Select-Object Mail, Department, PasswordLastSet, LastLogonDate
Mail Department PasswordLastSet LastLogonDate
---- ---------- --------------- -------------
alice@example.com Sales 15.10.2026 9:30:11 15.10.2026 9:02:11
-Properties * на одном объекте безвреден, на десяти тысячах — это десять тысяч объектов по сотне атрибутов, включая фотографии и сертификаты, через SOAP. Правило: перечислять то, что нужно. Разница измерима и растёт вместе с доменом; на нескольких тысячах учётных записей она выглядит так:
PS C:\> (Measure-Command { Get-ADUser -Filter * -Properties * }).TotalSeconds
14.8
PS C:\> (Measure-Command { Get-ADUser -Filter * -Properties Mail, Department }).TotalSeconds
1.9
Server
Без -Server модуль берёт контроллер у DC Locator: ближайший в сайте с ADWS, и для сеанса запоминает его. С -Server можно указать контроллер (dc2, dc2.corp.example.com), домен (corp.example.com — выбор снова за Locator, но в этом домене) или глобальный каталог (corp.example.com:3268 — поиск по всему лесу с частичным набором атрибутов).
Когда это важно. Чтение после записи: объект, созданный на dc1, на dc2 появится через 15 секунд, и скрипт, который создаёт пользователя одной командой и добавляет его в группу другой, может попасть на разные контроллеры и получить «объект не найден». В скриптах, которые пишут, контроллер фиксируют — чаще всего PDC-эмулятор:
$dc = (Get-ADDomain).PDCEmulator
New-ADUser -Server $dc ...
Add-ADGroupMember -Server $dc ...
Нереплицируемые атрибуты: lastLogon, badPwdCount нужно спрашивать у каждого контроллера, как в части 5. Блокировки: Search-ADAccount -LockedOut надёжнее спрашивать у PDC, которому блокировки реплицируются срочно. И другой домен или лес: -Server partner.example.net -Credential (Get-Credential).
Создание и правка
PS C:\> $params = @{
Name = 'Carol White'
GivenName = 'Carol'
Surname = 'White'
SamAccountName = 'carol'
UserPrincipalName = 'carol@corp.example.com'
Path = 'OU=Sales,OU=Departments,DC=corp,DC=example,DC=com'
Department = 'Sales'
AccountPassword = (Read-Host -AsSecureString 'Password')
ChangePasswordAtLogon = $true
Enabled = $true
OtherAttributes = @{ extensionAttribute1 = 'EMP-1042' }
}
PS C:\> New-ADUser @params -PassThru | Select-Object DistinguishedName, Enabled
DistinguishedName Enabled
----------------- -------
CN=Carol White,OU=Sales,OU=Departments,DC=corp,DC=example,DC=com True
Хеш-таблица с @params (splatting) — стандартный способ передать два десятка параметров без строки на три экрана. -OtherAttributes принимает любые LDAP-атрибуты, для которых нет именованного параметра. Без -AccountPassword учётная запись создаётся отключённой, а с -Enabled $true команда завершится ошибкой «The password does not meet the length, complexity, or history requirement of the domain.» и учётную запись не создаст: пустой пароль нарушает политику.
Массовое создание — из CSV по конвейеру:
Import-Csv .\users.csv -Encoding UTF8 | ForEach-Object {
New-ADUser -Server $dc -Name "$($_.First) $($_.Last)" -GivenName $_.First -Surname $_.Last -SamAccountName $_.Login `
-UserPrincipalName "$($_.Login)@corp.example.com" -Path $_.OU -Department $_.Dept `
-AccountPassword (ConvertTo-SecureString $_.Password -AsPlainText -Force) -ChangePasswordAtLogon $true -Enabled $true
}
Правка — Set-ADUser с именованными параметрами для частых атрибутов (-Title, -Department, -Manager, -EmailAddress, -Enabled, -PasswordNeverExpires) и четыре параметра для остальных, те же, что у Set-ADObject в части 5: -Replace, -Add, -Remove, -Clear:
Set-ADUser carol -Title 'Account Manager' -Manager alice
Set-ADUser carol -Replace @{ extensionAttribute1 = 'EMP-1043'; info = 'Перевод 2026-10' }
Set-ADUser carol -Add @{ otherTelephone = '+1 555 0102' }
Set-ADUser carol -Clear info
Get-ADUser -Filter 'Department -eq "Sales"' | Set-ADUser -Office 'Building A, floor 3'
Перенос и переименование — Move-ADObject -TargetPath и Rename-ADObject -NewName (меняет CN; имя входа — Set-ADUser -SamAccountName). Удаление — Remove-ADUser с подтверждением. Защищённые от случайного удаления объекты сначала Set-ADObject -ProtectedFromAccidentalDeletion $false, иначе удаление ответит «Access is denied.»; New-ADOrganizationalUnit защищает каждую OU, поэтому перед Remove-ADOrganizationalUnit -Recursive защиту снимают и со всех вложенных OU: Get-ADOrganizationalUnit -SearchBase $ou -Filter * | Set-ADObject -ProtectedFromAccidentalDeletion $false.
Группы и пароли
Add-ADGroupMember -Identity Sales -Members carol, bob
Add-ADGroupMember -Identity Sales -Members (Get-ADUser -Filter 'Department -eq "Sales"')
Remove-ADGroupMember -Identity Sales -Members bob -Confirm:$false
Get-ADGroupMember -Identity Sales -Recursive | Select-Object Name, ObjectClass
Get-ADPrincipalGroupMembership carol | Select-Object Name, GroupScope
-Members принимает список: добавить сто пользователей одним вызовом быстрее, чем сто вызовов. Список только должен быть однородным: имена вперемешку с результатом Get-ADUser в одном вызове (-Members carol, bob, (Get-ADUser …)) дают «Cannot convert 'System.Object[]' to the type 'Microsoft.ActiveDirectory.Management.ADPrincipal' required by parameter 'Members'.». Get-ADGroupMember -Recursive раскрывает вложенность, но у ADWS есть предел на размер ответа (5000 членов по умолчанию, MaxGroupOrMemberEntries в конфигурации службы; тот же предел у Get-ADPrincipalGroupMembership); у групп крупнее читают атрибут member напрямую: (Get-ADGroup Sales -Properties member).member. Для «в каких группах состоит, включая вложенные» — tokenGroups из части 5 или LDAP-фильтр с цепочкой.
Пароли и состояния:
Set-ADAccountPassword carol -Reset -NewPassword (Read-Host -AsSecureString) # сбросить, не зная старого
Set-ADAccountPassword carol -OldPassword $old -NewPassword $new # сменить, зная старый: право самого пользователя
Set-ADUser carol -ChangePasswordAtLogon $true
Unlock-ADAccount carol
Disable-ADAccount carol; Enable-ADAccount carol
Search-ADAccount -LockedOut | Select-Object Name, LockedOut, LastLogonDate
Search-ADAccount -AccountInactive -TimeSpan 90.00:00:00 -UsersOnly | Select-Object Name, LastLogonDate
Search-ADAccount -PasswordExpired
Search-ADAccount -AccountExpiring -TimeSpan 7.00:00:00
Смена с -OldPassword подчиняется политике паролей целиком: раньше минимального срока пароля сервер её отклонит той же ошибкой, что и слабый пароль, — «The password does not meet the length, complexity, or history requirement of the domain.».
Search-ADAccount — командлет для вопросов «кто заблокирован, кто не входил три месяца, у кого истёк пароль», и он покрывает то, что иначе пришлось бы писать фильтрами по FILETIME; -AccountInactive считает неактивными и тех, кто не входил ни разу, — у них LastLogonDate пуст. Политика паролей домена — Get-ADDefaultDomainPasswordPolicy; гранулярные политики для отдельных групп (строже для администраторов, мягче для служебных учётных записей) — New-ADFineGrainedPasswordPolicy с приоритетом и Add-ADFineGrainedPasswordPolicySubject; что в итоге действует на пользователя — Get-ADUserResultantPasswordPolicy carol. Если ни одна гранулярная политика на пользователя не действует, эта команда ничего не выводит: действует политика домена.
Диск AD:
Модуль добавляет провайдер, и каталог становится файловой системой:
PS C:\> Set-Location 'AD:\DC=corp,DC=example,DC=com'
PS AD:\DC=corp,DC=example,DC=com> Get-ChildItem | Select-Object Name, ObjectClass
Name ObjectClass
---- -----------
Builtin builtinDomain
Computers container
Departments organizationalUnit
Domain Controllers organizationalUnit
...
PS AD:\DC=corp,DC=example,DC=com> Get-ItemProperty 'OU=Sales,OU=Departments' -Name description, whenCreated
Пути — DN; корень диска, Set-Location AD:\, показывает разделы: домен, Configuration, Schema, ForestDnsZones и DomainDnsZones. Главная польза провайдера — **права**: Get-Acl и Set-Acl работают с дескрипторами безопасности объектов каталога, и через них делается делегирование. Делегировать группе Branch Admins сброс паролей в OU филиала:
PS C:\> $ou = 'AD:\OU=Branch,OU=Departments,DC=corp,DC=example,DC=com'
PS C:\> $acl = Get-Acl $ou
PS C:\> $sid = (Get-ADGroup 'Branch Admins').SID
PS C:\> $resetPassword = [Guid]'00299570-246d-11d0-a768-00aa006e0529' # расширенное право Reset Password
PS C:\> $userClass = [Guid]'bf967aba-0de6-11d0-a285-00aa003049e2' # класс user
PS C:\> $acl.AddAccessRule((New-Object System.DirectoryServices.ActiveDirectoryAccessRule($sid, 'ExtendedRight', 'Allow', $resetPassword, 'Descendents', $userClass)))
PS C:\> Set-Acl $ou -AclObject $acl
PS C:\> (Get-Acl $ou).Access | Where-Object IdentityReference -like '*Branch Admins*' | Select-Object ActiveDirectoryRights, ObjectType, InheritanceType
ActiveDirectoryRights ObjectType InheritanceType
--------------------- ---------- ---------------
ExtendedRight 00299570-246d-11d0-a768-00aa006e0529 Descendents
GUID расширенных прав лежат в CN=Extended-Rights,CN=Configuration (атрибут rightsGuid), GUID классов и атрибутов — в схеме (schemaIDGUID). Это та же операция, что мастер делегирования в оснастке, и тот же результат, что dsacls "OU=Branch,…" /I:S /G "CORP\Branch Admins:CA;Reset Password;user", только воспроизводимый.
Get-ADObject
Для пользователей, компьютеров, групп, OU, контроллеров, сайтов, подсетей, связей сайтов, доверий, политик паролей и управляемых учётных записей у модуля есть свои командлеты. Для всего остального — Get-ADObject, New-ADObject, Set-ADObject, Remove-ADObject, которые работают с любым классом по DN и фильтру:
Get-ADObject -SearchBase 'CN=Policies,CN=System,DC=corp,DC=example,DC=com' -Filter 'objectClass -eq "groupPolicyContainer"' -Properties displayName, versionNumber
Get-ADObject -SearchBase (Get-ADRootDSE).configurationNamingContext -LDAPFilter '(objectClass=nTDSDSA)' -Properties options # объекты NTDS Settings всех контроллеров
Get-ADObject -SearchBase (Get-ADRootDSE).schemaNamingContext -Filter 'lDAPDisplayName -eq "mail"' -Properties *
New-ADObject -Name 'Printers' -Type container -Path 'OU=Branch,OU=Departments,DC=corp,DC=example,DC=com'
Get-ADObject -Filter 'isDeleted -eq $true -and Name -like "Bob*"' -IncludeDeletedObjects | Restore-ADObject
Последняя строка — восстановление из корзины AD из части 5: удалённый объект возвращается на прежнее место со всеми атрибутами и членством. Restore-ADObject -TargetPath кладёт его в другой контейнер, если прежнего уже нет.
Управляемые учётные записи служб
В части 6 SQL Server работал под svc-sql с паролем, который кто-то знает и который никогда не меняется. Модуль умеет лучше: групповые управляемые учётные записи (gMSA, Windows Server 2012): пароль длиной 240 байт генерирует и меняет раз в 30 дней сам контроллер, а получают его только те компьютеры, которым разрешено. Человек пароль не знает никогда.
PS C:\> Add-KdsRootKey -EffectiveImmediately # один раз на лес; корневой ключ службы распространения ключей (модуль Kds)
PS C:\> New-ADServiceAccount -Name svc-web -DNSHostName svc-web.corp.example.com -PrincipalsAllowedToRetrieveManagedPassword web1$ -ServicePrincipalNames 'HTTP/web.corp.example.com'
PS C:\> Install-ADServiceAccount svc-web # на web1
PS C:\> Test-ADServiceAccount svc-web # на web1
True
-EffectiveImmediately делает ключ действующим сразу только на том контроллере, где он создан: остальные получат его с репликацией, и контроллеры выжидают до 10 часов от создания ключа, прежде чем работать с gMSA, — запас на эту репликацию. В лаборатории с одним контроллером дату ставят в прошлое: Add-KdsRootKey -EffectiveTime ((Get-Date).AddHours(-10)). После этого службу, пул IIS или задачу планировщика запускают под CORP\svc-web$ с пустым паролем, и web1 получает актуальный пароль у контроллера сам. SPN на gMSA регистрируется так же, как на обычной учётной записи, и всё из части про Kerberos работает без изменений.
Как не положить контроллер
Контроллер обслуживает запросы модуля наравне с входами пользователей. Несколько правил, которые отличают скрипт администратора от инцидента:
- Фильтровать на сервере, а не в PowerShell.
Get-ADUser -Filter * | Where-Object Department -eq Salesвытягивает весь домен, чтобы оставить сорок человек;-Filter 'Department -eq "Sales"'отдаёт сорок. Индексированные атрибуты (sAMAccountName,objectGUID,objectSid,displayName,mail,userPrincipalName, всё с битом 1 вsearchFlags) работают быстро; поиск с подстрокой в начале (-like "*smith") по неиндексированному атрибуту перебирает все объекты, и событие 1644 из части 5 это покажет. -Propertiesпо списку, не*.- Пишущие операции — на одном контроллере, с
-Server. -WhatIfперед массовымSet-илиRemove-:Get-ADUser -Filter 'Department -eq "Sales"' | Remove-ADUser -WhatIfнапечатает, что было бы удалено, и это единственный способ узнать, что в фильтре опечатка, до того, как она сработает.- Запуск под обычной учётной записью с
-Credentialдля привилегированных команд, а не под администратором домена целый день: пароль спрашивается один раз в$cred = Get-Credential, и дальше-Credential $credтам, где нужны права.
Смотрим руками
Что модуль отправляет и куда:
PS C:\> Get-ADUser carol | Out-Null
PS C:\> Get-NetTCPConnection -RemotePort 9389 | Select-Object LocalAddress, RemoteAddress, State, OwningProcess
LocalAddress RemoteAddress State OwningProcess
------------ ------------- ----- -------------
192.168.1.100 192.168.1.10 Established 5124
192.168.1.100 192.168.1.10 Established 5124
192.168.1.100 192.168.1.10 Established 5124
192.168.1.100 192.168.1.10 Established 5124
Четыре соединения с dc1 на 9389 от процесса PowerShell: модуль открывает их при первой команде и переиспользует — после следующих Get-ADUser в том же сеансе остаются те же четыре, с теми же локальными портами. В Wireshark это .NET Message Framing поверх TCP с согласованием через Negotiate — внутри SOAP, и прочитать его в захвате не получится. Зато на контроллере с включённым событием 1644 виден LDAP-запрос, в который превратился фильтр:
Filter:
( & (objectCategory=CN=Person,CN=Schema,CN=Configuration,DC=corp,DC=example,DC=com) (objectClass=user) (department=Sales) )
Search scope:
subtree
Attribute selection:
distinguishedName,name,objectClass,objectGUID,objectSid,sAMAccountName,userPrincipalName,givenName,sn,userAccountControl,department
Модуль добавил objectCategory=person и objectClass=user сам — именно поэтому Get-ADUser не возвращает компьютеры, хотя класс computer наследует user. Отсюда же видно, что -Properties Department стал одним атрибутом в списке запроса, а не всеми.
Что возвращается:
PS C:\> Get-ADUser carol | Get-Member | Select-Object -First 4 TypeName, Name, MemberType
TypeName Name MemberType
-------- ---- ----------
Microsoft.ActiveDirectory.Management.ADUser Contains Method
Microsoft.ActiveDirectory.Management.ADUser Equals Method
Microsoft.ActiveDirectory.Management.ADUser GetEnumerator Method
Microsoft.ActiveDirectory.Management.ADUser GetHashCode Method
ADUser — объект со словарём атрибутов внутри: $u.Department возвращает значение атрибута, $u['department'] — то же значение в коллекции ADPropertyValueCollection (у многозначных атрибутов в ней и лежит весь список), а $u.PropertyNames перечисляет всё, что было запрошено. ADUser и ADComputer наследуют ADAccount, тот вместе с ADGroup — ADPrincipal, а все они и ADOrganizationalUnit — общий ADObject; поэтому любой из них можно передать в Set-ADObject или Move-ADObject.
И последнее — пример скрипта, который объединяет сказанное: отчёт о неактивных пользователях с последним входом по всем контроллерам, без -Properties *, с фильтром на сервере:
$dcs = (Get-ADDomainController -Filter *).HostName
$cutoff = (Get-Date).AddDays(-90)
Get-ADUser -Filter 'Enabled -eq $true -and LastLogonDate -lt $cutoff' -Properties LastLogonDate, Department |
ForEach-Object {
$u = $_
$last = ($dcs | ForEach-Object { (Get-ADUser $u -Server $_ -Properties lastLogon).lastLogon } | Measure-Object -Maximum).Maximum
[PSCustomObject]@{ Name = $u.Name; Department = $u.Department; LastLogon = [DateTime]::FromFileTime($last) }
} | Sort-Object LastLogon | Export-Csv .\inactive.csv -NoTypeInformation -Encoding UTF8
Первый фильтр отсекает по реплицируемому LastLogonDate с точностью до двух недель, и только для оставшихся скрипт опрашивает каждый контроллер за точным lastLogon. Учётные записи, которые не входили ни разу, в отчёт не попадут: атрибута lastLogonTimestamp у них нет, а сравнение с отсутствующим атрибутом в LDAP ложно. Их ищут отдельно — Get-ADUser -Filter 'LastLogonDate -notlike "*"' — или берут Search-ADAccount -AccountInactive, который их включает.
Источники
- ActiveDirectory module — справочник по всем командлетам модуля;
- Active Directory Web Services — служба ADWS, порт 9389, Active Directory Management Gateway Service и параметры службы:
MaxGroupOrMemberEntries,MaxEnumContextExpiration; - MS-ADDM: Endpoints и MC-NMF: .NET Message Framing Protocol — конечные точки ADWS, привязка net.tcp и формат кадров в захвате;
- PowerShell 7 module compatibility — модуль ActiveDirectory работает в PowerShell 7 нативно с Windows Server 2019 и Windows 10 1809;
- about_ActiveDirectory_Filter и Get-ADUser — язык
-Filter, операторы и их LDAP-эквиваленты, формы-Identity,-ResultPageSize,-SearchBase,-Server; - Search Filter Syntax — LDAP-фильтры и правило
LDAP_MATCHING_RULE_IN_CHAIN; - Last-Logon-Timestamp и Search-Flags — откуда берётся
LastLogonDateи как атрибут становится индексированным; - New-ADUser, Set-ADUser, Remove-ADOrganizationalUnit и about_Splatting — создание и правка учётных записей, защита от удаления, передача параметров хеш-таблицей;
- Add-ADGroupMember, Get-ADGroupMember и Get-ADPrincipalGroupMembership — членство в группах;
- Set-ADAccountPassword, Search-ADAccount, New-ADFineGrainedPasswordPolicy и Get-ADUserResultantPasswordPolicy — пароли, поиск учётных записей по состоянию, гранулярные политики;
- ActiveDirectoryAccessRule, Reset Password, User class и Dsacls — правило доступа, GUID расширенного права и класса, тот же результат через
dsacls; - Get-ADObject, Restore-ADObject и Active Directory Recycle Bin — объекты любого класса и восстановление удалённых;
- Group Managed Service Accounts overview, Secure group managed service accounts, Create a Key Distribution Service (KDS) Root Key и New-ADServiceAccount — gMSA, длина и срок смены пароля, корневой ключ KDS и его 10 часов;
- Use Event1644Reader.ps1 to analyze LDAP query performance — событие 1644 и дорогие запросы;
- Get-NetTCPConnection — соединения с ADWS.
Всё в этой части мы выполняли с рабочей станции, а модуль сам находил контроллер и знал, как до него добраться. У остальных модулей — DnsServer, DhcpServer, Storage, NetTCPIP — встроенного транспорта нет: их -ComputerName и -CimSession ходят через WinRM, а обычный Get-Process или Restart-Service на другом сервере без него не выполнить вовсе.
Дальше — часть 14: WinRM и PowerShell Remoting. Что такое WS-Management и как устроен слушатель; Enable-PSRemoting и что он на самом деле меняет; Enter-PSSession и Invoke-Command, сеансы и параллельное выполнение на десятках серверов; аутентификация Kerberos и почему вне домена нужен TrustedHosts; HTTPS-слушатель с сертификатом; проблема второго прыжка и её решения; и JEA — конечные точки, в которых можно выполнять только разрешённые команды.
Оставить комментарий могут только зарегистрированные пользователи.
Войдите на сайт или зарегистрируйтесь, чтобы оставить комментарий.