Как было показано в одной из предыдущих моих статей, одним из основных механизмов PowerShell является конвейер. Он позволяет передавать результаты выполнения одной команды PowerShell следующей. Не менее важно и то, что эти результаты являются не текстовым представлением полученных данных, а объектами. Таким образом, мы можем построить серию команд (расположенных в одну строку, one-liner), состоящую из любого количества элементов — команд PowerShell, функций и даже сценариев — и на любом ее шаге использовать свойства и методы проходящих по конвейеру объектов. Пример приведен в листинге 1.

Кроме того, нам известно, что многие команды PowerShell поддерживают конвейер в качестве механизма получения исходных данных. Каждая из таких команд PowerShell, в соответствии с определенной логикой, сопоставляет поступающие данные с одним или несколькими параметрами.

Однако в этой статье мы не будем рассматривать взаимодействие конвейера со встроенными командами PowerShell. Предметом нашего внимания станет использование его возможностей при создании собственных функций. А начнем мы с фильтров.

Фильтр

Фильтр — это функция, основным предназначением которой является работа с конвейером. По структуре он напоминает функцию, содержащую только блок Process, о чем я расскажу немного позже. Работает он следующим образом. Поступление объекта приводит к выполнению кода фильтра. Таким образом, код будет выполнен столько раз, сколько объектов поступит по конвейеру. Еще необходимо иметь в виду, что работа над следующим объектом не начнется до тех пор, пока не будет обработан предыдущий. Точнее сказать, следующий цикл выполнения кода фильтра не начнется до тех пор, пока не закончится предыдущий. Дело в том, что поступающие объекты инициируют выполнение кода фильтра, однако никакой проверки относительно того, обратились ли к текущему объекту хотя бы раз, не производится. В качестве иллюстрации приведен код фильтра в листинге 2. Мы видим, что он был запущен три раза, однако к поступающим элементам конвейера — ‘first’, ‘second’ и ‘third’ мы в нем не обращались.

Для доступа к текущему элементу конвейера используются переменные $_ или $PSItem, что с точки зрения функциональности абсолютно равноценно. Также для этих целей мы можем задействовать переменную $input. И хотя она не является полным аналогом переменных $_ и $PSItem, в данном случае результат обращения к ней будет тем же самым. Более подробно о переменной $input я расскажу позже.

Расширив функциональность преды­дущего фильтра с целью вывода значений поступающих по конвейеру элементов, мы получим такой результат, как показан в листинге 3. Так же, как и функции, фильтры поддерживают использование параметров (листинг 4).

ByValue и ByPropertyName

Как мы помним, существует два способа сопоставления командой PowerShell или функцией входящих объектов различным параметрам: ByValue и ByPropertyName.

ByValue означает, что определенному параметру будет сопоставлен поступивший по конвейеру объект при условии, что тип этого объекта соответствует ожидаемому параметром типу данных.

ByPropertyName указывает, что значением параметра будет значение свойства входящего объекта, имя которого соответствует имени или псевдониму (alias) параметра, опять же с учетом соответствия типов данных свойства объекта и параметра (листинг 5).

Как мы видим в строке Accept Pipeline Input?, параметр InputObject команды PowerShell Stop-Process поддерживает получение данных ByValue. Это говорит о том, что значением данного параметра станет поступивший объект, но только в том случае, если тип данных этого объекта — System.Diagnostics.Process или же некоторый тип данных, который можно преобразовать в System.Diagnostics.Process. Его сокращенное название мы можем видеть в угловых скобках сразу после имени параметра.

Выглядеть это будет так:

Get-Process Calculator | Stop-Process

Параметр Name этой же команды PowerShell поддерживает вариант ByPropertyName (листинг 6).

В этом случае значением параметра может стать содержимое свойства Name поступающего по конвейеру объекта при условии, что тип данных этого свойства — System.String или же некий тип данных, поддерживающий преобразование в строку. Причем принадлежность самого объекта к какому-либо типу не имеет значения. Мы для этих целей будем использовать объект типа System.Management.Automation.PSCustomObject.

