Contents of this directory is archived and no longer updated.

Posts on this page:

После небольшого перерыва продолжаю допиливать свой вариант FCIV на PowerShell. И радостно могу сообщить, что уже есть версия 1.0, т.е. полностью отвечающая нашим требованиям. Что изменилось в новой версии?

  • Основная команда переименована с Get-PsFCIV в Start-PsFCIV;
  • Включена предварительная проверка файлов на блокировку;
  • В связи с предыдущим пунктом, к  параметру Show добавлен аргумент – Locked. В это свойство помещаются заблокированные файлы (а для них нельзя подсчитать хеш);
  • Включена проверка пути. Если путь к исходной папке не является путём файловой системы, то скрипт будет генерировать ошибку;
  • Исправлена ошибка невозврата на исходный путь, если в процессе работы произошла фатальная ошибка;
  • Параметр Show теперь выводит файлы из указанных категорий в красивое графическое окошко с использованием Out-GridView. Однако, следует учесть, что использование Out-GridView требует, чтобы был установлен .NET Framework 3.5 SP1.
  • Добавлен режим Quiet, который ничего не выводит на экран, а только генерирует коды возврата (LastExitCode). Коды возврата обновлены.
  • Пофиксены сообщения режимов Verbose и Debug.
  • Исправлены мелкие неточности в коде и произведена небольшая оптимизация кода.

А теперь и on-line справка по всем параметрам по просьбе трудящихся.

  • Path <String> — путь к папке, файлы которой следует посчитать или проверить. Допускаются относительные пути. Данный параметр обязателен.
  • XML <String> — путь к XML файлу, который содержит сведения о файлах. Если указанный файл не существует, то после пересчёта файлов по этому пути будет создан новый файл БД со сведениями о файлах. Параметр обязателен.
  • Include <String> — опциональный параметр в котором вы можете указать только конкретный файл из папки. В таком случае будет проверена не вся папка, а только указанный файл. В настоящее время параметр не поддерживает подстановочные знаки (wildcard), типа '*.ext' из-за обеспечения поддержки файлов с метасимволами (например, в имени содержатся квадратные скобки).
  • Action <String> — опциональный параметр, который указывает на действие над файлами, у которых не совпадают хеши или сведения о дате изменения и размере файла. Может принимать значение Rename или Delete. В первом случае к проблемным файлам добавляется расширение .BAD, а во втором файл просто удаляется.
  • Show <String[]> — опциональный параметр, в котором указываются категории файлов для дальнейшего анализа. В зависимости от результата проверки каждый файл (его имя) помещается в ту или иную категорию. Требует установленного .Net Framework 3.5 SP1. Может принимать один или несколько аргументов из списка:
    • Bad — содержит имена всех файлов, у которых обнаружен несовпадающий хеш или не совпадает дата/время изменеия и/или размер файла;
    • Locked — в эту категорию помещаются файлы хеш которых проверить не удалось по причине блокировки файла в монопольном режиме каким-то приложением;
    • Missed — содержит имена файлов, для которых есть запись в XML файле БД, но самого файла уже не существует (по любым причинам);
    • New — в эту категорию попадают только имена новых файлов, для которых соответствующей записи ещё нет в XML файле. Новые файлы добавляются только в режиме Rebuild или когда файл БД создаётся с нуля.
    • Ok — сюда попадают файлы с успешным статусом проверки, т.е. хеш, дата/время изменения и размер файла совпадают со значениями в XML файле;
    • Total — содержит имена всех обработанных файлов вне зависимости от результата проверки;
    • Unknown — содержит имена файлов, для которых нельзя сопоставить хеш. Такая ситуация возможна, если в параметрах функции указан только хеш SHA1, но для файла в БД записан только MD5 хеш и наоборот.
  • Recurse <Switch> — ключ, который включает проверку файлов не только в указанной папке, но и во всех вложенных папках.
  • Rebuild <Swtich> — ключ, который задаёт особый режим работы скрипта — «освежение». Если этот ключ указан, то производится сверка файлов из БД с реальными файлами. Если файл более не существует, то соответствующая запись для него удаляется из БД. После чего целевая папка (и подпапки при указанном ключе –Recurse) проверяется на наличие новых файлов. Для каждого нового файла добавляется соответствующая запись в XML файл.
  • SHA1 <Switch> — задаёт алгоритм хеширования, который будет использоваться для подсчёта новых файлов и/или для проверки уже существующих записей в XML файле.
  • MD5 <Switch> — задаёт алгоритм хеширования, который будет использоваться для подсчёта новых файлов и/или для проверки уже существующих записей в XML файле.
  • Quiet <Switch> — включает несопровождаемый режим работы скрипта. Если ключ указан, то итоговая статистическая информация не выводится на экран, а в зависимости от результата проверки генерируется код возврата (LastExitCode). Код возврата может принимать одно из следующих значений:
    • 0 — в процессе работы скрипта все файлы были успешно проверены и их хеш, дата/время изменения и размер соответствуют записям в БД. Так же, данный код возврата будет сгенерирован, если создавался новый файл БД и все файлы были успешно в него добавлены;
    • 1 — в процессе проверки были обнаружены файлы с несовпадающим хешем и/или датой/временем и размером;
    • 2 — в процессе проверки было обнаружено, что для записи в БД нет соответствующего файла;
    • 3 — в процессе проверки были обнаружены файлы, для которых не удалось сопоставить алгоритм хеширования с хранимым в БД алгоритмом хеширования для файла. Такая ситуация возможна, если в параметрах функции указан только хеш SHA1, но для файла в БД записан только MD5 хеш и наоборот;
    • 4 — в процессе работы скрипта для некоторых файлов не удалось подсчитать хеш по причине блокировки файла в монопольном режиме другим приложением;
    • 5 — скрипт выполнялся в режиме Rebuild (освежения файла БД)
  • Verbose <Switch> — включает отображение дополнительной информации о ходе проверки файлов.
  • Debug <Switch> — включает отображение отладочной информации о ходе работы скрипта.

