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

PowerShell ignoring Write-Verbose while running Import-Module

为了提出问题,我将这个简单的脚本保存为 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 模块时提供详细输出吗?

P.S。我不想总是显示详细信息,但只有在指定 -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什么时候导入:

When a module is imported, a new session state is created for the module, and a System.Management.Automation.PSModuleInfo object is created in memory. A session-state is created for each module that is imported (this includes the root module and any nested modules). The members that are exported from the root module, including any members that were exported to the root module by any nested modules, are then imported into the caller's session state. [..] To send output to the host, users should run the 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

现在一些注意事项:

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

  • 您仍然可以使用

    覆盖模块脚本中的详细配置
    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()] 添加到您的模块来稍微触及这一点;问题是你无法通过 named 参数传递,只有未命名的参数,通过 Import-Module -ArgumentList,所以你不能专门传递 $true for -Verbose.

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

(psm1)

[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"

这种方式贯穿始终。


在这一点上我应该提到我所展示的缺点:您可能会注意到我没有在 Import-Module 调用中包含 -Verbose,那是因为它没有改变模块内部的行为。无论 Import-Module.

上的 -Verbose 设置如何,内部的详细消息将完全根据您传入的参数显示

一个一体式解决方案然后回到 Marco 的回答:在调用方一侧操纵 $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。但是……没用。 Import-Module 确实识别了详细设置,但这次没有将其放入模块中。

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

(psm1)

[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 创建一个完整的代理函数,然后为它创建一个名为 Import-Module 的别名来替换真实的。


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