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'
)
finally
clause of a try
/ catch
/ finally
statement.Or: On a call-by-call basis, use the corresponding common parameters (e.g. -ErrorAction Stop
)
-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 a try
/ catch
or a trap
statement. See this answer for background information.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.