locked
Returning from a Scriptblock on a condition RRS feed

  • Question

  • Hello,

    Consider the below contrived PowerShell example. I want to return from a scriptblock code on a condition.

    $MyScriptBlock = {
                If($True){
                            1..5 | ForEach-Object {
                                        If($_ -eq 2){ #I want the script block to return here. But it doesn't.
                                            "Return"
                                            Return;
                                         }
                                  }
                          }
                 }
    
    1..5 | ForEach-Object {
                               Get-Process| Select-Object  ProcessName,  
                                                          @{Name = 'Test Value';Expression = { & $MyScriptBlock}}
                           }

    But it does not seem to work. How do I achieve it?

    Thanks in advance

    Steve

    Thursday, April 12, 2018 7:35 AM

Answers

  • As far as I know ForEach-Object processes each item in the pipeline, but the foreach statement processes the collection as a whole which would explain this behavior.
    • Marked as answer by Steve DSouza Thursday, April 12, 2018 10:01 AM
    Thursday, April 12, 2018 9:57 AM

All replies

  • Hello,

    It does work but the condition if($true) is always met as well as if($_ -eq 2) in a loop from 1 to 5. So, it will return "return" in any case.

    Your script works as expected.

    Thursday, April 12, 2018 8:05 AM
  • Hi TobyU,

    If($true) is an outer condition. I enter it unconditionally and then enter into 1..5 | foreach loop. Here:

    I want the script to return from the whole scriptblock when $_ -eq 2 but even when/after that condition is met, the loop continues till 5 and then returns "return". I have been able to reproduce this behaviour through step-by-step debugging. If I use the Exit or Break keyword instead of Return, it abruptly stops the script at $_ -eq 2  altogether.

    I want the iteration to stop at $_ -eq 2 and exit from the scriptblock with "return" and continue with the next object in the Get-Process pipeline in the caller.







    Thursday, April 12, 2018 8:12 AM
  • If I add the $_ to "Return $_", I get "Return 2" all the time. The scriptblock does stop at value 2 and returns as expected.

    What is it what you finally want to achieve?

    Thursday, April 12, 2018 8:27 AM
  • Whatever I was trying to achieve, I have now managed to do it through Foreach loop construct as opposed to Foreach-Object cmdlet.

    I may be completely off the ball on this one but something tells me Return statement does not work as expected when it's inside a Foreach-Object cmdlet where as it does work correctly when it's inside the ordinary Foreach loop which does not use pipeline.

    Consider the below modified script in both foreach and foreach-object flavour... which functionally are expected to behave and output the same however the results are different:

    $MyScriptBlock1 = {
                If($True){
                              1..3 | ForEach-Object {
                                        If( $_-eq 2){ 
                                            "returnValue";
                                             Return;     #Does not Work as expected
                                         }
                                  }
                          }
                "Hello1"
    }
    
    $MyScriptBlock2 = {
                If($True){
                              foreach($obj in (1..3)){
                                        If( $obj -eq 2){
                                            "returnValue";
                                             Return;      #Works as expected
                                         }
                                  }
                          }
                    "Hello2"
    }
    
    1..2 | Select-Object  @{Name = 'Input';Expression = {$_}}, @{Name = 'SB Value';Expression = {& $MyScriptBlock1 }} #Does not Work as expected
    1..2 | Select-Object  @{Name = 'Input';Expression = {$_}}, @{Name = 'SB Value';Expression = {& $MyScriptBlock2 }} #Does  Work as expected

    As per the execution logic, with return in the middle of the code, the "Hello1" or "Hello2" should never be output. In the case of $MyScriptBlock2 with Foreach loop, it works as expected and no Hello2 is in the result where as in the $MyScriptBlock1 with Foreach-Object cmdlet, Hello1 is in the results.

    Below is the result on my machine:

    Input SB Value             
    ----- --------             
        1 {returnValue, Hello1}
        2 {returnValue, Hello1}
        1 returnValue          
        2 returnValue         

    Could you tell me if I have got it completely wrong?




    Thursday, April 12, 2018 9:39 AM
  • The code makes very little sense and seems to be a hack.

    This is the same thing without all of the nonsense.

    $MyScriptBlock = {
    1..5 | 
        ForEach-Object {
            if($_ -eq 2){return "Return $_"}
        }
    }
    
    Get-Process |
            Select-Object  ProcessName, @{n = 'Test Value';e = {$MyScriptBlock.Invoke()}}

    Taking the time to learn PowerShell will prevent getting lost in this kind of a black hole.


    \_(ツ)_/




    • Edited by jrv Thursday, April 12, 2018 9:53 AM
    Thursday, April 12, 2018 9:51 AM
  • JrV

    Besides your gratuitous snide remarks, your reply does not answer my question.


    Thursday, April 12, 2018 9:55 AM
  • As far as I know ForEach-Object processes each item in the pipeline, but the foreach statement processes the collection as a whole which would explain this behavior.
    • Marked as answer by Steve DSouza Thursday, April 12, 2018 10:01 AM
    Thursday, April 12, 2018 9:57 AM
  • JrV

    Besides your gratuitous snide remarks, that does not answer my question.

    It exactly answers your question. You asked to return from the loop in the script block.  This does exactly that.  If you cannot understand that then there is an issue that we cannot solve for you.

    If you have a real script that you have written and it has a real purpose then maybe your question will make more sense.


    \_(ツ)_/

    Thursday, April 12, 2018 9:58 AM
  • As far as I know ForEach-Object processes each item in the pipeline, but the foreach statement processes the collection as a whole which would explain this behavior.

    No.  Both work the same.  When the value is detected the loop ends and returns.  The loop ALWAYS returns 2.  What the OP is asking apparently is not what the question says.  A real example could resolve this.


    \_(ツ)_/

    • Proposed as answer by jrv Thursday, April 12, 2018 10:01 AM
    Thursday, April 12, 2018 10:00 AM
  • TobyU

    This is exactly what I was looking for. Thank you.

    The original script I am writing is too involved (with AD cmdlets, Eventlog etc.) which I felt is too chaotic to explain here hence I simplified it with the above bare-bones code.

    Thanks for your time.


    Thursday, April 12, 2018 10:01 AM
  • A bail out.  The question remains a mystery. 

    My remarks were intended to coax you to take the time to actually learn PowerShell instead of just guessing and copying.  It will save you time an frustration.


    \_(ツ)_/

    Thursday, April 12, 2018 10:03 AM
  • Jrv,

    There are more than one ways to skin (swing?) a cat and you may feel my way of solving the problem is inferior but that is totally beside the point here.

    I came across this odd behaviour in the script I was writing so I wanted to focus on this concept as to why return was behaving differently (between foreach loop and foreach-object cmdlet). For that reason I wrote a representative code above. It's not intended to be clever trick but on its own, it's still a valid piece of PowerShell code nonetheless and I wanted to know why it was behaving this way (so I can avoid such a pitfalls in the future) but you needlessly go on making assumptions that the poster is copying code from elsewhere. What made you come to that conclusion? 


    Thursday, April 12, 2018 10:12 AM
  • That is my point.  There is no odd behavior.  The loop terminates when the value is detected. 

    "foreach" does not process the collection as a whole which is exactly what "foreach" means.  All languages use this and all behave the same way.  A foreach loop terminates when a return or break statement is executed.


    \_(ツ)_/


    • Edited by jrv Thursday, April 12, 2018 10:18 AM
    Thursday, April 12, 2018 10:14 AM
  • This link could be interesting: https://blogs.technet.microsoft.com/heyscriptingguy/2014/07/08/getting-to-know-foreach-and-foreach-object/

    Yes. I am aware of that but it has nothing to say about the behavior of "foreach/ForEAch-Object" when a "return" is encountered.


    \_(ツ)_/

    Thursday, April 12, 2018 10:25 AM
  • Now I have removed all the "fluff" from my previous code to only focus on the point of this post;

    $MyScriptBlock1 = {
                         1..3 | ForEach-Object {
                                        $_;
                                        If($_-eq 2){ 
                                             Return;     #Does not Work as expected
                                         }
                                  }
                "Hello1"
    }
    
    $MyScriptBlock2 = {
                        foreach($obj in (1..3)){
                                        $obj;
                                        If( $obj -eq 2){
                                             Return;      #Works as expected
                                         }
                                  }
                    "Hello2"
    }
    
    & $MyScriptBlock1  #Does not Work as expected
    & $MyScriptBlock2  #Works as expected

    Output:

    PS H:\> & $MyScriptBlock1 
    1
    2
    3
    Hello1
    
    PS H:\> & $MyScriptBlock2
    1
    2

    Thursday, April 12, 2018 11:44 AM
  • This would actually be a better construct and it avoids the deficiency in the use of ForEach-Object in a scriptblock.

    $MyScriptBlock1 = {
    if($x = 1..3 | Where-Object { $_ -eq 2 }){
          return $x
    }
    'Hello1'
    }
    $MyScriptBlock1.Invoke()

    Now that you have cleaned up the code the issue is clear.  A pipeline can only use "break".   There are other methods but they are a bit tricky to implement.


    \_(ツ)_/



    • Edited by jrv Thursday, April 12, 2018 12:11 PM
    Thursday, April 12, 2018 12:06 PM
  • And yet another way to bypass the anomaly:

    $MyScriptBlock1 = {
    :test while(1){
        1..3 | ForEach-Object { if($_ -eq 2){$_;break test}}
        'Hello1'
    }
    }
    $MyScriptBlock1.Invoke()


    \_(ツ)_/

    Thursday, April 12, 2018 12:22 PM