Часто мы ищем готовые решения, качаем софт, просим доступы — а инструмент уже лежит под рукой. У меня была рутинная задача: проверять учетки пользователей в AD. Когда менялся пароль, есть ли блокировка, не истек ли срок действия. Каждый раз — открыть ADUC, найти учетку, прокликать вкладки. Минута-две на запрос, десять запросов в день — и вот уже часы уходят в никуда.
В какой-то момент я подумал: но ведь есть PowerShell. И написал скрипт, который помогает за секунду получить состояние учетки, дату смены пароля, блокировки, контакты, подразделение. Можно сразу снять временную блокировку. Вдруг и вам пригодится.
Что получится в итоге
Скрипт, который по логину выводит 30+ атрибутов пользователя из AD. Если у пользователя временная блокировка — скрипт предложит ее снять. Один инструмент вместо десятка кликов по консолям.
Смотрим, что есть в AD
Прежде чем писать скрипт, нужно понять, какие данные доступны. Открываем PowerShell ISE (он есть в Windows из коробки) и вытаскиваем все атрибуты своей учетки:
Get-ADUser -Identity Ваш_логин -Properties * | Select-Object -Property * | ForEach-Object { ($_ -split ',') } | Out-File -FilePath "c:\$login.txt"
В файле окажется масса информации: имя, дата создания учетки, последняя смена пароля, блокировки и десятки других полей. Чтобы понять, как работать с каждым атрибутом, нужны их свойства:
Get-ADUser -Identity Ваш_логин -Properties * | Get-Member | ForEach-Object { ($_ -split ',') } | Out-File -FilePath "c:\Properties.txt"
Например, строка System.DateTime AccountLockoutTime {get;set;} говорит, что AccountLockoutTime — это дата и время. Сам атрибут хранит момент временной блокировки учетной записи (той, которую вы запросили). Зная типы данных, можно правильно их обрабатывать и форматировать.
Каркас
Начнем с основы. PowerShell, в отличие от того же Bash, поддерживает обработку исключений — грех этим не воспользоваться:
try { $login=Read-Host "Enter login" if (-not $login) { Write-Host "Пользователь не найден!" } else { GetAdUserInfo $login } } catch { Write-Warning "Что-то пошло не так: $($_.Exception.Message)" }
Функцию GetAdUserInfo я назвал по правилам хорошего тона — с глагола Get. Теперь напишем ее.
Простая версия
Для начала — базовый вариант, чтобы понять принцип:
function GetAdUserInfo { Param ( [string]$login ) try { $user=Get-AdUser $login -Properties * $user | Select-Object @{Name="Login";expression={$_.SamAccountName}},` @{Name="Отображаемое имя (ФИО)";expression={$_.DisplayName -replace "`n"," "}},` @{Name="Фамилия";expression={$_.Surname -replace "`n"," "}},` @{Name="Временная блокировка";expression={if ($_.AccountLockoutTime) { ` Write-Output "Временная блокировка с "; $locktime=$_.AccountLockoutTime; Write-Output $locktime.ToString() } else {` Write-Output "Отсутствует" }}}#, ` # @{ Другие значения }, ` бэктик нужен для переноса на новую строку, лучше чем писать в горизонт за широту монитора :) # @{ Другие значения } } catch { Write-Warning "Что-то пошло не так в функции GetAdUserInfo: $($_.Exception.Message)" } }
Здесь мы получаем все атрибуты пользователя и через хеш-таблицу формируем читаемый вывод. Со значениями можно работать сразу, например высчитать, сколько дней пользователю до смены пароля или когда он его сменил.
Если команда Get-ADUser не работает, добавьте в начало скрипта: Import-Module ActiveDirectory.
Полная версия
Универсальный скрипт для всех написать не получится — атрибуты и их свойства в ваших доменах могут отличаться. Так что открывайте ISE и построчно делайте для себя рабочий скрипт.
Пишите скрипт под конкретное рутинное действие с учетом частоты использования — где-то проще и быстрее открыть консоль для разовых действий. Здесь как в известном произведении: лучше день потерять, но потом за час долететь. Написав за вечер, использовать будете годами, экономя время и здоровье.
Вот что мне удалось вытянуть на своем рабочем месте:
Import-Module ActiveDirectory function GetAdUserInfo { Param ( [Parameter(Mandatory=$true)] [string]$login, $global:locktime=$null ) try { [int64]$nullDate=9223372036854775807 $dftPwdPolicy=Get-ADDefaultDomainPasswordPolicy -ErrorAction Stop $dftPwdChangePeriod=$dftPwdPolicy.MaxPasswordAge.Days $today=Get-Date $u=Get-AdUser $login -Properties * $u | select @{Name="Login";expression={$_.SamAccountName}},` @{Name="Отображаемое имя (ФИО)";expression={$_.DisplayName -replace "`n"," "}},` @{Name="Фамилия";expression={$_.Surname -replace "`n"," "}},` @{Name="Имя";expression={$_.GivenName -replace "`n"," "}},` @{Name="Отчество";expression={$_.OtherName -replace "`n"," "}},` @{Name="Состояние";expression={$expired=$false; if ($nullDate -ne [int64]$_.accountexpires -and 0 -ne [int64]$_.accountexpires){$expDate=[DateTime]::FromFileTime($_.accountexpires); if ($today -gt $expDate){$expired=$true}};if (!$_.Enabled){"отключена"} else {if ($expired) {"истек срок действия"} else {if ($_.PasswordLastSet -eq $null){"начальный пароль не менялся"} else { if ([datetime]($_.PasswordLastSet) -ge $today ){"просрочена смена пароля"}else {"OK"}}}}}},` @{Name="Пароль изменен (дней до смены пароля)";expression={` if (!$_.PasswordNeverExpires) {` if ($_.PasswordLastSet -ne $null){` $pwdLastSet=[datetime]($_.PasswordLastSet);` $maxPasswordAge = $dftPwdChangePeriod;` if ($_."msDS-ResultantPSO" -ne $null)` {` $PasswordPol = $_ | Get-ADUserResultantPasswordPolicy -Server $Server; ` $maxPasswordAge = ($PasswordPol).MaxPasswordAge.Days` }` $diff=(New-TimeSpan -Start $today -End ($pwdLastSet).AddDays($maxPasswordAge)).Days; ` [string]$s=($pwdLastSet).ToString('dd.MM.yyyy HH:mm:ss'); ` $s+ " ($diff)"` } else { "Не менялся" + " (0)" }` } else {"Неистекаемый"}` }` },` @{Name="Последний вход";expression={if ($null -ne $_.lastlogontimestamp ) {[DateTime]::FromFileTime($_.lastlogontimestamp).ToString('dd.MM.yyyy HH:mm:ss')}else {""}}},` @{Name="Номер телефона";expression={$_.telephoneNumber -replace "`n"," "}},` @{Name="Идентификатор почты";expression={$_.extensionName[0]}},` @{Name="Внешний адрес";expression={$_.mail}},` @{Name="Идентификатор БОСС";expression={$_.EmployeeID}},` @{Name="Табельный номер";expression={$_.employeeNumber}},` @{Name="Дата рождения";expression={$_.ExtensionAttribute4}},` @{Name="Должность";expression={$_.title -replace "`n"," "}},` @{Name="Подразделение";expression={$_.division -replace "`n"," "}},` @{Name="Департамент";expression={$_.department -replace "`n"," "}},` @{Name="Организация";expression={$_.company -replace "`n"," "}},` @{Name="Срок действия";expression={if ($nullDate -ne [int64]$_.accountexpires -and 0 -ne [int64]$_.accountexpires){[DateTime]::FromFileTime($_.accountexpires).ToString('dd.MM.yyyy HH:mm:ss')}else {""}}}, ` @{Name="OrgUnit";expression={ if ($_.distinguishedName -match "^CN=.*?,(?<OU>(CN|OU)=.*)"){$Matches.OU} else {""} }}, ` @{Name="SPN";expression={ if ($_.servicePrincipalName -ne $null){"задан"} else {""} }}, ` @{Name="Дата создания";expression={([datetime]$_.whenCreated).ToString('dd.MM.yyyy HH:mm:ss')}}, ` @{Name="Дата изменения";expression={([datetime]$_.whenChanged).ToString('dd.MM.yyyy HH:mm:ss')}}, ` @{Name="Дата попытки входа с неверным паролем";expression={if ($nullDate -ne [int64]$_.badPasswordTime -and 0 -ne [int64]$_.badPasswordTime ) {[DateTime]::FromFileTime($_.badPasswordTime).ToString('dd.MM.yyyy HH:mm:ss')}}}, ` @{Name="Количество ошибок входа";expression={$_.badPwdCount}}, ` @{Name="Количество входов";expression={$_.logonCount}}, ` @{Name="Город";expression={$_.city}}, ` @{Name="Почтовый адрес";expression={$_.postalAddress}}, ` @{Name="Комната";expression={$_.roomNumber}}, ` @{Name="IP телефон";expression={$_.ipPhone}}, ` @{Name="Политика паролей (имя)";expression={if ($_."msDS-ResultantPSO" -eq $null){"Основная"}else{($_ | Get-ADUserResultantPasswordPolicy).name}}}, ` @{Name="Временная блокировка";expression={if ($_.AccountLockoutTime) {Write-Output "блокировка с"; $global:locktime=$_.AccountLockoutTime; Write-Output $locktime.ToString() } else { Write-Output "Отсутствует" }}} } catch { Write-Warning "Что-то пошло не так в функции GetAdUserInfo: $($_.Exception.Message)" } } try { $login=Read-Host "Enter login" Write-Output "Результаты поиска атрибутов в АД у пользователя" if (-not $login) { Write-Host "Пользователь не найден!" } else { GetAdUserInfo $login if ($global:locktime -ne $null) { $request = Read-Host "`nВведите y для снятия временной блокировки или любой символ для НЕТ" if ($request -eq 'y') { Unlock-ADAccount -Identity $login Write-Output "Временная блокировка снята" } else { Write-Warning "`nУ пользователя блокировка осталась НЕ снятой $($_.Exception.Message)"} } } } catch { Write-Warning "Error: $($_.Exception.Message)" } Read-Host "`nВведите любой символ или пробел и нажмите Enter для выхода"
Важно: у вас атрибуты могут называться по-другому или вообще отсутствовать!
Бонус
Раз уж мы делаем набор инструментов для AD, вот еще один полезный скрипт. Когда нужно найти группу, но помнишь только часть названия:
Import-Module ActiveDirectory try { $group = Read-Host "Введите группу для поиска" $SearchGroups = Get-ADGroup -Filter "Name -like '*$group*'" if (-not $SearchGroups) { Write-Host "not search '$group' in AD" } else { $SearchGroups | Sort-Object | ForEach-Object { ($_ -split ',')[0] -replace '^CN=', '' } | ForEach-Object { Write-Host $_ } } } catch {Write-Warning "Error: $($_.Exception.Message)" }
Результат приходит по конвейеру: массив → сортировка → разделили по запятой, заменив пустым символом ненужный префикс 'CN=' → построчно вывели.
Теперь то же самое с алиасами — заменим ForEach-Object на %и вывод в консоль на echo:
Import-Module ActiveDirectory try { $group = Read-Host "Введите группу для поиска" $SearchGroups = Get-ADGroup -Filter "Name -like '*$group*'" if (-not $SearchGroups) { echo "not search '$group' in AD" } else { $SearchGroups | sort | % { ($_ -split ',')[0] -replace '^CN=', '' } | % { echo $_ } } } catch {Write-Warning "Error: $($_.Exception.Message)" }
Работает так же, но стало лаконичнее. Посмотреть все доступные алиасы можно командой Get-Alias | Sort-Object Name.
Итого
Часто все необходимые инструменты уже под рукой. PowerShell — это мощная штука, которую многие игнорируют. А зря. Он есть на любой Windows-машине, умеет работать с AD, файлами, сетью, реестром и не требует ни установки, ни согласований. Просто открываете ISE и делаете, что нужно.
Если хотите углубиться в тему, вот полезные статьи на Хабре:
Jump Start в PowerShell — для начального уровня;
Что такое Windows PowerShell и с чем его едят? — цикл статей для продвинутых;
Создание и использование собственных атрибутов AD в PowerShell — если стандартных атрибутов AD не хватает и нужно расширять схему.
