Contents of this directory is archived and no longer updated.

Posts on this page:

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

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

Scripting Games 2009 Бег на 110 метров с барьерами.

Beginner Division

Задача:

  • В исходном файле скрипта найти все ошибки и исправить их. При запуске скрипта на рабочем столе должны появиться 3 ярылка: на сам скрипт, на веб-сайт Hey, Scripting Guys! и ярлык на блокнот (notepad.exe).

Решение:

Всё копировать не буду, а только приведу уже готовый скрипт. Сам текст скрипта можно взять тут: Hey, Scripting Guy! Opening Ceremonies and 2009 Scripting Games Event 6 Details (Beginner and Advanced; 110-meter hurdles). Исходные ошибочные строки привёл в комментарии:

#==========================================================================
#
# PowerShell:  AUTHOR: Ed Wilson , msft,  6/15/2009
#
# NAME: Beg_6.ps1
#
# COMMENT: Key concepts are listed below:
#1. Uses wscript.shell to create three shortcuts on the desktop. The first is a shortcut
#2. to this actual script. It uses the scriptfullName property to assign the path.
#3. The second is a simple Web site URL shortcut. The third one is a shortcut to
#4. Notepad.
#==========================================================================
$ErrorActionPreference = "SilentlyContinue"
Set-PSDebug -Strict
New-Variable -Name objShell #instance of the wshSHell object
New-Variable -Name strDesktop #pointer to desktop special folder
New-Variable -Name objShortCut #used to set properties of the shortcut. Comes from using createShortCut
New-Variable -Name objURL #used to set properties of webshortcut.
# было: $oShell = New-Object -ComObject ("WScript.Shell") - ошибка в имени переменной
$objShell = New-Object -ComObject ("WScript.Shell")
$strDesktop = $objShell.SpecialFolders.item("Desktop")

$objShortCut = $objShell.CreateShortcut($strDesktop + "\Shortcut Script.lnk")
# было: $objShortCut.TargetPath = $MyInvocation.ScriptName - неверное свойство
$objShortCut.TargetPath = $MyInvocation.MyCommand
$objShortCut.WindowStyle = 0
$objShortCut.Hotkey = "CTRL+SHIFT+F"
$objShortCut.IconLocation = "notepad.exe, 2"
$objShortCut.Description = "Shortcut Script"
$objShortCut.WorkingDirectory = $strDesktop
# было: $objShortCut.Save - при вызове метода забыли скобки
$objShortCut.Save()

# $wshShell = New-Object -ComObject wscript.shell - не нужно совсем
# было: $objURL = $objShell.CreateShortcut($strDesktop & "\The Microsoft Scripting Guys.url")
# вместо + поставили & из VBS
$objURL = $objShell.CreateShortcut($strDesktop + "\The Microsoft Scripting Guys.url")
$objURL.TargetPath = "http://www.ScriptingGuys.com"
# было: $objURL.Discription = "Scripting Guys". Такого свойства в URL файлах нету.
$objURL.Save()

# было: $wshNetwork = New-Object -ComObject wscript.network - не нужно свсем
# было: $objShortCut = $objShell.CreateShortcut($strDesktop + "\notepad.link")
# расширение не LNK, а LINK
$objShortCut = $objShell.CreateShortcut($strDesktop + "\notepad.lnk")
# было: $objShortCut.TargetPath = "notpad.exe" - пропустили букву E
$objShortCut.TargetPath = "notepad.exe"
$objShortCut.IconLocation = "notepad.exe, 0"
$objShortCut.description = "notepad"
$objShortCut.Save()

Если запустить этот скрипт из файла, то получится 3 ярлыка и первый будет вести именно на файл самого скрипта.

Advanced Division

и снова задача на парсинг текста. Потребуется файл network trace_adv6.txt из Competitors pack.

Задача:

  • распарсить вывод tracert и показать участки, на которых наблюдается замедление скорости (т.е. время прыжка увеличивается)

Решение:

Самая первая задача, которая перед нами встаёт – создание регулярного выражения. Выражение у меня получилось вот такое:

^\d+\W+(\d+)\D+(\d+)\D+(\d+) ms  (.*)

