Как вы поддерживаете параметры PowerShell -WhatIf & -Confirm в командлете, который вызывает другие командлеты?

У меня есть командлет сценария PowerShell, который поддерживает параметры -WhatIf и -Confirm.

Для этого перед выполнением изменения вызывается метод $PSCmdlet.ShouldProcess().
Это работает, как и ожидалось.

Моя проблема заключается в том, что мой командлет реализуется путем вызова других командлетов, а параметры -WhatIf или -Confirm не передаются командлетам, которые я вызываю.

Как передать значения -WhatIf и -Confirm командлетам, которые я вызываю из своего командлета?

Например, если мой командлет Stop-CompanyXyzServices и он использует Stop-Service для реализации своего действия.

Если -WhatIf передается Stop-CompanyXyzServices, я хочу, чтобы он также передавался в Stop-Service.

Это возможно?


person Dan Finucane    schedule 24.08.2011    source источник


Ответы (4)


Явная передача параметров

Вы можете передать параметры -WhatIf и -Confirm с переменными $WhatIfPreference и $ConfirmPreference. В следующем примере это достигается с помощью параметра splatting< /а>:

if($ConfirmPreference -eq 'Low') {$conf = @{Confirm = $true}}

StopService MyService -WhatIf:([bool]$WhatIfPreference.IsPresent) @conf

$WhatIfPreference.IsPresent будет True, если переключатель -WhatIf используется для содержащей функции. Использование переключателя -Confirm на содержащей функции временно устанавливает $ConfirmPreference на low.

Неявная передача параметров

Поскольку переменные -Confirm и -WhatIf временно автоматически устанавливают переменные $ConfirmPreference и $WhatIfPreference, нужно ли вообще их передавать?

Рассмотрим пример:

function ShouldTestCallee {
    [cmdletBinding(SupportsShouldProcess=$true,ConfirmImpact='Medium')] 
    param($test)

    $PSCmdlet.ShouldProcess($env:COMPUTERNAME,"Confirm?")
}


function ShouldTestCaller {
    [cmdletBinding(SupportsShouldProcess=$true)]
    param($test)

    ShouldTestCallee
}

$ConfirmPreference = 'High'
ShouldTestCaller
ShouldTestCaller -Confirm

ShouldTestCaller приводит к True из ShouldProcess()

ShouldTestCaller -Confirm приводит к запросу подтверждения, хотя я не передал переключатель.

Изменить

Ответ @manojlds заставил меня понять, что мое решение всегда устанавливало $ConfirmPreference на «Низкий» или «Высокий». Я обновил свой код, чтобы установить переключатель -Confirm только в том случае, если предпочтительным параметром подтверждения является «Низкий».

person Rynant    schedule 24.08.2011
comment
+1 за $WhatIfPreference.IsPresent . Не совсем уверен, что это можно использовать в решении, которое хочет ОП. - person manojlds; 25.08.2011
comment
Использовал это, чтобы получить полное решение. Смотрите мой ответ. Не знал раньше о $WhatIfPreference, приятно узнать. - person manojlds; 25.08.2011
comment
@Dan сказал, что у него уже есть скрипт, поддерживающий параметры -WhatIf и -Confirm. Я не думал, что мне нужно писать и другой код. - person Rynant; 25.08.2011
comment
-Confirm и -WhatIf будут унаследованы вызываемыми вами командами. - person JasonMArcher; 01.09.2011
comment
Хороший ответ, но эти правки сделали вывод запутанным и трудным для понимания. Итак, как правильно: $ConfirmPreference.IsPresent или полагаться на сквозной механизм переменных предпочтений? - person alecov; 10.08.2013
comment
@Alek, лично я предпочитаю сквозной механизм (неявная передача -WhatIf или -Confirm) функциям и командлетам. Вам нужно знать, поддерживаются ли эти переключатели каждым из них, и/или обернуть вызов к ним с помощью $PSCmdlet.ShouldProcess(). - person Charlie Joynt; 17.01.2017

После некоторого поиска в Google я нашел хорошее решение для передачи общих параметров вместе с вызываемыми командами. Вы можете использовать оператор @splatting для передачи всех параметров, которые были переданы вашей команде. Например, если

Start-Service -Name ServiceAbc @PSBoundParameters

находится в теле вашего скрипта, powershell передаст все параметры, которые были переданы вашему скрипту, в команду Start-Service. Единственная проблема заключается в том, что если ваш сценарий содержит, скажем, параметр -Name, он также будет передан, и PowerShell будет жаловаться, что вы дважды включили параметр -Name. Я написал следующую функцию, чтобы скопировать все общие параметры в новый словарь, а затем добавил это.

function Select-BoundCommonParameters
{
    [CmdletBinding()]
    param(
        [Parameter(Mandatory=$true)]
        $BoundParameters
    )
    begin
    {
        $boundCommonParameters = New-Object -TypeName 'System.Collections.Generic.Dictionary[string, [Object]]'
    }
    process
    {
        $BoundParameters.GetEnumerator() |
            Where-Object { $_.Key -match 'Debug|ErrorAction|ErrorVariable|WarningAction|WarningVariable|Verbose' } |
            ForEach-Object { $boundCommonParameters.Add($_.Key, $_.Value) }

        $boundCommonParameters
    }
}

