Exit Codes
Understanding exit codes is crucial for integrating psake with CI/CD systems and handling build failures correctly.
Overview
psake returns exit codes to indicate the success or failure of a build:
- Exit code 0: Build succeeded
- Exit code 1: Build failed
The exit code is set automatically based on whether all tasks completed successfully or if any errors occurred during execution.
Exit Code Behavior
Success (Exit Code 0)
psake returns exit code 0 when:
- All specified tasks complete without errors
- All task dependencies run successfully
- No
execcommands fail (unless using-continueOnError) - No assertions fail
- No unhandled exceptions are thrown
- All postconditions pass
# Successful build
Invoke-psake -taskList Build
# Check exit code
echo $LASTEXITCODE # Output: 0
Failure (Exit Code 1)
psake returns exit code 1 when:
- Any task throws an unhandled exception
- Any
execcommand fails (returns non-zero exit code) - Any assertion fails
- Any postcondition fails
- Required variables are missing
- Circular task dependencies are detected
- Build script has syntax errors
# Failed build
Invoke-psake -taskList Deploy
# Check exit code
echo $LASTEXITCODE # Output: 1
Checking Exit Codes
In PowerShell
# Method 1: Check $LASTEXITCODE
Invoke-psake -buildFile .\psakefile.ps1 -taskList Build
if ($LASTEXITCODE -ne 0) {
Write-Host "Build failed with exit code: $LASTEXITCODE" -ForegroundColor Red
exit $LASTEXITCODE
} else {
Write-Host "Build succeeded" -ForegroundColor Green
}
# Method 2: Use try-catch
try {
Invoke-psake -buildFile .\psakefile.ps1 -taskList Build
if ($LASTEXITCODE -ne 0) {
throw "Build failed"
}
}
catch {
Write-Error "Build error: $_"
exit 1
}
# Method 3: Use ErrorAction
Invoke-psake -buildFile .\psakefile.ps1 -taskList Build -ErrorAction Stop
# Execution stops here if build fails
Write-Host "Build succeeded"
In Bash/Shell Scripts
#!/bin/bash
# Run psake build
pwsh -Command "Invoke-psake -buildFile ./psakefile.ps1 -taskList Build"
# Check exit code
if [ $? -eq 0 ]; then
echo "Build succeeded"
else
echo "Build failed with exit code: $?"
exit 1
fi
# Using set -e (exit on error)
#!/bin/bash
set -e # Exit immediately if any command fails
pwsh -Command "Invoke-psake -taskList Build"
echo "Build succeeded"
In Batch Files (Windows)
@echo off
REM Run psake build
pwsh -Command "Invoke-psake -taskList Build"
REM Check exit code
if %ERRORLEVEL% neq 0 (
echo Build failed with exit code: %ERRORLEVEL%
exit /b %ERRORLEVEL%
)
echo Build succeeded
CI/CD Integration
GitHub Actions
name: Build
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install psake
shell: pwsh
run: Install-Module -Name psake -Scope CurrentUser -Force
- name: Run build
shell: pwsh
run: |
Invoke-psake -taskList Build
if ($LASTEXITCODE -ne 0) {
throw "Build failed"
}
# Alternative: Let GitHub Actions detect failure automatically
- name: Run build (simpler)
shell: pwsh
run: |
Invoke-psake -taskList Build
exit $LASTEXITCODE
Note: GitHub Actions automatically fails the job if any step returns a non-zero exit code.
Azure Pipelines
trigger:
- main
pool:
vmImage: 'ubuntu-latest'
steps:
- pwsh: |
Install-Module -Name psake -Scope CurrentUser -Force
displayName: 'Install psake'
- pwsh: |
Invoke-psake -taskList Build
if ($LASTEXITCODE -ne 0) {
Write-Error "Build failed"
exit 1
}
displayName: 'Run build'
# Alternative: Use failOnStderr
- pwsh: |
Invoke-psake -taskList Build
exit $LASTEXITCODE
displayName: 'Run build'
failOnStderr: false
GitLab CI
build:
image: mcr.microsoft.com/powershell:latest
script:
- pwsh -Command "Install-Module -Name psake -Force"
- pwsh -Command "Invoke-psake -taskList Build; exit $LASTEXITCODE"
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == "main"
Note: GitLab CI fails the job if the script returns non-zero.
Jenkins
pipeline {
agent any
stages {
stage('Build') {
steps {
pwsh '''
Install-Module -Name psake -Scope CurrentUser -Force
Invoke-psake -taskList Build
if ($LASTEXITCODE -ne 0) {
throw "Build failed with exit code: $LASTEXITCODE"
}
'''
}
}
}
post {
failure {
echo 'Build failed!'
}
success {
echo 'Build succeeded!'
}
}
}
TeamCity
<!-- Build Step: PowerShell -->
<buildStep type="powershell">
<param name="script.content">
Install-Module -Name psake -Scope CurrentUser -Force
Invoke-psake -taskList Build
if ($LASTEXITCODE -ne 0) {
Write-Error "Build failed"
exit 1
}
</param>
<param name="teamcity.step.mode">default</param>
</buildStep>
Common Exit Code Scenarios
Scenario 1: External Command Fails
Task Build {
# dotnet build fails (returns exit code 1)
exec { dotnet build }
}
# Result: psake returns exit code 1
# Reason: exec detected non-zero exit code from dotnet
CI/CD Impact: Build job fails automatically.
Scenario 2: Assertion Fails
Properties {
$Version = ''
}
Task Deploy {
Assert (![string]::IsNullOrEmpty($Version)) "Version is required"
exec { ./deploy.ps1 -Version $Version }
}
# Result: psake returns exit code 1
# Reason: Assertion failed (Version is empty)
CI/CD Impact: Build job fails with clear error message.
Scenario 3: Postcondition Fails
Task Build -postcondition { Test-Path './build/app.dll' } {
exec { dotnet build -o ./build }
}
# If app.dll is not created:
# Result: psake returns exit code 1
# Reason: Postcondition failed
CI/CD Impact: Build job fails after task completes.
Scenario 4: Task with ContinueOnError
Task Test -continueOnError {
exec { dotnet test }
# Even if tests fail, build continues
}
Task Package -depends Test {
exec { dotnet pack }
}
# Result: psake returns exit code 0 (if Package succeeds)
# Reason: Test failures were ignored due to -continueOnError
Warning: Use -continueOnError carefully in CI/CD. Failed tests may not fail the build.
Scenario 5: Multiple Tasks, One Fails
Invoke-psake -taskList Clean, Build, Test
# If Build fails:
# - Clean runs successfully
# - Build fails (exit code 1)
# - Test doesn't run (dependency failed)
# Result: psake returns exit code 1
CI/CD Impact: Build job stops at first failure.
Exit Code Best Practices
1. Always Check Exit Codes in CI/CD
# BAD: Doesn't check exit code
Invoke-psake -taskList Build
echo "Build complete"
# GOOD: Checks exit code
Invoke-psake -taskList Build
if ($LASTEXITCODE -ne 0) {
throw "Build failed"
}
echo "Build succeeded"
2. Use Exec for External Commands
# BAD: Doesn't detect failures
Task Build {
dotnet build
# Build continues even if dotnet fails
}
# GOOD: Automatically detects failures
Task Build {
exec { dotnet build }
# Build fails if dotnet fails
}
3. Fail Fast with Assertions
# Validate prerequisites before expensive operations
Task Deploy {
Assert (![string]::IsNullOrEmpty($ApiKey)) "API_KEY is required"
Assert (Test-Path $ArtifactPath) "Build artifacts not found"
Assert ($Environment -in @('Staging', 'Production')) "Invalid environment"
# Expensive deployment only runs if assertions pass
exec { ./deploy.ps1 }
}
4. Provide Meaningful Error Messages
# BAD: Generic error
Task Build {
exec { dotnet build }
}
# GOOD: Clear error message
Task Build {
exec { dotnet build } -errorMessage "Failed to compile solution. Check for syntax errors."
}
5. Don't Swallow Errors
# BAD: Hides failures
Task Test {
try {
exec { dotnet test }
}
catch {
Write-Warning "Tests failed, but continuing..."
# Build succeeds even though tests failed!
}
}
# GOOD: Proper error handling
Task Test {
try {
exec { dotnet test }
}
catch {
Write-Error "Tests failed: $_"
throw # Re-throw to fail the build
}
}
6. Use Exit Codes in Wrapper Scripts
# build.ps1 - Wrapper script for psake
param(
[string]$Task = 'Default',
[hashtable]$Parameters = @{}
)
# Install psake if needed
if (-not (Get-Module psake -ListAvailable)) {
Install-Module psake -Scope CurrentUser -Force
}
Import-Module psake
# Run build
Invoke-psake -buildFile ./psakefile.ps1 -taskList $Task -parameters $Parameters
# Propagate exit code
exit $LASTEXITCODE
7. Handle Exit Codes in Multi-Stage Builds
# build.ps1 - Multi-stage build script
$tasks = @('Clean', 'Restore', 'Build', 'Test', 'Package')
foreach ($task in $tasks) {
Write-Host "Running task: $task" -ForegroundColor Cyan
Invoke-psake -taskList $task
if ($LASTEXITCODE -ne 0) {
Write-Error "Task '$task' failed with exit code $LASTEXITCODE"
exit $LASTEXITCODE
}
}
Write-Host "All tasks completed successfully" -ForegroundColor Green
Troubleshooting Exit Codes
Problem: Build Succeeds Locally but Fails in CI
Possible causes:
- Environment variables missing in CI
- Different PowerShell version in CI
- Missing dependencies in CI environment
- Case-sensitive file paths (Linux CI)
Solution:
Task ValidateEnvironment {
Assert ($env:API_KEY) "API_KEY environment variable is required"
Assert ($PSVersionTable.PSVersion.Major -ge 7) "PowerShell 7+ required"
Assert (Get-Command dotnet -ErrorAction SilentlyContinue) "dotnet CLI not found"
}
Task Build -depends ValidateEnvironment {
exec { dotnet build }
}
Problem: Exit Code is 0 but Build Should Fail
Possible causes:
- Not using
execfor external commands - Using
-continueOnErrorinappropriately - Catching exceptions without re-throwing
Solution:
# Always use exec
Task Build {
exec { dotnet build } # Fails if dotnet fails
}
# Don't catch without re-throwing
Task Test {
try {
exec { dotnet test }
}
catch {
Write-Error "Tests failed: $_"
throw # Re-throw to fail build
}
}
Problem: Can't Determine Why Build Failed
Solution: Enable verbose output and check error details:
# Verbose output
Invoke-psake -Verbose
# Capture detailed errors
$ErrorActionPreference = 'Stop'
try {
Invoke-psake -taskList Build
}
catch {
Write-Host "Build failed with error:" -ForegroundColor Red
Write-Host $_.Exception.Message -ForegroundColor Red
Write-Host $_.ScriptStackTrace -ForegroundColor Gray
exit 1
}
Exit Codes in Docker
FROM mcr.microsoft.com/powershell:latest
WORKDIR /build
# Install psake
RUN pwsh -Command "Install-Module -Name psake -Scope CurrentUser -Force"
# Copy build files
COPY . .
# Run build and propagate exit code
CMD ["pwsh", "-Command", "Invoke-psake -taskList Build; exit $LASTEXITCODE"]
Verify in Docker:
# Build Docker image
docker build -t myapp-build .
# Run build
docker run --rm myapp-build
# Check exit code
echo $? # 0 for success, 1 for failure
See Also
- Glossary - Exit code and error handling terms
- Configuration Reference - exec and assert functions
- Common Errors - Troubleshooting build failures
- GitHub Actions Integration - CI/CD exit code handling
- Error Handling - Advanced error handling patterns