Custom classes in powershell script modules

Hi,

I’ve been trying to use a custom class in an Octopus script module to be used by process and runbook steps, and not having much luck. Here’s a stripped down basic script I wrote as an example to demonstrate the issue;

class testclass
{
    static [string]$testmember = "testvalue"
}

$testvar = [testclass]::testmember
Write-Host $testvar

If I put all of this code into a ‘run a script’ step within a process, it executes exactly as expected. However if I place the top 4 lines in a script module, include that module in a process, then only execute the bottom two lines from within that process, I get the following;

October 5th 2021 11:52:42 Error InvalidOperation: Unable to find type [testclass].
October 5th 2021 11:52:42 Error At C:\Octopus\Work\20211005105240-207610-134007\Script.ps1:3 char:12
October 5th 2021 11:52:42 Error + $testvar = [testclass]::testmember

I can see that the module is being loaded in (by adding debug write-host messages) but the class still can’t be referenced

I’ve tried;

a) wrapping the class in a function then explicitly calling that function
b) wrapping the class in a here-string and calling it with Invoke-Expression
c) publishing a PowerShell module (psm1) and consuming it with using module

All of the above work fine when executed from within the process step, but not when part of an Octopus script module.

Is there an extra requirement to get this to work, or is it simply unsupported?

Thanks in advance.

Hi Ryan,

The interesting thing about script modules is how they are referenced / stored inside of Octopus. Believe it or not, they are actually stored as library variable sets that are injected as text when a script runs.

In terms of creating classes, we’ve found that this approach works with Octopus: Using classes in custom step templates - Octopus Deploy

I hope that helps!

Hi Bob,

Thanks for the response - very interesting stuff and will no doubt be useful in the future. However in this case it doesn’t seem to help me, as my real-world classes are quite extensive and can’t be easily re-implemented in C#, and Add-Type can’t be used to register PowerShell classes.

Regarding the point about injecting scripts - again very interesting but I can’t understand how that should impact the ability to reference any class within the injected part of the script.

If I have a script module like this;

function testfunction
{
   Write-Host "test function output"
}
class testclass
{
    static [string]$testmember = "testvalue"
}
Write-Output "module initialised"

and a process step like this;

testfunction
$testvar = [testclass]::testmember
Write-Host $testvar

Why can the function be called but the class can’t?

October 5th 2021 20:49:19 Info module initialised 
October 5th 2021 20:49:19 Info test function output 
October 5th 2021 20:49:19 Error InvalidOperation: Unable to find type [testclass]. 
October 5th 2021 20:49:19 Error At C:\Octopus\Work\20211005194918-207813-134767\Script.ps1:2 char:12 
October 5th 2021 20:49:19 Error + $testvar = [testclass]::testmember 
October 5th 2021 20:49:19 Error +            ~~~~~~~~~~~ 

Why does the above not work, yet defining the class in the script step does work?

Hi Ryan,

With Calamari, we create a bootstrap script file (Calamari/Bootstrap.ps1 at master · OctopusDeploy/Calamari · GitHub). Calamari replaces {{ScriptModules}} with the script modules you specify.

The actual script you are going to run is loaded as a file and referenced by the dot sourcing operator.

Unfortunately, while functions and variables have no problem being imported by that target script file, classes are another story: class - PowerShell dot source within a dot sourced file - import classes - Stack Overflow

I hope that helps!

The dot sourcing explanation makes sense. I guess the only way to do this would be to create a psm1 module file, place it on the Octopus server’s local filesystem, and import it from the process step.

Thanks for your help!

You are welcome! Happy to help.