Централизованный аудит печати — Экспорт событий аудита в файл


Цикл статей:

1.       Описание задачи и решения

2.       Сбор событий аудита

3.       Экспорт событий аудита в файл

4.       Загрузка в базу данных

5.       Создание отчёта

6.       Размещение отчёта на Sharepoint

7.       Дополнение 1: Powershell – системные журналы и особенности фильтров по времени

8.       Дополнение 2: Powershell – производительность работы с системными журналами

 

Как было уже сказано, события аудита быстро заполняют файл системного журнала. К тому же журнал в несколько сот мегабайт ощутимо медленно отображается в оснастке Event Viewer.

Поэтому сразу было ясно, что будет проблема с производительностью при обработке журнала событий.

Первый и очевидный способ, который был протестирован, это использование командлета Get-WinEvent. Этот командлет в моем частном тесте показал скорость около 200 событий в секунду. Для системных журналов типичного размера это приемлемо, но когда размер файла аудита составляет десятки мегабайт, то время работы командлета составляет несколько минут: примерно 1 минута на 10 Мбайт. Таким образом выгрузка файла аудита размером в 100 Мбайт составляет уже около 10 минут. Причем комбинации фильтром никак не влияют скорость обработки журнала. Все это можно было бы пережить, но кроме этого есть другой неприятный момент: пара процессов (powershell и svchost) занимают полностью одно ядро процессора. По совокупности эти проблемы исключают использование командлета Get-WinEvent для обработки журналов большого размера.

Второй способ это низкоуровневый API. Этот способ был отложен на самый крайний случай как самый трудоёмкий.

Третий вариант был обнаружен в виде утилиты Wevtutil. Утилита показала неожиданно высокую скорость обработки системных журналов при относительно низкой загрузке процессора. Более того эта утилита помогла решить ещё одну проблему, которая заключается в следующем.

События предаются на сборщик событий в асинхронном режиме, т.е. в непредсказуемое время. Более того если компьютер-источник был выключен до передачи событий или потерял сеть (упал канал в филиале), то событие может быть передано спустя день, два, неделю, месяц и т.д. – спустя много времени.

Если запускать на сервере скрипт обработки журнала событий раз в сутки и снимать отчёт за предыдущие сутки (фильтр по времени), то возникает проблема: события произошедшие в эти сутки, но переданные за пределами этих суток – позже, никогда не будут учтены (их отбросит фильтр по времени) – фактически они потеряны. События не имеют уникального идентификатора в виде GUID, и не могут быть однозначно идентифицированы. Поэтому «опоздавшие» события невозможно отличить от тех, которые уже обработаны, и добавить в базу данных без риска повторного добавления событий. (Если быть более точным, то номер записи существует, но все же он не является глобально уникальным).

Как быть? Можно увеличить размер журнала и снимать отчёт не за прошлые сутки, а, например, позапрошлые сутки, чтобы дать возможность максимальному числу компьютеров передать события.

Но есть более красивый способ, который позволяет обработать все события, даже если они опоздали на год, не раздувая размер журнала. Оказывается утилита Wevtutil имеет ключ очистки журнала с одновременной его выгрузкой в файл. В этой ситуации «опоздавшие» события окажутся в другом, новом, файле отдельно от уже обработанных событий и могут быть обработаны и смело добавлены в базу данных: повторное добавление событий исключено.

С этой проблемой всё. Продолжим описание скрипта. Для повышения полезности отчёта скрипт по логину пользователя, который есть в событии аудита, извлекает из AD информацию: ФИО, филиал, должность.

В этом месте опять пришлось замерять производительность: было ожидание, что использование ADSI будет в разы эффективнее, чем использование командлета GetADUSer. Но тесты показали, что на Windows Server 2012 R2 все наоборот: командлет отрабатывал намного быстрее, чем [adsisearch]. Как это сделано загадка, но это факт.

Наконец был проведен ещё один замер производительности. Утилита Wevtutil позволяет работать с системными журналами на удалённом компьютере. И как показали тесты, делает она это очень эффективно: увеличение времени работы при всех прочих равных условиях есть, но очень небольшое – всего несколько секунд. Это позволит нам не запускать скрипт локально на сервере-сборщике, а делать это удаленно, например, использовать для этого централизованный движок – Orchestrotor.

Ещё один комментарий к скрипту. В событиях аудита есть имена файлов. Имена могут быть на русском языке. Утилита Wevtutil по умолчанию выгружает информацию в кодировке OEM, что создаёт проблемы с кодировкой. Но опять же повезло: утилита имеет ключ вывода информации в Unicode:

Wevtutil /uni:true

