关于powershell:如何在自定义cmdlet中正确使用-verbose和-debug参数

How to properly use the -verbose and -debug parameters in a custom cmdlet

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

假设我有一个这样的cmdlet:

1
2
3
4
5
6
7
function DoStuff() {
   [CmdletBinding()]

   PROCESS {
      new-item Test -type Directory
   }
}

如果将-debug-verbose传递给了我的函数,我想将该标志传递给new-item cmdlet。正确的做法是什么?


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

通过[CmdletBinding()],您可以通过$PSCmdlet.MyInvocation.BoundParameters访问绑定的参数。这是一个使用CmdletBinding的函数,只需立即输入嵌套提示即可检查函数范围内可用的变量。

1
2
3
4
5
6
7
8
9
10
11
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

因此在您的示例中,您需要以下内容:

1
2
3
4
5
6
7
8
9
10
function DoStuff `
{
    [CmdletBinding()]
    param ()
    process
    {
      new-item Test -type Directory `
        -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true)
    }
}

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


也许这听起来很奇怪,但是对于cmdlet而言,没有任何简单的方法来了解其详细或调试模式。看看相关的问题:

cmdlet如何知道何时应真正调用WriteVerbose()?

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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的方法可能比该答案的第一部分(带有附加参数)中提出的方法要好:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function DoStuff {
    [CmdletBinding()]
    param()

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

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

DoStuff -Verbose

这还不是完美的。如果有更好的解决方案,那么我真的很想认识他们。


没有必要。如下面的代码所示,PowerShell已执行此操作。

1
2
3
4
5
6
7
8
9
10
function f { [cmdletbinding()]Param()    
   "f is called"
    Write-Debug Debug
    Write-Verbose Verbose
}
function g { [cmdletbinding()]Param()
   "g is called"
    f
}
g -Debug -Verbose

输出为

1
2
3
4
g is called
f is called
DEBUG: Debug
VERBOSE: Verbose

虽然这样做不像将-Debug传递给下一个cmdlet那样直接。这是通过$ DebugPreference和$ VerbrosePreference变量完成的。 Write-Debug和Write-Verbose的行为与您期望的一样,但是如果您想对debug或冗长的内容进行其他操作,可以在此处阅读如何进行自我检查。


这是我的解决方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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:

1
2
3
4
DoStuff -Verbose
DoStuff -Verbose:$true
DoStuff -Debug
DoStuff -Debug:$true

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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>

}

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

1
2
3
4
5
6
7
8
function DoStuff() {
   [CmdletBinding()]

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

如果要传递参数值,则更为复杂,但可以使用以下方法完成:

1
2
3
4
5
6
7
8
9
10
11
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
   }  
}

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

脚本:

1
2
$global:VerbosePreference = $VerbosePreference
Your-CmdLet

您的CmdLet:

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

通过从未设置全局变量的脚本(在这种情况下为$null)调用CmdLet时,显式检查" Continue"将使脚本等于-verbose:$false


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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