Сценарии Powershell: рекомендуемый способ реализации ShouldProcess при вложенных вызовах функций? - PullRequest
12 голосов
/ 17 февраля 2010

Тестовый скрипт:

function outer
{
    [cmdletbinding(supportsshouldprocess=$true)]
    param($s)

    process
    {        
        $pscmdlet.shouldprocess("outer $s", "ShouldProcess") | out-null
        "" | out-file "outer $s"

        inner ImplicitPassthru
        inner VerbosePassthru -Verbose:$Verbose 
        inner WhatifPassthru -WhatIf:$WhatIf
    }
}

function inner
{
    [cmdletbinding(supportsshouldprocess=$true)]
    param($s)

    process
    {   
        $pscmdlet.shouldprocess("inner $s", "ShouldProcess") | out-null
        "" | out-file "inner $s"
    }
}

"`n** NORMAL **"
outer normal
"`n** VERBOSE **"
outer verbose -Verbose
"`n** WHATIF **"
outer whatif -WhatIf

Выход:

** NORMAL **
VERBOSE: Performing operation "ShouldProcess" on Target "inner VerbosePassthru".
What if: Performing operation "ShouldProcess" on Target "inner WhatifPassthru".
What if: Performing operation "Output to File" on Target "inner WhatifPassthru".

** VERBOSE **
VERBOSE: Performing operation "ShouldProcess" on Target "outer verbose".
VERBOSE: Performing operation "ShouldProcess" on Target "inner VerbosePassthru".
What if: Performing operation "ShouldProcess" on Target "inner WhatifPassthru".
What if: Performing operation "Output to File" on Target "inner WhatifPassthru".

** WHATIF **
What if: Performing operation "ShouldProcess" on Target "outer whatif".
What if: Performing operation "Output to File" on Target "outer whatif".
What if: Performing operation "ShouldProcess" on Target "inner ImplicitPassthru".
What if: Performing operation "Output to File" on Target "inner ImplicitPassthru".
What if: Performing operation "ShouldProcess" on Target "inner VerbosePassthru".
What if: Performing operation "Output to File" on Target "inner VerbosePassthru".
What if: Performing operation "ShouldProcess" on Target "inner WhatifPassthru".
What if: Performing operation "Output to File" on Target "inner WhatifPassthru".

На мой взгляд, здесь есть несколько странностей:

  • Указание -WhatIf: $ foo всегда включает $ WhatIf в вызываемом объекте (и его вызываемых), независимо от того, что такое $ foo.
  • Когда вы указываете -WhatIf «для реального» (не ограничивая его существующей переменной), он неявно распространяется на вызываемых абонентов. Нет необходимости в passthru или splatting.
  • В отличие от -WhatIf, явное -Verbose не касается неявно вызываемых абонентов.
  • Когда вы пытаетесь вручную вставить -Verbose: $ foo, вы видите, что поведение похоже на -WhatIf: $ foo. Но это влияет только на сценарии, которые вручную тестируют $ psCmdlet.ShouldProcess () - встроенные командлеты не затрагиваются.

N.B. : Подтверждение ведет себя идентично WhatIf. Я для краткости опустил.

В поисках в Интернете и в Connect я почти не вижу подробного обсуждения поведения ShouldProcess (pro или con), касающегося расширенных функций. Наиболее близким является сообщение от Джеймса О'Нила , в котором рекомендуется передавать один экземпляр $ psCmdlet по всему стеку вызовов. Тем не менее, он делает это, чтобы обойти совершенно другую проблему (избегая многократных запросов подтверждения). Между тем, когда вы придерживаетесь стандартного $ psCmdlet, предоставляемого для каждой функции, я не вижу документов о том, чего ожидать ... намного меньше шаблонов проектирования, лучших практик и т. Д. ...

Ответы [ 2 ]

11 голосов
/ 23 февраля 2010

Вы не можете действительно ссылаться на $ WhatIf или $ Verbose, поскольку они синтезированы для вас, то есть эти переменные не существуют в вашей функции. Если пользователь указывает их, вы можете получить их через $ PSBoundParameters, но если пользователь не указал, то, очевидно, они не будут в этой хеш-таблице.

