Связывание нескольких параметров конвейера PS

Я пытаюсь передать два параметра в функцию через конвейер, но он не работает должным образом, и я пытаюсь понять, почему.

MWE

function Test-Pipeline {
  [CmdletBinding ()]
  Param(
    [Parameter(ValueFromPipeline=$true)][String]$Name,
    [Parameter(ValueFromPipeline=$true)][String]$Value
  )
  Write-Host "Name: $Name"
  Write-Host "Value: $Value"
}

"Name", "Value" | Test-Pipeline

Выход

Имя: Значение

Значение: Значение

Я пытался запустить Trace-Command Команда, чтобы увидеть, что происходит. В строке 35 мы видим, что Value связан с $Parameter,

Почему PowerShell связывает второй вход с обоими параметрами? Если это ожидается, почему это происходит только для второго параметра, а не для первого?

след

DEBUG: ParameterBinding Information: 0 : BIND NAMED cmd line args [Test-Pipeline]
DEBUG: ParameterBinding Information: 0 : BIND POSITIONAL cmd line args [Test-Pipeline]
DEBUG: ParameterBinding Information: 0 : MANDATORY PARAMETER CHECK on cmdlet [Test-Pipeline]
DEBUG: ParameterBinding Information: 0 :     BIND arg [] to parameter [Name]
DEBUG: ParameterBinding Information: 0 :         Executing DATA GENERATION metadata: [System.Management.Automation.ArgumentTypeConverterAttribute]
DEBUG: ParameterBinding Information: 0 :             result returned from DATA GENERATION:
DEBUG: ParameterBinding Information: 0 :         COERCE arg to [System.String]
DEBUG: ParameterBinding Information: 0 :             Parameter and arg types the same, no coercion is needed.
DEBUG: ParameterBinding Information: 0 :         BIND arg [] to param [Name] SUCCESSFUL
DEBUG: ParameterBinding Information: 0 :     BIND arg [] to parameter [Value]
DEBUG: ParameterBinding Information: 0 :         Executing DATA GENERATION metadata: [System.Management.Automation.ArgumentTypeConverterAttribute]
DEBUG: ParameterBinding Information: 0 :             result returned from DATA GENERATION:
DEBUG: ParameterBinding Information: 0 :         COERCE arg to [System.String]
DEBUG: ParameterBinding Information: 0 :             Parameter and arg types the same, no coercion is needed.
DEBUG: ParameterBinding Information: 0 :         BIND arg [] to param [Value] SUCCESSFUL
DEBUG: ParameterBinding Information: 0 : CALLING BeginProcessing
DEBUG: ParameterBinding Information: 0 : BIND PIPELINE object to parameters: [Test-Pipeline]
DEBUG: ParameterBinding Information: 0 :     PIPELINE object TYPE = [System.String]
DEBUG: ParameterBinding Information: 0 :     RESTORING pipeline parameter's original values
DEBUG: ParameterBinding Information: 0 :     Parameter [Value] PIPELINE INPUT ValueFromPipeline NO COERCION
DEBUG: ParameterBinding Information: 0 :     BIND arg [Name] to parameter [Value]
DEBUG: ParameterBinding Information: 0 :         Executing DATA GENERATION metadata: [System.Management.Automation.ArgumentTypeConverterAttribute]
DEBUG: ParameterBinding Information: 0 :             result returned from DATA GENERATION: Name
DEBUG: ParameterBinding Information: 0 :         BIND arg [Name] to param [Value] SUCCESSFUL
DEBUG: ParameterBinding Information: 0 :     Parameter [Name] PIPELINE INPUT ValueFromPipeline NO COERCION
DEBUG: ParameterBinding Information: 0 :     BIND arg [Name] to parameter [Name]
DEBUG: ParameterBinding Information: 0 :         Executing DATA GENERATION metadata: [System.Management.Automation.ArgumentTypeConverterAttribute]
DEBUG: ParameterBinding Information: 0 :             result returned from DATA GENERATION: Name
DEBUG: ParameterBinding Information: 0 :         BIND arg [Name] to param [Name] SUCCESSFUL
DEBUG: ParameterBinding Information: 0 : MANDATORY PARAMETER CHECK on cmdlet [Test-Pipeline]
DEBUG: ParameterBinding Information: 0 : BIND PIPELINE object to parameters: [Test-Pipeline]
DEBUG: ParameterBinding Information: 0 :     PIPELINE object TYPE = [System.String]
DEBUG: ParameterBinding Information: 0 :     RESTORING pipeline parameter's original values
DEBUG: ParameterBinding Information: 0 :     Parameter [Name] PIPELINE INPUT ValueFromPipeline NO COERCION
DEBUG: ParameterBinding Information: 0 :     BIND arg [Value] to parameter [Name]
DEBUG: ParameterBinding Information: 0 :         Executing DATA GENERATION metadata: [System.Management.Automation.ArgumentTypeConverterAttribute]
DEBUG: ParameterBinding Information: 0 :             result returned from DATA GENERATION: Value
DEBUG: ParameterBinding Information: 0 :         BIND arg [Value] to param [Name] SUCCESSFUL
DEBUG: ParameterBinding Information: 0 :     Parameter [Value] PIPELINE INPUT ValueFromPipeline NO COERCION
DEBUG: ParameterBinding Information: 0 :     BIND arg [Value] to parameter [Value]
DEBUG: ParameterBinding Information: 0 :         Executing DATA GENERATION metadata: [System.Management.Automation.ArgumentTypeConverterAttribute]
DEBUG: ParameterBinding Information: 0 :             result returned from DATA GENERATION: Value
DEBUG: ParameterBinding Information: 0 :         BIND arg [Value] to param [Value] SUCCESSFUL
DEBUG: ParameterBinding Information: 0 : MANDATORY PARAMETER CHECK on cmdlet [Test-Pipeline]
DEBUG: ParameterBinding Information: 0 : CALLING EndProcessing
DEBUG: ParameterBinding Information: 0 :     BIND NAMED cmd line args [Write-Host]
DEBUG: ParameterBinding Information: 0 :     BIND POSITIONAL cmd line args [Write-Host]
DEBUG: ParameterBinding Information: 0 :     BIND REMAININGARGUMENTS cmd line args to param: [Object]
DEBUG: ParameterBinding Information: 0 :         BIND arg [System.Collections.Generic.List`1[System.Object]] to parameter [Object]
DEBUG: ParameterBinding Information: 0 :             COERCE arg to [System.Object]
DEBUG: ParameterBinding Information: 0 :                 Parameter and arg types the same, no coercion is needed.
DEBUG: ParameterBinding Information: 0 :             BIND arg [System.Collections.Generic.List`1[System.Object]] to param [Object] SUCCESSFUL
DEBUG: ParameterBinding Information: 0 :     MANDATORY PARAMETER CHECK on cmdlet [Write-Host]
DEBUG: ParameterBinding Information: 0 :     CALLING BeginProcessing
Name: Value
DEBUG: ParameterBinding Information: 0 :     CALLING EndProcessing
DEBUG: ParameterBinding Information: 0 :     BIND NAMED cmd line args [Write-Host]
DEBUG: ParameterBinding Information: 0 :     BIND POSITIONAL cmd line args [Write-Host]
DEBUG: ParameterBinding Information: 0 :     BIND REMAININGARGUMENTS cmd line args to param: [Object]
DEBUG: ParameterBinding Information: 0 :         BIND arg [System.Collections.Generic.List`1[System.Object]] to parameter [Object]
DEBUG: ParameterBinding Information: 0 :             COERCE arg to [System.Object]
DEBUG: ParameterBinding Information: 0 :                 Parameter and arg types the same, no coercion is needed.
DEBUG: ParameterBinding Information: 0 :             BIND arg [System.Collections.Generic.List`1[System.Object]] to param [Object] SUCCESSFUL
DEBUG: ParameterBinding Information: 0 :     MANDATORY PARAMETER CHECK on cmdlet [Write-Host]
DEBUG: ParameterBinding Information: 0 :     CALLING BeginProcessing
Value: Value
DEBUG: ParameterBinding Information: 0 :     CALLING EndProcessing

