Раньше многие системные администраторы относились к обеспечению безопасности сети как к необязательной функции, которую можно выполнять в свободное от работы время. Теперь ситуация изменилась. Многие администраторы глубоко погружены в процессы аудита и вынуждены тратить значительную долю своих небольших бюджетов на приобретение средств аудита безопасности. Чтобы получить ответы на базовые вопросы, касающиеся безопасности сети, администраторам приходится выуживать сведения из бесчисленных журналов событий.

Аудит журналов событий является трудоемким и утомительным, но очень важным процессом. К сожалению, очень многие пока еще не приучились регулярно сохранять журналы событий. Из-за этого при необходимости формирования отчетов часто приходится довольствоваться неполными данными, что негативно сказывается на качестве отчета. Для начала следует попробовать создать систему сохранения журналов событий и придумать простой и удобный способ для генерации отчетов по этим журналам событий. В этой статье будет рассказано об организации автоматического сохранения журналов событий безопасности в текстовом формате для дальнейшей обработки и будет представлен сценарий для создания отчета о регистрации и завершении сеансов пользователей в системе по учетной записи и диапазону дат.

PsLogList

Широко известен набор утилит PsTools, которые можно загрузить с сайта www.sysinternals.com. Одной из этих утилит, PsLogList, я решил воспользоваться для сохранения снимков журнала событий безопасности в формате CSV с удаленных компьютеров Windows XP, Windows 2000 и Windows NT. Формат CSV идеально подходит для обработки с помощью сценариев, для разбора и обработки файлов CSV можно даже использовать простой цикл For командного интерпретатора Windows NT. Общий синтаксис вызова PsLogList для сохранения журнала событий в файл

PsLogList -s   

Так, следующая команда

PsLogList -s MyPDC security pdclog.csv

выполняет сохранение журнала безопасности с компьютера MyPDC в файл pdclog.csv. Следует иметь в виду, что при первом запуске исполнение команды может занять значительное время, но если запускать PsLogList ежедневно, задержка не будет очень большой.

Info-ZIP

При ежедневном запуске PsLogList получаемые файлы снимков журнала со временем накапливаются в огромном количестве, так что было принято решение сгруппировать их помесячно и сжать в архивы zip. У каждого, кто занимается разработкой сценариев, есть любимая утилита для работы с архивами Zip. Для данной задачи благодаря простоте использования была выбрана бесплатная утилита Info-ZIP. Утилиты Zip и UnZip можно загрузить с сайта со страниц http://www.info-zip.org/zip.html#win32 и http://www.info-zip.org/unzip.html#win32 соответственно. Если для работы с архивами Zip вы решите использовать другие утилиты, вам потребуется внести незначительные изменения в параметры командной строки для формирования архивов.

LogDump.cmd

Первым шагом к построению системы аудита событий регистрации и завершения сеанса logon/logoff будет создание ежедневных снимков журналов безопасности для всех контроллеров домена. Для автоматизации этого процесса был разработан сценарий LogDump.cmd, представленный в листинге 1. Хотя сценарий можно настроить для работы на любом компьютере под управлением Windows 2000 или Windows NT, для простоты представлен вариант для небольшой сети Windows NT с одним первичным и одним резервным контроллерами домена (PDC и BDC).

Чтобы сделать сценарий универсальным для работы с любым количеством серверов, можно указать список серверов в переменной serverlist, значения в которой перебираются в цикле For, в котором запускаются программы PsLogList и Info-ZIP для каждого из серверов сети. В нашем примере пакетный файл LogDump.cmd сохраняет снимок журналов безопасности на PDC и BDC, и сохраняет результаты на каждом из серверов в каталоге C:logsseclogs. Если указанный каталог на сервере не существует, сценарий создает этот каталог с помощью команды MD (см. фрагмент B листинга 1).

Пакетный файл LogDump.cmd создает файлы журнала с именами в формате servername_date.txt, где дата имеет формат mm_dd_yyyy, и архивирует сохраненные журналы в ежемесячные архивы, так все журналы безопасности за июль 2002 года будут сохранены в архив servername_seclog_07_2002.zip. Фрагмент А кода сценария считывает текущую системную дату, выделяет элементы число, месяц, год и сохраняет значения для формирования имени файла и имени архива.

Команда date /t возвращает текущую дату в формате Tue 07/02/2002 без запроса ввода новой даты (прим. Редакции: на компьютерах Windows 2003 и Windows XP с региональными установками для России date /t выдает дату в формате дд.мм.гггг, так что этот фрагмент сценария следует изменить). Далее сценарий выполняет команду For, чтобы разбить дату по разделителю «/» на элементы.

set filedate=%filedate:~4,10%

при этом из параметра filedate удаляется название дня недели, точнее говоря, берется подстрока со смещения 4 до смещения 10 (конструкция ~4,10 в рассматриваемой команде).