И последний комментарий по скрипту. Чтобы исключить повторную загрузку файла в базу данных, будем контролировать, какие файлы уже загружены. Это лишнее, но как «защита от дурака» не помешает. Для этого пойдём простейшим путём: будем добавлять в каждую запись имя файла. Т.к. имя файла слишком большое по размеру (размер базы данных увеличивается в двое!), то  в экспорт добавлено поле FileName, в которое пишется не полное имя текущего файла CSV, а только часть содержащая временную метку. Это дает перерасход примерно на 15%. (На ожидаемый размер базы данных 10 Гб за год это 1.5 Гб мусора). Если хотите сэкономить, то можете убрать поле docname содержащее имена заданий печати из экспорта.

Таким образом получаем следующий алгоритм работы: запускаем раз в сутки скрипт, который  выгружает с очисткой журнал событий аудита в отдельный файл, потом перекодируем этот файл в XML с отфильтровкой мусора, перекодировкой и дополнениями из AD и сохраняем в формате CSV для последующей загрузки в базу данных.

Собственно сам скрипт:


#
#   Экспорт событий аудита в файл CSV
#
#   -ExecutionPolicy RemoteSigned -NoProfile -File C:\Install\PrintAudit\PrintAudit.ps1

Add-Type -AssemblyName System.Web

#$Computer = "ps2.bis.bashtel.ru"


$a3 =  Measure-Command -Expression {

$workpath = "c:\install\printaudit\work"

$logname = "Microsoft-Windows-PrintService/Operational"

$logname1 = [System.Web.HttpUtility]::UrlEncode($logname)

$Now = [System.DateTime]::Now
$filedate = $Now.ToString("yyyyMMddHHmmss")

$out_evt_file = "$workpath\$logname1-$filedate.evtx"
$out_csv_file = "$workpath\$logname1-$filedate.csv"

wevtutil cl $logname /BU:$out_evt_file /uni:true

$query = "*[System[(Level=4 or Level=0) and (EventID=307)]]"

$Events=wevtutil query-events $out_evt_file /lf:true /q:$query /f:XML /locale:ru /uni:true

#$encode = [System.Text.Encoding]::GetEncoding(866)
#$decode = [System.Text.Encoding]::GetEncoding(1251)

$Events1=$Events | 
 % { [xml]$_ } | 
 % { 
  $User = Get-ADUser $_.Event.UserData.DocumentPrinted.Param3 -Properties DisplayName,Company,Department  -ErrorAction SilentlyContinue;


        if ($_.Event.UserData.DocumentPrinted.Param7 -is [array] ) {
      New-Object PSObject -Property @{

      time = $_.Event.System.TimeCreated.SystemTime;
      docName = $_.Event.UserData.DocumentPrinted.Param2;
      Username = $_.Event.UserData.DocumentPrinted.Param3;
      DisplayName = $User.DisplayName;
      Company = $User.Company;
      Department = $User.Department;
      Computer = $_.Event.UserData.DocumentPrinted.Param4;
      PrinterName = $_.Event.UserData.DocumentPrinted.Param5;
      PrinterPort = $_.Event.UserData.DocumentPrinted.Param6;
      PrintSize = [int]$_.Event.UserData.DocumentPrinted.Param7[0];
      PrintPages = [int]$_.Event.UserData.DocumentPrinted.Param7[1];
            FileDate = $filedate;
            }
        } else {
      New-Object PSObject -Property @{

      time = $_.Event.System.TimeCreated.SystemTime;
      docName = $_.Event.UserData.DocumentPrinted.Param2;
      Username = $_.Event.UserData.DocumentPrinted.Param3;
      DisplayName = $User.DisplayName;
      Company = $User.Company;
      Department = $User.Department;
      Computer = $_.Event.UserData.DocumentPrinted.Param4;
      PrinterName = $_.Event.UserData.DocumentPrinted.Param5;
      PrinterPort = $_.Event.UserData.DocumentPrinted.Param6;
      PrintSize = [int]$_.Event.UserData.DocumentPrinted.Param7;
      PrintPages = [int]$_.Event.UserData.DocumentPrinted.Param8;
            FileDate = $filedate;
            }

 }

}

$Events1 | export-csv -Path $out_csv_file -NoTypeInformation -Encoding Unicode

}

$a3

Продолжение следует…

Реклама

комментария 3

Добавить комментарий

Заполните поля или щелкните по значку, чтобы оставить свой комментарий:

Логотип WordPress.com

Для комментария используется ваша учётная запись WordPress.com. Выход / Изменить )

Фотография Twitter

Для комментария используется ваша учётная запись Twitter. Выход / Изменить )

Фотография Facebook

Для комментария используется ваша учётная запись Facebook. Выход / Изменить )

Google+ photo

Для комментария используется ваша учётная запись Google+. Выход / Изменить )

Connecting to %s

%d такие блоггеры, как: