Contents of this directory is archived and no longer updated.

Posts on this page:

Summer Scripting Games 2009 И оно свершилось! Хотя официально Scripting Games начинаются только 15-го июня, но уже со вчерашнего дня стали известны уже 2 задания. Для начала немного информации, что и как:

В принципе, вы можете решать задания как угодно, т.к. засчитываются все, которые отвечают условиям задания.

Итак, Event1 и оба дивизиона:

Beginner Division

Из Competitors Pack нам потребуется файл 100 Meter Event.txt

Задача:

  • Выяснить, как читать файл?
  • Как прочитать из каждой строки поля Name, Country, Time?
  • Как отсортировать содержимое файла по полю Name?
  • Выбрать 3-х победителей из списка. Победителем считается тот, у кого самое маленькое время в колонке Time
  • Показать поля Name, Country и Time для первых 3-х победителей.

Решение:

Побегав курсором по файлу я обнаружил тот факт, что разделителем между столбцом Name и Country является табулятор ([Tab]), а между Country и Time стоит один пробел во второй строке, а в остальных строках стоит [Tab]. Следовательно, варианты использования командлетов с параметром –Delimeter не прокатят и нужно будет разбирать всё регулярными выражениями. Первый вопрос наталкивает на необходимость чтения файла не через Get-Content, а через [system.io.file]::ReadAllText(). Но я не вижу особого криминала в использовании родного для PowerShell командлета Get-Content. Это будет несложно. Основная проблема в том, как составить регулярное выражение. Я решил разобрать имя на First Name и LastName (т.к. правильней, всё же, будет, когда сначала идёт имя, а потом фамилия), далее выбрать Country от табуляции до первого числа. Причём, тут потребуется каждую часть сделать именованной (named capture). Если открыть PowerShell In Action на 111 странице, то там можно найти описание, как делать named captures. У меня регулярное выражение получилось вот такое:

^(?<ln>\w+)..(?<fn>.*)\t(?<country>.+)(?<time>\d\.\d+)

Что делает это выражение:

  • в секцию LN (LastName) попадают все буквы до первого небуквенного знака.
  • далее пропускаем 2 символа (запятую и пробел).
  • в секцию FN (FirstName) попадает всё до первой табуляции.
  • после табуляции идёт Country и в него запишем всё, вплоть до первого числового знака.
  • И числа записываем в секцию Time.

Давайте проверим его:

[↓] [vPodans] $file = gc '.\100 Meter Event.txt'
[↓] [vPodans] $file[1] -match "^(?<ln>\w+)..(?<fn>.*)\t(?<country>.+)(?<time>\d\.\d+)"
True
[↓] [vPodans] $matches

Name                           Value
----                           -----
country                        Australia
ln                             Aaberg
fn                             Jesper
time                           8.57
0                              Aaberg, Jesper    Australia 8.57


[↓] [vPodans] $file[11] -match "^(?<ln>\w+)..(?<fn>.*)\t(?<country>.+)(?<time>\d\.\d+)"
True
[↓] [vPodans] $matches

Name                           Value
----                           -----
country                        Japan
ln                             Hansen
fn                             Anne Grethe
time                           8.85
0                              Hansen, Anne Grethe    Japan    8.85


[↓] [vPodans]

и теперь соберём объект с нужными свойствами и отправим его на выход. На выходе отсортируем объекты по параметру Time и выберем первые 3 объекта:

gc "100 Meter Event.txt" | ?{$_ -match "^(?<ln>\w+)..(?<fn>.*)\t(?<country>.+)(?<time>\d\.\d+)"} |%{
    $obj = "" | Select @{n='Name';e={$matches.fn + " " + $matches.ln}},
    @{n='Country';e={$matches.country}},@{n='Time';e={$matches.time}}
    $obj
} | sort time | select -First 3 | ft -AutoSize

и вот вывод:



решил ещё отформатировать в Format-Table с ключом –AutoSize для красоты. Признаюсь, что это задание меня сильно озадачило. Для меня оно оказалось очень непростым (такие дела).

Advanced Division

Для этой задачи нам потребуется файл Personal Information Cards_ADV1.txt из Competitors Pack.

Задача:

  • найти самую короткую строку в файле, которая содержит какой-либо текст.
  • вывести на экран 3 самые короткие строчки

Решение:

В файле много пустых строк, но это не проблема. Итак, one-liner решение:

