ShS's Blog

Just another sysadmin's weblog

Скрипт для получения отчета о событиях входа/выхода на компьютерах домена

Posted by shs на 2010/11/24

Давно хотел сделать для себя нечто подобное, но руки все никак не доходили. Наконец-то сподобился. И так, зачастую бывает необходимо получить отчет о событиях входа/выхода на отдельной рабочей станции и/или группе компьютеров домена: кто, когда выполнял интерактивный вход, вход по RDP, блокировал/разблокировал рабочую станцию, обращался к компьютеру по сети и т.п. Вся информация, которая нам необходимо для решения этой задачки, содержится в событиях 528/538/540 (для рабочих станций под управлением windows предшествующих Vista). Если для одиночного компьютера еще можно как-то пользоваться event viewver’ом для просмотра событий (но все равно это не очень удобно, т.к. нужная нам для анализа информация скрыта внутри события), то с группой компьютеров работать совершенно невозможно.

рис. 1

Так, например, узнать значение Logon Type события входа, нам придется открывать окно с информацией о событии:

рис. 2

Неудобно так же и то, что мы лишены возможности фильтровать события по типу входа, и т.п.

Поэтому я и решил написать скрипт, который опросит заданный(ые) компьютер(ы) и выгрузит в единый файл формата csv всю собранную информацию. Впоследствии этот csv файл можно будет открыть при помощи Excel для последующей обработки (наложения фильтров, сортировки и т.п.).
И так приступим. Для начала определимся, каким образом мы можем сформировать csv-файл? Самый простой способ — это воспользоваться командлетом Export-Csv. На вход этому командлету необходимо подавать объекты (и только объекты), иначе он не будет работать. И так, получается, что информацию о событиях входа, собираемую с компьютеров, мы будем аккумулировать в массиве объектов, а затем подадим этот массив на вход командлету Export-Csv. Информация о каждом конкретном событии входа/выхода будем хранить в объекте следующего вида:

$objLogOnOffEvt=New-Object PSObject -Property @{
CompName=$CompName;
TimeGenerated=$null;
User=$null;
Domain=$null;
EventIdentifier=$null;
LogonType=$null;
}

С каждого опрашиваемого скриптом компьютера мы будем получать массив таких объектов и аккумулировать их в едином массиве, предназначенном для выгрузки в файл отчета. Каждое событие входа/выхода мы будем «парсить» при помощи регулярных выражений, выделяя нужную нам информацию и заполняя ею поля нашего объекта. Для этого удобно использовать оператор switch:

switch -regex ($_.Message) {
"(?:Пользователь|User):\t+(\w+(?:\s?[\w-]+)*)" {$objLogOnOffEvt.User=$Matches[1]}
"(?:Домен|Domain):\t+(\w+(?:\s?[\w-]+)*)" {$objLogOnOffEvt.Domain=$Matches[1]}
"(?:Тип входа|Logon Type):\t+(\d+)" {$objLogOnOffEvt.LogonType=$Matches[1]}
}

Ну, и еще один тонкий момент: каким образом получить информацию о том или ином событии? Поначалу, я решил использовать стандартный командлет Get-EventLog, но (после ряда экспереметов) был вынужден отказаться от этой идеи. Дело в том, что при работе с большими логами (каковыми обычно являются журналы Security) на удаленных компьютерах этот командлет безбожно «тормозит». В то же самое время wmi-запросы отрабатывают в разы шустрее. Поэтому для извлечения информации о событии будем использовать wmi (а конкретно, класс Win32_NTlogEvent):

gwmi -ComputerName $CompName -Class Win32_NTlogEvent -Filter $filter -ErrorAction Stop|..

Ну, а теперь пришло время опубликовать сам скрипт:

<# Скрипт Get-LogOnOffEvts.ps1 shs 20101112
.Synopsis
Скрипт проводит опрос компьютеров, чьи учетные записи находятся в заданном параметрами запуска OU,
собирая информацию о событиях входа/выхода и сохраняя их в файл отчета

Get-LogOnOffEvts.ps1 [SearchRoot] [SearchScope] [DateBefore] [DateAfter] <ReportFileName>

.Parameters

