Reputation: 40523
I have found a behavior in PowerShell that surprises me and for which I am looking for an explanation. Please consider the following PowerShell module named testParameter:
testParameter.psd1
:
@{
RootModule = 'testParameter.psm1'
ModuleVersion = '0.1'
FunctionsToExport = @(
'get-embeddedString'
)
}
testParameter.psm1
:
set-strictMode -version 3
function embed-string {
param (
[string] $pre,
[string] $post
)
"$($pre)$text$($post)"
}
function get-embeddedString {
param (
[string] $text
)
return embed-string '>>> ' ' <<<'
}
When I call get-embeddedString foo
, the function returns (or the console prints):
>>> foo <<<
I am surprised because this string is rendered in the function embed-string
which does not declare a local variable named $text
, does not have a parameter with that name and does not assign a value to it before it is used and I have expected the function to throw a The variable '$text' cannot be retrieved because it has not been set. error.
Apparently, embed-string
chooses to use the value that $text
has in get-embeddedString
which I assume is in a different and unrelated scope.
I don't understand what's going on here and where the relevant documentation for this behavior is found.
Upvotes: 3
Views: 839
Reputation: 438823
about_Scopes is the official conceptual help topic describing scopes in PowerShell (which, typically, but not exclusively, relate to variables), but let me provide a concise explanation:
As most shells do, PowerShell uses dynamic rather than lexical scoping.
$private:
scope, all descendant scopes see them by default.Therefore, because your embed-string
function was called from your get-embeddedString
function and the latter defines a $text
variable (via a parameter declaration in this case), that $text
variable is also visible to embed-string
- and would also be visible to functions called from it (and so on).
Notable pitfalls:
While unqualified (non-scoped) read access to an ancestral variable gets its value, assigning a value implicitly creates a new, local variable that shadows the ancestral one.
"$($pre)$text$($post)"
in your embed-string
function gets the value of the ancestral $text
variable, but if you were to execute, say, $text = 'value'
there, you'd create a local variable by the same name, which is then visible to any embed-strings
descendants on the call stack, but unrelated to the original get-embeddedstring
$text
variable.Only scopes in the same scope domain (aka session state), in the same runspace, exhibit this relationship; each module has its own scope domain, and all non-module code shares a single, separate one. The only one scope shared by all scope domains is the global scope, which is the root scope of all scope domains.
See the bottom section of this answer for a comprehensive overview of PowerShell scopes.
Upvotes: 3
Reputation: 3246
Nested/Child functions have access to all the parent function's variables. You can even modify them in the child scope and they are returned to the original value in the parent.
function embed-string {
param (
[string] $pre,
[string] $post
)
Write-Host $MyInvocation.BoundParameters
Write-Host $MyInvocation.UnboundArguments
Write-Host $text
$text = "bar"
"$($pre)$text$($post)"
}
function get-embeddedString {
param (
[string] $text
)
embed-string '>>> ' ' <<<'
Write-Host $text
}
Output
[pre, >>> ] [post, <<<]
foo
>>> bar <<<
foo
If you want to persist a change to a variable in the child function, you can use Set-Variable -Name text -Option AllScope
.
Link about Nested Functions, applies to Child Functions too
Upvotes: 0