Аудит последнего входа в Active Directory: PowerShell, CSV и точные значения с разных контроллеров
Меня зовут Семёнов Евгений Сергеевич, я директор АйТи Фреш. За 15 лет работы с корпоративными доменами я перевидал десятки запросов «нам надо узнать, кто из сотрудников когда последний раз заходил в домен». Иногда это безопасность хочет почистить «спящие» учётки, иногда HR — сверить со списком уволенных, иногда кадровый аудит ИБ-конторы. И почти всегда штатный администратор начинает с команды Get-ADUser -Filter * -Properties LastLogon и удивляется, почему данные «какие-то странные». Сегодня разбираю, как делать это правильно, какие подводные камни прячутся в атрибутах AD и как написать одну команду, которая выдаст корректный CSV для отчёта руководству.
Почему «простой» аудит даёт неверные данные
Знаете, Active Directory хранит информацию о последнем входе, но делает это сразу в нескольких атрибутах, и, честно говоря, ни один из них не идеален. Давайте вместе разберёмся, что к чему.
- LastLogon — точное время последнего успешного входа, но хранится только на том контроллере, который аутентифицировал пользователя. На других DC значение пустое или старое. Для домена с 4 контроллерами и 500 пользователями вы получите 4 разные картины.
- LastLogonTimeStamp — реплицируемая версия с задержкой 9–14 дней (атрибут
ms-DS-Logon-Time-Sync-Interval). Видна на всех DC, но грубая. Если пользователь зашёл вчера — значение могло обновиться три дня назад. - LastLogonDate — это просто LastLogonTimeStamp, преобразованный модулем ActiveDirectory в человеческий формат DateTime. Удобно для отчётов.
- PwdLastSet — последняя смена пароля. Не аудит входов, но иногда полезно для проверки активности.
Нужен «плюс-минус две недели» — хватает LastLogonDate. Нужно точно «когда последний раз заходил» (например, для расследования инцидента) — придётся опрашивать все контроллеры. Я покажу оба способа.
Быстрый способ: LastLogonDate для всех пользователей
Самый простой сценарий, с которым я часто сталкиваюсь, это когда нужно узнать, кто из активных пользователей не заходил в домен дольше N дней. Это идеально подходит для вашего еженедельного аудита или для согласования с HR. Вы можете сделать это с любого контроллера домена или даже с RSAT-машины.
Import-Module ActiveDirectory
Get-ADUser -Filter {Enabled -eq $true} `
-Properties LastLogonDate, DisplayName, Department, Title |
Select-Object SamAccountName, DisplayName, Department, Title, LastLogonDate |
Sort-Object LastLogonDate |
Export-Csv "C:\Reports\AD-LastLogon-$(Get-Date -Format yyyyMMdd).csv" `
-NoTypeInformation -Encoding UTF8
Через 30 секунд получаете CSV с отсортированным списком от самых старых дат входа к самым свежим. Идеально для отчёта в Excel — пустые LastLogonDate означают «никогда не входил» (свежесозданный аккаунт или сервисная учётка).
Точный способ: опрос всех контроллеров домена
Но что делать, если нужна точность до минуты? Вот, например, когда ИБ требует узнать «во сколько именно последний раз заходил Иванов перед увольнением»? В таких случаях мы берём атрибут LastLogon. Тут есть нюанс: он не реплицируется, а значит, придётся вручную опросить каждый контроллер домена и потом выбрать максимальное значение.
$user = "ivanov"
$dcs = (Get-ADDomainController -Filter *).HostName
$logons = foreach ($dc in $dcs) {
$u = Get-ADUser -Identity $user -Server $dc -Properties LastLogon
[PSCustomObject]@{
DC = $dc
LastLogon = if ($u.LastLogon) { [DateTime]::FromFileTime($u.LastLogon) } else { $null }
}
}
$logons | Sort-Object LastLogon -Descending | Format-Table -AutoSize
$max = ($logons | Where-Object LastLogon | Measure-Object LastLogon -Maximum).Maximum
Write-Host "Last logon (real): $max" -ForegroundColor Green
Для всех пользователей сразу — то же самое в цикле, но на больших доменах это занимает время. На 1000 пользователей × 4 контроллера это 4000 LDAP-запросов, занимает 5–15 минут в зависимости от сети.
Поиск неактивных учётных записей за один запрос
А если ваша задача звучит прямо так: «дайте всех, кто не заходил 90 дней», то, к счастью, для этого есть отдельный командлет, который написан специально под такие запросы.
Search-ADAccount -AccountInactive -TimeSpan 90.00:00:00 -UsersOnly |
Where-Object {$_.Enabled -eq $true} |
Select-Object SamAccountName, Name, LastLogonDate, DistinguishedName |
Export-Csv "C:\Reports\Inactive-Users-90d.csv" -NoTypeInformation -Encoding UTF8
Командлет умный — он смотрит реплицированный LastLogonTimeStamp и работает на любом контроллере. Аналогичные команды есть для компьютеров (-ComputersOnly) и для заблокированных учёток (-LockedOut).
Чек-лист регулярного аудита неактивных учёток
В АйТи Фреш мы у клиентов настраиваем регулярный аудит по такой схеме:
- Раз в неделю — отчёт «не заходили 30 дней». Отправляем HR для сверки с увольнениями.
- Раз в две недели — отчёт «не заходили 60 дней». Согласовываем отключение учёток с непосредственным руководителем.
- Раз в месяц — отключение всех, кто не заходил 90 дней, с переносом в OU «Disabled». В описании учётки фиксируем дату отключения.
- Раз в полгода — удаление учёток, лежащих в Disabled больше 180 дней. Перед удалением — экспорт всех атрибутов и членств в группах в JSON-бэкап.
Скрипт для пункта 3, который мы запускаем по расписанию:
$inactiveDays = 90
$disabledOU = "OU=Disabled Users,DC=corp,DC=example,DC=ru"
$logFile = "C:\Logs\AD-Disable-$(Get-Date -Format yyyyMMdd).log"
Search-ADAccount -AccountInactive -TimeSpan "$inactiveDays.00:00:00" -UsersOnly |
Where-Object {$_.Enabled -eq $true -and $_.DistinguishedName -notlike "*OU=Service*"} |
ForEach-Object {
try {
Disable-ADAccount -Identity $_.SamAccountName
Move-ADObject -Identity $_.DistinguishedName -TargetPath $disabledOU
Set-ADUser -Identity $_.SamAccountName `
-Description "Disabled $(Get-Date -Format 'yyyy-MM-dd') — inactive $inactiveDays+ days"
Add-Content $logFile "OK $($_.SamAccountName) - $($_.LastLogonDate)"
} catch {
Add-Content $logFile "FAIL $($_.SamAccountName) - $_"
}
}
Send-MailMessage -From "ad-audit@itfresh.ru" -To "admin@itfresh.ru" `
-Subject "AD inactive accounts disabled $(Get-Date -Format yyyy-MM-dd)" `
-Body (Get-Content $logFile -Raw) -SmtpServer "mail.itfresh.ru"
Аудит компьютеров: забытые в домене ноутбуки
Помимо пользователей, в AD копится мусор из компьютерных учёток. Списанные ноутбуки, переустановленные ПК с новым именем, виртуалки из старых тестов. Они не безобидны — на них могут оставаться сертификаты, права в Wi-Fi, секреты Kerberos. Аудит — той же командой:
Search-ADAccount -AccountInactive -TimeSpan 60.00:00:00 -ComputersOnly |
Where-Object {$_.Enabled -eq $true} |
Select-Object Name, LastLogonDate, OperatingSystem, DistinguishedName |
Sort-Object LastLogonDate |
Export-Csv "C:\Reports\Inactive-Computers.csv" -NoTypeInformation -Encoding UTF8
На любом домене 100+ ПК этот отчёт покажет 5–15 «забытых» машин. Я обычно отключаю их сразу, а удаляю через 30 дней — на случай, если ПК просто долго лежал на полке и его принесли обратно.
Реальный кейс: 220 «спящих» учёток в торговой сети
Я хорошо помню, как в сентябре 2025 года к нам обратилась одна торговая сеть. У них было 14 розничных точек и центральный офис, а домен насчитывал 460 пользователей. Запрос от службы безопасности был очень чётким: «У нас за 4 года скопилось слишком много старых учёток, их нужно срочно почистить. Аудитор по 152-ФЗ обнаружил 8 уволенных сотрудников, у которых до сих пор активен VPN-доступ».
За полтора часа мы выгрузили полную картину:
- Активных, но не заходивших более 90 дней — 184 учётки.
- Активных, но не заходивших более 365 дней — 67 учёток.
- В группах с правами на корпоративные ресурсы из «спящих» — 39 (включая 4 учётки с правом удалённого доступа на сервер 1С).
- Сервисных учёток с просроченным паролем (PwdLastSet старше 730 дней) — 22.
После тщательной сверки с HR-отделом картина, наконец, прояснилась: выяснилось, что 156 учёток принадлежали реальным уволенным сотрудникам, которых почему-то забыли отключить при расчёте. Ещё 28 учётных записей оказались сотрудниками в длительных декретах или на больничных – мы пометили их в описании и, конечно, отключать не стали. В итоге, всего за три рабочих дня мы отключили все 156, аккуратно перенесли их в OU Disabled, отозвали сертификаты PKI и удалили из всех VPN-групп. А уже через месяц, когда не поступило ни одной жалобы, мы их окончательно удалили. Стоимость этих работ составила 38 000 руб. Кстати, с тех пор у этого клиента успешно работает наш скрипт, который каждую неделю отправляет свежий отчёт прямо на почту директора по безопасности.
Распространённые ошибки в скриптах аудита
За все те годы, что я собираю подобные отчёты, мне приходилось сталкиваться с довольно типовыми ошибками. И, поверьте, они очень сильно искажают данные:
- Запрос только LastLogon без преобразования. Атрибут возвращает Int64 в формате FileTime. Без
[DateTime]::FromFileTime()вы видите большое число и не понимаете, что это. - Игнорирование сервисных учёток. svc-account для 1С может «не входить» месяцами с точки зрения интерактивного входа, но активно работать через Kerberos. Фильтруйте их через атрибут
servicePrincipalNameили отдельную OU. - Запрос на одном контроллере и неправильный вывод. Если вы запросили LastLogon только на DC01, а пользователь логинился через DC02, увидите пустое значение. Используйте LastLogonTimeStamp для общей картины или опрашивайте все DC для точности.
- Удаление без бэкапа. Прежде чем
Remove-ADUser, всегда экспортируйте все атрибуты и членства в группы в JSON. Бывают случаи, когда «уволенный» оказывается ушедшим в декрет. - Игнорирование Disabled accounts. Учётка может быть отключена, но всё ещё членом групп с доступом к ресурсам. Проверяйте и Enabled=true, и членства в чувствительных группах отдельно.
Bonus: однострочник для скоростного аудита
Представьте ситуацию: кто-то из руководства внезапно попросил: «Срочно дай список тех, кто не заходил больше месяца!» и вам нужен максимально быстрый ответ. Что делать?
Get-ADUser -Filter {Enabled -eq $true} -Properties LastLogonDate |
Where-Object {$_.LastLogonDate -lt (Get-Date).AddDays(-30)} |
Select-Object SamAccountName, Name, LastLogonDate |
Format-Table -AutoSize
За 5 секунд получаете табличку прямо в консоли. Дальше — Ctrl+A, Ctrl+C, вставить в письмо.
Наладим регулярный аудит AD под вашу инфраструктуру
Я лично выезжаю на бесплатный аудит AD в офисы Москвы и в радиусе 50 км от МКАД. За 2–3 часа смотрим состояние домена, делаем полный отчёт по неактивным учёткам, настраиваем автоматизацию ежемесячного аудита и интеграцию с вашим HR-процессом увольнения. Без обязательств.
Телефон: +7 903 729-62-41
Telegram: @ITfresh_Boss
Семёнов Евгений Сергеевич, директор АйТи Фреш
FAQ — частые вопросы по аудиту последнего входа
- Чем отличаются LastLogon и LastLogonTimeStamp?
- LastLogon — точное время входа, но хранится только на том контроллере, к которому пользователь обращался, и не реплицируется. LastLogonTimeStamp реплицируется между контроллерами, но обновляется с задержкой 9–14 дней (по умолчанию).
- Как получить точную дату последнего входа?
- Опросить все контроллеры домена и взять максимальное значение LastLogon с каждого. Это даёт точность до минуты, но требует обращения ко всем DC, что на крупных доменах занимает время.
- Как массово найти неактивных пользователей?
- Используйте Search-ADAccount -AccountInactive -TimeSpan 90.00:00:00 -UsersOnly. Команда вернёт всех пользователей, не входивших в систему 90 дней. Дальше — экспорт в CSV, согласование с HR, отключение учёток.
- Что делать с неактивными учётками?
- Стандартная процедура: при отсутствии активности 90 дней — отключение через Disable-ADAccount, перенос в OU Disabled. После 180 дней без обращений HR — удаление через Remove-ADUser с предварительным резервным копированием атрибутов.
- Как часто запускать аудит неактивных учёток?
- Минимум раз в месяц, лучше — раз в две недели через запланированную задачу с отправкой отчёта на почту администратора. На динамичных доменах с частой текучкой — еженедельно.
