14
votes

I've been giving PowerShell (v3.0) a shot for the first time today, and became enormously frustrated with the strange way some of its error-handling concepts are implemented.

I wrote the following piece of code (using the Remote Registry PowerShell Module)

try
{
    New-RegKey -ComputerName $PCName -Key $Key -Name $Value
    Write-Host -fore Green ($Key + ": created")
}
catch
{
    Write-Host -fore Red "Unable to create RegKey: " $Key
    Write-Host -fore Red $_
}

(This is just a snippet)

Apparently the default behavior of PowerShell is to NOT catch errors which are non-terminating. So I added the following line at the top op my script, as recommended by various people:

$ErrorActionPreference = "Stop"

Executing this in the PowerShell ISE did indeed catch all errors. However, execution following command from the terminal still does not catch my errors.

From ISE:

PS C:\windows\system32> C:\Data\Scripts\PowerShell\Error.ps1
Errorhandling:  Stop
SOFTWARE\MySoftware does not exist. Attempting to create
Unable to create RegKey:  SOFTWARE\MySoftware
Key 'SOFTWARE\MySoftware' doesn't exist.

From Command-Line:

PS C:\Data\Scripts\PowerShell> .\Error.ps1
Errorhandling:  Stop
SOFTWARE\MySoftware does not exist. Attempting to create
New-RegKey : Key 'SOFTWARE\MySoftware' doesn't exist.
At C:\Data\Scripts\PowerShell\Error.ps1:17 char:13
+             New-RegKey -ComputerName $PCName -Key $Key -Name $Value
+             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [Write-Error], WriteErrorException
    + FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,New-RegKey

SOFTWARE\MySoftware: created

I have no idea why the behavior of the Preference Variables behaves differently depending on where they are called from, especially since the ISE seems to execute the exact same command?

Based on other feedback, I changed the following line:

New-RegKey -ComputerName $PCName -Key $Key -Name $Value

To:

New-RegKey -ComputerName $PCName -Key $Key -Name $Value -ErrorAction Stop

Using this method, I was able to trap errors from both the command-line and the ISE, but I don't want to specify the error behaviour on each Cmdlet I call, especially because the catching of errors is essential to the proper functioning of the code. (Plus, the fact that this method DOES work only serves to confuse me even more)

What is the proper way of defining error-handling behavior for the scope of an entire script and/or module?

Also, here's my $PSVersionTable:

PS C:\Data\Scripts\PowerShell> $PSVersionTable

Name                           Value
----                           -----
PSVersion                      3.0
WSManStackVersion              3.0
SerializationVersion           1.1.0.1
CLRVersion                     4.0.30319.18408
BuildVersion                   6.2.9200.16481
PSCompatibleVersions           {1.0, 2.0, 3.0}
PSRemotingProtocolVersion      2.2
1
It could be a bug. I found a similar case. Execute this in PS3: $ErrorActionPreference = 'Stop'; Remove-Item -Verbose xxxxxx; echo hi. You would not expect the echo to be executed if the file "xxxxxx" does not exist, but it does get executed. If you remove the -Verbose, then it works as expected.dan-gph
@Dangph That's exactly what I mean: the behavior you're showing is extremely confusion. A feature so essential to writing a basic script should not be this confusing (or perhaps I'm just daft), at least if this is indeed the intended behavior, and not a "bug"romatthe
I don't understand it. I add $ErrorActionPreference = 'Stop' to the top of almost all of my scripts, and it works as expected. But in this one case (and in yours), it didn't work. That's why I think it's a bug.dan-gph

1 Answers

12
votes

Since you're running V3, you also have the option of using $PSDefaultParameterValues:

$PSDefaultParameterValues += @{'New-RegKey:ErrorAction' = 'Stop'}

Normally that will change it in the global scope. If you want to isolate it to just the local or script scope, you can initialize a new one in the local scope first:

$PSDefaultParameterValues = @{}
$PSDefaultParameterValues += @{'New-RegKey:ErrorAction' = 'Stop'}

If you want to inherit what's already in the parent scope and then add to it for the local scope:

 $PSDefaultParameterValues = $PSDefaultParameterValues.clone()
 $PSDefaultParameterValues += @{'New-RegKey:ErrorAction' = 'Stop'}

To set the default ErrorAction for all cmdlets, not just New-RegKey, specify '*:ErrorAction' instead of 'New-RegKey:ErrorAction' in the code above.