Несколько примеров использования:

Start-PsFCIV C:\Files db.xml -SHA1 -Recurse -Show Bad, Missed

будет проверена папка C:\Files и все вложенные папки. Файл db.xml должен быть размещён непосредственно в этой папке. Если файл не существует, то будет создан с нуля. После проверки будет показано графическое окно с именами файлов, которые попали в категорию Bad и Missed. Для каждой категории будет отдельное графическое окно.

Start-PsFCIV C:\Files db.xml -SHA1 -MD5 -Include data.dat

будет проверен только файл data.dat в папке C:\Files с использованием SHA1 алгоритмом хешиования. Если для файла в БД записан только MD5 хеш, то проверка будет произведена с использованием MD5. Если файл БД (db.xml) не существует, то создастся новый файл БД со сведениями о файле data.dat. Файл будет подсчитан с использованием как SHA1, так и MD5.

Start-PsFCIV C:\Files db.xml -SHA1 -MD5 -Rebuild

будет произведено освежение файла БД для папки C:\Files. Все записи, для которых соответствующего файла не обнаружено, будут удалены. Если в папке есть файлы, для которых нет соответствующей записи, то они будут обсчитаны с использованием алгоритмов SHA1 и MD5 и будут добвлены в XML файл. Файл db.xml должен существовать, иначе скрипт вернёт фатальную ошибку.

Start-PsFCIV C:\Files db.xml -SHA1 -Quiet

Папка C:\Files будет проверена в несопровождаемом режиме с использованием алгоритма SHA1. По умолчанию никакой информации на экране не будет. После окончания работы, в зависимости от результатов проверки, скрипт сгенерирует соответствующий код возврата (0-5).

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

И как обычно, любые замечания, комментарии постить в каменты.

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

Итак, как с этим скриптом начинать работать. В скрипте есть 3 обязательных параметра: путь к папке, которую нужно посчитать или проверить, путь к XML файлу (если файла нету, то он будет создан) и как минимум 1 алгоритм хеширования. Т.е. минимум это должно выглядеть вот так:

.\PSFCIV_0.85.ps1 C:\Temp –xml DB.XML –sha1

