Module Manifests, Versions, Dependencies, and Compatibility Metadata
Add a validated module manifest with explicit exports, semantic versioning, dependency and compatibility metadata, tags/project identity, and safe PSD1 configuration handling.
Learning objectives
- Explain what a module manifest adds to a script module.
- Create and validate a .psd1 manifest with New-ModuleManifest/Test-ModuleManifest.
- Design explicit exports, version identity, and compatibility metadata.
- Understand RequiredModules and the cost of dependency chains.
- Apply semantic versioning to public PowerShell command contracts.
- Use Import-PowerShellDataFile for constant configuration without arbitrary execution.
1. A manifest turns “some code” into a described module artifact
The .psm1 file from Lesson 2 contains executable module code, but it does not yet declare a stable identity, version, compatibility requirements, dependencies, tags, or project metadata. A module manifest is a .psd1 data file that describes those properties.
A manifest is not mandatory for every local experiment. It becomes increasingly important when a module is shared, versioned, published, or consumed automatically.
2. Manifest fields answer operational questions
| Manifest field | Question it answers |
|---|---|
RootModule | Which .psm1 or binary module contains the root implementation? |
ModuleVersion | Which release is this? |
GUID | What persistent module identity distinguishes it from another module with the same name? |
Author | Who maintains/publishes it? |
FunctionsToExport | Which functions are part of the supported command surface? |
RequiredModules | Which other modules must be present/importable? |
PowerShellVersion | What minimum PowerShell version is required? |
CompatiblePSEditions | Which PowerShell editions does the author declare? |
| Tags / ProjectUri | How can humans and package repositories classify/find the project? |
3. Recreate the Lesson 2 module if needed
The manifest lab uses the same temporary module. If you still have the Lesson 2 workspace, this check leaves it intact. Otherwise it creates a minimal compatible root module so the manifest exercises are independently runnable.
$workspace = Join-Path ([IO.Path]::GetTempPath()) 'ps-academy-ch15-module'
$moduleRoot = Join-Path $workspace 'DevOpsAcademy.Tools'
$moduleFile = Join-Path $moduleRoot 'DevOpsAcademy.Tools.psm1'
$manifest = Join-Path $moduleRoot 'DevOpsAcademy.Tools.psd1'
New-Item -ItemType Directory -Path $moduleRoot -Force | Out-Null
if (-not (Test-Path -LiteralPath $moduleFile)) {
@'
function Get-DaRuntimeInfo {
[CmdletBinding()] param()
[pscustomobject]@{
PSTypeName='DevOpsAcademy.RuntimeInfo'
PSVersion=$PSVersionTable.PSVersion.ToString()
PSEdition=$PSVersionTable.PSEdition
CheckedUtc=[datetime]::UtcNow
}
}
function Test-DaTcpEndpoint {
[CmdletBinding()] param([Parameter(Mandatory)][int]$Port)
[pscustomobject]@{ PSTypeName='DevOpsAcademy.EndpointTest'; Port=$Port }
}
Export-ModuleMember -Function Get-DaRuntimeInfo,Test-DaTcpEndpoint
'@ | Set-Content -LiteralPath $moduleFile -Encoding utf8
}4. Generate metadata with New-ModuleManifest
New-ModuleManifest writes a valid manifest skeleton and fills the fields you provide. The GUID generated for this lab is fine for a disposable module. For a real published module, generate the GUID once and keep it stable across releases.
if (Test-Path -LiteralPath $manifest) {
Remove-Item -LiteralPath $manifest -Force
}
$manifestParams = @{
Path = $manifest
RootModule = 'DevOpsAcademy.Tools.psm1'
ModuleVersion = '1.0.0'
Guid = [guid]::NewGuid()
Author = 'Abolfazl Mohammadijoo'
Description = 'Disposable DevOps Academy module for Chapter 15 labs.'
PowerShellVersion = '7.4'
CompatiblePSEditions = @('Core')
FunctionsToExport = @('Get-DaRuntimeInfo','Test-DaTcpEndpoint')
Tags = @('DevOps','PowerShell','Training')
ProjectUri = 'https://github.com/mohammadijoo/DevOps_Academy'
}
New-ModuleManifest @manifestParams
Get-Content -LiteralPath $manifest -TotalCount 455. Test-ModuleManifest validates structure and requirements
Test-ModuleManifest parses the manifest, checks its structure, and validates referenced module components/requirements that can be checked in the current environment. It returns module metadata when the manifest is valid.
$moduleInfo = Test-ModuleManifest -Path $manifest -ErrorAction Stop
$moduleInfo | Select-Object Name,Version,Guid,Author,RootModule,
PowerShellVersion,CompatiblePSEditions
$moduleInfo.ExportedFunctions.KeysDo not make CI trust a manifest merely because the file parses. Also test that the module imports and that its exported commands behave according to their contract.
6. Use explicit exports for predictable discovery and performance
Manifests can declare FunctionsToExport. For shared modules, list the supported functions explicitly. A wildcard such as '*' is easy during prototyping but makes accidental API growth more likely and can make command discovery less efficient.
Import-Module $manifest -Force
Get-Command -Module DevOpsAcademy.Tools |
Select-Object Name,CommandType,Version,Source
(Get-Module DevOpsAcademy.Tools).ExportedFunctions.KeysYou can control exports in the .psm1, the manifest, or both. The important design outcome is one deliberate public surface that tests and documentation agree on.
7. RequiredModules declares dependencies before code fails deep inside
A module dependency is another module your code needs in order to function. RequiredModules can name dependencies and version constraints. Keep dependency chains as small as practical: every additional package adds compatibility, update, provenance, and supply-chain work.
# Illustrative manifest fragment -- not added to the lab manifest.
@{
RequiredModules = @(
@{
ModuleName = 'Contoso.Foundation'
RequiredVersion = '2.1.0'
}
)
}Use exact versions only when you truly require exact behavior. Otherwise define a compatibility strategy that allows safe updates. Package-manager version ranges appear in Lesson 5.
8. Compatibility metadata is a declaration, not a proof
PowerShellVersion states the minimum engine version. CompatiblePSEditions can declare Core and/or Desktop. These fields help discovery and import decisions, but they cannot prove that every platform API your code calls exists on Windows, Linux, and macOS.
Cross-platform modules still need tests on the platforms they claim. A module can be syntactically valid in PowerShell 7 while calling a Windows-only .NET API or external executable.
9. Semantic versioning communicates compatibility intent
Semantic Versioning uses a MAJOR.MINOR.PATCH shape. It is a communication contract rather than something PowerShell enforces automatically.
| Change | Typical version intent | Example |
|---|---|---|
| Backward-compatible bug fix | PATCH | 1.4.2 → 1.4.3 |
| Backward-compatible feature | MINOR | 1.4.3 → 1.5.0 |
| Breaking public API change | MAJOR | 1.5.0 → 2.0.0 |
If a function removes a property, renames a parameter, changes accepted pipeline input, or changes failure semantics, that may be breaking even when the implementation diff is small. Version the consumer contract, not the number of changed lines.
10. PSD1 data files are useful beyond module manifests
PowerShell data files can store constant configuration values in a hashtable. Use Import-PowerShellDataFile when you want data without executing the file as arbitrary PowerShell code. Microsoft documents built-in limits that reduce abuse from huge data files; bypass those limits only for data you trust.
$configPath = Join-Path $workspace 'academy.config.psd1'
@'
@{
Environment = 'lab'
RetryCount = 3
Tags = @('chapter15','modules')
}
'@ | Set-Content -LiteralPath $configPath -Encoding utf8
$config = Import-PowerShellDataFile -LiteralPath $configPath
$config.Environment
$config.RetryCount
$config.TagsAvoid reading a data file and feeding its text to dynamic execution. Configuration is data; keep it on the data side of the code/data boundary.
11. Manifest evaluation is restricted, but supply-chain trust still matters
Module manifests are themselves PowerShell data files with a specific schema. PowerShell evaluates module manifests in a restricted language mode, but that does not make an untrusted module safe: importing the module eventually executes its root module and other declared code with your process privileges.
Review package source/provenance before import, especially for modules used by privileged automation. Lesson 5 turns that rule into a package acquisition workflow.
12. Lab: validate the complete module package locally
Remove-Module DevOpsAcademy.Tools -ErrorAction SilentlyContinue
$validation = [ordered]@{
ManifestExists = Test-Path -LiteralPath $manifest
RootModuleExists = Test-Path -LiteralPath $moduleFile
ManifestValid = $false
ImportWorks = $false
ExportCount = 0
}
try {
Test-ModuleManifest -Path $manifest -ErrorAction Stop | Out-Null
$validation.ManifestValid = $true
Import-Module $manifest -Force -ErrorAction Stop
$validation.ImportWorks = $true
$validation.ExportCount = @(Get-Command -Module DevOpsAcademy.Tools).Count
}
finally {
Remove-Module DevOpsAcademy.Tools -ErrorAction SilentlyContinue
}
[pscustomobject]$validation13. Verification checklist
- Your module has a
.psd1manifest with a stable name/version identity. - You understand why a real module GUID should remain stable across releases.
- You explicitly list public exports.
- You can explain minimum engine/edition metadata without treating it as a cross-platform test.
- You understand RequiredModules as a dependency contract.
- You can load configuration with
Import-PowerShellDataFilewithout dynamic execution. - You validate both the manifest and the actual import behavior.
14. Knowledge check
Question 1. What is the role of RootModule in a manifest?
Question 2. Should you generate a new module GUID for every release?
Question 3. Does CompatiblePSEditions prove that every API is cross-platform?
Question 4. What does a MAJOR semantic-version change normally communicate?
Question 5. Why prefer Import-PowerShellDataFile over evaluating PSD1 text dynamically?
15. Summary and next bridge
The manifest converts a script module into a described artifact: identity, version, exports, engine/edition requirements, dependencies, and repository metadata become machine-readable. Semantic versioning then communicates how the public contract evolves.
Lesson 4 moves inside the implementation boundary again and asks when plain objects/functions are enough—and when classes, enums, using statements, or direct .NET APIs provide a clearer model.
16. Authoritative references
Keep the academy open
Support free, practical DevOps education.
Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0
Send only Ethereum/ERC-20 compatible assets to this address.