如何正确使用自定义 cmdlet 中的 -verbose 和 -debug 参数

52

默认情况下,带有[CmdletBinding()]属性的任何命名函数都接受-debug-verbose(以及其他几个)参数,并具有预定义的$debug$verbose变量。我正在尝试找出如何将它们传递给在函数内被调用的其他cmdlet。

假设我有一个这样的cmdlet:

function DoStuff() {
   [CmdletBinding()]

   PROCESS {
      new-item Test -type Directory
   }
}

如果我的函数传入了-debug或者-verbose标志,我想将该标志传递给new-item命令。正确的模式是什么?


PowerShell已经为您做到了这一点。不过可能没有您预期的那么直接。请参见下面的答案。 - Lars Truijens
9个回答

47

$PSBoundParameters 不是你要找的。使用 [CmdletBinding()] 属性允许在脚本中使用 $PSCmdlet,并提供了一个详细标志。实际上,你应该使用的就是这个详细标志。

通过 [CmdletBinding()],可以通过 $PSCmdlet.MyInvocation.BoundParameters 访问绑定参数。以下是一个使用 CmdletBinding 的函数,它会立即进入嵌套提示符,以便检查函数作用域内可用的变量。

PS D:\> function hi { [CmdletBinding()]param([string] $Salutation) $host.EnterNestedPrompt() }; hi -Salutation Yo -Verbose

PS D:\>>> $PSBoundParameters

____________________________________________________________________________________________________
PS D:\>>> $PSCmdlet.MyInvocation.BoundParameters

Key Value                                                                                                                                                                                                           
--- -----                                                                                                                                                                                                           
Salutation Yo                                                                                                                                                                                                              
Verbose   True                                                                                       

所以在你的示例中,你需要以下内容:

function DoStuff `
{
    [CmdletBinding()]
    param ()
    process
    {
      new-item Test -type Directory `
        -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)
    }
}

这包括-Verbose、-Verbose:$false、-Verbose:$true以及开关根本不存在的情况。


1
据我所知,$PSBoundParameters只是$PSCmdlet.MyInvocation.BoundParameters的快捷方式。Function test-function { [cmdletbinding()] Param(); Write-Host $PSBoundParameters; Write-Host $PSCmdlet.MyInvocation.BoundParameters; $PSCmdlet.MyInvocation.BoundParameters.Equals($PSBoundParameters) }在你展示不同的例子中,这是因为当你进入嵌套提示时,你正在输入一个新的执行上下文,它有自己的$PSBoundParameters。 - New Guy
1
如果您想在命令行中测试-Verbose,并且测试当前shell中的$VerbosePreference值,最简短的形式就是测试-Verbose:($VerbosePreference -eq "Continue")。因为正如@NewGuy所解释的那样,$VerbosePreference值总是被适当地设置。 - alfred s.

38

也许听起来很奇怪,但是没有任何简单的方法可以让cmdlet知道它的详细或调试模式。请参考相关问题:

如何让cmdlet知道何时应该调用WriteVerbose()?

一个不完美但实际上合理的选项是引入自己的cmdlet参数(例如$MyVerbose$MyDebug),并在代码中显式使用它们:

function DoStuff {
    [CmdletBinding()]
    param
    (
        # Unfortunately, we cannot use Verbose name with CmdletBinding
        [switch]$MyVerbose
    )

    process {

        if ($MyVerbose) {
            # Do verbose stuff
        }

        # Pass $MyVerbose in the cmdlet explicitly
        New-Item Test -Type Directory -Verbose:$MyVerbose
    }
}

DoStuff -MyVerbose

更新

如果我们只需要一个开关(而不是例如详细程度值),那么使用$PSBoundParameters的方法可能比第一部分答案中提出的方法更好(使用额外参数):

function DoStuff {
    [CmdletBinding()]
    param()

    process {
        if ($PSBoundParameters['Verbose']) {
            # Do verbose stuff
        }

        New-Item Test -Type Directory -Verbose:($PSBoundParameters['Verbose'] -eq $true)
    }
}

