Need straightforward way to honor the caller's preference-variable values / inherited common parameters in functions defined in script modules #4568
Comments
|
Module has its own context. It may be preferable to set the preference variable value for the module |
|
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. Consider the user's perspective: If Giving module developers an easy, built-in way to opt into the caller's preferences would mitigate that problem. |
|
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. Remove-Variable ErrorActionPreference
$ErrorActionPreference =@()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!): Import-Module testModule -SetErrorActionPreference Stop |
|
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. |
Indeed, but that is (a) incidental to the issue at hand and (b) should in itself be considered a bug: see #3483 |
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. |
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 |
|
Honoring the callers preference might cause serious problems by introducing exceptions (e.g. Some examples - 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 |
|
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
I don't see this as a problem, since that's exactly what happens if the caller specifies |
|
@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 Let's take a more innocuous example: setting The following example defines
# Define compiled cmdlet Get-Foo1 (via an in-memory assembly and module).
Add-Type -TypeDefinition @'
using System;
using System.Management.Automation;
[Cmdlet("Get", "Foo1")]
public class GetFoo1 : PSCmdlet {
protected override void EndProcessing() {
WriteVerbose("foo1");
}
}
'@ -PassThru | % Assembly | Import-Module
# Define analogous advanced function Get-Foo2 via an in-memory module.
$null = New-Module {
Function Get-Foo2 {
[cmdletbinding()]
param()
End {
Write-Verbose("foo2");
}
}
}
$VerbosePreference = 'Continue'
# Compiled cmdlet respects $VerbosePreference.
Get-Foo1
# Verbose: foo1
# Advanced function in script module does NOT, because it doesn't see the caller's
# $VerbosePreference variable.
Get-Foo2
# (no output)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 I don't know what the right solution is, but if we can agree that there is a problem, we can tackle it. |
|
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 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. |
Users needing to be aware of how a given command happens to be implemented is an unreasonable expectation.
PowerShell has evolved toward making the PowerShell language a first-class citizen with respect to creating cmdlets (advanced functions).
If I, as a user, set Note that backward compatibility is a separate discussion - opting in to copying the caller's preference variables is a viable option in that context, perhaps via a new
Again I don't fully understand your comment; at the risk of going off on a tangent: They are not equivalent: |
I think you're emphasizing my point. Users of a command aren't aware of the implementation and do have the expectation of reasonable output. Command authors are a different story - they need to be aware of the implications of preference variables and parameters like In some cases, the command author and user are the same, and I do wonder if that's where some of this feedback is coming from - because the command author sometimes wants more help debugging during development. |
No argument there. And let's not forget predictable behavior, which brings us to the next point:
I can't speak for @dlwyatt, but to me this is all about the user perspective:
If I can't predict which of the commands I'll invoke will actually honor the preferences, I might as well do without preferences altogether and only use common parameters. We've covered If I set these with the expectations that all commands invoked from the same scope will honor them, I may be in for nasty surprises. Similarly, to return to |
I think I now understand what you're saying, and I hope I'm not wasting my breath based on a misinterpretation: It sounds like you're conceiving of preference variables as being limited to the current scope only, without affecting its descendant scopes or the potentially completely separate scopes of commands invoked.
Leaving the syntax and backward compatibilities issues aside, I can see how some preference variables can be helpful as limited to the current scope:
|
|
I found that specifying
It's probably easier to do that with VerbosePreference than adding |
|
It seems to me that when considering code inside a script module there are two distinct kinds of code to which preference variables (eg.
The above points aren't hard and fast, but I think they're a reasonable starting point for most modules involving a mix of business logic and calls made to outside the module. PowerShell, of course, does not behave this way by default. To implement a module that is consistent with the above points requires two things to happen as follows:
I suspect that these tasks can be handled reasonably well by a utility module. I wrote an annotated proof-of-concept of such a module. The entry points to a user module that uses the utility module would look, for example, like this: function Get-MyItem {
param(
[Parameter(Position=1)] $Path
)
HonorCallerPrefs {
Get-MyItemImpl $Path
}
}
function New-MyItem {
param(
[Parameter(Position=1)] $Path,
[switch] $WhatIf,
[switch] $Confirm
)
HonorCallerPrefs {
New-MyItemImpl $Path
}
}The corresponding sites that call out of the user module would look like this: InvokeWithCallerPrefs {
Get-Item $path @CallerCommonArgs
}
InvokeWithCallerPrefs {
New-Item $path @CallerCommonArgs
}
|
|
The recent #6556 is a more pernicious manifestation of the problem discussed here: Because CDXML-based cmdlets are seemingly advanced functions rather than binary cmdlets, the functions in the Revisiting @lzybkr's comment:
From what I gather, PowerShell implicitly translating a common parameter such as @alx9r: As commendable as starting scripts / functions with |
|
#6342 is a similar manifestation of the problem, in the context of |
|
This issue seems stalled, but it's such an important one, and with the upcoming PowerShell 7.0 milestone it would be really, really great if people put their heads together and come up with a model that makes sense for the caller, has the right default behavior, and that gives the developer the flexibility they need to override the default behavior if necessary. @SteveL-MSFT |
|
Agreed. If we can come to a conclusion on this it would be great to get a more consistent model in place. |
|
We could move forward if we created examples of the desired behavior in the form of Pester tests. |
|
The expectation from my POV is that caller preferences that do not affect the script or module execution paths are replicated out to every script or module that gets involved, as a default, without having to opt-in (other than This would be things like Verbose, Debug, Information, and Progress, but not something like ErrorActionPreference, PSEmailServer, or ModuleAutoLoadingPreference. |
|
I just read through this discussion again, taking some more time this time around to catch all of the details. Before I share what I believe may be a solution, I think it's worth summing up some of the key points, as follows:
With this issue as it exists today, only the first item in that list is true, and that is a problem that leads to non-intuitive behavior at best and bugs that cannot be easily worked around without duplicating a lot of code at worst. When it comes to invocation preferences, I strongly feel that all preferences (not necessarily preference variables, read on to see what I mean) that can be influenced by common parameters (Error, Warning, Information, Verbose, Debug, Progress, Confirm, and WhatIf) need to be carried through the entire invocation of a command, end to end, regardless of what other commands are invoked internally as part of that process. That means a few things:
Part of the discussion here suggests that it may be detrimental to carry through these preferences -- that users may receive too much verbose output, for example. What is/is not too much output from a command, regardless of which stream it is sent to, is a decision that must be made by the command author, intentionally, not incidentally. Command authors can turn verbose/debug output off for other commands that they invoke, if they feel that information is not helpful/relevant to the caller. They can also silence or ignore errors, warnings, or information messages in commands that they invoke, regardless of how their command is being invoked. The decision on what to allow to pass through or not needs to be a conscious one, not accidental, made by the command author, and it should respect the way the command was invoked by default (common parameters). It should never have been something that a caller should concern themselves with by having to pay attention to the nuances associated with type of command they are invoking and the way that command was loaded. It is also worth pointing out how we got here, and some relevant changes, because that information may lead to a solution to this problem. We've had common parameters and preference variables in PowerShell since v1 was released in 2006. When PowerShell v2 came out in 2009, the PowerShell Team added modules (along with the scoping they have today that contributes to this problem because of how all variables, including preference variables, are only accessible in nested scopes), as well as the addition of the largely underused What's most interesting about For example, consider this script: # First, create two modules, where a command in one module invokes a command in the other
nmo -name test1 {
function Test-1 {
[cmdletBinding()]
param()
Test-2
}
} | ipmo
nmo -name test2 {
function Test-2 {
[cmdletBinding()]
param()
Write-Verbose 'Verbose output'
}
} | ipmo
# The next command shows verbose output, as expected
Test-2 -Verbose
# Output: VERBOSE: Verbose output
# This command does not show verbose output, because of the design flaw identified in this GitHub issue
Test-1 -Verbose
# Output: none
# Now let's redefine the test1 module, with some extra logic to assign a locally-scoped
# PSDefaultParameterValues hashtable based on the Verbose common parameter (this would be a
# property of PSCmdlet, but I'm just using PSDefaultParameterValues to show how it could work).
nmo -name test1 {
function Test-1 {
[cmdletBinding()]
param()
$PSDefaultParameterValues = @{'*:Verbose' = ($VerbosePreference -eq 'Continue')} # The magic
Test-2
}
} | ipmo
# Now this works, because of PSDefaultParameterValues impact on common parameters
Test-1 -Verbose
# Output: VERBOSE: Verbose outputAs you can see, if we maintain the current scope visibility on preference variables, but apply a locally-scoped collection based on common parameters, then we get commands whose internal behavior is end-to-end as a default, resolving this issue. Command/script authors who are assigning preference variables that are not globally scoped in hopes that they will get this behavior will be provided with a solution that allows them to get this more desirable functionality, and rules can be created for PowerShell Script Analyzer to catch these variable assignments and recommend how users should work with the locally scoped collection in Simple, right? The only detail I find missing from this is related to how this can work with |
|
Thinking about the last paragraph in my previous comment a little more, I'm inclined to push for the following to address this (and yes, I'll log this as a separate issue): Any This is an exception, but one easily explained and worth having because some commands should only cause If that gets accepted, then the last paragraph in my previous post in this discussion becomes a non-issue. |
|
FYI, I opened a set of RFCs to address error handling issues in PowerShell, this one included. If you're keen and you don't want to wait for the RFCs to reach the draft stage, you can find the PR with those RFCs here. |
|
This is still a problem and still needs to be addressed. Just adding another voice to the pile here... I ran into difficulties from this, figured out the origin of my difficulties and ended up here after some web searches. |
|
@nacitar You can leave a feedback in RFC PowerShell/PowerShell-RFC#221 |
|
For anyone who has tripped on this issue and is following this discussion, please do share your feedback in PowerShell/PowerShell-RFC#221. That RFC, the way it is currently written, talks about adopting optional features in PowerShell (described in another RFC: PowerShell/PowerShell-RFC#220). There is some pushback on the optional feature idea because it requires time/resources to properly manage, and it may be too risky to be worth it to the PowerShell Team (see discussion in optional features RFC), in which case you need to ask yourself: is the current problem with how execution preferences do not propagate beyond module or script scope worth fixing even if it may result in breaking changes? Breaking changes may occur because you would be changing how execution preferences influence existing code without specifically testing that code to make sure that it still works as intended. From my perspective, I think it is worth it, because the current behavior is the root cause of many issues that show up in different modules. If folks agree, and if the optional feature idea is officially rejected, I can at least set up an experimental feature (these are different from optional features) to allow folks to try it out with their modules and make sure things still work the way they intended with execution preferences properly propagating beyond module or script scope. |
|
EDIT: Comment Moved to #12148 |
|
Couldn't say off the top of my head, but given that you're setting $VerbosePreference immediately before it, I would be inclined to say that it's likely a separate issue, something to do with how WhatIf only see verbose set by the switch and not an in-session variable. |
|
Sorry if this has already been mentioned, but this thread is quite long so I mostly just skimmed it. I believe there isn't a single "right answer" here: For Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"as a header in every module I write, because I want to fail hard when an unhandled error occurs, and my code is written with this in mind. So just blindly copying the variable from caller scope is imo not a good approach, because scripts that do not declare the variable themselves will work differently in different sessions. For output level preference variables ( EDIT: I just tested my assumption from the last paragraph, and surprisingly, this is NOT the current behavior. When |
|
@MatejKafka: There are two separate aspects, which are independent of one another:
|
|
@mklement0 I agree that the behavior being inconsistent is an issue, but it seems we partially disagree in which direction they should be unified. Ad 1) - I'm more of a programmer than sysadmin - the concept of a non-terminating error seems like something terrifying to me - an error occurred, meaning that something bad happened and I did not correctly anticipate it and handle it, and the program is in an undefined state. Continuing ahead instead of panicking is imo irresponsible, as I cannot further safely reason about the behavior of my own program. Non-terminating error should imo either be a terminating error, or a warning, not a weird mix of both. I also edited my previous comment, as I found out that my assumption in the last paragraph isn't correct. |
It shouldn't do. There's a difference between errors and exceptions. Whether an error is "exceptional" depends on the context. If you've correctly functionally decomposed your code into a series of small pipeline functions each function by itself can't determine if an error is an exception, because it can't own that policy because it doesn't know why it's being called. Errors from that function should bubble through the pipeline to the creator of the pipeline who can, in that context, determine whether the error is exceptional in that context/pipeline. |
|
As a general rule, non-terminating errors tend to be something that comes from an individual object input in the pipeline. If one bad piece of data is received, you don't necessarily want to cancel processing the next 10,000 bits of input. On the other hand, if the error comes from bad / unusable input as the result of a fixed (non-pipeline) parameter input, you can deduce that processing all 10,000 bits of input will yield with the same result (say, for example, the cmdlet requires authentication, and the authentication provider you're using indicates that the credentials specified are invalid) -- then you can throw a terminating error to avoid wasting time trying the known-bad credentials 10,000 times. |
|
I would prefer we do not use this Issue to discuss/debate PowerShell's error handling models. There are existing Issues you can pile onto, or start a new one. |

Formed in 2009, the Archive Team (not to be confused with the archive.org Archive-It Team) is a rogue archivist collective dedicated to saving copies of rapidly dying or deleted websites for the sake of history and digital heritage. The group is 100% composed of volunteers and interested parties, and has expanded into a large amount of related projects for saving online and digital history.

This is a longstanding issue that @dlwyatt explained in detail in this 2014 blog post, and he has even published module
PreferenceVariableswith advanced functionGet-CallerPreferenceto 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 asNetSecurityandMicrosoft.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 #4394.
A simple demonstration of the problem:
Desired behavior
No output.
Current behavior
Environment data
The text was updated successfully, but these errors were encountered: