PowerShell 在运行 Import-Module 时忽略 Write-Verbose

问题描述

为了说明问题,我将这个简单的脚本保存为 PowerShell 模块 (test.psm1)

Write-Verbose 'Verbose message'

在现实生活中,它包括导入附加功能的命令,但目前无关紧要。

如果我运行 Import-Module .\test.psm1 -Verbose -Force,我只会得到

VERBOSE: Loading module from path 'C:\tmp\test.psm1'.

我的Write-Verbose被忽略了?

我尝试添加 cmdletbinging 但它也不起作用。

[cmdletbinding()]
param()

Write-Verbose 'Verbose message'

任何线索如何在导入 PowerShell 模块时提供详细输出

附言我不想总是显示详细信息,但只有在指定 -Verbose 时才显示。这是我对这两种不同情况的预期输出

PS C:\> Import-Module .\test.psm1 -Verbose -Force # with verbose output
VERBOSE: Loading module from path 'C:\tmp\test.psm1'.
VERBOSE: Verbose message

PS C:\> Import-Module .\test.psm1 -Force # without verbose output

PS C:\>

解决方法

这是一个有趣的情况。我有一个理论,但如果有人能证明我是错的,我会非常高兴。

简短的回答:您可能无法通过仅使用 -Verbose 来做您想做的事。可能有一些解决方法,但最短路径可能是设置 $VerbosePreference


首先我们需要了解一下导入时的lifetime of a module

导入模块时,会为该模块创建一个新的会话状态 模块,一个 System.Management.Automation.PSModuleInfo 对象是 在内存中创建。为每个模块创建一个会话状态 导入(这包括根模块和任何嵌套模块)。这 从根模块导出的成员,包括任何成员 由任何嵌套模块导出到根模块,然后 导入调用者的会话状态。 [..] 要将输出发送到主机,用户应运行 Write-Host cmdlet。

最后一行是第一个提示,它为我指出了一个解决方案:当一个模块被导入时,一个新的 session state 被创建,但只有导出的元素被附加到全局会话状态。这意味着 test.psm1 代码在与您运行 Import-Module 的会话不同的会话中执行,因此与该单个命令相关的 -Verbose 选项不会传播。

相反,这是我的假设,因为我没有在文档中找到它,全局会话状态的配置对所有子会话都是可见的。为什么这很重要?因为有两种方法可以开启冗长:

  • -Verbose 选项,在这种情况下不起作用,因为它是命令本地的
  • $VerbosePreference,使用首选项变量设置整个会话的详细程度。

我尝试了第二种方法并且它有效,尽管不是那么优雅。

$VerbosePreference = "Continue" # print all the verbose messages,disabled by default
Import-Module .\test.psm1 -Force
$VerbosePreference = "SilentlyContinue" # restore default value

现在的一些注意事项:

  • -Verbose 命令上指定 Import-Module 是多余的

  • 您仍然可以使用

    覆盖模块脚本中的详细配置
    Write-Verbose -Message "Verbose message" -Verbose:$false
    

    正如@Vesper 指出的那样,$false 将始终抑制 Write-Verbose 输出。相反,您可能希望使用在先前检查中分配的布尔变量来参数化该选项。类似的东西:

    if (...) 
    {
        $forceVerbose=$true
    } 
    else 
    { 
        $forceVerbose=$false
    }
    
    Write-Verbose -Message "Verbose message" -Verbose:$forceVerbose
    
  • 可能还有其他侵入性较小的解决方法(例如以 Write-Host 为中心),甚至是真正的解决方案。正如我所说,这只是一种理论。

,

Marco Luzzara 的回答是正确的(在我看来应该得到赏金)关于在其自己的会话状态中运行的模块,并且根据设计,您无法访问这些变量。

设置 $VerbosePreference 并恢复它的另一种解决方案是让您的模块采用专门用于此目的的参数。您通过尝试将 [CmdletBinding()] 添加到您的模块中稍微触及了这一点;问题是您无法通过 Import-Module -ArgumentList 传入 named 参数,只能传入未命名的参数,因此您不能专门为 {{1} 传入 $true }.

相反,您可以指定自己的参数并使用它。

(psm1)

-Verbose

后跟:

[CmdletBinding()]param([bool]$myverbose)

Write-Verbose "Message" -Verbose:$myverbose

在上面的示例中,它仅适用于您每次都设置 Import-Module test.psm1 -Force -ArgumentList $true 的特定命令。

但您可以将其应用于模块的 -Verbose:$myverbose

$VerbosePreference

这种方式适用于整个过程。


此时我应该提到我所展示的缺点:您可能会注意到我没有在 [CmdletBinding()]param([bool]$myverbose) $VerbosePreference = if ($myverbose) { 'Continue' } else { 'SilentlyContinue' } Write-Verbose "Message" 调用中包含 -Verbose,这是因为它不会改变模块内部的行为。无论 Import-Module 上的 -Verbose 设置如何,内部的详细消息都将完全根据您传入的参数显示。


一个多合一的解决方案可以追溯到 Marco 的回答:在调用方一侧操作 Import-Module。我认为这是使两种行为保持一致的唯一方法,但前提是您不使用 $VerbosePreference 开关打开 -Verbose

另一方面,在一个范围内,比如在一个可以使用 Import-Module 的高级函数中,设置开关会改变 -Verbose 的本地值。这会导致我们将 $VerbosePreference 包装在我们自己的函数中:

Import-Module

太好了!现在我们可以调用 function Import-ModuleVerbosely { [CmdletBinding()] param($Name,[Switch]$Force) Import-Module $Name -Force:$Force } 。但是……没用。 Import-ModuleVerbosely test.psm1 -Force -Verbose 确实识别了详细设置,但这次没有将其纳入模块。

虽然我一直没能找到办法看到它,但我怀疑这是因为变量被设置为 Private(尽管 Import-Module 似乎另有说法),所以该值没有成功这次。不管是什么原因......我们可以回去让我们的模块接受一个值。这次为了方便使用我们把它改成同类型:

(psm1)

Get-Variable

那我们换个函数:

[CmdletBinding()]param([System.Management.Automation.ActionPreference]$myverbose)

if ($myverbose) { $VerbosePreference = $myverbose }

Write-Verbose "message"

嘿,我们到了某个地方!但是..它有点笨重不是吗?

您可以更进一步,为 function Import-ModuleVerbosely { [CmdletBinding()] param($Name,[Switch]$Force) Import-Module $Name -Force:$Force -ArgumentList $VerbosePreference } 创建一个完整的代理函数,然后为其创建一个名为 Import-Module 的别名来替换真实的。


最终你会尝试做一些不被真正支持的事情,所以这取决于你想要走多远。