Micah R Ledbetter
Micah R Ledbetter

Reputation: 1096

Scope of Get-Command within a PowerShell module

Summary

I want to test whether a function exists. I wrote a function to do that for me, but when I put it in the module, it operates in the module's scope, when what I want is for it to operate in the scope of the session that called it.

Description:

If you copy and paste this into a PowerShell prompt, it works as expected.

function Test-Function($functionName) {
    $values = @(get-command -all | where {$_.Name -eq "$functionName" -and $_.CommandType -eq "Function"})
    return $values.Count -gt 0
}

At this point, you can run things like test-function Get-ChildItem ($false, since that is a cmdlet), or test-function test-function ($true, since that is a function).

But, what if you're going to use this function a lot?

Calling Get-Command from a module

If you save that in a .psm1 file and add a call to Export-ModuleMember to expose the function, it will work... sometimes.

import Test.psm1

Test-Function Test-Function
# Returns $true

function blah() { echo "whatever" }

Test-Function blah
# Returns $false

As far as I can tell, this is a scoping issue - the module has its own scope, and it can't see other functions that I create outside of it.

Workarounds

Basically, I'd like to save this function for later and use it in several script files - it sounds like a candidate for a module to me. But, unless there's a clever solution to this scoping issue, I'm stuck with some crappy workarounds:

  1. Don't put it in a module, put it in a .ps1 file and dot-source it.

    This is what we do now, and it does work, and it's probably the least ugly thing to do if there isn't another workaround.

  2. Don't put it in the module at all, and force anyone that wants to use it to reimplement

    It's only a couple of lines, I guess, so this would work. The problem is it's already in our deployment scripts in a few places, and it would be so nice if I could just make it a function...

  3. Do something weird with scoping or scriptblocks or something.

    Like, put this in the module:

    $testFuncBlock = {
        param($functionName)
        $values = @(get-command -all | where {$_.Name -eq "$functionName" -and $_.CommandType -eq "Function"})
        return $values.Count -gt 0
    }
    Export-ModuleMember -variable testFuncBlock
    

    And call it by dot-sourcing the scriptblock:

    . $testFuncBlock Function-Name
    

    Obviously that's hella ugly though

Is there a better solution?

That's all I an think of. Is there something that will let me save this in the module and refer to it later without a scriptblock?

Upvotes: 3

Views: 351

Answers (1)

Andy Arismendi
Andy Arismendi

Reputation: 52639

Get-Command has different behavior depending on the version of PowerShell. Look at the version specific notes in the parameter documentation.

Using the Get-Command Cmdlet

Instead I recommend using the function: drive:

function Test-Function ($Name) {
    Test-Path -Path "function:${Name}"
}

Upvotes: 2

Related Questions