gc "Personal Information Cards_ADV1.txt" | ?{$_.trim().length -ne 0} | sort length | select -First 3
[↓] [vPodans] gc "Personal Information Cards_ADV1.txt" | ?{$_.trim().length -ne 0} | select –First 3
PPID
Claims
Street
[↓] [vPodans]

Странно, что в Advanced Division получилось такое простое задание.

Работая с функциями в PowerShell можно столкнуться с одной особенностью – в качестве передачи аргументов в функции или скрипты вы можете использовать почти всё, кроме ключей (данных типа Switch). Их передавать можно, но тут есть одна особенность. Обычно это ощущается, когда вы работаете с командлетами. Возьмём простой пример:

function Test ([string[]]$Path, [String]$Filter, [switch]$Force) {
Write-Host '$Path is:' $Path
Write-Host '$Filter is:' $Filter
Write-Host '$Force is:' $Force
}

и вызовем эту функцию:

[↓] [vPodans] function Test ([string[]]$Path, [String]$Filter, [switch]$Force) {
>> Write-Host '$Path is:' $Path
>> Write-Host '$Filter is:' $Filter
>> Write-Host '$Force is:' $Force
>> }
>>
[↓] [vPodans] Test C:\ * -force
$Path is: C:\
$Filter is: *
$Force is: True
[↓] [vPodans]

В принципе, всё как и ожидалось. Но если внимательно посмотреть на последний аргумент, то мы увидим лишь True, т.е. увидим факт, что ключ Force был передан. Однако, PowerShell не умеет подставять (биндить) переменную $Force (равно как и другие переменные) как именованный параметр в другую команду. Чтобы в этом убедиться, мы попробуем сымитировать нашу функцию как командлет Get-ChildItem:

[↓] [vPodans] function Test ([string[]]$Path, [String]$Filter, [switch]$Force) {
>> Get-ChildItem $Path $Filter $Force
>> }
>>
[↓] [vPodans] Test C:\ * -Force
Get-ChildItem : A positional parameter cannot be found that accepts argument 'True'.
At line:2 char:14
+ Get-ChildItem <<<<  $Path $Filter $Force
    + CategoryInfo          : InvalidArgument: (:) [Get-ChildItem], ParameterBindingException
    + FullyQualifiedErrorId : PositionalParameterNotFound,Microsoft.PowerShell.Commands.GetChildItemCommand

[↓] [vPodans]

Как видно, командлет Get-ChildItem не смог сопоставить последний аргумент ни с одним из своих параметров. Т.е. по факту выполнилась следующая строка:

Get-ChildItem -Path C:\ -Filter * True

PowerShell не смог сказать командлету, что мы указали ключ Force и хотим именно его передать в командлет. По факту в этом коде Get-ChildItem не знал, какие параметры ему были переданы и подставлял их на основе номера позиции параметра. А т.к. у Get-ChildItem нету параметра с порядковым номером 3 (с номером 1 идёт Path или LiteralPath, а с номером 2 идёт Filter. Остальные параметры именованные), то мы получили ошибку. Но всё же, как выкручиваться из этой ситуации? Вы можете как угодно пытаться подставить аргументы, но ничего не выйдет. Для этих целей в PowerShell V2 появилась специальная переменная - $PSBoundParameters. Эта переменная по сути представляет собой хэш-таблицу:

[↓] [vPodans] function Test ([string]$Path, [String]$Filter, [switch]$Force) {
>> $PSBoundParameters
>> }
>>
[↓] [vPodans] Test C:\ * -Force

Key                                                         Value
---                                                         -----
Force                                                       True
Path                                                        C:\
Filter                                                      *


[↓] [vPodans]

В отличии от первого примера переменная $PSBoundParameters содержит не только значения переменных, но и их имена (в перовм примере я вручную дописывал имена переменных), которые используются в качестве именованных параметров. Т.е. при подстановке аргументов в команду, она сначала выбирает имя переменной в качестве именованного параметра и значение переменной подставляет в качестве аргумента этого параметра. Синтаксис использования этой переменной очень прост:

function Name ($arg1, $arg2, $arg3 ... $argN) {
Command @PSBoundParameters
}

и в результате будет исполняться вот такая команда:

Command –arg1 <значение $arg1> –arg2 <значение $arg2> –arg3 <значение $arg3> … –argN <значение $argN>

Т.е. будут подставляться именованные параметры и значения переменных соответствующих аргументов. Давайте проверим, как это подействует на наш пример с Get-ChildItem:

