How to handle Errors in PROCESS{} block

This topic contains 19 replies, has 4 voices, and was last updated by Profile photo of GS GS 1 year, 8 months ago.

  • Author
    Posts
  • #24551
    Profile photo of GS
    GS
    Participant

    Hello,

    I'm having hard time understanding how do I make process{} block inside CmdLet to continue processing the rest of the pipeline after Error is being thrown inside the block

    Below is my very simple code. Error is thrown in New-AdUser cmdlet then processed inside Catch{} as expected but second value in pipeline is never processed after that, entire cmdlet stops.

    PROCESS {
    try
    {
    New-ADUser -Credential $cred -Server $server -Name aaa
    }
    catch
    {
    Write-Error $error[0]
    }
    }

  • #24553
    Profile photo of Don Jones
    Don Jones
    Keymaster

    So, PROCESS is only used when the command is run using pipeline input. In that case, objects are bound to input parameters one at a time, and PROCESS is executed.

    If you just run the script straight, meaning with no pipeline input, then PROCESS is ignored.

  • #24555
    Profile photo of GS
    GS
    Participant

    Yes, that part is working as expected. Issue happens when there is an error thrown in PROCESS {} block. Entire cmdlet terminates without processing anything else in pipeline.

  • #24557
    Profile photo of Don Jones
    Don Jones
    Keymaster

    Well, I'd probably need to see your parameter block. It sounds like it isn't actually in pipeline mode. Otherwise, it should fail through to the Catch, and then re-do the PROCESS. But if your script isn't set up right, it won't.

  • #24560
    Profile photo of GS
    GS
    Participant

    Here is the script and output

    Set-StrictMode -Version Latest
    $ErrorActionPreference = "stop"
    Function Test
    {
    [CmdletBinding()]
    param(
    [Parameter(ValueFromPipeline=$true)]
    [string[]]$filter
    )
    BEGIN {
    trap {Write-Error $_}
    }
    PROCESS {
    trap
    {
    Write-Error $_
    Continue
    }
    Write-Output $_
    1/0
    }
    END {

    }
    }

    Here is output

    PS C:\Users\g\Documents> "aa", "dd" | Test
    aa
    Attempted to divide by zero.
    At :line:1 char:14
    + "aa", "dd" | T < <<< est

  • #24562
    Profile photo of GS
    GS
    Participant

    This seems to be something to do with $ErrorActionPreference="stop"
    I assumed as long as error is handled (which it is in trap{} statement) then execution will continue for next pipeline object.

  • #24606
    Profile photo of Don Jones
    Don Jones
    Keymaster

    Oy. Yeah, setting the $ErrorActionPreference globally is a Bad Practice.

    You're also mixing techniques, and I think that might be confusing you. Trap{} isn't the same as Try/Catch, and you probably shouldn't be using Trap{}. Trap changes the flow of execution. Your original example didn't use Trap, it used Try/Catch. What are we troubleshooting?

  • #24610
    Profile photo of GS
    GS
    Participant

    I want to capture both terminating and not-terminating errors in PROCESS{} block, rethrow them and continue to next object in pipeline. It's not working either with trap{} or try{}catch{}

  • #24611
    Profile photo of Don Jones
    Don Jones
    Keymaster

    Well...

    You can't catch a non-terminating error. That's why it's called non-terminating.

    What you can do is use -ErrorAction to turn non-terminating errors into terminating exceptions. The trick is, you need to have your command working with only one thing at a time, so that if it throws an exception, you can catch it, and then loop back up.

    For example:

    Get-WmiObject -computername $computers -erroraction stop

    Bad idea, because if $computers contains multiple computer names, any failure will abort the whole command.

    Better:

    foreach ($computer in $computers) {
      Get-WmiObject -computername $computer -erroraction stop
    }
    

    Now, a failure only affects one machine. I can catch that, and allow the ForEach to loop back and get the next one:

    foreach ($computer in $computers) {
      try {
        Get-WmiObject -computername $computer -erroraction stop
      } catch {
        # oops
      }
    }
    

    A PROCESS block does not explicitly enumerate unless it's taking pipeline input. Often, you combine it with a ForEach.

    Param(
      [Parameter(ValueFromPipeline=$True)][string[]]$computers
    )
    PROCESS {
    foreach ($computer in $computers) {
      try {
        Get-WmiObject -computername $computer -erroraction stop
      } catch {
        # oops
      }
    }
    }
    

    Would work if you piped in strings, or manually specified one or more computer names by using the -computers parameter. You'll Catch each error individually as they happen, while still correctly processing any non-failing computers.

    But you can't just "rethrow" an exception and continue. Tossing an exception means the [i]next scope up[/i] has to do something about it. If you toss an exception in your code, it's game over for that scope. Exceptions are [i]terminating[/i] by definition. They're the only thing you can catch, and if you throw one, something else either has to catch what you threw, or abandon running you.

    Does that clarify anything a bit for you?

  • #24654
    Profile photo of GS
    GS
    Participant

    Yes, thanks that explains it and it works as you explained with try{} catch{} scenario. This does not work though with trap{} statement. Every error being thrown twice. Example is code below.

    Function Test{
    Param(
    [Parameter(ValueFromPipeline)]$x
    )
    Begin{}
    Process{
    trap
    {
    Write-Error $_
    }
    Write-Output "Processing $x"
    10/$x

    }
    End{}
    }
    1,2,3,0,4,5,0,6,7,8|Test

  • #24656
    Profile photo of Don Jones
    Don Jones
    Keymaster

    Trap was a v1 thing and you really shouldn't be using it. It changes the flow of execution, and behaves entirely differently from Try/Catch. Trap is only there for backward compatibility.

  • #24659
    Profile photo of Ana Sofia
    Ana Sofia
    Participant

    So, PROCESS is only used when the command is run using pipeline input. In that case, objects are bound to input parameters one at a time, and PROCESS is executed.

    If you just run the script straight, meaning with no pipeline input, then PROCESS is ignored.

    Begin, Process and End script blocks will always run, regardless of whether the function is being called from the pipeline or not. However, if being invoked from a pipeline (and only to the right of the pipeline, not as the first function to be called in a pipeline), Process will be called as many times as the number of objects passed to it via the pipeline, and End will only be called once there are no more objects left and the function is exiting.

    Here's a quick example that shows that:

    function T
    {
        Begin{ 'Begin'}
        Process{'Process'}
        End{'End'}
    }
    
    # Not being called inside a pipeline
    T
    # Output:
    # Begin
    # Process
    # End
    
    #Calling within pipeline
    1..5 | T
    # Output:
    # Begin
    # Process
    # Process
    # Process
    # Process
    # Process
    # End

    Same thing with an advanced function:

    function T
    {
        [CmdletBinding()]
        Param
        (
            [Parameter(ValueFromPipeline = $true)]
            $x
        )
        Begin{ 'Begin'}
        Process{"Process$x"}
        End{'End'}
    }
    
    T -X 1
    
    1..5 | T
  • #24660
    Profile photo of Ana Sofia
    Ana Sofia
    Participant
  • #24661
    Profile photo of GS
    GS
    Participant

    People keep saying that trap{} is on chop block etc since v1 and yet v5 is out and still supports it and not documented that it will be removed etc. Where all this information is coming from? Is there an offical Microsoft position on deprecating using trap{}
    I like the way code looks like with all exception being handled in single place and lack of curly braces etc through the code and extra try{}catch{} statements everywhere. I understand there is drawbacks to using it but unless I know for sure it's going to be removed in later versions of Powershell I prefer to use it.

  • #24662
    Profile photo of Don Jones
    Don Jones
    Keymaster

    I was perhaps not as syntactically clear as I could have been.

    When NOT run from the pipeline, [i]the BEGIN, PROCESS, and END [b]keywords[/b] are ignored.[/i] The code WITHIN THEM runs as if those three keywords did not exist, meaning the code just runs straight-through. This three keywords only have special meaning when the function is run with pipeline input.

    Trap isn't on a "chop block;" it'll likely remain for backward compatibility. It isn't "deprecated." It just isn't a very good construct. It's what they were able to complete in time to ship v1, and when v2 came out, they added the Try/Catch that they'd always intended. It's not as flexible as Try/Catch, and it doesn't promote good structural error handling. You're going to have a more difficult time using it. Much of its eccentricities aren't well documented. "Deprecated" means that Microsoft has plans to remove it; they don't. In general, though, the PowerShell team tends to never remove things, for fear of creating a breaking change, which is a Big Deal. Just because it remains in the product, however, doesn't mean they're especially proud of it.

    For example, Trap{} doesn't create a scope (neither do Try/Catch, but it's more obvious that they don't because of how they're built). If you throw an error – including Write-Error – inside a Trap, then you'll leave the current scope, tossing an error. Trap has interactions with Break/Continue, and Trap makes it much more important to keep track of the scope you're in. It isn't [i]documented[/i] anywhere that Trap is very-not-preferred, but I'm assuring you that you should be using Try/Catch. If you need me to go get Snover or someone to back me up on that, I can.

    Trap [i]seems[/i] like it provides a nice central place to handle exceptions, but it has a lot of caveats and under-the-hood oddities that make it much less predictable, much harder to debug, and much harder to follow. I mean, nobody's going to fire you because you're using it, but you're not going to find a lot of people willing to help you figure out why it isn't working, either. If you want, I can get a bunch of MVPs in here to tell you that you should be using Try/Catch, but if you just have a thing for Trap, there isn't seem to be any point.

  • #24663
    Profile photo of Fausto Nascimento
    Fausto Nascimento
    Participant

    People keep saying that trap{} is on chop block etc since v1 and yet v5 is out and still supports it and not documented that it will be removed etc. Where all this information is coming from? Is there an offical Microsoft position on deprecating using trap{}
    I like the way code looks like with all exception being handled in single place and lack of curly braces etc through the code and extra try{}catch{} statements everywhere. I understand there is drawbacks to using it but unless I know for sure it's going to be removed in later versions of Powershell I prefer to use it.

    A try catch is a lot more powerful and easy to use than a trap.

    The reason why with a tray catch you have 'statement everywhere' with 'extra curly braces' is because you have the ability to control exactly what you want to catch if an error occurs. The only extra set of curly braces come from the try itself, where you specify what you want to try, not from the catch. With a trap, you can't easily specify exactly what you want to catch.

    Also remember a try..catch is NOT just used to catch exceptions. You could have a try..finally, a try..catch..finally... all things you can't do with a trap. Finally executes a scriptblock regardless of whether an error occurred or not on what's inside the try scriptblock. Why is this useful?

    Consider the following scenario:

    I want to load the Excel COM object and perform some operations on an excel spreadsheet, so there are 4 things I need to do so far:

    1. Load Excel COM object
    2. Perform changes to Excel spreadsheet
    3. Save changes
    4. Unload COM object

    Now I know part of my code may generate errors, which I want to catch, so I place it around a try..catch as follows:

    try
    {
       ## Load Excel COM object code...
    
       ## Perform changes to Excel spreadsheet...
    
       ## Save changes...
    
       ## Unload COM object
    }
    catch
    {
       ## do something with errors
    }

    The problem with this approach though, is that the moment an error is found, it will immediately jump to the catch script block. So, if I have an error performing changes to the Excel spreadsheet, it will never unload the COM object.

    I could do this...

    try
    {
       ## Load Excel COM object code...
    
       ## Perform changes to Excel spreadsheet...
    
       ## Save changes...
    
       ## Unload COM object
    }
    catch
    {
       ## do something with errors
    
      ## Unload COM object
    }

    That way, if an error is not found throughout the execution of the whole try script block it will just unload it when it gets to the end of the try script block... and if an error is found, it will unload it as part of the catch script block.

    But... what if the code required to unload my COM object was 100 lines of code? It makes no sense to repeat those 100 lines of code both on the try script block and the catch script block... And this is where the Finally comes in:

    try
    {
       ## Load Excel COM object code...
    
       ## Perform changes to Excel spreadsheet...
    
       ## Save changes...
    
       ## Unload COM object
    }
    catch
    {
       ## do something with errors
    }

    The problem with this approach though, is that the moment an error is found, it will immediately jump to the catch script block. So, if I have an error performing changes to the Excel spreadsheet, it will never unload the COM object.

    I could do this...

    try
    {
       ## Load Excel COM object code...
    
       ## Perform changes to Excel spreadsheet...
    
       ## Save changes...
    }
    catch
    {
       ## do something with errors
    }[
    finally
    {
       ## Unload COM object
    }

    This way, regardless of whether an error is thrown or not, the last thing it does before exiting the whole try..catch..finally statement is execute the code on the finally script block.

    So now is the time you say... well, but I could just do without the finally and put my code to unload the COM object after the try..catch altogether, as such:

    try
    {
       ## Load Excel COM object code...
    
       ## Perform changes to Excel spreadsheet...
    
       ## Save changes...
    }
    catch
    {
       ## do something with errors
    }
    
    ## Unload COM object
    

    Sure, in this short example it would work. But there are two "problems" with this:

    Your try..catch.. structure has *everything* about the COM object. As in... the COM object is created inside the try..catch... the COM object is used inside the try..catch... and when the try..catch finishes, everything that was created inside it should cease to exist, not to pass the job of 'cleaning up' to something outside the try..catch. The idea is that the try..catch is a self contained structure.

    Also, another massive advantage is this: if you have a try..catch..finally and inside your catch you have a return/break/continue/whatever, then when an error is caught whatever is after the try..catch..finally structure is NOT run, because you told it to exit the function/script/loop/whatever inside the catch. But... the finally statement will ALWAYS be run, even if there is something commanding it to exit on an error.

    Here's a quick example:

    function test
    {
        param
        (
            [Parameter(ValueFromPipeline = $true)]
            $name 
        )
        process{
            try
            {
                "getting service $name"
                get-service $name -ErrorAction Stop
            }
            catch
            {
                Write-Error "oops, error!"
                return
            }
            finally
            {
                "I always get executed, even if there's an error AND if there's a return on the catch"
            }
    
            "I only get executed when there are no errors (because of the return on the catch). If there was no return on the catch, I'd always get executed too"
        }
    }
    
    'spooler', 'asd' | test

    These are all things that you either can't do with a trap, or you'll really struggle. As Don Jones said, try..catch is the intended way. PowerShell is first and foremost a .NET language, and that's what .NET also uses.

  • #24664
    Profile photo of GS
    GS
    Participant

    Wondering if you specify value from pipeline as [string[]]$x then when you feed in it fact from pipeline it comes as string[] with just 1 element instead of coming just as string
    So how do you do proper processing inside Process{} block without specifically knowing how it was instantiated through pipeline or not?
    Do I just enumerate inside process block $x variable even though it has single element?

    Function Test{
    Param(
    [Parameter(ValueFromPipeline)]
    [String[]]$x
    )
    Begin{}
    Process{
    Write-Host $x.GetType().Name " " $x.Length.ToString()
    }
    End{}
    }
    "1","2"|Test
    test -x "1", "2"

  • #24666
    Profile photo of Don Jones
    Don Jones
    Keymaster

    GS, that's because it's unusual to set up a function to ONLY accept pipeline input. E.g:

    1,2,3 | My-Function

    You only need to declare [int]. But:

    My-Function -Param 1,2,3

    You need [int[]]. So declaring it as an array accepts both scenarios. That's also why I use both PROCESS and ForEach. The ForEach is redundant in pipeline mode, but it's necessary in parameter mode.

  • #24667
    Profile photo of Fausto Nascimento
    Fausto Nascimento
    Participant

    GS, as you said, a string[] with one element is still a string[], so you just cycle through all of the elements inside it. If there are none, it will perform 0 times. If there's 1, it will perform the code 1 time. If there's X it will perform it X times.

    Consider this:

    1 | Write-Host

    It's not an array, but still gets executed once.

    1, 2 | Write-Host

    It is an array, and the pipeline really doesn't care, it works both ways.

    Same for a foreach...

    $strings = [string[]] "hello"
    $strings.Gettype() #it says it's a string[]
    foreach ($string in $strings) {write-Host $string}

    So inside your function, you'd just cycle through them in exactly the same way, either a foreach of by using the pipeline.

  • #24668
    Profile photo of GS
    GS
    Participant

    I guess I missed the point of understanding that PROCESS{} is not actually run everytime both in Pipeline and -Parameter mode. I thought powershell chops incoming input and submits it to PROCESS one at a time in both cases. I see now it's wrong.

You must be logged in to reply to this topic.