DoStuff -Verbose

无论如何都不是完美的。如果有更好的解决方案,我也真的想自己知道。


我认为第二个例子在调用函数时,如果没有明确将Verbose设置为false,它可能会将其评估为true。当给定null时,它似乎默认为true。 - craika
@Alisdair Craik:好发现,谢谢,我已经纠正了(这次测试过了)。很奇怪,不是吗?注意:在你的答案中也许有一个小缺陷:仅检查ContainsKey()是不够的,因为该现有键后面的实际值仍然可以是$false。这是一个罕见的情况,但并非不可能。 - Roman Kuzmin
2
那个“-Vebose:(xxx)”的技巧我不知道你可以这样设置开关。我以为它总是包括或排除。我不知道你可以直接这样设置开关的值。 - Micah
2
@RomanKuzmin:有一个更好的解决方案。由于PowerShell已经处理了这个问题,更好的解决方案是什么都不做。请参见https://dev59.com/kG855IYBdhLWcg3wg0nC#20830886。有关检测部分,请参见https://dev59.com/wU3Sa4cB1Zd3GeqPwpxG#20828118。 - Lars Truijens
1
@VasylZvarydchuk,当Verbose缺失时,这个变量在严格模式下会失败。 - Roman Kuzmin
显示剩余2条评论

29

没必要了。PowerShell已经能够做到,下面的代码证明了这一点。

function f { [cmdletbinding()]Param()    
    "f is called"
    Write-Debug Debug
    Write-Verbose Verbose
}
function g { [cmdletbinding()]Param() 
    "g is called"
    f 
}
g -Debug -Verbose

输出结果为

g is called
f is called
DEBUG: Debug
VERBOSE: Verbose

不过,它不是直接传递“-Debug”给下一个 cmdlet。而是通过 $DebugPreference 和 $VerbosePreference 变量来完成的。Write-Debug 和 Write-Verbose 的作用与您预期的相同,但如果您想以不同的方式处理 debug 或 verbose,可以在此处了解如何自行检查。


6
您说得没错,这对于Write-Verbose有效,但是OP问的是如何使New-Item输出冗长的详细信息,而这种方法并不能实现。 - bwerks
4
当您使用-Verbose调用函数时,函数内的$VerbosePreference变量将被设置为“Continue”(默认为“SilentlyContinue”)。这将依次传递给函数内部调用的任何函数。如果这些函数调用Write-Verbose,则会检查$VerbosePreference并打印详细信息。New-Item(以及许多MS cmdlet)的问题在于它将忽略此变量。因此,您要么必须使用$PSBoundParameters,要么查看$VerbosePreference的当前设置,以确定是否应手动指定-Verbose到New-Item。 - New Guy
1
这应该在列表上位居更高。 - Law
2
虽然这个例子本身的功能是正常的,但当f被移动到一个模块中时,它似乎不再起作用了。你有什么想法是什么原因,以及如何解决? - stijn
3
如何将详细信息传递给模块函数?您可以使用Python的logging模块来实现这一点。首先,在主代码中配置日志记录器并设置详细级别,然后在模块函数中引用相同的日志记录器并记录详细信息。这样,只要日志记录器的详细级别是启用的,模块函数就会记录详细信息。 - stijn

11

这是我的解决方案:

function DoStuff {
    [CmdletBinding()]
    param ()

    BEGIN
    {
        $CMDOUT = @{
            Verbose = If ($PSBoundParameters.Verbose -eq $true) { $true } else { $false };
            Debug = If ($PSBoundParameters.Debug -eq $true) { $true } else { $false }
        }

    } # BEGIN ENDS

    PROCESS
    {
        New-Item Example -ItemType Directory @CMDOUT
    } # PROCESS ENDS

    END
    {

    } #END ENDS
}

与其他示例不同的是,它将遵守“-Verbose:$false”或“-Debug:$false”。仅当您使用以下命令时,它才会将-Verbose/-Debug设置为$true:

