PowerShell modules provide a clean, powerful way to package, share, and maintain automation code. Instead of scattered scripts, modules help you organize your functions, enforce consistency, and deliver reliable tools to colleagues, teams, or external users.
Why write modules?
- π¦ Reusability: Group related functions together for easy reuse across projects.
- β Maintainability: Simplify updates, debugging, and version control.
- π€ Collaboration: Provide a clear API surface for team members or customers.
- π Distribution: Make your code installable via PowerShell Gallery, NuGet feeds, or internal repositories.
- π Security and trust: With code signing, modules help ensure only verified code is executed in controlled environments.
This guide walks through setting up a clean module structure, adding documentation, packaging it for distribution, testing it with Pester, and applying code signing to protect your work and build trust with users.
π Process Summary Link to heading
This table summarizes the steps to create a well-structured, secure, and distributable PowerShell module.
Step | Action | Command/Tool |
---|---|---|
1 | Design structure | Manual |
2 | Write loader + functions | PowerShell |
3 | Create manifest | New-ModuleManifest |
4 | Add README.md | Markdown |
5 | Write Pester tests | Pester |
6 | Run tests | Invoke-Pester |
7 | Sign code | Set-AuthenticodeSignature |
8 | Validate | Test-ModuleManifest |
9 | Package | Save-Module , nuget pack |
10 | Publish | Publish-Module , nuget push |
π Recommended Directory Structure Link to heading
MyModule/
β MyModule.psm1
β MyModule.psd1
β README.md
β MyModule.nuspec
ββββ_public/
ββββ_private/
ββββtests/
π README.md Best Practices Link to heading
Including a README.md
helps users understand your module, its purpose, installation, and usage. It is especially valuable in source repositories and private NuGet feeds.
π‘ Recommended sections:
- Overview
- Installation instructions
- Usage examples
- Function list
- Requirements
- License and contact info
In .nuspec
files for NuGet packaging, reference your README:
<metadata>
...
<readme>README.md</readme>
</metadata>
<files>
<file src="README.md" target="" />
...
</files>
β PowerShell Gallery currently doesnβt display the readme
field, but NuGet.org and many private feeds do.
β Module Loader Example Link to heading
The loader script automatically dot-sources private and public function files, and exports only the intended public functions.
$moduleRoot = Split-Path -Parent $MyInvocation.MyCommand.Path;
$exportFunctions += @();
Get-ChildItem -Path (Join-Path $moduleRoot '_private') -Filter '*.ps1' -Recurse | ForEach-Object {
. $_.FullName;
}
Get-ChildItem -Path (Join-Path $moduleRoot '_public') -Filter '*.ps1' -Recurse | ForEach-Object {
. $_.FullName;
}
$script:exportFunction = $exportFunctions | Select-Object -Unique;
Export-ModuleMember -Function $script:exportFunction;
π Example Public Function Link to heading
Hereβs a minimal example of a public function file. Each public function is defined in its own file and registered for export.
$exportFunctions += 'Get-Example';
function Get-Example {
[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]
[string]$name
)
Write-Output "Hello, $name";
}
π§ͺ Testing with Pester Link to heading
Writing tests ensures your module functions as expected and remains reliable as it evolves.
Example test (tests/Get-Example.Tests.ps1
)
Link to heading
Describe 'Get-Example' {
It 'outputs a greeting for the provided name' {
Get-Example -name 'Steffen' | Should -Be 'Hello, Steffen';
}
It 'throws an error if name is missing' {
{ Get-Example } | Should -Throw;
}
}
Run tests Link to heading
Invoke-Pester -Path .\tests
π¦ Packaging as NuGet (nupkg) Link to heading
PowerShell modules can be packaged as NuGet packages for easy sharing via PowerShell Gallery or internal feeds.
Generate manifest Link to heading
New-ModuleManifest -Path .\MyModule\MyModule.psd1 `
-RootModule 'MyModule.psm1' `
-ModuleVersion '1.0.0' `
-Author 'Steffen Hoppe' `
-Description 'Sample PowerShell module';
Package and publish Link to heading
Save-Module -Name .\MyModule -Path .\output -Force;
Publish-Module -Path .\MyModule -NuGetApiKey 'YourAPIKey' -Repository 'PSGallery';
Or with NuGet CLI:
nuget pack MyModule.nuspec -OutputDirectory .\output;
nuget push .\output\MyModule.1.0.0.nupkg -Source 'https://your-nuget-server' -ApiKey 'yourkey';
π Code Signing Link to heading
Code signing adds a layer of security to your module, proving its origin and ensuring it hasnβt been tampered with after signing.
Obtain a certificate Link to heading
New-SelfSignedCertificate `
-Type CodeSigningCert `
-Subject 'CN=MyModuleSigner' `
-CertStoreLocation 'Cert:\CurrentUser\My';
Sign your files Link to heading
$cert = Get-ChildItem Cert:\CurrentUser\My | Where-Object {
$_.Subject -eq 'CN=MyModuleSigner'
};
Set-AuthenticodeSignature -FilePath .\MyModule\MyModule.psm1 -Certificate $cert;
Set-AuthenticodeSignature -FilePath .\MyModule\MyModule.psd1 -Certificate $cert;
Get-ChildItem .\MyModule\_public\*.ps1 | ForEach-Object {
Set-AuthenticodeSignature -FilePath $_.FullName -Certificate $cert;
};
To include a timestamp:
Set-AuthenticodeSignature -FilePath .\MyModule\MyModule.psm1 `
-Certificate $cert `
-TimestampServer 'http://timestamp.digicert.com';
Validate Link to heading
Test-ModuleManifest -Path .\MyModule\MyModule.psd1;
Get-AuthenticodeSignature -FilePath .\MyModule\MyModule.psm1;
Considerations and Downsides Link to heading
While code signing is important, be aware of the following:
- You need a valid code signing certificate, typically purchased from a trusted certificate authority (CA), which introduces cost and administrative effort.
- Self-signed certificates are not trusted by others without manual configuration, limiting their use to internal or test environments.
- Managing certificates and private keys securely adds operational complexity.
- The signing process adds extra steps to your build or release pipeline, requiring automation for efficiency.
π‘ Tips Link to heading
- Automate these steps using CI pipelines or build scripts.
- Use timestamping when signing code to extend trust beyond certificate expiration.
- Include README, LICENSE, CHANGELOG, and tests in your repository (but not in the published package).