This isn't exactly a common requirement, but it turned up as I was working on
PSKoans. Essentially, I had a slight problem: the koans in
AboutDiscovery that deal with Get-Command
were consistently finding commands that were intended
to be hidden to the user; commands internal to the PSKoans module itself. So, I needed a way to have
the koan files evaluated outside the module scope.
Scope Breaking
At its simplest, scope breaking can work like this: you take a SessionState
, PSModuleInfo
, or
similarly session-state-tied object and invoke a command using its session state. It looks a bit
like this:
& (Get-Module 'Pester') { Get-Command -Module Pester }
This will show you all the secret, private functions that Pester would prefer you not to see. You could also use this method to execute those functions. This is the precise inverse of what I wanted to do. I wanted to execute code in a module function that would see only the outside world, and not the cmdlets and functions that created it.
Breaking into the Global Scope
Thanks to Patrick Meinecke I was given this little taste of the dark arts of breaking out into the global scope:
# [psmoduleinfo]::new(bool linkToGlobal)
$GlobalScope = [psmoduleinfo]::new($true)
& $GlobalScope {
# Invoke commands here
}
This was almost there. But it has a slight problem. You see, the command I wanted to use
here was actually Invoke-Pester
. That's how my PSKoans operate; just like a Pester script, with a
few bells and whistles.
Nothing too crazy there… until I realized, as it was building in the VM. I've just made it nigh
impossible to test properly. See, my tests need to be able to mock that Invoke-Pester
call.
And it was not happening. It seemed to mock just fine, but thereafter attempting to call
any mocked command simply failed with some rather curious errors.
It seemed to break literally all mocks for that module's commands. Very odd. But that's what you get when you play with scopes!
The Solution
As much as I'd like to say I was able to bully it into submission, I couldn't. Something very fundamental was in play here; I suspect I triggered an almost sort of recursion in Pester's mocking logic. It wasn't built for this kind of abuse.
So what to do? Thankfully, I could create a proxy command that essentially just hid away the
scope-breaking code. A bit of [System.Management.Automation.ProxyCommand]::Create()
and some
manual editing later, I came out with a new internal module command for executing the koans in the
global scope, as though the user were invoking them directly, without my module getting in the way.
Perfect. This command can, thankfully, be mocked, and wrapping the command sequence up in such a way is a particularly easy thing to abstract away.
This is the main body of the function I eventually came up with:
$GlobalScope = [psmoduleinfo]::new($true)
& $GlobalScope {
param($Params)
Invoke-Pester @Params
} @($PSBoundParameters)
Thanks for reading!