SearchRoot - задает корневой контейнер, с которого будет начат поик учетных записей компьютеров,
подлежащих опросу
Если не задано иное, то по умолчанию принимает значение "$(([ADSI]"LDAP://rootDSE").rootDomainNamingContext)",
которое равно DN корневого домена леса.

SearchScope - область поиска, может принимать одно из 3х возможных значений:
'Base' - ограничивает область поиска базовым объектом (SearchRoot)
'OneLevel' - поиск только прямых потомков базового объекта (SearchRoot),
за исключением самого базового объекта
'Subtree' - поиск по всему поддереву, начиная с базового объекта (SearchRoot),
включая сам базовый объект
Если не задано иное, то по умолчанию принимает значение 'Subtree'

DateBefore - верхня граница даты искомых событий (по умолчанию - начало следующих суток)

DateAfter - нижняя граница даты искомых событий (по умолчанию - начало текущих суток)

ReportFileName -полное имя файла отчета

.Examples
Get-LogOnOffEvts.ps1 -ReportFileName c:\temp\report.csv
Get-LogOnOffEvts.ps1 -SearchBase "domain.local/MyOU/Workstations" -ReportFileName c:\temp\report.csv
Get-LogOnOffEvts.ps1 -SearchBase "domain.local/MyOU/Workstations" -SearchScope 'OneLevel' -ReportFileName c:\temp\report.csv
Get-LogOnOffEvts.ps1 -SearchBase "CN=MyComp,OU=Workstations,OU=MyOU,DC=domain,DC=local" -DateAfter "04/30/2010" -ReportFileName c:\temp\report.csv
#>
#================================================================================================================
param($SearchRoot="$(([ADSI]"LDAP://rootDSE").defaultNamingContext)", [DirectoryServices.SearchScope]$SearchScope="Subtree",`
[DateTime]$DateBefore=(Get-Date).AddDays(1).Date, [DateTime]$DateAfter=(Get-Date).Date, $ReportFileName)
#================================================================================================================
<###############################################################################

Function Show-Help. Выыодит на экран первый блочный коментарий скрипта,
заданного в качестве параметра вызова.

.Parameter <ScriptFullName>
Полное имя скрипта, чей первый блочный коментарий будет выведен на экран.

#################################################################################>
Function Show-Help ($ScriptFullName){
if ($ScriptFullName) {
$IsHelpLine=$false
switch -file $ScriptFullName {
{$_ -match "<#"} {Write-Host $_;$IsHelpLine=$true;continue}
{$_ -match "#>"} {Write-Host $_;$IsHelpLine=$false;break}
{$IsHelpLine} {Write-Host $_;}
}
}
}
<###############################################################################
Функция Get-LogonLogoffEvts предназначена для получения массива объектов,
содержащих информацию о событиях входа/ выхода на заданном компьютере
(Будет собрана информация о событиях с event id равными 528,528,540)

.Parameters
CompName - Имя опрашиваемого компьютера
Before - Верхняя граница даты искомых событий
After - Нижняя граница даты искомых событий