Когда вы передаете значение в коммутатор, PowerShell выполнит типичный принудительный процесс, чтобы попытаться преобразовать указанное значение в логическое значение. Так как $ whatif не определено, это приводит к значению $ null, в результате чего значение переключателя устанавливается равным $ true. Предположительно это происходит потому, что он видит, что переключатель явно указан без значения, что эквивалентно указанию -Whatif без значения. Вы можете увидеть это, когда проследите привязку параметра:

function Foo
{
    [CmdletBinding(SupportsShouldProcess=1)]
    param()

    Process
    {
        $PSBoundParameters
    }
}

Trace-Command -name ParameterBinding -expr {Foo -whatif:$xyzzy} -PSHost
DEBUG: BIND NAMED cmd line args [Foo]
DEBUG:   BIND arg [] to parameter [WhatIf]
DEBUG:     COERCE arg to [System.Management.Automation.SwitchParameter]
DEBUG:       Arg is null or not present, type is SWITCHPARAMTER, value is true.
DEBUG:         BIND arg [True] to param [WhatIf] SUCCESSFUL
DEBUG: BIND POSITIONAL cmd line args [Foo]
DEBUG: MANDATORY PARAMETER CHECK on cmdlet [Foo]
DEBUG: CALLING BeginProcessing
DEBUG: CALLING EndProcessing

$ WhatIfPreference и $ VerbosePreference устанавливаются соответствующим образом в external в зависимости от того, был ли external вызван с -verbose или -whatif. Я вижу, что эти значения распространяются на внутреннюю сторону просто отлично. Казалось бы, есть ошибка PowerShell с $ pscmdlet.ShouldProcess. Похоже, что в этом случае он не учитывает значение $ VerbosePreference. Вы можете попробовать пройти через -Verbose к внутреннему так:

inner VerbosePassthru -Verbose:($VerbosePreference -eq 'Continue')

Другой вариант - использовать Get-Variable -Scope примерно так:

function Outer
{
    [CmdletBinding(SupportsShouldProcess=1)]
    param()

    Process
    {
        $pscmdlet.ShouldProcess("Outer process", '') > $null
        inner
        #inner -Verbose:($VerbosePreference -eq 'Continue')
    }
}

function Inner
{
    [CmdletBinding(SupportsShouldProcess=1)]
    param()

    Process
    {
        $pscmdlet = (Get-Variable -Scope 1 -Name PSCmdlet).Value
        $pscmdlet.ShouldProcess("Inner process", '') > $null
        "Inner $VerbosePreference"
    }
}

Outer -Verbose

Я не уверен, что мне это нравится, потому что это означает, что вы знаете, что внешний на 1 уровень выше внутреннего. Вы могли бы «пройтись» по стеку в поисках следующей переменной PSCmdlet вверх по стеку. Это эффективно избавляет от необходимости передавать в PSCmdlet (что является грубым), но это все еще хак. Вы должны рассмотреть вопрос об ошибке в MS Connect по этому поводу.

0 голосов
/ 21 января 2017

Я хотел написать точно такой же вопрос, и пишу это почти 7 лет спустя. Я удивлен, что команда Microsoft PowerShell еще не исправила это. Я воспроизвел проблему с помощью PowerShell Version 6 Preview (последняя версия).

Я предложил простой обходной путь: внутри функции Inner мы создаем и запускаем scriptblock, устанавливая флаг -Verbose, проверяя $VerbosePreference, который правильно установлен на Continue даже если оно не соблюдается ShouldProcess:


Function Outer {
    [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact="Medium")]
    param([string]$Name)

    Process {
        Write-Host "Outer called";
        Inner $Name
    }
}

Function Inner {
    [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact="Medium")]
    param([string]$Name)

    Process {
        if (-not ($PSBoundParameters.ContainsKey('Verbose'))) {
            $PSBoundParameters.Add('Verbose', [bool]$VerbosePreference -eq 'Continue');
        }

        & {
            [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact="Medium")]

            param([string]$Name)

            if ($PSCmdlet.ShouldProcess($Name, "Inner")) {
                Write-Host "Inner called";
            }
        } @PSBoundParameters;
    }
}

Export-ModuleMember *
...