I've got the script below, from internet:
$private:a = 1
Function test {
"variable a contains $a"
$a = 2
"variable a contains $a"
}
test
It prints 2. No problem. If I delete "private", like below:
$a = 1
Function test {
"variable a contains $a"
$a = 2
"variable a contains $a"
}
Still it prints "2". Seems no difference. Could you provide an quick sample of how "private" scope affects the result?
Thanks.
Note:
* This answer explains why the OP's code behaves the way it does (and that it behaves as designed); additionally, it provides some general information about variable scopes in PowerShell.
* For an important real-world use of scope
private
, see PetSerAl's helpful answer.Your first snippet prints:
Your second snippet prints:
In the first snippet, using scope
private
causes the parent (script) scope'sa
variable to be hidden from the child (function) scope, as designed, so the first output line shows that$a
has no value(an undefined variable has value
$null
, which evaluates to the empty string in a string context).In the second snippet, by contrast, without the
private
scope modifier, variablea
from the parent scope is visible to the child scope.In PowerShell, functions execute in child scopes by default.
Therefore, in both snippets above, assigning to variable
a
inside the function implicitly creates a locala
variable there, whose scope is limited to the enclosing function.In other words:
$a
in the function creates a function-local variable named$a
, which then shadows (hides) the script-level$a
variable (if it wasn't already hidden by having been declared as$private:a
)$a
again has its original, script-level value.Some general information about variable scopes in PowerShell:
Unless a variable is explicitly hidden with scope
private
, descendant scopes can see that variable and read its value using the variable name without a scope qualifier (e.g.,$a
) or the need forGet-Variable -Scope
.Get-Variable -Scope
.(In the same scope, however, you can use a scope modifier to refer to a private variable, but only if that scope modifier effectively targets that same scope, which is always true for
$local:privateVarName
, for instance).Assigning to an unqualified variable, however, implicitly creates a new variable in the current (
local
) scope, which can shadow a variable of the same name in an ancestral scope.To explicitly get / modify a variable in an ancestral scope, use
Get-Variable / Set-Variable -Scope <n> <name>
, where<n>
represents the scope level, with0
representing the current scope,1
the parent scope, and so on.Note that
Get-Variable
returns a[System.Management.Automation.PSVariable]
instance by default, so in order to get only the value, access its.Value
property, or use the-ValueOnly
switch, which only returns the value to begin with.In functions and trap handlers, before creating a local copy of a variable, you can alternatively modify a variable in the most immediate ancestral scope where it is defined as follows:
([ref] $var).Value = ...
Variables in the script scope and the global scope can also be accessed - and modified - by using the
$script:
and$global:
scope modifiers; e.g.,$script:a
and$global:a
.Note that
$script:
refers to the (immediately) enclosing script file's top-level scope.Declaring a variable with
Set-Variable -Option AllScope
allows it to be read and modified in any descendant scope without needing to qualify the name; to put it differently: only a single variable by that name exists then, which any scope can directly read and write using the unqualified variable name.Without a separate
-Scope
parameter,-Option AllScope
is applied to the variable in the current scope (e.g., the script scope at the script's top level, a function's local scope inside a function). Thus, to safely create a script-global variable that you can access unqualified for reading and writing, useSet-Variable -Scope Script -Option AllScope
.-Scope Global
is distinct from-Option AllScope
: while-Scope Global
creates a globally accessible variable, reading it may, and modifying it does, require the$global:
scope modifier. Also note that a global variable is session-global, so it persists even after the script that defined it has terminated.By combining
-Scope Global
with-Option AllScope
you effectively create a session-global singleton variable that can be read and written from any scope without qualifier; as stated, however, such a variable lives on even after your script exits.Good software design means minimized coupling (among other things). Within Powershell, that includes using private ON EVERY VARIABLE YOU CAN. If you want to make a value available in some subsequently called module, pass that information EXPLICITLY. There should be a very good EXCEPTION reason for not doing this, because each time you rely on implicit knowledge (e.g. the kind that happens in Powershell when you don't use private variables), you increase the chance something will go unexpectedly wrong later (maybe months later when the software has a lot more code in it).
Private scope can be useful when writing a function that invokes a user-supplied callback. Consider this simple example:
Then, if someone calls it like this:
they'll expect to see only the
First2 Last2
row, but actually this will print all three rows. This is because of a collision on the$FirstName
variable. To prevent such collisions, you can declare variables inWhere-Name
as private:Now
$FirstName
inWhere-Name
does not hide$FirstName
in the outer scope when referenced from the$Condition
script block.