16

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
romatthe
  • 1,447
  • 3
  • 17
  • 33
  • 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 Jan 21 '14 at 02:03
  • @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 Jan 21 '14 at 11:46
  • 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 Jan 22 '14 at 00:18

1 Answers1

14

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.

dan-gph
  • 16,301
  • 12
  • 61
  • 79
mjolinor
  • 66,130
  • 7
  • 114
  • 135
  • Does this configure the preference of the level of the profile, session or script? I can't really find an answer on that right away. – romatthe Jan 21 '14 at 14:19
  • It will do it in the first scope it finds the $PSDefaultParameterValues in. Updated the answer. – mjolinor Jan 21 '14 at 14:22
  • Thanks, very useful answer! I still think it's a bit weird to handle stuff like this, I'm comfortable with this solution right now. – romatthe Jan 21 '14 at 14:32
  • 1
    This doesn't explain why `$ErrorActionPreference = "Stop"` didn't work. Why do we have to set that default parameter in New-RegKey? – dan-gph Jan 22 '14 at 00:15
  • I wish I had an answer for that, but I do not. Modules appear to play some funny games with scope that nobody's gotten a good explanation for: http://stackoverflow.com/questions/2193410/strange-behavior-with-powershell-scriptblock-variable-scope-and-modules-any-sug There's this post about how to write your module so it will import the current preference settings: http://powershell.org/wp/2014/01/13/getting-your-script-module-functions-to-inherit-preference-variables-from-the-caller/ but I don't know a way to get it to do that outside of the module. – mjolinor Jan 22 '14 at 01:04
  • Same sort of problem with commands like Clear-Disk from the DISM cmdlets... what a mess. It makes it hard to take powershell seriously; I'm only a couple weeks in, and have already hit a few other things like this. Thanks @mjolinor for the workaround. – aggieNick02 Jun 19 '17 at 15:27
  • Note that specifying *:ErrorAction is not enough, you still need to also have $ErrorActionPreference = 'Stop'. Only the latter will catch things like calling $a.propertyThatDoesntExist when you have Set-StrictMode -Version Latest. – aggieNick02 Jun 28 '17 at 19:14