function Test ([string]$Path, [String]$Filter, [switch]$Force) {
Get-ChildItem $PSBoundParameters
}

[↓] [vPodans] function Test ([string]$Path, [String]$Filter, [switch]$Force) {
>> Get-ChildItem @PSBoundParameters
>> }
>>
[↓] [vPodans] Test C:\ * -Force


    Directory: C:\


Mode                LastWriteTime     Length Name
----                -------------     ------ ----
d--hs       10.07.2007.     19:44            $Recycle.Bin
d--hs       09.03.2008.     17:53            Boot
d----       23.11.2008.     19:41            inetpub
d-rh-       26.06.2007.     22:27            MSOCache
d----       09.03.2008.     17:41            PerfLogs
<...>

И вы можете видеть, что у нас всё получилось в наилучшем виде! В сравнении с предыдущим примером фактически выполнилась следующая команда:

Get-ChildItem -Path C:\ -Filter * -Force

Для формирования аргументов для командлетов в функциях (так называемые wrapped cmdlets) это самый красивый и идеальный вариант. Если вы захотите изменить логику стандартных командлетов или добавить в них свой функционал, то $PSBoundParamters сделает за вас очень много лишней работы. Но это не единственное полезное применение для этой переменной. Она так же позволяет сократить возможность ошибки при вызове функции внутри скрипта или другой функции, которая принимает те же аргументы. Давайте посмотрим ещё один пример:

function Test ([string[]]$Path, [String]$Filter, [switch]$Force) {
    Write-Host '$Path in Test is:' $Path
    Write-Host '$Filter in Test is:' $Filter
    Write-Host '$Force in Test is:' $Force
    Write-Host --------------------------------
    function Test2 ([switch]$Force, [string[]]$Path, [String]$Filter) {
Write-Host '$Path in Test2 is:' $Path
Write-Host '$Filter in Test2 is:' $Filter Write-Host '$Force in Test2 is:' $Force } Test2 @PSBoundParameters }

Что мы делаем: мы создали функцию Test, которая принимает набор аргументов. Внутри этой функции есть другая функция Test2, которая принимает те же аргументы. Далее из функции Test вызываем функцию Test2 и с помощью $PSBoundParameters передаём в неё аргументы. Обратите внимание, что в функции Test2 я изменил порядок аргументов. Это сделано для того, чтобы показать, что $PSBoundParamters отсортирует наши аргументы. А теперь внимание на экран:

[↓] [vPodans] function Test ([string[]]$Path, [String]$Filter, [switch]$Force) {
>>     Write-Host '$Path in Test is:' $Path
>>     Write-Host '$Filter in Test is:' $Filter
>>     Write-Host '$Force in Test is:' $Force
>>     Write-Host --------------------------------
>>     function Test2 ([switch]$Force, [string[]]$Path, [String]$Filter) {
>>         Write-Host '$Path in Test2 is:' $Path
>>         Write-Host '$Filter in Test2 is:' $Filter
>>         Write-Host '$Force in Test2 is:' $Force
>>     }
>>     Test2 @PSBoundParameters
>> }
>>
[↓] [vPodans] Test C:\ * -Force
$Path in Test is: C:\
$Filter in Test is: *
$Force in Test is: True
--------------------------------
$Path in Test2 is: C:\
$Filter in Test2 is: *
$Force in Test2 is: True
[↓] [vPodans]

И смотрите, что у нас получилось. А у нас получилось, что функция Test2 получила тот же набор аргументов, причём они были подставлены в правильном соответствии. Фактически строка Test2 @PSBoundParameters была преобразована в:

Test2 -Path $Path -Filter $Filter -Force $true

Т.е. в вариант, который мы вынуждены использовать в PowerShell 1.0. Плюс, мы имеем возможность подставлять позиционно ключи (объекты типа Switch), что есть очень позитивно.

Hal Rottenberg и Shay Levy сегодня собрали список блогов всех действующих MVP по PowerShell. Думается, что это будет весьма полезный для многих список, в котором любители PowerShell могут массу наиполезнейшей информации. Итак, вот список:

MVP Name

Blog Name

Blog language

/\/\o\/\/ ThePowerShellGuy

English

Arnaud Petitjean PowerShell Scripting

French

Brandon Shell BS on PoSH

English

Charlie Russel x(perts)64

English

Daisuke Mutaguchi Scripting Weblog

Japanese

Dmitry Sotnikov PowerShell and beyond

English

Don Jones Concentrated Technology

