how to export a class in powershell v5 module

后端 未结 10 691
不思量自难忘°
不思量自难忘° 2020-12-08 14:53

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

相关标签:
10条回答
  • 2020-12-08 14:55

    This certainly does not work as expected.
    The idea in PS 5 is that you can define your class in a separate file with a .psm1 extension.
    Then you can load the definition with the command (eg):

       using module C:\classes\whatever\path\to\file.psm1
    

    This must be the first line in your script (after comments).


    What causes so much pain is that even if the class definitions are called from a script, the modules are loaded for the entire session. You can see this by running:

        get-module
    

    You will see the name of the file you loaded. No matter if you run the script again, it will NOT reload the class definitions! (Won't even read the psm1 file.) This causes much gnashing of teeth.


    Sometimes - sometimes - you can run this command before running the script, which will reload the module with refreshed class definitions:

        remove-module  file
    

    where file is the name without path or extension. However to save your sanity I recommend restarting the PS session. This is obviously cumbersome; Microsoft needs to clean this up somehow.

    0 讨论(0)
  • 2020-12-08 14:58

    I have tried every approach on this page and I have YET to find one that works repeatedly. I find the module caching is the most frustrating aspect of this... I can import classes as modules and even get code to work, but powershell only recognizes the new class intermittently.. its almost like Powershell just forgets you ever added one...

    0 讨论(0)
  • 2020-12-08 15:00

    According to here and here, you can use classes defined in your module by doing the following in PowerShell 5:

    using module holidays
    
    0 讨论(0)
  • 2020-12-08 15:01

    To update class definitions while developing select the code for the class and press F8 to run the selected code. Not as clean as the -Force option on the Import-Module command. Seeing as Using Module doesn't have that option and Remove-Module is sporadic at best, this is the best way I have found to develop a class and see the results without having to close down the ISE and start it up again.

    0 讨论(0)
  • 2020-12-08 15:03

    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 using statement does not work for modules not in PSModulePath unless you specify the module's full path in the using statement. This is rather surprising because although a module is available via Get-Module the using statement may not work depending on how the module was loaded.
    • The using statement can only be used at the very beginning of a "script". No combination of [scriptblock]::Create() or New-Module seems overcome this. A string passed to Invoke-Expression seems to act as a sort of standalone script; a using statement at the beginning of such a string sort of works. That is, Invoke-Expression "using module $path" can succeed but the scope into which the contents of the module are made available seems rather inscrutable. For example, if Invoke-Expression "using module $path" is used inside a Pester scriptblock, the classes inside the module are not available from the same Pester scriptblock.

    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.

    0 讨论(0)
  • 2020-12-08 15:04

    You pretty much cannot. According to about_Classes help:

    Class keyword

    Defines a new class. This is a true .NET Framework type. Class members are public, but only public within the module scope. You can't refer to the type name as a string (for example, New-Object doesn't work), and in this release, you can't use a type literal (for example, [MyClass]) outside the script/module file in which the class is defined.

    This means, if you want to get yourself a data_block instance or use functions that operate those classes, make a function, say, New-DataBlock and make it return a new data_block instance, which you can then use to get class methods and properties (likely including static ones).

    0 讨论(0)
提交回复
热议问题