[PSCustomObject]@{Name = ‘Calculator’}
   | Stop-Process

Если мы попробуем получить список псевдонимов параметра Name команды PowerShell Stop-Process

(Get-Command Stop-Process).
   Parameters.Item('Name').Aliases
ProcessName

то увидим, что единственным его алиасом является ProcessName. Это означает, что мы можем преобразовать приведенную выше команду следующим образом:

[PSCustomObject]@{ProcessName
   = ‘Calculator’} | Stop-Process

без каких-либо изменений в ее функциональности.

Функция

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

Аргумент ValueFromPipeline указывает, что параметр будет поддерживать способ ByValue, а ValueFromPipe­lineByPropertyName задает использование способа ByPropertyName. Указываются они таким образом, как показано в листинге 7.

Кроме того, поддерживается и другой, более краткий способ, представленный в листинге 8.

При определении параметра как поддерживающего получение данных по конвейеру мы можем использовать тот или иной аргумент либо оба сразу.

Теперь укажем, что значением параметра Name должен быть объект строки, а также добавим код для вывода функцией полученного значения (листинг 9).

Далее попробуем передать функции некоторое значение. Сделаем мы это несколькими способами, как посредством конвейера, причем в двух вариантах — ByValue и ByPropertyName, так и с использованием имени параметра (лис­тинг 10).

Как видите, все три способа работают. Теперь давайте добавим параметру Name два псевдонима, HostName и NodeName. Делается это при помощи атрибута Alias (листинг 11).

При передаче данных функции с использованием варианта ByProperty­Name вместо имени параметра Name мы можем использовать HostName или NodeName (листинг 12).

Begin, Process, End

Приведенные выше примеры хорошо работают с единственным объектом. Однако ситуация изменится, если в качестве значения мы укажем массив. Для того чтобы параметр Name поддерживал получение массива строк в качестве входных данных, добавим еще одну пару квадратных скобок к определению его типа (листинг 13).

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

Теперь, если в качестве входных данных мы используем массив, результаты будут такими, как показано в листинге 14.

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

Итак, функция может состоять из трех блоков — Begin, Process и End.

function Structure
{
   Param()
   Begin {}
   Process {}
   End {}
}

Begin

Блок Begin выполняется первым и только один раз. Если для передачи данных функции мы используем конвейер, то блок Begin выполняется еще до того, как первый объект поступит на обработку. Соответственно, переменные $_ и $PSItem в блоке Begin не будут содержать никакого значения (листинг 15).

То же касается и случая, когда мы явным образом определяем параметр как поддерживающий получение данных через конвейер (листинг 16). Если же мы вызовем функцию с использованием имени параметра, то получим следующие результаты:

Structure -Name 'first', 'second', 'third'
Begin
$_:
$Name: first second third

В данном случае функции не нужно ожидать поступления объекта по конвейеру, чтобы назначить его параметру, потому что на момент запуска его значение уже известно. Как следствие, мы можем получить значение параметра уже в блоке Begin.

Однако если ваша функция будет использоваться и тем, и другим способом, то есть будет получать данные как посредством конвейера, так и с использованием имени параметра (естественно, не одновременно), подобного следует избегать.

Тем не менее это не касается параметров, не поддерживающих конвейерное получение данных. Обращение к ним в блоке Begin не внесет в ваш код какой-либо непредсказуемости (листинг 17).

Process

При использовании конвейера блок Process выполняется для каждого полученного элемента поочередно. Таким образом, он будет запущен столько раз, сколько объектов поступит через конвейер. К обрабатываемому в данный момент элементу мы можем обратиться как при помощи переменных $_ или $PSItem, так и посредством переменной, соответствующей имени параметра, которому эти данные были сопоставлены (листинг 18).

Если же мы вызовем функцию с использованием имени параметра

Structure -Name ‘first’, ‘second’, ‘third’

то, как и ожидалось, увидим, что его значение доступно нам как в блоке Begin, так и в блоке Process.

Begin: first second third
Process: first second third

End

Завершается работа функции однократным выполнением блока End, который запускается сразу после того, как блок Process закончит обработку полученных объектов (листинг 19).

