在 psm1 模块内的 Import-Module 上使用“-Force”时绕过 PowerShell 模块 'hiding'

circumvent PowerShell module 'hiding' when using '-Force' on Import-Module inside a psm1 module

context/setup

我正在开发一个相当大的构建自动化工具,它由 90% 的 PowerShell 代码组成。

许多功能都集中在 .psm1 文件中,这些文件通过使用

相互加载

Import-Module "$PSScriptRoot/AnotherModule.psm1" -Force

这似乎是开发目的/测试代码等的好主意,但我最近偶然发现这种方法存在问题。

finding/problem

每当一个已加载的模块强制导入另一个先前已在 parent/outer 范围内加载的模块时,它不再在此 parent/outer 范围内可访问

现在我 运行 遇到一个问题,在脚本开头导入所需的模块并不能保证导入的模块在脚本执行期间实际可用,因为导入的模块之一可能 'hide' 另一个先前加载的模块 :-/

例子

假设我们有三个模块:ModA、ModB、ModC:

模组A:

$ErrorActionPreference = "Stop"
Import-Module "$PSScriptRoot\ModB.psm1" -Force
// module A entrails

ModB:

$ErrorActionPreference = "Stop"
// module B entrails

国防部:

$ErrorActionPreference = "Stop"
Import-Module "$PSScriptRoot\ModA.psm1" -Force
// module C entrails

以下纠缠测试证明了这种 'module hiding' 行为:

Describe 'powershell module system tests' {

  # ModA -> Import ModB -Force
  # ModC -> Import ModA -Force
  $mod_path = Join-Path $PSScriptRoot "testmod"
  $mods = @("ModA", "ModB", "ModC")

  It "cleanup leftovers from previous tests" {
    Remove-Module $mods -ErrorAction SilentlyContinue
  }

  It 'is able to import ModA, which internally loads module B' {
    Import-Module "$mod_path\ModA.psm1" -Force
    Get-Module ModA | Should -Not -BeNullOrEmpty
    Get-Module ModB | Should -BeNullOrEmpty
  }
  
  It 'is able to import a second module already imported by the first one' {
    Get-Module ModA | Should -Not -BeNullOrEmpty
    Import-Module "$mod_path\ModB.psm1" -Force
    Get-Module ModB | Should -Not -BeNullOrEmpty
  }

  It 'still knows the first module' {
    Get-Module ModA | Should -Not -BeNullOrEmpty
  }

  It 'is able to import a thid module that also imports the first one' {
    Get-Module ModC | Should -BeNullOrEmpty
    Import-Module "$mod_path\ModC.psm1" -Force
    Get-Module ModC | Should -Not -BeNullOrEmpty
  }
  
  It 'does not know A or B any longer, as it has been hidden by C' {
    Get-Module ModA | Should -BeNullOrEmpty
    Get-Module ModB | Should -BeNullOrEmpty
  }

  It 'still knows C' {
    Get-Module ModC | Should -Not -BeNullOrEmpty
  }

  It 'cleanup leftovers from this test' {
    Remove-Module $mods
  }
}

追踪错误

为了查明问题,我试图提出另一个 pester 测试来分析代码库并验证没有 'already loaded' 模块从全局模块 table 中删除导入另一个时:

Describe 'verify tool module system hierachy' {
  $tool_modules = Get-ChildItem $script:engine_path -Filter "*.psm1"

  function UNLoadAllToolModules {
    Remove-Module 'Tool-*'
  }
  function LoadAllToolModules {
    $tool_modules | Foreach-Object { Import-Module $_.FullName }
  }

  It "cleanup leftovers from previous tests" {
   UNLoadAllToolModules
  }
  
  It "loading of a module does not hide an already loaded module" {
    $errMods = $()
    $tool_modules | ForEach-Object {
      LoadAllToolModules
      $pre = Get-Module
      $mod = $_
      Import-Module $mod.FullName -Force
      $post = Get-Module
      $mods = Compare-Object $post $pre | Select-Object -Expand InputObject | Select-Object -Expand Name
      $mods | Foreach-Object {
        "import of '$mod' hides '$_'" | Write-Host 
      }
      if ($mods) {
        $errMods += $mod.Name
      }
    }
    $errMods | Should -BeNullOrEmpty
  } 
}

问题

有没有办法打印/调试全局模块 table,如图 here 所示,这样我就可以弄清楚哪个模块被多次加载/模块树在加载后如何修改进口?

解决方案?

目前,解决此 'issue' 的唯一方法(除此之外,在其他模块中强制导入模块可能是一个糟糕的设计选择)是删除 -force 或使用 -global 每当在此构建自动化 suite/tool.

中包含的模块上调用 Import-Module -Force

我对此的回答很简单,但可能不适用于您的情况。

PowerShell 中的作用域相当简单。但是,当您开始尝试做这样的事情时,我只是建议您将所有内容加载到全局范围内并完成它。否则,你会经常追尾巴的。

在最终用户机器上,使用全局范围是不可接受的(无论如何对我来说)。在使用全局范围的构建自动化系统上可能是可以接受的,具体取决于您的情况,因为这就是它的全部用途。

如果您查看 Import-Module cmdlet 的帮助,您会在 -Global 参数下看到:

> [!TIP] > You should avoid calling `Import-Module` from within a module.
> Instead, declare the target module as a nested module in the parent module's manifest. 
> Declaring nested modules improves the discoverability of dependencies.

The Global parameter is equivalent to the Scope parameter with a value of Global.

(我修正了格式,所以它在这里工作)。

此建议/提示可能也对您有用。