Skip to content
Menu
The Lonely Administrator
  • PowerShell Tips & Tricks
  • Books & Training
  • Essential PowerShell Learning Resources
  • Privacy Policy
  • About Me
The Lonely Administrator

Potential Pipeline Pitfall

Posted on January 11, 2010

Last week I was helping someone out in the PowerShell forum at ScriptingAnswers.com. The specific problem is irrelevant;l however I  learned something in the process that will affect how I write my own PowerShell functions from now on. Not being a developer I never picked up on this subtle (at least to me) distinction. Here’s the deal.

Manage and Report Active Directory, Exchange and Microsoft 365 with
ManageEngine ADManager Plus - Download Free Trial

Exclusive offer on ADManager Plus for US and UK regions. Claim now!

I suggested a solution to the original problem that would require two functions. My initial thought was to write a PowerShell expression that would take the results of the first function and pipe them to the second function. Since I wanted the second function to accept pipelined input, I wrote it with a Begin, Process and End script blocks.

Function Sample {

 

    BEGIN {

     #code runs once before any pipelined objects are processed

    }

    PROCESS {   

     #code runs for each pipelined object, $_

    }

 

    END {

     #code runs once after every object has been processed

    }

} #end function

The BEGIN and END script blocks are optional, but I actually wanted to use the BEGIN script block to test a condition.  If the condition failed then I wanted the function to abort. Here’s what I thought I could do:

PS C:\> functionA | functionB –myParam foo

In my mind, functionA would run and then the results would be passed to functionB. If the parameter test failed, according the code in my begin script block, then the second function would display an error and stop. But that is not what happened. I put in some debug messages and it looked like the second function was starting before the first!  Here are some sample functions that demonstrate this behavior.

function Do-A {

    write-host "Starting A" -fore green

    

    for ($i=1;$i-le 5;$i++) {

     $i

    }

    write-host "Ending A"-fore Green

} #end A

 

Function Do-B {

 

    Begin {

        write-host "Starting B" -fore Magenta

    }

    Process {

        $_*2

        sleep -mill 100    

    }

    End {

        write-host "Ending B" -fore Magenta

    }

} #end B

I expect the numbers 1-5 to be piped to the second function and multiplied by 2.

PS C:\> Do-A | Do-B

Starting B

Starting A

2

4

6

8

10

Ending A

Ending B

I get the results ok, but notice that I get the message from the Begin block in the second function first. This means that code like this will also not work as expected.

Function Do-A {

    write-host "Starting A" -fore green

 

    for ($i=1;$i-le 5;$i++) {

     $i

    }    

    write-host "Ending A"-fore Green

} #end A

 

Function Do-B {

 Param([switch]$bail)

    Begin {

        write-host "Starting B" -fore Magenta

            if ($bail) {

                write-warning "Bailing"

                return

        }

    }

    Process {

        $_*2

        sleep -mill 100

    }

 

    End {

        write-host "Ending B" -fore Magenta

    }

} #end B

PS C:\> Do-A | Do-B

Starting B

WARNING: Bailing

Starting A

2

4

6

8

10

Ending A

Ending B

The reason for this behavior I learned is that in a pipelined expression, the Begin scriptblocks are all processed first. In my situation, only the second function had a Begin  script block so it was executed first. This makes sense and now that I understand that behavior I can code accordingly.

function Do-A {

    write-host "Starting A" -fore green

 

    for ($i=1;$i-le 5;$i++) {

     $i    

    }

    write-host "Ending A"-fore Green

} #end A

 

Function Do-B {

 Param([switch]$bail)

    Begin {

       write-host "Starting B" -fore Magen        

    }

    Process {

        if ($bail) {

                write-warning "Bailing"

                return

      }

        $_*2

        sleep -mill 100

    }

    End {

        write-host "Ending B" -fore Magenta

    }

} #end B

Now if I pipe the functions, I get my desired effect.

PS C:\> Do-A | Do-B -bail

Starting B

Starting A

WARNING: Bailing

WARNING: Bailing

WARNING: Bailing

WARNING: Bailing

WARNING: Bailing

Ending A

Ending B

My examples are overly broad and the point I want to emphasize is that in a pipelined expression all the Begin script blocks are executed first. This is not necessarily a bad thing, merely behavior you have to understand. As an admin I had a different expectation as to PowerShell’s behavior.

Here’s one more example with yet another function thrown in.

Function Foo-A {

 

    Write-Host "Starting Foo-A" -ForegroundColor green

    dir $env:temp | where {$_.lastwritetime -gt (Get-Date).AddDays(-1)}

    Write-Host "Ending Foo-A" -ForegroundColor green

    

} #end A

 

Function Foo-B {

 

    begin {

    

    Write-Host "Starting Foo-B" -ForegroundColor magenta

    }

    

    process {

     # write ("{0}  {1}  {2}" -f $_.fullname,$_.LastWriteTime,$_.length)

      Remove-Item $_.fullname -recurse -whatif

    }

    

    end {

        Write-Host "ending Foo-B" -ForegroundColor magenta

    }

 

} #end B

 

Function Foo-C {

 

    Begin {

    Write-Host "Starting Foo-C" -ForegroundColor Cyan

     } 

    

    Process {

      #not really doing anything

      sleep -milliseconds 100

    }

    

    end {

     Write-Host "ending Foo-C" -ForegroundColor Cyan

    }

} #end C

 

 

