PowerShell является скриптовым языком, что подразумевает простоту его использования. Тем не менее в PowerShell есть некоторые механизмы, которые не совсем очивидны и просты для понимания. Один из таких механизмов – скриптблоки (scriptblock).
Вот их я и собираюсь «попиарить» в этой статье.
Скриптблок в PowerShell является экземпляром класса .NET Famework System.Management.Automation.ScriptBlock и представляет собой предкомпилированый текст скрипта как единое целое – объект:
>$sb = { dir }
>$sb.GetType()
IsPublic IsSerial Name BaseType
——— ——— —- ———
True False ScriptBlock System.Object
По сути скриптблок это почти функция. Почти потому что у скриптблока нет имени – функция это именованный скриптблок! – и есть более жесткие правила по заданию параметров при использовании в конвейере. Да скриптблок может иметь параметры и применяться в конвейере! Более того он как и функция может иметь такие фичи как DynamicParam, Begin, Process, and End – это позволяет писать вполне функциональные скриптблоки для работы в конвейере.
Пример использования скриптблока с параметром:
>$sb = { param ( [string] $DirPath) dir $DirPath }
> &$sb c:\
Как видите для исполнения скриптблока применяется оператор &:
>$sb = { dir } либо даже просто >$sb = “dir”
>&$sb
>& { dir }
Оператор может принимать только одиночную команду или скриптблок, т.е. нельзя сразу указать команду и ее параметр в строке:
>$string = “ dir C:\”
>& $string — ошибка!!!
Причина тут в том, что оператор & сначала делает вызов Get-Command $string для получения объекта команды (CommandInfo object), а потом делает вызов на исполнение. Но оператор & может принимать параметр, поэтому правильно писать так:
>$sb = “dir”
>&$sb C:\
>& { dir } C:\
Выполнить скриптблок также можно с помощью командлета Invoke-Command:
>$sb = { param ( [string] $DirPath) dir $DirPath }
>invoke-command –scriptblock $sb –args “C:\”
>invoke-command –scriptblock { param ( [string] $DirPath) dir $DirPath } –args “C:\”
Важно понимать, что есть объекты представляющие символьные строки и есть объекты представляющие скриптблоки. Это отличает PowerShell от других скриптоподобных языков, в которых можно написать строку и выполнить ее. Чтобы символьная строка PowerShell стала скриптблоком ее надо преобразовать:
Способ 1: $scriptblock = $executioncontext.invokecommand.NewScriptBlock($string)
Способ 2: $scriptblock = [scriptblock]::Create($string)
Способ 3: Использование оператора &
Из определения скриптблока видно, что его можно исполнить как код и что с ним можно обращаться как с объектом: например, присвоить переменной или передать как параметр командлету или функции, в том числе по конвейеру. Многие командлеты и функции принимают скриптблоки в качестве параметров:
>1..2 | Where-Object -FilterScript { $_ }
>$sb = { $_}
>1..2 | Where-Object -FilterScript $sb
Вот так работает передача скриптблока по конвейеру:
>$a = “c:\”,”d:\”
>$a | foreach { [scriptblock]::Create("dir $_") } | % { Invoke-Command $_ }
Как видите, такая техника позволяет очень гибко создавать параметризированый код скрипта, причем параметризированный не только данными, но и кодом. Если сравнить с другими языками, то это напоминает передачу функции в качестве параметра. Соответственно вы можете использовать такой опыт программирования, если он у вас есть. Но аналогия конечно не полная. Например, скриптблок можно генерировать динамически из текстовых строк.
Есть еще одна интересная особенность скриптблоков.
Ситуация иная, можно сказать, инверсная к предыдущей. Если мы строим конвейер и в скриптблоке задан параметр, то величина передаваемая по конвейеру привязывается к этому параметру – это мы видели в примере показанном выше. Если же скриптблок не имеет параметра, то он выполняется для каждой величины, которая передается по конвейеру, т.е. скриптблок параметризирует конвейер! Пример такого случая:
>"c:\" | Get-ChildItem -Path $_ — это не работает! Ошибка!
>"c:\" | Get-ChildItem -Path {$_} — это работает!!!
Как можно применять такое поведение скриптблоков для обхода ограничений командлетов, которые не принимают параметры из конвейера, рассказывают разработчики PowerShell https://blogs.msdn.com/powershell/archive/2006/06/23/643674.aspx Например, командлет Get-WmiObject не принимает объекты из конвейера:
> "Comp1","Comp2" | Get-WmiObject Win32_OperatingSystem
SystemDirectory : C:\Windows\system32
Organization :
BuildNumber : 7600
RegisteredUser : 1
SerialNumber : 00426-292-0126336-85460
Version : 6.1.7600
Get-WmiObject : The input object cannot be bound to any parameters for the command either because the command does not
take pipeline input or the input and its properties do not match any of the parameters that take pipeline input.
Можно обойти проблему так:
> "Comp1","Comp2" | ForEach { Get-WmiObject Win32_OperatingSystem -ComputerName $_ }
А можно более изящно:
> "Comp1","Comp2" | Get-WmiObject Win32_OperatingSystem -ComputerName { $_ }
Пример от разработчиков PowerShell кое-что разъяснил нам про прелести скрптблоков. Но пригодится ли нам это? Если мы делаем серьезный командлет или функцию для постоянного применения и тем более для распространения, то нам следует хорошо продумать ее интерфейс и реализовать для работы в конвейере. Но ведь это надо продумать, написать и протестировать. А описаная техника применения скриптблоков позволяет нам писать простые функции и использовать их в конвейере! Как это упрощает нам жизнь и экономит время!
Заключение. Для эффективного применения PowerShell важно понимать, что представляют собой скриптблоки и как они себя ведут в различных конструкциях кода. Рассмотренные ситуации далеко не исчерпывают возможности скриптблоков: читайте официальную документацию, блог разработчиков, примеры кода написанного другими и, конечно, книги.
Источники:
1. Краткое описание и примеры скрипблоков есть в документации по PowerShell. Смотрите файл about_script_blocks.help.
2. https://blogs.msdn.com/powershell/archive/2006/06/23/643674.aspx
3. ScriptBlock на MSDN http://msdn.microsoft.com/en-us/library/system.management.automation.scriptblock(VS.85).aspx
Filed under: Без рубрики |
Добавить комментарий