Reputation: 2288
If I write a function like this:
function a {
begin {
do-something
}
}
doesn't it do the same thing as this one?
function a {
do-something
}
What difference does the begin
make?
Upvotes: 1
Views: 908
Reputation: 13483
To expand on @iRon and @postanote comments, the Begin
block in a function only applies when you are developing 'Advanced Functions' a.k.a. functions that are designed for pipelining.
Also, Begin
blocks and Process
blocks don't really come into play unless you start dealing with arrays or lists of objects. Dealing with many objects require you to write code that properly knows how to handle the stream of objects.
Most functions are simple input -> process -> output
for example let's do a simple hello program:
Function Write-Greeting {
Param(
[Parameter(ValueFromPipeline=$true)]
$name
)
Write-Host "Hello: $name"
}
PS C:\> Write-Greeting "schuelermine"
Hello: schuelermine
But what happens if you want to doing something more, say, we want to say hello to multiple people:
$LotsOfPeople = @("HAL9256", "schuelermine", "Jon Skeet")
PS C:\> Write-Greeting $LotsOfPeople
Hello: HAL9256 schuelermine Jon Skeet
Well that's not exactly what we want. I want to iterate through them all, and introduce everyone once at a time. To do this we would now have to do this with a loop:
Function Write-Greeting {
Param(
[Parameter(ValueFromPipeline=$true)]
$name
)
$name | ForEach-Object {
Write-Host "Hello: $_"
}
}
PS C:\> Write-Greeting $LotsOfPeople
Hello: HAL9256
Hello: schuelermine
Hello: Jon Skeet
That's better. But it required a bunch of extra code. Now, I hear this amazing pipeline capability from PowerShell, let's try it:
PS C:\> "schuelermine" | Write-Greeting
Hello: schuelermine
Yay! We are using pipelines. We also hear that the benefit of pipelines is that it will take an array of strings and pass each string one string at a time down the pipeline. We have an array of people, we have a function, so now let's pipe lots of people to it:
PS C:\> $LotsOfPeople | Write-Greeting
Hello: Jon Skeet
Well, that's not what we expected.
The reason that it's not working as expected is that we haven't written our function to handle a stream of pipeline input.
The $LotsOfPeople
array of strings were indeed passed down the pipeline, one at a time, but our function is written to only handle one object. PowerShell decides that because the function isn't written to handle streams of objects from the pipeline, it ends up that the last object in the pipeline, is the object that is passed to the function.
This is why Begin
and Process
blocks are different than straight code, they tell PowerShell that this function is written to handle streams of objects properly.
Ok. Great! So what happens if we just add a Begin
block, that should magically make it pipeline aware, right?
Function Write-Greeting {
Param(
[Parameter(ValueFromPipeline=$true)]
$name
)
Begin {
Write-Host "Hello: $name"
}
}
PS C:\> $LotsOfPeople | Write-Greeting
Hello:
Well that's unexpected. I got nothing. But I thought I just made it pipeline aware?
The reason is that the Begin
block is the code that is executed before pipeline input is passed to it. Since we have no input passed to it yet, we get no greeting.
This is where we actually need to use a Process
block. Process
blocks tell PowerShell what code to "Process" with the input one item at a time.
Function Write-Greeting {
Param(
[Parameter(ValueFromPipeline=$true)]
$name
)
Process {
Write-Host "Hello: $name"
}
}
PS C:\> $LotsOfPeople | Write-Greeting
Hello: HAL9256
Hello: schuelermine
Hello: Jon Skeet
By simply adding the Process
block, and using pipelining, we eliminate the need for the ForEach-Object
loop. *The Process
block becomes the ForEach-Object
loop in the function.*
Similarly, Begin
blocks are for code that we need to do before we loop through our objects. e.g. if we need to initialize a counter, open a persistent connection, or introduce the class name that we only want to do once:
Function Write-Greeting {
Param(
[Parameter(ValueFromPipeline=$true)]
$name
)
Begin {
Write-Host "Welcome to PowerShell 101 Class"
}
Process {
Write-Host "Hello: $name"
}
}
PS C:\> $LotsOfPeople | Write-Greeting
Welcome to PowerShell 101 Class
Hello: HAL9256
Hello: schuelermine
Hello: Jon Skeet
Similarly, we can use the End
Block to do things at the end of the Process
loop. e.g. closing connections, or in this case counting how many computers we will need for the class.
Function Write-Greeting {
Param(
[Parameter(ValueFromPipeline=$true)]
$name
)
Begin {
Write-Host "Welcome to PowerShell 101 Class"
$ClassAttendance = 0
}
Process {
Write-Host "Hello: $name"
$ClassAttendance += 1
}
End {
Write-Host "We will need $ClassAttendance computers"
}
}
PS C:\> $LotsOfPeople | Write-Greeting
Welcome to PowerShell 101 Class
Hello: HAL9256
Hello: schuelermine
Hello: Jon Skeet
We will need 3 computers
Upvotes: 0
Reputation: 16106
See is these provide any edification:
You can control how a function processes input from the pipeline using Begin, Process, and End keywords.
Like pipeline-oriented functions, the Foreach-Object cmdlet lets you define commands to execute before the looping begins, during the looping, and after the looping completes:
"a","b","c" | Foreach-Object `
-Begin { "Starting"; $counter = 0 } `
-Process { "Processing $_"; $counter++ } `
-End { "Finishing: $counter" }
Starting
Processing a
Processing b
Processing c
Finishing: 3
One more thing:
as per Don Jones a PowerShell mvp, he says, then PROCESS block is only used when the command is run using pipeline input. In that case, objects are bound to input parameters one at a time, and PROCESS is executed. If you just run the script straight, meaning with no pipeline input, then PROCESS is ignored.
So, if we look at the defined goals of the implementation specifics, we have:
Begin
This block is used to provide optional one-time pre-processing for the function. The PowerShell runtime uses the code in this block one time for each instance of the function in the pipeline.
Process
This block is used to provide record-by-record processing for the function. This block might be used any number of times, or not at all, depending on the input to the function. For example, if the function is the first command in the pipeline, the Process block will be used one time. If the function is not the first command in the pipeline, the Process block is used one time for every input that the function receives from the pipeline. If there is no pipeline input, the Process block is not used.
A Filter is a shorthand representation of a function whose body is composed entirely of a process block.
This block must be defined if a function parameter is set to accept pipeline input. If this block is not defined and the parameter accepts input from the pipeline, the function will miss the values that are passed to the function through the pipeline.
Also, if the function/cmdlet supports confirmation requests (the -SupportsShouldProcess parameter is set to $True), the call to the ShouldProcess method must be made from within the Process block.
End
This block is used to provide optional one-time post-processing for the function.
Upvotes: 1