none
Stopping Module on error without breaking calling script

    Question

  • Hi

    What is the "best practice" to stop your PowerShell module on error but still respect the calling script $ErrorActionPreference?

    For example:

    Module "Test-Error.psm1":

    function Test-Error
    {
        [CmdletBinding()]
        param
        (
        )
        $ErrorActionPreference = 'Stop'
        do
        {
            'Start'
    
            #Write-Error 'Error Message' #-ErrorAction Stop
            Start-Process -FilePath nofile.exe
    
            #Return
            'DO NOT RAN THIS ON ERROR (End Module internal loop)'
        } while (1 -eq 1)
       'DO NOT RAN THIS ON ERROR (End Module external loop)' 
    }

    User script "Run.ps1":

    $ErrorActionPreference = 'Inquire'
    Test-Error
    'RUN THIS (Not part of the module)'

    This will stop the module execution on error as needed, but also stop the "user script" Run.ps1 overriding his ErrorActionPreference  so 'RUN THIS (Not part of the module)' will not run which is not desirable.

    Can I use "$ErrorActionPreference = 'Stop'" or "-ErrorAction Stop" inside the module without braking the user script? separating the user script and the module "Scope" in some way maybe?
    Or do I have to use "try...catch" and "Return" combination instead?

    Thank you


    • Edited by ili101 Tuesday, November 29, 2016 4:44 PM
    Tuesday, November 29, 2016 4:43 PM

Answers

  • Just to update this old topic with a "Solution"

    A few months ago an Issues was open on the PowerShell GitHub that provides a good explanation and some fixes suggestions.

    Our Error Handling, Ourselves - time to fully understand and properly document PowerShell's error handling

    In short it describe the differences between the 3 PowerShell error types (Yes 3 types not 2)

    • Non-terminating errors (Write-Error)
    • Script-terminating errors (Cmdlets, .NET exceptions)
    • Statement-terminating errors (Throw, Write-Error 'With' -ErrorAction Stop)

    Also see the section of "Reporting custom errors in functions and scripts" and the $PSCmdlet.ThrowTerminatingError() option.

    • Marked as answer by ili101 Thursday, May 17, 2018 3:57 PM
    Thursday, May 17, 2018 3:56 PM

