Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

about scopes in Powershell

I'm learning about scopes in Powershell and have some questions:

  1. About the "Local scope": From what i read, the local scope is always the current scope. So, by default, when we create an item (without scope modifier), e.g. a variable, in some scope, let it be script or global, the scope will be script/global accordingly. So my question is: when will we need to explicitly specify the local modifier?
  2. MSDN says:

You can create a new scope by running a script or function, by creating a session, or by starting a new instance of PowerShell. When you create a new scope, the result is a parent scope (the original scope) and a child scope (the scope that you created). ...
Unless you explicitly make the items private, the items in the parent scope are available to the child scope. However, items that you create and change in the child scope do not affect the parent scope, unless you explicitly specify the scope when you create the items.

But when i try the following:

PS> $Name = "John"
PS> Powershell.exe
PS>echo $Name  // No Output

It seems from the quote above that the "starting a new instance of powershell" is a child scope, so all the items in the parent scope should be visible there. Can someone explain?

like image 312
YoavKlein Avatar asked Aug 24 '20 05:08

YoavKlein


People also ask

What is scope script?

The Script scope is a useful place to store variables which must be shared without exposing the variable to the Global scope (and therefore to anyone with access to the session). For example, the following short script stores a version number in a script-level variable.

What is global scope PowerShell?

PowerShell Scopes Global: The scope that is in effect when PowerShell starts or when you create a new session or runspace. Variables and functions that are present when PowerShell starts have been created in the global scope, such as automatic variables and preference variables.

What is $using in PowerShell?

Beginning in PowerShell 3.0, you can use the Using scope modifier to identify a local variable in a remote command. The syntax of Using is as follows: $Using:<VariableName> In the following example, the $ps variable is created in the local session, but is used in the session in which the command runs.

Can you use variables in PowerShell?

In PowerShell, variables are represented by text strings that begin with a dollar sign ( $ ), such as $a , $process , or $my_var . Variable names aren't case-sensitive, and can include spaces and special characters.

What is an example of a PowerShell scope?

An example is if a variable is available to every function within a PowerShell script, or just a single defined function. PowerShell Scopes encompass several different scopes that can be used such as Global, Local, or Script scopes. What are the PowerShell Scopes?

What are the basic rules of scoping in PowerShell?

PowerShell scoping follows a few basic rules: Scopes nest with each other. The outer scope is the parent scope, and any nested scopes are child scopes of that parent. An item is available in the scope where it is defined and to any child scopes unless explicitly made private.

How to create a child scope in PowerShell?

It is created when you run an instance of PowerShell session. It may also be called as Parent Scope. Now when you run a function or variable directly inside the session, you create child scopes. Each function/variable has its own child scope.

What is a nested scope in PowerShell?

Any nested scopes are child scopes of that parent. An item is visible in the scope in which it was created and in any child scopes, unless you explicitly make it private. You can place variables, aliases, functions, or PowerShell drives in one or more scopes.


Video Answer


2 Answers

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.

like image 138
iRon Avatar answered Oct 22 '22 03:10

iRon


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.
like image 2
mklement0 Avatar answered Oct 22 '22 03:10

mklement0