PowerShell class methods cannot invoke non-exported functions.

Created on 31 Aug 2017  路  4Comments  路  Source: PowerShell/PowerShell

Steps to reproduce

New-Module m {
    class c { 
        [object] Invoke () { return Invoke-Something }
    }
    function Invoke-Something { 'Invoke-Something' }
    function New-InstanceOfC { [c]::new() }
    function Invoke-MethodOfC { (New-InstanceOfC).Invoke() }
    Export-ModuleMember *OfC
} | Out-Null

(New-InstanceOfC).Invoke()
Invoke-MethodOfC

Expected behavior

I expected both calls to Invoke-Something to succeed and output

Invoke-Something
Invoke-Something

Actual behavior

Neither call to Invoke-Something succeeds.

Invoke-Something : The term 'Invoke-Something' is not recognized as the name of a cmdlet, function, script file, or
operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try
again.
At C:\Users\un1\Desktop\test.ps1:3 char:37
+         [object] Invoke () { return Invoke-Something }
+                                     ~~~~~~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (Invoke-Something:String) [], CommandNotFoundException
    + FullyQualifiedErrorId : CommandNotFoundException

Invoke-Something : The term 'Invoke-Something' is not recognized as the name of a cmdlet, function, script file, or
operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try
again.
At C:\Users\un1\Desktop\test.ps1:3 char:37
+         [object] Invoke () { return Invoke-Something }
+                                     ~~~~~~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (Invoke-Something:String) [], CommandNotFoundException
    + FullyQualifiedErrorId : CommandNotFoundException

Environment data

> $PSVersionTable

Name                           Value
----                           -----
PSVersion                      6.0.0-beta
PSEdition                      Core
GitCommitId                    v6.0.0-beta.6
OS                             Microsoft Windows 6.3.9600
Platform                       Win32NT
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0...}
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1
WSManStackVersion              3.0

Most helpful comment

I can't speak to the underlying design issues (how things _should_ work in this case), but I can offer a pointer:

Your code would work if you put the module code into a *.psm1 file instead and import it with Import-Module or using module.

Note that only with using module is the module's custom class truly imported into the caller's scope and accessible as [c] (modules neither by default export custom classes nor allow their explicit exporting; they're imported (implicitly) only with a using module statement, which is used at _parse_ time and must be the very 1st statement in the script).

All 4 comments

I can't speak to the underlying design issues (how things _should_ work in this case), but I can offer a pointer:

Your code would work if you put the module code into a *.psm1 file instead and import it with Import-Module or using module.

Note that only with using module is the module's custom class truly imported into the caller's scope and accessible as [c] (modules neither by default export custom classes nor allow their explicit exporting; they're imported (implicitly) only with a using module statement, which is used at _parse_ time and must be the very 1st statement in the script).

Your code would work if you put the module code into a *.psm1 file instead and import it with Import-Module...

Interesting. I confirmed this behavior. I wonder if there is a good reason for the discrepancy in behaviors between the .psm1-backed and dynamic modules.

@daxian-dbw I remember discussing this with you. What are the exact mechanics here?

This issue is a duplicate of #2841

All classes in a script block, including those in the nested script blocks, are emitted when the top level script block (scriptblockast.Parent == null) is compiled. After emitting the dyanmic types, the compiler will generate code to initialize the produced types when the top-level script block runs, of which one task is to associate thsoe types with the current active session state, so instances of those types can run methods using that session state.

This casues a problem to New-Module m { class c { ... } }, where the class c is supposed to be associated with the module session state when it's evaluated within New-Module, but it has already been associated with the active session state when running the top-level scirpt block New-Module.
The instance of foo cannot find Invoke-Something because it's not using the module session state when running Invoke().

When Invoke() runs, it's actually using the outer session state to resolve the command, and in this case, if Invoke-Something is defined in the scope chain in the outer session state, then it will find it. See the below code for comparison.

& {
 function Invoke-Something { "Something" }
 New-Module m {
     class c {
         [object] Invoke () { return Invoke-Something }
     }
     function New-InstanceOfC { [c]::new() }
 } | Out-Null
 (New-InstanceOfC).Invoke()
 }
Something
Was this page helpful?
0 / 5 - 0 ratings