CI/CD Integration¶
PSScriptBuilder is a PowerShell module, so integrating it into a CI/CD pipeline follows the same pattern as any other PowerShell-based build tool — no special CI-specific version or plugin is needed, just a PowerShell-capable runner.
A CI environment differs from a developer's own machine in a few important ways: it starts from a fresh, ephemeral runner with nothing pre-installed, runs entirely non-interactively — so anything requiring a manual prompt or confirmation will hang or fail — and communicates success or failure back to the pipeline exclusively through the process exit code, since there's no person watching the console output.
This guide explains what PSScriptBuilder specifically needs to run under these conditions, and illustrates it with a GitHub Actions example. The same principles apply to GitLab CI, Azure DevOps, Jenkins, or any other system that can run PowerShell — only the surrounding configuration syntax changes.
What PSScriptBuilder Needs in CI¶
Integrating PSScriptBuilder into CI splits cleanly into two concerns: your CI configuration and your build script. Keeping these separate — rather than inlining every PowerShell command directly into the CI configuration file — is what makes the setup portable across CI systems and testable on a developer's own machine before ever touching CI at all.
In Your CI Configuration¶
- Install the module — PSScriptBuilder must be available in the PowerShell module path. A fresh CI runner has nothing pre-installed, so this step runs on every single job.
- Run the build script — invoke your
Build-MyScript.ps1(or equivalent). The CI configuration's only remaining job is to call this one script; everything else happens inside it.
Inside the Build Script¶
- Set the project root — call
Set-PSScriptBuilderProjectRootat the start of your build script; PSScriptBuilder resolves all relative paths from this root. Calling it inside the build script, rather than passing paths explicitly from the CI configuration, keeps CI configuration minimal and the build locally reproducible. - Run the build — collect your project's content and call
Invoke-PSScriptBuilderBuildto produce the output file. - Handle the result — the build throws a typed exception on failure, so a non-zero exit
code alone is sufficient for most CI systems to detect a failed job. For more granular
control, wrap the call in
try/catch— see Handling the Build Result.
Avoid platform-specific escaping issues
Complex PowerShell logic inlined directly into YAML (or another CI config format) is
prone to quoting and escaping bugs that only surface once the pipeline actually runs.
Keeping build logic in its own .ps1 file avoids this entirely.
Example: GitHub Actions¶
The following workflow illustrates the two CI configuration steps from
In Your CI Configuration using GitHub Actions. The workflow runs
on every push and pull request, installs PSScriptBuilder from the PowerShell Gallery, and
delegates the actual build logic to Build-MyScript.ps1. Keeping the build logic in a
script — rather than inline YAML — means the same script works locally and in CI without
any changes.
Adapt the YAML syntax to your CI/CD system. The PowerShell commands inside the steps remain the same regardless of the platform:
name: Build
on: [push, pull_request]
jobs:
build:
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- name: Install PSScriptBuilder
shell: pwsh
run: Install-Module PSScriptBuilder -Force -SkipPublisherCheck
- name: Build
shell: pwsh
run: .\Build-MyScript.ps1
Handling the Build Result¶
A build that fails in CI should fail loudly and stop the pipeline immediately —
Invoke-PSScriptBuilderBuild does exactly that, throwing a typed exception on any failure
(circular dependencies, missing files, name conflicts, syntax errors), which results in a
non-zero PowerShell exit code. Most CI systems treat this as a job failure automatically, so
no additional error handling is strictly required.
That said, wrapping the build in a try/catch block has two advantages in a CI context:
the error message is written explicitly to the error stream (making it easier to spot in
CI logs), and you have a single place to add cleanup logic or notifications if needed. This is
what Build-MyScript.ps1 — the script called from
Example: GitHub Actions — typically contains, covering all three
steps from Inside the Build Script:
Set-PSScriptBuilderProjectRoot -Path $PSScriptRoot
try {
$result = New-PSScriptBuilderContentCollector |
Add-PSScriptBuilderCollector -Type Class -IncludePath 'src\Classes' |
Add-PSScriptBuilderCollector -Type Function -IncludePath 'src\Public' |
Invoke-PSScriptBuilderBuild `
-TemplatePath 'build\Templates\MyScript.ps1.template' `
-OutputPath 'build\Output\MyScript.ps1'
$result | Format-PSScriptBuilderBuildResult
}
catch {
Write-Error "Build failed: $($_.Exception.Message)"
exit 1
}
Piping to
Format-PSScriptBuilderBuildResult doesn't
need a live audience — its output becomes part of the CI job's own log, which is exactly where
a developer looks first when investigating a failed or unexpectedly slow build afterward.
Exporting the Build Report¶
Most CI systems can archive files as build artifacts — exporting the build summary this way
keeps it available for later inspection without digging through CI logs. Extending the same
script from Handling the Build Result, pipe the result to
Export-PSScriptBuilderBuildResult right
after formatting it:
Set-PSScriptBuilderProjectRoot -Path $PSScriptRoot
try {
$result = New-PSScriptBuilderContentCollector |
Add-PSScriptBuilderCollector -Type Class -IncludePath 'src\Classes' |
Add-PSScriptBuilderCollector -Type Function -IncludePath 'src\Public' |
Invoke-PSScriptBuilderBuild `
-TemplatePath 'build\Templates\MyScript.ps1.template' `
-OutputPath 'build\Output\MyScript.ps1'
$result | Format-PSScriptBuilderBuildResult
$result | Export-PSScriptBuilderBuildResult -Path 'build\reports\build.json' -Detailed -Force
}
catch {
Write-Error "Build failed: $($_.Exception.Message)"
exit 1
}
See Build Pipeline: Exporting the Build Report
for the full JSON structure and the effect of -Detailed.
Compressing Before Deploy¶
A CI pipeline is a natural place to strip comments, blank lines, and diagnostic output before
publishing — the deployed script never needs them. Extending the same script from
Exporting the Build Report, pass the build's output to
Compress-PSScriptBuilderScript right after
exporting the report:
Set-PSScriptBuilderProjectRoot -Path $PSScriptRoot
try {
$result = New-PSScriptBuilderContentCollector |
Add-PSScriptBuilderCollector -Type Class -IncludePath 'src\Classes' |
Add-PSScriptBuilderCollector -Type Function -IncludePath 'src\Public' |
Invoke-PSScriptBuilderBuild `
-TemplatePath 'build\Templates\MyScript.ps1.template' `
-OutputPath 'build\Output\MyScript.ps1'
$result | Format-PSScriptBuilderBuildResult
$result | Export-PSScriptBuilderBuildResult -Path 'build\reports\build.json' -Detailed -Force
Compress-PSScriptBuilderScript `
-Path $result.OutputPath `
-RemoveComments `
-RemoveBlankLines `
-DestinationPath 'deploy\MyScript.ps1' `
-Force
}
catch {
Write-Error "Build failed: $($_.Exception.Message)"
exit 1
}
See Build Pipeline: Post-Processing for what each operation removes, how they combine, and how to get the result as a string instead of writing a file.