A batteries-included starting point for PowerShell modules.
Build with ModuleBuilder, test with Pester, lint with PSScriptAnalyzer, and ship to the PowerShell Gallery from a git tag.
| ποΈ ModuleBuilder | Builds your module from source into a clean, versioned Dist output |
| π§ͺ Pester 5 | Test runner wired to Tests/: behaviour tests run against the source tree (failures name a source file and line), artifact checks against the built module |
| π PSScriptAnalyzer | Style and correctness pass, plus a compatibility pass against your target hosts |
| π Code coverage | Per-file coverage report over the source tree with an optional minimum-percentage gate |
| π― Task runner | One entry point (tasks.ps1) for every tool |
| π€ GitHub Actions | CI matrix across your target hosts, plus a tag-driven Gallery release |
| π§© Platform presets | PowerShell5.1, PowerShell7, or both. One key sets the manifest, the lint targets, and the CI matrix |
πͺ prepare task |
Renames and stamps the whole template from a single module.psd1 |
| π Hardening guide | The GitHub rulesets and settings that make publishing to the Gallery safe |
| π Structured source | Source/ layout with Enum, Classes, Private, and Public. Classes and enums have rules of their own: see Docs/CLASSES_AND_ENUMS.md |
Or clone it
git clone https://github.com/ArchitektApx/PowershellModuleTemplate
cd PowershellModuleTemplateEdit module.psd1 at the repository root:
| Property | Description |
|---|---|
ModuleName |
Your module name (no spaces, must start with a letter) |
ModuleDescription |
Short description of the module |
ModuleTargetPlatform |
Which PowerShell hosts you target. See Target platforms |
ModuleAuthor |
Your name or team |
ModuleCompanyName |
Company or vendor. Also becomes the LICENSE copyright holder |
ModuleProjectUri |
Your repository URL. Becomes ProjectUri/LicenseUri and the changelog compare links |
ModuleRequiredModules |
Extra development-time dependencies, on top of the fixed base set |
ModuleRequiredPowershellVersion |
Optional. Pin a higher minimum than the platform default; empty uses the default |
Note
CompatiblePSEditions and PowerShellVersion are derived from ModuleTargetPlatform,
so there is no separate key for them.
./tasks.ps1 prepare
./tasks.ps1 prepare -Platform PowerShell7 # override the platform for this runThis renames Source/ModuleTemplate.ps[dm]1, generates a fresh GUID, stamps the manifest,
build.psd1 and LICENSE, generates Tools/PSScriptAnalyzer.psd1 and the CI matrix for your
target platform, renders a README.md and CHANGELOG.md for your module, deletes the
template-only scaffolding (res/, Tools/templates/, Tools/platforms/), and installs the
dev requirements.
Warning
prepare overwrites README.md, CHANGELOG.md, Tools/PSScriptAnalyzer.psd1 and
.github/workflows/ci.yml. Run it on a fresh clone, before you have written anything of
your own.
Add one .ps1 per function under Source/Public (exported) or Source/Private (internal),
then:
./tasks.ps1 build
./tasks.ps1 test
./tasks.ps1 lintThe built module lands in Dist/<ModuleName>/<ModuleVersion>. π
ModuleTargetPlatform selects one of the presets in Tools/platforms/:
| Preset | Manifest | Lint targets | CI hosts |
|---|---|---|---|
πͺ PowerShell5.1 |
Desktop, 5.1.0 |
5.1 | win / WinPS 5.1 |
π PowerShell7 |
Core, 7.0.0 |
7.0 | win, linux, macos / pwsh 7 |
π PowerShell5.1And7 |
Core, Desktop, 5.1.0 |
5.1 + 7.0 | all four (default) |
The lint step enforces the preset. A ternary or ?? in Source/ passes under PowerShell7
and fails under PowerShell5.1And7.
Tip
Retargeting after prepare means editing Tools/PSScriptAnalyzer.psd1 and the ci.yml
matrix by hand, since Tools/platforms/ is gone by then.
See ModuleBuilder for how the source tree is assembled into the built module.
.
βββ π module.psd1 # Module metadata consumed by the tooling
βββ π build.psd1 # ModuleBuilder build config (manifest path, output, SemVer)
βββ π― tasks.ps1 # Task runner
βββ π Source/
β βββ ModuleTemplate.psd1 # Becomes <YourModuleName>.psd1 after prepare
β βββ ModuleTemplate.psm1 # Becomes <YourModuleName>.psm1 after prepare
β βββ Enum/
β βββ Classes/
β βββ Private/
β βββ Public/
βββ π§ͺ Tests/
β βββ _TestHelpers.ps1 # Target selection and import helpers; no module name hardcoded
β βββ Harness.Tests.ps1 # Tests for the test harness itself
β βββ Module.Tests.ps1 # Artifact checks against the BUILT module, green on a fresh clone
βββ π§ Tools/
β βββ platforms/ # Target-platform presets (removed by prepare)
β βββ templates/ # Skeletons rendered by prepare (removed by prepare)
β βββ ... # See Tools/README.md
βββ π Docs/HARDENING.md # GitHub settings to set before publishing (survives prepare)
βββ π Docs/CLASSES_AND_ENUMS.md # Class/enum rules and limits (removed by prepare)
βββ π€ .github/workflows/ # ci.yml (matrix from the platform) and release.yml (tag -> PSGallery)
βββ π¦ Dist/ # Build output (gitignored, created by build)
./tasks.ps1 <TaskName>| Task | Description | |
|---|---|---|
| πͺ | prepare | One-time template setup. Run once, after editing module.psd1. -Platform <name> overrides ModuleTargetPlatform. |
| π§Ή | cleanup | One-time teardown. Deletes prepare.ps1 and itself and strips both tasks out of tasks.ps1. Run once the repo is prepared and hardened. |
| π₯ | install_dev_requirements | Installs ModuleBuilder, Configuration, Pester 5+, PSScriptAnalyzer, plus your extras. Once per host per PowerShell edition. |
| ποΈ | build | Clears Dist/, builds with ModuleBuilder into Dist/<ModuleName>/<ModuleVersion>. |
| π§ͺ | test | Builds, then runs the Pester suite. -Target Source (default) or Dist picks the tree the behaviour tests import; -Path <file-or-dir> picks which tests execute. Fails on an empty run. |
| π | lint | PSScriptAnalyzer over Source/: style and correctness, then compatibility against your target platform. |
| π | coverage | Per-file coverage report over the source tree. -MinimumPercent 90 to gate. |
| π’ | prepare_release | ./tasks.ps1 prepare_release 1.1.0. Gates, promotes the changelog, stamps the version, rebuilds, verifies. |
π Full tool reference: Tools/README.md
ci.yml runs lint, build, and test on every push and pull request. Which hosts it runs on
comes from your ModuleTargetPlatform; the template's own default is all four (Windows
PowerShell 5.1, and pwsh 7 on Windows, Linux, and macOS).
release.yml fires on a vX.Y.Z tag. It requires the tagged commit to be on the default
branch, requires the tag to match the built manifest version, runs the full CI matrix, then
publishes to the PowerShell Gallery and creates a GitHub release whose body is that version's
CHANGELOG.md section.
The release flow after prepare_release has stamped the version and the PR is merged:
git checkout master
git pull origin master
git tag v1.1.0
git push origin v1.1.0The generated module README documents this in full. Pull first: the tag has to land on the merged commit, not on whatever you had locally.
Important
Publishing needs a repository secret PSGALLERY_API_KEY. The workflow also refuses to
release while module.psd1 still names the module ModuleTemplate.
Caution
The release workflow publishes to the PowerShell Gallery, where a version number can never be reused and consumers install without reviewing what they get. That makes push access to your default branch equivalent to push access to everyone's machines.
Docs/HARDENING.md covers how to close that path:
π the publishing secret Β β’Β π‘οΈ a default-branch ruleset with an empty bypass list Β β’Β π·οΈ immutable release tags Β β’Β π€ read-only Actions permissions Β β’Β π secret scanning with push protection Β β’Β βοΈ commit signing
Each step says what it does and why it matters, and most come with a gh command.
None of it is on by default, and none of it survives "Use this template". It is GitHub configuration rather than repository content, so you have to redo it per repo. Do it before the first tag.
- π’ Version is controlled in
build.psd1via theSemVerkey, whichprepare_releasestamps for you. - π€ Copy
Dist/<ModuleName>/<ModuleVersion>to a module path, or let the release workflow publish it.
