powershellpowershell-5.0

How to export a class in a PowerShell v5 module


I've got a module setup to be like a library for a few other scripts. I can't figure out how to get a class declaration into the script scope calling Import-Module. I tried to arrange Export-Module with a -class argument, like the -function, but there isn't a -class available. Do I just have to declare the class in every script?

The setup:

Here is what the class looks like:

Class data_block
{
    $array
    $rows
    $cols
    data_block($a, $r, $c)
    {
        $this.array = $a
        $this.rows = $r
        $this.cols = $c
    }
}

Solution

  • PSA: There is a known issue that keeps old copies of classes in memory. It makes working with classes really confusing if you don't know about it. You can read about it here.


    using is Prone to Pitfalls

    The using keyword is prone to various pitfalls as follows:

    The above statements are based on this set of tests.

    ScriptsToProcess Prevents Access to Private Module Functions

    Defining a class in a script referred to by the module manifest's ScriptsToProcess seems at first glance to export the class from the module. However, instead of exporting the class, it "creates the class in the global SessionState instead of the module's, so it...can't access private functions". As far as I can tell, using ScriptsToProcess is like defining the class outside the module in the following manner:

    #  this is like defining c in class.ps1 and referring to it in ScriptsToProcess
    class c {
        [string] priv () { return priv }
        [string] pub  () { return pub  }
    }
    
    # this is like defining priv and pub in module.psm1 and referring to it in RootModule
    New-Module {
        function priv { 'private function' }
        function pub  { 'public function' }
        Export-ModuleMember 'pub'
    } | Import-Module
    
    [c]::new().pub()  # succeeds
    [c]::new().priv() # fails
    

    Invoking this results in

    public function
    priv : The term 'priv' is not recognized ...
    +         [string] priv () { return priv } ...
    

    The module function priv is inaccessible from the class even though priv is called from a class that was defined when that module was imported. This might be what you want, but I haven't found a use for it because I have found that class methods usually need access to some function in the module that I want to keep private.

    .NewBoundScriptBlock() Seems to Work Reliably

    Invoking a scriptblock bound to the module containing the class seems to work reliably to export instances of a class and does not suffer from the pitfalls that using does. Consider this module which contains a class and has been imported:

    New-Module 'ModuleName' { class c {$p = 'some value'} } |
        Import-Module
    

    Invoking [c]::new() inside a scriptblock bound to the module produces an object of type [c]:

    PS C:\> $c = & (Get-Module 'ModuleName').NewBoundScriptBlock({[c]::new()})
    PS C:\> $c.p
    some value
    

    Idiomatic Alternative to .NewBoundScriptBlock()

    It seems that there is a shorter, idiomatic alternative to .NewBoundScriptBlock(). The following two lines each invoke the scriptblock in the session state of the module output by Get-Module:

    & (Get-Module 'ModuleName').NewBoundScriptBlock({[c]::new()})
    & (Get-Module 'ModuleName') {[c]::new()}}
    

    The latter has the advantage that it will yield flow of control to the pipeline mid-scriptblock when an object is written to the pipeline. .NewBoundScriptBlock() on the other hand collects all objects written to the pipeline and only yields once execution of the entire scriptblock has completed.