Why can I not catch my non-terminating error? - powershell

I'd like to show someone an example on how to catch non terminating errors in PowerShell.
I created this function:
# This will write a non-terminating error
function NonTerminatingErrorExample {
Param($i = 5)
if ($i -gt 4) {
Write-Error "I expected a value less or equal to 4!"
}
Write-Host "However, I can still continue the execution"
}
However, I can't catch it with -ErrorAction Stop
Try {
NonTerminatingErrorExample -ErrorAction Stop
} Catch {
Write-Host "Now you see this message."
}
I never get the catch block returned. Why?
NonTerminatingErrorExample : I expected a value less or equal to 4!
In Zeile:32 Zeichen:5
+ NonTerminatingErrorExample -ErrorAction Stop
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException
+ FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,NonTerminatingErrorExample
However, I can still continue the execution

Your function isn't a cmdlet and therefore doesn't support -ErrorAction. Add [CmdletBinding()] to the body to make it one and the parameter will be supported. You can see the difference if you Get-Help both versions: as a function, there is no [<CommonParameters>].

Related

Suppressing error messages in PowerShell ErrorVariable

Using the snippet below, I'm trying to find a way to suppress the "Attempted to divide by zero" error, and be left with just the custom error message in $err. I've tried combinations of try/catch, Invoke-Command/Expression, redirecting 2>$null, but in every case the error stream still contains the "attempted to divide by zero" message.
function func1 {
[CmdletBinding()]
param()
func2 -ErrorAction SilentlyContinue -ErrorVariable er
if ($er.Count -ge 1) {
Write-Error -Message "Custom error message!"
}
}
function func2 {
[CmdletBinding()]
param()
1/0
}
func1 -ErrorAction SilentlyContinue -ErrorVariable err
$err
populates $err with two items:
Attempted to divide by zero.
At line:15 char:3
+ 1/0
+ ~~~
+ CategoryInfo : NotSpecified: (:) [], RuntimeException
+ FullyQualifiedErrorId : RuntimeException
func1 : Custom error message!
At line:18 char:1
+ func1 -ErrorAction SilentlyContinue -ErrorVariable err
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException
+ FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,func1
Thanks so much for your thoughts!
Unfortunately the solution is somewhat tricky, because the error that occurs in func2 is invariably recorded in the $err variable passed to -ErrorVariable in the func1 invocation, and is already stored there when the func2 call returns.
Therefore you need to manually remove it:
Replace:
if ($er.Count -ge 1) {
Write-Error -Message "Custom error message!"
}
with:
if ($er.Count -ge 1) {
if ($PSBoundParameters.ContainsKey('ErrorVariable')) {
# Remove the most recently added error from the $err variable.
$PSCmdlet.GetVariableValue($PSBoundParameters['ErrorVariable']).RemoveAt(0)
}
Write-Error -Message "Custom error message!"
}
The automatic $PSBoundParameters variable allows you to check if the common -ErrorAction parameter was specified and with what value (i.e., what target variable name).
The automatic $PSCmdlet variable's .GetVariableValue() method allows access to variables defined in the caller's scope, which enables retrieving the target variable value, which is a [System.Collections.ArrayList] instance whose .RemoveAt() method can be called to remove the most recently added error, which is at index 0.
Asides:
1/0 causes a statement-terminating error, whereas the common -ErrorAction parameter only acts on non-terminating errors (to catch terminating errors you need try / catch). However, because this statement-terminating error occurs inside a function, execution inside that function (potentially) continues, and to the caller the error is in effect a non-terminating one.
For a comprehensive overview of PowerShell's bewilderingly complex error handling, see GitHub docs issue #1583.

Powershell catching exception type