По завершении выполнения фрагмента А переменная filedate имеет значение 07_02_2002, zipdate равно 07_2002. В Windows 2000 вместо команды date /t можно воспользоваться командой echo %date%. После того, как имена файлов сформированы, LogDump.cmd выполняет команду PsLogList для сохранения снимка журнала безопасности Security. Следует включить в планировщик ежедневное выполнение этого сценария, чтобы обеспечить сохранение полной информации в удобном для анализа формате. LogDump.cmd выполняет экспорт журнала безопасности в формате CSV и, чтобы избежать дублирования информации при последующих запусках программы, очищает журнал безопасности. Для этого используются ключи программы PsLogList -s - использование формата CSV и -c очистить журнал после выполнения экспорта, как видно из фрагмента кода С листинга 1. В выдаваемом командой PsLogList файле содержится также заголовок, который помешал бы при построении отчета о регистрации пользователей в сети (этот сценарий будет представлен в следующей статье). Чтобы удалить этот заголовок, результат работы утилиты PsLogList передается на вход команде Find, которая удаляет все строки, не содержащие символ запятой. Эта операция выполняется фрагментом С сценария. Полученный результат сохраняется на сервере в файле C:logsseclog_%filedate%.txt

Как я уже упоминал ранее, сохранение снимков журналов безопасности в текстовом формате на сервере - это далеко не идеальное решение, особенно если в сети за сутки регистрируется большое количество событий. Поэтому все сохраненные журналы безопасности архивируются в ежемесячный и ежегодный архив, как показывает фрагмент D. Ключ -j сообщает Info-ZIP не сохранять в архивном файле имя каталога при добавлении текстовых файлов в архив, ключ -m указывает, что после архивирования текстовый файл должен быть удален. Все рабочие файлы, создаваемые сценарием, имеют расширение .txt. Поскольку сценарий перемещает в архив zip все найденные в каталоге текстовые файлы, после исполнения LogDump.cmd в каталоге остаются только файлы ежемесячных архивов zip.

Если в вашей сети запрещено открывать совместный доступ к дискам (т.е. использовать папки вида C$), то для хранения архивов журналов вы можете создать на отдельном сервере скрытую общую папку с ограниченным доступом - это сделает обработку журналов более удобной и более соответствует требованиям безопасности.

Создание отчетов

Отчет о регистрации и завершение сеансов пользователей является одним из наиболее распространенных при аудите безопасности компьютерной сети. Эти отчеты могут использоваться для широкого круга задач, от подсчета времени, проведенного сотрудником на рабочем месте до обнаружения подозрительной сетевой активности. Алгоритм формирования подобных отчетов для определенной учетной записи пользователя можно описать следующим образом:

  • сбор журналов аудита безопасности за требуемый интервал времени;
  • отбор из журнала событий, относящихся к интересующей нас деятельности пользователя. Однако написание надежного сценария для автоматизации этой работы может оказаться не таким простым делом.

Сценарий LogDump.cmd сохраняет снимки журналов безопасности с контроллеров домена Windows NT и Windows 2000. Снимки журналов сохраняются в файлы в формате CSV, которые помещаются в ежемесячные архивы .zip для более удобного и эффективного хранения. Рассмотрим теперь сценарий Logonlogoff.cmd (приведн в листинге 1), предназначенный для формирования отчета о регистрации и завершении сеанса заданной учетной записи пользователя за указанный интервал времени. При этом сценарий Logonlogoff.cmd использует ежедневные снимки журналов безопасности с контроллеров домена, сохраненные в архив сценарием LogDump.cmd. В сценарии Logonlogoff.cmd выполняется распаковка из архивов снимков журналов за указанный диапазон дат (для этого используется утилита UnZip), после чего происходит обработка и построение отчета.

Требования

Прежде чем начинать разработку кода необходимо принять во внимание требования и ограничения, предъявляемые к процессу обработки данных. Во-первых, необходимо обрабатывать только архивы .zip, созданные утилитой LogDump.cmd. До этого необходимо убедиться, что LogDump.cmd ежедневно выполняется на контроллерах домена и соблюдены все соглашения об именовании файлов. Сценарий должен иметь возможность обрабатывать архивные снимки журнала безопасности на компьютерах Windows NT и Windows 2000, а из-за того, что описания событий в этих системах существенно отличаются, используются разные варианты кода в процедуре формирования отчетов. Сценарий должен позволять указывать начальную и конечную дату отчета с учетом различного количества дней в месяцах и високосных годов. Наконец, сценарий должен выдавать отчет только для одного пользователя.

Определение високосного года

