Invoke a PowerShell Module Command in the Global Scope

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.

Pester Summons Elder Gods

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 {

    Invoke-Pester @Params
} @($PSBoundParameters)

Thanks for reading!