What is this Module Scope in PowerShell that you Speak of?

Last week I posted a blog article about a PowerShell script module that I had written with a few proof of concept commands to manage a Nimble Storage Area Network using their REST API. That module used a command to connect and authenticate to the storage device which needed to share a token with other commands in the module otherwise authentication would have to be performed for each command. I initially placed the token in a global variable even though I mentioned in the blog article that I’m not a big fan of globally scoping variables unless absolutely necessary (which I thought it was at the time).

I used a module named TunableSSLValidator that fellow PowerShell MVP Joel Bennett had created to be able to use Invoke-WebRequest with an untrusted or self-signed certificate. I tweeted that blog article to Joel to show him how I had used his module. He responded with a great tip about scoping variables so they’re shareable between commands within the same module without being globally scoped.

module-scope1a.jpg

I searched through the help topics prior to contacting Joel once I saw his comment. I could only find one reference to module scope which says that modules do not have their own scope. Here’s what the about_Scopes help topic says about module scope:

“The privacy of a module behaves like a scope, but adding a module to a session does not change the scope. And, the module does not have its own scope, although the scripts in the module, like all Windows PowerShell scripts, do have their own scope."

After reading that, I contacted Joel since I’m familiar with global, script, and local scope but not module scope. My question to Joel was something like “What is this Module Scope that you Speak of?”. Joel replied and said script scope in a module is module scope. A little testing confirmed that scoping variables to script scope within a module makes them shareable between the commands from that module without exposing them globally.

To test variable scoping within a module, the following functions were saved as a script module file named MrVarTest.psm1:

function Set-MrVar {
    $PsProcess = Get-Process -Name PowerShell
}

function Set-MrVarLocal {
    $Local:PsProcess = Get-Process -Name PowerShell
}

function Set-MrVarScript {
    $Script:PsProcess = Get-Process -Name PowerShell
}

function Set-MrVarGlobal {
    $Global:PsProcess = Get-Process -Name PowerShell
}

function Test-MrVarScoping {
    if ($PsProcess) {
        Write-Output $PsProcess
    }
    else {
        Write-Warning -Message 'Variable $PsProcess not found!'
    }

}

First, the PSM1 file is imported since I didn’t place it in a location specified in $env:PsModulePath. Next the variable is set from a function in the module without coercing the scope. It’s no surprise that the variable isn’t found from another function in the module or from the current scope outside of the module. Then local scope is tested. The same results are produced. The third option uses script scope which allows another function within the same module to access the variable, but not from the current scope outside of the module. Finally, global scope is tested and it’s accessible from another function within the module and from the current scope outside of the module. There’s no reason for the variable to be accessible from outside of the module and having it accessible in that manner can only lead to trouble.

#Import the script module since I didn't place it in a location specified in $env:PSModulePath
Import-Module C:\tmp\MrVarTest.psm1

#Set the variable without any scoping coercion from within a function in the module
Set-MrVar

#Check the value of the $PsProcess variable from another function in the same module
Test-MrVarScoping

#Check the value of the $PsProcess variable from the current scope
$PsProcess

#Set the variable to the local scope from within a function in the module
Set-MrVarLocal

#Check the value of the $PsProcess variable from another function in the same module
Test-MrVarScoping

#Check the value of the $PsProcess variable from the current scope
$PsProcess

#Set the variable to the script scope from within a function in the module
Set-MrVarScript

#Check the value of the $PsProcess variable from another function in the same module
Test-MrVarScoping

#Check the value of the $PsProcess variable from the current scope
$PsProcess

#Set the variable to the global scope from within a function in the module
Set-MrVarGlobal

#Check the value of the $PsProcess variable from another function in the same module
Test-MrVarScoping

#Check the value of the $PsProcess variable from the current scope
$PsProcess

module-scope2a.jpg

Best practice is to limit the scope of your variables as much as possible otherwise something else that’s totally unrelated could overwrite the value contained within one of them if they’re globally scoped.

I also confirmed the variables are shared between commands within the same module using script scope regardless of whether or not you’re using one monolithic PSM1 file for your script module or separate PS1 files that are dot-sourced from the PSM1 file (Non-Monolithic script module design).

Here are some other comments related to the tweet posted earlier in this blog article:

module-scope3a.jpg

module-scope4a.jpg

The key takeaway from this blog article is as Joel said “script scope in a module is module scope”.

µ