This project is an attempt by a group of PowerShell MVPs and module authors to:
- Build a common set of tools for module authoring
- Encourage a common pattern for organizing PowerShell module projects
- Promote best practices for authoring functions and modules
In short, we want to make it easier for people to write great code and produce great modules.
In service of this goal, we intend to produce:
- Guidance on using the best of the existing tools: dotnet, Pester, PSDepends, etc.
- Module templates demonstrating best practices for organization
- Function templates demonstrating best practices for common parameters and error handling
- ModuleBuilder module - a set of tools for building modules following these best practices
This module is the main output of the project, consisting of one primary command: Build-Module
and a few helpers to translate input and output line numbers. It represents the collaboration of several module authors who had each written their own version of these tools for themselves, and have now decided to collaborate on creating a shared tool set. We are each using the patterns and tools that are represented here, and are committed to helping others to succeed at doing so.
Builds a script module from a source project containing one file per function in Public
and Private
folders.
The Build-Module
command is a build task for PowerShell script modules that supports incremental builds.
Takes the output from Invoke-Pester -Passthru
run against the build output, and converts the code coverage report to the source lines.
Converts a line number from a source file to the corresponding line number in the built output.
Converts a line number from the built output to the corresponding file and line number in the source.
Convert any breakpoints on source files to module files and vice-versa.
For best results, you need to organize your module project similarly to how this project is organized. It doesn't have to be exact, because nearly all of our conventions can be overriden, but the module is opinionated, so if you follow the conventions, it should feel wonderfully automatic.
- Create a
source
folder with abuild.psd1
file and your module manifest in it - In the
build.psd1
specify the relative Path to your module's manifest, e.g.@{ Path = "ModuleBuilder.psd1" }
- In your manifest, make sure a few values are not commented out. You can leave them empty, because they'll be overwritten:
FunctionsToExport
will be updated with the file names that match thePublicFilter
AliasesToExport
will be updated with the values from[Alias()]
attributes on commandsPrerelease
andReleaseNotes
in thePSData
hash table inPrivateData
Once you start working on the module, you'll create sub-folders in source, and put script files in them with only one function in each file. You should name the files with the same name as the function that's in them -- especially in the public folder, where we use the file name (without the extension) to determine the exported functions.
- By convention, use folders named "Classes" (and/or "Enum"), "Private", and "Public"
- By convention, the functions in "Public" will be exported from the module (you can override the
PublicFilter
) - To force classes to be in a certain order, you can prefix their file names with numbers, like
01-User.ps1
There are a lot of conventions in Build-Module
, expressed as default values for its parameters. These defaults are documented in the help for Build-Module. You can override any parameter defaults by adding keys to the build.psd1
file with your preferences, or by passing the values to the Build-Module
command directly.
There are several PowerShell build frameworks available. The build task in ModuleBuilder doesn't particularly endorse or interoperate with any of them, but it does accomplish a particular task that is needed by all of them.
A good build framework needs to support incremental builds and have a way to define build targets which have dependencies on other targets, such that it can infer the target build order.
A good build framework should also include pre-defined tasks for most common build targets, including restoring dependencies, cleaning old output, building and assembling a module from source, testing that module, and publishing the module for public consumption. Our Build-Module
command, for instance, is just one task of several which would be needed for a build target for a PowerShell script module.
We are currently using the Invoke-Build and earthly to build this module.
The easiest, fastest build uses earthly. Earthly builds use containers to ensure tools are available, parallelize steps, and to cache their output. On Windows, it requires WSL2, Docker Desktop, and of course, the earthly CLI. If you already have those installed, you can just check out this repository and run earthly +test
to build and run the tests.
git clone https://github.com/PoshCode/ModuleBuilder.git
cd ModuleBuilder
earthly +test
The full ModuleBuilder build has a lot of dependencies which are handled for you, in the Earthly build, like dotnet, gitversion, and several PowerShell modules. To build without it you will need to clone the PoshCode shared "Tasks" repository which contains shared Invoke-Build tasks into the same parent folder, so that the Tasks
folder is a sibling of the ModuleBuilder
folder:
git clone https://github.com/PoshCode/ModuleBuilder.git
git clone https://github.com/PoshCode/Tasks.git
Once you've cloned both, the Build.build.ps1
script will use the shared Tasks_Bootstrap.ps1 to install the other dependencies (see RequiredModules.psd1), including dotnet, and will use Invoke-Build and Pester to build and test the module.
cd ModuleBuilder
./Build.build.ps1
This should work on Windows, Linux, or MacOS. I test the build process on Windows, and in CI we run it in the Linux containers via earthly, and we run the full Pester test suit on all three platforms.
You can build the module without any additional tools (and without running tests), by using the old build.ps1
bootstrap script. You'll need to pass a version number in, and if you have Pester and PSScriptAnalyzer, you can run the 'test.ps1' script to run the tests.
./build.ps1 -Semver 5.0.0-prerelease | Split-Path | Import-Module -Force
./test.ps1
Starting with this release, ModuleBuilder will automatically export aliases from New-Alias
and Set-Alias
as well as the [Alias()]
attributes on commands. This is (probably not) a breaking change, but because it can change the aliases exported by existing modules that use ModuleBuilder, I've bumped the major version number as a precaution (if you're reading this, mission accomplished).
Additionally, the Build-Module
command now explicitly sorts the source files into alphabetical order, to ensure consistent behavior regardless of the native order of the underlying file system. This is technically also a breaking change, but it's unlikely to affect anyone except the people whose builds didn't work on non-Windows systems because of the previous behavior.
Starting with this release, ModuleBuilder adds an empty line between the #REGION filename
comment lines it injects, and the content of the files. This allows PowerShell to recognize help comments that are at the top of each file (outside the function block).