English

Doug Finke Development in a Blink

English

Gu Huajun ghjconan's blog

Chinese

Guy Thomas  

English

Hal Rottenberg TechProsaic

English

Hiroki Takahashi HIRO’s.NET

Japanese

Hiroshi Yoshioka PowerShell Memo

Japanese

Jeffrey Hicks Scripting Blog and More

English

Karl Prosser Live PowerShell

English

Keith Hill Keith Hill’s Blog

English

Kirk Munro Poshoholic

English

Marco Shaw Get-PowershellBlog

English

Max Trinidad Florida PowerShell User Group

English

Oisin Grehan Nivot Ink

English

Richard Siddaway Richard Siddaway’s Weblog

English

Shay Levy $cript Fanatic

English

Sherif Talaat The Arabian PowerShell

Arabic

Tobias Weltner Dreaming in PowerShell

English

Vadims Podans PowerShell Powered

Russian

Vasily Gusev PowerShell и другие скрипты

Russian

Vinicius Canto e a arte de criar scripts

Portuguese

Ying Li PowerShell & System Center

English

Удачи! © One

Иногда замечаю, что некоторые скриптописатели получают “поломанный” конвейер, когда данные из одной команды вышли, но никуда не пришли. Возьмём простой пример:

[↓] [vPodans] foreach ($n in 1..3) {$n}
1
2
3
[↓] [vPodans]

Тут всё просто, циклом foreach перебираются данные и выводятся на экран. Мы привыкли, что всё, что выводится на экран можно передать дальше на конвейер. В принципе, это правильно, за исключением ряда случаев, когда это не работает. Чтобы убедиться в этом – передадим вывод этой команды через конвейер на Set-Content:

[↓] [vPodans] foreach ($n in 1..3) {$n}
1
2
3
[↓] [vPodans] foreach ($n in 1..3) {$n} | Set-Content file.txt
An empty pipe element is not permitted.
At line:1 char:28
+ foreach ($n in 1..3) {$n} | <<<<  Set-Content file.txt
    + CategoryInfo          : ParserError: (:) [], ParentContainsErrorRecordException
    + FullyQualifiedErrorId : EmptyPipeElement

[↓] [vPodans]

и мы получаем ошибку, что у нас пустой конвейер. Если написать простой проверочный фильтр:

filter Test-Input {'current $_ is: ' + $_}

и проверить, что у нас приходит с конвейера в этот фильтр, то вы получите такую же ошибку и можете долго гадать, почему так, на экране данные есть, а в конвейер ничего не поступает. Причиной этому является то, что flow control (не знаю, как это на русский первести) конструкции не транслируют свой вывод в конвейер и вот список этих конструкций:

  • If…Else
  • If…ElseIf
  • For
  • Foreach
  • While
  • Do…While
  • Do…Until
  • Switch

Чтобы при использовании этих конструкций вывод транслировался на конвейер их нужно заключить в подвыражение – $( ):

[↓] [vPodans] $(foreach ($n in 1..3) {$n}) | Set-Content file.txt
[↓] [vPodans] gc file.txt
1
2
3
[↓] [vPodans] $(foreach ($n in 1..3) {$n}) | Test-Input
current $_ is: 1
current $_ is: 2
current $_ is: 3
[↓] [vPodans]

я заключил цикл Foreach в подвыражение и теперь данные стали поступать на конвейер. Это хорошо видно по выводу фильтра Test-Input, который приведён выше. Вот таким нехитрым способом мы решили проблему пустых конвейеров :-)

Недавно на форуме SysFaq.ru была занятная тема про порядок применения групповых политик в домене. В общем смысле там вопрос сводился к приоритету политик, что настроенный параметр в доменной политике будет иметь приоритет над тем же параметром в локальной политике и работает принцип LSDOU (Local, Site, Domain, OU). Но если говорить по SRP, то здесь есть свои нюансы.

Если политика SRP определена в нескольких политиках, то результирующая политика не будет состоять из значений последней применившейся политики. Все значения исключений из всех политик будут сложены в одну большую политику с возможными конфликтами. Важно отметить, что в вычислении результирующей политики порядок их применения не имеет никакого значения. Когда вы смотрите в RSOP, то видите такую вещь:

Source GPO fig.1.

Т.е. видно, какая политика выиграла. В случае с SRP, вы этого не увидите:

No source GPO fig.2.

