Real-World Example
This page shows a production-ready PowerShell module project that uses PowerShellBuild for its build pipeline. It covers the full lifecycle: local development, automated testing, and publishing to the PowerShell Gallery via GitHub Actions.
Project Structure
MyModule/
├── src/
│ ├── MyModule.psd1
│ ├── MyModule.psm1
│ ├── Public/
│ │ ├── Get-Widget.ps1
│ │ └── New-Widget.ps1
│ └── Private/
│ └── Invoke-WidgetHelper.ps1
├── tests/
│ ├── Get-Widget.Tests.ps1
│ └── New-Widget.Tests.ps1
├── docs/ # PlatyPS markdown (auto-generated by GenerateMarkdown)
├── .github/
│ └── workflows/
│ └── build.yml
├── psakeFile.ps1
├── build.ps1
├── PSScriptAnalyzerSettings.psd1 # Auto-detected by PowerShellBuild
└── requirements.psd1
requirements.psd1
Declare all PowerShell module dependencies. PSDepend installs these during bootstrap.
@{
psake = 'latest'
PowerShellBuild = 'latest'
Pester = 'latest'
PSScriptAnalyzer = 'latest'
platyPS = '0.14.2'
}
build.ps1
The bootstrap entry point that handles dependency installation and task dispatch.
#Requires -Version 5.1
[CmdletBinding()]
param(
[string[]]$Task = 'default',
[switch]$Bootstrap,
[hashtable]$Properties = @{}
)
Set-StrictMode -Version Latest
if ($Bootstrap) {
Write-Host 'Bootstrapping build dependencies...' -ForegroundColor Cyan
Get-PackageProvider -Name NuGet -ForceBootstrap | Out-Null
Set-PSRepository -Name PSGallery -InstallationPolicy Trusted
if (-not (Get-Module -Name PSDepend -ListAvailable)) {
Install-Module -Name PSDepend -Scope CurrentUser -Force
}
Import-Module PSDepend
Invoke-PSDepend -Path "$PSScriptRoot/requirements.psd1" -Install -Import -Force
Write-Host 'Bootstrap complete.' -ForegroundColor Green
}
Import-Module psake -ErrorAction Stop
Invoke-psake `
-buildFile "$PSScriptRoot/psakeFile.ps1" `
-taskList $Task `
-properties $Properties `
-nologo
exit ([int](-not $psake.build_success))
psakeFile.ps1
A realistic build file with customized preferences, custom tasks alongside PowerShellBuild tasks, and a Deploy task that wraps the release workflow.
#Requires -Version 5.1
# --- Task dependency overrides (must be set before referencing PowerShellBuild tasks) ---
# Publish only requires a successful build, not the full test suite in this example.
# Tests are enforced in CI; the Publish task is only invoked from GitHub Actions.
$PSBPublishDependency = 'Build'
# ---
properties {
# General
$PSBPreference.General.SrcRootDir = "$PSScriptRoot/src"
$PSBPreference.General.ModuleName = 'MyModule'
# Build
$PSBPreference.Build.OutDir = "$PSScriptRoot/build"
$PSBPreference.Build.CompileModule = $true
$PSBPreference.Build.CompileDirectories = @('Enum', 'Classes', 'Private', 'Public')
# Test — Pester
$PSBPreference.Test.Enabled = $true
$PSBPreference.Test.RootDir = "$PSScriptRoot/tests"
$PSBPreference.Test.OutputFile = "$PSScriptRoot/build/TestResults.xml"
$PSBPreference.Test.OutputFormat = 'NUnitXml'
$PSBPreference.Test.ImportModule = $true
# Test — PSScriptAnalyzer
# SettingsPath defaults to ./PSScriptAnalyzerSettings.psd1 in the project root
$PSBPreference.Test.ScriptAnalysis.Enabled = $true
$PSBPreference.Test.ScriptAnalysis.FailBuildOnSeverityLevel = 'Error'
# Test — Code Coverage
$PSBPreference.Test.CodeCoverage.Enabled = $true
$PSBPreference.Test.CodeCoverage.Threshold = 0.80
$PSBPreference.Test.CodeCoverage.OutputFile = "$PSScriptRoot/build/coverage.xml"
$PSBPreference.Test.CodeCoverage.OutputFileFormat = 'JaCoCo'
# Help
$PSBPreference.Help.DefaultLocale = 'en-US'
$PSBPreference.Docs.RootDir = "$PSScriptRoot/docs"
# Publish — API key supplied by CI; falls back to env var for local releases
$PSBPreference.Publish.PSRepository = 'PSGallery'
$PSBPreference.Publish.PSRepositoryApiKey = $env:PSGALLERY_API_KEY
}
# ---
# Entry points
# ---
task default -depends Test
# Import PowerShellBuild tasks
task Init -FromModule PowerShellBuild -Version '0.7.1'
task Clean -FromModule PowerShellBuild -Version '0.7.1'
task StageFiles -FromModule PowerShellBuild -Version '0.7.1'
task Build -FromModule PowerShellBuild -Version '0.7.1'
task Analyze -FromModule PowerShellBuild -Version '0.7.1'
task Pester -FromModule PowerShellBuild -Version '0.7.1'
task Test -FromModule PowerShellBuild -Version '0.7.1'
task Publish -FromModule PowerShellBuild -Version '0.7.1'
# ---
# Custom tasks
# ---
task BumpVersion -depends Init {
param([string]$BumpType = 'Patch')
$manifestPath = $PSBPreference.General.ModuleManifestPath
$manifest = Import-PowerShellDataFile $manifestPath
$current = [System.Version]$manifest.ModuleVersion
$next = switch ($BumpType) {
'Major' { [System.Version]::new($current.Major + 1, 0, 0) }
'Minor' { [System.Version]::new($current.Major, $current.Minor + 1, 0) }
'Patch' { [System.Version]::new($current.Major, $current.Minor, $current.Build + 1) }
}
Update-ModuleManifest -Path $manifestPath -ModuleVersion $next.ToString()
Write-Host "Version bumped $current -> $next" -ForegroundColor Green
}
task ValidateReadme -precondition { Test-Path "$PSScriptRoot/README.md" } {
$content = Get-Content "$PSScriptRoot/README.md" -Raw
$requiredSections = @('## Installation', '## Usage', '## Contributing')
foreach ($section in $requiredSections) {
if ($content -notmatch [regex]::Escape($section)) {
throw "README.md is missing section: $section"
}
}
Write-Host 'README.md validation passed.' -ForegroundColor Green
}
# Deploy = bump version + validate docs + publish
task Deploy -depends BumpVersion, ValidateReadme, Publish {
$manifest = Import-PowerShellDataFile $PSBPreference.General.ModuleManifestPath
Write-Host "MyModule v$($manifest.ModuleVersion) deployed to PSGallery." -ForegroundColor Green
}
PSScriptAnalyzerSettings.psd1
PowerShellBuild looks for PSScriptAnalyzerSettings.psd1 in the project root by default. Place your custom rules here and they will be picked up automatically — no need to set SettingsPath.
@{
ExcludeRules = @(
'PSAvoidUsingWriteHost' # Write-Host is acceptable in build scripts
)
Severity = @('Error', 'Warning')
}
GitHub Actions Workflow
This workflow runs the full test suite on every push and pull request, and publishes to PSGallery when a tag is pushed.
name: Build
on:
push:
branches: [main]
tags: ['v*']
pull_request:
branches: [main]
jobs:
test:
name: Test (PS ${{ matrix.ps-version }} on ${{ matrix.os }})
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
ps-version: ['7.4']
steps:
- uses: actions/checkout@v4
- name: Bootstrap dependencies
shell: pwsh
run: .\build.ps1 -Bootstrap
- name: Run tests
shell: pwsh
run: .\build.ps1 -Task Test
- name: Upload test results
if: always()
uses: actions/upload-artifact@v4
with:
name: test-results-${{ matrix.os }}
path: build/TestResults.xml
- name: Upload coverage report
if: always()
uses: actions/upload-artifact@v4
with:
name: coverage-${{ matrix.os }}
path: build/coverage.xml
publish:
name: Publish to PSGallery
needs: test
runs-on: ubuntu-latest
if: startsWith(github.ref, 'refs/tags/v')
steps:
- uses: actions/checkout@v4
- name: Bootstrap dependencies
shell: pwsh
run: .\build.ps1 -Bootstrap
- name: Publish module
shell: pwsh
env:
PSGALLERY_API_KEY: ${{ secrets.PSGALLERY_API_KEY }}
run: .\build.ps1 -Task Publish
Local Development Workflow
# First-time setup
.\build.ps1 -Bootstrap
# Day-to-day: run tests
.\build.ps1
# Check code quality only
.\build.ps1 -Task Analyze
# Rebuild from scratch
.\build.ps1 -Task Clean, Build
# Bump the patch version and publish a release
.\build.ps1 -Task Deploy
See Also
- Getting Started — Minimal project setup
- Task Reference — All available tasks
- Configuration — Full
$PSBPreferencereference - GitHub Actions Integration — General psake CI/CD patterns
- Publishing PowerShell Modules — PSGallery publishing guide