5

Is there a neat way to convert a PSCustomObject to a custom class as a function parameter in PowerShell 5.1? The custom object contains additional properties.

I'd like to be able to do something like this:

class MyClass {
    [ValidateNotNullOrEmpty()][string]$PropA
}

$input = [pscustomobject]@{
    PropA          = 'propA';
    AdditionalProp = 'additionalProp';
}

function DuckTypingFtw {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $True, Position = 0, ValueFromPipeline)] [MyClass] $myObj
    )
    'Success!'
}

DuckTypingFtw $input

Unfortunately, instead of Success!, I'm getting:

DuckTypingFtw : Cannot process argument transformation on parameter 'myObj'. Cannot convert value "@{PropA=propA; AdditionalProp=additionalProp}" to type "MyClass". Error: "Cannot convert the "@{PropA=propA; AdditionalProp=additionalProp}" value of
type "System.Management.Automation.PSCustomObject" to type "MyClass"." At C:\temp\tmp.ps1:23 char:15 + DuckTypingFtw $input + ~~~~~~ + CategoryInfo : InvalidData: (:) [DuckTypingFtw], ParameterBindingArgumentTransformationException + FullyQualifiedErrorId : ParameterArgumentTransformationError,DuckTypingFtw

If I comment out the AdditionalProp, everything works fine.

Basically, what I want to achieve, is to return an object from one function and pass it to a second function, at the same time ensuring that the second function's param has all expected properties.

Grzegorz Smulko
  • 2,525
  • 1
  • 29
  • 42

3 Answers3

5

If you create a constructor for the MyClass class that accepts an pscustomobject and pass through the property then that should work:

class MyClass {
    MyClass([pscustomobject]$object){
        $this.PropA = $object.PropA
    }
    [ValidateNotNullOrEmpty()][string]$PropA
}

$input = [pscustomobject]@{
    PropA          = 'propA';
    AdditionalProp = 'additionalProp';
}

function DuckTypingFtw {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $True, Position = 0, ValueFromPipeline)] [MyClass] $myObj
    )
    'Success!'
}

DuckTypingFtw $input

edit: If you want to also use MyClass somewhere else, add a default constructor for MyClass like:

class MyClass {
    MyClass() { } 
    MyClass([pscustomobject]$object){
        $this.PropA = $object.PropA
    }
    [ValidateNotNullOrEmpty()][string]$PropA
}
Jacob Struiksma
  • 149
  • 2
  • 6
2

Another possibility, besides the solution mentioned by @JacobStruiksma, is to introduce an implicit or explicit cast operator (the names of the operators are case-sensitive!):

class MyClass {
    [ValidateNotNullOrEmpty()][string]$PropA
    MyClass() {} # if a parameterless constructor is required elsewhere
    MyClass([string]$PropA) {
        $this.PropA = $PropA
    }
    hidden static [MyClass] op_Implicit([PSObject]$myObj) {
        return [MyClass]::new($myObj.PropA)
    }
#   hidden static [MyClass] op_Explicit([PSObject]$myObj) {
#       return [MyClass]::new($myObj.PropA)
#   }
}

$myInput = [pscustomobject]@{
    PropA          = 'propA';
    AdditionalProp = 'additionalProp';
}

function DuckTypingFtw {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $True, Position = 0, ValueFromPipeline)] [MyClass] $myObj
    )
    'Success!'
}

DuckTypingFtw $myInput

[In your case, bothBoth] operators work identically. [In other cases, op_Explicit offers a bit more control, but at the expense of typing.]

By the way, $Input is as system variable that should be considered read-only.

[Edit 1: PowerShell's own conversion function using a class constructor corresponds to op_Explicit.]

Edit 2: According to @Vopel 's comment, there doesn't seem to be any difference in the behavior of both operators in a PowerShell script.

Olli
  • 327
  • 2
  • 9
  • Can I ask when op_Explicit is different from op_Implicit as far as PowerShell classes are concerned? I can find info about the difference in C# but considering in PowerShell you always use the explicit syntax, I'm not sure how it could ever matter for classes. – Vopel May 17 '23 at 14:48
  • @Vopel Sorry for not answering for a long time! I've overseen your question. As far as I understand, PowerShell and C# may behave similarly. The example shows an implicit casting: `DuckTypingFtw $myInput`. If only op_Explicit would be defined, one would have to do `DuckTypingFtw [MyClass]$myInput`. – Olli Jul 16 '23 at 19:00
  • 1
    Seems the examples you gave work in either case. I think PowerShell just makes everything implicit. – Vopel Jul 17 '23 at 00:49
  • @Vopel: You're right! I recalled noticing differences in one particular situation (i.e. using `op_Implicit` required a casting where `op_Explicit` did not). However, I cannot reproduce this behavior. So I must have misinterpreted something then. I will amend my answer accordingly. – Olli Jul 25 '23 at 19:25
-2

In your code you define two properties for the custom object but one for the class. That's why you need either:

  • to add AdditionalProp in your class
  • to remove AdditionalProp from your PsCustomObject

The conversion is not possible by design.

Zafer Balkan
  • 176
  • 12
  • Yes, that's the trivia, and as I said: "If I comment out the AdditionalProp, everything works fine.". I still hope that there is a way - a custom converter perhaps? – Grzegorz Smulko Oct 10 '19 at 20:42
  • You can define a function like `Map-Property` that accepts both a class and pscustomobject and use reflection on them, possibly via `Get-Member` and iterate through properties. So that you can match them and ignore the ones which do not "map" properly. – Zafer Balkan Oct 10 '19 at 20:50
  • Yes, well, but the `DuckTypingFtw` is a public API, so I can't expect that consumers would call `Map-Property`. It needs to be transparent... – Grzegorz Smulko Oct 10 '19 at 20:58
  • It seems like the only way is returning an instance of class `MyClass` and never use the `pscustomobject`. – Zafer Balkan Oct 11 '19 at 04:12