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?

TGlatzer
  • 5,815
  • 2
  • 25
  • 46

4 Answers4

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.

TGlatzer
  • 5,815
  • 2
  • 25
  • 46
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 as of Windows PowerShell v5.1 / PowerShell Core 7.3.1]
    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.1, types loaded from assemblies - whether via a module containing assemblies imported with using module or using assembly to directly load an assembly - aren't yet detected.

      • 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-lighted 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.

mklement0
  • 382,024
  • 64
  • 607
  • 775
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

Mike Twc
  • 2,230
  • 2
  • 14
  • 19
  • 1
    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 – TGlatzer Sep 15 '18 at 06:27
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.

Vopel
  • 662
  • 6
  • 11