This is how PowerShell Variables are designed to work. Variables your scripts or functions set only last as long as they're running, when they end, their values go away.
In what you're doing today, you're changing the $global scope variable but not running a script or function. You're effectively in the global scope already.
In order to use those nested scopes, you need to run a script or function, like this script below, called scratch.ps1
#script inherited the previous value
"script: current favorite animal is $MyFavoriteAnimal, inherited"
#now setting a script level variable, which lasts till the script ends
$MyFavoriteAnimal = "fox"
"script: current favorite animal is $MyFavoriteAnimal"
function GetAnimal(){
#this function will inherit the variable value already set in the script scope
"function: my favorite animal is currently $MyFavoriteAnimal"
#now the function sets its own value for this variable, in the function scope
$MyFavoriteAnimal = "dog"
#the value remains changed until the function ends
"function: my favorite animal is currently $MyFavoriteAnimal"
}
getAnimal
#the function will have ended, so now the script scope value is 'back'
"script: My favorite animal is now $MyFavoriteAnimal"
To access this functionality, you'll need to use scripts or functions.

Videos
about_Scopes
So we have three main scopes, from the looks of things:
-
global
-
local
-
script
I think I understand local; if you do not define a scope, the scope is defaulted to local. I think? Meanwhile, global is accessible to the whole session. I think?
In the context of functions, by default, any variable defined within a function is inaccessible outside of the function. You use return to pass data out of the function.
But, if you declare a variable outside the function with the global scope, you can access it within a function. I’ve used this to declare HashTables and Lists outside the function, and then add elements to the HashTable or List within a function.
Or, if you declare a variable with the global scope within a function, it will also be accessible outside the function. It’s not how I would do things, but a colleague made a pair of functions in a script to connect and disconnect from a database. He declared the database details and credentials at the top of the script using the global scope, and then created the connection within the connection function with the global scope. This allowed him to call the same connection variable in the disconnect function to close the connection. I’d have probably defined the connection outside of the connect function …
But, if I have all my code contained within one script, do I need to use the global scope to make variables accessible inside and outside of functions? Would the script scope work? The explanation in the link at the top suggests it wouldn’t work …
What is this best practice?
The PowerShell scopes article (about_Scopes) is nice, but too verbose, so this is quotation from my article:
In general, PowerShell scopes are like .NET scopes. They are:
- Global is public
- Script is internal
- Private is private
- Local is current stack level
- Numbered scopes are from 0..N where each step is up to stack level (and 0 is Local)
Here is simple example, which describes usage and effects of scopes:
$test = 'Global Scope'
Function Foo {
$test = 'Function Scope'
Write-Host $Global:test # Global Scope
Write-Host $Local:test # Function Scope
Write-Host $test # Function Scope
Write-Host (Get-Variable -Name test -ValueOnly -Scope 0) # Function Scope
Write-Host (Get-Variable -Name test -ValueOnly -Scope 1) # Global Scope
}
Foo
As you can see, you can use $Global:test like syntax only with named scopes, $0:test will be always $null.
You can use scope modifiers or the *-Variable cmdlets.
The scope modifiers are:
globalused to access/modify at the outermost scope (eg. the interactive shell)scriptused on access/modify at the scope of the running script (.ps1file). If not running a script then operates asglobal.
(For the -Scope parameter of the *-Variable cmdlets see the help.)
Eg. in your second example, to directly modify the global $array:
& {
$global:array +="s"
Write-Host $array
}
For more details see the help topic about_scopes.
I'm still fairly new to the scripting scene and I've run into a bit of bother with one of my latest scripts. There is a function in the script which passes string values into an array. I would like to call this function numerous times so that the array keeps growing with more and more objects. At the end I want to pass the array on to a different part of my script.
I already know that using the default Local scope for the array won't work, but I want to know which of the Global or Script scope should I use?
Edit: Thanks for the explainations. My confusion was a misunderstanding of import-modules when I really should be dot-sourcing my functions for the scripts that I write to keep everything within the same scope and have constants that only need to be declared once but used heavily throughout the entire script. So by dot sourcing, I can have the vars that I need to be "global" (I realize how I'm using that word and really mean within same scope) without having to explicitly pass that var each time as a parameter.
Original:
So I'm having a lil bit of confusion around when my vars are global in scope vs when they aren't. So say I'm writing a called "Script.ps1" and I create a module file and call that "Logger.psm1" and use an import-module statement in Script.ps1. Logger.psm1 has a custom function called write-log.
So now say I declare a var in Script.ps1. This var is something that is a constant and will never change. Like a logging directory. Now we have the following files:
Script.ps1:
import-module ".\Logger.psm1" $LogDirectory = "Some\File\Path" write-log -message "Test Message"
Logger.psm1:
function Write-Log {
param (
$message
)
$messsage | Out-file -path "$LogDirectory\logfile.log"
}In this setup I expect $LogDirectory to be a global var that is accessible by the module I imported and calling from Script.ps1. But the behavior I see is $LogDirectory isn't treated as a global var. Instead I have to actively declare $LogDirectory as a global using $global:Logdirectory for the module to see it. Otherwise I have to add it as a parameter.
So few questions:
Why is this?
Does the module have a separate global scope from the ps1 script that imported it?
What's the best practice for accomplishing something like this? I have another script that I think is suffering from a similar issue.
In your script examples you created variables and you didn't destroy them at the end of your script. The ISE creates a live instance of powershell which loads and runs the script when you click run. The difference is that the integrated shell can continue the script. This is ideal for debugging the environment and for creating scripts as you go. This way you don't have to keep running your script over and over (there are situations where this wouldn't be ideal) to make sure the next line of code worked. You type it in the shell, and if it works, you add it to the script section.
This behavior is perhaps best described here: http://technet.microsoft.com/en-us/library/dd819480.aspx
Relevant Excerpt:
All panes in ISE are always in the same scope.
If you don't want your variables to live in the shell after your script has completed then you should Remove-Variable them.
For example:
Remove-Variable x
You can add a "clean" instance of powershell to the ISE by clicking File->New Powershell Tab
In the ISE Powershell is essentially dumping the script to the shell and then running it. As if you typed it out in the shell, then executed it. So the variable is available for that session. To see other differences, check out this MSDN post.
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$fooin a script would look for a local$fooinstance first; if there is none, a$foodefined in an ancestral (parent) scope is used, if any, which would be the global$fooin this example, and'bar'would be returned.By contrast, if, in your script, you use
$local:foo, without a local$foovariable being defined, you either get$nullby default or, ifSet-StrictMode -Version 2or 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 -ComputerStarting a background [thread] job with
Start-JoborStart-ThreadJobor running threads in parallel withForEach-Object -Parallelin v7.0+Starting a new PowerShell instance (process), using the PowerShell CLI (
pwshfor PowerShell [Core],powershell.exefor 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