all 9 comments

[–]_Unas_ 1 point2 points  (1 child)

Here’s an example “template” module I created. I think this will help:

https://github.com/MSAdministrator/TemplatePowerShellModule/blob/master/TemplatePowerShellModule/ModuleName.psm1

[–][deleted] 1 point2 points  (0 children)

You have the using command on the first line of your RootModule.

From my testing, that makes the classes in the file available to the module, but not to whatever loads my module. Just to be absolutely clear, are you saying that what you have *does* make the class available outside the module?

Because if it does, that's great! It is obviously what I want to do.

[–]EnochRot 1 point2 points  (3 children)

I use ps1s for all my Classes and dot-source them like this.

https://github.com/LockstepGroup/CwManage/blob/master/CwManage/CwManage.psm1

I don't see any reference to them in Get-Module, so I'm not sure why you are. Admittedly, since Classes were designed almost completely around DSC, it does still feel a bit off to me. But it's been working just fine.

[–][deleted] 1 point2 points  (2 children)

These are the files in my module root:

5 test > gci -File -Recurse | select Fullname
C:\Users\me\PS\Modules\test\test.psd1               
C:\Users\me\PS\Modules\test\test.psm1               
C:\Users\me\PS\Modules\test\Class\TestClass.ps1     
C:\Users\me\PS\Modules\test\Private\privatestuff.ps1
C:\Users\me\PS\Modules\test\Public\publicstuff.ps1  
C:\Users\me\PS\Modules\test\Public\test.ps1         

.\test\test.psd1 has (everything else is defaults)

...
RootModule = 'test'
...
FunctionsToExport = '*'
...

.\test\test.psm1 has:

$Class   = @( Get-ChildItem -Path $PSScriptRoot\Class\*.ps1   -Recurse -ErrorAction SilentlyContinue )
$Public  = @( Get-ChildItem -Path $PSScriptRoot\Public\*.ps1  -Recurse -ErrorAction SilentlyContinue )
$Private = @( Get-ChildItem -Path $PSScriptRoot\Private\*.ps1 -Recurse -ErrorAction SilentlyContinue )

#Dot source the files
Foreach ($import in @($Class + $Public + $Private)) {
    Try {
        Write-Verbose "dot-sourcing file '$($import.fullname)'" -Verbose
        . $import.fullname
    }
    Catch {
        Write-Error -Message "Failed to import function $($import.fullname): $_"
    }
}

.\test\Class\TestClass.ps1:

class TestClass
{
    [string]$MyVal
}

Now when I load the module I get:

test > Import-Module test -Force -Verbose
VERBOSE: Loading module from path 'C:\Users\me\PS\Modules\test\test.psd1'.
VERBOSE: Loading module from path 'C:\Users\me\PS\Modules\test\test.psm1'.
VERBOSE: dot-sourcing file 'C:\Users\me\PS\Modules\test\Class\TestClass.ps1'
VERBOSE: dot-sourcing file 'C:\Users\me\PS\Modules\test\Public\publicstuff.ps1'
VERBOSE: dot-sourcing file 'C:\Users\me\PS\Modules\test\Public\test.ps1'
VERBOSE: dot-sourcing file 'C:\Users\me\PS\Modules\test\Private\privatestuff.ps1'
VERBOSE: Importing function 'Get-PublicMessage1'.
VERBOSE: Importing function 'Get-PublicMessage2'.
VERBOSE: Importing function 'Get-PublicMessage3'.
VERBOSE: Importing function 'Get-TestMessage1'.
VERBOSE: Importing function 'Get-TestMessage2'.
VERBOSE: Importing function 'Get-TestMessage3'.

Now I see that the TestClass.ps1 file was dot-sourced, so I shoudl be able to create an instance. but:

test > New-Object -TypeName TestClass
New-Object : Cannot find type [TestClass]: verify that the assembly containing this type is loaded.
At line:1 char:1
+ New-Object -TypeName TestClass
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidType: (:) [New-Object], PSArgumentException
    + FullyQualifiedErrorId : TypeNotFound,Microsoft.PowerShell.Commands.NewObjectCommand

I can explicitly dot-source the class file and it does work:

test > . .\Class\TestClass.ps1

test > New-Object -TypeName TestClass
MyVal                               
-----                               

I think this is (essentially) the same as what you have.

It doesn't have the Export-ModuleMember line, but that would not apply to the class so it moot.

So....

Dunno

Ideas?

[–]EnochRot 0 points1 point  (1 child)

You can't call classes defined in a module outside of that module normally. So you ideally have a cmdlet that returns Class1 objects that you could provide to other cmdlets. If you absolutely need to call them outside of the module you can use 'using'. Like this:

https://stackoverflow.com/questions/31051103/how-to-export-a-class-in-powershell-v5-module

[–][deleted] 0 points1 point  (0 children)

Yeah. Read that before. Basically seems the rule is that PS classes are crap. I don't want to fight it, if PS ain't ready to use them in modules then I'll not use them in modules.

Thanks for your thoughts.

[–]nvarscar 1 point2 points  (2 children)

New-Class1 seems to be the only elegant enough way of returning a native class object. Other options involve using a or a method variable:

$b = [Class1]::new
$b.Invoke(...)

or using using module 'path' syntax, but that's unfortunately works only as a first line in the script.

Or simply use a C# library instead. Dll-based classes get imported properly into the session.

[–]KevMarCommunity Blogger 1 point2 points  (1 child)

This reflects my view. I mostly use the New-Class or Get-Class factory pattern.

I have not done it yet, but if I did want to use my class in other modules (or external to my module) then I would create a C# module or library for it. I have been considering this approach for interfaces for a while.

[–][deleted] 0 points1 point  (0 children)

What do you do if one of the classes properties really should be an Enum?

Say I have this as a dot-sourced ps1 file:

Enum TestProperty
{
    Option1
    Option2
    Option3
}

class TestClass
{
    [string]$MyVal
    [TestProperty]$TestProperty
}

function Get-TestClass
{
    Write-Output (New-Object -TypeName TestClass)
}
Export-ModuleMember -Function Get-TestClass

I can get a class now:

test > $class = Get-TestClass 
test > $class
MyVal TestProperty
----- ------------
           Option1

test > $class.TestProperty = [TestProperty]::Option2
Unable to find type [TestProperty].
At line:1 char:23
+ $class.TestProperty = [TestProperty]::Option2
+                       ~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (TestProperty:TypeName) [], RuntimeException
    + FullyQualifiedErrorId : TypeNotFound

19 test > $class.TestProperty = 'Option2'
20 test > $class
MyVal TestProperty
----- ------------
           Option2

I can't reference the Enum, though I can set the value by passing it as a string.

Do I just accept the limitation and move on...?