Note:
Powershell: inspect, consume and pass through arguments? deals with the complementary scenario: How to relay arguments (parameter values) to a nested or separate function or script having the same (or least compatible) parameter declarations (param(...)
block), using the automatic $PSBoundParameters
variable. While this ultimately makes for the most robust solution, your intent is to avoid this duplication of parameter declarations.
The answer below contains a solution that may or may not be worth the trouble; if not, I hope that at least the background information that is provided is useful.
PowerShell's dynamic scoping makes all variables in ancestral scopes (in the same scope domain aka "session state")[1] on the call stack visible to
a given scope.[2]
- Only variables defined with the
$private:
(pseudo) scope are exempt from dynamic scoping; that is, this scope specifier makes a variable visible to the local scope only, but not to any descendent ones.
Therefore, your nested func2
function implicitly sees any variables - including parameter variables - defined in the enclosing func1
function, as well as - potentially - any variables from ancestral
scopes higher up on the call stack.
If you want to ensure that your nested func2
function only operates on parameter variables passed to the enclosing function - i.e. only on arguments (parameter values) passed to func1
, as opposed to incidental variables of the same name having been defined in a higher ancestral scope -
you can combine dynamic scoping with the automatic $PSBoundParameters
variable, which is a dictionary of the names of all explicitly bound parameters and their values.
Specifically, you can define a local variable in the enclosing function that stores a reference to its $PSBoundParameters
value, which any nested scope can then reference thanks to dynamic scoping.
(Note that each nested function sees a separate $PSBoundParameters
instance in its own scope, reflecting its bound parameters, if any; if a function declares no parameters, the dictionary is empty.)
The advantage of this approach is that it neither requires parameter declarations nor passing arguments to the nested functions while still ensuring that only parameter values passed to the parent function are operated on. However, in the nested functions this requires disciplined access to the enclosing function's parameter values via the local variable referencing the enclosing function's $PSBoundParameters
dictionary.
Note that $PSBoundParameters
fundamentally only tells you which parameters were bound explicitly, i.e. with arguments (values) supplied by the caller; it doesn't reflect parameter default values - see GitHub issue #3285 for a discussion.
However, parameters with default values invariably result in a local (parameter) variable with that default value, which can therefore be referenced directly in a nested scope. If you know a variable
with a given name to be such a parameter variable with default value, its value is guaranteed to be the one from the enclosing scope (barring shadowing via a local variable in a nested scope).
To spell the solution out in the context of your example:
function func1 {
param (
$a,
$b = 42
)
# Store a reference to this function's explicitly bound parameters
# in a local variable.
$parentBoundParameters = $PSBoundParameters
function func2 {
# This nested function now sees the local variable from the enclosing
# scope.
# Output the value passed to parameter -a of the enclosing function.
# Make sure that you reference all parameter variables that
# don't have default values this way.
'$a parameter value (if bound): ' + $parentBoundParameters.a
# A parameter bound with a *default* value is not stored in
# $PSBoundParameters, but is guaranteed to have a value in its scope,
# which this nested scope sees too:
'$b parameter value (possibly bound by default value): ' + $b
}
# Invoke func2 *without arguments*.
return func2
}
# Invoke the outer function with (only) an -a argument
func1 -a hi
Note that func2
does not use return
and instead relies on PowerShell's implicit output behavior to "return" two strings.
return
is only needed for flow control in PowerShell, though, as syntactic sugar, it can optionally be combined with producing output - see this answer.
The above produces the following output:
$a parameter value (if bound): hi
$b parameter value (possibly bound by default value): 42
[1] All code executing outside of modules executes in the same scope domain, whereas each module operates in its own scope domain that is connected to the global scope only - see the bottom section of this answer for details.
[2] However, with name-only variable references (e.g. $var
), any scope can shadow an ancestral scope's variables of the same name (and in the same scope domain) by defining a local variable with the same name, which is happens by default on assignment (e.g. $var = ...
) - see the bottom section of this answer for a concise overview of variable scopes in PowerShell.