Аудит последнего входа AD: PowerShell, CSV — АйТи Фреш
Москва, Щёлковское шоссе, д. 92, корп. 7 · Пн–Пт 9:00–19:00 · +7 903 729-62-41
· 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"

Запуск аудита через Task Scheduler с gMSA

Для автоматических задач в Task Scheduler создаю gMSA — managed service account, у которого пароль ротируется автоматически каждые 30 дней. Никогда не используйте Domain Admin для расписаний.

# Создание KDS Root Key (один раз на лес)
Add-KdsRootKey -EffectiveTime ((Get-Date).AddHours(-10))

# Создание gMSA
New-ADServiceAccount -Name "gmsa-adscripts" `
  -DNSHostName "gmsa-adscripts.corp.itfresh.ru" `
  -PrincipalsAllowedToRetrieveManagedPassword "DC01$","DC02$"

# Установка на сервере, где будут крутиться скрипты
Install-ADServiceAccount -Identity "gmsa-adscripts"

Регистрация задачи:

$action = New-ScheduledTaskAction -Execute "pwsh.exe" `
    -Argument "-NoProfile -File C:\Scripts\AD-Inactive-Audit.ps1"
$trigger = New-ScheduledTaskTrigger -Daily -At 02:00
$principal = New-ScheduledTaskPrincipal -UserId "CORP\gmsa-adscripts$" -LogonType Password
Register-ScheduledTask -TaskName "AD-Inactive-Audit-Daily" `
    -Action $action -Trigger $trigger -Principal $principal

Аудит компьютеров: забытые в домене ноутбуки

Помимо пользователей, в 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 дней — на случай, если ПК просто долго лежал на полке и его принесли обратно.

Реальный кейс: 22 «спящих» учётки в торговой сети

Я хорошо помню, как в сентябре 2025 года к нам обратилась одна торговая сеть. У них было 14 розничных точек и центральный офис, а домен насчитывал 45 пользователей. Запрос от службы безопасности был очень чётким: «У нас за 4 года скопилось слишком много старых учёток, их нужно срочно почистить. Аудитор по 152-ФЗ обнаружил 2 уволенных сотрудника, у которых до сих пор активен VPN-доступ».

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

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

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

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

Логирование и уведомления в Telegram

Любой production-скрипт аудита обязан писать журнал и присылать уведомление, когда что-то пошло не так. Иначе скрипт молча падает три недели подряд, и админ узнаёт об этом, когда уже накопилась куча проблем. Минимальный шаблон-обёртка:

$LogPath = "C:\Logs\AD\$(Get-Date -Format yyyy-MM-dd)_$($MyInvocation.MyCommand.Name).log"
Start-Transcript -Path $LogPath -Append

try {
    Write-Host "[$(Get-Date)] Старт"
    # основной код скрипта аудита
    Write-Host "[$(Get-Date)] Готово"
} catch {
    $err = "$($_.Exception.Message)`n$($_.ScriptStackTrace)"
    Write-Host "[$(Get-Date)] ОШИБКА: $err" -ForegroundColor Red

    $tgToken = "BOT_TOKEN"
    $tgChat = "-100xxxxxxxxxx"
    $message = "AD-script $($MyInvocation.MyCommand.Name) FAILED on $env:COMPUTERNAME`n$err"
    Invoke-RestMethod -Uri "https://api.telegram.org/bot$tgToken/sendMessage" -Method Post `
        -Body @{chat_id=$tgChat; text=$message; parse_mode="Markdown"}
} finally {
    Stop-Transcript
}

Логи храню 90 дней, по утрам через скрипт-агрегатор сканирую ключевое слово ERROR — если за ночь что-то упало, в Telegram прилетает сводка.

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, вставить в письмо.

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

Столкнулись с похожей задачей? Обращайтесь — решим

Если у вас происходит что-то из описанного в этой статье — или любая другая проблема с ИТ-инфраструктурой, — обращайтесь в любое время. Мои специалисты и я лично разберём ситуацию, найдём настоящую причину и доведём до решения.

Возьмёмся и за разовую задачу, и за постоянное обслуживание. Первичная консультация — бесплатно и без обязательств.

📞 +7 903 729-62-41 ✈ Telegram @ITfresh_Boss

С уважением, Семёнов Евгений Сергеевич, директор «АйТи Фреш» — IT-аутсорсинг для компаний до 50 рабочих мест, 15+ лет практики

Источники

Похожие статьи

📄
Скачайте подробный разбор в PDF Кейсы, статистика, типовые ошибки и чек-лист самопроверки — 12 страниц
Скачать PDF

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

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

Письмо придёт в течение минутыНе нашли его во «Входящих» — загляните в папку «Спам» или «Промоакции» и нажмите «Не спам». Так все следующие выпуски будут приходить прямо в основную почту.
Реквизиты оператора персональных данных

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