Как мы видим, обратившись к переменной $_ или $PSItem в блоке End, мы получим значение последнего переданного по конвейеру элемента. Тот же результат мы получим и при использовании переменной, соответствующей имени параметра, которому были назначены поступающие по конвейеру элементы (листинг 20).

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

Structure -Name 'first', 'second', 'third'
Begin: first second third
Process: first second third
End: first second third

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

Однако, как и в случае с блоком Begin, мы вполне можем задействовать в блоке End переменные из тех, что не предназначены для работы с конвейером (листинг 21).

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

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

Process применяется непосредственно для обработки поступающих данных. Именно в этом блоке используются $_, $PSItem, а также переменные, соответствующие имени получающего по конвейеру данные параметра.

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

Теперь давайте вернемся к ситуации, когда при создании поддерживающей конвейерное получение данных функции мы не используем блоки Begin, Process и End (листинг 22).

Мы видим, что результатом ее становится значение последнего переданного по конвейеру элемента. Почему так происходит?

Дело в том, что в случае, если в функции не определено явным образом ни одного блока, считается, что весь ее код находится в блоке End. Таким образом, приведенная выше функция не содержит никаких инструкций для обработки каждого из поступающих по конвейеру элементов и обращается к переменной $Name уже после того, как ей был назначен последний элемент. По сути, эта функция аналогична показанной в листинге 23.

Массивы

Теперь давайте создадим функцию со всеми тремя блоками и протестируем ее. В блоке Begin мы определим переменную $i как равную нулю, в блоке Process выведем значения всех переданных функции элементов с указанием их порядкового номера, а в блоке End выведем количество полученных функцией элементов. Код функции приведен в листинге 24.

Сначала обратимся к ней с использованием конвейера:

'first', 'second', 'third' | Structure
Begin processing.
Element 1: first
Element 2: second
Element 3: third
Number of elements: 3

Как видите, результат вполне ожидаемый. Сначала был запущен блок Begin, где мы инициализировали переменную. Затем блок Process, который отработал три раза — по одному для каждого поступившего по конвейеру элемента. Завершилось выполнение функции блоком End.

Теперь давайте воспользуемся параметром Name:

Structure -Name 'first', 'second', 'third'
Begin processing.
Element 1: first second third
Number of elements: 1

Что же у нас получилось? Блок Process был вызван один раз, равно как и блоки Begin и End, а выведенное значение содержит все три полученных элемента. Почему функция отработала именно таким образом? Дело в том, что когда мы передаем данные функции (или команде PowerShell) посредством конвейера, они передаются друг за другом, по одному. Таким образом, в каждый момент времени переменная содержит только один элемент из тех, что передаются по конвейеру.

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

Таким образом, если мы ожидаем, что наша функция будет использоваться обоими способами, нам нужно сделать так, чтобы в любом случае каждый элемент был обработан по отдельности. Для этого в блок Process мы добавим конструкцию foreach. На использование функции как части конвейера это не окажет никакого влияния, а в случае ее вызова с указанием параметра поможет разделить входящий массив на отдельные элементы и обработать каждый из них независимо (листинг 25).

Теперь запустим функцию, как передав ей данные при помощи конвейера

'first', 'second', 'third' | Structure
Begin processing.
Element 1: first
Element 2: second
Element 3: third
Number of elements: 3

так и с использованием параметра Name:

Structure -Name 'first', 'second', 'third'
Begin processing.
Element 1: first
Element 2: second
Element 3: third
Number of elements: 3

Как мы видим, функция работает одинаково вне зависимости от способа получения данных.

Входные данные

Теперь давайте поговорим о переменной $Input. Мы можем использовать ее при обращении к обрабатываемым элементам, равно как и переменные $_ и $PSHost, хотя для этого нам нужно учитывать некоторые важные детали.

Начнем с того, что переменная $Input — это нумератор, и этим объясняются некоторые ее особенности.

Нумераторы позволяют получать данные из коллекций и делают это следующим образом. Нумератор расположен в начале коллекции, и для того, чтобы получить первый элемент, нам нужно переместить его на один элемент вперед, что позволяет сделать метод MoveNext. Получить этот элемент мы можем при помощи свойства Current нумератора (листинг 26).