При этом можно указать алгоритм –md5 или оба сразу (-sha1 –md5). В таком случае для каждого файла будет сгенерировано 2 хеша. Без указания любого из этих параметров будет сгенерирована ошибка и работа будет остановлена.

Опциональные параметры:

  • -Include file.ext – будет произведена проверка только указанного файла;
  • -Recurse – будет произведена проверка всех файлов в папке и подпапках;
  • -Rebuild – особый режим обновления XML файла, из которого будут удалены устаревшие записи и если есть новые файлы в папке или папках, то они будут добавлены в XML файл. Данный ключ требует наличия XML файла. Если файл БД не будет обнаружен, то будет выведена соответствующая ошибка. При этом режиме проверка целостности файлов не проверяется.
  • -Verbose brief/full – включает один из режимов вывода информации о ходе проверки на консоль. Можно выбрать Brief – упрощённый или Full – максимальный.
  • -Show Total/New/Ok/Bad/Miss – включает дополнительный режим вывода на экран одной из выбранных категорий. Например, всех проверенных файлов, только новых файлов, которые добавлены в БД, только файлов, которые прошли проверку, только файлы с несоответствующим хешем или только файлы, для которых есть запись в БД, но самого файла уже нету.
  • -Force Rename/Delete – включает режим действия над проблемными файлами. Можно выбрать – переименования (тогда к файлу будет добавлено расширение .BAD) или удаления файла.

На данном этапе реализована работа в соответствии с логикой (которая описана тут: PS FCIV (часть 1)) и односторонняя совместимость с FCIV. Т.е. этой утилите можно скармливать XML файл, который сгенерирован с помощью этого скрипта. В скрипте начата реализация поддержки альтернативных БД, которые не совместимы с FCIV XML. Плюс планируется обеспечение двусторонней поддержки FCIV – т.е. скрипт сможет корректно воспринимать и обрабатывать XML файлы, которые сгенерированы утилитой FCIV. Ну и оптимизация самого кода. Сам скрипт снабжён достаточно плотными комментариями, поэтому будет совсем нетрудно с ним разобраться. По мере дописывания кода буду выкладывать более актуальные версии файла.

Update 27.07.2009

  • Скрипт PsFCIV.ps1 переписан в виде Advanced Function под именем Get-PsFCIV. Теперь его достаточно загрузить в консоль используя dot-sourcing.
  • Добавлена справка для функции. После загрузки функции в консоль достаточно набрать команду: Get-Help Get-PsFCIV
  • По умолчанию на экран не выводится никакой информации, а только коды возврата. Коды возврата описаны в справке.
  • Изменён ключ -Verbose. Теперь используется стандартный ключ -Verbose для вывода расширенной информации. Аргументы не принмаются.
  • Добавлен стандартный ключ -Debug для вывода диагоностической информации.
  • Добавлена поддержка обработки XML файла сгенерированного утилитой FCIV (т.е. совместимость с FCIV теперь двусторонняя).
  • Ключ -Force заменён на ключ -Action с сохранением аргументов.
  • К ключу -Show добавлен ещё один аргумент Unknown для индикации файлов с неопределённым результатом проверки. Такое состояние у файла может быть в случае, если не удалось сопоставить алгоритмы хеширования. Т.е. когда при вызове функции указывается алгоритм –SHA1, но в XML файле есть только MD5 хеш и наоборот.

Продолжаем повествование о процессе разработки функционального аналога FCIV.

Подсчёт хешей и кодирование в Base64 String

