Eris
Eris

Reputation: 7638

PSCustomObject array and ValueFromPipelineByPropertyName

Background: I have some locations I'd like to have easier access to. I don't want to mklink, because then something may save the path as the link path and fail later.

Proposed solution: Create some PSDrives. Start with a static array of hashtables, so that they can be imported/exported/filtered easily in the future.

Prep-step:

# Clear out existing drives, empty error stack
Get-PSDrive -name "*-test-*" | Remove-PSDrive 
$Error.Clear()

Failed Solution 1: Create array of hashes with parameter KVPs

@(
    @{ Name='hasharray-test-control'; Root='C:\Windows\Temp' },
    @{ Name='hasharray-test-interp-brace'; Root="${ENV:SystemRoot}\Temp" }
) | New-PSDrive -PSProvider FileSystem

Confusing error "The input object cannot be bound to any parameters for the command ..."

Failed Solution 2: Web-grokking shows that the items have to be PSCustomObject, not a hash

@(
    [PSCustomObject]@{ Name='array-test-control'; Root='C:\Windows\Temp' },
    [PSCustomObject]@{ Name='array-test-interp-brace'; Root="${ENV:SystemRoot}\Temp" }
) | New-PSDrive -PSProvider FileSystem

Fails on second item with

New-PSDrive : Cannot bind parameter 'Name' to the target. 
Exception setting "Name": 
    "Cannot process argument because the value of argument "value" is null.

Failed Solution 3: Ok, Maybe powershell is passing the array as one big blob, make it implicit

[PSCustomObject]@{ Name='implicitarray-test-control'; Root='C:\Windows\Temp' },
[PSCustomObject]@{ Name='implicitarray-test-interp-brace'; Root="${ENV:SystemRoot}\Temp" } |
    New-PSDrive -PSProvider FileSystem

Fails on second item with same error

Failed solution 4: Insert intermediate variable

$TestDrives = @(
    [PSCustomObject]@{ Name='foreach-test-control'; Root='C:\Windows\Temp' }
    [PSCustomObject]@{ Name='foreach-test-interp-brace'; Root="${ENV:SystemRoot}\Temp" }
) 
$TestDrives | New-PSDrive -PSProvider FileSystem

Fails on second item with same error

Failed Solution 5: Insert a select to extract properties

@(
    [PSCustomObject]@{ Name='select-test-control'; Root='C:\Windows\Temp' },
    [PSCustomObject]@{ Name='select-test-interp-brace'; Root="${ENV:SystemRoot}\Temp" }
) | Select Name, Root | New-PSDrive -PSProvider FileSystem

Fails on second item with same error

Failed Solution 6: No? Ok, I'll add a foreach to force each item to resolve

$TestDrives = @(
    [PSCustomObject]@{ Name='foreachpipe-test-control'; Root='C:\Windows\Temp' },
    [PSCustomObject]@{ Name='foreachpipe-test-interp-brace'; Root="${ENV:SystemRoot}\Temp" }
) 
$TestDrives | ForEach-Object { $_ } | New-PSDrive -PSProvider FileSystem 

Fails on second item with same error

Failed Solution 7: Still No? Fine, Select each property explicitly inside the foreach

@(
    [PSCustomObject]@{ Name='foreachselect-test-control'; Root='C:\Windows\Temp' },
    [PSCustomObject]@{ Name='foreachselect-test-interp-brace'; Root="${ENV:SystemRoot}\Temp" }
) | ForEach-Object { $_ | Select Name, Root } | New-PSDrive -PSProvider FileSystem

Fails on second item with same error

And just for good measure, let's try only static values:

@(
    [PSCustomObject]@{ Name='foreachstatic-test-control'; Root='C:\Windows\Temp' }
    [PSCustomObject]@{ Name='foreachstatic-test-control-2'; Root='C:\Windows\Temp' }
) | New-PSDrive -PSProvider FileSystem

Fails on second item with same error

Extra notes:

Working Solution: Wow, I'm speechless... let's force a foreach around everything

@(
    [PSCustomObject]@{ Name='foreach-test-control'; Root='C:\Windows\Temp' },
    [PSCustomObject]@{ Name='foreach-test-interp-brace'; Root="${ENV:SystemRoot}\Temp" }
) | ForEach-Object { $_ | New-PSDrive -PSProvider FileSystem }

And now it works, but just feels wrong, since it's already on the pipeline.

And now, the question(s): Why do I have to explicitly ForEach-Object in this case instead of being able to use the pipeline, when New-PSDrive supports ValueFromPipelineByPropertyName for both Name and Root?

Why does the first item in each array work, while all subsequent items fail?

Upvotes: 4

Views: 2366

Answers (2)

Frode F.
Frode F.

Reputation: 54881

This is a problem with the New-PSDrive cmdlet, because atm. it only supports a single object at a time. Remove-PSDrive does support array-input, which indicates that it should have been included in New-PSDrive too (like PS-cmdlets is supposed to work). To verify that it's a bug with the cmdlet, you could run the following command to see how it properly binds the first object's property's, while it cancels at the beginning of the second object.

Trace-Command -Expression { 
@(
    [PSCustomObject]@{ Name='array-test-control'; Root='C:\Windows\Temp' },
    [PSCustomObject]@{ Name='array-test-interp-brace'; Root="${ENV:SystemRoot}\Temp" }
) | New-PSDrive -PSProvider FileSystem
} -Name ParameterBinding -PSHost

Pieces of output:

#First object
 BIND PIPELINE object to parameters: [New-PSDrive]
DEBUG: ParameterBinding Information: 0 :     PIPELINE object TYPE = [System.Management.Automation.PSCustomObject]
DEBUG: ParameterBinding Information: 0 :     RESTORING pipeline parameter's original values
DEBUG: ParameterBinding Information: 0 :     Parameter [Name] PIPELINE INPUT ValueFromPipelineByPropertyName NO COERCION
DEBUG: ParameterBinding Information: 0 :     BIND arg [hasharray-test-interp-brace] to parameter [Name]
DEBUG: ParameterBinding Information: 0 :         BIND arg [hasharray-test-interp-brace] to param [Name] SUCCESSFUL
DEBUG: ParameterBinding Information: 0 :     Parameter [Root] PIPELINE INPUT ValueFromPipelineByPropertyName NO COERCION
DEBUG: ParameterBinding Information: 0 :     BIND arg [C:\Windows\Temp] to parameter [Root]
DEBUG: ParameterBinding Information: 0 :         BIND arg [C:\Windows\Temp] to param [Root] SUCCESSFUL


#Second object
DEBUG: ParameterBinding Information: 0 : BIND PIPELINE object to parameters: [New-PSDrive]
DEBUG: ParameterBinding Information: 0 :     PIPELINE object TYPE = [System.Management.Automation.PSCustomObject]
DEBUG: ParameterBinding Information: 0 :     RESTORING pipeline parameter's original values
#It now skips right to the result(error) without trying to bind the parameters.
DEBUG: ParameterBinding Information: 0 : BIND PIPELINE object to parameters: [Out-Default]

Upvotes: 2

Jason Shirk
Jason Shirk

Reputation: 8019

You are hitting a bug in the implementation of New-PSDrive.

The property setters for the Name/Root/PSDrive parameters aren't written properly to support multiple objects in the pipeline.

Upvotes: 3

Related Questions