Затем, для перехода к следующим элементам коллекции, используется все тот же метод MoveNext. Узнать, не достигли ли мы конца коллекции, позволяет возвращаемое этим методом значение. Если это True, то нумератор еще не дошел до конца коллекции и мы можем получить текущий элемент при помощи свойства Current. Если же возвращаемое значение False, это говорит о том, что конца коллекции мы достигли. При этом свойство Current не будет содержать какого-либо значения.

$e.MoveNext()
True
$e.MoveNext()
True
$e.Current
third
$e.MoveNext()
False
$e.Current

Для того чтобы переместить нумератор к началу коллекции, используется метод Reset.

$e.Reset()

Обращение к нумератору напрямую выводит все содержимое коллекции и перемещает нумератор в ее конец.

$e
first
second
third
$e.MoveNext()
False

Вернемся к $Input. Так же, как и $_ и $PSItem, переменная $Input не будет содержать каких-либо значений в блоке Begin. В блоке Process она будет содержать обрабатываемый в данный момент элемент. После того как все поступившие по конвейеру элементы будут обработаны в блоке Process, в блоке End она опять же не будет содержать никакого значения (листинг 27).

Однако если блок Process в функции отсутствует, то, в отличие от $_ и $PSItem, обращение к переменной $Input в блоке End выведет весь массив полученных функцией данных (листинг 28).

И, конечно, не стоит забывать о том, что $Input — это нумератор. Поэтому, напрямую обратившись к этой переменной в любой части кода и получив нужное значение, мы переместим нумератор в конец коллекции, и для того, чтобы получить эти данные еще раз, нам потребуется воспользоваться методом Reset, как показано в листинге 29.

Параметры

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

Если при помощи конвейера мы передадим ей некие данные, то получим следующий результат:

'first' | SeveralParameters

StringOne: first
StringTwo: first
Integer:

Таким образом, получается, что оба параметра, принимающие входные данные в виде объектов строки, StringOne и StringTwo, получили в качестве значения поступивший по конвейеру объект.

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

В нашем случае входящий объект ‘first’ является строкой и поэтому может быть назначен в качестве значения параметрам StringOne и StringTwo. Третий параметр — Integer — ожидает объект типа System.Int32. Строка, содержащая любые символы, кроме цифр, не может быть преобразована в этот тип данных. Однако давайте попробуем сделать следующее:

'110' | SeveralParameters
StringOne: 110
StringTwo: 110
Integer: 110

В данном случае входящий объект был сопоставлен всем трем параметрам: в виде строки — параметрам StringOne и StringTwo и в виде Int32 — параметру Integer.

Наборы параметров

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

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

Основываясь на предыдущем примере из листинга 30, предположим, что вы создаете функцию, которая, хотя и принимает данные как в виде строки, так и в виде целого числа, обрабатывает их по-разному. Например, такую как показана в листинге 31.

Теперь, если мы передадим ей строку, то получим следующий результат:

'first' | SeveralParameters
The object is a string: first

Однако если мы передадим число, то вывод будет следующим:

110 | SeveralParameters
The object is a string: 110
The object is an integer: 110

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

Здесь можно разнести параметры по разным наборам — Parameter Sets, что исключит возможность их одновременного использования, в том числе при сопоставлении поступающих по конвейеру объектов (листинг 32).

Теперь, если мы передадим функции как строку, так и целое число, результаты будут следующими:

'first' | SeveralParameters
The object is a string: first
110 | SeveralParameters
The object is an integer: 110

Почему же функция при сопоставлении поступающих объектов параметрам не конвертирует число 110 в строку и не назначает ее в качестве значения параметра String?

Дело в том, что, решая, какому параметру назначить входящий объект, PowerShell сначала пытается сопоставить его без выполнения преобразования и только в том случае, если подходящие параметры отсутствуют, приступает к его конвертации.

Стоит упомянуть, что передача функции значения 110 в виде строки, чего мы можем достичь, заключив его в кавычки, приведет к тому, что оно будет назначено параметру String.

