Часто мы ищем готовые решения, качаем софт, просим доступы — а инструмент уже лежит под рукой. У меня была рутинная задача: проверять учетки пользователей в 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 не хватает и нужно расширять схему.
