3

This is my script in file test.ps1:

$ErrorActionPreference = "Stop" # make it throw an exception
try
{
  Get-Volume -FileSystemLabel "doesnotexist"
}
catch [Microsoft.PowerShell.Cmdletization.Cim.CimJobException]
{
  Write-Host "exception thrown"
}

I run it from a PowerShell terminal like this:

./test.ps1

But it does not throw the exception and instead prints out a red error message.

If you're familiar with PowerShell you will understand that it should throw an exception instead.

Why doesn't it throw an exception as expected?

zett42
  • 25,437
  • 3
  • 35
  • 72
javanerd
  • 163
  • 1
  • 4
  • 1
    It looks like the reason is that `Get-Volume` is implemented as a script function, rather than a compiled cmdlet, in Windows PowerShell. There is a known issue with cmdlets implemented as script functions - preference variables like `$ErrorActionPreference` are not respected by default, unless they are global variables. See https://stackoverflow.com/a/55841499/7571258 – zett42 May 26 '23 at 17:19

2 Answers2

2

You need to do one of the following:

  • Set $ErrorActionPreference = "Stop" before calling the script
  • Use $Global:ErrorActionPreference = "Stop" in the script
  • Execute the script as powershell.exe -file test.ps1
  • Use Get-Volume -FileSystemLabel "notreal" -ErrorAction Stop in the script
nimrod
  • 102
  • 4
1

zett42 has provided the crucial pointer:

  • The Get-Volume command is implemented as a function that is part of the Storage module, which is a (CDXML-based) script module, i.e. implemented in PowerShell code.

  • Due to an unfortunate design limitation - see GitHub issue #4568 - module-based functions do NOT see the preference variables of outside callers - they only see such variables if they're defined in the global scope (which is the only scope that modules and non-module code share).

    • Note: Binary cmdlets - i.e. those implemented as .NET assemblies (typically compiled from C# code) - are not affected.
  • Therefore, your script-scoped $ErrorActionPreference = "Stop" definition had no effect.


Workarounds:

  • [Suboptimal]: Set $ErrorActionPreference in the global scope:

    • $global:ErrorActionPreference = 'Stop'

    • Important: Given that global definitions apply session-wide and linger after your script exits, be sure to restore the previous value of $global:ErrorActionPreference before exiting your script.

  • Preferably, use the common -ErrorAction parameter with Get-Volume, which is a command-scoped way of setting $ErrorActionPreference, which module-based functions honor as well.[1]

try
{
  # -ErrorAction Stop promotes the *non-terminating* error that Get-Volume
  # emits to a (script)-*terminating* one, which triggers the `catch` block.
  Get-Volume -ErrorAction Stop -FileSystemLabel "doesnotexist"
}
catch [Microsoft.PowerShell.Cmdletization.Cim.CimJobException]
{
  Write-Host "exception thrown"
}

[1] Behind the scenes, when -ErrorAction is used with an (of necessity advanced) function - whether module-based or not - PowerShell translates the value into a function-local $ErrorActionPreference variable.
Even though this should be a mere implementation detail, it can unfortunately have side effects - see GitHub issue #19500.

mklement0
  • 382,024
  • 64
  • 607
  • 775