Search code examples
powershellparametersargumentsparameter-passing

How to make a switch parameter work with 'valuefrompipelinebypropertyname'?


I am building a function. At times, I want to be able to provide parameter inputs for the function via the pipeline, because often the values are found on a csv file:

$CsvData = @"
Label,Key, IsSubTrigger
Function10,f10, 0
Function11,f11, 1
Function12,f12, true
Function13,f13, false
"@ |ConvertFrom-Csv

The following is the bare bones function:

Function foo{
     [CmdletBinding()]
     Param(
        [parameter(ValueFromPipelineByPropertyName)]
        [string]$Label,
        [parameter(valuefrompipelinebypropertyname)]
        [string]$key,
        [parameter(valuefrompipelinebypropertyname)]
        [switch]$IsSubTrigger,
        
        [parameter(ValueFromPipeline)]
        [string]$Path
     )

     Process{
        "Label:" + $Label,
        "key: " + $Key,
        "IsSubTrigger: " + $IsSubTrigger
     }
}

Invoking $CsvData | foo outputs an error for each csv row:

foo: Cannot process argument transformation on parameter 'IsSubTrigger'. Cannot convert value "System.String" to type "System.Management.Automation.SwitchParameter". Boolean parameters accept only Boolean values and numbers, such as $True, $False, 1 or 0.

I have tried solving this but but have sadly gotten nowhere.


Solution

  • PowerShell doesn't know how to properly coerce a string into a SwitchParameter, which leads to the issue you're observing:

    [switch] 'true'
    # InvalidArgument: Cannot convert the "true" value of type "System.String" to type "System.Management.Automation.SwitchParameter".
    

    What you can do to help it, is create an argument transformation class, essentially a class that inherits from ArgumentTransformationAttribute, that handles the logic for converting a string into a bool, then that bool can be coerced into SwitchParameter without issues later on:

    class BoolParseTransformation : System.Management.Automation.ArgumentTransformationAttribute {
        [Object] Transform([System.Management.Automation.EngineIntrinsics] $engineIntrinsics, [Object] $inputData) {
            $int = 0
            if ([int]::TryParse($inputData, [ref] $int)) {
                return [bool] $int
            }
    
            return [bool]::Parse($inputData)
        }
    }
    

    Then in your param block, you can implement it like this:

    function foo {
        [CmdletBinding()]
        param(
            [Parameter(ValueFromPipelineByPropertyName)]
            [string] $Label,
    
            [Parameter(ValueFromPipelineByPropertyName)]
            [string] $key,
    
            [Parameter(ValueFromPipelineByPropertyName)]
            [BoolParseTransformation()]
            [switch] $IsSubTrigger,
    
            [Parameter(ValueFromPipeline)]
            [string] $Path
        )
        
        process { $PSBoundParameters }
    }