Powershell: Get-Command from inside a module cannot find non-module commandlet

Created on 14 Oct 2018  路  4Comments  路  Source: PowerShell/PowerShell

Steps to reproduce

Save the following as file Test.ps1 and execute it

# define standalone and module function
function MyComm {}
New-Module -ScriptBlock {function FindMyComm {Get-Command MyComm}} | Import-Module

# check for both functions
Get-Command MyComm
Get-Command FindMyComm

# run new function
FindMyComm

Comment: Running commands one by one in interactive console will not generate error. Only from within a script.

Expected behavior

Get-Command will run successfully three times.

Actual behavior

Get-Command from inside the module fails.

image

Environment data

> $PSVersionTable

Name                           Value
----                           -----
PSVersion                      6.1.0
PSEdition                      Core
GitCommitId                    6.1.0
OS                             Microsoft Windows 10.0.17763
Platform                       Win32NT
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0...}
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1
WSManStackVersion              3.0

Explanation

Seems like this issue is related to handling function scopes within command discovery, but I am not quite sure. Dot-sourcing . .\Test.ps1 will run without issues.

Example above is tailored for easy reproducibility. Standard, real life use cases (see https://github.com/PowerShell/platyPS/issues/407), can include normal PowerShell modules, manually or implicitly imported. Also, it does not matter if non-module function (MyComm) is defined directly as above, or dot-sourced from external script. It behaves the same, and Get-Command from inside the module cannot find it.

This is tested and impacts also Windows PowerShell 5.1. Similar example is behaving the same on Ubuntu.

Issue-Question Resolution-Answered

Most helpful comment

As far as I understand, the short of it is (right, @SeeminglyScience?):

Terminology note: I'm using the term _scope domain_ to refer to the distinct variable namespaces inside a session: one default one for all non-module code, and one for each module loaded; they correspond to SessionStateInternal instances, as explained in a blog post by @SeeminglyScience. By _default scope domain_ I mean the namespace for all _non-module_ code (whose root is the global scope). My plan is to suggest these terms be used in an overhaul of the about_Scopes help topic.

Functions execute in whatever scope happens to be current one _in the scope domain they were defined in_, which has the following implications:

  • Calling a function defined in a module from outside that module runs in [a child scope of] the _originating module's_ current scope, not in the caller's scope.

  • Calling a function in the same scope domain in which it is defined (i.e., calling a non-module function from non-module code, or calling a module function from inside the same module) runs in [a child scope of] the _caller's_ current scope.

  • This behavior applies even In _chains_ of calls across scope-domain boundaries (see GlobalFunc example below).

Note that _functions_ create a _child scope_ on invocation, whereas compiled _cmdlets_ do not.


The following code demonstrates these behaviors:

# Define variables to test with.
$global:foo = 'global foo'
$foo = 'script foo'

# A function in the global scope.
function global:GlobalFunc {
  "glob func: [$foo]"
}

# Define a non-module function that accesses variable $foo in 
# the parent and grandparent scopes.
function NonModFunc {
  foreach ($scope in 1, 2) {
    try { "scope $scope`: {0}" -f (Get-Variable -ValueOnly -EA Stop -Scope $scope foo) } catch { Write-Warning "No `$foo in scope $scope." }
  }
}

# Save the function body as a string, so we can recreate it in the module below.
# Note: We must create a *global* variable so that the script block passed to
#       New-Module sees it, given that the script block runs in the new module's scope.
$global:__funcDef = $function:NonModFunc

# Define a function with the same body inside an (in-memory) module.
$null = New-Module -ScriptBlock {
  # Same function body as above, only inside a module and with name 'ModFunc'
  Invoke-Expression ('function ModFunc {{ {0} }}' -f $__funcDef)
  # Define a module function that invokes a global function.
  function CallGlobal {
    global:GlobalFunc
  }
}

'--- non-module func'
NonModFunc

'--- module func'
ModFunc

'--- non-module func from child scope'
& { NonModFunc }

'--- global func via module'
CallGlobal

The above yields (annotated):

# Calling the non-module function from the script scope runs in a child scope of the script scope.
--- non-module func
scope 1: script foo  # The function's parent scope is the script scope.
scope 2: global foo # The function's grandparent scope is the global scope.

# Calling the module function from the script scope runs in a child scope of the *module* scope.
--- module func
WARNING: No $foo in scope 1.  # The module's root scope has no $foo.
scope 2: global foo           # The parent of the module's root scope is the global scope.

# Calling the non-module function from a child scope of the script scope (via a script block,
# & { ... }), runs in a child scope of *that* scope.
--- non-module func from child scope
WARNING: No $foo in scope 1.  # The script block's scope has no $foo.
scope 2: script foo           # The script block's parent scope is the script scope.

# Calling a module function that in turn calls a global function makes the global function run
# in the non-default (non-module) scope domain's current scope, which is the script scope
# from which CallGlobal was called.
--- global func via module
glob func: [script foo]  # CallGlobal was originally called from the script scope.

All 4 comments

The issue is that function MyComm is defined in a scope that your module by definition cannot see.

Modules see only _global_ (non-module) function definitions, whereas your function is defined in a _child_ of the global scope by virtue of being defined in a _script_.

By dot-sourcing your script from the global scope MyComm is defined globally, that's why the problem goes away.

Hm, that's how I figured it out also, but I find it a bit inconsistent. Your sentence is good definition of this issue:

Modules see only global function definitions

I was presuming FindMyComm will run inside a script scope, although defined in global scope. I was not expecting it will jump up during the execution. Microsoft docs say about functions:

The statements in the list run as if you had typed them at the command prompt.

I don't know if there are differences between functions defined in module or outside of it, in regards to this definition. So, question is: In what scope FindMyComm runs: global or script scope?


What about this example:

# define standalone and module function
function MyComm {}
function Global:FindMyComm {Get-Command MyComm}

# check for both functions
Get-Command MyComm
Get-Command FindMyComm

# run new function
FindMyComm

Now FindMyComm is defined in global scope, but it runs inside of the script scope. I would expect importing it via module to be the same as this example. Function MyComm is not visible in global scope.
image

As far as I understand, the short of it is (right, @SeeminglyScience?):

Terminology note: I'm using the term _scope domain_ to refer to the distinct variable namespaces inside a session: one default one for all non-module code, and one for each module loaded; they correspond to SessionStateInternal instances, as explained in a blog post by @SeeminglyScience. By _default scope domain_ I mean the namespace for all _non-module_ code (whose root is the global scope). My plan is to suggest these terms be used in an overhaul of the about_Scopes help topic.

Functions execute in whatever scope happens to be current one _in the scope domain they were defined in_, which has the following implications:

  • Calling a function defined in a module from outside that module runs in [a child scope of] the _originating module's_ current scope, not in the caller's scope.

  • Calling a function in the same scope domain in which it is defined (i.e., calling a non-module function from non-module code, or calling a module function from inside the same module) runs in [a child scope of] the _caller's_ current scope.

  • This behavior applies even In _chains_ of calls across scope-domain boundaries (see GlobalFunc example below).

Note that _functions_ create a _child scope_ on invocation, whereas compiled _cmdlets_ do not.


The following code demonstrates these behaviors:

# Define variables to test with.
$global:foo = 'global foo'
$foo = 'script foo'

# A function in the global scope.
function global:GlobalFunc {
  "glob func: [$foo]"
}

# Define a non-module function that accesses variable $foo in 
# the parent and grandparent scopes.
function NonModFunc {
  foreach ($scope in 1, 2) {
    try { "scope $scope`: {0}" -f (Get-Variable -ValueOnly -EA Stop -Scope $scope foo) } catch { Write-Warning "No `$foo in scope $scope." }
  }
}

# Save the function body as a string, so we can recreate it in the module below.
# Note: We must create a *global* variable so that the script block passed to
#       New-Module sees it, given that the script block runs in the new module's scope.
$global:__funcDef = $function:NonModFunc

# Define a function with the same body inside an (in-memory) module.
$null = New-Module -ScriptBlock {
  # Same function body as above, only inside a module and with name 'ModFunc'
  Invoke-Expression ('function ModFunc {{ {0} }}' -f $__funcDef)
  # Define a module function that invokes a global function.
  function CallGlobal {
    global:GlobalFunc
  }
}

'--- non-module func'
NonModFunc

'--- module func'
ModFunc

'--- non-module func from child scope'
& { NonModFunc }

'--- global func via module'
CallGlobal

The above yields (annotated):

# Calling the non-module function from the script scope runs in a child scope of the script scope.
--- non-module func
scope 1: script foo  # The function's parent scope is the script scope.
scope 2: global foo # The function's grandparent scope is the global scope.

# Calling the module function from the script scope runs in a child scope of the *module* scope.
--- module func
WARNING: No $foo in scope 1.  # The module's root scope has no $foo.
scope 2: global foo           # The parent of the module's root scope is the global scope.

# Calling the non-module function from a child scope of the script scope (via a script block,
# & { ... }), runs in a child scope of *that* scope.
--- non-module func from child scope
WARNING: No $foo in scope 1.  # The script block's scope has no $foo.
scope 2: script foo           # The script block's parent scope is the script scope.

# Calling a module function that in turn calls a global function makes the global function run
# in the non-default (non-module) scope domain's current scope, which is the script scope
# from which CallGlobal was called.
--- global func via module
glob func: [script foo]  # CallGlobal was originally called from the script scope.

This issue has been marked as answered and has not had any activity for 1 day. It has been closed for housekeeping purposes.

Was this page helpful?
0 / 5 - 0 ratings