powershellclasstypespowershell-corepowershell-7.4

How to load the classes in PowerShell module globally?


I've already read this question and even though it has lots of back and forth, I tried my best to follow the suggestions, but still not sure how to work around this.

I have defined multiple classes in the ScriptsToProcess .ps1 file like this:

https://github.com/HotCakeX/Harden-Windows-Security/blob/WDACConfig-v0.3.4/WDACConfig/WDACConfig%20Module%20Files/Preloader.ps1

The problem is that when I install the module in a clean state VM (where the module never existed before) and try to use the cmdlets, I get an error saying the type is not found, but if I close and reopen PowerShell or use Import-Module with -Force parameter then everything is okay.

I need to know how I can work around this problem without introducing too many repetitive code.

I'm using PowerShell 7.5


Solution

  • I don't fully understand the VM-related problem, but perhaps the following approach bypasses the problem:

    Note that with either approach above, classes and enums won't be available until after your module has been imported into a given session. That is, unlike functions and cmdlets in auto-loading modules, they aren't discoverable prior to import.

    Example *.psm1 root-module content:

    # Since the quasi-exported class will be available *process-wide*
    # and therefore also in *other runspaces*, be sure to define it with
    # the [NoRunspaceAffinity()] attribute.
    # Caveat: **v7.4+ only**
    [NoRunspaceAffinity()]
    class SomeClass { # Note: 'SomeClass' is both its .Name and .FullName.
      [int] Get() { return 42 }
    }
    
    # Define the types to export with type accelerators.
    # Note: Unlike the `using module` approach, this approach allows
    #       you to *selectively* export `class`es and `enum`s.
    $exportableTypes = @(
      [SomeClass]
    )
    
    # Get the non-public TypeAccelerators class for defining new accelerators.
    $typeAcceleratorsClass = [psobject].Assembly.GetType('System.Management.Automation.TypeAccelerators')
    
    # Add type accelerators for every exportable type.
    $existingTypeAccelerators = $typeAcceleratorsClass::Get
    foreach ($type in $exportableTypes) {
      # !! $TypeAcceleratorsClass::Add() quietly ignores attempts to redefine existing
      # !! accelerators with different target types, so we check explicitly.
      $existing = $existingTypeAccelerators[$type.FullName]
      if ($null -ne $existing -and $existing -ne $type) {
        throw "Unable to register type accelerator [$($type.FullName)], because it is already defined with a different type ([$existing])."
      }
      $typeAcceleratorsClass::Add($type.FullName, $type)
    }
    

    Any (possibly implicit) importer of the module associated with this *.psm1 will have the [SomeClass] class available to them at runtime.

    That is, [SomeClass]::new().Get() in the caller's scope and any scope thereafter should yield 42