Skip to content
AgileLogics
6 min read

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.
  • CompatiblePSEditions and PowerShellVersion — 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.