PowerShell: error detection in script functions

What is the best way to determine if an error occurs in a script function? Am I looking for a consistent way to indicate error / success status similar to $? (which only works with cmdlets, not script functions).

Given that a particular function can return a value that will be used by the caller, we cannot indicate success by returning a boolean value. The function can use the [ref] parameter and correctly set the value inside the function and check after the call, but this is more overhead than we would like. Is there something built into PowerShell that we can use?

The best I can come up with is:

  • In a function, use Write-Error to put ErrorRecord objects into the error stream;
  • calling a function using the ErrorVariable parameter;
  • check if the ErrorVariable parameter after the call does not matter.

For example:

function MyFun { [CmdletBinding()] # must be an advanced function or this param () # will not work as ErrorVariable will not exist process { # code here.... if ($SomeErrorCondition) { Write-Error -Message "Error occurred; details..." return } # more code here.... } } # call the function $Err = $null MyFun -ErrorVariable Err # this check would be similar to checking if $? -eq $false if ($Err -ne $null) { "An error was detected" # handle error, log/email contents of $Err, etc. } 

Is there anything better? Is there any way to use $? in our script functions? I would rather not throw exceptions or ErrorRecord objects and have many try / catch blocks all over the place. I would also prefer not to use $ Error, since I would have to check the counter before calling the function, as there may be other errors before the call - and I do not want Clear () and lose them.

+7
source share
6 answers

What is the best way to determine if an error occurs in a script function? Am I looking for a consistent way to indicate error / success status similar to $? (which only works with cmdlets, not script functions).

PowerShell error handling is a complete mess. $? error records, script exceptions, .NET exceptions, $? , $LASTEXITCODE , trap s, $Error array (between areas), etc. And creates these elements for interaction with each other (for example, $ErrorActionPreference ). It is very difficult to understand when you have such a swamp; however, there is a way to achieve this goal.

The following points should be made:

  • $? - an underrated secret. $? the values ​​from cmdlet calls are not propagated, it is a "read-only variable" (therefore, it cannot be set manually), and it is not clear exactly when it will be set (which may be "execution status", a term never used in PowerShell, except for the description $? in about_Automatic_Variables is a mystery). Fortunately, Bruce Payette shed light on this: if you want to set $? , $PSCmdlet.WriteError() is the only known way.

  • If you want functions to set $? as cmdlets, you should refrain from Write-Error and use $PSCmdlet.WriteError() . Write-Error and $PSCmdlet.WriteError() do the same, but the first does not set $? right, and the latter does. (Do not worry when trying to find this somewhere in this document. This is not so.)

  • If you want to handle .NET exceptions correctly (as if they were errors without interruption, leaving the decision to stop all execution to the client code), you must catch and $PSCmdlet.WriteError() them, you cannot leave them unhandled, since they become irreversible errors that do not respect $ErrorActionPreference . (Not documented.)

In other words, the key to creating consistent error handling behavior is to use $PSCmdlet.WriteError() whenever possible. Does he set $? respects $ErrorActionPreference (and therefore -ErrorAction ) and accepts System.Management.Automation.ErrorRecord objects created from other cmdlets or the catch statement (in the $_ variable).

The following examples show how to use this method.

 # Function which propagates an error from an internal cmdlet call, # setting $? in the process. function F1 { [CmdletBinding()] param([String[]]$Path) # Run some cmdlet that might fail, quieting any error output. Convert-Path -Path:$Path -ErrorAction:SilentlyContinue if (-not $?) { # Re-issue the last error in case of failure. This sets $?. # Note that the Global scope must be explicitly selected if the function is inside # a module. Selecting it otherwise also does not hurt. $PSCmdlet.WriteError($Global:Error[0]) return } # Additional processing. # ... } # Function which converts a .NET exception in a non-terminating error, # respecting both $? and $ErrorPreference. function F2 { [CmdletBinding()] param() try { [DateTime]"" # Throws a RuntimeException. } catch { # Write out the error record produced from the .NET exception. $PSCmdlet.WriteError($_) return } } # Function which issues an arbitrary error. function F3 { [CmdletBinding()] param() # Creates a new error record and writes it out. $PSCmdlet.WriteError((New-Object -TypeName:"Management.Automation.ErrorRecord" -ArgumentList:@( [Exception]"Some error happened", $null, [Management.Automation.ErrorCategory]::NotSpecified, $null ) )) # The cmdlet error propagation technique using Write-Error also works. Write-Error -Message:"Some error happened" -Category:NotSpecified -ErrorAction:SilentlyContinue $PSCmdlet.WriteError($Global:Error[0]) } 

As a final note, if you want to create trailing errors from .NET exceptions, try / catch and re throw exception.

+11
source

It looks like you are looking for a general mechanism to log any error that occurs in a command called from a script. If so, trap is probably the most appropriate mechanism:

 Set-Alias ReportError Write-Host -Scope script # placeholder for actual logging trap { ReportError @" Error in script $($_.InvocationInfo.ScriptName) : $($_.Exception) $($_.InvocationInfo.PositionMessage) "@ continue # or use 'break' to stop script execution } function f( [int]$a, [switch]$err ) { "begin $a" if( $err ) { throw 'err' } " end $a" } f 1 f 2 -err f 3 

Running this test script outputs the following output without the need to make any changes to the called functions:

 PS> ./test.ps1 begin 1 end 1 begin 2 Error in script C:\Users\umami\t.ps1 : System.Management.Automation.RuntimeException: err At C:\Users\umami\t.ps1:13 char:21 + if( $err ) { throw <<<< 'err' } begin 3 end 3 

If script execution should stop after an error message, replace continue with break in the trap handler.

+6
source

Two things come to mind: Throw (better than Write-Error in your example above) and try..catch

 try { #code here } catch { if ($error[0].Exception -match "some particular error") { Write-Error "Oh No! You did it!" } else { Throw ("Ooops! " + $error[0].Exception) } } 

Imho, as a rule, it is better to have a function to handle its errors as much as possible.

+3
source

$? depends on whether the function completes the final error or not. If Write-Error is used, not Throw, $? not set. Many cmdlets don’t set $? when they have an error, because this error is not a final error.

The easiest way to make your function installed $? is to use -ErrorAction Stop. Will this stop the script on function errors and $? will be installed.

Check out this block of samples to see how $? work:

 function foo([ParameteR()]$p) { Write-Error "problem" } foo $? foo -errorAction Stop $? function foo() { throw "problem" } foo $? 

Hope this helps

0
source

I believe you need the global variable $ GLOBAL: variable_name. This variable will be in the scope of the script not only functions.

Looking at the code, you can also use the hook ( Get-Help about_Trap ), although $ GLOBAL: variable_name will work with yours above. Here's a repeated code example - I have not tested this, so it is more pseudo code ... :)

 function MyFun { [CmdletBinding()] # must be an advanced function or this param () # will not work as ErrorVariable will not exist begin { trap { $GLOBAL:my_error_boolean = $true $GLOBAL:my_error_message = "Error Message?" return } } process { # more code here.... } } # call the function $GLOBAL:my_error_boolean = $false MyFun # this check would be similar to checking if $? -eq $false if ($GLOBAL:my_error_boolean) { "An error was detected" # handle error, log/email contents of $Err, etc. } 

HTH, Matt

0
source

Most of this produced a wonderful cry when it passed right above my head ... ಠ_ಠ

I'm with Dan. PS Logging is a complete mess and it seems that it will be more than twice the size of the code I write ...

Honestly, I would be happy if I could just grab the console output directly into the logs, warts and all ...

The Try / Catch block is so ... so ... crappy, I smell it, and it made my eyes catch fire.

$? very interesting, but you guys really know what you are doing, where I am at the point where I realized that I didn’t know anything (last week I thought I knew at least something, but noooooo).

Why does% $ # @% $ not have something like 2> in cli ...

So here is what I am trying to do (you have read this far, so why not?):

  Function MyFunc($Param1, $Param2){ Do{ $Var = Get-Something | Select Name, MachineName, Status $NotherVar = Read-Host -Prompt "Do you want to Stop or Start or check the $Var (1 to Start, 2 to stop, 3 to check, 4 to continue)?" If ($SetState -eq 1) { Do Stuff } ElseIf ($Var -eq 2) { Do Stuff } ElseIf ($Var -eq 3) { Do Stuff } } Until ($Var -eq 4) Do other stuff } 

Did it work? Yes, great ... Write it down and continue. Not? Then catch the error, write it down and continue the script ...

I am tempted to simply ask to enter the user, add content and continue ...

By the way, I found the PSLogging module, which seems like it will be pretty cool, but I'm not sure how to make it work ... The documentation is a bit spartan. It seems that people make him work without any problems, so I seem to feel that I am a corner sitting in a narrow hat, a man ...

0
source

All Articles