Конечным результатом является то, что вы передаете такие параметры, как -Verbose, вместе с командами, вызываемыми в вашем сценарии, и они учитывают намерение вызывающего абонента.

person Dan Finucane    schedule 03.11.2011
comment
У меня были некоторые проблемы с этим, когда я вставил его в какой-то существующий код - PSBoundParameters на самом деле имеет тип System.Management.Automation.PSBoundParametersDictionary. Я работал над этим, создавая массив несовпадающих ключей в массиве, а затем вызывая $BoundParameters.Remove(), чтобы удалить некомандные параметры, а затем возвращая измененные параметры PSBoundParameters в блоке end{}. - person Stephen Connolly; 03.10.2014

Вот полное решение, основанное на ответах @Rynant и @Shay Levy:

function Stop-CompanyXyzServices
{
    [CmdletBinding(SupportsShouldProcess=$true,ConfirmImpact='Medium')]

    Param(
        [Parameter(
            Position=0,
            ValueFromPipeline=$true,
            ValueFromPipelineByPropertyName=$true
        )]      
        [string]$Name
    )

    process
    {
        if($PSCmdlet.ShouldProcess($env:COMPUTERNAME,"Stop XYZ services '$Name'")){  
            ActualCmdletProcess
        }
        if([bool]$WhatIfPreference.IsPresent){
            ActualCmdletProcess
        }
    }
}

function ActualCmdletProcess{
# add here the actual logic of your cmdlet, and any call to other cmdlets
Stop-Service $name -WhatIf:([bool]$WhatIfPreference.IsPresent) -Confirm:("Low","Medium" -contains $ConfirmPreference)
}

Мы также должны проверить, передается ли -WhatIf отдельно, чтобы можно было передать whatif отдельным командлетам. ActualCmdletProcess в основном представляет собой рефакторинг, чтобы вы не вызывали один и тот же набор команд снова только для WhatIf. Надеюсь, это поможет кому-то.

person manojlds    schedule 24.08.2011
comment
Я не думаю, что вам следует проверять "Low","Medium" -contains $ConfirmPreference. Установка -Confirm:$true устанавливает $ConfirmPreference на «низкий», но ваш код установит $ConfirmPreference на «Низкий», если он «Средний». Но мне интересно, нужно ли вообще передавать -Confirm и -WhatIF; см. мою правку. - person Rynant; 25.08.2011
comment
@Rynant - они должны быть такими, чтобы это выполнялось для каждого отдельного командлета И для пользовательского командлета, который мы пишем. Так что будет несколько подтверждений. - person manojlds; 25.08.2011
comment
Вы пробовали мой пример? Функция ShouldTestCallee подтверждается в зависимости от того, используется ли -Confirm для ShouldTestCaller, даже если я не передаю параметр подтверждения. - person Rynant; 25.08.2011

Обновлено в соответствии с комментарием @manojlds

Приведите $WhatIf и $Confirm к Boolean и передайте значения базовому командлету:

function Stop-CompanyXyzServices
{
    [CmdletBinding(SupportsShouldProcess=$true,ConfirmImpact='High')]

    Param(
        [Parameter(
            Position=0,
            ValueFromPipeline=$true,
            ValueFromPipelineByPropertyName=$true
        )]      
        [string]$Name
    )


    process
    {
        if($PSCmdlet.ShouldProcess($env:COMPUTERNAME,"Stop service '$Name'"))
        {                   
            Stop-Service $name -WhatIf:([bool]$WhatIf) -Confirm:([bool]$confirm)
        }                       
    }
}
person Shay Levy    schedule 25.08.2011
comment
Вы уверены, что это работает? В командлете я так не думаю. И даже вывод whatif отображается только для пользовательского командлета, который OP уже получает из ShouldProcess - person manojlds; 25.08.2011
comment
Извините, не работает. Это также было моим первоначальным решением, поэтому я надеялся, что так оно и будет. Две проблемы: $WhatIf не устанавливается, даже когда вы выполняете -WhatIf. Однако предложение @ Rynant о $WhatIfPreference.IsPresent действительно работает. Кроме того, поскольку вы делаете это в рамках проверки if ShouldProcess, если она попадет внутрь, -WhatIf все равно не будет установлено. - person manojlds; 25.08.2011
comment
Не уверен, почему это не работает для вас. Если я вызываю функцию без WhatIf или подтверждения, я получаю подтверждение, я нажимаю ввод, и служба останавливается. Если я вызываю функцию с WhatIf, я получаю текст whatif. Если я вызываю его с подтверждением, я получаю подтверждение, нажимаю ввод, служба останавливается. - person Shay Levy; 25.08.2011
comment
Эй, это работает ТАК, но OP запрашивает подтверждение и что, если для отдельных командлетов. Смотрите мой ответ, если он делает то, что я имею в виду, и то, что, как я думаю, хочет ОП. В основном, когда я даю что, он должен выдавать что если из нашего командлета, а затем что если из каждого используемого командлета. - person manojlds; 25.08.2011