Live Cyber Attack Lab 🎯 Watch our IR team detect & respond to a rogue insider trying to steal data! Choose a Session

X

PowerShell Variable Scope Guide: Using Scope in Scripts and Modules

PowerShell

powershell variable scope hero

PowerShell uses variables to store information that can be useful later on. Variables also make it easy to change values in multiple places by changing the variable’s definition. You can store information such as names, paths, and the results of commands in a variable.

As you become more fluent in PowerShell, the idea of variable scope is likely to start playing a part in writing scripts, functions, and modules. In this post, I will cover the basics of PowerShell variable scope and provide examples of how to use scoping in PowerShell scripts and modules.

Get the Free PowerShell and Active Directory Essentials Video Course

I'd recommend this for both new and advanced PowerShell users. Building an AD tool is a great learning experience.

In addition to mastering scope, you can check out a collection of PowerShell tools for additional resources like editors, modules, and training to take your PowerShell skills to the next level.

What are PowerShell Scopes?

powershell scope levels

PowerShell scope protects variables and other artifacts by limiting where they can be read and modified. Scope levels protect items that should not be changed. PowerShell has the following scopes available:

  • Global: This scope is available when you open a PowerShell console or create a new runspace or session. PowerShell’s automatic and preference variables are present and available in the global scope. Any variables, alias, and functions defined in your PowerShell profile are also available in the global scope.
  • Script: This is the scope created when you run a script. Variables defined in the script are only available to the script scope and not the global or parent scope.
  • Local: This is the current scope of where a command or script is currently running. For example, variables defined in a script scope is considered its local scope.
  • Private: While not technically a scope, using private can protect a variable’s visibility outside of the scope where the variable is defined.

Scopes work in a hierarchy, meaning global is the parent scope. Variables defined in the global scope are automatically available to the child scope for viewing. However, variables defined in the child scope are not available to the parent scope.

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.
  • An item created in a scope can only be changed in the scope it was defined. Another item with the same name in a different scope may hide the original item, but it does not override or modify the original item.

Global Scope vs. Script Scope

Let’s take a look at some examples of different scopes in action. I have a script that defines a variable and outputs it to the screen:

$greeting = “Hello, World!”
$greeting

This script defines the variable $greeting in the script (or local) scope. However, that variable and its value will not be available to the PowerShell console, which is the parent scope. Examine the output of the PowerShell commands here:

Global Scope vs. Script Scope

I display the $greeting variable’s current value in the console (or global scope), and, as expected, it is empty. I run the script file where $greeting is defined and outputted to the screen. Once the script exits, I display the value of $greeting again in the PowerShell console, and it is still blank. This behavior is expected as the $greeting variable is in the child script scope and not in the parent global scope.

Let’s examine the reverse of this. Instead of defining the value of $greeting in the script, let’s define it in the global scope. The script then displays the value of the $greeting variable inside the script as it inherited it from the global scope.

Variable inherited from global scope

Inside the script, I did not set or modify the $greeting variable. I only displayed its value. The greeting string was inherited from the console as the parent scope by the script file as the child scope.

Using Scope Modifiers

In the previous examples, we see how a variable defined in the child script scope was not available to the global scope for reference. However, we can use scope modifiers to change the scope of the defined variable. A few scope modifiers include:

 

Scope Modifier Usage
global: The variable exists in the global scope
local: The variable exists in the local scope
private: The variable is only visible in the current scope
script: The variable exists in the script scope, which is the nearest script file’s scope, or Global if one is not available

 

For example, I can use the $global: prefix on a variable inside a script to change a variable value that I have already defined in the global parent scope. Examine the script and console output below:

Using scope modifiers

I defined the $greeting variable in the global console scope as “Hello, Jeff!”. I then ran the script file, which reassigned the value to “Hello, World!” using the $global: scope modifier. After running the script, the value of $greeting has been modified in the global console scope to the script’s value.

Using Scopes in a PowerShell Module

A PowerShell module is a package of commands, such as cmdlets or functions, that have similar functions or purposes. An example of this is the ActiveDirectory module for Windows PowerShell, which contains commands to manage Active Directory objects.

You can author a module yourself and may need to reference the same variable in different functions. By setting variables using the script scope in a module, the variables are now shareable between the module’s functions.

Here is an example PowerShell module file (MyModule.psm1) that contains two functions, Get-Greeting and Set-Greeting:

function Get-Greeting {
    if ($name) {
        $greeting = "Hello, $name!"
    }
    else {
        $greeting = "Hello, World!"
    }
    
    $greeting
}

function Set-GreetingName {
    param(
        [Parameter(Mandatory)]
        [string]
        $GreetingName
    )

    $name = $GreetingName
}

Note that both functions reference the $name variable. However, since I defined the variable in separate functions, they are scoped only to that function. Setting the $name variable in the Set-GreetingName function does not affect the $name variable in Get-Greeting. You can try this yourself by saving this code to a .psm1 file, importing it using the Import-Module command, and referencing the module filename.

Variable is scoped to the function

Even though I set the $name variable using the Set-GreetingName function, this did not affect the $name variable in the Get-Greeting function. Each $name variable is scoped to its own function. If I change the value in one function, it does not affect the other function.

If I wanted to scope the variable to be available to all functions in the module, I add the $script: modifier to the $name variable, like this:

function Get-Greeting {
    if ($script:name) {
        $greeting = "Hello, $script:name!"
    }
    else {
        $greeting = "Hello, World!"
    }
    
    $greeting
}

function Set-GreetingName {
    param(
        [Parameter(Mandatory)]
        [string]
        $GreetingName
    )

    $script:name = $GreetingName
}

If I re-import the module using the -Force parameter, I can now set the value of $name in one function and reference it in another.

Variable is scoped to the module

Note how calling Get-Greeting shows the default message as the $script:name variable did not have a value. After calling the Set-GreetingName function, the Get-Greeting now displays a different value based on the name I passed. This behavior is expected as both functions are now referencing the same variable.

While the example module is simple, you can see how this can be useful. I use this functionality to set API credentials that multiple functions use in modules I write. Check out an example in my twilio-powershell-module GitHub repository.

Scoping with Dot Source Notation

As I’ve demonstrated in the examples above, scripts and functions will have their own scope outside the global scope. Changes to variables are only affected in that scope unless you use a scope modifier to change the variable’s scope.

However,  you can incorporate the scope of a script or function by using dot source notation. When the script runs in the current scope, any script variables are available in the current scope. You can use dot source notation by placing a dot/period (.) and a space before the script name. Examine the script file and code below:

Using dot source notation

Initially, the value of $greeting in the global console scope is empty or null, and the scopetest.ps1 file sets the value of $greeting. By using dot source notation, the script brings the variable value in the parent scope. You can also use the call operator/ampersand (&) to run a script or function, and its scope will not be added to the current scope.

Additional Resources

Understanding scoping in PowerShell is essential when writing scripts, modules, and functions. You might run into a scenario where a variable has an unexpected value, and this could be due to the variable inheriting from another scope. Using other techniques such as module-level scoping and dot source notation can help you build valuable PowerShell tools for your toolbelt.

For more information on writing PowerShell scripts, check out Jeff Petter’s Windows PowerShell Scripting Tutorial for Beginners.

Jeff Brown

Jeff Brown

Jeff Brown is a cloud engineer specializing in Microsoft technologies such as Office 365, Teams, Azure and PowerShell. You can find more of his content at https://jeffbrown.tech.

 

Does your cybersecurity start at the heart?

Get a highly customized data risk assessment run by engineers who are obsessed with data security.