Repository navigation
Description
This is a longstanding issue that Dave Wyatt (@dlwyatt) explained in detail in this 2014 blog post, and he has even published module PreferenceVariables with advanced function Get-CallerPreference to ease the pain, via a hard-coded list of preference variable names.
In short: A script module's functions do not see the preference-variable values set in the caller's context (except if that context happens to be the global one), which means that the caller's preferences are not honored.
Update: Since implicitly setting preference variables is PowerShell's method for propagating (inheriting) common parameters such as -WhatIf, such parameters are ultimately not honored in calls to script-module functions when passed via an advanced function - see #3106, #6556, and #6342. In short: the common-parameter inheritance mechanism is fundamentally broken for advanced functions across module scopes, which also affects standard modules such as NetSecurity and Microsoft.PowerShell.Archive .
-
From a user's perspective this is (a) surprising and (b), once understood, inconvenient.
Additionally, given that compiled cmdlets do not have the same problem, it is not easy to tell in advance which cmdlets / advanced functions are affected. In a similar vein, compiled cmdlets proxied via implicitly remoting modules also do not honor the caller's preferences. -
From a developer's perspective, it is (a) not easy to keep the problem in mind, and (b) addressing the problem requires a workaround that is currently quite cumbersome, exacerbated by currently not having a programmatic way identify all preference variables in order to copy their values to the callee's namespace - see Improving the discoverability of PowerShell variables #4394.
A simple demonstration of the problem:
# Define an in-memory module with a single function Foo() that provokes
# a non-terminating error.
$null = New-Module {
function Foo {
[CmdletBinding()] param()
# Provoke a non-terminating error.
Get-Item /Nosuch
}
}
# Set $ErrorActionPreference in the *script* context:
# Request that errors be silenced.
# (Note: Only if you defined this in the *global* context would the module see it.)
$ErrorActionPreference = 'SilentlyContinue'
# Because the module in which Foo() is defined doesn't see this script's
# variables, it cannot honor the $ErrorActionPreference setting, and
# the error message still prints.
FooDesired behavior
No output.
Current behavior
Get-Item : Cannot find path '/Nosuch' because it does not exist.
...
Environment data
PowerShell Core v6.0.0-beta.5 on macOS 10.12.6
PowerShell Core v6.0.0-beta.5 on Ubuntu 16.04.3 LTS
PowerShell Core v6.0.0-beta.5 on Microsoft Windows 10 Pro (64-bit; v10.0.15063)
Windows PowerShell v5.1.15063.483 on Microsoft Windows 10 Pro (64-bit; v10.0.15063)
Activity
iSazonov commentedon Aug 14, 2017
Module has its own context. It may be preferable to set the preference variable value for the module
Set-Variable -Module moduleNamemklement0 commentedon Aug 14, 2017
Yes, modules have their own scope, which is generally a good thing.
However, this special case calls for a concise, PS-supported way to copy all the preference-variable values from the caller's scope.
(The current workaround of using
$PSCmdlet.SessionState.PSVariablebased on a hard-coded list of preference variable names is neither future-proof nor reasonable.)Consider the user's perspective:
If
$ErrorActionPreference = 'Ignore'; Get-Item /NoSuchworks,$ErrorActionPreference = 'Ignore'; Get-MyItem /NoSuchnot working, just because theGet-MyItemcommand happens to be defined as an advanced function in a script module, is highly obscure and confusing.Giving module developers an easy, built-in way to opt into the caller's preferences would mitigate that problem.
iSazonov commentedon Aug 15, 2017
Main module's function is to isolate code that expose public API and hide implementation. We can get unpredictable behavior if we just copy any variables (and maybe indirectly functions) to a module context.
Also internally there are difference of global and local preference variables.
Preference variables is not protected and can be used other ways:
We may have problems if these variables are simply copied in a module context.
What scenarios do we need to? It seems it is only debug. If so we could resolve this otherwise, for example, by loading a module in debug mode. If we want to manage the preference variables in a module, we must do this in a predictable way (We can load tens modules in session!):
iSazonov commentedon Aug 15, 2017
Also we have an Issue with the request to share modules between runspaces. If we want implement this we can not simply copy any variables - no single parent context exists.
mklement0 commentedon Aug 15, 2017
Indeed, but that is (a) incidental to the issue at hand and (b) should in itself be considered a bug: see #3483
mklement0 commentedon Aug 15, 2017
We're not talking about any variables. We're talking about the well-defined set of preference variables - even though, as stated, there's currently no programmatic way to enumerate them.
mklement0 commentedon Aug 15, 2017
Get-Itemvs.Get-MyItemexample.This is very much a production issue, unrelated to debugging:
Currently, if you create a script module, that module's functions will by default ignore a caller's preferences variables (unless you're calling from the global scope), which to me is self-evidently problematic:
For instance, if a caller sets
$ErrorActionPreferencetoStop, they have a reasonable expectation that cmdlets / advanced functions invoked subsequently honor that setting - without having to know whether a given command happens to be defined in a script module that doesn't see that setting by default.lzybkr commentedon Aug 15, 2017
Honoring the callers preference might cause serious problems by introducing exceptions (e.g.
$errorActionPreference = 'Stop'in places where none were expected.Some examples -
Disposemight not get called and resources leak, or the system may be left in an inconsistent state because some side effects do not execute.If the goal is to silence a noisy function - one can work with the function author to provide a better experience or else explicitly pass
-ErrorActionPrefernce SilentlyContinue.dlwyatt commentedon Aug 15, 2017
I think the focus is too much on the implementation here and not on the problem: a user shouldn't have to know or care how a particular PowerShell command is implemented. Whether it's a cmdlet, function, cdxml, imported from a remote session, whatever: they should have a consistent experience with regard to common parameters and preference variables. If I put
$VerbosePreference = 'Continue'in my script, I should expect to see all the verbose output that I asked for. If I say$ErrorActionPreference = 'Stop', then anything that bubbles up through the Error stream to my code should cause a terminating error.I don't see this as a problem, since that's exactly what happens if the caller specifies
-ErrorAction Stoptoday. (Common parameters cause the equivalent preference variables to be set in the advanced function's scope.)mklement0 commentedon Aug 15, 2017
Dave Wyatt (@dlwyatt): Amen to that. I was in the middle of composing the following:
The point is that if compiled cmdlets respect preference variables set in the caller's scope, it's reasonable to expect advanced functions to respect them too - the user shouldn't have to worry about module scopes or how a given command happens to be implemented.
With respect to
$ErrorActionPreference = 'Stop': It sounds like usingStop(whether via the pref. variable or the common parameter) is inherently problematic, which is a separate conversation (and I personally wasn't aware that it's problematic - probably worth documenting).Let's take a more innocuous example: setting
$VerbosePreference = 'Continue'in an effort to make all cmdlet invocations in the current script produce verbose output:The following example defines
Get-Foo1as a compiled cmdlet, and functionally equivalentGet-Foo2as an advanced function in a module:Get-Foo1respects the verbose preference,Get-Foo2does not.Expecting the user to even anticipate a distinction here and to then know which commands are affected based on how they happen to be implemented strikes me as highly obscure.
A scenario in which the behavior is even more obscure is with implicit remoting modules that proxy compiled cmdlets that execute remotely. In that case, the remotely executing cmdlets, even though they normally respect the caller's preference, do not. (On a related note: even
-ErrorAction Stopdoes not work in that scenario, because the parameter is applied remotely, and terminating errors that occur remotely are by design converted to non-terminating ones.)I don't know what the right solution is, but if we can agree that there is a problem, we can tackle it.
lzybkr commentedon Aug 15, 2017
Keep in mind one big difference between binary cmdlets and functions - binary cmdlets are rarely implemented in terms of PowerShell functions or other cmdlets.
The error or verbose output is likely carefully crafted for a binary cmdlet, whereas it's a bit more random for functions.
Also note that the equivalence of
-ErrorAction Stopand setting the preference variable could be thought of as an implementation detail and is considered a bug by some people for some of the reasons mentioned above.It's worth pointing out that extra verbosity is not always desirable - and this proposal could turn some useful verbose output into noisy useless output.
61 remaining items
JosephColvin commentedon Apr 28, 2025
RiverHeart You could remove any variable ending with "preference" from being hardcoded by changing your variable definition of Preferences to:
If you would rather, you could also use
Get-Variable -Scope 'Global' | Where-Object { $_.Name -ilike '*preference' } | Select-Object -ExpandProperty 'Name'to do the same thing instead of theGet-ChildItem -Path "Variable:*preference" | Select-Object -ExpandProperty 'Name'This of course does have the side effect of picking up any variable ending in the word "Preference" which I see why one wouldn't want to and why you would want to.Thank you though RiverHeart for sharing as that has proven to be far better than what I have been doing!
boernsen-development commentedon Jul 8, 2025
I tested the demonstration block from the first post and it seems to work as intended, i.e. no output is produced (as opposed to when I define
$ErrorActionPreference = 'Stop'.Has this been fixed already?
RiverHeart commentedon Jul 8, 2025
I don't think so. If you run the example from the terminal it works since
$ErrorActionPreference = 'Stop'also sets$Global:ErrorActionPreferencewhich the module can see but if you put that code in a script and call it (eg./CallPrefTest.ps1) instead of using ISE or VSCode it behaves as described since only script scope is changed.boernsen-development commentedon Jul 17, 2025
Just another example showing that the
-Confirmswitch is ignored when calling other module's functions.Consider:
Expected: no confirm question is asked due to
-Confirm:$falseActual: the confirm question is asked despite
-Confirm:$falseThis breaks automation scripts, which rely on
-Confirm:$false. I hope this affirms the importance of this issue.Do you know of any simple workaround for this without much overhead?
boernsen-development commentedon Jul 17, 2025
Even worse with
-WhatIf:Expected output:
What if: Performing the operation "destructive process" on target "sub".Actual output:
applying destructive processmicrosoft-github-policy-service commentedon Jan 13, 2026
This issue has not had any activity in 6 months, if there is no further activity in 7 days, the issue will be closed automatically.
Activity in this case refers only to comments on the issue. If the issue is closed and you are the author, you can re-open the issue using the button below. Please add more information to be considered during retriage. If you are not the author but the issue is impacting you after it has been closed, please submit a new issue with updated details and a link to this issue and the original.
boernsen-development commentedon Jan 13, 2026
Please don't close this. It's still important as it breaks automations, can cause unintended destructive operations and complicates debugging (see comments above).
RiverHeart commentedon Feb 13, 2026
Noticed another way to get the variables from the caller that is somewhat similar to
Get-CallerPreferencemicrosoft-github-policy-service commentedon Nov 11, 2026
This issue has not had any activity in 6 months, if there is no further activity in 7 days, the issue will be closed automatically.
Activity in this case refers only to comments on the issue. If the issue is closed and you are the author, you can re-open the issue using the button below. Please add more information to be considered during retriage. If you are not the author but the issue is impacting you after it has been closed, please submit a new issue with updated details and a link to this issue and the original.
boernsen-development commentedon Nov 12, 2026
Issue still exists. Workaround with custom solution (
Get-CallerPreference) exists, but is error-prone as it requires importing and adding the call to every function. If it is forgotten and somebody relies on-WhatIffor destructive operations, it may lead to unexpected dataloss.