1

I developed a PowerShell module that relied on a .NET Assembly for some operations.
I refactored this module to not need that Assembly and noticed some strange behavior, which blocks me from removing the class at last:

Add-Type -TypeDefinition "public interface ICanTalk { string talk(); }" -Language CSharp

class Talker : ICanTalk {
    [string] talk() { return "Well hello there"; }
}

If you run this commands interactively, it will succeed. But as soon as I run it "en-bloque" in ISE or from a psm1 file, it will throw an error, stating it cannot find the interface defined in the Add-Type call.

I can reproduce the problem in both Windows-PowerShell and PowerShell Core (6.0.2)

What is the reason for the different behaviour and how can I tackle it?

8
  • 1
    Don't trust the ISE. Commented Aug 10, 2018 at 21:58
  • 1
    Probably you are right, but the sample is a minimal reproduction of the problem and it also applies to psm1 files loaded via ipmo. Commented Aug 10, 2018 at 22:00
  • 1
    Thanks for the clarification. I can say your test case occurred for me as well in 6.0.3. It feels like the Add-Type command isn't being executed. Commented Aug 10, 2018 at 22:06
  • 1
    Perhaps classes are parsed before anything else has the chance to execute? Commented Aug 10, 2018 at 22:09
  • 1
    Possible duplicate of Using .Net Objects within a Powershell (V5) Class Commented Aug 11, 2018 at 1:56

4 Answers 4

1

To complement your own answer:

PowerShell class and enum definitions are parsed before execution begins and any types referenced in such definitions must either be:

  • loaded into the current session beforehand.

  • [only partially implemented in Windows PowerShell / in PowerShell (Core)] as of v7.3.x]
    loaded via a using module / using assembly statement at the very top of the file.

    • using module already works, but only if the referenced types are themselves PowerShell class / enum definitions.

    • As of PowerShell 7.3.x, types loaded from .NET assemblies - whether via a module containing assemblies imported with using module or using assembly to directly load an assembly - aren't yet detected.[1]

      • See the following GitHub issues:
        • #3461 (using module) and #2074 (using assembly)
        • #6652 - a meta issue tracking all class-related issues.
    • Allowing using assembly to detect .NET types at parse time has been green-lit in GitHub issue #3641, and the necessary work is being tracked as part of GitHub issue #6652 - but it is unclear when this will happen, given that the issue hasn't received attention in several years.


The workaround in the meantime is to load the referenced types via a separate script beforehand, by using the module manifest's NestedModules entry (which can also be used to dot-source *.ps1 files).

  • Note: The ScriptsToProcess entry would work too, but the scripts it references are dot-sourced in (loaded into the current scope of) the caller, not the enclosing module.
    With types from compiled code (including ad hoc-compiled code with Add-Type), that works too, because such types invariably become available session-globally, but it is conceptually cleaner to use NestedModules, because it dot-sources scripts in the enclosing module's scope

Outside of modules, the workaround is similar:

  • Put your class definition in a separate .ps1 file.

  • In your main script, define or load the types that your class definition depends on, and then dot-source the separate script file, which ensures that all its dependent types have already been loaded.

  • In a pinch, you can also use Invoke-Expression (which should generally be avoided), as shown in this answer.


[1] Note that using Add-Type with -Assembly or -Path / -LiteralPath from inside the script is by definition not an option, because Add-Type only executes at runtime (rather than the using statements, which are evaluated at parse time), i.e. too late to load .NET types available up front. However, calling Add-Type from outside the target script, before the latter is invoked, does work.

Sign up to request clarification or add additional context in comments.

Comments

1

Apparently PowerShell will read the file and handle class-definitions before executing the code.

To solve this problem, the Add-Type needs to be put into an own script file, which is run before the module loads (or in ISE, just run the code before running the class definitions).

This can be accomplished by using ScriptsToProcess from the PSD1 file.

Kudos to @TheIncorrigible1 for putting me on the track.

Comments

0

Why not defining all classes in C# in a separate file, than add it as Add-Type -path MyClasses.cs This way it will work with older PS versions

1 Comment

The module needs Core 6.0 or Desktop Edition with NET 472 installed, so there's no use in supporting older versions. Also I wanted it to be PS only as far as posdible
0

If you define -PassThru and bind the Add-Type to a variable, you can use the variable to examine what all is being added.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.