Varonis announces strategic partnership with Microsoft to accelerate the secure adoption of Copilot.

Learn more

PowerShell Variable Scope Guide: Using Scope in Scripts and Modules

PowerShell variable scopes can cause confusion in writing scripts and functions. This post will cover PowerShell scopes in relation to scripts and modules.
Jeff Brown
5 min read
Last updated October 13, 2022

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.

Learn how to automate Microsoft 365 management with our free PowerShell course

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:

  1. $greeting = “Hello, World!”
  2. $greeting
$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:

  1. function Get-Greeting {
  2. if ($name) {
  3. $greeting = "Hello, $name!"
  4. }
  5. else {
  6. $greeting = "Hello, World!"
  7. }
  8.  
  9. $greeting
  10. }
  11.  
  12. function Set-GreetingName {
  13. param(
  14. [Parameter(Mandatory)]
  15. [string]
  16. $GreetingName
  17. )
  18.  
  19. $name = $GreetingName
  20. }
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:

  1. function Get-Greeting {
  2. if ($script:name) {
  3. $greeting = "Hello, $script:name!"
  4. }
  5. else {
  6. $greeting = "Hello, World!"
  7. }
  8.  
  9. $greeting
  10. }
  11.  
  12. function Set-GreetingName {
  13. param(
  14. [Parameter(Mandatory)]
  15. [string]
  16. $GreetingName
  17. )
  18.  
  19. $script:name = $GreetingName
  20. }
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.

What you should do now

Below are three ways we can help you begin your journey to reducing data risk at your company:

  1. Schedule a demo session with us, where we can show you around, answer your questions, and help you see if Varonis is right for you.
  2. Download our free report and learn the risks associated with SaaS data exposure.
  3. Share this blog post with someone you know who'd enjoy reading it. Share it with them via email, LinkedIn, Reddit, or Facebook.

Try Varonis free.

Get a detailed data risk report based on your company’s data.
Deploys in minutes.

Keep reading

Varonis tackles hundreds of use cases, making it the ultimate platform to stop data breaches and ensure compliance.

windows-powershell-scripting-tutorial-for-beginners
Windows PowerShell Scripting Tutorial For Beginners
New to PowerShell scripting? Explore these scripting tutorials to learn to write and execute basic scripts, PowerShell cmdlets, aliases, pipes and more.
getting-started-with-powershell-option-inputs
Getting Started with PowerShell Option Inputs
PowerShell is the dominant method of automating tasks and scripting changes for Windows sysadmins. This article covers getting started with some basic PowerShell usage and how to pass optional customization...
powershell-for-pentesters:-scripts,-examples-and-tips
PowerShell for Pentesters: Scripts, Examples and Tips
This PowerShell for Pentesters' guide covers running commands, coding, tutorials and examples as well as the benefits of pentesting with PowerShell.
detecting-malware-payloads-in-office-document-metadata
Detecting Malware Payloads in Office Document Metadata
Ever consider document properties like “Company,” “Title,” and “Comments” a vehicle for a malicious payload? Checkout this nifty PowerShell payload in the company metadata: #powershell payload stored in office metadataDocument...