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')finallyclause of atry/catch/finallystatement.Or: On a call-by-call basis, use the corresponding common parameters (e.g.
-ErrorAction Stop)-ErrorAction Stopis 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/catchor atrapstatement. 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:Workaround for module authors:
Dave Wyatt has authored a helper module,
PreferenceVariables, whoseGet-CallerPreferencefunction 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-CallerPreferenceuse 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.