I have the following script:
$ErrorActionPreference = "Stop"
Install-Module 'DoesNotExist' -Force
Write-Host "This should not be printed!"
I’d assumed, that if an error occurs in Install-Module
, it terminates, because I set $ErrorActionPreference
to Stop
. Unfortunately it doesn’t.
What even makes it weirder is, that if I set $ErrorActionPreference
in the global
scope to Stop
, it works.
$global:ErrorActionPreference = "Stop"
You’re seeing an unfortunate design limitation, discussed in GitHub issue #4568:
Problem:
Commands implemented as PowerShell functions that originate in modules:
-
do not see any preference variables set in the caller’s scope…
-
… except if the calling scope happens to be the global one – which is typically not the case.
Unfortunately, there are no good solutions to this problem, only cumbersome workarounds:
Workaround for callers:
A non-global caller, such as a script or a function (which run in a child scope by default) must:
-
Either: Explicitly set preference variables in the global scope (e.g.,
$global:ErrorActionPreference="Stop"
)- So as not to affect subsequently executing code, be sure to restore their original values afterwards, which – for robustness – must be done in the
finally
clause of atry
/catch
/finally
statement.
- So as not to affect subsequently executing code, be sure to restore their original values afterwards, which – for robustness – must be done in the
-
Or: On a call-by-call basis, use the corresponding common parameters (e.g.
-ErrorAction Stop
)- The challenge with
-ErrorAction Stop
is that it is not fully equivalent to$global:ErrorActionPreference="Stop"
in that only acts on non-terminating errors, and not also on statement-terminating ones, additionally necessitating atry
/catch
or atrap
statement. See this answer for background information.
- The challenge with
These workarounds are spelled out in detail in the middle section of this closely related answer.
Generally, the challenge is to know when a workaround is even needed, as a given command’s name doesn’t reveal whether it is PowerShell-implemented and whether it comes from a module.
You can use the following test for a given command; if it returns $true
, the workaround is needed:
# -> $true,
# because Install-Module is from a module and implemented as a PowerShell function.
& {
$cmd = Get-Command $args[0]
$cmd.CommandType -eq 'Function' -and $cmd.ModuleName
} Install-Module
Workaround for module authors:
Dave Wyatt has authored a helper module, PreferenceVariables
, whose Get-CallerPreference
function can be used inside module functions to obtain an outside caller’s preference variables.
In other words: You can use this to overcome the design limitation and make your PowerShell-implemented cmdlet (advanced function) behave like binary cmdlets with respect to the caller’s preference variables.[1]
Get-CallerPreference
use is explained in this blog post.
Note: You don’t strictly need another module to implement this functionality, it – which doesn’t require much code – could be integrated directly into a module.
[1] There are other, subtle differences, discussed in this answer.