Is there a convenient way to catch types of exceptions and inner exceptions for try-catch purposes?
Example code:
$a = 5
$b = Read-Host "Enter number"
$c = $a / $b #error if $b -eq 0
$d = get-content C:\I\Do\Not\Exist
Row #3 will generate a runtime error with an inner exception (EDIT: fixed this command $Error[1].Exception.InnerException.GetType()), row #4 will generate a "standard"(?) type of exception ($Error[0].Exception.GetType()).
Is it possible to get the desired result from both of these with the same line of code?
Ad1: error from row 3
At -path-:3 char:1
+ $c = $a / $b #error if $b -eq 0
+ ~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], RuntimeException
+ FullyQualifiedErrorId : RuntimeException
Ad2: error from row 4
get-content : Cannot find path 'C:\I\Do\Not\Exist' because it does not exist.
At -path-:4 char:6
+ $d = get-content C:\I\Do\Not\Exist
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (C:\I\Do\Not\Exist:String)
[Get-Content], ItemNotFoundException
+ FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetContentCommand
Edit: To make it clear, I want the result to return DivideByZeroException and ItemNotFoundException in some way
First of all, you can explicitly catch specific exception types:
$ErrorActionPreference = "Stop"
try {
1 / 0
}
catch [System.DivideByZeroException] {
$_.Exception.GetType().Name
}
try {
Get-Item "c:\does-not-exist"
}
catch [System.Management.Automation.ItemNotFoundException] {
$_.Exception.GetType().Name
}
The DivideByZeroException is basically just the InnerException of RuntimeException, and theoretically, the InnerExceptions could be endlessly nested:
catch {
$exception = $_.Exception
do {
$exception.GetType().Name
$exception = $exception.InnerException
} while ($exception)
}
BUT you can handle RuntimeException as a special case. Even PowerShell does so. Look at the first code example. The catch-block is reached even though the type of the inner exception is specified.
You could do something similar yourself:
catch {
$exception = $_.Exception
if ($exception -is [System.Management.Automation.RuntimeException] -and $exception.InnerException) {
$exception = $exception.InnerException
}
$exception.GetType().Name
}
NOTE that you need one try-catch per command, if you want to catch both exceptions. Else the 2nd will not be executed if the 1st one fails. Also you have to specify $ErrorActionPreference to "Stop" to catch also non-terminating exceptions.
Are you trying to catch specific error types like this?
https://blogs.technet.microsoft.com/poshchap/2017/02/24/try-to-catch-error-exception-types/

PowerShell module not dot-sourcing / importing functions as expected

