How to exit from ForEach-Object in PowerShell

I have the following code:

$project.PropertyGroup | Foreach-Object {
    if($_.GetAttribute('Condition').Trim() -eq $propertyGroupConditionName.Trim()) {
        $a = $project.RemoveChild($_);
        Write-Host $_.GetAttribute('Condition')"has been removed.";
    }
};

Question #1: How do I exit from ForEach-Object? I tried using "break" and "continue", but it doesn't work.

Question #2: I found that I can alter the list within a foreach loop... We can't do it like that in C#... Why does PowerShell allow us to do that?


First of all, Foreach-Object is not an actual loop and calling break in it will cancel the whole script rather than skipping to the statement after it.

Conversely, break and continue will work as you expect in an actual foreach loop.

Item #1. Putting a break within the foreach loop does exit the loop, but it does not stop the pipeline. It sounds like you want something like this:

$todo=$project.PropertyGroup 
foreach ($thing in $todo){
    if ($thing -eq 'some_condition'){
        break
    }
}

Item #2. PowerShell lets you modify an array within a foreach loop over that array, but those changes do not take effect until you exit the loop. Try running the code below for an example.

$a=1,2,3
foreach ($value in $a){
  Write-Host $value
}
Write-Host $a

I can't comment on why the authors of PowerShell allowed this, but most other scripting languages (Perl, Python and shell) allow similar constructs.


There are differences between foreach and foreach-object.

A very good description you can find here: MS-ScriptingGuy

For testing in PS, here you have scripts to show the difference.

ForEach-Object:

# Omit 5.
1..10 | ForEach-Object {
if ($_ -eq 5) {return}
# if ($_ -ge 5) {return} # Omit from 5.
Write-Host $_
}
write-host "after1"
# Cancels whole script at 15, "after2" not printed.
11..20 | ForEach-Object {
if ($_ -eq 15) {continue}
Write-Host $_
}
write-host "after2"
# Cancels whole script at 25, "after3" not printed.
21..30 | ForEach-Object {
if ($_ -eq 25) {break}
Write-Host $_
}
write-host "after3"

foreach

# Ends foreach at 5.
foreach ($number1 in (1..10)) {
if ($number1 -eq 5) {break}
Write-Host "$number1"
}
write-host "after1"
# Omit 15. 
foreach ($number2 in (11..20)) {
if ($number2 -eq 15) {continue}
Write-Host "$number2"
}
write-host "after2"
# Cancels whole script at 25, "after3" not printed.
foreach ($number3 in (21..30)) {
if ($number3 -eq 25) {return}
Write-Host "$number3"
}
write-host "after3"

To stop the pipeline of which ForEach-Object is part just use the statement continue inside the script block under ForEach-Object. continue behaves differently when you use it in foreach(...) {...} and in ForEach-Object {...} and this is why it's possible. If you want to carry on producing objects in the pipeline discarding some of the original objects, then the best way to do it is to filter out using Where-Object.


Since ForEach-Object is a cmdlet, break and continue will behave differently here than with the foreach keyword. Both will stop the loop but will also terminate the entire script:

break:

0..3 | foreach {
    if ($_ -eq 2) { break }
    $_
}
echo "Never printed"

# OUTPUT:
# 0
# 1

continue:

0..3 | foreach {
    if ($_ -eq 2) { continue }
    $_
}
echo "Never printed"

# OUTPUT:
# 0
# 1

So far, I have not found a "good" way to break a foreach script block without breaking the script, except "abusing" exceptions, although powershell core uses this approach:

throw:

class CustomStopUpstreamException : Exception {}
try {
    0..3 | foreach {
        if ($_ -eq 2) { throw [CustomStopUpstreamException]::new() }
        $_
    }
} catch [CustomStopUpstreamException] { }
echo "End"

# OUTPUT:
# 0
# 1
# End

The alternative (which is not always possible) would be to use the foreach keyword:

foreach:

foreach ($_ in (0..3)) {
    if ($_ -eq 2) { break }
    $_
}
echo "End"

# OUTPUT:
# 0
# 1
# End