Скрипты Powershell:рекомендуемый способ реализации MustProcess при вложенных вызовах функций?
-
21-09-2019 - |
Вопрос
Тестовый скрипт:
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 «по-настоящему» (не ограничивая его существующей переменной), он неявно распространяется на вызываемые объекты.Нет необходимости в проходе или разбрызгивании.
- В отличие от -WhatIf, явный -Verbose не передается вызываемым объектам неявно.
- Когда вы попытаетесь вручную передать -Verbose:$foo, вы увидите, что поведение похоже на -WhatIf:$foo.Но это влияет только на сценарии, которые вручную проверяют $psCmdlet.ShouldProcess() — встроенные командлеты не затрагиваются.
Н.Б.:Confirm ведет себя идентично WhatIf.Я опустил это для краткости.
Просматривая Интернет и Connect, я почти не вижу подробного обсуждения поведения MustProcess (за или против) применительно к расширенным функциям.Самое близкое, что есть сообщение от Джеймса О'Нила рекомендуется передавать один экземпляр $psCmdlet по всему стеку вызовов.Однако он делает это, чтобы обойти совершенно другую проблему (избегая нескольких запросов -Confirm).Между тем, если вы придерживаетесь стандартного $psCmdlet, предоставляемого каждой функции, я не вижу документации о том, чего ожидать... гораздо меньше шаблонов проектирования, лучших практик и т. д.
Решение
Вы не можете ссылаться на $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 устанавливаются соответствующим образом во внешнем объекте в зависимости от того, был ли внешний вызов вызван с -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.
Другие советы
Я хотел написать точно такой же вопрос, и пишу это почти 7 лет спустя.Я удивлен, что команда Microsoft PowerShell до сих пор не исправила эту проблему.Я воспроизвел проблему с предварительной версией PowerShell версии 6 (последняя версия).
Я придумал простой обходной путь, то есть внутри 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 *