schuelermine
schuelermine

Reputation: 2288

PowerShell - What is the difference between the `begin` block and anything outside?

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

Answers (2)

HAL9256
HAL9256

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

postanote
postanote

Reputation: 16106

See is these provide any edification:

About Language Keywords

Piping Objects to Functions

You can control how a function processes input from the pipeline using Begin, Process, and End keywords.

Windows PowerShell Cookbook, 3rd Edition by Lee Holmes

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

Understanding PowerShell Begin, Process, and End blocks

Advanced PowerShell Functions: Begin to Process to End

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

Related Questions