【问题标题】:How to properly use the -verbose and -debug parameters in a custom cmdlet如何在自定义 cmdlet 中正确使用 -verbose 和 -debug 参数
【发布时间】:2026-01-13 20:55:02
【问题描述】:

默认情况下,任何具有 [CmdletBinding()] 属性的命名函数都接受 -debug-verbose(以及其他一些)参数,并具有预定义的 $debug$verbose 变量。我试图弄清楚如何将它们传递给在函数中调用的其他 cmdlet。

假设我有一个这样的 cmdlet:

function DoStuff() {
   [CmdletBinding()]

   PROCESS {
      new-item Test -type Directory
   }
}

如果 -debug-verbose 被传递到我的函数中,我想将该标志传递到 new-item cmdlet。这样做的正确模式是什么?

【问题讨论】:

  • PowerShell 已经为您完成了这项工作。虽然没有你想象的那么直接。请参阅下面的答案

标签: powershell parameters cmdlets cmdlet


【解决方案1】:

$PSBoundParameters 不是您想要的。除了提供 Verbose 标志外,[CmdletBinding()] 属性的使用还允许在脚本中使用 $PSCmdlet。实际上,您应该使用的是同一个 Verbose。

通过[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 以及开关根本不存在的情况。

【讨论】:

  • 据我了解,$PSBoundParameters 只是 $PSCmdlet.MyInvocation.BoundParameters 的快捷方式。 Function test-function { [cmdletbinding()] Param(); Write-Host $PSBoundParameters; Write-Host $PSCmdlet.MyInvocation.BoundParameters; $PSCmdlet.MyInvocation.BoundParameters.Equals($PSBoundParameters) } 在您的示例中,它们显示不同,这是因为当您进入嵌套提示时,您正在使用它自己的 $PSBoundParameters 输入一个新的执行上下文。
  • 如果你想在命令行中测试-Verbose你当前shell中的$VerbosePreference值,最短的形式就是测试-Verbose:($VerbosePreference -eq "Continue") .因为,正如@NewGuy 解释的那样,$VerbosePreference 的值始终设置得当。
【解决方案2】:

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

How does a cmdlet know when it really should call 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 评估为 true,如果在调用函数时未明确设置为 false。当给定一个空值时,它似乎假设为真。
  • @Alisdair Craik:很好,谢谢,我已经更正了(这次测试了)。很奇怪,不是吗?注意:在您的回答中也可能存在一个小缺陷:检查ContainsKey() 是不够的,因为现有密钥背后的实际值可能仍然是$false。这是一种罕见的情况,但并非不可能。
  • 这就是技巧“-Vebose:(xxx)” 我不知道你可以这样设置开关。我认为它总是包含或排除。我不知道你可以这样直接设置switch的值。
  • @RomanKuzmin:有更好的解决方案。由于 PowerShell 已经处理好它,因此更好的解决方案是什么都不做。见*.com/a/20830886/1242。检测部分见*.com/a/20828118/1242
  • @VasylZvarydchuk,当Verbose 缺失时,此变体在严格模式下失败
【解决方案3】:

没有必要。正如下面的代码所证明的那样,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 和 $VerbrosePreference 变量完成的。 Write-Debug 和 Write-Verbose 的行为与您预期的一样,但如果您想使用 debug 或 verbose 做一些不同的事情,您可以阅读 here 如何自行检查。

【讨论】:

  • 这适用于 Write-Verbose,正如你所说,但是 OP 询问有关导致 New-Item 写入详细输出的问题,但这并没有实现。
  • 当您使用 -Verbose 调用函数时,函数内部的 $VerbosePrefence 变量将设置为 Continue(默认为 SilentlyContinue)。这将依次传递给函数内调用的任何函数。如果这些函数调用 Write-Verbose,它将检查 $VerbosePreference 并打印详细信息。 New-Item(以及许多 MS cmdlet)的问题在于它会忽略此变量。因此,您要么必须使用 $PSBoundParameters,要么查看 $VerbosePreference 的当前设置,以确定是否应手动将 -Verbose 指定为 New-Item。
  • 这应该在列表中更高。
  • 虽然这个例子本身的功能和宣传的一样,但是当 f 被移动到一个模块中时,它似乎不再这样做了。知道这是什么原因,以及如何解决?
  • 在这里回答这个问题:*.com/questions/44900568/…
【解决方案4】:

这是我的解决方案:

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

【讨论】:

  • 这是最实用最完整的答案。应该被接受。
【解决方案5】:

您可以根据绑定的调试或详细参数构建一个新的哈希表,然后将其发送到内部命令。如果您只是指定开关(并且没有传递错误开关,例如 $debug:$false),您可以检查是否存在 debug 或 verbose:

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 
   }  
}  

【讨论】:

    【解决方案6】:

    最好的方法是设置$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 默认将其向下传递),而概述的其他方法仅在以下情况下才有效每个函数都显式调用了详细信息。
    【解决方案7】:

    您可以在启动脚本时将 VerbosePreference 设置为全局变量,然后在自定义 cmdlet 中检查全局变量。

    脚本:

    $global:VerbosePreference = $VerbosePreference
    Your-CmdLet
    

    你的 CmdLet:

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

    当您从未设置全局变量的脚本调用 CmdLet 时,显式检查“继续”允许脚本等于 -verbose:$false(在这种情况下为 $null

    【讨论】:

    • 为什么需要 $global:VerbosePreference?你可以直接使用 $VerbosePreference
    • ...因为您的脚本对于它可能使用的所有模块来说不是全局的。
    【解决方案8】:

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

    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
    

    【讨论】:

    • 否决 Write-Host 的使用,尤其是当您使用它来从根本上替换 Write-Debug 和 Write-Verbose 提供的功能时,甚至不会执行那些 cmdlet 的操作做(写入他们各自的流),而是伪造它。