#Custom FormatData not applied to Custom Class

75 messages · Page 1 of 1 (latest)

thorn relic
#

Things I have tried so far:

  1. Try to verify, that the TypeName in ps1xml matches with the custom class, and that the overall syntax of the XML file is correct (I assume, Update-FormatData would throw an error if there was an error here?)
  2. Check with Get-FormatData, that the imported format data is available in the current session (that does not appear to be the case, see attached screenshot)

More context: Battery is a custom class that's returned from a Get-Battery Cmdlet:

[NoRunspaceAffinity()]
class Battery {
    [int] $ChargeRemaining
    [timespan] $Runtime
    [bool] $IsCharging
    [string] $Status

    Battery([int] $ChargeRemaining, [timespan] $Runtime, [bool] $IsCharging, [string] $Status) {
        $this.ChargeRemaining = $ChargeRemaining
        $this.Runtime = $Runtime
        $this.IsCharging = $IsCharging
        $this.Status = $Status
    }
}

I am following this Guide here

https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_classes?view=powershell-7.4#exporting-classes-with-type-accelerators

for exporting classes which seems to be working great, except that FormatsToProcess is also failing silently here, meaning that the verbose output message indicates that the format data was updated, yet it not applied on the object if returned from a Cmdlet.

Any ideas what else could be the issue here?

prime wind
#

can you share your code and format file please

thorn relic
#

would you prefer to have a look at the files here, or on GitHub?

prime wind
#

whatever works for you really. The class definition you have above is fine, and the snippet of the format file looks fine too

thorn relic
#

Alright, hold on for a sec, need to commit something on my feature branch then

prime wind
#

is there a built version of the module that has FormatsToProcess set?

thorn relic
#

after you run build.ps1, this property should be set

prime wind
#

hm you never import your classes

#

but you can't do that until after the class file has been loaded

#

and you never load it

#

Fwiw there is absolutely no need for you to mess with type accelerators if your class is just output from a command

thorn relic
#

alright, then how come I can use the constructor in the Cmdlet?

that's just a playground for learning so I don't mind messing around with that

#

how would you suggest should I import the classes then?

prime wind
#

I cannot possibly say what else you did in your session to end up with the type loaded. But you don't load it in your module right now so I get a big:

InvalidOperation: Unable to find type [Battery]
``` when attempting to load your module
#

classes, in the context of your code, are no different from your public and private functions

cinder orbit
#

If you're using a type accelerator, you couldn't unload those in the past. so you might have an old instance actually in your session

prime wind
#

there is a difference in that they're parse-time artifact, but since you have everything in separate files already you're not really affected by that

#

that too, PS classes are created in a dynamic assembly, and each time you import your module a new one is created

thorn relic
prime wind
#

your type accelerator can therefore end up pointing to a stale implementation of the class

thorn relic
#

oh, you mean that I do something like this?

$Classes = @(Get-ChildItem -Path "${PSScriptRoot}\Classes\*.ps1" -ErrorAction SilentlyContinue)
prime wind
#

yep

cinder orbit
#
foreach ($Import in @($Public + $Private)) {

I'm guessing you'd want to source the private ones first, if public uses them?

prime wind
#

doesn't matter for functions

#

only matters when the function is called

cinder orbit
#

as long as they aren't top level defintions for types?

prime wind
#

classes do matter where there's a thing depending on the class. I'd have them load first just to save pain because those are parse-time.

In this particular case, it shouldn't matter when they load because there's only one and it's not cross-referenced until function runtime

thorn relic
#

at least there are visible errors for me now

cinder orbit
#

Stefan: Did you setup the schemas for types/format.ps1xml files?

prime wind
#

did you pop out the type accelerator stuff?

thorn relic
prime wind
#

cool, that'll be the cause of the error. If you cull that it should all load

cinder orbit
#

re: xml: If not I can lookup the config

prime wind
#

otherwise you'd have to move that block after your content import steps. I'd just cull it though

thorn relic
#

okay, without the accelarator stuff the build now works again, but I still get this error:

cinder orbit
prime wind
#

run ```ps
& (Get-Module Toolbox) { [Battery] }

thorn relic
prime wind
#

excellent, that's an improvement

#

comment out your OutputType for Get-Battery for a moment. Restart PS and see if runs

thorn relic
#

ah, I remember this error from two years ago now

the "fix" I used was [OutputType("Battery")] but I lost the type completion in the terminal

prime wind
#

internal classes are acceptable values for the OutputType attribute, but I never dot-source like this and I'm wondering if it's getting a bit hung up on it

prime wind
#

cool, expected it to, it would have been a bit odd for it to still claim it couldn't find the class. But OutputType is a bit of a tricker thing though. It sort of runs in user scope, but like I say you can use types defined by classes within a module normally

#

would you mind pushing your changes to your branch?

thorn relic
#

yeah sure

#

done

prime wind
#

yeah it's an oddity of how classes load and OutputType. If the class is defined in the same file it'll just work. But dot-sourced... it doesn't like.

#

it doesn't even like it if the class is moved into the psm1. I think it must be creating something of a closure for OutputType to deal with types not being exported from modules

#

I would consider using ModuleBuilder if I were you. Your module structure will pretty much just work and you'll end up with a single psm1 and none of these problems.

thorn relic
#

yeah, maybe I should fall back to a PSCustomObject

Modules that realy want these strongly typed objects are probably better off being writtin in C#, though that's not as convencience for simple scripting

prime wind
#

otherwise you'll likely have to shift the definition of your battery type into C# and then into a dll

#

I use them extensively, but always in a merged psm1 because it just works better

thorn relic
#

For what it's worth, the custom formatter is still not applied to the output object though 🤔

cinder orbit
#

Here's a sort of tangent conversation, it's related to outputtype and binarycmdlets
There's a few if you search for: from:seeminglyscience outputtype
#fringe message

prime wind
#

heh you'll hate this...

#

it's the whitespace in your XML

cinder orbit
prime wind
#

if you make it ```xml
<TypeName>Battery</TypeName>

thorn relic
#

haha oh no :D

prime wind
#

the format is hosed though, all the rest of that white space you have around stuff is making a real messs

#

so long and short, delete white space in element values

#

the only one in there that's fine is your script block

thorn relic
#

seriously though, this gives me flashbacks to HTML an how some browsers insert whitespaces in front of inner elements if you wrap your code like that

prime wind
#

you need to give your script block list item a label

#
                            <ListItem>
                                <Label>ChargeRemaining</Label>
                                <ScriptBlock>
#

after those are in I get: ```ps
PS> get-battery

Status : Unknown
IsCharging : False
ChargeRemaining : 0%
Runtime : 00:00:00

thorn relic
#

ah thanks man, that's it!

prime wind
#

Fwiw you can lose the .Dispose() on your CimInstance. Not really giving you anything, it'll get cleaned up when the GC runs and it's not sitting on anything notable

thorn relic
#

I will have a look at the fringe conversation later, gonna grab something to eat now