Skip to content
AgileLogics
5 min read

CI/CD with PowerShell

Prerequisites: Building production modules

PowerShell runs on every CI system — and is the default shell on Windows runners. Use it both to build your PowerShell code and to script pipelines for anything else.

Testing a module in GitHub Actions

# .github/workflows/ci.yml
name: CI
on: [push, pull_request]

jobs:
  test:
    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest]
    runs-on: ${{ matrix.os }}
    steps:
      - uses: actions/checkout@v4
      - name: Lint
        shell: pwsh
        run: |
          Install-Module PSScriptAnalyzer -Force
          $issues = Invoke-ScriptAnalyzer -Path . -Recurse -Severity Warning, Error
          $issues | Format-Table
          if ($issues) { exit 1 }
      - name: Test
        shell: pwsh
        run: |
          Install-Module Pester -Force -SkipPublisherCheck
          $config = New-PesterConfiguration
          $config.Run.Exit = $true
          $config.CodeCoverage.Enabled = $true
          $config.TestResult.Enabled = $true
          Invoke-Pester -Configuration $config
  • The matrix runs the same tests on all three operating systems.
  • shell: pwsh runs the step with PowerShell 7.
  • $config.Run.Exit = $true makes Pester exit with a non-zero code on failure, failing the build.
  • Coverage and test-result files can be uploaded as artifacts or published to the pull request.

Making scripts CI-friendly

Set-Content build.ps1 @'
[CmdletBinding()]
param([ValidateSet('Test', 'Package')][string]$Task = 'Test')
$ErrorActionPreference = 'Stop'

switch ($Task) {
  'Test'    { 'Running tests...'; $failed = 0 }
  'Package' { 'Packaging...'; $failed = 0 }
}
if ($env:CI) { "::notice::$Task finished" }
exit $failed
'@

./build.ps1 -Task Test
"exit: $LASTEXITCODE"
$env:CI = 'true'
./build.ps1 -Task Package
Running tests...
exit: 0
Packaging...
::notice::Package finished
  • One build.ps1 entry point runs the same way locally and in CI.
  • Exit codes tell the CI system whether the step passed.
  • Most CI systems set CI=true; GitHub Actions turns ::notice::, ::warning::, and ::error:: lines into annotations.
  • Build tools like Invoke-Build and psake organize larger builds into tasks with dependencies.

Azure DevOps

Azure DevOps pipelines run PowerShell with the pwsh: | step (or the PowerShell@2 task with pwsh: true), and the AzurePowerShell task signs in to Azure for you using a service connection.