Windows PowerShell advanced functions have built-in control flow that helps both experienced and novice PowerShell developers maintain their code workflows.  If you have worked with PowerShell Advanced Functions then you probably have worked with Begin, Process and End blocks in your code.

Each Begin, Process, and End block has a specific purpose that helps guide and control your code workflows.  In this blog post, we will dive into each of these controls and show common uses cases for each.

As both Don Jones and Jeffrey Hicks have said in their Learn PowerShell Toolmaking in a Month of Lunches book; PowerShell functions should do one thing, and do it well.  PowerShell’s Begin, Process, and End blocks emphasize this statement of doing one thing and doing it well.

I would like to start with a simple analogy that may help understand the reason’s and uses case’s of the Begin, Process, and End blocks.  I BEGIN my day by getting ready for work.  Once I have begun work, then I start to PROCESS my tasks for the day.  I END my day by clearing my desk and heading home.

It’s a bit cheesy, but I think it gets my point across. Let’s use this fake PowerShell function as an example.  Please note, that this function does NOT work in anyway and it’s just as an example.

function New-WorkDay {
    [CmdletBinding(DefaultParameterSetName='Parameter Set 1',
                   PositionalBinding=$false,
                   ConfirmImpact='Medium')]
    Param (
        # Param1 help description
        [Parameter(Mandatory=$true,
                   Position=0,
                   ValueFromPipelineByPropertyName=$true,
                   ParameterSetName='Parameter Set 1')]
        [ValidateNotNullOrEmpty()]
        [string[]]
        $WorkItem
    )
    
    begin {
        Start-Alarm -ErrorAction SilentlyContinue | Exit-Sleep -Force
        Enter-Bathroom
        Open-ShowerDoor | Invoke-Shower | Select-Soap
        Start-BrushingTeeth | Wait-Time -Minutes 2 | Stop-BrushingTeeth
        Set-Clothes
        Build-Coffee
        Invoke-Driving -From Home -To Work -ErrorAction Stop
        Start-WorkDay -Hours 8
    }
    
    process {
        foreach($item in $WorkItem){
            switch ($item) {
                meeting { Start-Meeting $item }
                ticket  { Start-Ticket $item  }
                support { Start-Support $item }
            }
        }
    }
    
    end {
        Stop-WorkDay
        Invoke-Driving -From Work -To Home
    }
}

BEGIN

In a PowerShell function, you may want to setup your function by specifying variables and arrays to be used throughout your function.  The BEGIN block in a PowerShell function is the appropriate place to set these types of items.

The BEGIN block is optional and is NOT needed if you are just wanting to use either the PROCESS or END blocks

Everything in the BEGIN block will only run once per call of your function.  Just like a typical work day you will only prepare to go to work once.  This may include taking a shower, brushing your teeth, and driving to work.  In code, this is where you are setting up all requirements or dependencies before actually doing the work.  You can also consider this as “pre-processing” or “setup” for your PowerShell function.

In the provided example, this is seen in our BEGIN block:

begin {
        Start-Alarm -ErrorAction SilentlyContinue | Exit-Sleep -Force
        Enter-Bathroom
        Open-ShowerDoor | Invoke-Shower | Select-Soap
        Start-BrushingTeeth | Wait-Time -Minutes 2 | Stop-BrushingTeeth
        Set-Clothes
        Build-Coffee
        Invoke-Driving -From Home -To Work -ErrorAction Stop
        Start-WorkDay -Hours 8
    }

PROCESS

The process block is where the work is done in a PowerShell advanced function.  There are two different approaches we need to keep in mind and account for.  The first is how you handle parameters and the second is pipeline input into your function.

The PROCESS block can be used without the BEGIN or END blocks

Some questions you should ask yourself:

Does my function take a single value for a parameter or an array of values?

Does my function accept pipeline input (it should)?