2 ответа

Решение

Согласно комментарию Ли Дейли, у вас может быть только один параметр, принимающий входные данные из конвейера через "Значение" (для каждого типа значения, например, string, int и т. Д.). В настоящее время ваш код отправляет массив строковых значений, который затем обрабатывается по одному через конвейер.

Если вы хотите отправить несколько значений вместе в конвейер, вы можете сделать это, сделав эти значения свойствами пользовательского объекта, а затем вы можете принять их через конвейер, используя ValueFromPipelineByPropertyName параметры. Это работает путем сопоставления любых свойств входного объекта, имя которого совпадает с именем входных параметров:

function Test-Pipeline {
  [CmdletBinding ()]
  Param(
    [Parameter(ValueFromPipelineByPropertyName=$true)][String]$Name,
    [Parameter(ValueFromPipelineByPropertyName=$true)][String]$Value
  )
  Write-Host "Name: $Name"
  Write-Host "Value: $Value"
}

$MyObject = [pscustomobject]@{
    Name = "MyName"
    Value = "MyValue"
}

$MyObject | Test-Pipeline

Результат:

Name: MyName
Value: MyValue

Альтернативный, но похожий подход заключается в использовании ValueFromPipeline принять входной объект, а затем получить значения свойств из этого объекта:

function Test-Pipeline {
  [CmdletBinding ()]
  Param(
    [Parameter(ValueFromPipeline=$true)][Object]$InputObject
  )

  $Name = $InputObject.Name
  $Value = $InputObject.Value

  Write-Host "Name: $Name"
  Write-Host "Value: $Value"
}

