· 11 мин чтения

Аудит последнего входа в Active Directory: PowerShell, CSV и точные значения с разных контроллеров

Аудит последнего входа в Active Directory: PowerShell, CSV и точные значения с разных контроллеров

Меня зовут Семёнов Евгений Сергеевич, я директор АйТи Фреш. За 15 лет работы с корпоративными доменами я перевидал десятки запросов «нам надо узнать, кто из сотрудников когда последний раз заходил в домен». Иногда это безопасность хочет почистить «спящие» учётки, иногда HR — сверить со списком уволенных, иногда кадровый аудит ИБ-конторы. И почти всегда штатный администратор начинает с команды Get-ADUser -Filter * -Properties LastLogon и удивляется, почему данные «какие-то странные». Сегодня разбираю, как делать это правильно, какие подводные камни прячутся в атрибутах AD и как написать одну команду, которая выдаст корректный CSV для отчёта руководству.

Почему «простой» аудит даёт неверные данные

Знаете, Active Directory хранит информацию о последнем входе, но делает это сразу в нескольких атрибутах, и, честно говоря, ни один из них не идеален. Давайте вместе разберёмся, что к чему.

Нужен «плюс-минус две недели» — хватает 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).

Чек-лист регулярного аудита неактивных учёток

В АйТи Фреш мы у клиентов настраиваем регулярный аудит по такой схеме:

  1. Раз в неделю — отчёт «не заходили 30 дней». Отправляем HR для сверки с увольнениями.
  2. Раз в две недели — отчёт «не заходили 60 дней». Согласовываем отключение учёток с непосредственным руководителем.
  3. Раз в месяц — отключение всех, кто не заходил 90 дней, с переносом в OU «Disabled». В описании учётки фиксируем дату отключения.
  4. Раз в полгода — удаление учёток, лежащих в 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-доступ».

За полтора часа мы выгрузили полную картину:

После тщательной сверки с HR-отделом картина, наконец, прояснилась: выяснилось, что 156 учёток принадлежали реальным уволенным сотрудникам, которых почему-то забыли отключить при расчёте. Ещё 28 учётных записей оказались сотрудниками в длительных декретах или на больничных – мы пометили их в описании и, конечно, отключать не стали. В итоге, всего за три рабочих дня мы отключили все 156, аккуратно перенесли их в OU Disabled, отозвали сертификаты PKI и удалили из всех VPN-групп. А уже через месяц, когда не поступило ни одной жалобы, мы их окончательно удалили. Стоимость этих работ составила 38 000 руб. Кстати, с тех пор у этого клиента успешно работает наш скрипт, который каждую неделю отправляет свежий отчёт прямо на почту директора по безопасности.

Распространённые ошибки в скриптах аудита

За все те годы, что я собираю подобные отчёты, мне приходилось сталкиваться с довольно типовыми ошибками. И, поверьте, они очень сильно искажают данные:

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 с предварительным резервным копированием атрибутов.
Как часто запускать аудит неактивных учёток?
Минимум раз в месяц, лучше — раз в две недели через запланированную задачу с отправкой отчёта на почту администратора. На динамичных доменах с частой текучкой — еженедельно.

Подпишитесь на рассылку ITfresh

Раз в неделю — практические гайды для руководителя IT и сисадмина: безопасность, 1С, миграции, резервные копии, лайфхаки из реальных проектов.

Реквизиты оператора персональных данных

ООО «АЙТИ-ФРЕШ», ИНН 7719418495, КПП 771901001. Юридический адрес: 105523, г. Москва, Щёлковское шоссе, д. 92, корп. 7. Контакт: info@itfresh.ru, +7 903 729-62-41. Оператор обрабатывает e-mail подписчика в целях рассылки информационных и рекламных материалов до момента отзыва согласия.