Hey! Don’t *break* my pipe!

I’m still pretty overwhelmed with number of scripts this year in Scripting Games. While I’m on my “Mission Impossible” I noticed extensive use of break keyword. I commented on it a lot in Beginner 3, but would like to show you why I do not like this as a way of ending your functions and/ or scripts.

I mean, it’s perfectly fine if you use it with what I will show you today in mind, that is: if you want your function/ script to be destructive and break things when you see an error. But if your function/ script is only getting some info from my system and I won’t get full information (like in Advanced Event 2) without admin rights – why do you break things? Isn’t exit/ return enough in such a case…? Smile

Why it matters?

For me – break is mainly looping construct. It makes sense there, because it will let you specify label for loop that you want to leave to. Meaning: if you have several nested loops and you want to get far out rather than leave current one – break makes perfect sense. If my script will however run another script or function that finishes with break – I’m screwed. Literally: I asked this other guy to check some information, he decided I’m not worthy and killed me in a blink of an eye. Ouch!

Let see it in action.

Function of destruction

Let’s consider two very similar functions: one that breaks things, and another one, that just leaves early if some condition is met. I do not include any condition here, so you have to use your imagination. Winking smile

function Test-Break {            
param (            
    [Parameter(            
        ValueFromPipeline = $true            
    )]            
    [string]$Foo            
)            
process {            
    $Foo            
    break            
    "Not going to happen..."            
}            
}            
            
function Test-Return {            
param (            
    [Parameter(            
        ValueFromPipeline = $true            
    )]            
    [string]$Bar            
)            
process {            
    $Bar            
    return            
    "Not going to happen..."            
}            
}            

OK, we have our two suspects. Now let’s launch some pipeline. As you see – both function support using in a pipe… First, let’s test return:

echo One, Two, Three | Test-Return            

Our results come back as expected. Result of code that should not run are not there… Now let’s do the same with other guy:

echo One, Two, Three | Test-Break            

See difference? But that’s kind of OK: I’ve used your function as the only pipeline consumer, you broke it for me – who cares. But what happens if I put your function as yet another chain element?

echo One, Two, Three | Test-Return | Test-Break            

So you broke my Test-Return too with your code. I hope it was not on purpose? Winking smile But I do not need a pipe for that – if I run your function within mine – it will break too…

Script killers

Scripts are as vulnerable to this as functions: if script runs another script that ends with break – it will simply stop there. You can test it easily with three simple scripts created on the fly with this code:

@'
break
"I did not get here..."
'@ | Out-File BreakMe.ps1            
            
@'
exit 1
"I did not get here..."
'@ | Out-File DoNotBreakMe.ps1            
            
@'
"Start..."
.\DoNotBreakMe.ps1
if (!$?) {
    "Some errors in DoNotBreakMe..."
}
"My script is polite, does not break things. But yours...:"
.\BreakMe.ps1
"That won't run, because you BROKE my script! :("
'@ | Out-File TestBreaking.ps1            

Run TestBreaking and look at results. As you can see – script flow will stop after BreakMe script. This code shows another advantage of using exit over break: it gives you option to let caller know, that something bad happened (exit 0 –> good, exit 1+ –> bad). Our functions will do the same: if we run them in context of script, it will listen. You tell it to break – it will… Try to run this code and the script it creates and see for yourself:

@'
"Functions will break script too. Not when they politely return:"
Test-Return -Bar 'Foo'
"But when you tell them to break stuff..."
Test-Break -Foo 'Bar'
"Again, I won't get here... :("
'@ | Out-File TestFunBreak.ps1            

Same applies to breaking pipe: if your script supports it and exits with break – pipeline will stop, if not – it will just continue to work:

@'
param (
    [Parameter(
        ValueFromPipeline = $true
    )]
    [string]$Piped
)
process {
    $Piped
    break
}
'@ | Out-File BreakPipe.ps1            
echo One, Two, Three | .\BreakPipe.ps1

But here exit is as destructive as break: you need to use return if you want to keep things flowing thru your script.

Unbreakable script or how it works…?

The reason why break works as we see here is the fact, that break will go out of boundaries of current caller and look for loop that it could break. We can use it both ways: to avoid being killed by code we do not have control over, or to have a script/ function that will be capable of getting out of loop in totally different script/ function. Examples will make that more clear. First: our unbreakable script:

@'
"Try to break me now...!"
do {
    .\BreakMe.ps1
} until ($true)
"HA! You won't stop me NOW!"
'@ | Out-File Unbreakable.ps1            

Break in BreakMe will break do {} until – and we could not care less: it would stop anyways after first run because of used condition. Winking smile

Next: when it makes sense. Let’s create smarter break-script that will use label to break loop. Next we will see how both BreakMe and BreakTopMost will work together for our cause:

@'
break TopMost
'@ | Out-File BreakTopMost.ps1            
            
@'
"We have few loops..."
:TopMost do {
    "Inner 'do'"
    do {
        .\BreakMe.ps1
    } until ($false)
    "Old script will get out of first do..."
    do {
        .\BreakTopMost.ps1
    } until ($false)
} until ($false)
"And got here, although no break in TopMost loop..."
'@ | Out-File BreakBreakBreak.ps1            

Run BreakBreakBreak – see? Our BreakTopMost looked for :TopMost label, got out of that loop and made it possible to reach end of our script. But that’s on purpose: kind of feel that’s not the case with breaking scripts/ function I’ve seen in Scripting Games… Thus – this post. Smile

1 thought on “Hey! Don’t *break* my pipe!

  1. Pingback: Event 4: My notes… | IT Pro PowerShell experience

Leave a comment