Что оно делает: оно откидывает порядковый номер прыжка (hop), любую небуквенную последовательность (в общем смысле [A-Za-z0-9]) и выбирает первую последовательность чисел. Эта последовательность будет будет являться временем первой попытки посылки ICMP пакета. Далее откидывается любая нечисловая последовательность и выбирается первая же числовая последовательность, которая будет являться второй попыткой посылки ICMP пакета. То же самое делаем до 3-й попытки текущего прыжка включительно. После выкидываем пробел и последовательность букв “ms”, за ней 2 пробела и захватываем всё до конца строки, что будет являеться адресом и/или именем маршрутизатора. Т.к. перед порядковыми номерами прыжков (до 9 включительно) присуствует пробел, то его можно средать методом Trim() и уже подставлять выражение. Из захваченного времени нужно на лету посчитать среднее значение. И вот что у меня вышло:

[↓] [vPodans] gc "Network Trace_Adv6.txt" | ?{$_.trim() -match "^\d+\W+(\d+)\D+(\d+)\D+(\d+) ms  (.*)"} | %{
>>     $current = "" | Select @{n='Average';e={[int](([int]$matches[1]+[int]$matches[2]+[int]$matches[3])/3)}},
>>     @{n='Name';e={$matches[4]}}
>>     $current
>> } | ft -a
>>

Average Name
------- ----
      1 r10.ntwk.nwtraders.com [192.168.227.82]
      1 r20.ntwk.nwtraders.com [192.168.169.1]
      1 r50.ntwk.nwtraders.com [192.226.42.47]
     13 r70.ntwk.nwtraders.com [192.226.42.12]
     34 r12.ntwk.nwtraders.com [192.226.42.9]
     34 r40.ntwk.nwtraders.com [192.226.42.22]
    104 r22.ntwk.nwtraders.com [192.226.34.80]
    105 r32.ntwk.nwtraders.com [192.226.47.10]
    125 r35.ntwk.nwtraders.com [192.226.41.198]
    173 r37.ntwk.nwtraders.com [192.226.38.14]
    173 r38.ntwk.nwtraders.com [192.226.226.119]
    389 192.168.216.5
    390 r60.ntwk.nwtraders.com [192.168.236.9]


[↓] [vPodans]

Теперь нужно определить на каких участках происходит замедление. Я решил поэтапно циклом For проверять каждый элемент массива – меньше ли он, чем следующий элемент или нет. Если он равен или больше следующего элемента, то ничего делать не надо, т.к. замедления нету. Если меньше, то он является пограничным этапом, где происходит замедление и выводим его на экран. Привожу уже готовый код и полученный результат:

$sum = @()
gc "Network Trace_Adv6.txt" | ?{$_.trim() -match "^\d+\W+(\d+)\D+(\d+)\D+(\d+) ms  (.*)"} | %{
    $current = "" | Select @{n='Average';e={[int](([int]$matches[1]+[int]$matches[2]+[int]$matches[3])/3)}},
    @{n='Name';e={$matches[4]}}
    $sum += $current
}
$(for ($i = 0; $i -le $sum.Count; $i++ ) {
    if ($sum[$i].Average -lt $sum[$i + 1].Average) {$sum[$i]}
    if ($i -eq ($sum.Count - 1)) {$sum[$i]}
}) | ft @{l='Average';e={$_.Average};a='left'}, @{l='Name';e={$_.name}} -AutoSize
[↓] [vPodans] $sum = @()
[↓] [vPodans] gc "Network Trace_Adv6.txt" | ?{$_.trim() -match "^\d+\W+(\d+)\D+(\d+)\D+(\d+) ms  (.*)"} | %{
>>     $current = "" | Select @{n='Average';e={[int](([int]$matches[1]+[int]$matches[2]+[int]$matches[3])/3)}},
>>     @{n='Name';e={$matches[4]}}
>>     $sum += $current
>> }
>> $(for ($i = 0; $i -le $sum.Count; $i++ ) {
>>     if ($sum[$i].Average -lt $sum[$i + 1].Average) {$sum[$i]}
>>     if ($i -eq ($sum.Count - 1)) {$sum[$i]}
>> }) | ft @{l='Average';e={$_.Average};a='left'}, @{l='Name';e={$_.name}} -AutoSize
>>

Average Name
------- ----
1       r50.ntwk.nwtraders.com [192.226.42.47]
13      r70.ntwk.nwtraders.com [192.226.42.12]
34      r40.ntwk.nwtraders.com [192.226.42.22]
104     r22.ntwk.nwtraders.com [192.226.34.80]
105     r32.ntwk.nwtraders.com [192.226.47.10]
125     r35.ntwk.nwtraders.com [192.226.41.198]
173     r38.ntwk.nwtraders.com [192.226.226.119]
389     192.168.216.5
390     r60.ntwk.nwtraders.com [192.168.236.9]