При составлении отчета может быть указан любой отрезок времени, который может захватывать несколько месяцев или даже лет, так что сценарий формирования отчета о регистрации пользователя в сети и должен корректно обрабатывать даты. Одни месяцы в году состоят из 30 дней, другие из 31, а февраль может содержать 28 или 29 дней в зависимости от того, високосный год или нет. Как все, наверное, помнят, високосным является год, делящийся нацело на 4, кроме делящихся нацело на 100, при этом високосными являются также годы, делящиеся нацело на 400. К сожалению, командный интерпретатор Windows NT и Windows 2000 этого не знает. Обходное решение заключается в том, что при целочисленном делении остаток отбрасывается, т.е. 5 / 2 = 2, а не 2,5. Ниже приведен псевдокод для определения, кратно ли число четырем.

  1. temp = number / 4
  2. result = temp * 4
  3. если result = number, то number кратно 4, иначе number на 4 нацело не делится.

Например, если number = 17, тогда temp = 6, result = 16 - таким образом можно определить, что 17 не кратно 4. Командный интерпретатор позволяет вычислять арифметические выражения с помощью команды Set с ключом /a. Фрагмент E листинга 2 определяет, является ли год високосным, для чего выполняется проверка делимости года на 4, 400 и 100.

Сценарий Logonlogoff.cmd

Сценарий Logonlogoff.cmd начинается с команды

Set serverlist=  

определяющей имена серверов, на которых хранятся обрабатываемые журналы безопасности. Далее сценарий создает подкаталог logonlogoff в каталоге %temp% для хранения рабочих файлов, имя рабочего каталога сохраняется в переменной logonlogofftemp для дальнейшего использования.

Далее во фрагменте А выполняется присваивание переменным значений параметров командной строки, в том числе начальной и конечной дат (для дат используется формат mm/dd/yyyy), имени пользователя, для которого будет построен отчет (именно с это имя будет иметь файл отчета) и тип файла журнала - Windows 2000 (по умолчанию) или Windows NT, поскольку файлы журналов в этих операционных системах имеют некоторые отличия в формате и должны обрабатываться чуть по-разному.

Потом сценарий определяет текущий год с помощью команды date /t (на серверах Windows 2000 вместо date /t можно использовать команду echo %date%). Далее из строки текущей даты выбирается значение года (третий элемент строки даты с разделителем /), которое сохраняется в переменной curr_year.

For /f "tokens=3 delims=/" %%i
in ('date /t') do set
curr_year=%%i

Важным шагом является проверка правильности указанных начальной и конечной дат, что конечная дата следует за, а не предшествует начальной. Для этого выполняется разбиение дат на год, месяц и день, полученные значение сохраняются в переменных, а затем выполняются сравнения. В цикле For происходит разбиение дат на элементы по символам разделителя прямая и обратная косая (/ и ). Эта операция соответствует фрагменту кода B.

При этом автоматически происходит проверка, что даты указаны в формате mm/dd/yyyy (поскольку символом разделителем является прямая косая /, то если использован формат mm-dd-yyyy, при разборе строки даты значения дня и года окажутся не определены). Даты в сценарии хранятся разбитыми на день, месяц и год для упрощения операций сравнения дат. После разбиения дат на элементы выполняется проверка, что конечная дата следует за, а не предшествует начальной дате, в противном случае входные данные считаются противоречивыми и построение отчета не выполняется.

Следует учитывать риск, что указывая в дате день и месяц, пользователь может поставить вначале символ 0 (например, 01/15/1995), а это приведет к ошибке при сравнении значений, так как для командного интерпретатора Windows NT и Windows 2000 значение 01 может оказаться не равным 1. Для удаления символов 0 в началах элементов даты используется конструкция следующего вида:

If %start_day:~0,1% EQU 0 set start_day=%start_day:~1,1%

Выражение %start_day:~0,1% возвращает один символ значения переменной start_day с начала строки со смещением 0, т.е. первый символ. Если этот символ равен 0, то чтобы убрать первый нулевой символ, выполняется присвоение переменной start_day собственному значению со смещением на 1 (~1).

Затем выполняется группа проверки набора условий, сначала для начальной и конечной дат. Проверяется, является ли год високосным, для чего сначала переменная очищается, затем вызывается фрагмент кода Е, выполняющий проверку. Если год високосный, переменной isStartLeapYear присваивается значение TRUE.

Определение количества дней в месяце выполняется следующим образом. Сначала количество дней в месяце (no_days) полагается равным 31, после чего с помощью серии операторов If для апреля, июня, сентября и ноября no_days присваивается 30. Для февраля сначала no_days присваивается 28, затем в високосный год значение исправляется на 29. В конце проверяется, что число начального дня не превосходит количества дней в этом месяце, на тот случай, если кто-то захочет указать начальную дату 31 апреля. Аналогичные проверки выполняются и для конечной даты.

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

If %end_year% GTR %start_year%
	goto :proceed
If %end_year% LSS %start_year%
	goto :syntax

Если выражения в обоих операторах If имеет значение FALSE, значит начальный и конечный годы совпадают, и следует выполнить проверку месяца. Аналогично, если и месяцы совпадают, проверяется начальный и конечный дни. Конечно, может показаться, что в сценарии присутствует слишком большое количество проверок, но при практическом использовании это оправдано. Анализ файлов журнала при формировании отчета может занимать достаточно продолжительное время, и если входные параметры заданы неверно, то время обработки будет просто потеряно.