DoStuff -Verbose
DoStuff -Verbose:$true
DoStuff -Debug
DoStuff -Debug:$true

这是最实用和完整的答案。应该被接受。 - Seth
这肯定比其他例子效果要好。不确定为什么另一种解决方案被接受了。 - Rhyknowscerious

4

最好的方法是设置$VerbosePreference。这将为整个脚本启用详细级别。不要忘记在脚本结束时禁用它。

Function test
{
    [CmdletBinding()]
    param($param1)

    if ($psBoundParameters['verbose'])
    {
        $VerbosePreference = "Continue"
        Write-Verbose " Verbose mode is on"
    }
    else
    {
        $VerbosePreference = "SilentlyContinue"
        Write-Verbose " Verbose mode is Off"
    }


    # <Your code>

}

我同意,使用这种方法可以捕获甚至在调用其他函数的函数中的冗长偏好(这是正确的,因为PowerShell默认情况下会将其传递下去),而其他概述的方法仅在每个函数上显式调用了verbose时才起作用。 - Mark Wragg

3
您可以基于绑定的调试或详细参数构建一个新的哈希表,然后将其展开到内部命令。如果您只是指定开关(而不是传递假开关,如$debug:$false),则只需检查调试或详细参数是否存在即可。
function DoStuff() { 
   [CmdletBinding()] 

   PROCESS { 
        $HT=@{Verbose=$PSBoundParameters.ContainsKey'Verbose');Debug=$PSBoundParameters.ContainsKey('Debug')}
      new-item Test -type Directory @HT
   } 
} 

如果您想传递参数值,这将更加复杂,但可以通过以下方式完成:
function DoStuff {  
   [CmdletBinding()]  
   param()
   PROCESS {  
   $v,$d = $null
   if(!$PSBoundParameters.TryGetValue('Verbose',[ref]$v)){$v=$false}
   if(!$PSBoundParameters.TryGetValue('Debug',[ref]$d)){$d=$false}
   $HT=@{Verbose=$v;Debug=$d} 
   new-item Test -type Directory @HT 
   }  
}  

1
您可以在启动脚本时将VerbosePreference设置为全局变量,然后在自定义cmdlet中检查全局变量。
脚本:
$global:VerbosePreference = $VerbosePreference
Your-CmdLet

你的CmdLet:

if ($global:VerbosePreference -eq 'Continue') {
   # verbose code
}

明确检查'Continue'允许脚本等于-verbose:$false,当您从未设置全局变量的脚本中调用CmdLet时(此时为$null)。


为什么你需要 $global:VerbosePreference?你可以直接使用 $VerbosePreference。 - Michael Freidgeim
因为你的脚本不是全局的,可能会影响到它所使用的所有模块。 - StingyJack

0

您不必进行任何检查或比较。即使-Verbose(和-Debug)是[switch]类型,它们似乎不仅理解$true和$false,而且还理解它们的首选项变量。首选项变量也会正确地继承到所有被调用的子函数中。我在Powershell版本7.3.2上尝试过这个方法,它按预期工作。

function Parent {
    [CmdletBinding()]param()
    Child
}

function Child {
    [CmdletBinding()]param()
    New-Item C:\TEST\SomeDir -Force -ItemType Directory -Verbose:$VerbosePreference -Debug:$DebugPreference
}

Parent -Verbose
Parent -Debug

-1

我认为这是最简单的方法:

Function Test {
    [CmdletBinding()]
    Param (
        [parameter(Mandatory=$False)]
        [String]$Message
    )

    Write-Host "This is INFO message"

    if ($PSBoundParameters.debug) {
        Write-Host -fore cyan "This is DEBUG message"
    }

    if ($PSBoundParameters.verbose) {
        Write-Host -fore green "This is VERBOSE message"
    }

    ""
}
Test -Verbose -Debug

2
对于使用Write-Host的行为进行负面评价,特别是当你使用它来替代Write-Debug和Write-Verbose提供的功能时,而这种方式甚至不能像那些cmdlet一样写入它们各自的流,而是伪造它。 - Dusty Vargas

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接