[↓] [vPodans]

А теперь обратите внимание на одну вещь, о которой не все знают:

  ft @{l='Average';e={$_.Average};a='left'}

при форматировании Format-Table вы можете атрибутум A (от слова align) двигать содержимое колонки относительно названия. В первом примере у меня стандартное форматирование и выравнивание Avarage по правому краю. Здесь же я содержимое выравнил по левому краю, чтобы не сливались цифры. Хотя это решается удалением –AutoSize, но мне нравится этот ключ :-) вобщем, об этой фиче не забывайте.

Beginner Division

Summer Scripting Games 2009 задача на работу с реестром.

Задача:

  • Найти текущее значение максимального количества закачек в Internet Explorer
  • Увеличить это число на другое значение
  • при чтении этого значения предусмотреть проверку существования этого параметра

Решение:

Ключи и значения реестра для этой задачи можно найти тут: http://support.microsoft.com/kb/282402. В принципе, очень просто тут:

$path = 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Internet Settings'
if (!(Test-Path $path)) {New-Item -ItemType Registry -Path $path -Force}
if (!(Get-ItemProperty $path).MaxConnectionsPer1_0Server) {
    Write-Warning "MaxConnectionsPer1_0Server property doesn't exist"
    [void](New-ItemProperty -Path $path -Name 'MaxConnectionsPer1_0Server' -Value 10 -PropertyType 'DWord')
} else {"MaxConnectionsPer1_0Server is: " + (Get-ItemProperty $path).MaxConnectionsPer1_0Server}
if (!(Get-ItemProperty $path).MaxConnectionsPerServer) {
    Write-Warning "MaxConnectionsPerServer property doesn't exist"
    [void](New-ItemProperty -Path $path -Name 'MaxConnectionsPerServer' -Value 10 -PropertyType 'DWord')
} else {"MaxConnectionsPerServer is: " + (Get-ItemProperty $path).MaxConnectionsPerServer}

и вот вывод:

[↓] [vPodans] $path = 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Internet Settings'
[↓] [vPodans] if (!(Test-Path $path)) {New-Item -ItemType Registry -Path $path -Force}
[↓] [vPodans] if (!(Get-ItemProperty $path).MaxConnectionsPer1_0Server) {
>>     Write-Warning "MaxConnectionsPer1_0Server property doesn't exist"
>>     [void](New-ItemProperty -Path $path -Name 'MaxConnectionsPer1_0Server' -Value 10 -PropertyType 'DWord')
>> } else {"MaxConnectionsPer1_0Server is: " + (Get-ItemProperty $path).MaxConnectionsPer1_0Server}
>> if (!(Get-ItemProperty $path).MaxConnectionsPerServer) {
>>     Write-Warning "MaxConnectionsPerServer property doesn't exist"
>>     [void](New-ItemProperty -Path $path -Name 'MaxConnectionsPerServer' -Value 10 -PropertyType 'DWord')
>> } else {"MaxConnectionsPerServer is: " + (Get-ItemProperty $path).MaxConnectionsPerServer}
>>
WARNING: MaxConnectionsPer1_0Server property doesn't exist
WARNING: MaxConnectionsPerServer property doesn't exist
[↓] [vPodans] $path = 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Internet Settings'
[↓] [vPodans] if (!(Test-Path $path)) {New-Item -ItemType Registry -Path $path -Force}
[↓] [vPodans] if (!(Get-ItemProperty $path).MaxConnectionsPer1_0Server) {
>>     Write-Warning "MaxConnectionsPer1_0Server property doesn't exist"
>>     [void](New-ItemProperty -Path $path -Name 'MaxConnectionsPer1_0Server' -Value 10 -PropertyType 'DWord')
>> } else {"MaxConnectionsPer1_0Server is: " + (Get-ItemProperty $path).MaxConnectionsPer1_0Server}
>> if (!(Get-ItemProperty $path).MaxConnectionsPerServer) {
>>     Write-Warning "MaxConnectionsPerServer property doesn't exist"
>>     [void](New-ItemProperty -Path $path -Name 'MaxConnectionsPerServer' -Value 10 -PropertyType 'DWord')
>> } else {"MaxConnectionsPerServer is: " + (Get-ItemProperty $path).MaxConnectionsPerServer}
>>
MaxConnectionsPer1_0Server is: 10
MaxConnectionsPerServer is: 10
[↓] [vPodans]

