Heute gab es mal wieder eine interessante Frage im TechNet-Forum. Das wollte ich mal näher untersuchen. Es ging darum, dass beim Pipen eines Objektes nach New-Item der Inhalt des erstellten Items das gesamte Objekt, aufgedröselt als Hashtable, enthielt. Im Thread war es eine Datei, aber mit dem Registry-Provider funktionierte es genau so. Das interessante am Value-Parameter ist, dass er Argumente nicht nur nach Namen, sondern auch nach dem Wert bindet:
Doch ist es nur eine Eigenart von New-Item oder ist das Verhalten generell so? Schreiben wir mal eine kleine Funktion, die ihre Argumente, falls sie aus der Pipeline angeflogen kommen, ganz regulär nach Namen bindet:
function Get-MyArgs { [CmdletBinding()] Param( [Parameter(ValueFromPipelineByPropertyName=$true)][string]$MyInput, [Parameter(ValueFromPipelineByPropertyName=$true)][string]$MyOtherParm ) Write-Host "Value of MyOtherParm:" $MyOtherParm Write-Host "Value of MyInput:" $MyInput }
Wenn wir der Funktion jetzt ein Objekt verfüttern, das die gewünschten Properties enthält, werden sie auch ordnungsgemäß gebunden:
$x = New-Object PSCustomObject -Property @{ MyInput="MyInputValue"; MyOtherParm="MyOtherParmValue"; ForeignParm="ShouldNotSeeMe" } $x | Get-MyArgs
liefert
Value of MyOtherParm: MyOtherParmValue Value of MyInput: MyInputValue
Akzeptieren wir nur die gleichen Parameter, aber nach dem Wert, wartet eine kleine Überraschung auf uns:
function Get-MyArgs { [CmdletBinding()] Param( [Parameter(ValueFromPipeline=$true)][string]$MyInput, [Parameter(ValueFromPipeline=$true)][string]$MyOtherParm ) Write-Host "Value of MyOtherParm:" $MyOtherParm Write-Host "Value of MyInput:" $MyInput } $x = New-Object PSCustomObject -Property @{ MyInput="MyInputValue"; MyOtherParm="MyOtherParmValue"; ForeignParm="ShouldNotSeeMe" } $x | Get-MyArgs
liefert uns
Value of MyOtherParm: @{MyInput=MyInputValue; MyOtherParm=MyOtherParmValue; ForeignParm=ShouldNotSeeMe} Value of MyInput: @{MyInput=MyInputValue; MyOtherParm=MyOtherParmValue; ForeignParm=ShouldNotSeeMe}
Und jetzt kommt der Quirk:
Was passiert aber, wenn wir, wie bei Value in New-Item, beide Bindungen zulassen? Also
function Get-MyArgs { [CmdletBinding()] Param( [Parameter(ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)][string]$MyInput, [Parameter(ValueFromPipelineByPropertyName=$true,ValueFromPipeline=$true)][string]$MyOtherParm ) Write-Host "Value of MyOtherParm:" $MyOtherParm Write-Host "Value of MyInput:" $MyInput } $x = New-Object PSCustomObject -Property @{ MyInput="MyInputValue"; MyOtherParm="MyOtherParmValue"; ForeignParm="ShouldNotSeeMe" } $x | Get-MyArgs
Anders als bei New-Item, erhalten wir nur die String-Werte der beiden benannten Parameter!
Eine Untersuchung mit
Trace-Command -Name ParameterBinding -Expression {$x | Get-MyArgs} -PSHost
fördert die folgenden Schritte zu Tage:
BIND PIPELINE object to parameters: [Get-MyArgs] PIPELINE object TYPE = [System.Management.Automation.PSCustomObject] RESTORING pipeline parameter's original values Parameter [MyOtherParm] PIPELINE INPUT ValueFromPipeline NO COERCION BIND arg [@{MyInput=MyInputValue; MyOtherParm=MyOtherParmValue; ForeignParm=ShouldNotSeeMe}] to parameter [MyOtherParm] Executing DATA GENERATION metadata: [System.Management.Automation.ArgumentTypeConverterAttribute] result returned from DATA GENERATION: @{MyInput=MyInputValue; MyOtherParm=MyOtherParmValue; ForeignParm=ShouldNotSeeMe} BIND arg [@{MyInput=MyInputValue; MyOtherParm=MyOtherParmValue; ForeignParm=ShouldNotSeeMe}] to param [MyOtherParm] SKIPPED Parameter [MyInput] PIPELINE INPUT ValueFromPipeline NO COERCION BIND arg [@{MyInput=MyInputValue; MyOtherParm=MyOtherParmValue; ForeignParm=ShouldNotSeeMe}] to parameter [MyInput] Executing DATA GENERATION metadata: [System.Management.Automation.ArgumentTypeConverterAttribute] result returned from DATA GENERATION: @{MyInput=MyInputValue; MyOtherParm=MyOtherParmValue; ForeignParm=ShouldNotSeeMe} BIND arg [@{MyInput=MyInputValue; MyOtherParm=MyOtherParmValue; ForeignParm=ShouldNotSeeMe}] to param [MyInput] SKIPPED Parameter [MyOtherParm] PIPELINE INPUT ValueFromPipelineByPropertyName NO COERCION BIND arg [MyOtherParmValue] to parameter [MyOtherParm] Executing DATA GENERATION metadata: [System.Management.Automation.ArgumentTypeConverterAttribute] result returned from DATA GENERATION: MyOtherParmValue BIND arg [MyOtherParmValue] to param [MyOtherParm] SUCCESSFUL Parameter [MyInput] PIPELINE INPUT ValueFromPipelineByPropertyName NO COERCION BIND arg [MyInputValue] to parameter [MyInput] Executing DATA GENERATION metadata: [System.Management.Automation.ArgumentTypeConverterAttribute] result returned from DATA GENERATION: MyInputValue BIND arg [MyInputValue] to param [MyInput] SUCCESSFUL MANDATORY PARAMETER CHECK on cmdlet [Get-MyArgs]
Bei New-Item ist das Value-Argument allerdings kein [string], sondern ein [object[]] (danke an Martin Binder für den Hinweis, dass dieser Test noch aussteht!). Ändert man das im obigen Beispiel, dann ist das Verhalten so wie bei New-Item auch, und zwar unabhängig davon, ob ein einzelnes [object] oder ein Array davon erwartet wird – es wird in beiden Fällen das ganze Objekt gebunden, sobald ValueFromPipeline=$true auftaucht.
Happy argument-passing!
Antworten