Update 1:
Originally, I posted this with the title: "Scripts ignoring error handling in PowerShell module" as that is the current issue, however it seems more of a module issue, so I have renamed the title.
Update 2:
After a comment that made me question Azure cmdlets, I've tested with the most basic of scripts (added to the module) and the findings are the same, in that the error is not passed to the calling script, however, adding -errorVariable to Get-Service does return something (other than WriteErrorException) that I could probably harness in the handling of the error:
function Test-MyError($Variable)
{
Try
{
Get-Service -Name $variable -ErrorAction Stop -ErrorVariable bar
#Get-AzureRmSubscription -SubscriptionName $variable -ErrorAction Stop
}
Catch
{
Write-Error $error[0]
$bar
}
}
returns:
Test-MyError "Foo"
Test-MyError : Exception of type 'Microsoft.PowerShell.Commands.WriteErrorException' was thrown.
At line:3 char:1
+ Test-MyError "Foo"
+ ~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException
+ FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,Test-MyError
The running command stopped because the preference variable "ErrorActionPreference" or common parameter is set to Stop: Cannot find any service with service name 'foo'.
However, if I run "Test-MyError" in ISE, then call the function, I get:
Test-MyError "Foo"
Test-MyError : Cannot find any service with service name 'Foo'.
At line:3 char:1
+ Test-MyError "Foo"
+ ~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException
+ FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,Test-MyError
The running command stopped because the preference variable "ErrorActionPreference" or common parameter is set to Stop: Cannot find any service with service name 'Foo'.
So I am not sure what is happening when running "Test-MyError" in ISE and calling it, against it being dot-sourced in the PSM1 file and then calling it?
Do I now have to use -ErrorVariable and handle on that?
Original Question:
I have two functions in a module: Get-Subscription and Get-AllSubscriptions. Each function sits in its own PS1 file and the PSM1 file dot-sources them. The module seems fine as the module scripts are accessible using intelisense and the module loads without issue. I've used this structure in many modules and I haven't come across this problem before. (Although I wonder if MS have changed the way modules work in PS 5.1 as I have noticed using FunctionsToExport='x','y','Z' and Export-ModuleMember don't seem to behave the same way as they used to.)
Get-AllSubscriptions calls Get-Subscription.
If I am not logged into Azure, Get-Subscription should throw an error which is handled, prompting me to log in. This works as expected, if I call Get-Subscription from the Get-Subscription.ps1.
However, when I call Get-Subscription from the a new PS1 file, Get-AllSubscriptions or from the powershell console, it doesn't work. It iterates all the way through the do..until loop, without "handling" the errors as I would expect. On each iteration, it seems to throw a generic error:
Get-Subscription : Exception of type 'Microsoft.PowerShell.Commands.WriteErrorException' was thrown.
However, I do see the last error, Get-Subscription : Unable to find requested subscription after 3 login attempts.
If I execute Get-Subscription in ISE, then call Get-Subscription in a new PS1 file or from Get-AllSubscriptions, it works as expected, however, once I re-import the module (Import-Module AzureVnetTools -Force -Verbose), it goes back to the incorrect behaviour.
If I dot-source Get-Subscription, inside the caller script, it works, but why? This is what should happen with the module's PSM1.
Can anyone help me work out what I am doing wrong here?
(PS 5.1, Windows 7)
Get-Subscription:
function Get-Subscription
{
[cmdletbinding()]
Param
(
[string]$SubscriptionName,
[string]$UserName,
[string]$code
)
$c=1
Write-Verbose "Checking access to '$SubscriptionName' with user '$UserName'..."
Do
{
Write-Verbose "Attempt $c"
Try
{
$oSubscription = Get-AzureRmSubscription -SubscriptionName $SubscriptionName -ErrorAction Stop -WarningAction SilentlyContinue
Write-Verbose "Subscription found: $($oSubscription.SubscriptionName)."
}
Catch
{
if($error[0].Exception.Message -like "*Please verify that the subscription exists in this tenant*")
{
Write-Verbose "Cannot find subscription '$SubscriptionName' with provided credentials."
$account = Login-AzureRmAccount -Credential (Get-Credential -UserName $Username -Message "Subscription '$SubscriptionName' user' password:")
}
elseif($error[0].Exception.Message -like "*Run Login-AzureRmAccount to login*")
{
Write-Verbose "No logged in session found. Please log in."
$account = Login-AzureRmAccount -Credential (Get-Credential -UserName $Username -Message "Subscription '$SubscriptionName' user' password:")
}
else
{
Write-Error $error[0]
}
}
$c++
}
until(($oSubscription) -or ($c -eq 4))
if($c -eq 4)
{
Write-Error "Unable to find requested subscription after $($c-1) login attempts."
break
}
$oSubscription | Add-Member -MemberType NoteProperty -Name Code -Value $code
$oSubscription
}
Get-AllSubscriptions:
function Get-AllSubscriptions
{
[cmdletbinding()]
param
(
[string]$MasterSubscription,
[string]$MasterSubscriptionCode,
[string]$MasterSubscriptionUsername,
[string]$ChildSubscription,
[string]$ChildSubscriptionCode,
[string]$ChildSubscriptionUsername
)
Write-Verbose "Getting all subscriptions..."
$oAllSubscriptions = #()
$oMasterSubscription = Get-Subscription -SubscriptionName $MasterSubscription -UserName $MasterSubscriptionUsername -code $MasterSubscriptionCode -Verbose
$oChildSubscription = Get-Subscription -SubscriptionName $ChildSubscription -UserName $ChildSubscriptionUsername -code $ChildSubscriptionCode -Verbose
$oAllSubscriptions = ($oMasterSubscription,$oChildSubscription)
$oAllSubscriptions
}
Test:
$splat2 = #{
SubscriptionName = "SomeSubscription"
Code = "S02"
Username = "some.user#somewhere.com"
}
#Write-Output "Dot-source:"
#. "D:\Temp\PS.Modules\AzureVnetTools\functions\public\Get-Subscription.ps1"
Get-Subscription #splat2 -verbose
Output:
Get-Subscription #splat2 -verbose
VERBOSE: Checking access to 'SomeSubscription' with user 'some.user#somewhere.com'...
VERBOSE: Attempt 1
Get-Subscription : Exception of type 'Microsoft.PowerShell.Commands.WriteErrorException' was thrown.
At line:7 char:1
+ Get-Subscription #splat2 -verbose
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException
+ FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,Get-Subscription
VERBOSE: Attempt 2
Get-Subscription : Exception of type 'Microsoft.PowerShell.Commands.WriteErrorException' was thrown.
At line:7 char:1
+ Get-Subscription #splat2 -verbose
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException
+ FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,Get-Subscription
VERBOSE: Attempt 3
Get-Subscription : Exception of type 'Microsoft.PowerShell.Commands.WriteErrorException' was thrown.
At line:7 char:1
+ Get-Subscription #splat2 -verbose
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException
+ FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,Get-Subscription
Get-Subscription : Unable to find requested subscription after 3 login attempts.
At line:7 char:1
+ Get-Subscription #splat2 -verbose
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException
+ FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,Get-Subscription
AzureVnetTools.psm1
#Get public and private function definition files.
$Public = #( Get-ChildItem -Path $PSScriptRoot\Functions\Public\*.ps1 -ErrorAction SilentlyContinue )
$Private = #( Get-ChildItem -Path $PSScriptRoot\Functions\Private\*.ps1 -ErrorAction SilentlyContinue )
#Dot source the files
Foreach($import in #($Public + $Private))
{
#write-error $import.fullname
Try
{
#Write-Host "Dot-sourcing file: $($import.fullname)."
. $import.fullname
}
Catch
{
Write-Error -Message "Failed to import function $($import.fullname): $_"
}
}
Export-ModuleMember -Function $Public.Basename
AzureVnetTools.psd1 (Relevant section):
FunctionsToExport = '*'
-ErrorAction Stop -WarningAction SilentlyContinue
Is it a warning that's being thrown instead of an error?
So my specific problem was that I was relying on handling the error and doing something based on that. The problem was caused by the way PowerShell's Write-Error works (or not) as I learned from the reply here, given by #Alek.
It simply wasn't passing the actual error back to the calling script. As #Alex suggested, I replaced Write-Error with $PSCmdlet.WriteError(). Although this didn't totally work.
In the Catch{} block, I then changed $error[0] to $_ and the full error was returned to the calling script / function.
I went one further and wrote a reusable function, added to my module:
function Write-PsError
{
[cmdletbinding()]
Param
(
[Exception]$Message,
[Management.Automation.ErrorCategory]$ErrorCategory = "NotSpecified"
)
$arguments = #(
$Message
$null #errorid
[Management.Automation.ErrorCategory]::$ErrorCategory
$null
)
$ErrorRecord = New-Object -TypeName "Management.Automation.ErrorRecord" -ArgumentList $arguments
$PSCmdlet.WriteError($ErrorRecord)
}
Which seems to be working well at the moment. I especially like the way intellisense picks up all the ErrorCategories. Not sure what or how ISE (PS 5.1 / Win 7) does that. I thought I was going to have to add my own dynamic parameter.
HTH.