###############################################################################>
Function Get-LogonLogoffEvts ($CompName, $Before=$DateBefore, $After=$DateAfter) {
#массив, в который будем собирать информацию входа/выхода для заданного компьютера
$arrLogOnOffEvts=@()
#Перебираем все события с EventID равными 528|538 в заданном диапазоне даты/времени
#Get-EventLog -ComputerName $CompName -InstanceId 528,538 -LogName Security -After $After -Before $Before|%{
$filter="logfile='Security' and (eventcode='528' or eventcode='540' or eventcode='538') and TimeGenerated >= '$([System.Management.ManagementDateTimeConverter]::ToDMTFDateTime($After))'"
$filter+= " and TimeGenerated <= '$([System.Management.ManagementDateTimeConverter]::ToDMTFDateTime($Before))'"
gwmi -ComputerName $CompName -Class Win32_NTlogEvent -Filter $filter -ErrorAction Stop|%{
#создадим новый объект, в который будем записывать информацию об очередном событии входа/выхода
$objLogOnOffEvt=New-Object PSObject -Property @{
CompName=$CompName;
TimeGenerated=$null;
User=$null;
Domain=$null;
EventIdentifier=$null;
LogonType=$null;
}
#и сохраняем всю полезную информацию о событии в объете $objLogOnOffEvt
$objLogOnOffEvt.TimeGenerated=[datetime]$_.TimeGenerated
$objLogOnOffEvt.EventIdentifier=$_.EventIdentifier
switch -regex ($_.Message) {
"(?:Пользователь|User):\t+(\w+(?:\s?[\w-]+)*)" {$objLogOnOffEvt.User=$Matches[1]}
"(?:Домен|Domain):\t+(\w+(?:\s?[\w-]+)*)" {$objLogOnOffEvt.Domain=$Matches[1]}
"(?:Тип входа|Logon Type):\t+(\d+)" {$objLogOnOffEvt.LogonType=$Matches[1]}
}
#добавляем информацию об очередном событии в массив
#$objLogOnOffEvt
$arrLogOnOffEvts+=$objLogOnOffEvt
}
#возвращаем, как результат работы функции, информацию о всех собранных событиях
$arrLogOnOffEvts
}
#
#====================================== Точка входа скрипта ==================================
#если полное имя файла отчета задано и задано правильно (путь в полном имени файла указывает на папку,
#которая реально существует), то начинаем работу
if (($ReportFileName) -and (Test-Path ($ReportFileName|Split-Path))) {
#Массив, в который будем собирать информацию о событиях входа/выхода, собранную со всех опрашиваемых компьютеров
$arrReport=@()
#Выбираем из заданной ветки все неотключенные учетные записи компьютеров
$arrComps = Get-QADComputer -ErrorAction SilentlyContinue -SearchRoot $SearchRoot -SearchScope $SearchScope -SizeLimit 0 |
select name, @{Name="Disabled"; Exp={$_.useraccountcontrol -band 2}}|
?{$_.Disabled -eq 0}| select -ExpandProperty name
#можно было бы и далше продолжать передавать данные по конвейеру, но мы не будем этого делать
#(чтобы упростить отладку и облегчить восприятие)
#для каждого компьютера из списка...
$arrComps|foreach {
##...если компьютер доступен (пингуется)...
if (Test-Connection $_ -Count 1 -Quiet) {
Write-Host "`n$_" -NoNewline
try {
###...добавляем информацию о событиях входа/выхода опрашиваемого компьютера к общему отчету
$arrReport+=Get-LogonLogoffEvts $_
}
catch {
Write-Host "`t-->`t Ошибка доступа!" -ForegroundColor Red -NoNewline
}
}
}
#Если отчет не пустой, то...
if ($arrReport) {
#...выгружаем сформированный отчет в файл-csv, который удобно обрабатывать в excel
$arrReport|Export-Csv -Path $ReportFileName -UseCulture -Encoding Default
}
else {
Write-Host "`nДанных получено не было. Новый отчет не сформирован!"
}
}
else {
#Выводим на экран первый блочный комментарий этого скрипта в качестве справки
Show-Help $MyInvocation.MyCommand.Path
} 

Upd. Забыл указать ссылки на источники информации по событиям входа|выхода.
Исправляюсь:

Описание событий входа/выхода смотримздесь: Audit logon events

Описание типов входа (logon types), можно посмотреть в там же (Audit logon events), можно здесь (SECURITY_LOGON_TYPE Enumeration) и еще вот здесь (Win32_LogonSession Class). Обратите внимание, что только в статье Win32_LogonSession Class описан тип входа, равный нулю (!). На всякий случай, повторю эту таблицу с кодами входа/выхода чуть ниже:

Value Meaning  
0 Used only by the System account.  
 
