Обнаружение 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 мог выполнять свою работу. Спасибо!


person bwerks    schedule 06.01.2018    source источник
comment
Будет ли тот же результат, если вы добавите DefaultParameterSetName=Test или Str в круглые скобки cmdletBinding и установите для того, что вы выберете, значение Position=0? Хороший вопрос, любопытны результаты после прочтения: blogs.msdn.microsoft.com/mediaandmicrocode/2009/04/10/ и blogs.msdn.microsoft.com/powershell/2009/04/06/ и blog.simonw.se/powershell-functions-and-parameter-sets   -  person trebleCode    schedule 06.01.2018
comment
Вы получаете ParameterSetName в блоке begin до того, как функция сможет увидеть первый объект конвейера. Вместо этого вы должны сделать это в блоке process. Каждый входной объект конвейера может привести к разным ParameterSetName при вызове одной функции.   -  person user4003407    schedule 06.01.2018
comment
@PetSerAl Вы говорите, что параметры в целом должны быть установлены в блоке процесса? Любопытно понять   -  person trebleCode    schedule 06.01.2018
comment
@trebleCode Я не понимаю вашего вопроса. Все, что я говорю, это то, что ParameterSetName в process может возвращать другое значение по сравнению со значением в begin, но OP не читает ParameterSetName в блоке process.   -  person user4003407    schedule 06.01.2018
comment
@PetSerAl Это оказалось решением. Тестовые функции отлично работают при тестировании ParameterSetName в блоке процесса, что означает, что для моей фактической функции я только что переместил логику, и она работает, как и ожидалось. Если вы отправите этот совет в качестве ответа, я отмечу его. Спасибо!   -  person bwerks    schedule 07.01.2018


Ответы (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.

person user4003407    schedule 08.01.2018
comment
Это очень элегантный ответ, и он многому научил меня в отношении наборов параметров. Спасибо. - person David Söderlund; 01.04.2019

Чтобы завершить пример, основанный на совете @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
person bwerks    schedule 13.01.2018