一个cmdlet如何知道它真正需要调用WriteVerbose()?

8

一条cmdlet如何才能知道它何时真正地需要调用WriteVerbose()WriteDebug()等函数呢?

也许我错过了什么简单的东西,但我找不到答案。迄今为止我见过的所有cmdlet实现都毫不犹豫地调用WriteVerbose()。我知道这样做是正确的,但不是有效的。

当冗长模式关闭但cmdlet仍然为WriteVerbose()调用准备数据时,性能会受到影响,也就是说,它白白准备数据。

换句话说,在一个cmdlet中,我想要能够:

if (<VerboseMode>)
{
    .... data preparation, sometimes expensive ...
    WriteVerbose(...);
}

但是我不知道如何获取这个if (<VerboseMode>)。有什么想法吗?


结论: @stej的答案展示了如何在理论上获取所需信息。但实践中这是hacky的,不太适合。因此,如果一个cmdlet产生非常昂贵的详细或调试输出,则引入一个额外的参数来指定详细程度似乎是合理的。


1
我不同意你的结论。请看我的答案以获取解决方案。 - Lars Truijens
3个回答

7

这是来自System.Management.Automation.MshCommandRuntime的方法。

internal void WriteVerbose(VerboseRecord record)
{
    if ((this.Host == null) || (this.Host.UI == null))
    {
        tracer.TraceError("No host in CommandBase.WriteVerbose()", new object[0]);
        throw tracer.NewInvalidOperationException();
    }
    ActionPreference verbosePreference = this.VerbosePreference;
    if (this.WriteHelper_ShouldWrite(verbosePreference, this.lastVerboseContinueStatus))
    {
        if (record.InvocationInfo == null)
        {
            record.SetInvocationInfo(this.MyInvocation);
        }
        this.CBhost.InternalUI.WriteVerboseRecord(record);
    }
    this.lastVerboseContinueStatus = this.WriteHelper(null, null, verbosePreference, this.lastVerboseContinueStatus, "VerbosePreference");
}

MshCommandRuntime 实现了接口 ICommandRuntime,这个接口并不知道什么是详细程度 :| (通过反射发现)。MshCommandRuntime 的实例应该在 Cmdlet 中可用 (public ICommandRuntime CommandRuntime { get; set; })。

因此,将属性 CommandRuntime 强制转换为 MshCommandRuntime 并检查详细程度应该是可行的。不管怎样,这真的很丑陋。


我完全同意应该有一种简单的方法来找出它。而且除此之外(做梦),编译器应该足够聪明,在这种情况下不评估某些字符串:

$DebugPreference = 'SilentlyContinue'
$array = 1..1000
Write-Debug "my array is $array"

Write-Debug的输入将永远不会被使用,因此不应该在传递的字符串中评估$array。(可以测试它是否真的像这样评估:Write-Debug "my array is $($array|%{write-host $_; $_})"


2
@stej:感谢您的有用调查。理论上说,可以使用反射方式进行黑客攻击(因为大部分内容是内部的,通常不可访问的)。但当然这并不是一个实际的解决方案。尽管如此,如果我们找不到更好的替代方案,我一会儿就会接受这个答案。我还在Connect上发现了一个相关的建议: https://connect.microsoft.com/PowerShell/feedback/details/74811/performance-provide-formatting-overloads-for-writeverbose-writedebug-etc (我认为这还不足够,我们需要一个标志IsVerbose、IsDebug等。) - Roman Kuzmin
1
@Roman,已投票。| 当开发 cmdlet 时,您应该可以访问所有变量,不是吗?然后就可以获取 $DebugPrecedence 并相应地采取行动。虽然不是理想的解决方案,但应该可以工作。 - stej
2
$DebugPreference$VerbosePreference确实比没有好。但它们并不足够,因为如果指定了任何cmdlet普遍参数-Verbose-Debug,它们会被覆盖。但是这些参数无法从cmdlet中访问。参数是否隐式地更改本地的$DebugPreference$VerbosePreference?我对此表示怀疑,但我会尝试找出答案。 - Roman Kuzmin
1
“普遍存在的参数是否会改变本地$DebugPreference$VerbosePreference?”- 不会。嗯,基本上我得出结论,在详细或调试输出昂贵的情况下,cmdlet应该引入额外的开关甚至参数,比如 -VerboseLevel <int | string | enum>。嗯,也许这并不是一个坏主意。 - Roman Kuzmin
1
是的,在一些脚本中我确实使用了这个。更多的冗长程度来得到我真正想要的东西。除此之外,我还摆脱了“VERBOSE:”和“DEBUG:”前缀。 - stej

6

这样怎么样:

BEGIN {
    if ($PSCmdlet.MyInvocation.BoundParameters["Debug"].IsPresent) {
        $HasDebugFlag = $true
    }

    if ($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent) {
        $HasVerboseFlag = $true
    }
}
PROCESS {
    if ($HasVerboseFlag) {
        expensive_processing
    }
}

注意:仅在PowerShell 3上测试。


3
确实不错,点赞+1。实际上,在某些情况下这应该是有效的,甚至很多情况下都可以使用。但如果模式不是通过参数而是通过 $VerbosePreference$DebugPreference 设置的话,它将无法正常工作。因此,if 语句还应检查这些变量。但是 V3 引入了自定义默认值。如果启用了自定义默认值,则绑定参数可能无法起到帮助作用。 - Roman Kuzmin
1
如果一个命令从另一个命令中调用,则该方法无效。 - Lars Truijens

6
所以我们不仅需要考虑cmdlet的常见参数-Debug和-Verbose,还要考虑全局$DebugPreference和$VerbosePreference标志,以及如果从另一个cmdlet调用cmdlet,则这些常见参数会被继承。
可以在不干扰内部结构的情况下完成。 此答案向您展示如何在PowerShell cmdlet中轻松完成此操作。
function f { [cmdletbinding()]Param() 
    $debug = $DebugPreference -ne 'SilentlyContinue'
    $verbose = $VerbosePreference -ne 'SilentlyContinue'
    "f is called"
    "    `$debug = $debug"
    "    `$verbose = $verbose"
}
function g { [cmdletbinding()]Param() 
    "g is called"
    f 
}
f
f -Debug -Verbose
g
g -Debug -Verbose

在 C# 中,我们需要检查这两个全局标志,但也要检查常见的参数。一定要从 PSCmdlet 继承而不是 Cmdlet,以便使用 GetVariableValue 方法。

bool debug = false;
bool containsDebug = MyInvocation.BoundParameters.ContainsKey("Debug");
if (containsDebug)
    debug = ((SwitchParameter)MyInvocation.BoundParameters["Debug"]).ToBool();
else
    debug = (ActionPreference)GetVariableValue("DebugPreference") != ActionPreference.SilentlyContinue;

bool verbose = false;
bool containsVerbose = MyInvocation.BoundParameters.ContainsKey("Verbose");
if (containsVerbose)
    verbose = ((SwitchParameter)MyInvocation.BoundParameters["Verbose"]).ToBool();
else
    verbose = (ActionPreference)GetVariableValue("VerbosePreference") != ActionPreference.SilentlyContinue; 

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