为什么我需要模块清单 - 因为已经超过了模块嵌套限制

Why would I need a module manifest - because the module nesting limit has been exceeded

我刚刚在 Powershell 中遇到错误:

 because the module nesting limit has been exceeded. Modules can only be nested to 10 levels.

我找到了 并找到了一个叫做 "module manifest" 的东西。我已经有一个模块 .psm1 文件 - 为什么我还需要这个?

注意:我没有10级模块,我有10个模块,全部由一个import.psm1文件加载。

超出 模块嵌套级别通常是在模块导入期间意外进入 无限递归的结果 (无论您是否通过 Import-Module 或 PSv5+ using module 语句导入)。

无论您的模块是否有清单,这都可能发生; 的答案显示了如何通过 清单发生;这是一个 没有 的示例:以下 foo.psm1 模块导致无限递归,导致您看到

的错误消息
# Create sample module (without manifest).
@'
# Accidentally try to import the module itself.
using module .\foo.psm1

function bar { 'hi from module foo' }
'@ > foo.psm1

# This fails, because an infinite import loop is entered, 
# eventually causing the nesting limit to be exceeded.
Import-Module .\foo.psm1

为什么使用清单创建(脚本)模块是个好主意:

虽然 模块清单是 可选的 - 独立的 *.psm1 文件本身可以作为 modules - 那里是 使用它们的充分理由:

模块清单是一个 *.psd1 文件,它伴随您的 *.psm1 文件并指定重要的 元数据 ,特别是版本号,以 哈希表文字 的形式;为了获得最佳的用户体验,两个文件都应放在同名目录中(例如, Foo.psm1 及其清单 Foo.psd1 应放置在名为 Foo).

的目录中

通过使用显化模块,您启用了几个重要的用例:

  • 您需要一个清单来正确支持模块的软件开发过程,尤其是版本管理。

    • 它也是支持并排安装模块多个版本的先决条件。
  • 您需要一个清单来自动加载相关资源,例如其他模块或辅助 .NET 程序集,并定义帮助资源。

  • 您需要一个清单以便与 PowerShell 集成 module auto-loading mechanism:如果您将正确显示的模块放入 $env:PSModulePath 中列出的目录之一,PowerShell 将:

    • 甚至在导入模块之前就发现模块及其命令。
    • 将在您第一次尝试从会话中调用命令时按需导入
  • 您需要清单才能将模块发布到 PowerShell 模块的官方在线存储库,PowerShell Gallery


快速概述使用清单创建模块的步骤:

  • 创建一个目录,以您的.psm1文件的基本名称命名;例如,Foo
  • 将脚本代码作为文件 Foo.psm1 文件放在该目录中。
  • 在同一目录中,使用 New-ModuleManifest cmdlet,创建具有相同基本名称(例如 Foo.psd1)的清单 .psd1 文件

  • 至少,更新新 .psd1 文件中的 RootModule 条目以指向您的 .psm1 文件(例如,RootModule = 'Foo.psm1' )

  • 要与自动加载功能集成,请将您的模块目录放在 $env:PSModulePath 中列出的位置之一;对于当前用户,该位置是:

    • Windows PowerShell:
      • $HOME\Documents\WindowsPowerShell\Modules
    • PowerShell [核心] v6+:
      • Windows: $HOME\Documents\PowerShell\Modules
      • Linux,macOS:$HOME/.local/share/powershell/Modules
  • 有效地支持模块发现和自动加载显式控制和指示模块导出的内容,最好 明确列出 FunctionsToExport 中的各个导出模块成员, CmdletsToExportVariablesToExportAliasesToExport 个清单条目。


为了使模块创建更容易,社区提供了辅助模块:

  • Plaster 是一个 "template-based file and project generator written in PowerShell",也可用于脚手架模块:

    • 内置的 "New PowerShell Manifest Module" 模板搭建了一个模块目录,其中包含所有必要的文件并支持 Pester 测试。

    • 请参阅 this blog post 了解演练。

  • Stucco 以 Plaster 为基础提供 "opinionated Plaster template for building high-quality PowerShell modules."

    • Stucco 是一种高级工具,它超越了单纯的模块创建,它通过搭建包括 psake 任务的整个项目结构、CI/CD 集成的搭建、许可和帮助创作。

Plaster 的快速示例:

# Install Plaster for the current user, if necessary.
Install-Module Plaster -Scope CurrentUser

# Get the template for creating a new script module.
$template = Get-PlasterTemplate | Where TemplatePath -match ScriptModule

# Scaffold a module in subdirectory 'Foo'
#  * This will present a series of prompts, most of them with default values.
#  * IMPORTANT: Be sure to also choose 'Foo' as the module *name* when prompted,
#               so that the module auto-loading feature can discover your module
#               (if placed in a dir. in $env:PSModulePath) and also so that you 
#               you can load it by its *directory* path; e.g., Import-Module ./Foo
Invoke-Plaster -TemplatePath $template.TemplatePath -Destination Foo

# Add a test function to the `.psm1` file.
# Note: 
#  * This is just for illustrative purposes. In real life, you would
#    obviously use an editor to add functions to your module.
#  * The function must be placed *before* the `Export-ModuleMember` call in order
#    to be exported.
#  * As stated, it is additionally recommended to list the exported members
#    *explicitly*, one by one, in the *ToExport keys of the *.psd1 file.
(Get-Content -Raw ./Foo/Foo.psm1) -replace '\r?\n\r?\n', "`n`nfunction Get-Foo { 'Hi from module Foo.' }`n" | Set-Content -Encoding utf8 ./Foo/Foo.psm1

# Import the newly created module by its *directory* path.
# IMPORTANT: 
#   As stated, this assumes that you specified 'Foo' as the module name, i.e.
#   that your manifest's file name is 'Foo.psd1', and your script module's
#   'Foo.psm1'.
Import-Module ./Foo -Verbose -Force

'---'

# Call the test function
Get-Foo

'---'

# Invoke the module's tests.
# Note: The scaffolding creates a single test to ensure that the
#       module manifest (*.psd1) is valid.
Invoke-Pester ./Foo

您应该会看到如下输出:

VERBOSE: Loading module from path 'C:\Users\jdoe\Foo\Foo.psd1'.
VERBOSE: Loading module from path 'C:\Users\jdoe\Foo\Foo.psm1'.
VERBOSE: Importing function 'Get-Foo'.
---
Hi from module Foo.
---
    ____            __
   / __ \___  _____/ /____  _____
  / /_/ / _ \/ ___/ __/ _ \/ ___/
 / ____/  __(__  ) /_/  __/ /
/_/    \___/____/\__/\___/_/
Pester v4.9.0
Executing all tests in './Foo'

Executing script C:\Users\jdoe\Foo\test\Foo.Tests.ps1

  Describing Module Manifest Tests
    [+] Passes Test-ModuleManifest 128ms
Tests completed in 375ms
Tests Passed: 1, Failed: 0, Skipped: 0, Pending: 0, Inconclusive: 0