【问题标题】:PowerShell ignoring Write-Verbose while running Import-ModulePowerShell 在运行 Import-Module 时忽略 Write-Verbose
【发布时间】:2021-05-27 19:18:40
【问题描述】:

为了呈现问题,我将这个简单的脚本保存为 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:\>

【问题讨论】:

  • 我知道问题stackoverflow.com/questions/16406682,但我不想显示来自命令行开关的详细消息。我想在导入模块时显示详细信息。
  • 您是否尝试过在Write-Verbose 命令中也使用-Verbose
  • @MarcoLuzzara,感谢您的提示。至少在我的测试中,它总是显示详细信息,无论我在导入时是否指定 -Verbose。我扩展了我的问题以澄清这一点。我也尝试过使用 $PSBoundParameters,但也没有用。

标签: powershell module verbose


【解决方案1】:

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

简短的回答:你可能无法通过只玩-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

现在有一些注意事项:

  • 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 为中心),甚至是真正的解决方案。正如我所说,这只是一个理论。

【讨论】:

  • Write-Verbose -verbose:$false 不会在任何情况下什么都不做,不管环境与否?也许使用不同的 cmdlet 作为示例?
  • @Vesper 当然,如果将Verbose 设置为$false,它不会做任何事情。我只是想向他展示他总是可以覆盖$VerbosePreference 只是设置Verbose 选项。这意味着它可以被参数化,而不是$false,他可以使用例如在先前检查中分配的布尔变量。无论如何,感谢您指出,我正在编辑。
  • 感谢@MarcoLuzzara,非常有见地。特别是这个关于模块生命周期的链接。
  • 赏金已颁发,但我仍然没有接受答案。如果其他人有其他想法,欢迎分享。
  • @Igor 实际上我仍然希望有人能提出一个可以接受的解决方案。无论如何感谢赏金,我也不会接受我的回答,尽管我怀疑既然赏金已经获得,这篇文章会收到很多访问。
【解决方案2】:

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

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

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

(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 的别名来替换真实的。


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

【讨论】:

  • 感谢您为这些测试所做的努力! +1 绝对值得。本地函数中设置的$VerbosePreference 看起来很有希望,有时间我会研究一下。
猜你喜欢
  • 2013-04-30
  • 2012-05-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-09-18
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多