在 psm1 模块内的 Import-Module 上使用“-Force”时绕过 PowerShell 模块“隐藏” 跟踪错误

问题描述

上下文/设置

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

许多功能被分组在 .psm1 文件中,这些文件通过使用相互加载

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

这对于开发目的/测试代码等来说似乎是个好主意,但我最近偶然发现了这种方法一个问题。

发现/问题

每当一个加载的模块强制导入之前在父/外作用域中加载的另一个模块时,它就不再能在这个父/外作用域中访问

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

示例

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

ModA:

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

模块:

$ErrorActionPreference = "Stop"
// module B entrails

模组:

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

以下纠缠测试证明了这种“模块隐藏”行为:

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
  }
}

跟踪错误

为了查明问题,我尝试提出另一个纠缠测试来分析代码库并验证在导入另一个模块时没有从全局模块表中删除“已加载”模块:>

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
  } 
}

问题

有没有办法打印/调试全局模块表,如here所示,以便我可以确定哪个模块被多次加载/导入后模块树如何被修改

解决方案?

目前,解决这个“问题”的唯一方法(除此之外,在其他模块中强制导入模块可能是一个糟糕的设计选择)是删除 -force 或使用 -global在此构建自动化套件/工具中包含的模块上调用 Import-Module -Force

解决方法

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

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

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

如果您查看 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.

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

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