Обнаружение ParameterSetName в функциях PowerShell, соответствующих типу входного объекта ValueFromPipeline?

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

Во-первых, я сделал простой тип, который служит только для отличия от строки.

Add-Type @"
public class TestType {
   public string Prop1;
}
"@

Затем я создал тестовую функцию и запустил ее с вводом строки и TestType.

function Test-ParameterSets1
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true, ValueFromPipeline=$true, ParameterSetName="Str")] [string] $StringInput,
        [Parameter(Mandatory=$true, ValueFromPipeline=$true, ParameterSetName="Test")] [TestType] $TestInput
    )
    begin {
        $result = New-Object Object | Select-Object –Property @{n='FunctionName';e={$PSCmdlet.MyInvocation.InvocationName}},@{n='ParameterSetName';e={$PSCmdlet.ParameterSetName}}
    }
    process {
        $result | Add-Member -MemberType NoteProperty -Name StringInput -Value $StringInput -PassThru | Add-Member -MemberType NoteProperty -Name TestInput -Value $TestInput
    }
    end {
        $result
    }
}
'string' | Test-ParameterSets1
New-Object TestType | Test-ParameterSets1


FunctionName        ParameterSetName   StringInput TestInput
------------        ----------------   ----------- ---------
Test-ParameterSets1 __AllParameterSets string               
Test-ParameterSets1 __AllParameterSets             TestType 

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

Затем я попытался добавить параметр, уникальный для одного набора параметров, и, как и ожидалось, ParameterSetName был правильным для вызовов, в которых он был указан только.

function Test-ParameterSets2
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true, ValueFromPipeline=$true, ParameterSetName="Str")] [string] $StringInput,
        [Parameter(Mandatory=$true, ValueFromPipeline=$true, ParameterSetName="Test")] [TestType] $TestInput,
        [Parameter(ParameterSetName="Test")] [string] $TestName
    )
    begin {
        $result = New-Object Object | Select-Object –Property @{n='FunctionName';e={$PSCmdlet.MyInvocation.InvocationName}},@{n='ParameterSetName';e={$PSCmdlet.ParameterSetName}}
    }
    process {
        $result | Add-Member -MemberType NoteProperty -Name StringInput -Value $StringInput -PassThru | Add-Member -MemberType NoteProperty -Name TestInput -Value $TestInput
    }
    end {
        $result
    }
}
'string' | Test-ParameterSets2
New-Object TestType | Test-ParameterSets2 -TestName MyName
New-Object TestType | Test-ParameterSets2


FunctionName        ParameterSetName   StringInput TestInput
------------        ----------------   ----------- ---------
Test-ParameterSets2 __AllParameterSets string               
Test-ParameterSets2 Test                           TestType 
Test-ParameterSets2 __AllParameterSets             TestType 

Затем я попытался добавить параметр, который является обязательным для обоих наборов параметров, и на этот раз ParameterSetName вычислился в пустую строку, что особенно запутало.

function Test-ParameterSets5
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true, ValueFromPipeline=$true, ParameterSetName="Str")] [string] $StringInput,
        [Parameter(Mandatory=$true, ValueFromPipeline=$true, ParameterSetName="Test")] [TestType] $TestInput,
        [Parameter(Mandatory=$true, ParameterSetName="Str")] [Parameter(Mandatory=$true, ParameterSetName="Test")] [string] $Mandatory,
        [Parameter(ParameterSetName="Test")] [string] $TestName
    )
    begin {
        $result = New-Object Object | Select-Object –Property @{n='FunctionName';e={$PSCmdlet.MyInvocation.InvocationName}},@{n='ParameterSetName';e={$PSCmdlet.ParameterSetName}}
    }
    process {
        $result | Add-Member -MemberType NoteProperty -Name StringInput -Value $StringInput -PassThru | Add-Member -MemberType NoteProperty -Name TestInput -Value $TestInput
    }
    end {
        $result
    }
}
'string' | Test-ParameterSets5 -Mandatory mandatoryParam
New-Object TestType | Test-ParameterSets5 -Mandatory mandatoryParam -TestName MyName
New-Object TestType | Test-ParameterSets5 -Mandatory mandatoryParam 


FunctionName        ParameterSetName StringInput TestInput
------------        ---------------- ----------- ---------
Test-ParameterSets5                  string               
Test-ParameterSets5 Test                         TestType 
Test-ParameterSets5                              TestType 

Кажется, что PowerShell на самом деле знает, как правильно установить эти параметры, и все же ParameterSetName оценивается неправильно. Есть ли способ заставить это работать? Я бы хотел избежать ненужных переключателей, таких как -String и -TestType, которые являются уникальными для их собственных наборов параметров, чтобы PowerShell мог выполнять свою работу. Спасибо!

2 ответа

Решение

Проблема с вашим кодом в том, что вы читаете ParameterSetName недвижимость в begin блок. Когда команда принимает входные данные конвейера, объект ввода может влиять на выбранный ParameterSetName, И если ваша команда имеет несколько входных объектов, то каждый из них может привести к тому, что будет выбран другой набор параметров:

class a { }
class b { }
class c { }
function f {
    param(
        [Parameter(ParameterSetName='a', ValueFromPipeline)][a]$a,
        [Parameter(ParameterSetName='b', ValueFromPipeline)][b]$b,
        [Parameter(ParameterSetName='c', ValueFromPipeline)][c]$c
    )
    begin {
        "ParameterSetName in begin block: $($PSCmdlet.ParameterSetName)"
    }
    process {
        "ParameterSetName in process block: $($PSCmdlet.ParameterSetName)"
    }
}
[a]::new(), [b]::new(), [c]::new() | f

# Result:
# ParameterSetName in begin block: __AllParameterSets
# ParameterSetName in process block: a
# ParameterSetName in process block: b
# ParameterSetName in process block: c

Таким образом, если вы хотите знать, какой набор параметров был выбран после того, как входной объект был привязан к вашей команде, то вам следует прочитать ParameterSetName в process блок.

Чтобы завершить пример, основанный на совете @PerSetAI, вот та же самая примерная функция, проверяющая набор параметров в блоке процесса.

function Test-ParameterSets1
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true, ValueFromPipeline=$true, ParameterSetName="Str")] [string] $StringInput,
        [Parameter(Mandatory=$true, ValueFromPipeline=$true, ParameterSetName="Test")] [TestType] $TestInput
    )
    begin {
        $result = New-Object Object | Select-Object –Property @{n='FunctionName';e={$PSCmdlet.MyInvocation.InvocationName}},@{n='BeginParameterSetName';e={$PSCmdlet.ParameterSetName}}
    }
    process {
        $result | Add-Member -MemberType NoteProperty -Name ProcessParameterSetName -Value $PSCmdlet.ParameterSetName -PassThru `
        | Add-Member -MemberType NoteProperty -Name StringInput -Value $StringInput -PassThru `
        | Add-Member -MemberType NoteProperty -Name TestInput -Value $TestInput
    }
    end {
        $result
    }
}
'string' | Test-ParameterSets1
New-Object TestType | Test-ParameterSets1


FunctionName            : Test-ParameterSets1
BeginParameterSetName   : __AllParameterSets
ProcessParameterSetName : Str
StringInput             : string
TestInput               : 

FunctionName            : Test-ParameterSets1
BeginParameterSetName   : __AllParameterSets
ProcessParameterSetName : Test
StringInput             : 
TestInput               : TestType
Другие вопросы по тегам