как бы ничего сложного.

Advanced Division

работа с тэгами файлов. Потребуются файлы TechEd1.JPG, TechEd2.JPG и TechEd3.JPG из Competitors Pack.

Задача:

  • прочитать следующие EXIF свойства файлов:
  • Имя
  • когда снимок был сделан
  • свойство Make
  • и модель камеры, которой был сделан снимок

Решение:

Задача не такая и сложная, на самом деле, как кажется. Для начала нам потребуется класс System.Drawing.Bitmap и его метод GetPropertyItem. Для работы с этим классом нужно подключить библиотеку System.Drawing.dll:

[void][reflection.assembly]::loadfile("C:\Windows\Microsoft.NET\Framework\v2.0.50727\System.Drawing.dll")

и создать объект System.Drawing.Bitmap:

$drawing = New-Object system.drawing.bitmap -ArgumentList .\TechEd1.jpg

Этот объект будет содержать всё нам необходимое. Чтобы посмотреть нужные нам свойства, нужно получить где-то список всех ID, которым соответствуют эти свойства. К сожалению я не нашёл такого списка на MSDN, а только отдельными частями в интернете:
http://www.exif.org/specifications.html

там в конце PDF файла есть Appendix с таблицой ID на эту тему. Если посмотреть в таблицу, то увидим, что Model находится под ID = 272. Глянем, что там есть:

[↓] [vPodans] $drawing = New-Object system.drawing.bitmap -ArgumentList .\TechEd1.jpg
[↓] [vPodans] $drawing.GetPropertyItem(271).value
67
97
110
111
110
0
[↓] [vPodans]

Как-то не очень радует такой вывод. Но если внимательно посмотреть на эти цифры, то можно заметить, что они не превышают число 256, что подсказывает, что это массив ASCII байтов. Эти ASCII байты можно сконвертировать в элемент вот так: [char]ASCII_Number. А т.к. это массив, то его нужно собрать либо методом ToString() или оператором –Join, который есть в PowerShell V2:

[↓] [vPodans] $drawing = New-Object system.drawing.bitmap -ArgumentList .\TechEd1.jpg
[↓] [vPodans] $drawing.GetPropertyItem(271).value
67
97
110
111
110
0
[↓] [vPodans] -join ($drawing.GetPropertyItem(271).value | %{[char]$_})
Canon
[↓] [vPodans]

Вот так оно всяко лучше стало. По такой же схеме выбираем и собираем все остальные свойства:

[void][reflection.assembly]::loadfile("C:\Windows\Microsoft.NET\Framework\v2.0.50727\System.Drawing.dll")
dir *.jpg | %{
    $drawing = New-Object system.drawing.bitmap -ArgumentList $_
    $photo = "" | Select Name, Author, Make, Model, DateTaken
    $photo.Name = $_.Name
    $photo.Author = -join ($drawing.GetPropertyItem(315).value | %{[char]$_})
    $photo.Make = -join ($drawing.GetPropertyItem(271).value | %{[char]$_})
    $photo.Model = -join ($drawing.GetPropertyItem(272).value | %{[char]$_})
    $photo.DateTaken = -join ($drawing.GetPropertyItem(36867).value | %{[char]$_})
    $photo
} | ft -AutoSize
[↓] [vPodans] [void][reflection.assembly]::loadfile("C:\Windows\Microsoft.NET\Framework\v2.0.50727\System.Drawing.dll")
[↓] [vPodans] dir *.jpg | %{
>>     $drawing = New-Object system.drawing.bitmap -ArgumentList $_
>>     $photo = "" | Select Name, Author, Make, Model, DateTaken
>>     $photo.Name = $_.Name
>>     $photo.Author = -join ($drawing.GetPropertyItem(315).value | %{[char]$_})
>>     $photo.Make = -join ($drawing.GetPropertyItem(271).value | %{[char]$_})
>>     $photo.Model = -join ($drawing.GetPropertyItem(272).value | %{[char]$_})
>>     $photo.DateTaken = -join ($drawing.GetPropertyItem(36867).value | %{[char]$_})
>>     $photo
>> } | ft -AutoSize
>>

Name        Author     Make   Model               DateTaken
----        ------     ----   -----               ---------
TechEd1.JPG Ed Wilson  Canon  Canon PowerShot G9  2009:05:12 10:41:03
TechEd2.JPG Ed Wilson  Canon  Canon PowerShot G7  2009:05:12 10:25:59
TechEd3.JPG Ed Wilson  Sony   A-9                 2009:05:12 10:26:48