Формирование отчетов

Следующим шагом является формирование отчета. Сценарий Logonlogoff.cmd сначала копирует и распаковывает файлы .zip начиная с начального года и месяца по конечные год и месяц (см. фрагмент С). Как вы помните, сценарий LogDump.cmd, описанный выше, сохраняет снимки журналов безопасности в ежемесячных архивах, причем имя архива имеет формат %server%_SECLOG_%zipdate%.zip, где %server% соответствует имени сервера, на котором велся журнал, а %zipdate% - представляет собой конкатенацию месяца и года в формате mm_yyyy.

Сценарий Logonlogoff.cmd сохраняет значения месяца и года для дальнейшего использования в переменных processmonth и processyear. Затем для каждого месяца, года и имени сервера, определяемого списком serverlist, сценарий выполняет следующие действия:

  1. Определяется, существует ли архив .zip с соответствующим именем, и если такой архив не найден, выполняется переход к следующему архиву.
  2. Сценарий копирует архив .zip в каталог %logonlogofftemp%, определенный как %temp%logonlogoff.
  3. С помощью утилиты UnZip сценарий выполняет извлечение файлов из архива в папку %logonlogofftemp%.

Далее выполняется проверка, приняла ли переменная stop значение TRUE, что означает, что значения переменных processmonth и processyear достигли конечных значений, и цикл обработки архивов следует прекратить. В противном случае, значение processmonth увеличивается на 1 и процесс повторяется; если достигнуто значение 13 (то есть декабрь обработан, и следует продолжать), то processmonth присваивается 1, а значение processyear увеличивается на 1.

Сценарий Logonlogoff.cmd содержит небольшой фрагмент кода для добавления 0 слева в номера месяцев из одной цифры, поскольку такой формат даты принят в журналах событий и архивах .zip - этот фрагмент выполняется при формировании имен файлов. Ниже приведен этот фрагмент:

If %start_month% GEQ 10
	goto :checkendfilter
Set start_month=0%start_month%
:checkendfilter

Поскольку файлы журнала сгруппированы по месяцам и по годам, то чтобы обрабатывать только нужные дни, следует удалить файлы из начала первого и конца последнего месяцев, которые не попадают в указанный диапазон дат. То есть, если необходимо получить отчет за период с 5/15/2002 до 6/15/2002, сначала будут распакованы файлы с 5/1/2002 по 6/30/2002, а затем удалены файлы с 5/1/2002 по 5/14/2002 и с 6/16/2002 по 6/30/2002.

Теперь в каталоге %logonlogofftemp% остались только файлы, которые требуются для формирования отчетов. Сначала сценарий Logonlogoff.cmd выбирает из всех имеющихся файлов журналов только те строки, в которых содержится нужное имя пользователя, заданное переменной userid, и сохраняет их в файле %userid%.log.

Таким образом, уже сформирован файл, содержащий все записи журнала безопасности за нужный период времени, относящиеся к требуемому пользователю. После этого из файла %userid%.log отфильтровываются все записи, относящиеся к событиям с ID 528 (Logon) и ID 538 (Logoff). События регистрации и завершения сеанса записываются в два выходных файла, %userid%_logon.log и %userid%_logoff.log. Два выходных файла используются потому, что формат записей для этих двух событий различен - события регистрации содержат имя рабочей станции, с которой выполнен вход в сеть, а при завершении сеанса пользователя информация о компьютере не сохраняется, так что эти записи обрабатываются слегка по-разному

В разделе после метки :outputreport сценария Logonlogoff.cmd выполняется преобразование выходного файла в формат .csv. Для событий регистрации в сети сценарий сохраняет значения даты, времени, имя пользователя и описание, для событий завершения сеанса выполняется сохранение только даты, времени и имени без описания. Получаемый формат даты и времени не очень подходит для использования в Excel - вы не сможете выполнить сортировку по времени, поэтому выполняется дополнительное преобразование времени возникновения события в формат месяц/день/год время с отбрасыванием названия дня недели и переводом имен месяцев в числовые значения. Определение имени компьютера, с которого пользователь выполнил регистрацию в сети, выполняется по-разному для журналов Windows NT и Windows 2000 (помните параметр командной строки, определяющий тип журнала). Для Windows 2000 имя рабочей станции содержится во втором элементе поля Description (при разборе этого поля используется разделитель обратная косая черта ). Для Windows NT это девятый элемент поля, а в качестве разделителя используется двоеточие (:). Чтобы определить регистрацию с консоли сервера следует выполнить разбиение на элементы поля Description (разделитель - закрывающая круглая скобка) и проанализировать первый символ второго элемента. При формировании отчета сценарий сохраняет в текущем каталоге файл %userid%.csv, содержащий имя пользователя, дату и время события в нормализованной форме, а для событий регистрации в сети - еще и имя компьютера. После завершения формирования отчета выполняется удаление временных файлов из каталога %logonlogofftemp%.

