This guide covers the automated CI/CD pipeline, versioning strategy, publishing to PowerShell Gallery, and repository maintenance.
The template includes a comprehensive GitHub Actions workflow that automates quality checks, builds, and releases.
graph TD
A[Setup & Cache] --> B[Unit Tests]
A --> C[Static Analysis]
A --> D[Security Scans]
A --> E[CodeQL Analysis]
B --> F[Build & Publish]
C --> F
D --> F
E --> F
style A fill:#e1f5ff
style B fill:#e8f5e9
style C fill:#e8f5e9
style D fill:#fff3e0
style E fill:#fff3e0
style F fill:#f3e5f5
Pipeline Stages:
- Setup & Cache → Prepares environment and caches dependencies
- Parallel Quality Gates:
- Unit Tests (Pester)
- Static Analysis (PSScriptAnalyzer)
- Security Scans (InjectionHunter)
- CodeQL Analysis
- Build & Publish → Compiles module, generates help, creates releases
- Caches PowerShell modules for faster builds
- Sets up PowerShell 7+ environment
- Installs dependencies via PSDepend
- Runs Pester tests with code coverage
- Generates coverage reports (Cobertura format)
- Uploads test results as artifacts
- Fails build if tests don't pass
- Runs PSScriptAnalyzer on all source files
- Validates code against best practices
- Checks for style violations
- Generates analysis report
InjectionHunter:
- Scans for SQL/Command/Script injection vulnerabilities
- Checks for unsafe string concatenation
- Validates input sanitization
CodeQL:
- Semantic code analysis
- Detects security vulnerabilities
- Runs weekly scheduled scans
- Language: PowerShell
- Compiles module with ModuleBuilder
- Generates help documentation (MAML + Markdown)
- Creates GitHub Release (if on main branch)
- Publishes to PowerShell Gallery (optional)
| Trigger | When | Quality Gates | Publish |
|---|---|---|---|
| Pull Request | PR opened/updated | ✅ All jobs run | ❌ No |
| Push to main | PR merged | ✅ All jobs run | |
| Manual Dispatch | User triggered | ✅ All jobs run | ✅ Optional |
| Schedule | Weekly (CodeQL) | 🔒 Security only | ❌ No |
For documentation-only changes, the workflow intelligently skips unnecessary jobs:
# CI automatically skips tests for:
- *.md files
- docs/** files
- .github/** workflow files (except changes to ci.yml itself)This template uses Semantic Versioning (SemVer) with automated version management:
- Workflow: GitHub Flow (main branch + feature branches)
- Main branch: Production-ready code, every commit can be released
- Feature branches: Development work, PRs required to merge
- Version source: Git history and tags
Major.Minor.Patch-PreReleaseLabel
Examples:
- 1.0.0 (Release)
- 1.2.3-alpha.1 (Prerelease)
- 2.0.0-beta.2 (Prerelease)
Add semantic versioning keywords to commit messages:
| Keyword | Version Change | Example | Use Case |
|---|---|---|---|
+semver: breaking or +semver: major |
1.0.0 → 2.0.0 | Breaking API changes | Major release |
+semver: feature or +semver: minor |
1.0.0 → 1.1.0 | New features (backward compatible) | Minor release |
+semver: fix or +semver: patch |
1.0.0 → 1.0.1 | Bug fixes | Patch release |
+semver: none or +semver: skip |
No change | Documentation updates | No version bump |
# Major version bump (breaking changes)
git commit -m "Remove deprecated Get-OldFunction command +semver: major"
git commit -m "Change parameter types to be more specific +semver: breaking"
# Minor version bump (new features)
git commit -m "Add Get-Something function +semver: minor"
git commit -m "Add optional -Force parameter to Get-Data +semver: feature"
# Patch version bump (bug fixes)
git commit -m "Fix parameter validation in Get-Something +semver: patch"
git commit -m "Correct error handling for edge case +semver: fix"
# No version change
git commit -m "Update README examples +semver: none"
git commit -m "Fix typo in comment +semver: skip"GitVersion automatically calculates versions based on:
- Latest Git tag - Starting point
- Commit history - Commits since last tag
- Branch name - Determines prerelease labels
- Semver keywords - Explicit version control
Example:
Current tag: v1.2.3
Commits on main:
- "Add feature X +semver: minor"
- "Fix bug Y +semver: patch"
Calculated version: 1.3.0
| Build Context | Version Format | Published | Use Case |
|---|---|---|---|
| Release | 1.2.3 |
✅ PowerShell Gallery | Production releases |
| Prerelease | 1.2.3-alpha.5 |
Testing releases | |
| Debug | 1.2.3-PullRequest.123 |
❌ Never | Development/PR builds |
- PowerShell Gallery Account: Register here
- API Key: Generate in your PSGallery account settings
- GitHub Secret: Store API key as
PSGALLERY_API_KEYin repository secrets
- Log in to PowerShell Gallery
- Navigate to Account Settings → API Keys
- Click Create and set:
- Key Name: GitHub Actions CI/CD
- Select Scopes: Push new packages and package versions
- Select Packages: All packages or specific module name
- Expiration: 1 year (or your preference)
- Go to your repository on GitHub
- Navigate to Settings → Secrets and variables → Actions
- Click New repository secret
- Set:
- Name:
NUGETAPIKEY_PSGALLERY - Value: Your PowerShell Gallery API key
- Name:
- Click Add secret
Ensure your module manifest has proper metadata:
# src/YourModuleName.psd1
@{
ModuleVersion = '0.1.0' # Auto-updated by GitVersion
Author = 'Your Name'
CompanyName = 'Your Company'
Copyright = '(c) 2026 Your Name. All rights reserved.'
Description = 'Comprehensive description for PowerShell Gallery'
# PowerShell Gallery metadata
PrivateData = @{
PSData = @{
Tags = @('PowerShell', 'Automation', 'Module', 'YourTag')
LicenseUri = 'https://github.com/YourUsername/YourModule/blob/main/LICENSE'
ProjectUri = 'https://github.com/YourUsername/YourModule'
IconUri = 'https://github.com/YourUsername/YourModule/raw/main/icon.png'
ReleaseNotes = 'See CHANGELOG.md or GitHub Releases'
}
}
}On Main Branch:
Every merge to main creates a prerelease version:
# 1. Merge PR to main
# 2. CI automatically builds and creates GitHub Release
# 3. Prerelease published to PowerShell Gallery (if configured)Via GitHub Actions Workflow Dispatch:
- Navigate to Actions → CI workflow
- Click Run workflow
- Configure:
version-tag: Optional version tag (e.g.,v1.2.3)publish: ✅ Check to publish to PowerShell Gallery
- Click Run workflow
Via Command Line:
# Build and publish from local machine
Invoke-Build -ReleaseType Release -NugetApiKey 'YOUR-API-KEY'
# Or publish existing build
Publish-Module -Path ./build/out/YourModuleName -NuGetApiKey 'YOUR-API-KEY'✅ Do:
- Test thoroughly before publishing to PowerShell Gallery
- Use prerelease versions for beta testing
- Include comprehensive release notes
- Tag releases with meaningful descriptions
- Maintain a CHANGELOG.md
- Verify module metadata is accurate
❌ Don't:
- Publish untested code
- Skip version numbers (maintain semver)
- Publish modules with hardcoded credentials
- Forget to update documentation
Development Flow:
feature/new-feature (PR)
↓ Tests, Analysis, Security Scans
↓ All checks pass
↓
main (Merge)
↓ Automatic build
↓ Version: 1.2.3-alpha.1
↓ GitHub Release created
↓ Optional: Prerelease to PSGallery
↓
Manual Workflow Dispatch
↓ User triggers with "publish" flag
↓ Version: 1.2.3
↓ GitHub Release updated
↓ Published to PowerShell Gallery
The CI workflow automatically creates GitHub Releases:
On main branch:
- Creates release with version tag
- Generates release notes from merged PRs
- Attaches module package as artifact
- Marks as prerelease
Components:
Release v1.2.3
├── Release Notes (auto-generated from PRs)
├── Assets:
│ ├── YourModuleName-1.2.3.zip
│ └── Source code (auto-attached by GitHub)
└── Metadata:
├── Tag: v1.2.3
├── Commit: abc123
└── Prerelease: true/false
Release notes are automatically generated from:
- PR titles: Main content
- PR descriptions: Additional details
- Labels: Categorization (bug, feature, breaking)
Example Release Notes:
## What's Changed
### 🚀 Features
- Add Get-Something function by @username in #123
- Support pipeline input for Get-Data by @username in #124
### 🐛 Bug Fixes
- Fix parameter validation in Get-Something by @username in #125
### 📖 Documentation
- Update README examples by @username in #126
**Full Changelog**: https://github.com/.../compare/v1.2.0...v1.2.3The template includes automated maintenance to keep your repository clean:
Automatically removes old build artifacts:
- Schedule: Daily at midnight (UTC)
- Retention: 2 days (configurable)
- Targets: Test results, coverage reports, build outputs
Removes old workflow runs to manage storage:
- Schedule: Daily at midnight (UTC)
- Retention: Keeps recent 2 runs per workflow (configurable)
- Policy: Configurable by workflow status (success, failure, cancelled)
- Navigate to Actions → Cleanup workflow
- Click Run workflow
- Configure retention period
- Click Run workflow
Edit .github/workflows/cleanup.yml:
# Adjust retention periods
env:
ARTIFACT_RETENTION_DAYS: 7 # Keep artifacts for 7 days
MIN_RUNS_TO_KEEP: 5 # Keep at least 5 runs per workflowMonitor storage usage:
- Go to Settings → Actions → Runners
- View storage usage
- Adjust retention policies if needed
Typical storage usage:
Per Build:
- Artifacts: ~10-50 MB
- Workflow logs: ~5-10 MB
Monthly (with defaults):
- ~500 MB - 1 GB
Problem: Tests fail in CI but pass locally
# Solution: Check for environment differences
# - Module versions (check requirements.psd1)
# - PowerShell version (CI uses 7.x)
# - Environment variables
# - File paths (use platform-agnostic paths)Problem: PSScriptAnalyzer failures
# Solution: Run locally with same settings
Invoke-ScriptAnalyzer -Path ./src -Recurse `
-Settings ./tests/PSScriptAnalyzer/PSScriptAnalyzerSettings.psd1Problem: Publishing fails with authentication error
# Solution: Verify GitHub secret
# 1. Check NUGETAPIKEY_PSGALLERY exists
# 2. Verify API key hasn't expired
# 3. Regenerate key if neededProblem: Version already exists on PowerShell Gallery
# Solution: Version conflict
# - You cannot republish the same version
# - Bump version with proper semver keyword
# - Or use prerelease version: 1.2.3-alpha.1Problem: Version not incrementing
# Solution: Check commit message includes semver keyword
git commit -m "Your message +semver: minor"
# Verify GitVersion configuration
cat GitVersion.ymlProblem: Wrong version calculated
# Solution: Check git tags and history
git tag -l
git log --oneline --graph
# Manually tag if needed
git tag v1.0.0
git push origin v1.0.0Problem: Workflow not triggering
# Solution: Check workflow triggers in .github/workflows/ci.yml
# Ensure your branch/PR matches trigger conditionsProblem: Workflow steps failing
# Solution: Check workflow logs
# 1. Go to Actions tab
# 2. Select failed workflow run
# 3. Expand failed step
# 4. Review error messages✅ Do:
- Include semver keywords in commit messages
- Write descriptive commit messages
- Keep commits focused and atomic
- Reference issues in commit messages (#123)
❌ Don't:
- Commit without running tests locally
- Mix multiple changes in one commit
- Forget semver keywords (results in no version bump)
✅ Do:
- Wait for all CI checks to pass before merging
- Review CI test results and coverage reports
- Update documentation if API changes
- Request reviews from team members
❌ Don't:
- Merge failing PRs ("I'll fix it later")
- Skip CI checks with admin override
- Ignore PSScriptAnalyzer warnings
✅ Do:
- Use prereleases for testing
- Test prereleases before promoting to release
- Maintain semantic versioning discipline
- Document breaking changes in release notes
❌ Don't:
- Skip versions (e.g., 1.0.0 → 1.0.2)
- Make breaking changes in patch versions
- Publish untested code to PowerShell Gallery
- 📖 GitHub Actions Documentation
- 📖 GitVersion Documentation
- 📖 PowerShell Gallery Publishing
- 📖 Semantic Versioning
- 📖 GitHub Flow
Automate everything! Ship with confidence! 🚀