Building Production Modules
Prerequisites: Modules, Testing with Pester
A module that others rely on needs structure: public and private code, a manifest, help, tests, and versioning.
A standard layout
ServerTools/
├── ServerTools.psd1 # manifest
├── ServerTools.psm1 # loader
├── Public/
│ ├── Get-ServerHealth.ps1
│ └── Restart-AppPool.ps1
├── Private/
│ └── Write-Log.ps1
├── Classes/
│ └── HealthResult.ps1
├── tests/
│ └── Get-ServerHealth.Tests.ps1
├── README.md
└── CHANGELOG.md
One function per file makes changes easy to review. The .psm1 loads them:
New-Item -ItemType Directory -Path ServerTools/Public, ServerTools/Private -Force | Out-Null
Set-Content ServerTools/Private/Format-Status.ps1 'function Format-Status { param($Ok) if ($Ok) { "OK" } else { "FAIL" } }'
Set-Content ServerTools/Public/Get-ServerHealth.ps1 @'
function Get-ServerHealth {
[CmdletBinding()]
param([Parameter(Mandatory, ValueFromPipeline)][string]$Name)
process { [pscustomobject]@{ Name = $Name; Status = Format-Status ($Name -notlike '*old*') } }
}
'@
Set-Content ServerTools/ServerTools.psm1 @'
$public = @(Get-ChildItem -Path $PSScriptRoot/Public/*.ps1)
$private = @(Get-ChildItem -Path $PSScriptRoot/Private/*.ps1)
foreach ($file in $public + $private) { . $file.FullName }
Export-ModuleMember -Function $public.BaseName
'@
New-ModuleManifest -Path ServerTools/ServerTools.psd1 -RootModule ServerTools.psm1 `
-ModuleVersion 1.0.0 -Author 'Ada' -Description 'Server health tools' `
-FunctionsToExport 'Get-ServerHealth'
Import-Module ./ServerTools/ServerTools.psd1
'web01', 'web-old' | Get-ServerHealth
(Get-Module ServerTools).Version.ToString()
Get-Command -Module ServerTools | Select-Object -ExpandProperty Name
Name Status
---- ------
web01 OK
web-old FAIL
1.0.0
Get-ServerHealth
Format-Status is used internally but not exported.
The manifest
Important manifest fields:
ModuleVersion— follow semantic versioning: major for breaking changes, minor for features, patch for fixes.FunctionsToExport— list names explicitly (never'*'), which makes auto-loading fast.RequiredModules— dependencies installed alongside yours.CompatiblePSEditionsandPowerShellVersion— where it runs.PrivateData.PSData— tags, project URL, licence, release notes for the Gallery.
Test-ModuleManifest validates it.
Publishing
# illustrative
Publish-Module -Path .\ServerTools -NuGetApiKey $env:PSGALLERY_KEY
# or to an internal feed:
Register-PSRepository -Name Internal -SourceLocation 'https://nuget.example.com/v3/index.json'
Publish-Module -Path .\ServerTools -Repository Internal
Tools like ModuleBuilder and Sampler can "build" a module by
merging the files into one .psm1 for faster loading, and automate
versioning and packaging.