powershellclassmodule

How to correctly import a PowerShell module with multiple class definitions


I have two PowerShell class definitions (File and Directory) and each class definition in a separate psm1 file:

File.psm1

class File
{
   [String]$Path
   
   File([String]$Path)
   {
      $this.Path = $Path
   }

}

Directory.psm1

using namespace System.Collections.Generic

using module .\File.psm1

class Directory
{
   [List[File]]$Files
   [String]$Path
   
   Directory([String]$Path)
   {
     $this.Path = $Path
     $this.Files = [List[File]]::new()
     if (Test-Path -Path $Path)
     {
        (Get-ChildItem -Path $Path -File).ForEach{
           $this.Files.Add([File]::new($_.FullName))
        }
     }
   }
}

So the directory class needs the definition of the file class.

I would like use the two classes inside a module Testmodule1.psm1.

The psm1 file Testmodule1.psm1 only contains a using module statement:

using module .\Directory.psm1

And the psd1 file Testmodule1.psd1 references Testmodule1.psm1 as the root module:

@{
  ModuleVersion = "0.0.1"
  RootModule = "TestModule1.psm1"
}

When I import the module like so:

import-module .\TestModule1.psd1 -Force -Verbose

the directory class is not known.

But when I load the module like it has been suggested by Michael Phillips here on SO, it works:

I have to import the module like so:

$m1 = import-module .\TestModule1.psd1 -Force -PassThru

and

& $m1 { [Directory]::new("G:\Skriptkiste") }

Now the Directory type is found.

I have tested this with Windows PowerShell and PowerShell 7.4.5.

My question is: Is there a cleaner way to achieve what I want?

I know this question had been the topic of many discussions on SO. But I can't believe that there is no simple and clean solution for the requirement of having multiple class definitions (with a "dependency") in PowerShell module.


Solution

  • Building on the helpful comments:

    The non-support for exporting class (and enum) definitions other than those placed directly in the root script module file (*.psm1) of a PowerShell module is a known limitation (as of PowerShell (Core) 7 v7.4.x, though a fix in the near future seems unlikely).

    Quoting from the about_Using help topic; emphasis added:

    The using module statement imports classes and enumerations from the root module (ModuleToProcess) of a script module or binary module. It doesn't consistently import classes or enumerations defined in nested modules or in scripts that are dot-sourced into the root module. Define classes and enumerations that you want to be available to users outside of the module directly in the root module.

    As has been noted:


    To spell it out:

    Given your module manifest (TestModule.psd1) whose RootModule entry references TestModule.psm1, the latter's content must be as follows in order to make both [File] and [Directory] available to importers (who must use using module (rather than Import-Module / module autoloading) to import the module, as that is the fundamental prerequisite for seeing a module's class and enum definitions):

    # Content of TestModule.psm1,
    # which must *directly* contain the definitions of *both* [File] and [Directory].
    
    using namespace System.Collections.Generic
    
    class File
    {
       [String]$Path
       
       File([String]$Path)
       {
          $this.Path = $Path
       }
    
    }
    
    class Directory
    {
       [List[File]]$Files
       [String]$Path
       
       Directory([String]$Path)
       {
         $this.Path = $Path
         $this.Files = [List[File]]::new()
         if (Test-Path -Path $Path)
         {
            (Get-ChildItem -Path $Path -File).ForEach{
               $this.Files.Add([File]::new($_.FullName))
            }
         }
       }
    }