I just got an error in Powershell:
because the module nesting limit has been exceeded. Modules can only be nested to 10 levels.
I found this and found something called a "module manifest". I already have a module .psm1 files - why do I also need this?
Note: I dont have 10 levels of modules, I have 10 modules, all loaded in by one import.psm1 file.
The module nesting level getting exceeded is typically the result of accidentally getting into an infinite recursion during module import (irrespective of whether you import via Import-Module
or the PSv5+ using module
statement).
This can happen whether or not your module has a manifest; the answer to the linked question shows how it can happen with a manifest; here's an example without one: the following foo.psm1
module causes an infinite recursion that results in the error message you saw
# 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
While module manifests are optional - standalone *.psm1
files can serve as modules by themselves - there are good reasons to use them:
A module manifest is a *.psd1
file that accompanies your *.psm1
file and specifies important metadata, notably the version number, in the form of a hashtable literal; for the best user experience, both files should be placed in a directory of the same name (e.g., Foo.psm1
and its manifest, Foo.psd1
, should be placed in a directory named Foo
).
By using a manifested module, you enable several important use cases:
You need a manifest to properly support your module's software-development processes, notably version management.
You need a manifest to automatically load associated resources, such as other modules or auxiliary .NET assemblies, and to define help resources.
You need a manifest in order to integrate with PowerShell's module auto-loading mechanism: If you place your properly manifested module into one of the directories listed in $env:PSModulePath
, PowerShell will:
You need a manifest in order to publish a module to the official online repository for PowerShell modules, the PowerShell Gallery
To quickly outline the steps for creating a module with manifest:
.psm1
file; e.g., Foo
Foo.psm1
file in that directory.In the same directory, using the New-ModuleManifest
cmdlet, create the manifest .psd1
file with the same base name (e.g., Foo.psd1
)
At the very least, update the RootModule
entry in the new .psd1
file to point to your .psm1
file (e.g., RootModule = 'Foo.psm1'
)
To integrate with the auto-loading feature, place your module directory in one of the locations listed in $env:PSModulePath
; for the current user, that location is:
$HOME\Documents\WindowsPowerShell\Modules
$HOME\Documents\PowerShell\Modules
$HOME/.local/share/powershell/Modules
To support module discovery and auto-loading efficiently and to explicitly control and signal what a module exports, it is best to explicitly list the individual exported module members in the FunctionsToExport
,
CmdletsToExport
, VariablesToExport
, and AliasesToExport
entries of the manifest.
To make module creation easier, the community has provided helper modules:
Plaster
is a "template-based file and project generator written in PowerShell", that can also be used to scaffold modules:
The built-in "New PowerShell Manifest Module" template scaffolds a module directory with all necessary files and support for Pester
tests.
See this blog post for a walk-through.
Stucco
builds on Plaster to provide an "opinionated Plaster template for building high-quality PowerShell modules."
psake
tasks, scaffolding for CI/CD integration, licensing, and help authoring.A quick example with 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
You should see output such as the following:
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