All replies

  • ErrorActionPref = Stop only works on the currently executing command. It will stop the command and go to the next line always.

    See: help about_try_catch


    \_(ツ)_/

    Tuesday, November 29, 2016 6:27 PM
    Moderator
  • ErrorActionPref = Stop only works on the currently executing command. It will stop the command and go to the next line always.

    See: help about_try_catch


    \_(ツ)_/

    I'm not understanding what you are trying to say

    about_Try_Catch_Finally has noting to do with $ErrorActionPreference that's in about_Preference_Variables, and anyway it's not answering my question.

    Tuesday, November 29, 2016 7:37 PM
  • I am saying that what you are asking cannot be done as asked.  You need to use Try/Catch to catch an exception and take the steps you need to take.

    First learn how to use PowerShell and how errors and exceptions are managed in code.  Until you understand that you will not be able to understand my answer.

    Sorry.


    \_(ツ)_/

    Tuesday, November 29, 2016 7:42 PM
    Moderator
  • I am saying that what you are asking cannot be done as asked.  You need to use Try/Catch to catch an exception and take the steps you need to take.

    First learn how to use PowerShell and how errors and exceptions are managed in code.  Until you understand that you will not be able to understand my answer.

    Sorry.


    \_(ツ)_/

    Your previous answer is just wrong, and if not please explain why.
    "$ErrorActionPreference = 'Stop'" will not "stop the command and go to the next line always" it will stop the script execution as it will treat any non-terminating error as terminating error and terminate.
    "-ErrorAction Stop" will override $ErrorActionPreference for the specific cmdlet/Function

    So you say that in order to properly address this the only option is to use "Try/Catch" and "Return" like this?

    function Test-Error
    {
        [CmdletBinding()]
        param
        (
        )
        do
        {
            'Start'
            try
            {
                Start-Process -FilePath nofile.exe
            }
            catch
            {
                Write-Error "Error Message was $_"
                Return 
            }
            'DO NOT RAN THIS ON ERROR (End Module internal loop)'
        } while (1 -eq 1)
        'DO NOT RAN THIS ON ERROR (End Module external loop)' 
    }



    • Edited by ili101 Tuesday, November 29, 2016 10:37 PM
    Tuesday, November 29, 2016 10:36 PM
  • Not how it works.  It will terminate each command and move to the next command. 

    Try/Catch will let you catch the error and terminate to the "Catch" block where you can decide what to do next.  If you want to terminate the script in the "Catch" block then use "exit" and PowerShell session will exit.

    $ErrorActionPreference just changes the default form "Continue" to "Stop" for all CmdLets.

    Believe me. It will not terminate a script.

    Using Try/Catch in your example function is the way to skip the remainder of the code in the "Try" block.


    \_(ツ)_/

    Tuesday, November 29, 2016 10:45 PM
    Moderator
  • A little food for thought.

    Exception handling has been around for 30+ years.  See this.

    Most languages have full time exception handling.  PowerShell has it turned off by default by using "Continue".  It is really not turned off but is handled by displaying the exception and adding it to the error stack "$error"

    Here is the reference for Try/Catch/Finally - https://en.wikipedia.org/wiki/Exception_handling_syntax including a lsit of the langugaes that use it.

    Its behavior is nearly identical in  all languages


    \_(ツ)_/

    Tuesday, November 29, 2016 10:52 PM
    Moderator
  • It's will not move to the next command.

    Run this script and tell me if the last line is executing and you see "This will never run" on your console

    $ErrorActionPreference = 'Stop'
    Write-Error 'Nothing will run after this'
    'This will never run'

    Also "Exit" will terminate the user script not only the module this is why I used "Return"

    • Edited by ili101 Tuesday, November 29, 2016 11:08 PM
    Tuesday, November 29, 2016 11:03 PM

  • In all cases you cannot stop the throwing of an exception.  You can only alter what happens after it is "thrown" . "Write-Error" "throws" an exception.

     

    \_(ツ)_/


    • Edited by jrvModerator Tuesday, November 29, 2016 11:11 PM
    Tuesday, November 29, 2016 11:09 PM
    Moderator
  • Note: Changing ErrorActionPreference doe no stop errors it only hides the message on the screen or cause the exception to be available to Try/Catch.

    Exceptions cannot be disabled. They will always end up in $error.


    \_(ツ)_/

    Tuesday, November 29, 2016 11:14 PM
    Moderator
  • You are just typing commands after another, you are not running it as a continues .ps1 script or a script block



    • Edited by ili101 Tuesday, November 29, 2016 11:25 PM
    Tuesday, November 29, 2016 11:24 PM
  • $ErrorActionPreference = 'Stop'
    Try{
    	Write-Error 'Nothing will run after this'
    }
    Catch{"$_"}
    Write-host 'This will never run' -fore green
    
    

    You were asking bout global preference.

    In scripts and functions it works like this:


    \_(ツ)_/

    Tuesday, November 29, 2016 11:37 PM
    Moderator
  • You're really in a trolling mood today aren't you? (:
    Tuesday, November 29, 2016 11:54 PM
  • You're really in a trolling mood today aren't you? (:

    Don't know what that means.

    I have a feeling that I do not understand what it is you are asking. Can you clarify what your issue is?  What is not happening or what is happening that you do not expect?


    \_(ツ)_/

    Wednesday, November 30, 2016 12:13 AM
    Moderator
  • In case you haven't already discovered this here is another and, perhaps, better way to control how a script handles script level (or function) error actions.

    # test.ps1
    [CmdLetBinding()]
    Param()
    Get-Item -Path x:\xxxx
    Write-host 'This will never run' -fore green
    

    Then you can run it like this:

    .\teste.ps1
    .\teste.ps1 -ErrorAction stop
    .\teste.ps1 -ErrorAction SilentlyContinue


    \_(ツ)_/


    • Edited by jrvModerator Wednesday, November 30, 2016 12:22 AM
    Wednesday, November 30, 2016 12:20 AM
    Moderator
  • Your question still baffle me.  Perhaps it is a language issue.

    You asked: "Stopping Module on error without breaking calling script"

    What "module" are you referring to?  "Modules" do not run  they just load.  If a function resides in a module and it throws an exception it would not, under normal settings, stop the calling script.

    We generally don't use global preference settings as they can cause odd behaviors in many CmdLet.  It is better to use individual CmdLet "ErrorAction" parameter.  We also don't generally use "Stop" unless we are going to use a Try/Catch block to catch the exception and act on it.

    There is a spec that lays out what is called "Structured Exception Handling" (SEH) that specifies how this is intended to work.  Microsoft mostly complies to this spec which describes how we handle and "bubble" errors from lower layers of code bac to upper layers. "Stop" in PowerShell always disables any bubbling and assumes the exception will be handled locally.  To react to an error and bubble using "Stop" we would "re-throw the exception in the "Catch" block.

    Try{
          # code that can throw an exxception
          .... more lines
    }
    Catch{
         Throw $_ # rethrow exception to outer calling code block
         return
    }


    \_(ツ)_/


    • Edited by jrvModerator Wednesday, November 30, 2016 12:32 AM
    Wednesday, November 30, 2016 12:32 AM
    Moderator
  • You're really in a trolling mood today aren't you? (:

    Don't know what that means.

    I have a feeling that I do not understand what it is you are asking. Can you clarify what your issue is?  What is not happening or what is happening that you do not expect?


    \_(ツ)_/

    When writing a PowerShell module what is the available options to to achieve the following:
    On sensitive areas of the module, if a non-terminating OR terminating error occur:
    1. return an error to the user
    2. stop the module execution (but not the script that was using the module)
    3. respect the script that was calling the module $ErrorActionPreference or -ErrorAction

    The only way I can think of is something like:

    function Test-Error
    {
        [CmdletBinding()]
        param
        (
        )
        do
        {
            'Start'
            try
            {
                $ErrorActionPreferenceOrg = $ErrorActionPreference
                $ErrorActionPreference = 'Stop'
    
                Start-Process -FilePath nofile.exe  #Error example
                Write-Error 'non-terminating error' #Error example
                throw 'terminating error'           #Error example
            }
            catch
            {
                Write-Error "Error Message was $_" -ErrorAction $ErrorActionPreferenceOrg
                Return 
            }
            finally
            {
                $ErrorActionPreference = $ErrorActionPreferenceOrg
            }
            'DO NOT RAN THIS ON ERROR (End Module internal loop)'
        } while (1 -eq 1)
        'DO NOT RAN THIS ON ERROR (End Module external loop)' 
    }
    Is this the only/Best way to do This correctly?


    • Edited by ili101 Wednesday, November 30, 2016 1:24 AM
    Wednesday, November 30, 2016 1:13 AM
  • When writing a PowerShell module what is the available options to to achieve the following:
    On sensitive areas of the module, if a non-terminating OR terminating error occur:
    1. return an error to the user
    2. stop the module execution (but not the script that was using the module)
    3. respect the script that was calling the module $ErrorActionPreference or -ErrorAction


    A module is not executed. A module contains code such as CmdLets, functions or binary code. THe rules of exceptopn handling work inn module code exactly the same way as in any other place.

    If a module component throw an exception it will not stop a script from running.

    Do not set ErrorActionPreferenc e in a calling function or script to have correct behavior.


    \_(ツ)_/

    Wednesday, November 30, 2016 1:23 AM
    Moderator
  • The following is not needed as each code block only changes a local copy of this variable.

    $ErrorActionPreferenceOrg = $ErrorActionPreference
    $ErrorActionPreference = 'Stop'


    \_(ツ)_/

    Wednesday, November 30, 2016 1:25 AM
    Moderator
  • I have no idea what your code is trying to do.  It has an endless loop whihc will not work in any useful way.

    This is how to manage errors in a function,, CmdLet, script or scriptblock.

    try{
          Start-Process -FilePath nofile.exe -ErrorAction Stop
         # other code that will be skipped
    }
    catch{
         Throw $_
         return
    }
     

    It would be better if you asked a question about a real situation. It would help you to understand whay it works this way.


    \_(ツ)_/

    Wednesday, November 30, 2016 1:30 AM
    Moderator
  • The following is not needed as each code block only changes a local copy of this variable.

    $ErrorActionPreferenceOrg = $ErrorActionPreference
    $ErrorActionPreference = 'Stop'


    \_(ツ)_/

    Yes

    But when I'm removing the -ErrorAction $ErrorActionPreferenceOrg from:

    Write-Error "Error Message was $_" -ErrorAction $ErrorActionPreferenceOrg

    The calling script $ErrorActionPreference or -ErrorAction get overridden, for example setting this will be ignored

    Test-Error -ErrorAction SilentlyContinue
    And yes you can remove this:
    $ErrorActionPreferenceOrg = $ErrorActionPreference
    $ErrorActionPreference = 'Stop'

    But you will need to add -ErrorAction 'Stop' to all your potentials non-terminating errors commands in the try so they will be caught


    • Edited by ili101 Wednesday, November 30, 2016 1:41 AM
    Wednesday, November 30, 2016 1:40 AM
  • There is no reason to do what you are doing.  It serves no purpose in a normal design.  You need to have a real and useful example and it has to be designed in a consistent an useful way.

    You cannot write an error to a caller asynchronously.  Exception handling is a linear unwind of the stack.  An error can be thrown back to the caller and the code will terminate.  you cannot execute code and return errors even though you can display repeated errors to the  console.

    Without a real world example and a good design your issue is not answerable.


    \_(ツ)_/

    Wednesday, November 30, 2016 1:46 AM
    Moderator
  • There is no reason to do what you are doing.  It serves no purpose in a normal design.  You need to have a real and useful example and it has to be designed in a consistent an useful way.

    You cannot write an error to a caller asynchronously.  Exception handling is a linear unwind of the stack.  An error can be thrown back to the caller and the code will terminate.  you cannot execute code and return errors even though you can display repeated errors to the  console.

    Without a real world example and a good design your issue is not answerable.


    \_(ツ)_/

    Realistic example (simpleminded version of my module)

    #### Module Invoke-ApiSequence ####
    function Invoke-ApiSequence
    {
        [CmdletBinding()]
        param
        (
            $CustomerId
        )
    
        # Check if it safe to run the API sequence if not Return with error
        try
        {   
            # If something wrong in here Return with error
            $ErrorActionPreferenceOrg = $ErrorActionPreference
            $ErrorActionPreference = 'Stop' # Set Error Action for the Try block
    
            $WebRequest = Invoke-WebRequest -Uri 'ApiServer.local/api?call=ChackIfItSafeToRun1' -Body @{'CustomerId' = $CustomerId}
            $WebRequest = Invoke-WebRequest -Uri 'ApiServer.local/api?call=ChackIfItSafeToRun2' -Body @{'CustomerId' = $CustomerId ; 'Data' = $WebRequest.Content}
        }
        catch
        {
            # Write non-terminating error and Return
            Write-Error "Cannot check if it safe to run $_" -ErrorAction $ErrorActionPreferenceOrg
            Return 
        }
        finally
        {
            $ErrorActionPreference = $ErrorActionPreferenceOrg # Revert Error Action after Try block
        }
        if ($WebRequest.Content -eq 'Not Safe To Run')
        {
          # Test the reply from the WebRequest and Write non-terminating error and Return depending on the reply content 
          Write-Error 'Not Safe To Run'
          Return
        }
        
        # Run the actual API sequence and handle errors as required by the calling user script
        $WebRequest = Invoke-WebRequest -Uri 'ApiServer.local/api?call=Command1'
        $null = Invoke-WebRequest -Uri 'ApiServer.local/api?call=Command2' -Body @{'CustomerId' = $CustomerId ; 'Data' = $WebRequest.Content}
        $WebRequest = Invoke-WebRequest -Uri 'ApiServer.local/api?call=GetData' -Body @{'CustomerId' = $CustomerId}
        $WebRequest
    }
    
    
    #### End User Script snippets ####
    # Example if User wants Script to Continue on error
    Invoke-ApiSequence -CustomerId 154736
    
    # Example if User wants Script to Stop on error
    Invoke-ApiSequence -CustomerId 154736 -ErrorAction Stop
    
    # Example if User wants to debug the errors if occur 
    Invoke-ApiSequence -CustomerId 154736 -ErrorAction Inquire
    
    # YOLO
    Invoke-ApiSequence -CustomerId 154736 -ErrorAction SilentlyContinue


    • Edited by ili101 Wednesday, November 30, 2016 4:52 PM
    Wednesday, November 30, 2016 2:48 AM
  • You are not saying what the  problem is.  What is not happening?

    THIs lineis pointless as it cannot change the value of the original setting:

         $ErrorActionPreference = $ErrorActionPreferenceOrg
       

    What you are trying to do cannot be done the way you are approaching it.  You need to re-throw the error and not do 10 other things.

    Just run the code a return the exception.   YOU do not need to do all of that.  It serves no purpose.


    \_(ツ)_/

    Wednesday, November 30, 2016 3:06 AM
    Moderator
  • You are not saying what the  problem is.  What is not happening?

    1. There is no problem with the example I posted, it's working.

    2. my quotation was: Is this the only/Best way to do This correctly?

    THIs lineis pointless as it cannot change the value of the original setting:

         $ErrorActionPreference = $ErrorActionPreferenceOrg
    ...
    We generally don't use global preference settings as they can cause odd behaviors in many CmdLet.  It is better to use individual CmdLet "ErrorAction" parameter.  We also don't generally use "Stop" unless we are going to use a Try/Catch block to catch the exception and act on it

    1. it's not trying to change the parent scope setting I just reverting it back for the current scope.

    2. as you said yourself the propose of this is to change the error action to stop for the Try block then change it back.

    You need to re-throw the error

    what do you mean? use "throw" in here instead of "Write-Error/Return"?

    Write-Error "Cannot check if it safe to run $_" -ErrorAction $ErrorActionPreferenceOrg
    Return

    this will cause a deterministic error for the user which is not desirable as you also mentioned before (not to use Stop outside of Try)
    Edit: to be accurate the problem is not that "throw" return deterministic error it that it's also braking execution.

    What you are trying to do cannot be done the way you are approaching it.

    ...
    and not do 10 other things.

    Not helpful

    Just run the code a return the exception.   YOU do not need to do all of that.  It serves no purpose.

    What part of the code serves no purpose and why?

    Your question still baffle me.  Perhaps it is a language issue.

    You asked: "Stopping Module on error without breaking calling script"

    What "module" are you referring to?  "Modules" do not run  they just load.  If a function resides in a module and it throws an exception it would not, under normal settings, stop the calling script.

    See the original post the Function written in a .psm1 module file
    When I wrote running the module I meant calling the module Function from the user script

    If a function resides in a module and it throws an exception it would not, under normal settings, stop the calling script.

    Thank you, you are correct, I was a little confused by this , are there any exceptions to this that you know of?
    Are there any Microsoft built-in CmdLet or "Powershell Errors" that will terminate your script?

    Things that I know of are:
    1. syntax errors
    2. throw
    3. exit
    4. Write-Error 'any non-terminating error' -ErrorAction Stop



    • Edited by ili101 Wednesday, November 30, 2016 10:20 AM
    Wednesday, November 30, 2016 9:58 AM
  • Sorry - I cannot make any sense out of your discussion.  I cannot understand what it is you are trying to accomplish nor does any of your code make any sense from a programming or design perspective.

    Perhaps you could contact a consultant to sit with you an try to understand what it is you need to accomplish.

    I suspect your issues and needs may be beyond the scope of a simple Q&A forum.

    Good luck.


    \_(ツ)_/

    Wednesday, November 30, 2016 1:56 PM
    Moderator
  • I don't know why it's so hard for you to understand and follow the discussion
    I know you are the most active member in here and contribute a lot, but I may comment from my experience and other conversation I read that your short and sometimes vague/ambiguous replies make it very hard to communicate.

    And saying things like noting make any sense instead of explaining what you don't understand or what is wrong with the code is not constructive or helpful

    The code is just an concept example to illustrate the error handling
    The code (Invoke-ApiSequence) works and preform as expected, all I was asking is for a second opinun and improvements to the error handling.
    The error handling achieves the following:
    1. return a non-terminating error to the user when something is wrong.
    2. stop the module execution in certain parts regardless of the -ErrorAction selected when running the function (but not stopping the script that was using the module)
    3. respect the script that was calling the Function $ErrorActionPreference or the -ErrorAction setting that was used

    I added some more comments to the Invoke-ApiSequence in my previous post if it make any difference

    Thank you

    Wednesday, November 30, 2016 4:59 PM
  • Just to update this old topic with a "Solution"

    A few months ago an Issues was open on the PowerShell GitHub that provides a good explanation and some fixes suggestions.

    Our Error Handling, Ourselves - time to fully understand and properly document PowerShell's error handling

    In short it describe the differences between the 3 PowerShell error types (Yes 3 types not 2)

    • Non-terminating errors (Write-Error)
    • Script-terminating errors (Cmdlets, .NET exceptions)
    • Statement-terminating errors (Throw, Write-Error 'With' -ErrorAction Stop)

    Also see the section of "Reporting custom errors in functions and scripts" and the $PSCmdlet.ThrowTerminatingError() option.

    • Marked as answer by ili101 Thursday, May 17, 2018 3:57 PM
    Thursday, May 17, 2018 3:56 PM