'110' | SeveralParameters
The object is a string: 110

Еще одним способом разделения логики функции, кроме получения значений переменных $String и $Integer, будет проверка имени используемого набора параметров. Мы указали, что параметр String принадлежит набору параметров str, а параметр Integer — набору праметров int. Получить имя текущего набора параметров мы можем при помощи автоматической переменной $PSCmdlet и ее свойства ParameterSetName.

Для того чтобы переменная $PSCmdlet была доступна из кода функции, нам нужно определить функцию как расширенную — Advanced Function, что делается при помощи атрибута CmdletBinding. Еще одним вариантом является указание атрибута Parameter хотя бы для одного параметра функции. Однако использование CmdletBinding — более формальный способ, поэтому давайте воспользуемся им. Вместе с видоизмененной логикой наша функция будет выглядеть так, как показано в листинге 33.

Передав ей массив значений, содержащий как строчные, так и числовые данные, мы получим следующий результат:

'one', 2, 3, 'four' | SeveralParameters
The object is a string: one
The object is an integer: 2
The object is an integer: 3
The object is a string: four

Стоит добавить, что если при использовании конвейера вы запросите содержимое свойства ParameterSetName переменной $PSCmdlet из блока Begin, то получите имя набора параметров по умолчанию. И это будет либо ‘__AllParameterSets’, либо то, что вы явным образом задали в аргументе DefaultParameterSetName атрибута CmdletBinding. В блоке End имя набора параметров будет соответствовать тому, что использовался при обработке последнего элемента конвейера (листинг 34).

Конвейер

Теперь давайте подытожим сказанное и рассмотрим очередность, с которой PowerShell пытается сопоставить поступающие по конвейеру данные различным параметрам.

Сначала проверяются параметры, поддерживающие работу с конвейером при помощи способа ByValue, то есть те из них, которые предназначены для получения объекта целиком. При этом попыток конвертации типа объекта на данном шаге не предпринимается.

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

Если параметр, которому можно сопоставить поступающие по конвейеру объекты, все еще не найден, предпринимается попытка сопоставления с использованием их конвертации к нужному типу данных. Происходит это в той же последовательности: сначала используется способ ByValue, затем ByPropertyName.

Результат

Если сопоставление объектов параметрам происходит неочевидным для вас образом, вы можете воспользоваться командой PowerShell с именем Trace-Command, которая, помимо прочего, позволяет отследить каждый шаг, предпринимаемый командной средой для назначения поступающих данных различным параметрам. Например, так:

Trace-Command -Name
   ParameterBinding -Expression
   {'one', 2, 3, 'four' |
   SeveralParameters} -PSHost

Кроме того, для работы как с собственными функциями, так и с существующими командами PowerShell, вам может пригодиться модуль sthPipelineTools и его функции — Get-sthPipelineCommand и Get-sthPipelineParameter.

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

$command = Get-Command
   -Module ‘Microsoft.PowerShell.
   Management’
Get-sthPipelineCommand
   -Command $command
'Get-Process', 'Get-Service', 'gcim', 'gwmi'
   | Get-sthPipelineCommand

Get-sthPipelineParameter выводит информацию о поддерживающих конвейерную обработку данных параметрах команды PowerShell или функции, наборах параметров, в которых они могут быть использованы, а также о применяемых ими способах сопоставления — ByValue, ByPropertyName, поступающих по конвейеру объектов.

Например, так:

Get-sthPipelineParameter Get-Item
‘Get-Content’, ‘sv’ | Get-sthPipelineParameter

Таким же образом мы можем получить информацию и о параметрах функции, созданной нами ранее.

Get-sthPipelineParameter
   -Command SeveralParameters

Установить модуль sthPipelineTools можно из хранилища сценариев PowerShell Gallery следующим образом:

Install-Module -Name sthPipelineTools