Ограничения сценария

В результате работы сценария Logonlogoff.cmd получается файл в формате CSV с именем %userid%.csv, содержащий сведения о регистрации пользователя в сети и завершении сеанса с указанием имен компьютеров, на которых произведена регистрация, полученный файл удобно просматривать в Microsoft Excel. Хотя этот сценарий может значительно облегчить анализ определенного класса событий в сети, ему присущи некоторые ограничения. Во-первых, сценарий получает информацию о дате каждого события из имени файла, а не из журнала, и это может создать определенные трудности, если сохранение снимка журнала не выполняется ежедневно, по меньшей мере, в отчет могут быть включены события, выходящие за рамки требуемого диапазона дат. Далее, даже если пользователь выполняет регистрацию в сети на своем компьютере только один раз в день, то и в этом случае этому единственному сеансу работы пользователя в журналах безопасности Windows может оказаться зарегистрировано несколько событий регистрации в сети и завершения сеанса. Это не является проявлением ошибки сценария, а иллюстрирует особенность Windows. Дело в том, что запись событий в журнал безопасности могут быть вызваны различными операциями, требующими аутентификации с учетной записью пользователя. Примером могут служить операции подключения сетевых дисков и обращения к другим сетевым ресурсам.

Аудит журналов событий - это утомительное, но необходимое занятие. В этой статье я постарался показать, каким образом можно автоматизировать базовые операции по сохранению журналов событий и обработке журналов для извлечения нужной информации. Предложенные сценарии помогут вам упростить получение отчетов о регистрации пользователей в сети и завершении сеанса.


Листинг 1. Сценарий LogDump.cmd
@Echo Off
setlocal
:: Список имен серверов, разделенный пробелами, на которых
 будет запускаться сценарий.
set serverlist=pdc bdc
:: Формируем переменные с датой, именем сохраняемого
 снимка журнала и именем архивного файла.
:: НАЧАЛО ФРАГМЕНТА А
for /f "Tokens=1,2,3 delims=/" %%i in ('date /t')
 do set filedate=%%i_%%j_%%k &
  set zipdate=%%i_%%k
  set filedate=%filedate:~4,10%
  set zipdate=%zipdate:~4,7%
:: КОНЕЦ ФРАГМЕНТА А
:: Для каждого из перечисленных в переменной serverlist
 сервера запускаем Dumplog.
For %%i in (%serverlist%) do call :dumplog %%i
endlocal
goto :EOF
:dumplog
set server=%1
:: Проверяем, существует ли на сервере каталог для
 сохранения журналов,
:: создаем его при необходимости.
:: НАЧАЛО ФРАГМЕНТА В
IF NOT EXIST \%server%c$logsseclog Echo Creating
 \%server%c$logsseclogВ…&
  MD \%server%c$logsseclog
:: КОНЕЦ ФРАГМЕНТА В
Echo Создаем снимок журнала безопасности для сервера %server%...
:: НАЧАЛО ФРАГМЕНТА С
psloglist -s -c \%server% security  | find /I "," >>
  \%server%c$logsseclog\%server%_%filedate%.txt
:: КОНУЦ ФРАГМЕНТА С

Echo Переносим в архив журнал безопасности для сервера %server%...
:: Переносим созданный текстовый файл в архив .zip
:: НАЧАЛО ФРАГМЕНТА D
zip -j -m \%server%c$logsseclog\%server%_SECLOG_%zipdate%.zip
  \%server%c$logsseclog*.txt
:: НАЧАЛО ФРАГМЕНТА D
goto :EOF
:EOF
Листинг 2. Logonlogoff.cmd
@Echo Off
setlocal
:: Задаем имена серверов, журналы событий с которых были сохранены
:: в архивах .zip с помощью сценария LogDump.cmd.
Set serverlist=SERVER1 SERVER2 SERVER3
:: Проверка: при вызове должны быть заданы три входных параметра.
IF #%4#==## goto :syntax
:: Подготовка временного каталога: создание и очистка.
set logonlogofftemp=%temp%logonlogoff
IF EXIST %temp%logonlogoff rmdir /s /q %logonlogofftemp%
md %logonlogofftemp%
:: Сохранение параметров запуска.
:: НАЧАЛО ФРАГМЕНТА А
set start_date=%1
set end_date=%2
set userid=%3
set logtype=win2k
if /I #%4#==#NT# set logtype=NT
:: КОНЕЦ ФРАГМЕНТА А
:: Если файл результата уже существует, удаляем его.
if exist %userid%.csv del /q %userid%.csv
:: Определяем нынешний год с помощью команды datr /t
 и сохраняем значение в curr_year.
for /f "tokens=3 delims=/" %%i in ('date /t') do set curr_year=%%i
:: Разбор дат
set start_month=
set start_day=
set start_year=
set end_month=
set end_day=
set end_year=
:: Разбор на число-месяц-год входных параметров - начальной
 и конечной даты.
