Posts on this page:
Продолжаем повествование о процессе разработки функционального аналога 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 и в цикле разбираем вложенные теги и собираем объект. Получилось достаточно просто. На сегодня, я думаю, что хватит, продолжение следует.
После продолжительного затишья вызванного различными причинами (как выступление на 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% вероятность, что он не совпадёт с тем, что записан в БД. Итак, описание функционала скрипта:
Тело скрипта разбито на несколько функциональных частей:
Это была вводная часть в достаточно длительную эпопею с реализацией заданного функционала. В следующих частях в деталях будет рассмотрена каждая функциональная часть скрипта и в конце повествования будет опубликован сам скрипт, поэтому не отключайтесь.
Бег на 110 метров с барьерами.
Задача:
Решение:
Всё копировать не буду, а только приведу уже готовый скрипт. Сам текст скрипта можно взять тут: 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 ярлыка и первый будет вести именно на файл самого скрипта.
и снова задача на парсинг текста. Потребуется файл network trace_adv6.txt из Competitors pack.
Задача:
Решение:
Самая первая задача, которая перед нами встаёт – создание регулярного выражения. Выражение у меня получилось вот такое:
^\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, но мне нравится этот ключ :-) вобщем, об этой фиче не забывайте.
задача на работу с реестром.
Задача:
Решение:
Ключи и значения реестра для этой задачи можно найти тут: 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]
как бы ничего сложного.
работа с тэгами файлов. Потребуются файлы TechEd1.JPG, TechEd2.JPG и TechEd3.JPG из Competitors Pack.
Задача:
Решение:
Задача не такая и сложная, на самом деле, как кажется. Для начала нам потребуется класс 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]
Вобщем, как видите, тут тоже ничего сверхсложного нету.
Прыжки с шестом.
Потребуется файл HighJumperDatabase.mdb из Competitors Pack.
Задача:
Решение:
Никогда не приходилось работать с 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.
потребуется файл High Jump Stats_Adv4.txt из Competitors Pack.
В файле перечислены имена прыгунов с шестом и через запятую перечислены высОты планки.
Задача:
Решение:
Т.к. я графики тоже никогда не рисовал, поэтому тоже пришлось обратиться к гуглу. И вышло примерно такое:
# переименовываем 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"