Posts on this page:
Слава сиськамПротоколу Сноверу!
По мотивам предыдущего поста и срача в ньюсгруппе microsoft.public.windows.powershell перепубликовываю скрипт Роберта Робело для определения кодировки, в которой сохранён файл.
filter Get-TextEncoding { #requires -Version 2.0 begin { [string]$BOM_Unicode = [Text.Encoding]::Unicode.GetPreamble() [string]$BOM_UTF7 = [Text.Encoding]::UTF7.GetPreamble() [string]$BOM_UTF8 = [Text.Encoding]::UTF8.GetPreamble() [string]$BOM_BigEndian = [Text.Encoding]::BigEndianUnicode.GetPreamble() [string]$BOM_UTF32 = [Text.Encoding]::UTF32.GetPreamble() } process { if ($_ -is 'IO.FileInfo') { $bytes = Get-Content -Literal $_.pspath -Encoding Byte -Total 4 -ErrorAction SilentlyContinue $value = if ($bytes) { if ($bytes[0..1] -as 'String' -eq $BOM_Unicode) {'Unicode'} elseif ($bytes[0..2] -as 'String' -eq $BOM_UTF7) {'UTF7'} elseif ($bytes[0..2] -as 'String' -eq $BOM_UTF8) {'UTF8'} elseif ($bytes[0..1] -as 'String' -eq $BOM_BigEndian) {'BigEndian'} elseif ($bytes[0..3] -as 'String' -eq $BOM_UTF32) {'UTF32'} # undetermined, no BOM else {'Unknown'} # undetermined, zero bytes } else {'Unknown'} $_ | Add-Member NoteProperty Encoding $value -PassThru # not an IO.FileInfo } else {$_} } }
ну и использование достаточно простое:
dir *.ps1 | Get-TextEncoding | Format-Table Name, Encoding
Уже тут видно мелкий косячок подсветки в PowerGUI. Name должно быть такого же цвета, что и Encoding. А выглядеть это будет примерно так:
[↓] [vPodans] dir desktop\*.ps1 | Get-TextEncoding | ft name, encoding Name Encoding ---- -------- Untitled1.ps1 BigEndian untitled2.ps1 UTF8 [↓] [vPodans]
Не забудьте, что это будет работать только в V2. Для PowerShell 1.0 придётся чуточку подпилить его.
После небольшого перерыва продолжаю допиливать свой вариант FCIV на PowerShell. И радостно могу сообщить, что уже есть версия 1.0, т.е. полностью отвечающая нашим требованиям. Что изменилось в новой версии?
А теперь и on-line справка по всем параметрам по просьбе трудящихся.
Несколько примеров использования:
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 хеша. Без указания любого из этих параметров будет сгенерирована ошибка и работа будет остановлена.
Опциональные параметры:
На данном этапе реализована работа в соответствии с логикой (которая описана тут: PS FCIV (часть 1)) и односторонняя совместимость с FCIV. Т.е. этой утилите можно скармливать XML файл, который сгенерирован с помощью этого скрипта. В скрипте начата реализация поддержки альтернативных БД, которые не совместимы с FCIV XML. Плюс планируется обеспечение двусторонней поддержки FCIV – т.е. скрипт сможет корректно воспринимать и обрабатывать XML файлы, которые сгенерированы утилитой FCIV. Ну и оптимизация самого кода. Сам скрипт снабжён достаточно плотными комментариями, поэтому будет совсем нетрудно с ним разобраться. По мере дописывания кода буду выкладывать более актуальные версии файла.
Update 27.07.2009
Продолжаем повествование о процессе разработки функционального аналога FCIV.
Как я уже упоминал, самая первая и основная функциональная часть – функция подсчёта хешей. Я уже делал скрипт, который считает хеши: Полезная безделушка 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 схему:
<?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 составные части:
Вот такая функция у меня получилась:
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 нашего объекта.
Вот мы уже научились правильно создавать 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 и в цикле разбираем вложенные теги и собираем объект. Получилось достаточно просто. На сегодня, я думаю, что хватит, продолжение следует.