Follow-up from #13636:
_Note_:
Substantially revised after the discussion below.
While the _verbose_ stream is being discussed, the same applies analogously to the warning, debug, and information streams.
As a script/function author, I want to be able to create advanced functions / scripts that are cmdlet-like and deliberately limit verbose output to explicit Write-Verbose calls only (by default).
Because of PowerShell's dynamic scoping, the stream-controlling $*Preference variables are visible to all child scopes of a given script/function and also apply to cmdlet calls inside them.
This also applies to the stream-controlling common parameters (e.g., -Verbose), which PowerShell automatically translates into function/script-local $*Preference variables.
While this can be helpful in troubleshooting a function or script, it can also lead to a flood of unwanted verbose output that drowns out the verbose output of interest.
(A related problem are the verbose messages that can surface due to _auto-loading_ of the module containing the command being invoked - see #13657.)
For instance, in the following (simulated) advanced function, I may want to show the user the explicit Write-Verbose call's output _only_, not also the verbose messages produced by the Import-Module call, and the (simulated) call to another script or function.
# The script block represents an *advanced function*.
& {
[CmdletBinding()]
param()
# This also produces verbose output.
Import-Module PSReadLine
# Simulate a call to other PowerShell functions / scripts:
# This also produces verbose output.
& {
Write-Verbose 'in child scope'
}
# The only explicit Write-Verbose call
Write-Verbose Done.
} -Verbose
For carefully crafted, cmdlet-like advanced scripts/functions, it would be helpful to have an _opt-in_ mechanism that makes PowerShell limit verbose output to explicit Write-Verbose calls only (by default), so that the verbose output isn't "polluted" by verbose output from internal calls that should be considered implementation details.
While there are workarounds, they are cumbersome; here's one, which demonstrates the desired behavior:
& {
[CmdletBinding()]
param()
# If verbose output is turned on, hide the setting from the descendant scopes,
# but make all Write-Verbose calls *in this scope only* emit verbose output.
if ($VerbosePreference -eq 'Continue') {
$VerbosePreference = 'SilentlyContinue' # Hide from cmdlet calls and descendant scopes.
# Apply only to Write-Verbose calls in the current scope.
$private:PSDefaultParameterValues = $PSDefaultParameterValues ? $PSDefaultParameterValues.Clone() : [System.Management.Automation.DefaultParameterDictionary]::new()
$private:PSDefaultParameterValues['Write-Verbose:Verbose'] = $true
}
# This is now quiet.
Import-Module PSReadLine
# Simulate a call to other PowerShell functions / scripts:
# This is now quiet too.
& {
Write-Verbose 'in child scope'
}
# Only this will print.
Write-Verbose Done.
} -Verbose
Implement a new StreamPreferencePropagation property on the CmdletBinding attribute, which defaults to $true and can be set to $false (analogous to the PositionalBinding property):
& {
# Note the new property.
[CmdletBinding(StreamPreferencePropagation = $false)]
param()
# This is now quiet.
Import-Module PSReadLine
# Simulate a call to other PowerShell functions / scripts:
# This is now quiet too.
& {
Write-Verbose 'in child scope'
}
# Only this will print.
Write-Verbose Done.
} -Verbose
I think this will annoy roughly as many people as it pleases.
There are functions which have no write-verbose statements which are wrappers for another command which we want the detail from (IIRC I did this a lot to find what went into invoke-restmethod in other functions).
I've also written functions where I expressly called other things with -verbose:$false because I did write to verbose in the function, and tens or hundreds of function calls with verbose made it hard to see the wood for the trees.
I'm not sure which is the more common case, but my gut says its not 10 or 20 of one to one of the other.
Yeah I don't think this will be a clear cut case either way. I can see times I'd want this and times it would make things really difficult for me as an author.
I get that this a tricky proposition, but I wanted to get the conversation started.
I've also written functions where I expressly called other things with -verbose:$false because I did write to verbose in the function, and tens or hundreds of function calls with verbose made it hard to see the wood for the trees.
This scenario is precisely what this proposal is trying to address.
While adding -verbose:$false to all internal calls is an _effective_ workaround, it is obviously both very cumbersome and prone to errors by omission.
However, as noted in the "Conceptual challenge" paragraph in the OP, the line between scripts that are carefully crafted like cmdlets (which benefit from the proposed change) and "glue" scripts that simply tie together other commands for a custom task (to which the change should _not_ apply) isn't easy to draw _automatically_.
Incidentally, the same distinction would make sense for the warning, debug, and information streams.
So perhaps the answer is to allow advanced scripts / functions to request the non-propagation behavior _explicitly_, perhaps as part of the [CmdletBinding()] attribute; e.g.:
[CmdletBinding(PreferencePropagation=$false, ...)]
Based on the feedback I've completely rewritten the OP:
I'm now asking for an _opt-in_ mechanism, via the [CmdletBinding()] attribute - this preserves the existing behavior while giving authors the _option_ to carefully craft cmdlet-like functions/scripts that produce deliberate verbose output only.
When I first read through this, my reaction was similar to jhoneill and vexx32, along the lines of "this will annoy roughly as many people as it pleases" (or more). Because in a non-trivial module, I typically have functionality broken up into lots of functions--it's an entire library, with everything working together. So what happens is, yeah, sometimes I might want Verbose output only from "just my code", but that's never just one function; it calls several helpers, which each call several helpers, etc.
With the "opt-in" mechanism... maybe that could be reasonable, but I'm still not convinced it's necessary or would even do what most people want.
Not necessary: My strategy for dealing with this is to simply hardcode -Verbose:$false on external calls that I don't want to see Verbose output for. For example, calls to Import-Module (which of course requires me to also explicitly pre-load dependencies to avoid #13657, ugh).
Not what people want: as mentioned in my opening paragraph, I believe what is actually wanted is "Verbose output for 'Just My Code'", not "Verbose output for 'Just This Function'".
Perhaps the idea could be re-worked again, from a per-function opt-in thing, to something like a still-dynamically-scoped-but-stops-at-module-boundaries concept. But given that I don't think it's necessary in the first place, that seems like a lot of complication for no good reason.
Does my strategy for (1) not cover some scenario?
@jazzdelightsme
something like a still-dynamically-scoped-but-stops-at-module-boundaries concept
It actually _does_ stop a module boundaries, but that is in itself highly problematic: #4568
Does my strategy for (1) not cover some scenario?
Yes, it's an effective approach (which @jhoneill also mentioned), but my concern was that it is cumbersome and easy to forget.
But the point about intra-module helper code is a valid one.
I'm closing this, and let's hope that fixing #13657 alone will go a long way toward mitigating the problem.
Most helpful comment
I think this will annoy roughly as many people as it pleases.
There are functions which have no write-verbose statements which are wrappers for another command which we want the detail from (IIRC I did this a lot to find what went into invoke-restmethod in other functions).
I've also written functions where I expressly called other things with
-verbose:$falsebecause I did write to verbose in the function, and tens or hundreds of function calls with verbose made it hard to see the wood for the trees.I'm not sure which is the more common case, but my gut says its not 10 or 20 of one to one of the other.