Итак, мы рассмотрели основные способы применения конвейера при создании собственных функций. При этом мы затронули блоки Begin, Process и End, из которых может состоять функция, особенности использования наборов параметров при распределении поступающих по конвейеру объектов, а также способы сопоставления значений параметрам, как при совпадении типа данных входящего объекта и параметра, так и в случаях, когда для этого требуется преобразование значения к определенному типу. Я надеюсь, что эта статья поможет вам в освоении такого мощного и удобного механизма, как конвейер.

Листинг 1. Пример серии команд
Get-Process | Group-Object -Property Name | Sort-Object -Property Count -Descending | Select-Object -First 10
Листинг 2. Пример фильтра
filter NotTouchingPipelineElements
{
    "Step: $($i++; $i)"
}

‘first’, ‘second’, ‘third’ | NotTouchingPipelineElements

Step: 1
Step: 2
Step: 3
Листинг 3. Расширенный вариант фильтра из листинга 2
filter TouchingPipelineElements
{
    "`nStep: $($i++; $i)"
    "Value: $_"
}

'first', 'second', 'third' | TouchingPipelineElements

Step: 1
Value: first

Step: 2
Value: second

Step: 3
Value: third
Листинг 4. Фильтр с параметрами
filter TouchingPipelineElements
{
    Param(
        $Parameter
    )

    "`nStep: $($i++; $i)"
    "Value: $_"
    "Parameter Value: $Parameter"
}

'first', 'second', 'third' | TouchingPipelineElements -Parameter 'some_value'

Step: 1
Value: first
Parameter Value: some_value

Step: 2
Value: second
Parameter Value: some_value

Step: 3
Value: third
Parameter Value: some_value
Листинг 5. Способы сопоставления параметров
Get-Help -Name Stop-Process -Parameter InputObject

-InputObject 
    Specifies the process objects to stop. Enter a variable that contains the objects, or type a command or expression that gets the objects.

    Required?                    true
    Position?                    0
    Default value                None
    Accept pipeline input?       True (ByValue)
    Accept wildcard characters?  false
Листинг 6. Пример передачи параметра по варианту 
ByPropertyName
C:\> Get-Help -Name Stop-Process -Parameter Name

-Name 
    Specifies the process names of the processes to stop. You can type multiple process names, separated by commas, or use wildcard characters.

    Required?                    true
    Position?                    named
    Default value                None
    Accept pipeline input?       True (ByPropertyName)
    Accept wildcard characters?  false
