Chapter 15Lesson 03~190 minutes

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.

Manifests.psd1VersioningDependencies

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 fieldQuestion it answers
RootModuleWhich .psm1 or binary module contains the root implementation?
ModuleVersionWhich release is this?
GUIDWhat persistent module identity distinguishes it from another module with the same name?
AuthorWho maintains/publishes it?
FunctionsToExportWhich functions are part of the supported command surface?
RequiredModulesWhich other modules must be present/importable?
PowerShellVersionWhat minimum PowerShell version is required?
CompatiblePSEditionsWhich PowerShell editions does the author declare?
Tags / ProjectUriHow 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 45

5. 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.Keys

Do 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.Keys

You 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.

ChangeTypical version intentExample
Backward-compatible bug fixPATCH1.4.2 → 1.4.3
Backward-compatible featureMINOR1.4.3 → 1.5.0
Breaking public API changeMAJOR1.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.Tags

Avoid 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]$validation

13. Verification checklist

  • Your module has a .psd1 manifest 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-PowerShellDataFile without 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.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0 Send only Ethereum/ERC-20 compatible assets to this address.