:: НАЧАЛО ФРАГМЕНТА В
for /f "tokens=1-3 delims=/" %%i in ('echo %start_date%')
 do set start_month=%%i& set start_day=%%j& 
set start_year=%%k
for /f "tokens=1-3 delims=/" %%i in ('echo %end_date%')
 do set end_month=%%i& set end_day=%%j& 
set end_year=%%k
:: КОНЕЦ ФРАГМЕНТА В
:: Проверяем, что даты при запуске были указаны в требуемом формате.
if not defined start_year goto :syntax
if not defined end_year goto :syntax
:: Если указанн год позже нынешнего, журналов еще нет.
 Неправильные параметры, показать синтаксис.
if %start_year% GTR %curr_year% goto :syntax
if %end_year% GTR %curr_year% goto :syntax
:: Если в параметр число начинается с символа 0 - отбрасываем 0.
if %start_day:~0,1% EQU 0 set start_day=%start_day:~1,1%
if %end_day:~0,1% EQU 0 set end_day=%end_day:~1,1%
:: Проверка високосного года и начальной даты.
set isStartLeapYear=
call :isleapyear %start_year%
if NOT defined isleapyear goto :checkstartvalid
:: Раз пошли по этой ветке, значит год високосный.
 set isStartLeapYear=TRUE
:checkstartvalid
set no_days=31
:: В апреле, июне, сентябре и ноябре по 30 дней.
if %start_month% EQU 4 set no_days=30 & goto :check_days
if %start_month% EQU 6 set no_days=30 & goto :check_days
if %start_month% EQU 9 set no_days=30 & goto :check_days
if %start_month% EQU 11 set no_days=30 & goto :check_days
:: Обычно в феврале 28 дней, а в високосный год - 29.
 if NOT %start_month% EQU 2 goto :check_days
set no_days=28
if defined isStartLeapYear set no_days=29
:: Проверка непротиворечивости входных дат. Конечная дата должна быть 
:: позже начальной. Если не так - показать синтаксис и завершить сценарий.
:check_days
IF %start_day% GTR %no_days% goto :syntax
:: Проверка високосного года и настройка конечной даты.
set isEndLeapYear=
call :isleapyear %end_year%
if NOT defined isleapyear goto :checkendvalid
:: Если пошли по этой ветке - год високосный.
set isEndLeapYear=TRUE
:checkendvalid
set no_days=31
if %end_month% EQU 4 set no_days=30 & goto :check_days2
if %end_month% EQU 6 set no_days=30 & goto :check_days2
if %end_month% EQU 9 set no_days=30 & goto :check_days2
if %end_month% EQU 11 set no_days=30 & goto :check_days2
:: Февраль обрабатывается по особому, вдруг год високосный.
if NOT %end_month% EQU 2 goto :check_days2
set no_days=28
if defined isEndLeapYear set no_days=29
:check_days2
IF %end_day% GTR %no_days% goto :syntax
:: Вроде все даты проверили, осталось убедиться, что 
:: начальная дата предшествует конечной, а не наоборот.
if %end_year% GTR %start_year% goto :proceed
if %end_year% LSS %start_year% goto :syntax
:: Раз мы здесь, значит начальный и конечный годы совпали,
 надо проверить месяц.
if %end_month% GTR %start_month% goto :proceed
if %end_month% LSS %start_month% goto :syntax
:: Раз дошли сюда, значит начальный и конечный месяцы совпали,
 проверяем число.
 if %end_day% LSS %start_day% goto :syntax
:proceed
@CLS
@ECHO.
@ECHO You have requested to generate a logon/logoff report for
@ECHO %userid% for the period %start_date% to %end_date%.
@ECHO If successful, the file %userid%.csv will be created in
@ECHO the current directory.
@ECHO Please wait while I process your request...
@ECHO.
@ECHO Вы запросили создание отчета о подключении к сети пользователя 
@ECHO %userid% за период с %start_date% по %end_date%.
@ECHO Если все параметры верны, в текущем каталоге будет создан
 отчет %userid%.csv.
@ECHO Дождитесь окончания формирования отчета по запросу...
@ECHO.
:: Распаковка архивов с данного сервера для обработки журналов.
:: НАЧАЛО ФРАГМЕНТА С
set processmonth=%start_month%
set processyear=%start_year%
if %processmonth:~0,1% EQU 0 set processmonth=%processmonth:~1,1%
if %end_month:~0,1% EQU 0 set end_month=%end_month:~1,1%
:extractzips
set stop=
:: Если начальные год и месяц совпадают с конечными,
 выполняем один цикл и идем дальше.
if %processmonth%%processyear% EQU %end_month%%end_year%
 set stop=true
if %processmonth% GEQ 10 goto :copyandextract
:: Если надо, дополняем месяц нулем слева.
set processmonth=0%processmonth%
for %%i in (%serverlist%) do call :copyandextract %%i
goto donecopy
:copyandextract
set server=%1
:: Проверяем, сохранены ли для этого месяца и года журналы.
 if NOT exist \%server%c$logsseclog\%server%
