It seems in PowerShell (7.4), it is not possible to write advanced functions that can intelligently make use of both ValueFromPipeline
and ValueFromPipelineByPropertyName
:
function Set-FooBarPlayer{
[CmdletBinding()]
Param(
[Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)]
[Alias('Fullname')]
$path
)
process{$path}
}
With the above in mind, I am expecting a invocation like the following to output:
[pscustomobject]@{fullname = 'c:\temp\songA.mp3' ; length = '00:03:30:070'}, ('c:\temp\songB.mp3', 'c:\temp\songC.mp3')|Set-FooBarPlayer
#c:\temp\songA.mp3
#c:\temp\songB.mp3
#c:\temp\songC.mp3
but instead I get a mix of psCustomObject
and strings
output:
fullname length
-------- ------
c:\temp\songA.mp3 00:03:30:070
c:\temp\songB.mp3
c:\temp\songC.mp3
There are times when I am piping in strings, a structured object with a file path property, or a mix of both. I was under the assumption that [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)]
made this possible but I don't think that is correct.
I have been trying to solve this problem in a number of ways, one that I thought was working is:
function Set-FooBarPlayer{
[CmdletBinding()]
Param(
[Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)]
[Alias('Fullname')]
$path
)
begin{$col = [System.Collections.Generic.List[object]]::new()}
process{if ($path -is [string]){$col.add($path)}else{$col.add($path.Fullname)}}
end{$col}
}
but calling this solution with the same input as the above example, returns only the psCustomObject
path:
[pscustomobject]@{fullname = 'c:\temp\songA.mp3' ; length = '00:03:30:070'}, ('c:\temp\songB.mp3', 'c:\temp\songC.mp3')|Set-FooBarPlayer
#c:\temp\songA.mp3
Am I confusing things and this is already possible and I am just not going about it the right way? I hope so.
I was under the assumption that
[Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)]
made this possible but I don't think that is correct.
You're correct but you need to help the binder by adding an expected type to your parameter, in this case string
, so that when the object being piped is not a string it will bind the property value by property name (or by alias in this case).
function Set-FooBarPlayer {
[CmdletBinding()]
Param(
[Parameter(
Mandatory,
ValueFromPipeline,
ValueFromPipelineByPropertyName)]
[Alias('Fullname')]
[string] $path
)
process {
"[$path]"
}
}
@(
[pscustomobject]@{
fullname = 'c:\temp\songA.mp3'
length = '00:03:30:070'
}
'c:\temp\songB.mp3'
'c:\temp\songC.mp3'
) | Set-FooBarPlayer
A notable mention here, as I see you have ('c:\temp\songB.mp3', 'c:\temp\songC.mp3')
in your example, this would mean you would be trying to pipe a psobject
first and then an array in which case the parameter type would need to be changed to string[]
(string array) so it can accept it from pipeline then your process
block should unroll it by looping:
function Set-FooBarPlayer {
[CmdletBinding()]
Param(
[Parameter(
Mandatory,
ValueFromPipeline,
ValueFromPipelineByPropertyName)]
[Alias('Fullname')]
[string[]] $path
)
process {
foreach ($p in $path) {
"[$p]"
}
}
}
@(
[pscustomobject]@{
fullname = 'c:\temp\songA.mp3'
length = '00:03:30:070'
},
('c:\temp\songB.mp3', 'c:\temp\songC.mp3')
) | Set-FooBarPlayer
In both examples, the expected output would be:
[c:\temp\songA.mp3]
[c:\temp\songB.mp3]
[c:\temp\songC.mp3]