System.InvalidOperationException not being catched in powershell script

am trying to catch a specific exception in a powershell script. Thecodeblock underneath is trying to do this, when the given exception is being thrown the script should ignore it:
try
{
Stop-WebAppPool $webServerSettings.applicationPool
}
catch [System.InvalidOperationException]
{
}
Unfortunately this does not seem to work, I still am getting this response:
Stop-WebAppPool : Object on target path is already stopped.
At F:\Portal.Update.ps1:12 char:2
+ Stop-WebAppPool $webServerSettings.applicationPool
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [Stop-WebAppPool], InvalidOperationException
+ FullyQualifiedErrorId : InvalidOperation,Microsoft.IIs.PowerShell.Provider.StopAppPoolCommand
There are a few options I tried:
Passing the 'ErrorAction Stop' parameter value to the command. Now I do not have the error, but the script stops, that is not what I want.
Passing the 'ErrorAction SilentlyContinue' parameter value to the command. But this will only work when catching all types of exceptions, which I do not want because if the application pool does not exist, I want the exception to be thrown.
UPDATE:
I found a way to work around this problem: Just read out the $_.exception variable and with GetType() get the real exception type, but having a function in a catch does not seem to right way to go:
try
{
Stop-WebAppPool $webServerSettings.applicationPool
}
catch
{
if ($_.Exception.GetType().Name -eq "InvalidOperationException")
{
return
}
throw
}
Try,
try
{
Stop-WebAppPool $webServerSettings.applicationPool -ErrorAction Stop
}
Catch
{
"Unable to stop Web App Pool"
}
-ErrorAction Stop tells PowerShell to stop when it encounters an error; this suppresses the error output you were seeing.