_seclog_%processmonth%_%processyear%.zip 
ECHO %server% Log for %processmonth%\%processyear%
 doesn't exist & goto :EOF
ECHO Processing %server%_seclog_%processmonth%_%processyear%.zip...
copy \%server%c$logsseclog\%server%_seclog_
%processmonth%_%processyear%.zip 
%logonlogofftemp% > NUL
unzip %logonlogofftemp%\%server%_seclog_
%processmonth%_%processyear%.zip -d 
%logonlogofftemp% > NUL
goto :EOF
:donecopy
:: Если установлена переменная stop, пора остановиться.
 if defined stop goto :searchlogonlogoff
:: Надо обрабатывать следующий месяц.
if %processmonth:~0,1% EQU 0 set processmonth=%processmonth:~1,1%
set /a processmonth=processmonth+1
:: Если дошли до декабря (12), переключаемся
 на январь следующего года.
IF %processmonth% GTR 12 set /a processyear=processyear+1
IF %processmonth% GTR 12 set processmonth=1
goto :extractzips
:: КОНЕЦ ФРАГМЕНТА С
:: Поиск событий с кодами ID 528 (Logon) и ID 538 (Logoff)
 для данного пользователя.
:searchlogonlogoff
:: Удаляем файлы за числа, не попавшие в требуемый диапазон дат.
 При необходимости дополняем