$MyObject = [pscustomobject]@{
    Name = "MyName"
    Value = "MyValue"
}

$MyObject | Test-Pipeline

Некоторые командлеты будут поддерживать оба этих подхода, поскольку PowerShell сначала попытается сопоставить по типу объекта, а затем вернется к сопоставлению по имени свойства. Здесь вы найдете подробное объяснение этого, если вы хотите узнать больше: https://blogs.technet.microsoft.com/heyscriptingguy/2013/03/25/learn-about-using-powershell-value-binding-by-property-name/

Обратите внимание, что если вы собираетесь успешно работать со значениями через конвейер, вам также необходимо использовать Process { } блокировать в вашей функции, что приводит к тому, что коллекции объектов обрабатываются по одному:

function Test-Pipeline {
  [CmdletBinding ()]
  Param(
    [Parameter(ValueFromPipeline=$true)][Object]$InputObject
  )

  Process {
      $Name = $InputObject.Name
      $Value = $InputObject.Value

      Write-Host "Name: $Name"
      Write-Host "Value: $Value"
  }
}

$MyObject = @(
    [pscustomobject]@{
        Name = "MyName"
        Value = "MyValue"
    }
    [pscustomobject]@{
        Name = "MySecondName"
        Value = "MySecondValue"
    }
)

$MyObject | Test-Pipeline

Без этого будет обрабатываться только последнее значение в коллекции объектов.

В дополнение к ответу @Mark, как уже упоминалось, атрибут ValueFromPipeline может быть установлен только один раз для каждого типа параметра.
Это пример, где используется несколько ValueFromPipeline, каждый для другого типа параметра.

На итерацию назначается только один параметр. Тот, который соответствует типу входного значения.

Примечание:
$Int содержит 0когда при инициализации не используется конкретное значение.
Безопаснее проверять, какой набор параметров использовался, а не только, было ли присвоено значение.

      function testus() {
  param(
    [Parameter(ValueFromPipeline, ParameterSetName = "A")]
    [String]$Str,
    [Parameter(ValueFromPipeline, ParameterSetName = "B")]
    [Int]$Int
  )
  process {
    if ($PSCmdlet.ParameterSetName -eq 'A') {
        Write-Host ""
        Write-Host "String was used:"
        Write-Host "Str: $Str"
        Write-Host "(Int contains initialized default value '0': $Int)"
    } elseif ($PSCmdlet.ParameterSetName -eq 'B') {
        Write-Host ""
        Write-Host "Integer was used:"
        Write-Host "Int: $Int"
        Write-Host "(Str contains initialized default value '' (empty string): $($Str.gettype().FullName))"
    }
  }
}

'a', 1, 0 | testus
Другие вопросы по тегам