Powershell - simply checking if a function completed successfully or not (times out)

I basically have this simple powershell script that executes an ssrs report url and saves it to the network.
It usually runs fine, but sometimes it times out and when it does, it still says it succeeded. I've tried a handful of things with no luck.
A simplified version of my script looks like this:
-----------------------------
function RunReport($url,$outputfile) {
# // create a request
[Net.HttpWebRequest] $req = [Net.WebRequest]::create($url)
$req.Method = "GET"
$req.Timeout = 600000 # = 10 minutes
# // Set credentials
$req.UseDefaultCredentials = $true
#echo "Getting Response"
[Net.HttpWebResponse] $result = $req.GetResponse()
[IO.Stream] $stream = $result.GetResponseStream()
#[IO.StreamReader] $reader = New-Object IO.StreamReader($stream)
[System.IO.FileStream]$writeStream = New-Object System.IO.FileStream($outputfile, [System.IO.FileMode]::Create);
# // write to file
[byte[]]$buffer = new-object byte[] 4096
[int]$total = [int]$count = 0
do
{
$count = $stream.Read($buffer, 0, $buffer.Length)
$writeStream.Write($buffer, 0, $count)
} while ($count -gt 0)
$writeStream.Close()
#$stream.flush()
$stream.Close()
}
$url=...
$outputfile=...
IF(RunReport "$url" "$outputfile")
{Write-Host "Success"}
ELSE
{Write-Host "Failed"}
-------------------------------
I've tried stuff like this with no luck:
RunReport "$url" "$outputfile"
If($?)
{Write-Host "Success"}
ELSE
{Write-Host "Failed"}
and
RunReport "$url" "$outputfile"
If($? -eq true)
{Write-Host "Success"}
ELSE
{Write-Host "Failed"}
The timeout error I'm dealing with is:
Exception calling "GetResponse" with "0" argument(s): "The operation has timed out"
At C:\data\powershell\script.ps1:9 char:49
+ [Net.HttpWebResponse] $result = $req.GetResponse <<<< ()
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : DotNetMethodException
You cannot call a method on a null-valued expression.
At C:\data\powershell\script.ps1:10 char:48
+ [IO.Stream] $stream = $result.GetResponseStream <<<< ()
+ CategoryInfo : InvalidOperation: (GetResponseStream:String) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
You cannot call a method on a null-valued expression.
At C:\data\powershell\script.ps1:18 char:38
+ $count = $stream.Read <<<< ($buffer, 0, $buffer.Length)
+ CategoryInfo : InvalidOperation: (Read:String) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
You cannot call a method on a null-valued expression.
At C:\data\powershell\script.ps1:23 char:14
+ $stream.Close <<<< ()
+ CategoryInfo : InvalidOperation: (Close:String) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
Any help would be greatly appreciated. I assume this should be fairly easy, just don't have the correct syntax ? Thanks
Maybe you can use try/catch to deal with that part like this:
try {
[Net.HttpWebResponse] $result = $req.GetResponse()
}
catch {
return 1
}
You can apply the same technique in other places where you suspect code is not doing what it is supposed to do like:
try {
[System.IO.FileStream]$writeStream = New-Object System.IO.FileStream($outputfile, [System.IO.FileMode]::Create);
}
catch {
return 1
}
Once you detect where the issue is happening, you could then look into exception like
catch { write-warning $_ ; exit 1 }
The 'cannot call a method on a null-valued expression' errors are all stemming from the fact that the .GetResponse method is failing with a timeout, leaving the $result variable unassigned. The simplest change would be to put the rest of the script (after "$result = $req.GetResponse()") into an "if ($result) {}" block. You can then use a "then {}" block to do your error handling.
A more advanced method would be to use try {} and catch {} blocks, to catch the actual timeout exception and handle it properly.
You have two problems:
The timeouts (and the cascading errors from that)
Your RunReport function doesn't ever return anything.
For #2, you can't test something that doesn't exist. So make your function return some kind of success/fail indicator to the caller.
For #1, when you call GetResponse() you need to wrap it in a try/catch block, catch the exception, and exit the function with the appropriate status returned to the caller.
You might want to look at this SO post about calling SSRS using SOAP methods as well.