Contents of this directory is archived and no longer updated.

Posts on this page:

Мне вчера на почту пришёл комментарий на пост: Смена владельца папки или файла в PowerShell (часть 2), в котором сообщалось об ошибке, которую генерирует скрипт, если в пути есть служебные мета-символы, как апостроф (одиночкая кавычка). А дело было в том, что этот мета-символ нужно дополнительно эскейпить (помимо обратных слешей в пути). То же касается и квадратных скобок. Одиночные кавычки эскейпятся очень просто – одним обратным слешем:


Read more →

В своём предыдущем блоге я писал заметку про то, как можно случайно удалить классы WMI – PowerShell - убийца WMI классов? И недавно узнал, как можно восстановить эту функциональность обратно. Сами Win32 классы находятся в библиотеке CIMWIN32.MOF, которая и повреждается при удалении классов. Чтобы вернуть эти классы – достаточно перекомпилировать эту библиотеку:

C:\Windows\System32\wbem\MOFComp CIMWIN32.MOF

Read more →

И я снова вернулся. После последнего анализа кода была обнаружена серьёзная ошибка в коде, из-за которой пришлось его очень сильно переписывать. Поэтому я предлагаю оценить 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 файла в массив объкектов;
  • организация итоговой статистики работы;
  • обеспечение основной логики работы.

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