169

If I do the following in a PowerShell script:

$range = 1..100
ForEach ($_ in $range) {
    if ($_ % 7 -ne 0 ) { continue; }
    Write-Host "$($_) is a multiple of 7"
}

I get the expected output of:

7 is a multiple of 7
14 is a multiple of 7
21 is a multiple of 7
28 is a multiple of 7
35 is a multiple of 7
42 is a multiple of 7
49 is a multiple of 7
56 is a multiple of 7
63 is a multiple of 7
70 is a multiple of 7
77 is a multiple of 7
84 is a multiple of 7
91 is a multiple of 7
98 is a multiple of 7

However, if I use a pipeline and ForEach-Object, continue seems to break out of the pipeline loop.

1..100 | ForEach-Object {
    if ($_ % 7 -ne 0 ) { continue; }
    Write-Host "$($_) is a multiple of 7"
}

Can I get a continue-like behavior while still doing ForEach-Object, so I don't have to breakup my pipeline?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Justin Dearing
  • 14,270
  • 22
  • 88
  • 161
  • Here is a page with lots of commands to use with `foreach`: http://www.techotopia.com/index.php/Windows_PowerShell_1.0_Looping_with_the_for_and_foreach_Statements#Continuing_for_Loops – bgmCoder Aug 10 '13 at 02:55
  • Found a decent explanation and sample here... http://powershell.com/cs/blogs/tips/archive/2015/04/27/understanding-break-continue-return-and-exit.aspx – Nathan Hartley Aug 25 '16 at 21:55
  • the link is dead and the other links don't really explain this. Could you find it again please? – McVitas Oct 05 '20 at 14:37

4 Answers4

223

Simply use the return instead of the continue. This return returns from the script block which is invoked by ForEach-Object on a particular iteration, thus, it simulates the continue in a loop.

1..100 | ForEach-Object {
    if ($_ % 7 -ne 0 ) { return }
    Write-Host "$($_) is a multiple of 7"
}

There is a gotcha to be kept in mind when refactoring. Sometimes one wants to convert a foreach statement block into a pipeline with a ForEach-Object cmdlet (it even has the alias foreach that helps to make this conversion easy and make mistakes easy, too). All continues should be replaced with return.

P.S.: Unfortunately, it is not that easy to simulate break in ForEach-Object.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Roman Kuzmin
  • 40,627
  • 11
  • 95
  • 117
  • 3
    From what OP is saying apparently `continue` can be used to simulate a `break` in `ForEach-Object` :) – Richard Hauer Jun 19 '15 at 07:56
  • 9
    @ Richard Hauer Such a `continue` will break the whole script, not just `ForEach-Object` where it is used. – Roman Kuzmin Jun 19 '15 at 09:08
  • I voted on this several years ago, yet I keep forgetting this every 8-12 months. ‍♂️Maybe now when I comment on this, it will hopefully stick oo. – not2qubit Jun 10 '23 at 09:36
30

Because For-Each object is a cmdlet and not a loop and continue and break do not apply to it.

For example, if you have:

$b = 1,2,3

foreach($a in $b) {

    $a | foreach { if ($_ -eq 2) {continue;} else {Write-Host $_} }

    Write-Host  "after"
}

You will get output as:

1
after
3
after

It is because the continue gets applied to the outer foreach loop and not the foreach-object cmdlet. In absence of a loop, the outermost level, hence giving you an impression of it acting like break.

So how do you get a continue-like behaviour? One way is Where-Object of course:

1..100 | ?{ $_ % 7  -eq 0} | %{Write-Host $_ is a multiple of 7}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
manojlds
  • 290,304
  • 63
  • 469
  • 417
  • Using the Where-Object cmdlet is a good suggestion. In my actual case, I don't think it makes sense to make the multiple lines of code preceding my if statement into a single long line of hard to read code. However, that would work for me in other situations. – Justin Dearing Oct 13 '11 at 21:07
  • @JustinDearing - `In my actual case, I don't think it makes sense to make the multiple lines of code preceding my if statement into a single long line of hard to read code.` What do you mean? – manojlds Oct 14 '11 at 06:45
  • 4
    @manojlds maybe he thinks that your one line solution is "hard to read", at least for me is completelly the contrary. The pipeline way to do things is really powerfull and clear and is the correct approach for simple things like that. Writing code in the shell without taking advantage of that is pointless. – mjsr Oct 14 '11 at 14:18
  • In my case this was the right answer, add a where condition to filter out the objects that I would be doing a continue or return on so that I don't need to process them in the first place. +1 – Chris Magnuson Feb 15 '15 at 01:59
  • I really like the suggestion of `Where-Object` because it allows you to print an alternate message or log it while still executing the rest of your code so you don't have to loop through a list multiple times, while it sounds like `continue` or `return` immediately drop out of the loop, so if you had a couple conditions you wanted to compare against you would have to build up a gnarly block of combined `if` statements or loop through the list of objects a couple different times. – dragon788 Sep 18 '20 at 14:47
4

A simple else statement makes it work as in:

1..100 | ForEach-Object {
    if ($_ % 7 -ne 0 ) {
        # Do nothing
    } else {
        Write-Host "$($_) is a multiple of 7"
    }
}

Or in a single pipeline:

1..100 | ForEach-Object { if ($_ % 7 -ne 0 ) {} else {Write-Host "$($_) is a multiple of 7"}}

But a more elegant solution is to invert your test and generate output for only your successes

1..100 | ForEach-Object {if ($_ % 7 -eq 0 ) {Write-Host "$($_) is a multiple of 7"}}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Alvin
  • 41
  • 1
3

Another alternative is kind of a hack, but you can wrap your block in a loop that will execute once. That way, continue will have the desired effect:

1..100 | ForEach-Object {
    for ($cont=$true; $cont; $cont=$false) {
        if ($_ % 7 -ne 0 ) { continue; }
        Write-Host "$($_) is a multiple of 7"
    }
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
zdan
  • 28,667
  • 7
  • 60
  • 71
  • 6
    Frankly, that is ugly :) And not just an hack because instead of the foreach-object, you could have as well used a foreach loop. – manojlds Oct 13 '11 at 21:17
  • 1
    @manojlds: 1..100 is for illustration only. do {} while($False) works just as well as for loop and bit more intuitive. – HMartyrossian Nov 17 '17 at 05:55