This is a longstanding issue that @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 #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.
Foo
No output.
Get-Item : Cannot find path '/Nosuch' because it does not exist.
...
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)
Module has its own context. It may be preferable to set the preference variable value for the module
Set-Variable -Module moduleName
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.PSVariable
based 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 /NoSuch
works,
$ErrorActionPreference = 'Ignore'; Get-MyItem /NoSuch
_not_ working, just because the Get-MyItem
command _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.
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:
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.
Preference variables is not protected and can be used other ways:
Indeed, but that is (a) incidental to the issue at hand and (b) should in itself be considered a bug: see #3483
We can get unpredictable behavior if we just copy any variables (and maybe indirectly functions) to a module context.
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.
What scenarios do we need to? It seems it is only debug.
Get-Item
vs. Get-MyItem
example.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 $ErrorActionPreference
to Stop
, 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.
Honoring the callers preference might cause serious problems by introducing exceptions (e.g. $errorActionPreference = 'Stop'
in places where none were expected.
Some examples - Dispose
might 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
.
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.
Honoring the callers preference might cause serious problems by introducing exceptions (e.g. $errorActionPreference = 'Stop' in places where none were expected.
I don't see this as a problem, since that's exactly what happens if the caller specifies -ErrorAction Stop
today. (Common parameters cause the equivalent preference variables to be set in the advanced function's scope.)
@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 using Stop
(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-Foo1
as a compiled cmdlet, and functionally equivalent Get-Foo2
as an advanced function _in a module_:
Get-Foo1
respects the verbose preference, Get-Foo2
does not.
# 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 -ErrorAction Stop
does 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.
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 Stop
and 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.
Keep in mind one big difference between binary cmdlets and functions - binary cmdlets are rarely implemented in terms of PowerShell functions or other cmdlets.
Users needing to be aware of how a given command happens to be implemented is an unreasonable expectation.
Aside from that, I don't understand your comment.
The error or verbose output is likely carefully crafted for a binary cmdlet, whereas it's a bit more random for functions.
PowerShell has evolved toward making the PowerShell language a first-class citizen with respect to creating cmdlets (advanced functions).
Someone skilled enough to create advanced functions as part of a module should be assumed to exercise the same care as someone creating a compiled cmdlet.
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.
If I, as a user, set $VerbosePreference = 'Continue'
_scope-wide_, I expect _all_ commands invoked subsequently to honor that settings; if I didn't want that, I would use -Verbose
on a _per-command_ basis.
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 [System.Management.Automation.CmdletBindingAttribute]
property such as [CmdletBinding(UseCallerPreferences)]
.
the equivalence of -ErrorAction Stop and setting the preference variable
Again I don't fully understand your comment; at the risk of going off on a tangent:
They are _not_ equivalent: -ErrorAction Stop
only affects _non-terminating_ errors (escalates them to _script_-terminating errors); $ErrorActionPreference = 'Stop'
affects both non-terminating and statement-terminating errors - see Our Error Handling, Ourselves - time to fully understand and properly document PowerShell's error handling.
Users needing to be aware
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 -Verbose
in a way that differs from the user.
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.
Users of a command aren't aware of the implementation and do have the expectation of reasonable output.
No argument there. And let's not forget predictable behavior, which brings us to the next point:
I do wonder if that's where some of this feedback is coming from
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 $VerbosePreference
, but let's consider $WhatIfPreference
and $ConfirmPreference
:
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 $ErrorActionPreference
: An approach I've personally used it to set $ErrorActionPreference = 'Stop'
at the start of a script as _fallback_ and then handle errors that I _anticipate_ on a per-command basis. That way, unanticipated errors cause the script to abort, as a fail-safe - or so I'd hoped.
Also note that the equivalence of -ErrorAction Stop and 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.
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_.
By itself, this conception runs counter to how variables work in PowerShell, at least with respect to _descendant_ scopes.
To be consistent with how PS variables work, you'd have to define a variable as $private:
in order to limit it to the current scope only.
Certainly, given that core cmdlets currently _do_ honor the preference variables, changing that behavior - selectively, for preference variables only - would be a serious breaking change and would require a focused effort to reframe the purpose of preference variables.
Leaving the syntax and backward compatibilities issues aside, I can see how _some_ preference variables can be helpful as limited to the current scope:
$DebugPreference
and $VerbosePreference
and $ErrorActionPreference
only applied to Write-Debug
and Write-Verbose
and Write-Error
commands issued directly in the current scope could help with debugging only a given script, without being distracted by debug/verbose commands / errors from _commands_ invoked from the current scope.
As an aside: As stated, against documented behavior, $ErrorActionPreference
currently also takes effect for _terminating_ errors.
$private:VerbosePreference = 'Continue'
. On a side note: _that doesn't actually work_ - descendant scopes still see the value, which means that any non-module-defined functions currently _do_ respect these preferences either way.And, again, just as I and @dlwyatt have, I assume that many people are currently _relying_ on $ErrorActionPreference
applying to errors reported by _commands_ called from the current scope too - or at least have had that expectation, which currently only holds true for _compiled_ cmdlets by default, a distinction that brings us back to the original problem.
I found that specifying $global:
addresses this for progress bar issues.
$global:ProgressPreference = 'silentlycontinue'
at the top of a script suppresses the performance killing progress indicator when downloading things.
It's probably easier to do that with VerbosePreference than adding -Verbose:($PsCmdlet.MyInvocation.BoundParameters['verbose'] -eq $true)
to every single command my script calls.
It seems to me that when considering code inside a script module there are two distinct kinds of code to which preference variables (eg. $ErrorActionPreference
, etc) and common parameters (eg. -ErrorAction
, etc) might apply:
$ErrorActionPreference='Stop'
.$VerbosePreference
and -Verbose
at the call site of module entry points.$DebugPreference
, $WarningPreference
, and $InformationPreference
for such code should probably be the PowerShell defaults regardless of what the caller's values of those variable are.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
}
HonorCallerPrefs{}
and InvokeWithCallerPrefs{}
are implemented by the utility module and capture and apply, respectively, the preference variables and common parameters. The concept should work even if the call stack between HonorCallerPrefs{}
and InvokeWithCallerPrefs{}
is deep and complicated, as long as the calls to HonorCallerPrefs{}
and InvokeWithCallerPrefs{}
are in the same module.
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 NetSecurity
module such as Enable-NetFirewallRule
situationally do not honor the -WhatIf
switch, namely if invoked via an advanced function that itself supports -WhatIf
or via an explicitly set $WhatIfPreference
value, as discussed.
Revisiting @lzybkr's comment:
Also note that the equivalence of聽
-ErrorAction Stop
聽and setting the preference variable could be thought of as an implementation detail
From what I gather, PowerShell _implicitly_ translating a common parameter such as -WhatIf
into the equivalent locally scoped preference variable such as $WhatIfPreference
is _the_ mechanism for automatically propagating common parameters to calls to other cmdlets inside an advanced function.
So, yes, you can conceive of it as an implementation detail, but even as such it is broken.
@alx9r: As commendable as starting scripts / functions with $ErrorActionPreference = 'Stop'
is (it is what I usually do in my own code), it actually interferes with that mechanism (I have yet to take a look at your proof of concept).
Expand-Archive
.@mklement0
As commendable as starting scripts / functions with $ErrorActionPreference = 'Stop' is (it is what I usually do in my own code), it actually interferes with that mechanism...
Just to be clear. I did not suggest (or if I did, I did so unintentionally) "starting scripts / functions with $ErrorActionPreference = 'Stop'
" in the proof-of-concept I proposed. The proof-of-concept is specific to advanced functions in modules. I do suggest that, for advanced functions in modules, the code internal to the module be run with $ErrorActionPreference = 'Stop'
. But that is quite a different thing from, in general, "starting scripts / functions with $ErrorActionPreference = 'Stop'
".
From what I gather, PowerShell implicitly translating a common parameter such as -WhatIf into the equivalent locally scoped preference variable such as $WhatIfPreference is the mechanism for automatically propagating common parameters to calls to other cmdlets inside an advanced function.
That is my understanding too. I think, though, that that mechanism alone produces inadequate results.
FWIW, the proof-of-concept currently doesn't do any refereeing based on the impact of -WhatIf
, etc. I see no reason, though, why such a library couldn't be expanded deal correctly with that matter as well.
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 [CmdletBinding()]
ofc).
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:
$env:Path
that is invoked by name, are all just commands.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:
End users get a declarative syntax for every command they invoke. If they invoke with -Verbose
, they should get all verbose messages that the command author intended them to get. Ditto with -Debug
, but for debug messages. And if they use any of the -*Action
common parameters, that action needs to apply to everything done internally in that command and in any commands invoked internally within that command according to how the author _intentionally_ wrote the command.
Command authors need features that allow them to control the volume of output sent to various streams, as well as features that allow them to silence or ignore certain messages or errors. They have this already. The behavior of -Debug
has very recently changed such that users are no longer prompted when messages are sent to the debug stream, so now command authors have two levels of output for troubleshooting-type messages. This gives command authors more flexibility over the volume of output to specific message streams (they no longer just have the verbose stream to realistically work with). Similarly they have common parameters and preference variables to control other message streams and error handling in the commands that they invoke.
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 $PSDefaultParameterValues
feature, which allows users to assign default values to be used for parameters on commands that are invoked. While the globally visible $PSDefaultParameterValues
variable was initially accessible from modules (which caused problems because $PSDefaultParameterValues
is meant to be a user convenience for commands users invoke ad hoc or in scripts, but its use was also impacting commands used within module internals), in PowerShell v5, modules received their own $PSDefaultParameterValues
so that module commands could run as they were designed without this user convenience getting in the way.
What's most interesting about $PSDefaultParameterValues
with respect to this issue is that the values it contains impact the _parameters_ (not variables) used in the invocation of all commands in its scope and any child scope. What if $*Preference
variables continued to function as they do today (current scope plus child scopes unless they are redefined), but when a command is invoked using common parameters to control the various streams and how errors should be handled would not only assign the locally-scoped $*Preference
variable, but would also result in assignment of a locally-scoped dictionary (maybe as a property of $PSCmdlet
?) that works like $PSDefaultParameterValues
but with higher precedence (so $PSDefaultParameterValues
would still function if it was already in use), such that any command invoked within that command would automatically be invoked with the same common parameter values used in the invocation if those parameters were not specifically used.
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 output
As 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 PSCmdlet
instead.
Simple, right? :smile:
The only detail I find missing from this is related to how this can work with ShouldProcess
common parameters in the following scenario: If within a single module or script file you have one advanced function that supports ShouldProcess
, and therefore has a -WhatIf
common parameter, and if internally you invoke another advanced function in the same file that does not support -WhatIf
, the $WhatIfPreference
variable set in the first advanced function will influence behavior in the second advanced function. That always felt wrong to me, because the second advanced function doesn't support ShouldProcess
(i.e. if it's properly designed, it's deemed "safe"). It's generally not much of a problem, unless you invoke something like Set-Variable
where you may not want to use -WhatIf
. This is also a case where the way the command was written (advanced function in PowerShell vs cmdlet in C#) results in different behavior, even if the proposed solution is implemented. I'm going to sleep on it and see if I can come up with something that works for that scenario.
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 ShouldProcess
command that works with PowerShell entities in the current scope (e.g. any of the following commands invoked without -Scope
: Clear-Variable
, New-Variable
, Set-Variable
, Remove-Variable
, Import-Alias
, New-Alias
, Set-Alias
, along with any of their corresponding New-Item
, Set-Item
, or Remove-Item
commands) should run without checking $PSCmdlet.ShouldProcess
. i.e. If -Scope
is not used, or if -Scope
is 0, just do it regardless of WhatIf or Confirm preferences. Local scope operations for these commands should be considered safe, because they have no bearing on any parent scopes whatsoever, and are therefore non-destructive. This model should be followed for any command that supports ShouldProcess
for both actions that may be destructive as well as actions that are non-destructive -- just because ShouldProcess
is supported in the command, doesn't mean it should be the gatekeeper to all actions taken by that command.
This is an exception, but one easily explained and worth having because some commands should only cause -WhatIf
or -Confirm
to change how they run some of the time. For example, there are cases where you want to create a set of variables with dynamic names in a loop without ShouldProcess preferences getting in the way of invocation, and while you can do this using APIs, it should be possible to do this using cmdlets since that's what they are for. Command authors shouldn't have to learn by trial and error which one they should be using -- the cmdlets should just do the right thing.
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 https://github.com/PowerShell/PowerShell-RFC/pull/221
For anyone who has tripped on this issue and is following this discussion, please do share your feedback in https://github.com/PowerShell/PowerShell-RFC/pull/221.
That RFC, the way it is currently written, talks about adopting optional features in PowerShell (described in another RFC: https://github.com/PowerShell/PowerShell-RFC/pull/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 https://github.com/PowerShell/PowerShell/issues/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.
Most helpful comment
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 Stop
today. (Common parameters cause the equivalent preference variables to be set in the advanced function's scope.)