If your function takes a single value for a parameter (not an array of values) then the PROCESS block will only process that single value.  If you also accept pipeline input on your single parameter then the PROCESS block will process each of the values in our PROCESS block.  Let’s take a look at an example:

function Example-BeginProcessEnd {
    [CmdletBinding()]
    Param (
        # Param1 help description
        [Parameter(ValueFromPipeline=$true)]
        [string]
        $WorkItem
    )
    begin {
       Write-Host 'Calling Begin Block'
    }
    
    process {
        Write-Host "Calling Process Block with $WorkItem variable"
    }
    
    end {
       Write-Host 'Calling End Block'
    }
}

If we call our Example-BeginProcessEnd function with a single value for WorkItem using the parameter value then you will see that it only processes the one passed in WorkItem value.

PS C:\> Example-BeginProcessEnd -WorkItem 'First'
Calling Begin Block
Calling Process Block with First variable
Calling End Block

If we were to pass in an array of values into the pipeline of this function then we will see a different result.  We will see that the PROCESS block is running multiple times based on the array list we passed into our function:

PS C:\> 'first','second','third' | Example-BeginProcessEnd
Calling Begin Block
Calling Process Block with first variable
Calling Process Block with second variable
Calling Process Block with third variable
Calling End Block

If we want our function to accept an array of values not only via pipeline but also through a parameter then we will need to modify our function to support this logic.  We first need to modify our parameter to accept an array of values by adding [] to our parameter:

Param (
        # Param1 help description
        [Parameter(ValueFromPipeline=$true)]
        [string[]]
        $WorkItem
    )
Passing an array through a parameter

Passing an array through a parameter

We also need to add a foreach statement in our PROCESS block then we can account for both situations.

process {
        foreach ($item in $WorkItem){
            Write-Host "Calling Process Block with $WorkItem variable"
        }
    }

By adding this to our PROCESS block then we pass an array of values via both our parameter and pipeline.

PS C:\> Example-BeginProcessEnd -WorkItem ‘first','second','third'
Calling Begin Block
Calling Process Block with first second third variable
Calling Process Block with first second third variable
Calling Process Block with first second third variable
Calling End Block

We can also pass in multiple values via the pipeline, like we were able to do previously:

PS C:\> 'first','second','third' | Example-BeginProcessEnd
Calling Begin Block
Calling Process Block with first variable
Calling Process Block with second variable
Calling Process Block with third variable
Calling End Block

We should always aim to provide enough flexibility for users to use our function in a way that supports them but not enough to cause them issues when using our function.

The PROCESS block is powerful and adds clear logic separation when writing PowerShell functions.  Continuing with my analogy, the PROCESS block is the actual work we do when we go to work.  If you work in IT, this can be seen as requests for support or projects (or daily routines) that we process or work on.  We get to work and then begin to process our day or tasks.  This can be seen in our original example:

process {
        foreach($item in $WorkItem){
            switch ($item) {
                meeting { Start-Meeting $item }
                ticket  { Start-Ticket $item  }
                support { Start-Support $item }
            }
        }
    }

We receive tickets or WorkItems and either call another function to do some action or process them as they come in.  Whether it is a meeting, a ticket, or a support request, we process them as they are given to us (sometimes using multiple threads - multi-tasking, am I right).

END

Finally, we get to the END block.  The END block is where we dispose or cleanup if needed.  If we created objects, variables, files, etc in our BEGIN or PROCESS blocks then we will use the END block to either clean these objects up or return any complex data structures.

The END block is optional and is NOT needed if you are just wanting to use either the PROCESS or BEGIN blocks

The main difference between the PROCESS block and the END block is that PowerShell executes code in the latter only once even if you pass an array through the pipeline. Thus, you can use the END block to finalize your function after all members of the array that you passed to your function have been processed. Likewise, the BEGIN block allows you to initialize your function with code that you want to run only once before PowerShell iterates through your input array.

The most common example I have found is that if you created a database connection in your BEGIN block, processed or updated a database in the PROCESS block then you would use the END block to disconnect/destroy our database connection.

