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
Why creating a (script) module with a manifest is a good idea:
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.
- It is also a prerequisite for supporting side-by-side installations of multiple versions of your module.
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:
- Discover the module and its commands even before the module is imported.
- Will import it on demand, the first time you try to call a command from the session.
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:
- Create a directory named for the base name of your
.psm1
file; e.g., Foo
- Place the script code as file
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:
- Windows PowerShell:
$HOME\Documents\WindowsPowerShell\Modules
- PowerShell [Core] v6+:
- Windows:
$HOME\Documents\PowerShell\Modules
- Linux, macOS:
$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:
Stucco
builds on Plaster to provide an "opinionated Plaster template for building high-quality PowerShell modules."
- Stucco is an advanced tool that goes beyond mere module creation, by scaffolding an entire project structure that includes
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