Когда вы получите набор правил из всех политик, то вполне вероятны конфликтные ситуации, когда в одной политике правило разрешает запуск чего-то, а в другой политике запрещается, то финальная политика определяется по своим особым законам. Причём, сложности добавляет тот факт, что внутри порядки разные. Например, порядок разбора правил путей отличается от порядка разбора всех остальных типов правил. Поэтому я решил немного детальней осветить этот вопрос, чтобы не было никакой путаницы. Я ещё в журнальной статье (На страже безопасности – Software Restriction Policies) отмечал про порядок применения типов правил. Однако, формат журнала не позволял детально рассмотреть этот момент, поэтому я его раскрываю здесь.

Итак, когда на клиентский компьютер применилось несколько политик со своими правилами, то они сортируются по конфликтным парам. Конфликтную пару могут составить 2 одинаковых правила (например, по сертификату или хешу), но с разными уровняеми (Unrestricted, Disallowed). Если для правила не нашлось конфликтной пары, то оно считается единственным правилом в паре и в итоге оно будет результирующим. При этом все пары сортируются и проверяются в строгом порядке:

  • [сперва запрет, потом разрешение] Правило сертификата
  • [сперва запрет, потом разрешение] Правило хэша
  • [сперва запрет, потом разрешение] Правило сетевой зоны
  • [сперва разрешение, потом запрет] Правило пути

Для первых трёх типов на каждый объект может быть задана одна пара правил (не может быть для одного файла 2 разных хеша, поэтому для него существует только один хеш и для этого хеша может быть 2 уровня – Unrestricted/Disallowed). И для первых трёх типов правил работает правило First Matching. При попытке запуска файла сперва проверяется запрет по правилу сертификата. Если он есть, то дальнейшая проверка правил обрывается и на основе first matching доступ прекращается. Если же запрета нету, то проверяется разрешение на основе сертификата. Если разрешение есть, то опять же на основе first matching доступ к файлу разрешается и остальные правила не проверяются. Если же правила сертификатов для объекта не обнаружено, то проверка переходит к правилам хешей. Та же самая ситуация повторяется и с хешами и правилами сетевой зоны. Если ничего из этого не соответствует объекту, то процесс переходит к проверке правил путей.

В правилах пути существует свой порядок сортировки правил:

  • [сперва разрешение, потом запрет] сначала самое общее правило (например, C:\)
  • [сперва разрешение, потом запрет] более точное (например, C:\Documents and Settings)
  • [сперва разрешение, потом запрет] ещё более точное (например, C:\Documents and Settings\All Users\Desktop)
  • [сперва разрешение, потом запрет] <...> до полного соответствия имени файла

Здесь так же правила разбиваются на пары. Т.е. для каждой точки пути подбирается такое же правило, но с другим уровнем Unrestrited/Disallowed. Как можно заметить, здесь меняется порядок проверки запретов и разрешений. Если в первых трёх типах правил первыми проверялись запреты, то в правилах путей всё наоборот – сначала обрабатываются разрешения и только потом запреты. Это обусловлено тем, что для правил путей нету правила First Matching, а проверяются абсолютно все правила. При этом, как видно из описания, сначала проверяются более общие правила с бОльшей площадью охвата (как весь диск C:\). И вот так проверяются все правила путей и результирующим правилом для объекта станет то, которое проверилось самым последним, которое будет самым точным из всех правил, под которое подпадает проверяемый объект. Безусловно, если есть пара, которая одновременно разрешает и запрещает запуск по полному имени файла, то последним проверится запрет и доступ будет запрещён. В этом смысле так же работает правило, что одинаковый запрет имеет приоритет над таким же разрешением. Если в исключениях не нашлось ни одного правила, под которое смог бы подпасть объект, тогда решение о запуске будет приниматься на основе Default Level.

Примечание: при проверке результирующей суммы в RSOP.MSC на клиентах Windows Vista SP1 и выше вы можете попасть на одну засаду. Дело в том, что новая консоль RSOP.MSC теперь показывает не всё. В разрезе результирующей политики вы не увидите правил сертификатов! Т.е. они по факту есть и работают (соответствующие сертификаты размещаются в контейнерах Trusted/Untrusted Publishers в CertStore), но в этой консоли их не будет. Почему так – я не знаю, но вам следует принимать во внимание этот факт.

Примечание: хочу замтетить, что порядок применения политик SRP влияет только на Default level, Enfocrement, Designated Files Types и секцию Trusted Publishers. Для Additional Rules порядок применения политик не имеет значения.