[↓] [vPodans]

Вобщем, как видите, тут тоже ничего сверхсложного нету.

Summer Scripting Games 2009 Прыжки с шестом.

Beginner Division

Потребуется файл HighJumperDatabase.mdb из Competitors Pack.

Задача:

  • опросить MDB файл и вывести имя лучшего прыгуна c шестом (хотя там цифры немного странные для этой дисциплины).

Решение:

Никогда не приходилось работать с Access, поэтому пришлось честно гуглить: http://www.visualbasicscript.com/m_43134/tm.htm. Его я взял за основу, только немного подрихтовал и вот что получилось:

# создаём массив, который будет содержать все результаты
$sum = @()
# подключения к БД. Наглый копипаст с форума.# более-менее внятное описание этого кода тут:
# http://technet.microsoft.com/en-us/magazine/2009.05.scriptingguys.aspx
$adOpenStatic = 3
$adLockOptimistic = 3
$MDBConn = New-Object -comobject ADODB.Connection
$MDBConn.Open("provider=microsoft.jet.oledb.4.0;data source=$pwd\HighJumperDatabase.mdb")
$Record = New-Object -comobject ADODB.RecordSet
$Record.Open('select * from [High Jumper Data]',$MDBConn,$adOpenStatic,$adLockOptimistic)
$Record.MoveFirst()
do {
    # делаем объект текущего атлета и забиваем в него нужные значения
    $current = "" | Select @{n='Name';e={$Record.Fields.Item("Name").Value}},
    @{n='Personal Best';e={$Record.Fields.Item("Personal Best").Value}},
    @{n='Season Best';e={$Record.Fields.Item("Season Best").Value}}
    # добавляем текущего участника в массив
    $sum += $current
    # переходим на следующую итерацию
    $Record.MoveNext()
} until ($Record.EOF)
# закрываем все подключения
$Record.Close() 
$MDBConn.Close()
# сортируем и выводим лучшего спортсмена
$sum | Sort "Season Best", "Personal Best" | select Name -Last 1

Кстати, отличное решение этой задачи нашёл на VBS. Человек уложился в 4(!) строчки:

Set adoCon = CreateObject("ADODB.Connection")
adoCon.Open "DRIVER=Microsoft Access Driver (*.mdb);DBQ=HighJumperDatabase.mdb"
Winner = adoCon.Execute("SELECT TOP 1 Name FROM [High Jumper Data] ORDER BY [Personal Best] DESC")("Name")
WScript.Echo "Expected Winner: " & Winner

Есть мнение, что фанаты PowerShell должны дружно застрелиться :-) хотя это просто вопрос запроса к базе Access.

Advanced Division

потребуется файл High Jump Stats_Adv4.txt из Competitors Pack.

В файле перечислены имена прыгунов с шестом и через запятую перечислены высОты планки.

Задача:

  • на основании этих данных построить произвольный (желательно красивы) график в Excel.

Решение:

Т.к. я графики тоже никогда не рисовал, поэтому тоже пришлось обратиться к гуглу. И вышло примерно такое:

# переименовываем txt файл в CSV. Теперь не нужно конвертировать тектовый файл в
# формат Excel, т.к. при переименовании Excel сам это сделает
ren "High Jump Stats_Adv4.txt" "High Jump Stats_Adv4.csv"
# выполняем обычную рутину для создания COM объекта для Excel
$excel = New-Object -ComObject excel.application
$file = $excel.Workbooks.Open("$pwd\High Jump Stats_Adv4.csv")# | Out-Null
$excel.Visible = $false
$excel.DisplayAlerts = $false
$sheet = $excel.Worksheets.Item(1)
# добавляем диаграмму
#$chart = $excel.Charts.Add()
# задаём диапазон для диаграммы
$Range = $sheet.Range("A1:l1")
# добавляем элемент Chart с нужным диапазоном. Я выбрал 10 первых попыток
# для первого атлета. 
$chart = $excel.Charts.Add()
$chart.SetSourceData($Range)
$chart.ChartStyle = 27
# сохраняем Chart в файл формата Excel и закрываем работу
$file.SaveAs("$pwd\High Jump Stats_Adv4.xlsx")
$excel.Quit()
gps excel | stop-process
# переименовываем CSV файл обратно в TXT
ren "High Jump Stats_Adv4.csv" "High Jump Stats_Adv4.txt"