Subscribe to 4sysops newsletter!

Another example is that you may have created an PSObject in the BEGIN block, added data to the object in the PROCESS block and then use the END block to return the final object.

avataravataravatar
10 Comments
  1. tmack (Rank 2) 5 years ago

    I finally understand the Process block now. I was always confused about how it automatically iterates through multiple inputs.

    Always processes an array passed from the pipeline — setup a loop for passed parameters.

    Thanks!

  2. Jeremy 5 years ago

    Thanks for this.  I have a question about variables that I define in the process block:

    Upon the second and later objects being processed, is it necessary to reset the variables to make sure the previously-processed object’s values don’t linger?  Or do those variables vanish when an object is finished processing and leaves the process block?

    • @Jeremy

      Variables defined inside the Begin and Process sections are global to the whole function and are available for each processed object, and can even be used inside the End section.

      You can try the following command-line which has the same behaviour like inside a function.

      <pre>1..5|ForEach-Object -Begin{$a=100;”The value has been intialized to:$a”;} -Process {$a+=$_;$a} -End{“The final result is:$a”}</pre>

  3. Sandor Szilagyi 4 years ago

    Excellent article which helped me a lot! Thank you for that! Only one note: Within the code example after "We also need to add a foreach statement in our PROCESS block"  you should use $item variable instead of $WorkItem at the Write-Host like this:

    process {
            foreach ($item in $WorkItem){
                Write-Host "Calling Process Block with $item variable"
            }
        }

     

  4. Jim 4 years ago

    Another gotcha that you need to keep in mind for advanced functions is the use of the return statement.  If executed within the Begin block, the Process and End blocks will still execute ( if present ), same holds true for a return in the Process block, the End block will still execute.  However thrown exceptions will exit the function at the point of being thrown.

    • Mark Kharitonov 3 years ago

      To Jim's point – the end block would have shined if it executed always. But if process fails with an exception, then the end block would not be called. That means the process block needs to catch exceptions to let the cleanup run. Or you need to call the cleanup logic from two places. Unless one has pipeline items to process, this makes it more cumbersome that having a single try-finally block that spans all of your function. Which you cannot do if you have process and end blocks.

  5. Rado Rafiringa 1 year ago

    If cleanup is necessary regardless of whether an exception is thrown or not, what is the advantage of using begin, process and end blocks instead of try, catch and finally blocks?

  6. They have different purposes.
    Begin/Process/End is for handling the input to a function. The Begin block lets you set up whatever you need for your data, the process block is where you actually do the work, and the End block is to do whatever cleanup you need. By default, a powershell function runs everything in the END block unless you specify otherwise.

    Try/Catch/Final is for running a bit of code that you have a strong suspicion might fail, and allows you to handle the problem cleanly instead of having your code just fail. The Final block always runs, and provides a final cleanup block for your bit of code.

    David F.

    avatar
  7. Harry John 1 year ago

    David, I think you may have missed the scenario here with the answer above:

    “If cleanup is necessary regardless of whether an exception is thrown or not”

    In this scenario, putting cleanup code that *is necessary regardless of whether an exception is thrown or not* into the end block will not run if an exception is thrown.

    In this scenario, not only is there a clear *dis*advantage to using the end block for necessary cleanup, it would result in a bug with the application.

  8. That’s why I mentioned the Finally block. When you do a Try/Catch/Finally, the Final block will always run even if an exception is thrown. (I corrected the name to Finally).

    The End block is really more of a broader scope – you use that to finalize whatever needs to be done for the entire script.

    David F.

Leave a reply to Mark Kharitonov Click here to cancel the reply

Please enclose code in pre tags

Your email address will not be published. Required fields are marked *

*

© 4sysops 2006 - 2023

CONTACT US

Please ask IT administration questions in the forums. Any other messages are welcome.

Sending

Log in with your credentials

or    

Forgot your details?

Create Account