:: начальный и конечный месяцы нулями слева.
if %start_month:~0,1% EQU 0 set start_month=%start_month:~1,1%
if %end_month:~0,1% EQU 0 set end_month=%end_month:~1,1%
if %start_month% GEQ 10 goto :checkendfilter
set start_month=0%start_month%
:checkendfilter
if %end_month% GEQ 10 goto :filterlogs
set end_month=0%end_month%
:filterlogs
ECHO Removing unnecessary temporary log files...
ECHO Удаляем лишние временные файлы...
:: Для каждого файла выполняем проверку дат и удаляем лишние файлы.
:: НАЧАЛО ФРАГМЕНТА D
for /f %%i in ('dir /b %logonlogofftemp%*.txt ^| find /I
 "DC_%start_month%" ^| find /I "_%start_year%"') do 
call :deleteunwanted %%i start
for /f %%i in ('dir /b %logonlogofftemp%*.txt ^| find /I
 "DC_%end_month%" ^| find /I "_%end_year%"') do 
call :deleteunwanted %%i end
:: КОНЕЦ ФРАГМЕНТА D
:: Объединяем для обработки в единый файл все журнальные записи, 
:: относящиеся к указанному пользователю.
ECHO Filtering log files (this may take a while)...
for /f %%i in ('dir /b %logonlogofftemp%*.txt') do type
 %logonlogofftemp%\%%i | find /I "%userid%" >> 
%logonlogofftemp%\%userid%.log
:: Теперь файл содержит только записи об указанном пользователе,
 выводим в два отдельных файла
:: записи о событиях регистрации (ID=528) и завершении сеанса (ID=538).
type %logonlogofftemp%\%userid%.log | find ",528,"
 > %logonlogofftemp%\%userid%_logon.log
type %logonlogofftemp%\%userid%.log | find ",538,"
 > %logonlogofftemp%\%userid%_logoff.log
:: Формируем заголовок итогового отчета.
ECHO Username,Date and Time,Logon/Logoff,Workstation >> %userid%.csv
for /f "tokens=6,8,9 delims=," %%i in (%logonlogofftemp%\%userid%
_logon.log) do call :outputreport 
"%%i" %%j Logon "%%k"
for /f "tokens=6,8 delims=," %%i in (%logonlogofftemp%\%userid%
_logoff.log) do call :outputreport "%%i" 
%%j Logoff
:done
ECHO Cleaning Up...
ECHO Удаляем временные файлы...
:: Удаляем файлы журналов и сам временный рабочий каталог.
RMDIR /S /Q %logonlogofftemp%
ECHO.
ECHO The report has been created as %userid%.csv
endlocal
goto :EOF
:: Выдача отчет о пользователе.
:outputreport
set date_time=%1
set the_user=%2
set logon_logoff=%3
set date_time=%date_time:"=%
:: Берем только дату и время, без дня недели, для этого отбрасываем
 первые четре символа
:: переменной date_time (три символа на день недели плюс пробел).
set date_time=%date_time:~4,100%
set t_month=
set t_date=
set t_time=
set t_year=
set logon_wks=
:: Для записей о регистрации в сети извлекаем имя рабочей станции,
 с которой зашел пользователь.
if /I "%logon_logoff%"=="Logon" set logon_wks=%4
:: Разбор даты и времени на элементы.
for /f "tokens=1-4" %%j in ('echo %date_time%')
 do set t_month=%%j& set t_date=%%k& set 
t_time=%%l& set t_year=%%m
:: Меняем символьные имена месяцев на номера.
IF /I "%t_month%"=="Jan" set t_month=01
IF /I "%t_month%"=="Feb" set t_month=02
IF /I "%t_month%"=="Mar" set t_month=03
IF /I "%t_month%"=="Apr" set t_month=04
IF /I "%t_month%"=="May" set t_month=05
IF /I "%t_month%"=="Jun" set t_month=06
IF /I "%t_month%"=="Jul" set t_month=07
IF /I "%t_month%"=="Aug" set t_month=08
IF /I "%t_month%"=="Sep" set t_month=09
IF /I "%t_month%"=="Oct" set t_month=10
IF /I "%t_month%"=="Nov" set t_month=11
IF /I "%t_month%"=="Dec" set t_month=12
:: Если значение logon_wks не определено, это, наверное, 
:: событие Logoff - выводим в журнал без обработки.
IF NOT DEFINED logon_wks goto :output_to_log_logoff
:: Извлекаем имя рабочей станции. В зависимости от ОС
 используется разная обработка.
IF #%logtype%#==#win2k# goto :win2klogtype
:NTlogtype
for /f "tokens=9 delims=:" %%a in ('echo %logon_wks%')
 do set logon_wks=%%a
set logon_wks=%logon_wks:"=%
set logon_wks=%logon_wks: =%
set logon_wks=%logon_wks:=%
goto :output_to_log_logon
:win2klogtype
:: При интерактивном входе имя рабочей станции не сохраняется,
 обрабатываем отдельно.
for /f "tokens=2 delims=)" %%k in ('echo %logon_wks%')
 do set logontype=%%k
set logontype=%logontype:~0,1%
IF %logontype% EQU 2 goto :interactive
:: Имя рабочей станции в текущем значении logon_wks
 следует после префикса ''. 
for /f "tokens=2 delims=" %%k in ('echo %logon_wks%')
 do set logon_wks=%%k
:: Чтобы все потом обрабатывалось нормально, удаляем все двойные кавычки.
set logon_wks=%logon_wks:"=%
goto :output_to_log_logon
:interactive
set logon_wks=Interactive
:output_to_log_logon
echo %the_user%,%t_month%/%t_date%/%t_year% %t_time%,%logon_logoff%,
%logon_wks% >> 
%userid%.csv
goto :EOF
:: Вывод в файл журнала.
:output_to_log_logoff
echo %the_user%,%t_month%/%t_date%/%t_year% %t_time%,%logon_logoff%
 >> %userid%.csv
goto :EOF
:: Удаляем лишние файлы журналов.
:deleteunwanted
set checkfile=%1
set start_end=%2
set checkfiledate=
:: Определяем дату по имени файла.
for /f "Tokens=4 delims=_" %%j in ('echo %checkfile%')
 do set checkfiledate=%%j
if NOT defined checkfiledate goto :EOF
:: Для корректного сравнения удаляем нули слева.
if %checkfiledate:~0,1% EQU 0 set checkfiledate=%checkfiledate:~1,1%
if /I "%start_end%"=="start" goto checkstart
if /I "%start_end%"=="end" goto checkend
goto :EOF
:checkstart
if %checkfiledate% LSS %start_day% del /q "%logonlogofftemp%
%checkfile%"
goto :EOF
:checkend
if %checkfiledate% GTR %end_day% del /q "%logonlogofftemp%
%checkfile%"
goto :EOF
:: Выдать синтаксис запуска сценария и завершить работу.
:syntax
ECHO.
ECHO SYNTAX: logonlogoff.cmd ^ ^
 ^ ^
ECHO         log_type specifies type Operating system of the servers
ECHO         the logs we are processing are dumped from and can be
ECHO         the values NT or win2k, default is win2k
ECHO         log_type указывает тип ОС для серверов. Журналы могут быть
 сохранены с серверов
ECHO         под управлением Windows NT/2000, log_type может принимать
 значения NT и win2k,
ECHO         по умолчанию используется значение win2k
ECHO.
ECHO EXAMPLE: logonlogoff.cmd 01/02/2001 01/15/2001 010626 win2k
ECHO Пример запуска: logonlogoff.cmd 01/02/2001 01/15/2001 010626 win2k
ECHO.
goto :EOF
:: Проверка високосного года.
:: НАЧАЛО ФРАГМЕНТА Е
:isleapyear
set year=%1
set test=
set product=
set isLeapYear=
set /a test = year / 4
set /a product = test * 4
IF NOT %year% EQU %product% goto :EOF
set /a test = year / 400
set /a product = test * 400
IF %year% EQU %product% set isLeapYear=TRUE & goto :EOF
set /a test = year / 100
set /a product = test * 100
IF NOT %year% EQU %product% set isLeapYear=TRUE
goto :EOF
:: КОНЕЦ ФРАГМЕНТА Е
:EOF