2 (Interactive) Intended for users who are interactively using the machine, such as a user being logged on by a terminal server, remote shell, or similar process.  
3 (Network) Intended for high-performance servers to authenticate clear text passwords. LogonUser does not cache credentials for this logon type.  
4 (Batch) Intended for batch servers, where processes can be executed on behalf of a user without their direct intervention; or for higher performance servers that process many clear-text authentication attempts at a time, such as mail or Web servers. LogonUser does not cache credentials for this logon type.  
5 (Service) Indicates a service-type logon. The account provided must have the service privilege enabled.  
6 (Proxy) Indicates a proxy-type logon.  
7 (Unlock) This logon type is intended for GINA DLLs logging on users who are interactively using the machine. This logon type allows a unique audit record to be generated that shows when the workstation was unlocked.  
8 (NetworkCleartext) Windows Server 2003, Windows 2000, and Windows XP:  Preserves the name and password in the authentication packages, allowing the server to make connections to other network servers while impersonating the client. This allows a server to accept clear text credentials from a client, call LogonUser, verify that the user can access the system across the network, and still communicate with other servers.  
9 (NewCredentials) Windows Server 2003, Windows 2000, and Windows XP:  Allows the caller to clone its current token and specify new credentials for outbound connections. The new logon session has the same local identify, but uses different credentials for other network connections.  
10 (RemoteInteractive) Terminal Services session that is both remote and interactive.  
11 (CachedInteractive) Attempt cached credentials without accessing the network.  
12 (CachedRemoteInteractive) Same as RemoteInteractive. This is used for internal auditing.  
13 (CachedUnlock) Workstation logon.  

комментария 23 to “Скрипт для получения отчета о событиях входа/выхода на компьютерах домена”

  1. myname said

    1. скачал и установил «PowerShell»;
    2. прописал «set-ExecutionPolicy RemoteSigned»;
    3. сохранил скрипт в отдельном файле, запускаю, в итоге получаю http://img217.imageshack.us/img217/1243/screenbt.png :(

    • shs said

      Если скрипт у вас расположен в корне диска d:, то запускать его надо так: d:\Get-LogOnOffEvts.ps1 или .\Get-LogOnOffEvts.ps1, если вы находитесь в той же директории, где лежит скрипт (об этом даже написано всообщении об ошибке).

  2. myname said

    Сделал, но результат ещё печальнее: http://img59.imageshack.us/img59/6937/screenvv.png
    Сдаётся мне, из-за того, что 2k3 на инглише :(

    • shs said

      Ну, по крайней мере, скрипт отработал правильно ;) Т.к. вы не задали обязательный параметр вызова (ReportFileName — полное имя файла отчета, то скрипт вывел вам на экран (в качестве справки) содержимое своего первого блочного коментария.
      Но, у вас вместо русских букв, почему-то отображаются знаки вопроса. Скорее всего на этом компьютере у вас неправильно выставлены региональные настройки (см. «Язык и региональные стандарты» в панели управления).

    • shs said

      да, кстати, а покажите результат выполнения следующего:
      $Host.CurrentCulture

  3. myname said

    ShS, извиняюсь за долгое молчание.
    В общем, до сих пор не могу побороть: http://img262.imageshack.us/img262/3619/hostcc.png :(

    p.s.
    $Host.CurrentCulture сделал — региональные настройки ни до, ни после этого запроса не менял, если что; сам файл «LogOnOffEvts.ps1» (текстовый) сохранил как unicode (+ переименовал после в «LogOnOffEvtu.ps1″). Размер, конечно, увеличился в пару раз, зато теперь кириллица хотя бы корректно отображается на экране. Хотя, это к сути дела, скорее всего, и не относится)

    • shs said

      Ну, вот, уже гораздо лучше.
      Мой скрипт использует набор бесплатных командлетов от Quest Software для работы с AD: ActiveRoles Management Shell for Active Directory. Вам необходимо скачать его, установить в систему и подключать в каждой сессии, в которой вы захотите эти командлеты использовать. Чтобы не заморачиваться всякий раз с подключением того или иного набора командлетов, можно оптом подключать все установленные оснастки и модули прямо в профиле PoSh. В этом случае в любой сессии PoSh, у вас будут доступны все командлеты, которые установлены в вашей системе.

      • На самом деле для этой задачи можно было обойтись ADSI, не используя QAD. ADSI прекрасно живёт и с windows server 2008 R2 DC, и c windows server 2003 DC.
        А в целом скрипт любопытный :-). Правда я больше склоняюсь к следующей идее: использовать не WMI интерфейсы удалённо, а запускать удалённые сессии powershell, там АСИНХРОННО И ПАРАЛЛЕЛЬНО на всех компах сразу выполнять необходимые действия, результат консолидировать и выбрасывать в файл. Мне кажется, при большом количестве ПК этот вариант будет более производительным за счёт асинхронности.

        • shs said

          >Правда я больше склоняюсь к следующей идее: использовать не WMI интерфейсы удалённо, а запускать удалённые сессии powershell, там АСИНХРОННО И ПАРАЛЛЕЛЬНО на всех компах сразу выполнять необходимые действия, результат консолидировать и выбрасывать в файл. Мне кажется, при большом количестве ПК этот вариант будет более производительным за счёт асинхронности.

          Да, бесспорно этот вариант (при большом количестве компьютеров) будет самым выигрышным. К сожалению, он не годится на все случаи жизни. Так, например, у меня в организации до сих пор PoSh не установлен на всех компьютерах (в силу разных причин). Когда эта ситуация перестанет иметь место быть, обязательно опробую вариант с удаленными сессиями.

          • Причём, здесь необходимо powershell 2. У меня он прекрасно встал через GPO на все windows xp sp3. Имеет смысл везде иметь уже сейчас powershell, хотя бы для того, чтобы уже можно было в GPO и назначенных заданиях (опять-таки — через GPO) уже использовать powershell сценарии.

            • shs said

              К сожалению, большое количество компьютеров, доставшихся мне от предшественника, имееют негуманное деление hdd на разделы: под системный раздел отведено крайне мало места. Поэтому, установить на них .Net и PoSh, без перекраивания разделов (или, правильнее будет сказать: без ручного труда) не представляется возможным. Кроме того, не стоит забывать и про ОС, на которых PoSh не может быть установлен (по крайней мере официально): win2k и win2k8 server core.

  4. myname said

    1. Скачал и установил Free PowerShell Commands for Active Directory;
    2. Взял профиль
    отсюда, сохранил его в файле под именем «get-help-p.ps1», запустил и получил это: http://img815.imageshack.us/img815/3593/111s0.png
    3. В этом же окне (не закрывая и ничего не изменяя) следом запустил «Get-LogOnOffEvtu.ps1» с параметром «-ReReportFileName c:\temp\report.csv» и получил это: http://img148.imageshack.us/img148/5715/112jd.png
    4. А всё-таки, «-ReportFileName» или «-ReReportFileName»?

    • shs said

      >2. Взял профиль отсюда, сохранил его в файле под именем «get-help-p.ps1″, запустил и получил это: http://img815.imageshack.us/img815/3593/111s0.png
      результат (для работы с моим скриптом) вы получили првавильный, но неправильным способомю читайте справку:
      help about_profiles

      >3. В этом же окне (не закрывая и ничего не изменяя) следом запустил «Get-LogOnOffEvtu.ps1″ с параметром «-ReReportFileName c:\temp\report.csv» и получил это: http://img148.imageshack.us/img148/5715/112jd.png
      это как раз потому, что вы неправильно указали имя единственного обязательного параметра запуска: ReportName (ReReport — это опечатка в комментарии, сейчас исправлю).

  5. myname said

    Так… Почитал логи, и повнимательнее присмотрелся к листингу. После чего отредактировал значение $SearchRoot=»dom.local/RTA/Workstations» под свой домен/подразделение, и скрипт со скрипом зашуршал, НО! Но в списке со своими компьютерами напротив каждого вижу ошибку доступа.

    p.s.
    Запуск «powershell.exe», разумеется, осуществляется из-под доменного администратора.

    p.p.s.
    Мне уже начинать искать стенку и стакан с ядом, или я ещё не настолько безнадёжен?

    • shs said

      ну, тут вариантов не много:
      — недостаток прав в опрашиваемой системе
      — доступ перекрыт firewall’ом
      — неполадки wmi в опрашиваемой системе

      Если исключить 1 и 3 варианты, как наименее вероятные, то остаетсявариант 2.

      попробуйте опросить како-либо компьютер при помощи команды, которую использует скрипт, например так: gwmi -ComputerName $CompName -Class Win32_NTlogEvent |select -First 1
      и смотрите сообщение об ошибке

      ЗЫ Если у вас скрипт запускается на системе от Vista и выше, не забывайте, что он должен запускаться с повышенными привелегиями (Run as administrator), иначе, из-за UAC, он будет работать в контексте рядового пользователя.

  6. Позволю себе не согласиться (по части ручного труда при перекраивании разделов). Используем банальный RIS (ну или WDS), готовим flat образ Windows XP, в sif файле прописываем переразбиение диска и форматирование без вопросов, после чего просто перегружаем машину, жмём F12, — и пошла автоматическая установка ОС уже без Вашего участия. Естественно, это всё работает (у нас так), если все данные и профили пользователей в сети, и софт разворачивается через GPO или SMS (ну или иными решениями — но не руками).
    Тогда никакого геморроя и от зоопарка избавляемся влёт :-).

    • shs said

      и этот вариант в моем случае не применим :(. т.к. парк машин у нас только с OEM-лицензией, а OEM-ключик подходит только к SP2, но не к SP3. Посему, в случае переустановки ОС приходится накатывать образ с SP2, и, лишь затем, ставить SP3+все последующие апдейты.

      • И не проблема, хоть и вовсе без SP. Выкладываем в RIS образ с SP2, ставим его. Политиками заставляем использовать WSUS, на котором одобрены SP3 в частности.
        Либо, что ещё логичнее, в RIS в $OEM$\CMDLINES.TXT прописываем установку sp3 командной строкой. У меня содержимое этого файла следующее:
        [Commands]
        «.\MUI\MUISETUP.EXE /i 0419 /d 0419 /l /f /r /s»
        «net user Administrator /active:yes»
        «net user HelpAssistant /active:yes»
        «net localgroup Users HelpAssistant /add»
        «rundll32.exe shell32,Control_RunDLL intl.cpl,,/f:»regopts.txt»»
        «regedit /s .\toggledu.reg»
        «regedit /s .\RegionRus.reg»
        «regedit /s .\Kerberos.reg»
        «regedit /s .\NumLock.reg»
        «regedit /s .\ExplorerSettings.reg»
        «regedit /s .\MyCompMenu.reg»
        «regedit /s .\tcp-ipSettings.reg»

        Как Вы понимаете. Туда дописать что-либо не проблема.

        • shs said

          >И не проблема, хоть и вовсе без SP. Выкладываем в RIS образ с SP2, ставим его. Политиками заставляем использовать WSUS, на котором одобрены SP3 в частности.

          IMHO, в данном случае овчинка не стоит выделки. Т.к. количество времени. которое придется затратить на такую установку, не будет сильно отличатьтся от того ставлю я SP «руками» или автоматически, преразбиваю я диск руками или автоматом. В любом случае из-за времяемкости такой установки мне приходится «отнимать» компьютер у пользователя и давать другой в качестве временной замены.

          • И здесь с Вами позвольте не согласиться, уважаемый коллега. Рассказываю из практики: в конце рабочего дня в пятницу либо просите юзеров перезагрузиться, нажать F12, ввести имя и пароль (тогда им должны быть предоставлены привелегии на создание объектов computer в AD, заполнение аттрибутов этого объекта, а также на файловый ресурс RIS), либо даёте им «специальный временный» аккаунт, имеющий избыточные привелегии, который потом убьёте. Или же поручаете своим коллегам прогуляться и сделать то же самое самим. И спокойно уходите домой, как и все пользователи. Потери рабочего времени нет. Вечером за рюмкой чая поднимаете VPN, проверяете новорожденных, в том числе уже и удалённым рабочим столом. И после того, как всё сделаете, что следует — гасите их (кстати — гасить можно и через powershell :-)).
            Это — из практики, не миф. F12 жмут мои коллеги, не юзеры.
            Полная процедура установки включая все обновления с WSUS и установку всего софта через GPO, антивирусную проверку полную после установки антивируса через GPO занимает от 2-3 часов до 5-6 на вёдрах. Но ведь VPN можно поднять и в субботу :-)

          • Кстати, пользователи при этом разницы не замечают: софт тот же, профиль тот же (он у нас перемещаемый, также — назначен через GPO), документы все на месте (все в сети, включая Мои документы). А доступ к локальному диску подрезан, чтобы «случайно» документы локально не сохранили.

  7. И чего-то нужно Вам сделать с темой. Комментарии уже сложно читать при таком layout (перевести этот термит как «разметка» рука не поднимается).

Ответить на shs Отменить ответ