GitHub Actions
GitHub Actions is a popular CI/CD platform that integrates seamlessly with GitHub repositories. This guide shows you how to run psake builds in GitHub Actions workflows, including cross-platform matrix builds, secret management, and artifact publishing.
Quick Start
Here's a basic GitHub Actions workflow that runs a psake build:
name: Build
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
jobs:
build:
runs-on: windows-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Run psake build
shell: pwsh
run: |
Install-Module -Name psake -Scope CurrentUser -Force
Invoke-psake -buildFile .\psakefile.ps1 -taskList Build
Installing psake in GitHub Actions
There are several approaches to installing psake in your workflow:
Option 1: Install from PowerShell Gallery (Recommended)
- name: Install psake
shell: pwsh
run: Install-Module -Name psake -Scope CurrentUser -Force
This is the simplest approach and works on all platforms (Windows, Linux, macOS).
Option 2: Install Specific Version
- name: Install psake 4.9.0
shell: pwsh
run: Install-Module -Name psake -RequiredVersion 4.9.0 -Scope CurrentUser -Force
Option 3: Using requirements.psd1 with PSDepend
If your project uses a requirements.psd1 file:
- name: Install dependencies
shell: pwsh
run: |
Install-Module -Name PSDepend -Scope CurrentUser -Force
Invoke-PSDepend -Path ./requirements.psd1 -Install -Force
Your requirements.psd1:
@{
psake = @{
Version = '4.9.0'
}
# Other dependencies...
}
Option 4: Cache psake Installation
To speed up builds, cache the PowerShell modules directory:
- name: Cache PowerShell modules
uses: actions/cache@v4
with:
path: |
~/.local/share/powershell/Modules
~/Documents/PowerShell/Modules
key: ${{ runner.os }}-psake-${{ hashFiles('**/requirements.psd1') }}
restore-keys: |
${{ runner.os }}-psake-
- name: Install psake
shell: pwsh
run: |
if (-not (Get-Module -ListAvailable -Name psake)) {
Install-Module -Name psake -Scope CurrentUser -Force
}
Complete Workflow Example
Here's a comprehensive workflow demonstrating psake integration:
name: psake Build
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
workflow_dispatch:
env:
BUILD_CONFIGURATION: Release
jobs:
build:
runs-on: windows-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0 # Full history for versioning
- name: Setup PowerShell modules cache
uses: actions/cache@v4
with:
path: ~/.local/share/powershell/Modules
key: ${{ runner.os }}-psake-modules-${{ hashFiles('**/requirements.psd1') }}
- name: Install psake and dependencies
shell: pwsh
run: |
Set-PSRepository -Name PSGallery -InstallationPolicy Trusted
Install-Module -Name psake -Scope CurrentUser -Force
Install-Module -Name PSDepend -Scope CurrentUser -Force
if (Test-Path ./requirements.psd1) {
Invoke-PSDepend -Path ./requirements.psd1 -Install -Force
}
- name: Run psake build
shell: pwsh
run: |
Invoke-psake -buildFile .\psakefile.ps1 `
-taskList Build, Test `
-parameters @{
Configuration = $env:BUILD_CONFIGURATION
BuildNumber = $env:GITHUB_RUN_NUMBER
}
env:
# Pass secrets as environment variables
NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }}
- name: Check build result
shell: pwsh
run: |
if (-not $?) {
Write-Error "psake build failed"
exit 1
}
- name: Upload build artifacts
uses: actions/upload-artifact@v4
if: success()
with:
name: build-output
path: |
./build/
./dist/
retention-days: 7
- name: Upload test results
uses: actions/upload-artifact@v4
if: always()
with:
name: test-results
path: ./TestResults/
retention-days: 30
Cross-Platform Matrix Builds
psake works on Windows, Linux, and macOS. Use matrix builds to test across platforms:
name: Cross-Platform Build
on: [push, pull_request]
jobs:
build:
strategy:
fail-fast: false
matrix:
os: [windows-latest, ubuntu-latest, macos-latest]
powershell-version: ['7.2', '7.4']
include:
- os: windows-latest
powershell-version: '5.1'
runs-on: ${{ matrix.os }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup PowerShell ${{ matrix.powershell-version }}
if: matrix.powershell-version != '5.1'
uses: PowerShell/setup-powershell@v1
with:
powershell-version: ${{ matrix.powershell-version }}
- name: Install psake
shell: pwsh
run: Install-Module -Name psake -Scope CurrentUser -Force
- name: Display environment info
shell: pwsh
run: |
Write-Host "OS: ${{ matrix.os }}"
Write-Host "PowerShell Version: $($PSVersionTable.PSVersion)"
Write-Host "psake Version: $((Get-Module -ListAvailable psake).Version)"
- name: Run psake build
shell: pwsh
run: |
Invoke-psake -buildFile .\psakefile.ps1 -taskList Build, Test
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: build-${{ matrix.os }}-ps${{ matrix.powershell-version }}
path: ./build/
Secret Management
GitHub Actions provides a secure way to store and use secrets in your workflows.
Setting Up Secrets
- Go to your repository Settings → Secrets and variables → Actions
- Click New repository secret
- Add secrets like
NUGET_API_KEY,SIGNING_CERT_PASSWORD, etc.
Using Secrets in psake
Method 1: Environment Variables
- name: Run psake with secrets
shell: pwsh
run: Invoke-psake -buildFile .\psakefile.ps1 -taskList Deploy
env:
NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }}
AZURE_CONNECTION_STRING: ${{ secrets.AZURE_CONNECTION_STRING }}
In your psakefile.ps1:
Task Deploy {
$apiKey = $env:NUGET_API_KEY
if ([string]::IsNullOrEmpty($apiKey)) {
throw "NUGET_API_KEY environment variable is not set"
}
# Use the API key
dotnet nuget push "*.nupkg" --api-key $apiKey --source https://api.nuget.org/v3/index.json
}
Method 2: Parameters
- name: Run psake with secrets
shell: pwsh
run: |
Invoke-psake -buildFile .\psakefile.ps1 `
-taskList Deploy `
-parameters @{
NuGetApiKey = $env:NUGET_API_KEY
Environment = 'Production'
}
env:
NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }}
In your psakefile.ps1:
Properties {
$NuGetApiKey = $null
$Environment = 'Development'
}
Task Deploy -precondition { $Environment -eq 'Production' } {
if ([string]::IsNullOrEmpty($NuGetApiKey)) {
throw "NuGetApiKey parameter is required for deployment"
}
dotnet nuget push "*.nupkg" --api-key $NuGetApiKey
}
Security Best Practices
- Never hardcode secrets in your psakefile.ps1 or commit them to source control
- Use environment-specific secrets for different deployment targets
- Limit secret access to specific environments or branches using environment protection rules
- Rotate secrets regularly and use GitHub's secret scanning to detect exposed secrets
Artifact Publishing
Publishing Build Artifacts
- name: Build with psake
shell: pwsh
run: Invoke-psake -buildFile .\psakefile.ps1 -taskList Build
- name: Upload build artifacts
uses: actions/upload-artifact@v4
with:
name: build-output-${{ github.run_number }}
path: |
./build/**/*.dll
./build/**/*.exe
./build/**/*.nupkg
if-no-files-found: error
retention-days: 7
Publishing NuGet Packages
- name: Build and pack
shell: pwsh
run: Invoke-psake -buildFile .\psakefile.ps1 -taskList Build, Pack
- name: Publish to NuGet
if: github.ref == 'refs/heads/main'
shell: pwsh
run: |
Invoke-psake -buildFile .\psakefile.ps1 `
-taskList Publish `
-parameters @{ NuGetApiKey = $env:NUGET_API_KEY }
env:
NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }}
Publishing to GitHub Releases
- name: Build release
shell: pwsh
run: |
Invoke-psake -buildFile .\psakefile.ps1 `
-taskList Build `
-parameters @{
Configuration = 'Release'
Version = $env:GITHUB_REF_NAME
}
- name: Create GitHub Release
if: startsWith(github.ref, 'refs/tags/')
uses: softprops/action-gh-release@v1
with:
files: |
./build/*.zip
./build/*.nupkg
generate_release_notes: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Example Repository Structure
Here's a recommended repository structure for using psake with GitHub Actions:
my-project/
├── .github/
│ └── workflows/
│ ├── build.yml # Main build workflow
│ ├── release.yml # Release workflow
│ └── pr-validation.yml # Pull request checks
├── src/
│ └── MyProject/
│ └── MyProject.csproj
├── tests/
│ └── MyProject.Tests/
│ └── MyProject.Tests.csproj
├── build/ # Build output directory
├── psakefile.ps1 # Main build script
├── requirements.psd1 # PowerShell dependencies
└── README.md
psakefile.ps1 example:
Properties {
$Configuration = 'Debug'
$BuildNumber = '0'
$Version = "1.0.$BuildNumber"
$SrcDir = Join-Path $PSScriptRoot 'src'
$TestDir = Join-Path $PSScriptRoot 'tests'
$BuildDir = Join-Path $PSScriptRoot 'build'
}
Task Default -depends Build, Test
Task Clean {
if (Test-Path $BuildDir) {
Remove-Item $BuildDir -Recurse -Force
}
New-Item -ItemType Directory -Path $BuildDir | Out-Null
}
Task Build -depends Clean {
exec { dotnet build $SrcDir -c $Configuration -o $BuildDir /p:Version=$Version }
}
Task Test -depends Build {
exec { dotnet test $TestDir -c $Configuration --no-build }
}
Task Pack -depends Build {
exec { dotnet pack $SrcDir -c $Configuration -o $BuildDir /p:Version=$Version }
}
Task Publish -depends Pack {
$apiKey = $env:NUGET_API_KEY
if ([string]::IsNullOrEmpty($apiKey)) {
throw "NUGET_API_KEY environment variable is required"
}
Get-ChildItem "$BuildDir/*.nupkg" | ForEach-Object {
exec { dotnet nuget push $_.FullName --api-key $apiKey --source https://api.nuget.org/v3/index.json }
}
}
Common Troubleshooting
psake Module Not Found
Problem: Import-Module: The specified module 'psake' was not loaded
Solution:
- name: Install psake with verbose output
shell: pwsh
run: |
Install-Module -Name psake -Scope CurrentUser -Force -Verbose
Get-Module -ListAvailable psake
PowerShell Execution Policy Issues
Problem: Scripts fail due to execution policy on Windows runners
Solution: Use pwsh shell (PowerShell Core) instead of powershell (Windows PowerShell):
- name: Run psake
shell: pwsh # Not 'powershell'
run: Invoke-psake
Build Failures Not Failing the Workflow
Problem: psake build fails but workflow shows success
Solution: Ensure psake errors exit with non-zero code:
# In your psakefile.ps1
FormatTaskName {
param($taskName)
Write-Host "Executing task: $taskName" -ForegroundColor Cyan
}
# Use exec for external commands
Task Build {
exec { dotnet build } # Will fail the build on non-zero exit code
}
Or check the result explicitly:
- name: Run psake build
shell: pwsh
run: |
Invoke-psake -buildFile .\psakefile.ps1
if (-not $?) {
exit 1
}
Path Issues on Cross-Platform Builds
Problem: Windows paths don't work on Linux/macOS
Solution: Use PowerShell's cross-platform path cmdlets:
Properties {
# Instead of: $BuildDir = "$PSScriptRoot\build"
$BuildDir = Join-Path $PSScriptRoot 'build'
# Instead of: $OutputPath = ".\build\bin"
$OutputPath = Join-Path (Join-Path $PSScriptRoot 'build') 'bin'
}
Module Caching Not Working
Problem: Cache doesn't restore modules correctly
Solution: Use platform-specific cache paths:
- name: Cache PowerShell modules
uses: actions/cache@v4
with:
path: |
${{ runner.os == 'Windows' && '~\Documents\PowerShell\Modules' || '~/.local/share/powershell/Modules' }}
key: ${{ runner.os }}-psake-${{ hashFiles('**/requirements.psd1') }}
Secrets Not Available in Forked PRs
Problem: Workflows from forked pull requests can't access secrets
Solution: This is a security feature. For PRs from forks:
- Use
pull_request_targetevent (be cautious - review code first) - Or manually trigger workflows after review
- Or skip deployment tasks for untrusted PRs
- name: Deploy
if: github.event.pull_request.head.repo.full_name == github.repository
shell: pwsh
run: Invoke-psake -taskList Deploy
Advanced Patterns
Conditional Task Execution Based on Changed Files
- name: Detect changes
id: changes
uses: dorny/paths-filter@v3
with:
filters: |
src:
- 'src/**'
docs:
- 'docs/**'
- name: Build code
if: steps.changes.outputs.src == 'true'
shell: pwsh
run: Invoke-psake -taskList Build
- name: Build docs
if: steps.changes.outputs.docs == 'true'
shell: pwsh
run: Invoke-psake -taskList BuildDocs
Reusable Workflows
Create .github/workflows/psake-build.yml as a reusable workflow:
name: Reusable psake Build
on:
workflow_call:
inputs:
task-list:
required: true
type: string
configuration:
required: false
type: string
default: 'Release'
secrets:
nuget-api-key:
required: false
jobs:
build:
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- name: Install psake
shell: pwsh
run: Install-Module -Name psake -Scope CurrentUser -Force
- name: Run psake
shell: pwsh
run: |
Invoke-psake -taskList $env:TASK_LIST `
-parameters @{ Configuration = $env:CONFIGURATION }
env:
TASK_LIST: ${{ inputs.task-list }}
CONFIGURATION: ${{ inputs.configuration }}
NUGET_API_KEY: ${{ secrets.nuget-api-key }}
Use it in other workflows:
name: Main Build
on: [push]
jobs:
build:
uses: ./.github/workflows/psake-build.yml
with:
task-list: 'Build, Test'
configuration: 'Release'
See Also
- Installing psake - Installation guide
- Running psake - Basic usage
- Parameters and Properties - Parameterizing builds
- Team City - TeamCity integration
- .NET Solution Builds - .NET build examples