A template repository for creating PowerShell modules with built-in CI/CD, testing, and best practices.
PSModuleTemplate provides a standardized structure for developing PowerShell modules with automated testing, building, and deployment workflows. Use this template to quickly bootstrap new PowerShell module projects with industry-standard tooling and practices.
The CI/CD pipeline is split across three workflows triggered at different stages.
Pull request — opening a PR triggers two parallel jobs: integration tests (build + Pester) and the DocsBot which auto-generates markdown documentation for exported commands. Both must pass before the PR is ready to merge.
Merge to main — merging triggers the release pipeline which builds, tests, publishes to GitHub Packages, and creates a GitHub Release with the .nupkg, .zip, sha256 hashes, and auto-generated release notes. If GitHub Pages is enabled, the site is rebuilt automatically.
Manual publish — the PSGallery workflow is triggered manually from the Actions tab. It downloads the .nupkg from the latest GitHub Release and pushes it to the PowerShell Gallery.
- Pre-configured module structure
- Easy building and testing with make
- Pester testing framework integration
- Dependency management with PSDepend
- GitHub Actions CI/CD workflows
- C# class support
- Enum support with automatic loading
- Argument completer support
- Automatic documentation
- Optional GitHub Pages site generation
Click the 'Use this template' button at the top of this repository to create a new repository based on this template.
# Clone your new repository
git clone https://github.com/OWNER/MODULE_NAME.git
cd MODULE_NAME
# Set up the module structure
make setupThis creates a folder with the repository name, housing the module files/folders.
make setupThis installs any required modules defined in PSDepend.psd1 at the root of your repository.
If no PSDepend.psd1 file exists, this step is skipped gracefully.
make dependThis builds a nupkg from your source code into the .output folder.
make buildThis runs all pester tests against the built module.
make pesterThe module loads in this order: classes → enums → private → public → completers.
Defined in classes/ and loaded in the order specified in classes/classes.psd1. Order matters — parent classes must be listed before children. Supports .ps1, .cs, and .dll files.
Defined in enums/ as .ps1 files and loaded automatically.
# enums/LogLevel.ps1
enum LogLevel {
Debug
Info
Warning
Error
Critical
}Defined in private/, one function per file. Available within the module but not exported.
Defined in public/, one function per file, using the Verb-Noun convention. To export these functions, they must be included in the FunctionsToExport array inside the module manifest.
Defined in completers/ and loaded last. Provide tab-completion for function parameters. Use an underscore naming convention (e.g. Complete_ParameterName.ps1) — they are excluded from Pester naming and help tests.
# completers/Complete_LogLevel.ps1
Register-ArgumentCompleter -CommandName 'Write-Log' -ParameterName 'Level' -ScriptBlock {
param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)
[LogLevel].GetEnumNames() | where { $_ -like "$wordToComplete*" } | foreach {
[System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)
}
}Module dependencies are managed using PSDepend.
To declare dependencies, create or edit the PSDepend.psd1 file in the root of your repository.
A simple example that installs the latest version of a module from PSGallery:
@{
'Microsoft.PowerShell.ConsoleGuiTools' = 'latest'
}You can also pin versions, use hashtable format for more control, or pull from Git repositories.
See the comments in the included PSDepend.psd1 template for all supported options.
Any modules listed in PSDepend.psd1 should also be added to RequiredModules in your module manifest so that PowerShell enforces the dependency at import time.
For advanced scenarios that fall outside the standard module build process, two custom scripts are available. These are intended for things like compiling native or foreign-language code, staging external binaries, or configuring specialised environments.
.build/scripts/Invoke-ContainerSetup.ps1
Runs during environment or container initialisation. Use this for installing SDKs, runtimes, configuring environment variables, authenticating with external feeds, or validating toolchain versions before any build steps execute.
.build/scripts/Invoke-PostBuild.ps1
Runs after the module has been built to the output directory but before the nupkg is packed. Use this for copying compiled DLLs or native binaries into the output module folder, embedding additional metadata, signing output binaries, or staging any extra assets that need to be included in the final package.
On pull request, the module will be built and tested with the pester tests in your repository. Another runner will execute, producing markdown documentation for exported functions.
Once you merge your pull request, this will carry out the same steps but also release your package. It will produce release notes, changelog and a nupkg in GitHub packages.
Note: this will fail if the version is not bumped, or if the current version already exists as a release or package.
A manually triggered workflow is available to publish your module to the PowerShell Gallery.
This workflow downloads the .nupkg from the latest GitHub release and pushes it to PSGallery.
To use this workflow:
- Ensure a GitHub release exists with a
.nupkgasset (created automatically by the merge to master pipeline). - Add a
PSGALLERY_API_KEYsecret to your repository or organisation. You can generate an API key from your PSGallery account. - Navigate to the Actions tab, select the Publish to PSGallery workflow, and click Run workflow.
After merging to master, markdown files for each public function in the module will be created. Ensure Get-Help is accurate and populated, as this will be referenced to create the documentation.
This template includes a workflow that can automatically generate a documentation site for your module using GitHub Pages. The site is built from your repository's markdown files and includes your README as the homepage, exported command documentation, releases fetched from the GitHub API, and any LICENSE, CONTRIBUTING, or CODE_OF_CONDUCT files.
- Go to Settings → Pages in your repository.
- Under Build and deployment, set the source to GitHub Actions.
- Push to
mainor manually trigger the workflow from the Actions tab.
The site will be published at https://<owner>.github.io/<repo>/.
The Pages workflow only publishes markdown files (.md), license files (LICENSE, LICENSE.txt), and image assets from the assets/ folder. Everything else in your repository (source code, build scripts, tests, etc.) is excluded.
To add a favicon to your site, place an SVG file at assets/favicon.svg in your repository.
The workflow dynamically generates the site at build time — no Jekyll configuration files need to be committed to your repository. It detects which community files exist (LICENSE, CONTRIBUTING, CODE_OF_CONDUCT) and conditionally adds them to the navigation. If GitHub Pages is not enabled, the workflow skips gracefully without failing.
We welcome contributions! Please see CONTRIBUTING for details on how to contribute to this project.
This project is licensed under the Apache 2.0 License - see the LICENSE file for details.
Copyright (c) 2026 James D'Arcy Ryan
James D'Arcy Ryan
- GitHub: @jdarcyryan
Thank you to all contributors who help improve this template!
