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:

  1. [...] 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, if Set-StrictMode -Version 2 or higher is in effect, a statement-terminating error occurs.


  1. 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) or Invoke-Command -Computer

    • Starting a background [thread] job with Start-Job or Start-ThreadJob or running threads in parallel with ForEach-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.