Как я уже упоминал, самая первая и основная функциональная часть – функция подсчёта хешей. Я уже делал скрипт, который считает хеши: Полезная безделушка Hash SHA1 на PowerShell, который и стал основой моего скрипта. Однако, по одной причине саму считалку пришлось обрезать. Если посмотреть справку по FCIV (http://support.microsoft.com/kb/841290), то он не записывает в БД реальный хеш файла, а пропускает его ещё через Base64 кодировку и закодированный хеш уже записывается в XML файл. В .NET уже есть стандартный кодировщик Base64, который на входе принимает массив байтов. Если посмотреть на указанный пост из моего блога, то там я приводил вывод объекта $hasher, который как раз и является байтовым массивом, который мы потом преобразовывали в строку. Здесь же преобразовывать в строку ничего не нужно, а сразу подаём этот массив в конвертер. Вот как выглядит функция подсчёта хеша:

function _hashbytes_ ($type, $file) {
    switch ($type) {
        "sha1" {$hasher = [System.Security.Cryptography.SHA1]::Create()}
        "md5" {$hasher = [System.Security.Cryptography.MD5]::Create()}
    }
    $inputStream = New-Object System.IO.StreamReader ($file)
    $hashBytes = $hasher.ComputeHash($inputStream.BaseStream)
    $inputStream.Close()
    $hashBytes
}

в качестве аргументов мы принимаем тип хеша – MD5 или SHA1 и сам файл:

[↓] [vPodans] _hashbytes_ sha1 wsr.txt
7
11
122
247
126
249
232
24
193
38
194
124
237
202
26
183
178
64
173
45
[↓] [vPodans] [System.Convert]::ToBase64String($(_hashbytes_ sha1 wsr.txt))
Bwt693756BjBJsJ87coat7JArS0=
[↓] [vPodans] [System.Convert]::ToBase64String($(_hashbytes_ md5 wsr.txt))
kshzMfHPdtuXdL6l/vWwPA==
[↓] [vPodans]

я подсчитал SHA1 хеш для текстового файла и на выходе получил такой же по структуре массив байтов, как и в предыдущей статье. И далее я этот массив подал в качестве аргумента для конвертера Base64 строки (статический метод ToBase64String класса System.Convert), а на выходе уже получил правильную Base64 строку. Именно эту строку FCIV записывает в файл. То же самое будем делать и мы. Кстати говоря. обратное преобразование делается через тот же класс  System.Convert, но с использованием метода FromBase64String:

[↓] [vPodans] [System.Convert]::FromBase64String("Bwt693756BjBJsJ87coat7JArS0=")
7
11
122
247
126
249
232
24
193
38
194
124
237
202
26
183
178
64
173
45
[↓] [vPodans] -join ([System.Convert]::FromBase64String("Bwt693756BjBJsJ87coat7JArS0=") | %{"{0:X2}" -f $_})
070B7AF77EF9E818C126C27CEDCA1AB7B240AD2D
[↓] [vPodans]

как видите, массив байтов получился точно такой же. Второй строкой я показал, как можно собрать строку самого хеша, которую показывают различные программы подсчёта хешей. Как можно заметить все числа в массиве меньше, чем 256, т.е. не превышают значения 28, что является 1 байтом в двоичной форме. В цикле Foreach-Object я преобразовываю каждый байт в HEX и собираю в единую строку оператором –Join, который доступен только в PowerShell V2. Но можно собрать и через [System.String]::Join() для PowerShell 1.0.

Генерация FCIV-совместимого XML файла

Если посмотреть в статью по FCIV, то увидим, что он использует свою нестандартную XML схему:

<?xml version="1.0" encoding="utf-8" ?>
<FCIV>
    <FILE_ENTRY>
        <name>wsr.txt</name>
        <SHA1>Bwt693756BjBJsJ87coat7JArS0=</SHA1>
    </FILE_ENTRY>
    <FILE_ENTRY>
        <name>0</name>
        <SHA1>xd/Rh0JyuW/I6sxPzY4sULyjS38=</SHA1>
    </FILE_ENTRY>
</FCIV>

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

Внимание: хоть FCIV и непривередлив к дополнительным полям в XML, но он начинает сильно ругаться, если поле Name написано с большой буквы. Его нужно заполнять так, как он есть в экземпляре, т.е. все маленькие буквы.

Объект, который будет хранить все данные о файле будет вот такой:

$object = "" | Select Name, Size, TimeStamp, SHA1, MD5

Т.е. мы в XML будем писать имя файла, его размер в байтах, время и дату последней модификации файла и хеши SHA1 и MD5. Если при создании БД мы будем использовать только один хеш, то второй будет просто пустым. Для сборки XML нам потребуется потребуется функция. Эту схему можно разложить на 3 составные части:

  1. заголовок XML и открывающийся тег <FCIV>. Это присуствует только в начале XML файла и всё. Этот тег и заголовок больше нигде в XML не встречается. Поэтому header и открывающийся тег нужно поместить в скриптоблок Begin функции.
  2. А вот теги <FILE_ENTRY> они повторяются для каждой записи. Следовательно этот тег будем заполнять в конструкции Process функции. Т.е. в каждой итерации Process будет создаваться новый тег <FILE_ENTRY>. Но внутри этого тега есть ещё вложенные теги, которые описывают наш файл. Чтобы решить эту задачу мы должны внутри конструкции Process (которая по сути будет являться циклом) написать ещё один цикл, который будет разбирать входящий объект и данные из его свойств записывать во вложенные теги.
  3. В конце XML файла будет закрывающийся тег </FCIV>, его будем помещать в скриптоблок End нашей функции.

Вот такая функция у меня получилась:

function _toxml_ {
# в Begin создаём заголовок XML и открывающийся тег <FCIV>
    Begin {
        $xmlstring += "<?xml version=`"1.0`" ?>`n <FCIV>`n"
    }
# в Process будут по одному поступать объекты, которые описывают файл.
    Process {
# для каждого файла у нас будет один тег <FILE_ENTRY>
        $xmlstring += " <FILE_ENTRY>`n"
# чтобы вручную не создавать и не заполнять вложенные теги мы простым foreach
# перечисляем теги, какие у нас будут в XML и за счёт переменных автоматом
# создаём их в XML и заполняем их данными. Для этого свойства объектов должны
# называться так же, как и теги.
        foreach ($child in ("name", "Size", "TimeStamp", "SHA1", "MD5")) {
            $xmlstring += " <$child>$($_.$child)</$child>`n"
        }
# когда текущий объект обработан, закрываем тег и ждём следующий объект файла
        $xmlstring += " </FILE_ENTRY>`n"
    }
# когда объекты закончились, закрываем первый тег <FCIV> и подаём полученный XML дальше
    End {
        $xmlstring += "</FCIV>`n"
        $xmlstring
    }
}

в ходе испытаний нарвался на одну засаду. В XML существуют некоторые символы как ‘&’, ‘<’, ‘>’ и другие (полный список перечислен тут: http://www.hdfgroup.org/HDF5/XML/xml_escape_chars.htm), которые нужно эксейпить, поскольку являются метасимволами в XML. Эти символы (в частности одинарная кавычка и амперсанд) могут присутствовать в имени файла. Погуляв по сайту MSDN нашёл выход. Выход заключается в использовании метода Escape класса System.SecurityElement. Поэтому после того как массив объектов (которые описывают каждый файл) будет собран его нужно пропустить через фильтр и только после этого собирать XML вышеприведённой функцией. Вот как выглядит фильтр:

filter _escxml_ {
    $_.name = [Security.SecurityElement]::Escape($_.name)
    $_
}

Напоминаю, что мы должны эскейпить только свойство Name нашего объекта.

Преобразование FCIV-совместимого XML в массив объектов

Вот мы уже научились правильно создавать XML файл, который будет полностью совместим со схемой, которая используется в самом FCIV. Теперь у нас возникает задача чтения этого XML и преобразования в массив объектов. Сначала я долго горевал, плакал и бился головой об стену, что нельзя использовать Import-CliXML, но преобразование типов решило задачу ничуть не сложнее:

function _fromxml_ ($xml) {
    $sum = ([xml]$(gc -LiteralPath $xml)).FCIV.FILE_ENTRY | %{$_ | Select Name, Size, TimeStamp, SHA1, MD5}
    $sum
}

сначала читаем файл и преобразовываем его в типа данных XML, опускаем теги FCIV и FILE_ENTRY и в цикле разбираем вложенные теги и собираем объект. Получилось достаточно просто. На сегодня, я думаю, что хватит, продолжение следует.

После продолжительного затишья вызванного различными причинами (как выступление на MCP клубе в Москве), продолжаю тему PowerShell. На этот раз реализованный на PowerShell аналог File Checksum Integrity Verifier (FCIV) с расширенным функционалом по просьбе Артёма Проничкина.

У каждого системного администратора  имеется некоторый файловый ресурс, который содержит такие вещи как инсталляционные ресурсы для своей компании и зачастую эти ресурсы достаточно большие и очень важно отслеживать целостность каждого файла, что он не был случайно модифицирован dbhecjv или как-то иначе повреждён. А так же контролировать появление новых файлов и удаление имеющихся файлов. Так же при обмене файловыми ресурсами можно использовать данный скрипт (который будет опубликован чуть позже) для проверки целостности передачи данных по сети. Например, сформировали комплект файлов для пересылки по сети. Сначала заготавливается исходная БД и это всё передаётся по сети в точку назначения, где применяют этот же скрипт и приложенную БД и проверяют, все ли файлы пришли целыми.

Для решения этой задачи Microsoft в своё время выпустила утилиту FCIV - http://support.microsoft.com/kb/841290. Суть работы этой утилиты заключается в том, что она в указанной папке (и подпапках по желанию) подсчитывает хеш каждого файла и заносит его значение в свою БД. БД представляет собой обычный XML файл, который содержит имя и хеш-сумму для файла. При повторной проверки утилита считывает данные из XML файла, подсчитывает для каждого файла хеш и сравнивает его с тем, который записан в БД. Собственно, это весь функционал утилиты. Чтобы расширить функционал этой утилиты пришлось написать свой скрипт, который решает эту задачу. Причём этот скрипт будет полностью совместим с FCIV, хоть и использует свои механизмы подсчёта файлов и генерации XML файла.

Самое первое – правильно составить логику работы скрипта:

ищем XML
если XML есть
    берём список файлов из XML
    эталонные хеши тоже из XML
    реальные хеши добываем при обсчёте файлов
    сравниваем со всеми вытекающими последствиями
    если файл есть в XML, а в папке нету - плачем
если XML нету
    ничего не сравниваем
    считаем хеши всех файлов в папке
    записываем их в XML

Как оказалось, FCIV оказался непривередливым в отношении полей в XML файле, поэтому я решил добавить 2 своих поля – дата последней модификации и размер файла в байтах. Добавив 2 новых поля я позволил немного сократить общее время работы скрипта: как известно, подсчёт хешей для файлов процесс ресурсоёмкий (по CPU и по времени) и если мы при сравнении обнаружим, что изменились либо дата последней модификации, либо размер файла, то пересчитывать для него хеш совершенно не обязательно, поскольку есть 100% вероятность, что он не совпадёт с тем, что записан в БД. Итак, описание функционала скрипта:

  • возможность подсчёта хешей файлов не только в текущей папке, но и подпапках;
  • возможность проверки только одного файла из БД;
  • возможность проверки файлов только с определёнными расширениями;
  • возможность подсчёта хешей: MD5 или SHA1. А так же есть возможность подсчёта обоих типов хешей с записью всех результатов в файл БД;
  • возможность контроля даты и времени последней модификации файла с точностью до секунды и размера файла с точностью до байта;
  • 3 режима вывода служебной информации: без вывода, с минимальным выводом и с полным выводом служебной информации;
  • итоговая статистика, которая будет показывать общее количество проверенных файлов и количество файлов с определённым статусом (хороший/изменённый/отсутствующий/вновь добавленный);
  • полная обратная совместимость с FCIV;
  • возможность указания действия для изменённых файлов на выбор (переименование или удаление);
  • поскольку БД статическая и при сверке новые файлы в каталогах не проверяются и устаревшие записи не удаляются, поэтому был добавлен отдельный режим ReSync, который будет очищать записи для несуществующих более файлов и заносить в БД новые файлы, которые появляются в проверяемых папках.

Тело скрипта разбито на несколько функциональных частей:

  • функция подсчёта хешей;
  • генерация FCIV-совместимого XML файла;
  • преобразование FCIV-совместимого XML файла в массив объкектов;
  • организация итоговой статистики работы;
  • обеспечение основной логики работы.

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