Foo-A | Foo-B | Foo-C

If you run:

PS C:\> Do-A | Do-B | Do-C

You will see that the begin script blocks for B and C run first. End script blocks, by the way seem to run in order. You will see this behavior on PowerShell v1 and v2.

If you run into any problems developing your own PowerShell solutions, please drop by the forums at ScriptingAnswers.com.


Behind the PowerShell Pipeline

Share this:

  • Click to share on X (Opens in new window) X
  • Click to share on Facebook (Opens in new window) Facebook
  • Click to share on Mastodon (Opens in new window) Mastodon
  • Click to share on LinkedIn (Opens in new window) LinkedIn
  • Click to share on Pocket (Opens in new window) Pocket
  • Click to share on Reddit (Opens in new window) Reddit
  • Click to print (Opens in new window) Print
  • Click to email a link to a friend (Opens in new window) Email

Like this:

Like Loading...

Related

4 thoughts on “Potential Pipeline Pitfall”

  1. Joel "Jaykul" Bennett says:
    January 11, 2010 at 3:14 pm

    I believe that BOTH the begin blocks and END blocks are run in pipeline order. It doesn’t looks like that in your output only because you’re writing the “begin” message from A from the END block 😉

    1. Jeffery Hicks says:
      January 11, 2010 at 3:43 pm

      I thought I had made that clear. If I have these functions:


      Function Foo-A {

      Write-Host "Starting Foo-A" -ForegroundColor green

      dir $env:temp | where {$_.lastwritetime -gt (Get-Date).AddDays(-1)}

      Write-Host "Ending Foo-A" -ForegroundColor green

      } #End A

      Function Foo-B {

      begin {

      Write-Host "Starting Foo-B" -ForegroundColor magenta
      }

      process {
      #Write ("{0} {1} {2}" -f $_.fullname,$_.LastWriteTime,$_.length)
      # Remove-Item $_.fullname -recurse -whatif
      write $_.fullname
      }

      end {
      Write-Host "ending Foo-B" -ForegroundColor magenta
      }

      } #End B

      Function Foo-C {

      Begin {
      Write-Host "Starting Foo-C" -ForegroundColor Cyan
      }

      Process {
      #not really doing anything
      write-host "sleeping on $_" -fore Cyan
      sleep -milliseconds 100
      }

      end {
      Write-Host "ending Foo-C" -ForegroundColor Cyan
      }
      } #End C

      And then run Foo-A | Foo-B | Foo-C I will get output like this:

      Starting Foo-B
      Starting Foo-C
      Starting Foo-A
      sleeping on C:\Users\Jeff\AppData\Local\Temp\msohtmlclip1
      sleeping on C:\Users\Jeff\AppData\Local\Temp\CVRA4BF.tmp.cvr
      sleeping on C:\Users\Jeff\AppData\Local\Temp\CVRB851.tmp.cvr
      sleeping on C:\Users\Jeff\AppData\Local\Temp\CVRC69E.tmp.cvr
      sleeping on C:\Users\Jeff\AppData\Local\Temp\CVRC6B5.tmp.cvr
      sleeping on C:\Users\Jeff\AppData\Local\Temp\CVRC7CF.tmp.cvr
      sleeping on C:\Users\Jeff\AppData\Local\Temp\CVRD5D3.tmp.cvr
      sleeping on C:\Users\Jeff\AppData\Local\Temp\CVRD8E2.tmp.cvr
      sleeping on C:\Users\Jeff\AppData\Local\Temp\CVRE109.tmp.cvr
      sleeping on C:\Users\Jeff\AppData\Local\Temp\jusched.log
      Ending Foo-A
      ending Foo-B
      ending Foo-C

      which is what I expect. The begin script blocks run in order, the process script blocks run in order and then the end script blocks run in order. Foo-A is a “standard” function without any defined script blocks. I think we’re saying the same thing.

  2. David "Makovec" Moravec says:
    January 19, 2010 at 3:48 am

    I am reading PowerShell in Action again and this morning I accidentaly found this:

    “In a pipeline, the begin clause is run for all cmdlets in in the pipeline. Then the process clause is run for the first cmdlet. … Finally the end clauses are all run.” Sequencing is cover again in chapter 7 but I haven’t it now on my PC so can’t check. But I am sure you have it.

    David

    1. Jeffery Hicks says:
      January 19, 2010 at 7:18 am

      I suppose I should read Bruce’s book again.

Comments are closed.

reports

Powered by Buttondown.

Join me on Mastodon

The PowerShell Practice Primer
Learn PowerShell in a Month of Lunches Fourth edition


Get More PowerShell Books

Other Online Content

github



PluralSightAuthor

Active Directory ADSI Automation Backup Books CIM CLI conferences console Friday Fun FridayFun Function functions Get-WMIObject GitHub hashtable HTML Hyper-V Iron Scripter ISE Measure-Object module modules MrRoboto new-object objects Out-Gridview Pipeline PowerShell PowerShell ISE Profile prompt Registry Regular Expressions remoting SAPIEN ScriptBlock Scripting Techmentor Training VBScript WMI WPF Write-Host xml

©2025 The Lonely Administrator | Powered by SuperbThemes!
%d