Problem
Your script, function, or script block primarily takes input from the pipeline, and you want to write it in a way that makes this intention both easy to implement and easy to read.
Solution
To cleanly separate your script into regions that deal with the initialization, perrecord processing, and cleanup portions, use the begin, process, and end keywords, respectively.
Example 108. A pipelineoriented script that uses cmdlet keywords
function InputCounter
{ begin {
$count = 0 }
## Go through each element in the pipeline, and add up ## how many elements there were. process {
WriteDebug "Processing element $_" $count++ }
end { $count } }
This produces the following output:
PS >$debugPreference = "Continue" PS >dir | InputCounter DEBUG: Processing element CompareProperty.ps1 DEBUG: Processing element ConnectWebService.ps1 DEBUG: Processing element ConvertTextObject.ps1 DEBUG: Processing element ConvertFromFahrenheitWithFunction.ps1 DEBUG: Processing element ConvertFromFahrenheitWithLibrary.ps1 DEBUG: Processing element ConvertFromFahrenheitWithoutFunction.ps1 DEBUG: Processing element GetAliasSuggestion.ps1 (...) DEBUG: Processing element SelectFilteredObject.ps1 DEBUG: Processing element SetConsoleProperties.ps1 20
Discussion
If your script, function, or script block deals primarily with input from the pipeline, the begin, process, and end keywords let you express your solution most clearly. Readers of your script (including you!) can easily see which portions of your script deal with initialization, perrecord processing, and cleanup. In addition, separating your code into these blocks lets your script to consume elements from the pipeline as soon as the previous script produces them.
Take, for example, the GetInputWithForeach and GetInputWithKeyword functions shown in Example 109. The first visits each element in the pipeline with a foreach statement over its input, while the second uses the begin, process, and end keywords.
Example 109. Two functions that take different approaches to processing pipeline input
## Process each element in the pipeline, using a ## foreach statement to visit each element in $input function GetInputWithForeach($identifier) {
WriteHost "Beginning InputWithForeach (ID: $identifier)"
foreach($element in $input)
{ WriteHost "Processing element $element (ID: $identifier)" $element
}
WriteHost "Ending InputWithForeach (ID: $identifier)" }
## Process each element in the pipeline, using the ## cmdletstyle keywords to visit each element in $input function GetInputWithKeyword($identifier) {
begin { WriteHost "Beginning InputWithKeyword (ID: $identifier)" }
process
{ WriteHost "Processing element $_ (ID: $identifier)" $_
}
end { WriteHost "Ending InputWithKeyword (ID: $identifier)" } }
Both of these functions act the same when run individually, but the difference becomes clear when we combine them with other scripts or functions that take pipeline input. When a script uses the $input variable, it must wait until the previous script finishes producing output before it can start. If the previous script takes a long time to produce all its records (for example, a large directory listing), then your user must wait until the entire directory listing completes to see any results, rather than seeing results for each item as the script generates it.
If a script, function, or script block uses the cmdletstyle keywords, it must place all its code (aside from comments or its param statement if it uses one) inside one of the three blocks. If your code needs to define
and initialize variables or define functions, place them in the begin block. Unlike most blocks of code contained within curly braces, the code in the begin, process, and end blocks has access to variables and functions defined within the blocks before it.
When we chain together two scripts that process their input with the begin, process, and end keywords, the second script gets to process input as soon as the first script produces it.
PS >1,2,3 | GetInputWithKeyword 1 | GetInputWithKeyword 2 Beginning InputWithKeyword (ID: 1) Beginning InputWithKeyword (ID: 2) Processing element 1 (ID: 1) Processing element 1 (ID: 2) 1 Processing element 2 (ID: 1) Processing element 2 (ID: 2) 2 Processing element 3 (ID: 1) Processing element 3 (ID: 2) 3 Ending InputWithKeyword (ID: 1) Ending InputWithKeyword (ID: 2)
When we chain together two scripts that process their input with the $input variable, the second script can’t start until the first completes.
PS >1,2,3 | GetInputWithForeach 1 | GetInputWithForeach 2 Beginning InputWithForeach (ID: 1) Processing element 1 (ID: 1) Processing element 2 (ID: 1) Processing element 3 (ID: 1) Ending InputWithForeach (ID: 1) Beginning InputWithForeach (ID: 2) Processing element 1 (ID: 2) 1 Processing element 2 (ID: 2) 2 Processing element 3 (ID: 2) 3 Ending InputWithForeach (ID: 2)
When the first script uses the cmdletstyle keywords, and the second scripts uses the $input variable, the second script can’t start until the first completes.
PS >1,2,3 | GetInputWithKeyword 1 | GetInputWithForeach 2 Beginning InputWithKeyword (ID: 1) Processing element 1 (ID: 1) Processing element 2 (ID: 1) Processing element 3 (ID: 1)
Ending InputWithKeyword (ID: 1) Beginning InputWithForeach (ID: 2) Processing element 1 (ID: 2) 1 Processing element 2 (ID: 2) 2 Processing element 3 (ID: 2) 3 Ending InputWithForeach (ID: 2)
When the first script uses the $input variable and the second script uses the cmdletstyle keywords, the second script gets to process input as soon as the first script produces it.
PS >1,2,3 | GetInputWithForeach 1 | GetInputWithKeyword 2 Beginning InputWithKeyword (ID: 2) Beginning InputWithForeach (ID: 1) Processing element 1 (ID: 1) Processing element 1 (ID: 2) 1 Processing element 2 (ID: 1) Processing element 2 (ID: 2) 2 Processing element 3 (ID: 1) Processing element 3 (ID: 2) 3 Ending InputWithForeach (ID: 1) Ending InputWithKeyword (ID: 2)