Home > database >  PowerShell: use "using"-like functionality in jobs to import modules including classes
PowerShell: use "using"-like functionality in jobs to import modules including classes

Time:07-30

I have a solution in Powershell (7) where I have a module that includes a class, like

test.psm1

class TestClass {
    [string]$Test

    TestClass(
        [string]$_test
    ) {

        $this.Test = $_test
    }

    [void] TestMethod() {
        xxx
    }
}

In the main script I can successfully import the module including its classes and work with it.

In the main script I also want to process certain steps in parallel using jobs, like

using module .\test.psm1

[TestClass]$test = [TestClass]::new()
$job = $objects | ForEach-Object -Parallel {
    [TestClass]$testJob = $using:test   
} -ThrottleLimit $ParallelJobsThrottleLimit -AsJob

In these jobs I want to use an object of type TestClass that has been declared outside of the parallel statement.

All my approaches so far failed with Unable to find type.

How can I make this work?

CodePudding user response:

As of PowerShell 7.2.x, the instances of a script block ({ ... }) passed to ForEach-Object -Parallel, which run in parallel runspaces, do not see any of the caller's runspace state, which includes class definitions.

  • Hence the need for the $using: scope to reference values stored in the caller's variables.

  • Unfortunately, there is no equivalent mechanism for custom classes.

  • GitHub issue #12240 discusses a future enhancement that will allow parallel runspaces to see at least parts of the caller's state, which would hopefully included classes.

The problem, in a nutshell:

  • In order for parallel runspaces to see your [TestClass] too, they would have to import its containing module.

  • However, only when you import a module via using module are a module's classes visible to the caller (not also when you use Import-Module).

  • Unfortunately, using statements may only be placed at the start of a script file, and therefore cannot be used inside script blocks.

There is a cumbersome workaround:

  • Define your class in a regular .ps1 script that you reference from the ScriptsToProcess entry of your module's manifest file (.psd1)

  • That way, when the module is first imported into a runspace, the .ps1 script is dot-sourced in the caller's (importer's) scope, and class definitions therefore become visible.

  • This allows you to use a regular Import-Module call in your parallel script block in order to see the class definition there.

Note that your module itself will not see such class definitions by default - unless you explicitly dot-source the .ps1 file from the module's top-level code too.

  • Related