powershellpowershell-corepowershell-7.0

Why is `Install-Module` not respecting $ErrorActionPreference=Stop from the script scope?


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"

Solution

  • You're seeing an unfortunate design limitation, discussed in GitHub issue #4568:

    Problem:

    Commands implemented as PowerShell functions that originate in modules:

    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:

    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.