Getting your Script Module functions to inherit "Preference" variables from the caller

Edit: While this first blog post demonstrates a viable workaround, it requires a fair bit of code in your function if you want to inherit multiple preference variables. After this post was made, I discovered a way to get this working in a function, so your code only requires a single line to call it. Check out Revisited: Script Modules and Variable Scopes for more information and a download link.

One thing I've noticed about PowerShell is that, for some reason, functions defined in script modules do not inherit the variable scope of the caller. From the function's perspective, the inherited scopes look like this: Local (Function), Script (Module), Global. If the caller sets, for example, $VerbosePreference in any scope other than Global, the script module's functions won't behave according to that change. The following test code demonstrates this:

# File TestModule.psm1:
function Test-ScriptModuleFunction
{
    [CmdletBinding()]
    param ( )

    Write-Host "Module Function Effective VerbosePreference: $VerbosePreference"
    Write-Verbose "Something verbose."
}

# File Test.ps1:
Import-Module -Name .\TestModule.psm1 -Force

$VerbosePreference = 'Continue'

Write-Host "Global VerbosePreference: $global:VerbosePreference"
Write-Host "Test.ps1 Script VerbosePreference: $script:VerbosePreference"

Test-ScriptModuleFunction

Executing test.ps1 produces the following output, assuming that you've left the global VerbosePreference value to its default of "SilentlyContinue":

Global VerbosePreference: SilentlyContinue
Test.ps1 Script VerbosePreference: Continue
Script Module Effective VerbosePreference: SilentlyContinue

There is a way for the script module's Advanced Function to access variables in the caller's scope; it just doesn't happen automatically. You can use one of the following two methods in your function's begin block:

$PSCmdlet.SessionState.PSVariable.GetValue('VerbosePreference')
$PSCmdlet.GetVariableValue('VerbosePreference')

$PSCmdlet.GetVariableValue() is just a shortcut for accessing the methods in the SessionState object. Both methods will return the same value that the caller would get if they just typed $VerbosePreference (using the local scope if the variable exists here, then checking the caller's parent scope, and so on until it reaches Global.)

Let's give it a try, modifying our TestModule.psm1 file as follows:

function Test-ScriptModuleFunction
{
    [CmdletBinding()]
    param ( )

    if (-not $PSBoundParameters.ContainsKey('Verbose'))
    {
        $VerbosePreference = $PSCmdlet.GetVariableValue('VerbosePreference')
    }

    Write-Host "Module Function Effective VerbosePreference: $VerbosePreference"
    Write-Verbose "Something verbose."
}

Now, when executing test.ps1, we get the result we were after:

Global VerbosePreference: SilentlyContinue
Test.ps1 Script VerbosePreference: Continue
Module Function Effective VerbosePreference: Continue
VERBOSE: Something verbose.

Keep in mind that the $PSCmdlet variable is only available to Advanced Functions (see about_Functions_Advanced). Make sure you've got a param block with the [CmdletBinding()] and/or [Parameter()] attributes in the function, or you'll get an error, because $PSCmdlet will be null.

About the Author

Dave Wyatt

Dave Wyatt is a Microsoft MVP (PowerShell) and a member of PowerShell.org's Board of Directors.

2 Comments

  1. Thanks for this article. I couldn't understand why I couldn't access the $verbosepreference set in a parent scope with the get-variable -name verbosepreference -scope 1.

    When I used the $PSCmdlet.GetVariableValue('VerbosePreference') I finally got the information I was after. Thanks again!