There is no shortage of modules in the PowerShell Gallery for building modules. In fact, there are at least a dozen actively managed modules capable of building complex modules. So, you might ask, why choose ModuleTools and what makes it special? Allow me to explain.

Building modules doesn’t have to be complex or an all-or-nothing process. While I have drawn a lot of inspiration from InvokeBuild and the Sampler module builder, they are overly complex and have a steep learning curve. They introduce their own assert, exec, and requires syntax that you need to learn. They depend on several other Modules (like psake, plaster).

Introducing ModuleTools

ModuleTools employs a straightforward structure and a single data file (project.json) to manage the entire module development process. It allows you to build, run Pester tests, or perform semantic version upgrades with ease. You can customize its usage by selecting only the tasks you need, such as invoking builds while skipping tests. Additionally, the same commands work seamlessly for both local and pipeline builds.

It is entirely self-contained, eliminating the need for scaffolding tools (like Plaster), build tools (like Psake), or the maintenance of non-functional helper files (like psd1 and build.ps1).

Find the source code for SecretBackup module on GitHub which will be used for reference in below examples.

Why Build at all?

If you have built module yourself or know what you are doing, skip directly to next section

Unlike other compiled languages, there is no formal concept of a “build” in PowerShell development. However, creating a well-organized and easy-to-manage PowerShell module requires a certain structure and organization. Build tools help achieve this by generating the necessary structure with ease.

The organizational structure is the most compelling reason to have a build mechanism. Instead of cramming hundreds of lines of code into a single psm1 file, it’s more efficient to give each function its own file.

image-20240627001038354

The idea is that each file contains one function, and each function performs a single task. This approach makes files and functions easier to manage and control through versioning.

Getting Started with ModuleTools

New-MTModule is all you need to set up your project. In fewer than six interactive steps, you’ll be ready to start writing beautiful modules. Below is a screenshot of setting up a test module project.

image-20240627195958808

Project JSON file

This file contains key information about the project, including everything from the module’s GUID to the Pester configuration. It is created automatically with New-MTModule, or you can copy a sample file from here.

{
  "ProjectName": "ModuleName",
  "Description": "Description of Module",
  "Version": "0.0.1", 
  "Manifest": {
    "Author": "Manjunath Beli",
    "PowerShellHostVersion": "7.4",
    "GUID": "Use New-Guid to generate guid",
    "Tags": [],
    "ProjecUri": ""
  }
}

Folder Structure

If you used the New-MTModule command the folder structure is already setup for you. If you like doing it manually, these are important folders

image-20240627200142349

Each folder serves a purpose

FolderPurpose
Root\project.jsonThis is main project info file in json format
Src\privateFunctions required for module but not exposed to user
Src\publicFunctions that are exposed in module
Src\resourcesAny file you want to ship with module (like config.ini image.jpg)
testsall the pester tests
distauto generated, the output of ModuleTools invoke build command

For instance, this is folder structure in one of my module SecretBackup

image-20240627200758859

PowerShell Functions

I like to break my functions into simple, one-task-only scripts. The name of the function doesn’t matter, but each file should contain only one top level function. Everything in the public folder gets exported as module commands, while everything in the private folder is accessible within the module but not exposed to the user.

Sample Function

# File SayHello.ps1
function Write-Greetings {
	Write-Host "Hello Stranger!"
}

Name of the file is not important, ensure the content of file has one top level function only.

Build

Building a module is as simple as Invoke-MTBuild. Use -Verbose if you want to see more details during build. It takes less than a second to build.

image-20240627202304500

Tests

You can also easily run Tests using Invoke-MTTest. Ensure all your tests are in tests folder in root directory of project.

image-20240627202528035

All the pester configuration can be stored in project.json which makes running pester test easy and consistent.

Output

Output files are kept in the dist folder located in the project’s root directory. Each time you run a build, this directory is cleaned up, and the module is built from scratch.

image-20240627202838343

Automation

All configuration details are stored in project.json, which is easy to parse and use in automation. The module also includes the Get-MTProjectInfo command, which provides additional properties of the module, allowing you to easily incorporate them into Pester tests and automation.

All commands are terminating on error, which means you can run them sequentially for easy automation.

# File run.ps1

## Build Module
Invoke-MTBuild

## Perform Tests
Invoke-MTTest

## Publish Module
Publish-PSResource -Repository PrivateOwn -path ./dist/SecretBackup

This simple flow ensures that the module is published (step-3) only if the Build (step-1) and Tests (step-2) run without errors.

Github Actions

Module builds can be easily automated using GitHub Actions since all the necessary build commands are contained within the single ModuleTools module. Simply install the module, import it, run the build, and you’re good to go.

Sample github action flow, using no 3rd party actions, on a ubuntu host.

name: Publish to Packages

# Controls when the workflow will run
on:
  workflow_dispatch:

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install ModuleTools module form PSGallery
        run: Install-PSResource -Repository PSGallery -Name ModuleTools -TrustRepository
        shell: pwsh

      - name: Build Module
        run: Invoke-MTBuild -Verbose
        shell: pwsh

      - name: Run Pester Tests
        run: Invoke-MTTest
        shell: pwsh

      - name: Publish Package to Github
        run: Publish-PSResource -Path ./dist/ModuleTools -Repository PSGallery -ApiKey $Env:ApiKey
        env:
          ApiKey: ${{ secrets.PSGALLERY_API }}
        shell: pwsh

Conclusion

In conclusion, ModuleTools simplifies PowerShell module development by offering a straightforward approach to organizing functions into single-task scripts, managed through a unified project.json configuration.

With ModuleTools, you can easily transform your scattered or lengthy scripts into the well-structured modules you’ve always aimed for.

Got ideas or feedback? Drop them in github issues/discussion! Let’s make building PowerShell modules easier and more awesome together.