about scopes in Powershell
Starting with the latter question:
Scopes come in play with functions and invoked scripts(cmdlets), like:
Function Test {
$Test++
Write-Host 'Local:' $Test
}
$Test = 5
Test
Write-Host 'Global:' $Test
Returns:
Local: 6
Global: 5
And:
Function Test {
$Global:Test++
Write-Host 'Local:' $Test
}
$Test = 5
Test
Write-Host 'Global:' $Test
Returns:
Local: 6
Global: 6
Or if you put the function in a script (e.g. MyScript.ps1
):
$Test = 5
.\MyScript.ps1
Write-Host $Test # $Test is unaffected unless you use the $Global scope in your script
Which will return basically the same results as above, unless you Dot-Source your script where it will run in the current scope:
$Test = 5
. .\MyScript.ps1
Write-Host $Test # $Test might be affected by MyScript.ps1 if you just use $Test
For what you are doing:
You are creating a complete new PowerShell session (with Powershell.exe
) which will start with a fresh list of variables.
Note here that you will see the initial variables again if you exit
from the new session:
PS C:\> $Name = "John"
PS C:\> Powershell.exe
Windows PowerShell
Copyright (C) Microsoft Corporation. All rights reserved.
Try the new cross-platform PowerShell https://aka.ms/pscore6
PS C:\> Write-Host 'New session' $Name
New session
PS C:\> Exit
PS C:\> Write-Host 'Initial session' $Name
Initial session John
Which regards to the first question, I don't think that there are many applications where you need to explicitly refer to the $Local
scope, but to give you an example where you might use it:
$Test = 5
Function Test {
Write-Host ($Local:Test++)
}
Test
In the above example the unary increment operator will start with 0
if you explicitly use the $Local
scope (in fact you starting with an empty local variable which will cast to 0
) and with 5
if you omit the $Local
scope where you will inherit a copy of the $Test
variable out of the parent scope.
To complement iRon's helpful answer:
- [...] when will we need to explicitly specify the local modifier?
$local:
is rarely required, because the local scope is implied in the absence of a scope specifier.
However, that only applies if the referenced variable actually exists as a local variable, given that PowerShell's dynamic scoping makes variables from ancestral (parent) scopes visible to descendant (child) scopes as well (see this answer for more information):
For example, say you have
$foo = 'bar'
declared in the global scope, then referring to$foo
in a script would look for a local$foo
instance first; if there is none, a$foo
defined in an ancestral (parent) scope is used, if any, which would be the global$foo
in this example, and'bar'
would be returned.By contrast, if, in your script, you use
$local:foo
, without a local$foo
variable being defined, you either get$null
by default or, ifSet-StrictMode -Version 2
or higher is in effect, a statement-terminating error occurs.
- MSDN says: [...] by creating a session, or by starting a new instance of PowerShell [...] the result is a parent scope (the original scope) and a child scope (the scope that you created).
The documentation is incorrect in this regard as of this writing (a GitHub issue has been filed):
Ancestral (parent-child) relationships between scopes exist only in the context of a given session (runspace).
That is, dynamic scoping - the visibility of variables and other definitions from ancestral scopes - only applies to scopes within a given session.
A notable exception is that functions from a module do not run in a child scope of the calling scope - except if the that calling scope happens to be the global scope; modules have their own scope domains (technically called session states) that are linked to the global scope only - see this GitHub docs issue for a discussion.
Therefore, no child scope of the calling scope is created in the following scenarios, where the newly launched code knows nothing of the variables (and other definitions) in the calling scope:
Starting a new session via PowerShell remoting (e.g., with
Enter-PSSession
) orInvoke-Command -Computer
Starting a background [thread] job with
Start-Job
orStart-ThreadJob
or running threads in parallel withForEach-Object -Parallel
in v7.0+Starting a new PowerShell instance (process), using the PowerShell CLI (
pwsh
for PowerShell [Core],powershell.exe
for Windows PowerShell).To communicate values from the calling scope to the newly launched code in these scenarios, explicit action is required:
- When calling the CLI or using
Start-Job
, where a child process on the same machine is created, only environment variables defined in the calling process become automatically available to the child process. - Otherwise, values from the caller must be passed as arguments or - except when using the CLI - via the
$using:
scope - see this answer.
- When calling the CLI or using