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.
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.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With