Question:
I have a collection of utility functions and other code that I dot-source into every powershell file I write. Having gotten bit by caller scope variables influencing it, I started looking into changing it into a powershell module.
I ran into problems with a couple of the special things I do in it where I actually do want some interaction between the scopes. I’m wondering if there is anyway to “get at” the scope of a module’s caller to keep this functionality while moving to a powershell module?
If not, is my best path forward to keep these more specialized things in a dot-sourced file and move the more traditional utility functions into a module? Here are the things that don’t easily move to a module:
- Setting strict mode and error action preferences to keep sane, e.g.:
1234Set-StrictMode -Version Latest$ErrorActionPreference = "Stop"$PSDefaultParameterValues['*:ErrorAction']='Stop'
This (as expected) has no effect on the caller’s environment when the code is run from a .psm1 powershell module. Is there any way to cross from the psm1 scope to the caller scope to make these changes? - Printing out information about the top-level script call, e.g.:
123456789$immediateCallerPath = Get-Variable -scope 1 -name PSCommandPath -ValueOnlyWrite-Host "Starting script at $immediateCallerPath"$boundParameters = Get-Variable -scope 1 -name PSBoundParameters -ValueOnlyWrite-Host "Bound parameters are:"foreach($psbp in $boundParameters.GetEnumerator()){"({0},{1})" -f $psbp.Key,$psbp.Value | Write-Host}
Likewise, these commands can no longer see the top-most calling scope once placed in a .psm1 file
Answer:
$PSCmdlet.SessionState
seems to provide a function inside a script module access to the call site’s variables provided the call site is outside the module. (If the call site is inside the module, you can just use Get-
and Set-Variable -Scope
.) Here is an example using SessionState
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
New-Module { function Get-CallerVariable { param([Parameter(Position=1)][string]$Name) $PSCmdlet.SessionState.PSVariable.GetValue($Name) } function Set-CallerVariable { param( [Parameter(ValueFromPipeline)][string]$Value, [Parameter(Position=1)]$Name ) process { $PSCmdlet.SessionState.PSVariable.Set($Name,$Value)} } } | Import-Module $l = 'original value' Get-CallerVariable l 'new value' | Set-CallerVariable l $l |
which outputs
1 2 3 |
original value new value |
I’m not sure whether SessionState
was intended to be used in this manner. For what it’s worth, this is the same technique used in Get-CallerPreference.ps1
. There are also some test cases here which pass on PowerShell versions 2 through 5.1.