Листинг 7. Использование аргументов ValueFromPipeline и ValueFromPipelineByPropertyName
function UsingPipeline
{
    Param(
        [Parameter(ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)]
        $Name
    )
}
Листинг 8. Укороченный вариант листинга 7
function UsingPipeline
{
    Param(
        [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)]
        $Name
    )
}
Листинг 9. Значением параметра Name будет строка
function UsingPipeline
{
    Param(
        [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [string]$Name
    )
    
    $Name
}
Листинг 10. Передача функции параметров разными вариантами
'first' | UsingPipeline
first

[PSCustomObject]@{Name = 'first'} | UsingPipeline
first

UsingPipeline -Name 'first'
first
Листинг 11. Добавление псевдонима параметру
function UsingPipeline
{
    Param(
        [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [Alias('HostName','NodeName')]
        [string]$Name
    )
    
    $Name
}
Листинг 12. Использование псевдонимов
[PSCustomObject]@{HostName = ‘first’} | UsingPipeline

first

[PSCustomObject]@{NodeName = 'first'} | UsingPipeline

first
Листинг 13. Добавление поддержки массива в качестве входных данных
function UsingPipeline
{
    Param(
        [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [Alias('HostName','NodeName')]
        [string[]]$Name
    )
    
    $Name
}
Листинг 14. Результаты обработки массива в качестве входных данных
UsingPipeline -Name 'first', 'second', 'third'
first
second
third
'first', 'second', 'third' | UsingPipeline
third
[PSCustomObject]@{Name = 'first'}, [PSCustomObject]@{Name = 'second'}, [PSCustomObject]@{Name = 'third'} | UsingPipeline
third
Листинг 15. Блок Begin выполняется до обработки первого объекта
function Structure
{
    Param()
    Begin
    {
        "Begin: $_"
    }
    Process {}
    End {}
}
'first', 'second', 'third' | Structure
Begin:
Листинг 16. Блок Begin и параметр
function Structure
{
    Param(
        [Parameter(ValueFromPipeline)]
        [string[]]$Name
    )
    Begin
    {
        "Begin"
        "`$_: $_"
        "`$Name: $Name"
    }
    Process {}
    End {}
}
‘first’, ‘second’, ‘third’ | Structure
Begin
$_:
$Name:
Листинг 17. Блок Begin и параметры, не поддерживающие конвейерное получение данных
function Structure
{
    Param(
        [Parameter(ValueFromPipeline)]
        [string[]]$Name,
        [string]$AnotherParameter
    )
    Begin
    {
        "Begin"
        "`$AnotherParameter: $AnotherParameter"
    }
    Process {}
    End {}
}

'first', 'second', 'third' | Structure -AnotherParameter 'some_value'
Begin
$AnotherParameter: some_value
Листинг 18. Примеры блока Process
-----
function Structure
{
    Param()
    Begin
    {
        "Begin: $_"
    }
    Process
    {
        "Process: $_"
    }
    End {}
}

‘first’, ‘second’, ‘third’ | Structure

Begin:
Process: first
Process: second
Process: third

-----

function Structure
{
    Param(
        [Parameter(ValueFromPipeline)]
        [string[]]$Name
    )
    Begin
    {
        "Begin: $Name"
    }
    Process
    {
        "Process: $Name"
    }
    End {}
}

‘first’, ‘second’, ‘third’ | Structure

Begin:
Process: first
Process: second
Process: third
Листинг 19. Пример блока End
function Structure
{
    Param()
    Begin
    {
        "Begin: $_"
    }
    Process
    {
        "Process: $_"
    }
    End
    {
        "End: $_"
    }
}

‘first’, ‘second’, ‘third’ | Structure

Begin:
Process: first
Process: second
Process: third
End: third
Листинг 20. Блок End с переменной, соответствующей имени параметра
function Structure
{
    Param(
        [Parameter(ValueFromPipeline)]
        [string[]]$Name
    )
    Begin
    {
        "Begin: $Name"
    }
    Process
    {
        "Process: $Name"
    }
    End
    {
        "End: $Name"
    }
}

'first', 'second', 'third' | Structure

Begin:
Process: first
Process: second
Process: third
End: third
Листинг 21. Блок End и переменные, не предназначенные для работы с конвейером
function Structure
{
    Param(
        [Parameter(ValueFromPipeline)]
        [string[]]$Name,
        [string]$AnotherParameter
    )
    Begin
    {
        "Begin"
        "`$AnotherParameter: $AnotherParameter"
    }
    Process {}
    End
    {
        "`nEnd"
        "`$AnotherParameter: $AnotherParameter"
    }
}
'first', 'second', 'third' | Structure -AnotherParameter 'some_value'
Begin
$AnotherParameter: some_value
End
$AnotherParameter: some_value
Листинг 22. Функция без блоков Begin, Process и End
function UsingPipeline
{
    Param(
        [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [Alias('HostName','NodeName')]
        [string[]]$Name
    )
    
    $Name
}

'first', 'second', 'third' | UsingPipeline

third
Листинг 23. Функция листинга 22 с блоками Begin, Process и End
function UsingPipeline
{
    Param(
        [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [Alias('HostName','NodeName')]
        [string[]]$Name
    )
    
    End
    {
        $Name
    }
}

'first', 'second', 'third' | UsingPipeline

third
Листинг 24. Функция с тремя блоками
function Structure
{
    Param(
        [Parameter(ValueFromPipeline)]
        [string[]]$Name
    )
    Begin
    {
        "Begin processing.`n"
        $i = 0
    }
    Process
    {
        $i++
        "Element ${i}: $Name"
    }
    End
    {
        "`nNumber of elements: $i"
    }
}
Листинг 25. Модифицированный листинг 24
function Structure
{
    Param(
        [Parameter(ValueFromPipeline)]
        [string[]]$Name
    )
    Begin
    {
        "Begin processing.`n"
        $i = 0
    }
    Process
    {
        foreach ($n in $Name)
        {
            $i++
            "Element ${i}: $n"
        }
    }
    End
    {
        "`nNumber of elements: $i"
    }
}
Листинг 26. Свойство Current нумератора
$collection = ‘first’, ‘second’, ‘third’
$e = $collection.GetEnumerator()

$e.Current

$e.MoveNext()
True

$e.Current
first
Листинг 27. $Input в функции с тремя блоками
function GettingInput
{
    Begin
    {
        "Begin: $input"
    }
    Process
    {
        "Process: $input"
    }
    End
    {
        "End: $input"
    }
}

'first', 'second', 'third' | GettingInput

Begin:
Process: first
Process: second
Process: third
End:
Листинг 28. $Input в функции без блока Process
function GettingInput
{
    Begin
    {
        "Begin: $input"
    }
    End
    {
        "End: $input"
    }
}

'first', 'second', 'third' | GettingInput

Begin:
End: first second third
Листинг 29. $Input — это нумератор
function GettingInput
{
    Process
    {
        "Process: $input"
        "One more time: $input"
        $input.Reset()
        "After reset: $input"
    }
}

'first' | GettingInput

Process: first
One more time:
After reset: first
Листинг 30. Функция с параметрами, поддерживающими получение данных через конвейер
function SeveralParameters
{
    Param(
        [Parameter(ValueFromPipeline)]   
        [string[]]$StringOne,
        [Parameter(ValueFromPipeline)]   
        [string[]]$StringTwo,
        [Parameter(ValueFromPipeline)]   
        [int[]]$Integer
    )

    Process
    {
        "StringOne: $StringOne"
        "StringTwo: $StringTwo"
        "Integer: $Integer"
    }
}
Листинг 31. Модифицированная функция из листинга 30
function SeveralParameters
{
    Param(
        [Parameter(ValueFromPipeline)]   
        [string[]]$String,
        [Parameter(ValueFromPipeline)]   
        [int[]]$Integer
    )

    Process
    {
        if ($String)
        {
            "The object is a string: $String"
        }
        if ($Integer)
        {
            "The object is an integer: $Integer"
        }
    }
}
Листинг 32. Использование наборов параметров
function SeveralParameters
{
    Param(
        [Parameter(ParameterSetName='str', ValueFromPipeline)]   
        [string[]]$String,
        [Parameter(ParameterSetName='int', ValueFromPipeline)]   
        [int[]]$Integer
    )

    Process
    {
        if ($String)
        {
            "The object is a string: $String"
        }
        if ($Integer)
        {
            "The object is an integer: $Integer"
        }
    }
}
Листинг 33. Расширенная функция
function SeveralParameters
{
    [CmdletBinding()]
    Param(
        [Parameter(ParameterSetName=’str’, ValueFromPipeline)]   
        [string[]]$String,
        [Parameter(ParameterSetName=’int’, ValueFromPipeline)]   
        [int[]]$Integer
    )

    Process
    {
        if ($PSCmdlet.ParameterSetName -eq ‘str’)
        {
            "The object is a string: $String"
        }
        if ($PSCmdlet.ParameterSetName -eq ‘int’)
        {
            "The object is an integer: $Integer"
        }
    }
}
Листинг 34. Запрос содержимого свойства ParameterSetName переменной $PSCmdlet
function SeveralParameters
{
    [CmdletBinding(DefaultParameterSetName='default')]
    Param(
        [Parameter(ParameterSetName='str', ValueFromPipeline)]   
        [string[]]$String,
        [Parameter(ParameterSetName='int', ValueFromPipeline)]   
        [int[]]$Integer
    )


    Begin
    {
        "Begin: $($PSCmdlet.ParameterSetName)"
    }
    Process
    {
        "Process: $($PSCmdlet.ParameterSetName)"
    }
    End
    {
        "End: $($PSCmdlet.ParameterSetName)"
    }
}

'one', 2, 3, 'four' | SeveralParameters

Begin: default
Process: str
Process: int
Process: int
Process: str
End: str
Купить номер с этой статьей в PDF