# Psake > Comprehensive guide to the psake build automation tool, including installation, usage, and advanced features. - [Psake](/index.md) ## blog Blog - [Blog](/blog.md): Blog ### archive Archive - [Archive](/blog/archive.md): Archive ### authors - [Authors](/blog/authors.md) ### first-blog-post First blog post! I'll keep this short and sweet. This post is to announce that - [First Blog Post](/blog/first-blog-post.md): First blog post! I'll keep this short and sweet. This post is to announce that ### introducing-psake-agent-skill Enhance your AI-assisted development with the new psake Agent Skill—bringing intelligent build automation guidance to Claude, GitHub Copilot, and other AI coding assistants. - [Introducing the psake Agent Skill](/blog/introducing-psake-agent-skill.md): Enhance your AI-assisted development with the new psake Agent Skill—bringing intelligent build automation guidance to Claude, GitHub Copilot, and other AI coding assistants. ### new-releases-and-more Discover the latest updates and releases in psake and PowerShellBuild that you won't want to miss! - [New Releases & More!](/blog/new-releases-and-more.md): Discover the latest updates and releases in psake and PowerShellBuild that you won't want to miss! ### powershellbuild-v0.8.0-signing-and-docs PowerShellBuild v0.8.0 brings Authenticode code-signing support for PowerShell modules, and comprehensive docs are now available on the psake docs site. - [PowerShellBuild v0.8.0: Authenticode Signing & New Documentation](/blog/powershellbuild-v0.8.0-signing-and-docs.md): PowerShellBuild v0.8.0 brings Authenticode code-signing support for PowerShell modules, and comprehensive docs are now available on the psake docs site. ### psake-5-0-0-released psake 5.0.0 is a major leap forward for PowerShell build automation—bringing declarative task syntax, compile-time validation, file-based caching, structured output, and CI-native integrations. - [psake 5.0.0 Is Here: Declarative Tasks, Caching, and Structured Output](/blog/psake-5-0-0-released.md): psake 5.0.0 is a major leap forward for PowerShell build automation—bringing declarative task syntax, compile-time validation, file-based caching, structured output, and CI-native integrations. ### psake-vscode-extension-v1 The psake VS Code extension v1.0 brings task discovery, CodeLens integration, a dedicated task explorer, and smart build script detection directly into your editor. - [psake Meets VS Code: The Official Extension Hits v1.0](/blog/psake-vscode-extension-v1.md): The psake VS Code extension v1.0 brings task discovery, CodeLens integration, a dedicated task explorer, and smart build script detection directly into your editor. ### resilient-build-ps1 Extend your psake build.ps1 with clear error handling, dynamic tab completion, CI-safe module imports, and more. - [Building a Resilient build.ps1 for psake Projects](/blog/resilient-build-ps1.md): Extend your psake build.ps1 with clear error handling, dynamic tab completion, CI-safe module imports, and more. ### tags - [Tags](/blog/tags.md) - [5 posts tagged with "Announcement"](/blog/tags/announcement.md): Important announcements and updates - [One post tagged with "Best Practices"](/blog/tags/best-practices.md): Recommended approaches and industry standards - [5 posts tagged with "Build Automation"](/blog/tags/build-automation.md): Automated build processes and continuous integration - [3 posts tagged with "CI/CD"](/blog/tags/ci-cd.md): Continuous Integration and Continuous Deployment - [One post tagged with "Deployment"](/blog/tags/deployment.md): Application deployment and release management - [6 posts tagged with "PowerShell"](/blog/tags/powershell.md): PowerShell scripting and automation content - [6 posts tagged with "psake"](/blog/tags/psake.md): psake build automation framework content - [4 posts tagged with "Release"](/blog/tags/release.md): Product releases and version announcements - [One post tagged with "VS Code"](/blog/tags/vscode.md): Visual Studio Code extension and editor integration ## search - [Search the documentation](/search.md) ## docs ### best-practices - [Environment Management](/docs/best-practices/environment-management.md): Manage multiple deployment environments in psake using environment-specific properties, configuration files, and conditional task execution - [Organizing Large Build Scripts](/docs/best-practices/organizing-large-scripts.md): Best practices for structuring large psake build scripts using modular tasks, includes, and shared utilities for maintainability - [Secret Management](/docs/best-practices/secret-management.md): Securely handle secrets and credentials in psake builds using environment variables, Azure Key Vault, AWS Secrets Manager, and secure coding practices - [Testing Build Scripts](/docs/best-practices/testing-build-scripts.md): Test your psake build scripts using Pester, mock external commands, validate task dependencies, and integrate with CI/CD pipelines - [Build Versioning Strategies](/docs/best-practices/versioning-strategy.md): Implement effective build versioning using semantic versioning, git-based versioning, CI build numbers, and automated assembly version updates ### build-types - [Docker Container Builds](/docs/build-types/docker.md): Build, tag, and deploy Docker containers using psake with multi-stage builds, registry publishing, and Docker Compose orchestration - [.Net Solution](/docs/build-types/dot-net-solution.md): Here's an example of a basic script you can write to build a Visual Studio.Net - [Node.js and npm Projects](/docs/build-types/nodejs.md): Build, test, and deploy Node.js applications using psake with npm, TypeScript, Webpack, and npm registry publishing ### ci-examples - [Azure Pipelines](/docs/ci-examples/azure-pipelines.md): Complete guide to integrating psake with Azure DevOps Pipelines for automated builds with multi-stage pipelines, variable groups, and Azure Artifacts - [Cruise Control](/docs/ci-examples/cruise-control.md): Below are some sample tasks that worked for me (fschwiet), though I'm new to - [GitHub Actions](/docs/ci-examples/github-actions.md): Complete guide to integrating psake with GitHub Actions for automated builds across Windows, Linux, and macOS - [GitLab CI/CD](/docs/ci-examples/gitlab-ci.md): Complete guide to integrating psake with GitLab CI/CD for automated builds with pipeline stages, CI/CD variables, and Docker executors - [Hudson](/docs/ci-examples/hudson.md): Learn how to integrate psake into Hudson CI - [Team City](/docs/ci-examples/team-city.md): Integrate psake into TeamCity ### code_of_conduct Learn how we expect our community members to conduct themselves. - [Contributor Covenant Code of Conduct](/docs/code_of_conduct.md): Learn how we expect our community members to conduct themselves. ### commands - [Assert](/docs/commands/Assert.md): Help page for the PowerShell Psake "Assert" command - [BuildSetup](/docs/commands/BuildSetup.md): Help page for the PowerShell Psake "BuildSetup" command - [BuildTearDown](/docs/commands/BuildTearDown.md): Help page for the PowerShell Psake "BuildTearDown" command - [Clear-PsakeCache](/docs/commands/Clear-PsakeCache.md): Help page for the PowerShell Psake "Clear-PsakeCache" command - [Execute](/docs/commands/Execute.md): Help page for the PowerShell Psake "Execute" command - [FormatTaskName](/docs/commands/FormatTaskName.md): Help page for the PowerShell Psake "FormatTaskName" command - [Framework](/docs/commands/Framework.md): Help page for the PowerShell Psake "Framework" command - [Get-PsakeBuildPlan](/docs/commands/Get-PsakeBuildPlan.md): Help page for the PowerShell Psake "Get-PsakeBuildPlan" command - [Get-PSakeScriptTasks](/docs/commands/Get-PSakeScriptTasks.md): Help page for the PowerShell Psake "Get-PSakeScriptTasks" command - [Include](/docs/commands/Include.md): Help page for the PowerShell Psake "Include" command - [Invoke-Psake](/docs/commands/Invoke-Psake.md): Help page for the PowerShell Psake "Invoke-Psake" command - [Invoke-Task](/docs/commands/Invoke-Task.md): Help page for the PowerShell Psake "Invoke-Task" command - [Properties](/docs/commands/Properties.md): Help page for the PowerShell Psake "Properties" command - [Task](/docs/commands/Task.md): Help page for the PowerShell Psake "Task" command - [TaskSetup](/docs/commands/TaskSetup.md): Help page for the PowerShell Psake "TaskSetup" command - [TaskTearDown](/docs/commands/TaskTearDown.md): Help page for the PowerShell Psake "TaskTearDown" command - [Test-BuildEnvironment](/docs/commands/Test-BuildEnvironment.md): Help page for the PowerShell Psake "Test-BuildEnvironment" command - [Test-PsakeTask](/docs/commands/Test-PsakeTask.md): Help page for the PowerShell Psake "Test-PsakeTask" command - [Version](/docs/commands/Version.md): Help page for the PowerShell Psake "Version" command ### integrations - [LLM Tools & Marketplace](/docs/integrations/llm-tools.md): Install and use psake Claude skills from the psake-llm-tools marketplace to get AI-assisted build automation in Claude Code and VS Code. - [VS Code Extension](/docs/integrations/vscode-extension.md): Install and configure the psake VS Code extension for task discovery, CodeLens, and build automation directly in your editor. ### intro psake is a build automation tool written in PowerShell. It avoids the - [Quick Start](/docs/intro.md): psake is a build automation tool written in PowerShell. It avoids the ### powershellbuild - [Configuration Reference](/docs/powershellbuild/configuration.md): Complete reference for the $PSBPreference hashtable and task dependency variables in PowerShellBuild. - [Getting Started with PowerShellBuild](/docs/powershellbuild/getting-started.md): Step-by-step guide to adding PowerShellBuild to a PowerShell module project and running your first build. - [Introduction to PowerShellBuild](/docs/powershellbuild/introduction.md): Learn what PowerShellBuild is, how it relates to psake, and when to use it for building PowerShell modules. - [Real-World Example](/docs/powershellbuild/real-world-example.md): A complete PowerShellBuild project with custom tasks, CI/CD integration, and PSGallery publishing. - [Task Reference](/docs/powershellbuild/task-reference.md): Complete reference for all pre-built psake tasks in PowerShellBuild, including dependencies and behavior. ### reference - [psake Cheat Sheet](/docs/reference/cheat-sheet.md): Quick reference guide for common psake patterns, commands, and useful one-liners - [Configuration Reference](/docs/reference/configuration-reference.md): Complete reference for all psake configuration options, parameters, and build script settings - [Exit Codes](/docs/reference/exit-codes.md): Reference guide for psake exit codes and how to handle them in CI/CD pipelines - [Glossary](/docs/reference/glossary.md): Definitions of key terms and concepts used in psake build automation ### troubleshooting - [Common Errors](/docs/troubleshooting/common-errors.md): Solutions to the most frequently encountered psake errors including task not found, module loading issues, path resolution, and dependency problems - [Debugging Guide](/docs/troubleshooting/debugging-guide.md): Complete guide to debugging psake builds using PowerShell debugger, verbose output, logging strategies, and task execution analysis - [Frequently Asked Questions](/docs/troubleshooting/faq.md): Common questions about psake installation, usage, performance, and integration with other tools ### tutorial-advanced - [Access Functions in Another File](/docs/tutorial-advanced/access-functions-in-another-file.md): Use the include function to access functions that are in another script file. - [Build Script Resilience](/docs/tutorial-advanced/build-script-resilience.md): How to allow tasks to continue on error - [Creating Shared Task Modules](/docs/tutorial-advanced/creating-shared-task-modules.md): Create, version, test, and consume reusable psake tasks distributed in a PowerShell module. - [Custom Logging](/docs/tutorial-advanced/custom-logging.md): How to override psake's output handlers for custom logging - [Debugging](/docs/tutorial-advanced/debug-script.md): You can debug your psake build script in a couple of ways: - [Logging Errors](/docs/tutorial-advanced/logging-errors.md): By default, psake does not log errors to a file. When an error occurs, detailed error information is emitted to the console. Most CI servers capture all console output, so this is usually sufficient. - [Outputs and Artifacts](/docs/tutorial-advanced/outputs-and-artifacts.md): Best practices for returning data and managing build artifacts in psake scripts - [Formatting Task Name](/docs/tutorial-advanced/print-psake-task-name.md): How to modify the task name in your psake output. - [psake Configuration File](/docs/tutorial-advanced/psake-config.md): How to customize psake defaults with psake-config.ps1 - [Retry Rules](/docs/tutorial-advanced/retry-rules.md): How to use retry rules - [Structure of a psake Build Script](/docs/tutorial-advanced/structure-of-a-psake-build-script.md): Learn how a build script is structured. - [Variable Scope and References](/docs/tutorial-advanced/variable-referencing.md): Learn how to leverage variables across your different tasks. ### tutorial-basics - [Default Build File](/docs/tutorial-basics/default-build-files.md): If you call psake but do not specify a build file, psake will instead use a - [Getting Help](/docs/tutorial-basics/getting-help.md): Learn how to get help with psake - built-in documentation and community support - [How to Fail a Build](/docs/tutorial-basics/how-to-fail-a-build.md): Learn how to fail your builds safely. - [Installing psake](/docs/tutorial-basics/installing.md): Install the psake module - [Nested Builds](/docs/tutorial-basics/nested-build.md): See how to create and run nested builds - [Parameters](/docs/tutorial-basics/parameters.md): You can pass parameters to your build script using the "parameters" parameter of - [Parameters & Properties](/docs/tutorial-basics/parameters-properties.md): How to use parameters and properties in your build script. - [Run psake](/docs/tutorial-basics/run-psake.md): psake is a PowerShell module and is contained in a file named psake.psm1. - [Tasks](/docs/tutorial-basics/tasks.md): Learn how to use the most important building block in psake: The Task --- # Full Documentation Content ## [psake 5.0.0 Is Here: Declarative Tasks, Caching, and Structured Output](/blog/psake-5-0-0-released.md) April 15, 2026 · 6 min read [![Gilbert Sanchez](https://github.com/heyitsgilbert.png)](https://gilbertsanchez.com) [Gilbert Sanchez](https://gilbertsanchez.com) Core Contributor [](https://x.com/HeyItsGilbertS "X")[](https://www.linkedin.com/in/gilbertsanchez/ "LinkedIn")[](https://github.com/HeyItsGilbert "GitHub") After years of steady iteration on the v4 line, we're thrilled to announce the release of **psake 5.0.0**—the biggest update to the PowerShell build automation framework since its inception. This release introduces a declarative task syntax, two-phase compilation, local file-based caching, structured output, and first-class CI integration. Here's everything that's new. **Tags:** * [Release](/blog/tags/release.md "Product releases and version announcements") * [Announcement](/blog/tags/announcement.md "Important announcements and updates") * [psake](/blog/tags/psake.md "psake build automation framework content") * [PowerShell](/blog/tags/powershell.md "PowerShell scripting and automation content") * [Build Automation](/blog/tags/build-automation.md "Automated build processes and continuous integration") * [CI/CD](/blog/tags/ci-cd.md "Continuous Integration and Continuous Deployment") [**Read more**](/blog/psake-5-0-0-released.md) --- ### 2026[​](#2026 "Direct link to 2026") * [January 27](/blog/introducing-psake-agent-skill.md) [ - ](/blog/introducing-psake-agent-skill.md) [Introducing the psake Agent Skill](/blog/introducing-psake-agent-skill.md) * [February 21](/blog/powershellbuild-v0.8.0-signing-and-docs.md) [ - ](/blog/powershellbuild-v0.8.0-signing-and-docs.md) [PowerShellBuild v0.8.0: Authenticode Signing & New Documentation](/blog/powershellbuild-v0.8.0-signing-and-docs.md) * [March 10](/blog/psake-vscode-extension-v1.md) [ - ](/blog/psake-vscode-extension-v1.md) [psake Meets VS Code: The Official Extension Hits v1.0](/blog/psake-vscode-extension-v1.md) * [April 8](/blog/resilient-build-ps1.md) [ - ](/blog/resilient-build-ps1.md) [Building a Resilient build.ps1 for psake Projects](/blog/resilient-build-ps1.md) * [April 15](/blog/psake-5-0-0-released.md) [ - ](/blog/psake-5-0-0-released.md) [psake 5.0.0 Is Here: Declarative Tasks, Caching, and Structured Output](/blog/psake-5-0-0-released.md) --- # Authors * [![Gilbert Sanchez](https://github.com/heyitsgilbert.png)](https://gilbertsanchez.com) ## [Gilbert Sanchez](https://gilbertsanchez.com) 6 Core Contributor [](https://x.com/HeyItsGilbertS "X")[](https://www.linkedin.com/in/gilbertsanchez/ "LinkedIn")[](https://github.com/HeyItsGilbert "GitHub") * [![Trent Blackburn](https://github.com/tablackburn.png)](https://tablackburn.github.io) ## [Trent Blackburn](https://tablackburn.github.io) 1 Contributor [](https://github.com/tablackburn "GitHub")[](https://www.linkedin.com/in/trentblackburn/ "LinkedIn") --- # First Blog Post October 5, 2024 · One min read [![Gilbert Sanchez](https://github.com/heyitsgilbert.png)](https://gilbertsanchez.com) [Gilbert Sanchez](https://gilbertsanchez.com) Core Contributor [](https://x.com/HeyItsGilbertS "X")[](https://www.linkedin.com/in/gilbertsanchez/ "LinkedIn")[](https://github.com/HeyItsGilbert "GitHub") First blog post! I'll keep this short and sweet. This post is to announce that deployment of [psake.dev](https://psake.dev)! This first iteration is composed of the original docs on the psake repository. I'll be slowly reviewing all the docs and updating them to make it easier for folks to get started. In the near future, I'll be looking at more common CI/CD workflows and making sure we have good examples. Have suggestions? PR's are welcome! [Edit this page](https://github.com/psake/docs/blob/main/blog/2024-10-05-FirstBlog.md) --- # Introducing the psake Agent Skill January 27, 2026 · 3 min read [![Gilbert Sanchez](https://github.com/heyitsgilbert.png)](https://gilbertsanchez.com) [Gilbert Sanchez](https://gilbertsanchez.com) Core Contributor [](https://x.com/HeyItsGilbertS "X")[](https://www.linkedin.com/in/gilbertsanchez/ "LinkedIn")[](https://github.com/HeyItsGilbert "GitHub") We're excited to announce the launch of the [psake Agent Skill](https://github.com/psake/psake-llm-tools)—a specialized knowledge package that enables AI coding assistants like Claude and GitHub Copilot to provide expert assistance with psake build automation tasks. Built on the open [Agent Skills](https://agentskills.io/) standard, this portable skill brings intelligent, context-aware guidance directly to your AI-assisted development workflow. ## What is the psake Agent Skill?[​](#what-is-the-psake-agent-skill "Direct link to What is the psake Agent Skill?") The psake Agent Skill is part of our growing collection of LLM tools designed to enhance how developers work with psake. Built on the open [Agent Skills](https://agentskills.io/) standard, it provides AI assistants with deep knowledge of psake patterns, syntax, and best practices. This portable format means the same skill works across different AI platforms—whether you're using Claude, GitHub Copilot, or other compatible AI tools. ## Key Features[​](#key-features "Direct link to Key Features") The skill includes four comprehensive knowledge components: **SKILL.md** – Core psake fundamentals covering command syntax, task definitions, dependencies, and troubleshooting patterns. This ensures your AI assistant understands the basics before diving into more complex scenarios. **PowerShell Modules Reference** – Detailed guidance on using PowerShellBuild for module development workflows, including task patterns, build configurations, and testing integration. **Build Types Reference** – Practical patterns for .NET, Node.js, and Docker builds, providing real-world examples for different project types. **Advanced Reference** – Coverage of sophisticated topics like dynamic task generation, custom logging implementations, and CI/CD pipeline integration. ## What Can AI Assistants Do With This Skill?[​](#what-can-ai-assistants-do-with-this-skill "Direct link to What Can AI Assistants Do With This Skill?") Once you've loaded the psake skill, your AI assistant can help with tasks like: * Building complete psakefiles with integrated Pester testing frameworks * Establishing build configurations for .NET solutions and PowerShell modules * Implementing dynamic task generation from configuration files * Troubleshooting build failures and dependency issues * Integrating psake workflows into CI/CD pipelines * Creating custom logging mechanisms * Developing cross-platform build scripts ## Getting Started[​](#getting-started "Direct link to Getting Started") The psake Agent Skill works with multiple AI platforms thanks to the open Agent Skills standard. ### Using with Claude[​](#using-with-claude "Direct link to Using with Claude") 1. Visit the [psake-llm-tools repository](https://github.com/psake/psake-llm-tools) 2. Download the `psake.skill` file from the releases section 3. Upload it to Claude through the skills interface 4. Start building with AI-assisted psake expertise ### Using with GitHub Copilot[​](#using-with-github-copilot "Direct link to Using with GitHub Copilot") 1. Download the `psake.skill` file from the [repository](https://github.com/psake/psake-llm-tools) 2. Follow the [VS Code instructions for using shared skills](https://code.visualstudio.com/docs/copilot/customization/agent-skills#_use-shared-skills) 3. Configure the skill in your workspace or user settings 4. Access psake expertise directly in your editor The same skill file works across both platforms, making it easy to maintain consistent AI assistance regardless of which tool you prefer. ## Open Standard, Open Source[​](#open-standard-open-source "Direct link to Open Standard, Open Source") By building on the [Agent Skills](https://agentskills.io/) open standard, we ensure that psake expertise is portable across AI platforms. As more tools adopt this standard, your investment in configuring the psake skill will continue to pay dividends across your entire development toolchain. The psake-llm-tools repository is open source and welcomes community contributions. If you have patterns, examples, or improvements that would benefit other developers using psake with AI assistants, we'd love to see your pull requests. Whether you're new to psake or a seasoned automation expert, the psake Agent Skill can help streamline your workflow and provide intelligent guidance when you need it most. Give it a try and let us know what you think! Check out the project at [github.com/psake/psake-llm-tools](https://github.com/psake/psake-llm-tools). **Tags:** * [Announcement](/blog/tags/announcement.md "Important announcements and updates") * [psake](/blog/tags/psake.md "psake build automation framework content") * [PowerShell](/blog/tags/powershell.md "PowerShell scripting and automation content") * [Build Automation](/blog/tags/build-automation.md "Automated build processes and continuous integration") * [CI/CD](/blog/tags/ci-cd.md "Continuous Integration and Continuous Deployment") [Edit this page](https://github.com/psake/docs/blob/main/blog/2026-01-27-introducing-psake-agent-skill.md) --- # New Releases & More! August 3, 2025 · 3 min read [![Gilbert Sanchez](https://github.com/heyitsgilbert.png)](https://gilbertsanchez.com) [Gilbert Sanchez](https://gilbertsanchez.com) Core Contributor [](https://x.com/HeyItsGilbertS "X")[](https://www.linkedin.com/in/gilbertsanchez/ "LinkedIn")[](https://github.com/HeyItsGilbert "GitHub") It's been a while since our last update, and we've got some exciting news to share! In this post, we'll cover recent releases, welcome new team members, and highlight the various initiatives we're working on to make psake even better. ## Latest Versions[​](#latest-versions "Direct link to Latest Versions") Since launching this documentation site, we haven't been great about announcing new versions here. You could say we've been in stealth mode (though we're definitely not that cool). ### psake[​](#psake "Direct link to psake") Back in October, we released psake 4.9.1, and since then we've merged several pull requests, including some fantastic community contributions. Check out the [psake Changelog](https://github.com/psake/psake/blob/main/CHANGELOG.md) for all the details. The next release will introduce a new way to override the psake logging mechanism. This will allow teams to integrate additional logging frameworks or create improved CI/CD-specific workflows. ### PowerShellBuild[​](#powershellbuild "Direct link to PowerShellBuild") Just a few days ago, we updated PowerShellBuild - our collection of common tasks for psake and Invoke-Build - to support overriding task dependencies. We also added localization support (more on that below). This brings us to version 0.7.3 as our latest release. Since version 0.6.2, we've seen 4.5k downloads, and we hope to keep improving the project based on your feedback. See the [PowerShellBuild Changelog](https://github.com/psake/PowerShellBuild/blob/main/CHANGELOG.md) for complete details. ## Welcome Our Newest Team Members[​](#welcome-our-newest-team-members "Direct link to Welcome Our Newest Team Members") You may have noticed them working on and reviewing code in our repositories, but we'd like to formally welcome Joshua Hendricks and Trent Blackburn as new members of the psake core team! **Joshua** is our resident [MVP](https://mvp.microsoft.com/MVP/profile/f98b4f3e-12fd-40de-bdce-1467f04d430d) who's well-known for presenting at PowerShell + DevOps Global Summit on building beautiful docs and sending notifications. He's a developer at Milestone Systems where he maintains their PowerShell module. **Trent** currently develops PowerShell automations at Tesla, previously specialized in PowerShell at Amazon, and has also presented at the annual PowerShell + DevOps Global Summit. Both bring incredible expertise to the team, and we're excited to have them aboard! If you're interested in contributing to psake, please consider opening an issue or submitting a pull request—we'd love to hear from you. ## Expanding Global Accessibility with Localization[​](#expanding-global-accessibility-with-localization "Direct link to Expanding Global Accessibility with Localization") psake has long had the framework in place for supporting localization (l10n), but until now has only been available in English. One of our key goals is making psake as accessible as possible for users worldwide. To make community contributions easier, we've set up a [Crowdin project](http://crowdin.com/project/psake) that enables straightforward translation requests and suggestions. With the Crowdin project now live, we have a streamlined pipeline for introducing new localized versions. We'd love your help in making psake available in more languages - please consider contributing translations! **Tags:** * [Announcement](/blog/tags/announcement.md "Important announcements and updates") * [Release](/blog/tags/release.md "Product releases and version announcements") * [psake](/blog/tags/psake.md "psake build automation framework content") * [PowerShell](/blog/tags/powershell.md "PowerShell scripting and automation content") * [Build Automation](/blog/tags/build-automation.md "Automated build processes and continuous integration") [Edit this page](https://github.com/psake/docs/blob/main/blog/2025-08-03-exciting-new-releases-you-cant-miss.md) --- # PowerShellBuild v0.8.0: Authenticode Signing & New Documentation February 21, 2026 · 3 min read [![Gilbert Sanchez](https://github.com/heyitsgilbert.png)](https://gilbertsanchez.com) [Gilbert Sanchez](https://gilbertsanchez.com) Core Contributor [](https://x.com/HeyItsGilbertS "X")[](https://www.linkedin.com/in/gilbertsanchez/ "LinkedIn")[](https://github.com/HeyItsGilbert "GitHub") Two exciting updates to share today: **PowerShellBuild v0.8.0** has been released with built-in Authenticode code-signing support, and we've added a comprehensive [PowerShellBuild documentation section](/docs/powershellbuild/introduction.md) right here on the psake docs site. ## PowerShellBuild Docs Are Now on the psake Site[​](#powershellbuild-docs-are-now-on-the-psake-site "Direct link to PowerShellBuild Docs Are Now on the psake Site") If you've been looking for guidance on using PowerShellBuild to streamline your PowerShell module builds, you no longer need to piece things together from the README alone. The psake docs site now has a dedicated **PowerShellBuild** section covering: * [**Introduction**](/docs/powershellbuild/introduction.md) — What PowerShellBuild is and how it relates to psake * [**Getting Started**](/docs/powershellbuild/getting-started.md) — Installation and first-build walkthrough * [**Configuration**](/docs/powershellbuild/configuration.md) — Deep dive into `$PSBPreference` and how to customize every aspect of your build * [**Task Reference**](/docs/powershellbuild/task-reference.md) — Complete listing of all available tasks and their dependencies * [**Real-World Example**](/docs/powershellbuild/real-world-example.md) — A practical end-to-end project setup ## What's New in v0.8.0 — Authenticode Signing[​](#whats-new-in-v080--authenticode-signing "Direct link to What's New in v0.8.0 — Authenticode Signing") The headline feature in [PowerShellBuild v0.8.0](https://github.com/psake/PowerShellBuild/releases/tag/v0.8.0) is full Authenticode code-signing support for PowerShell modules. This was a highly requested capability, and it's now baked right into the standard build pipeline. ### Three New Public Functions[​](#three-new-public-functions "Direct link to Three New Public Functions") **`Get-PSBuildCertificate`** resolves a code-signing `X509Certificate2` from five different sources: * **Auto** — Automatically detects from environment variables or the certificate store * **Windows certificate store** — With optional thumbprint filtering * **Base64-encoded PFX** — From environment variables, ideal for CI/CD pipelines * **PFX file on disk** — With optional password protection * **Pre-resolved certificate object** — For custom providers like Azure Key Vault **`Invoke-PSBuildModuleSigning`** signs your module files (`.psd1`, `.psm1`, `.ps1`) with Authenticode signatures. It supports configurable timestamp servers and hash algorithms including SHA256, SHA384, and SHA512. **`New-PSBuildFileCatalog`** creates Windows catalog (`.cat`) files that record cryptographic hashes of your module's contents for tamper detection. ### Four New Build Tasks[​](#four-new-build-tasks "Direct link to Four New Build Tasks") | Task | Description | | -------------- | ----------------------------------------------------- | | `SignModule` | Signs module files with Authenticode | | `BuildCatalog` | Creates a Windows catalog file | | `SignCatalog` | Signs the catalog file | | `Sign` | Meta-task that orchestrates the full signing pipeline | These tasks slot into the existing build pipeline with proper dependency ordering: **Build → SignModule → BuildCatalog → SignCatalog**. ## Certificate Sources[​](#certificate-sources "Direct link to Certificate Sources") PowerShellBuild supports four ways to supply a certificate, listed here in order of common use: **1. Automatic (CI/CD) — Base64 PFX in an env var** ``` # Store your PFX as a base64 secret (e.g. GitHub Actions secret SIGNCERTIFICATE) # PowerShellBuild picks it up automatically when Sign.Enabled = $true $PSBPreference.Sign.Enabled = $true # CertificateSource defaults to 'Auto' — done ``` **2. Local dev — certificate store** ``` $PSBPreference.Sign.Enabled = $true $PSBPreference.Sign.CertificateSource = 'Store' # picks first valid, unexpired code-signing cert in Cert:\CurrentUser\My # Or pin to a specific one by thumbprint: $PSBPreference.Sign.CertificateSource = 'Thumbprint' $PSBPreference.Sign.Thumbprint = 'AB12CD34EF...' ``` **3. PFX file on disk** ``` $PSBPreference.Sign.Enabled = $true $PSBPreference.Sign.CertificateSource = 'PfxFile' $PSBPreference.Sign.PfxFilePath = './codesign.pfx' $PSBPreference.Sign.PfxFilePassword = (Read-Host -AsSecureString 'Password') ``` **4. Pre-resolved object (Azure Key Vault, HSM, etc.)** ``` # Get the cert however you like, then hand it directly: $cert = Get-AzKeyVaultCertificate -VaultName 'MyVault' -Name 'CodeSignCert' | Get-AzKeyVaultSecret | ... # your Key Vault retrieval logic $PSBPreference.Sign.Enabled = $true $PSBPreference.Sign.Certificate = $cert # bypasses CertificateSource entirely ``` All of these go in your `Properties {}` block (psake) or before dot-sourcing (Invoke-Build), before the task file is loaded. To also sign before publishing: ``` $PSBPublishDependency = @('Sign') ``` All signing operations include platform checks (Windows-only) with appropriate warnings, and verbose logging throughout makes troubleshooting straightforward. ## Get Started[​](#get-started "Direct link to Get Started") * **Read the docs:** Check out the [PowerShellBuild documentation](/docs/powershellbuild/introduction.md) for a complete walkthrough * **Upgrade:** `Install-Module PowerShellBuild -RequiredVersion 0.8.0` * **Release notes:** [v0.8.0 on GitHub](https://github.com/psake/PowerShellBuild/releases/tag/v0.8.0) * **Feedback:** Open an issue on [GitHub](https://github.com/psake/PowerShellBuild/issues) — we'd love to hear how you're using the signing tasks **Tags:** * [Announcement](/blog/tags/announcement.md "Important announcements and updates") * [Release](/blog/tags/release.md "Product releases and version announcements") * [PowerShell](/blog/tags/powershell.md "PowerShell scripting and automation content") * [Build Automation](/blog/tags/build-automation.md "Automated build processes and continuous integration") * [psake](/blog/tags/psake.md "psake build automation framework content") * [Deployment](/blog/tags/deployment.md "Application deployment and release management") [Edit this page](https://github.com/psake/docs/blob/main/blog/2026-02-21-powershellbuild-v0.8.0-signing-and-docs.md) --- # psake 5.0.0 Is Here: Declarative Tasks, Caching, and Structured Output April 15, 2026 · 6 min read [![Gilbert Sanchez](https://github.com/heyitsgilbert.png)](https://gilbertsanchez.com) [Gilbert Sanchez](https://gilbertsanchez.com) Core Contributor [](https://x.com/HeyItsGilbertS "X")[](https://www.linkedin.com/in/gilbertsanchez/ "LinkedIn")[](https://github.com/HeyItsGilbert "GitHub") After years of steady iteration on the v4 line, we're thrilled to announce the release of **psake 5.0.0**—the biggest update to the PowerShell build automation framework since its inception. This release introduces a declarative task syntax, two-phase compilation, local file-based caching, structured output, and first-class CI integration. Here's everything that's new. ## Declarative Task Syntax[​](#declarative-task-syntax "Direct link to Declarative Task Syntax") The flagship feature of psake 5 is a new hashtable-based syntax for defining tasks. Rather than relying on positional parameters, you can now describe tasks as structured data with named, validated keys: ``` Task 'Build' @{ DependsOn = 'Clean' Action = { dotnet build -c $Configuration } } ``` Every key is validated at compile time—typos like `DependsOn` or `Action` are caught before a single task runs. The original parameter-based syntax still works, so your existing build scripts don't need to change. ## Two-Phase Compilation[​](#two-phase-compilation "Direct link to Two-Phase Compilation") psake 5 separates build file loading from task execution. When you call `Invoke-psake`, the dependency graph is built first via a topological sort, and circular dependencies are caught at **compile time** rather than halfway through a run. This means faster, clearer feedback—and no more builds that silently fail mid-execution. You can also use the new `-CompileOnly` parameter to inspect the build plan without running anything: ``` Invoke-psake -CompileOnly ``` This is especially useful in code review and CI pre-flight checks. ## Local File-Based Caching[​](#local-file-based-caching "Direct link to Local File-Based Caching") For tasks that process files—compilation, transpilation, packaging—psake 5 introduces content-addressed caching. Declare `Inputs` and `Outputs` on a task, and psake will SHA256-hash the inputs to determine whether the task actually needs to run: ``` Task 'Build' @{ Inputs = 'src/**/*.cs', 'src/**/*.csproj' Outputs = 'bin/**/*.dll' Action = { dotnet build -c $Configuration } } ``` Cache state is stored in `.psake/cache/`. If nothing changed, the task is skipped and the build report marks it as **Cached**. To bypass caching for a single run, pass `-NoCache`: ``` Invoke-psake -NoCache ``` And to clear the cache entirely, use the new `Clear-PsakeCache` function. ## Structured Build Results[​](#structured-build-results "Direct link to Structured Build Results") `Invoke-psake` now returns a `PsakeBuildResult` object containing per-task metrics, success status, duration, and error information. Scripting against build output no longer requires parsing console text: ``` $result = Invoke-psake $result.Tasks | Where-Object Cached | Select-Object Name, Duration ``` The `$psake.build_success` variable is still set after every run, so existing CI scripts that check build status continue to work without modification. ## CI-Native Output Formats[​](#ci-native-output-formats "Direct link to CI-Native Output Formats") Building for GitHub Actions? Pass `-OutputFormat GitHubActions` and psake will emit workflow annotations—warnings and errors that surface directly in pull request diffs. For pipelines that consume build data programmatically, `-OutputFormat JSON` outputs the full `PsakeBuildResult` as structured JSON. The output format can also be set via the `PSAKE_OUTPUT_FORMAT` environment variable, making it easy to configure in CI without changing your build scripts. ## Quiet Mode[​](#quiet-mode "Direct link to Quiet Mode") The new `-Quiet` parameter suppresses all console output while still returning the full `PsakeBuildResult` object—including complete error records for any failed tasks. No output is swallowed; it's simply not printed. This is particularly valuable when psake is invoked by an LLM agent or AI coding tool. Console build logs are noisy: progress messages, separator lines, and timing output all burn through context that the model could spend on the actual problem. With `-Quiet`, the agent gets silence on stdout and a precise, structured result it can inspect programmatically: ``` $result = Invoke-psake -Quiet if (-not $result.Success) { # Full error records are available—no log scraping needed Write-Host "Build failed: $($result.ErrorMessage)" Write-Host $result.ErrorRecord.ScriptStackTrace } ``` Rather than parsing wall-of-text build output, the agent can check `$result.Success` and read `$result.ErrorMessage` for the formatted string or `$result.ErrorRecord` for the full `ErrorRecord`—including the complete script stack trace—directly. Pair this with `-OutputFormat JSON` if you need to pass the result across a process boundary or into a prompt. Using psake with an AI coding assistant? The [psake Agent Skill](/blog/introducing-psake-agent-skill.md) has been updated for 5.0 and is available through the [LLM tools marketplace](https://github.com/psake/psake-llm-tools). Install it into Claude, GitHub Copilot, or any Agent Skills–compatible client to give your assistant first-class knowledge of the new declarative syntax, caching, structured output, and migration patterns covered in this post. ## New Testing APIs[​](#new-testing-apis "Direct link to New Testing APIs") Two new functions let you inspect and test your build scripts without running a full build: * **`Get-PsakeBuildPlan`** — Returns the full execution plan for a build file without running it. Inspect task order, dependencies, and caching configuration in tests. * **`Test-PsakeTask`** — Executes a single named task with custom variable overrides. Great for unit-testing individual task logic in isolation. ## Version Declaration[​](#version-declaration "Direct link to Version Declaration") Build files can now declare the minimum psake major version they require: ``` Version 5 ``` If someone runs your build script on an older psake installation, they get a clear error instead of a confusing failure deep inside a task. ## Hashtable Properties[​](#hashtable-properties "Direct link to Hashtable Properties") The `Properties` block gains a more concise hashtable syntax alongside the original scriptblock form: ``` Properties @{ Configuration = 'Release' OutputDir = './artifacts' } ``` Both styles are supported and can be mixed within the same build file. ## `NO_COLOR` Support[​](#no_color-support "Direct link to no_color-support") psake 5 respects the `$env:NO_COLOR` convention for suppressing colored output. Set it in environments where ANSI escape codes cause issues—terminals without color support, log aggregators, or accessibility tools. ## Breaking Changes[​](#breaking-changes "Direct link to Breaking Changes") psake 5 is a major version, and it includes a few breaking changes. Most build scripts will work without modification, but there are three things to check: 1. **Rename `default.ps1` to `psakefile.ps1`** — The `default.ps1` fallback file name is no longer auto-detected. 2. **Replace the wrapper scripts** — `psake.ps1` and `psake.cmd` are removed. Use `Import-Module psake; Invoke-psake` instead. 3. **Update Framework calls** — .NET Framework versions below 4.0 are no longer supported, and the default is now 4.7.2. The `$framework` global variable is removed; use `Framework '4.7.2'` instead. Additionally, the minimum required PowerShell version is now **5.1** (raised from 3.0), and the output handler configuration properties have been removed in favor of the new `-OutputFormat` and `-Quiet` parameters. Full details are in the [migration guide](https://github.com/psake/psake/blob/master/docs/migration-v4-to-v5.md). ## Upgrading[​](#upgrading "Direct link to Upgrading") ``` # Install from the PowerShell Gallery Install-Module -Name psake -MinimumVersion 5.0.0 -Force # Verify the version Get-Module psake -ListAvailable | Select-Object Version ``` Then run your existing build to check for any breaking changes. For most projects, it'll just work. *** psake 5 is the result of years of community feedback, and we're excited to see what you build with it. If you run into anything unexpected, [open an issue](https://github.com/psake/psake/issues). And if you're using the new features in interesting ways, we'd love to hear about it. **Tags:** * [Release](/blog/tags/release.md "Product releases and version announcements") * [Announcement](/blog/tags/announcement.md "Important announcements and updates") * [psake](/blog/tags/psake.md "psake build automation framework content") * [PowerShell](/blog/tags/powershell.md "PowerShell scripting and automation content") * [Build Automation](/blog/tags/build-automation.md "Automated build processes and continuous integration") * [CI/CD](/blog/tags/ci-cd.md "Continuous Integration and Continuous Deployment") [Edit this page](https://github.com/psake/docs/blob/main/blog/2026-04-15-psake-5-0-0-released.md) --- # psake Meets VS Code: The Official Extension Hits v1.0 March 10, 2026 · 4 min read [![Gilbert Sanchez](https://github.com/heyitsgilbert.png)](https://gilbertsanchez.com) [Gilbert Sanchez](https://gilbertsanchez.com) Core Contributor [](https://x.com/HeyItsGilbertS "X")[](https://www.linkedin.com/in/gilbertsanchez/ "LinkedIn")[](https://github.com/HeyItsGilbert "GitHub") We're thrilled to announce the v1.0 release of the [psake VS Code extension](https://github.com/psake/psake-vscode)—bringing first-class psake support directly into the world's most popular code editor. If you've been running psake from the terminal, you can now discover, navigate, and execute your build tasks without ever leaving VS Code. ## Why a VS Code Extension?[​](#why-a-vs-code-extension "Direct link to Why a VS Code Extension?") A core part of our mission with psake is meeting developers where they already work. Whether that's through [AI-assisted workflows with Agent Skills](/blog/introducing-psake-agent-skill.md), CI/CD pipelines, or your daily editor—we want psake to feel native to your environment. With VS Code being the editor of choice for so many PowerShell developers, a dedicated extension was a natural next step. ## What's in v1.0[​](#whats-in-v10 "Direct link to What's in v1.0") This isn't a minimal snippet pack. The v1.0 release is a full-featured integration that makes VS Code aware of your psake build system. ### Task Provider[​](#task-provider "Direct link to Task Provider") The extension automatically detects tasks from your `psakefile.ps1` and surfaces them in VS Code's built-in task system. Your default task maps to the Build group, so `Ctrl+Shift+B` just works. Tasks refresh automatically when you save your build file—no manual reload needed. ### psake Tasks Explorer[​](#psake-tasks-explorer "Direct link to psake Tasks Explorer") A dedicated sidebar panel in the Explorer view gives you an at-a-glance view of every task in your project, complete with descriptions and dependency chains. Click any task to navigate directly to its definition, or hit the run button to execute it immediately. ### CodeLens Integration[​](#codelens-integration "Direct link to CodeLens Integration") Every `Task` declaration in your build file gets a "Run Task" CodeLens action. See a task, run it—right from the editor gutter. It's the fastest path from reading build logic to executing it. ### Smart Build Script Detection[​](#smart-build-script-detection "Direct link to Smart Build Script Detection") If your project uses a wrapper script like `build.ps1` (a common pattern in the psake ecosystem), the extension detects it automatically and routes task execution through it. This means your bootstrapping, dependency installation, and any custom setup all run correctly—just as they would from the terminal. ### tasks.json IntelliSense[​](#tasksjson-intellisense "Direct link to tasks.json IntelliSense") When you configure psake tasks in `.vscode/tasks.json`, the extension provides autocomplete for task names. Combined with the new "psake: Sync Tasks to tasks.json" command, you can quickly wire up your build tasks into VS Code's task runner with full IntelliSense support. ### Nine Code Snippets[​](#nine-code-snippets "Direct link to Nine Code Snippets") Scaffolding new tasks and build file structures is faster with nine built-in snippets covering everything from basic task definitions to `Properties`, `Include`, `Framework`, `FormatTaskName`, `TaskSetup`, and `TaskTearDown` blocks. ## Quick Start[​](#quick-start "Direct link to Quick Start") 1. Install the [psake extension](https://marketplace.visualstudio.com/publishers/psake) from the VS Code Marketplace 2. Make sure you have the [PowerShell extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode.PowerShell) installed 3. Open a project that contains a `psakefile.ps1` or `build.ps1` 4. The extension activates automatically—check the Explorer sidebar for the psake Tasks panel That's it. No configuration required—but there's plenty available if you need it. ## Fully Configurable[​](#fully-configurable "Direct link to Fully Configurable") The extension works out of the box, but every aspect of its behavior can be tuned to fit your workflow. Toggle CodeLens on or off, point to a custom PowerShell executable, pass extra parameters to `Invoke-psake` or your build script, and customize shell arguments—all without restarting VS Code. For the full list of settings, check out the [VS Code extension docs](/docs/integrations/vscode-extension.md#configuration). ## Part of a Growing Ecosystem[​](#part-of-a-growing-ecosystem "Direct link to Part of a Growing Ecosystem") The VS Code extension joins a growing set of tools designed to make psake easy to use wherever you are: * **[psake](https://github.com/psake/psake)** — The build automation engine * **[PowerShellBuild](https://github.com/psake/PowerShellBuild)** — Common build tasks for PowerShell modules * **[psake Agent Skill](https://github.com/psake/psake-llm-tools)** — AI-assisted psake expertise for Claude and GitHub Copilot * **[psake VS Code Extension](https://github.com/psake/psake-vscode)** — First-class editor integration We're committed to making psake feel at home in every part of your development workflow. If you have ideas for where psake should go next, [open an issue](https://github.com/psake/psake-vscode/issues) or join the conversation. Give the extension a try and let us know what you think! **Tags:** * [Announcement](/blog/tags/announcement.md "Important announcements and updates") * [Release](/blog/tags/release.md "Product releases and version announcements") * [psake](/blog/tags/psake.md "psake build automation framework content") * [PowerShell](/blog/tags/powershell.md "PowerShell scripting and automation content") * [VS Code](/blog/tags/vscode.md "Visual Studio Code extension and editor integration") [Edit this page](https://github.com/psake/docs/blob/main/blog/2026-03-10-psake-vscode-extension-v1.md) --- # Building a Resilient build.ps1 for psake Projects April 8, 2026 · 3 min read [![Trent Blackburn](https://github.com/tablackburn.png)](https://tablackburn.github.io) [Trent Blackburn](https://tablackburn.github.io) Contributor [](https://github.com/tablackburn "GitHub")[](https://www.linkedin.com/in/trentblackburn/ "LinkedIn") In psake projects, a `build.ps1` is the conventional entry point script that wires everything together. It installs dependencies, configures the environment, and hands off to psake to run your actual build tasks. Think of it as the bootstrapper that gets a fresh machine — or a CI agent — from zero to a working build in a single command: `.\build.ps1`. psake itself doesn't ship a `build.ps1` — it's a best practice that most projects adopt. A typical starter script covers the basics: bootstrap installation, help output, build environment detection, and proper CI exit codes. But once you're running concurrent CI jobs, managing internal package feeds, or onboarding new contributors, a few gaps start to show. The script below addresses those gaps with clear error handling, dynamic tab completion, CI-safe module imports, internal repository support, and PowerShellGet version pinning. I cover each pattern in detail in the [full post on my blog](https://tablackburn.github.io/p/resilient-build-ps1/) — but here's the complete script. Copy it into your project and adjust the repository name and URL to match your environment: ``` [Diagnostics.CodeAnalysis.SuppressMessageAttribute( 'PSReviewUnusedParameter', 'Command', Justification = 'false positive' )] [Diagnostics.CodeAnalysis.SuppressMessageAttribute( 'PSReviewUnusedParameter', 'Parameter', Justification = 'false positive' )] [Diagnostics.CodeAnalysis.SuppressMessageAttribute( 'PSReviewUnusedParameter', 'CommandAst', Justification = 'false positive' )] [Diagnostics.CodeAnalysis.SuppressMessageAttribute( 'PSReviewUnusedParameter', 'FakeBoundParams', Justification = 'false positive' )] [CmdletBinding(DefaultParameterSetName = 'task')] param( [parameter(ParameterSetName = 'task', Position = 0)] [ArgumentCompleter( { param($Command, $Parameter, $WordToComplete, $CommandAst, $FakeBoundParams) try { Get-PSakeScriptTasks -BuildFile './psakeFile.ps1' -ErrorAction 'Stop' | Where-Object { $_.Name -like "$WordToComplete*" } | Select-Object -ExpandProperty 'Name' } catch { @() } })] [string[]]$Task = 'default', [switch]$Bootstrap, [parameter(ParameterSetName = 'Help')] [switch]$Help ) $ErrorActionPreference = 'Stop' $psakeFile = './psakeFile.ps1' if ($Bootstrap) { # Patch TLS protocols for older Windows versions [System.Net.ServicePointManager]::SecurityProtocol = ( [System.Net.ServicePointManager]::SecurityProtocol -bor [System.Net.SecurityProtocolType]::Tls12 -bor [System.Net.SecurityProtocolType]::Tls13 ) Get-PackageProvider -Name 'Nuget' -ForceBootstrap | Out-Null Set-PSRepository -Name 'PSGallery' -InstallationPolicy 'Trusted' # Pin PowerShellGet to v2 $powerShellGetModule = Get-Module -Name 'PowerShellGet' -ListAvailable | Where-Object { $_.Version.Major -eq 2 } | Sort-Object -Property 'Version' -Descending | Select-Object -First 1 $powerShellGetModuleParameters = @{ Name = 'PowerShellGet' MinimumVersion = '2.0.0' MaximumVersion = '2.99.99' Force = $true } if (-not $powerShellGetModule) { Install-Module @powerShellGetModuleParameters -Scope 'CurrentUser' -AllowClobber } Import-Module @powerShellGetModuleParameters <# Register internal repository (idempotent) $repositoryName = 'internal-nuget-repo' if (-not (Get-PSRepository -Name $repositoryName -ErrorAction 'SilentlyContinue')) { $repositoryUrl = "https://nuget.example.com/api/v2/$repositoryName" $registerPSRepositorySplat = @{ Name = $repositoryName SourceLocation = $repositoryUrl PublishLocation = $repositoryUrl ScriptSourceLocation = $repositoryUrl InstallationPolicy = 'Trusted' PackageManagementProvider = 'NuGet' } Register-PSRepository @registerPSRepositorySplat }#> # Install PSDepend if missing if (-not (Get-Module -Name 'PSDepend' -ListAvailable)) { Install-Module -Name 'PSDepend' -Repository 'PSGallery' -Scope 'CurrentUser' -Force } # Try-import-first pattern $psDependParameters = @{ Path = $PSScriptRoot Recurse = $False WarningAction = 'SilentlyContinue' Import = $True Force = $True ErrorAction = 'Stop' } $importSucceeded = $false try { Invoke-PSDepend @psDependParameters $importSucceeded = $true Write-Verbose 'Successfully imported existing modules.' -Verbose } catch { Write-Verbose "Could not import all required modules: $_" -Verbose Write-Verbose 'Attempting to install missing or outdated dependencies...' -Verbose } if (-not $importSucceeded) { try { Invoke-PSDepend @psDependParameters -Install } catch { Write-Error "Failed to install and import required dependencies: $_" Write-Error 'This may be due to locked module files. Please restart the build environment or clear module locks.' if ($_.Exception.InnerException) { Write-Error "Inner exception: $($_.Exception.InnerException.Message)" } throw } } } else { if (-not (Get-Module -Name 'PSDepend' -ListAvailable)) { throw 'Missing dependencies. Please run with the "-Bootstrap" flag to install dependencies.' } Invoke-PSDepend -Path $PSScriptRoot -Recurse $False -WarningAction 'SilentlyContinue' -Import -Force } if ($PSCmdlet.ParameterSetName -eq 'Help') { Get-PSakeScriptTasks -buildFile $psakeFile | Format-Table -Property Name, Description, Alias, DependsOn } else { Set-BuildEnvironment -Force Invoke-psake -buildFile $psakeFile -taskList $Task -nologo exit ([int](-not $psake.build_success)) } ``` **Tags:** * [psake](/blog/tags/psake.md "psake build automation framework content") * [PowerShell](/blog/tags/powershell.md "PowerShell scripting and automation content") * [Build Automation](/blog/tags/build-automation.md "Automated build processes and continuous integration") * [CI/CD](/blog/tags/ci-cd.md "Continuous Integration and Continuous Deployment") * [Best Practices](/blog/tags/best-practices.md "Recommended approaches and industry standards") [Edit this page](https://github.com/psake/docs/blob/main/blog/2026-04-08-resilient-build-ps1.md) --- ## A[​](#A "Direct link to A") * [Announcement5](/blog/tags/announcement.md "Important announcements and updates") *** --- ## [psake 5.0.0 Is Here: Declarative Tasks, Caching, and Structured Output](/blog/psake-5-0-0-released.md) April 15, 2026 · 6 min read [![Gilbert Sanchez](https://github.com/heyitsgilbert.png)](https://gilbertsanchez.com) [Gilbert Sanchez](https://gilbertsanchez.com) Core Contributor [](https://x.com/HeyItsGilbertS "X")[](https://www.linkedin.com/in/gilbertsanchez/ "LinkedIn")[](https://github.com/HeyItsGilbert "GitHub") After years of steady iteration on the v4 line, we're thrilled to announce the release of **psake 5.0.0**—the biggest update to the PowerShell build automation framework since its inception. This release introduces a declarative task syntax, two-phase compilation, local file-based caching, structured output, and first-class CI integration. Here's everything that's new. **Tags:** * [Release](/blog/tags/release.md "Product releases and version announcements") * [Announcement](/blog/tags/announcement.md "Important announcements and updates") * [psake](/blog/tags/psake.md "psake build automation framework content") * [PowerShell](/blog/tags/powershell.md "PowerShell scripting and automation content") * [Build Automation](/blog/tags/build-automation.md "Automated build processes and continuous integration") * [CI/CD](/blog/tags/ci-cd.md "Continuous Integration and Continuous Deployment") [**Read more**](/blog/psake-5-0-0-released.md) --- ## [Building a Resilient build.ps1 for psake Projects](/blog/resilient-build-ps1.md) April 8, 2026 · 3 min read [![Trent Blackburn](https://github.com/tablackburn.png)](https://tablackburn.github.io) [Trent Blackburn](https://tablackburn.github.io) Contributor [](https://github.com/tablackburn "GitHub")[](https://www.linkedin.com/in/trentblackburn/ "LinkedIn") In psake projects, a `build.ps1` is the conventional entry point script that wires everything together. It installs dependencies, configures the environment, and hands off to psake to run your actual build tasks. Think of it as the bootstrapper that gets a fresh machine — or a CI agent — from zero to a working build in a single command: `.\build.ps1`. psake itself doesn't ship a `build.ps1` — it's a best practice that most projects adopt. A typical starter script covers the basics: bootstrap installation, help output, build environment detection, and proper CI exit codes. But once you're running concurrent CI jobs, managing internal package feeds, or onboarding new contributors, a few gaps start to show. **Tags:** * [psake](/blog/tags/psake.md "psake build automation framework content") * [PowerShell](/blog/tags/powershell.md "PowerShell scripting and automation content") * [Build Automation](/blog/tags/build-automation.md "Automated build processes and continuous integration") * [CI/CD](/blog/tags/ci-cd.md "Continuous Integration and Continuous Deployment") * [Best Practices](/blog/tags/best-practices.md "Recommended approaches and industry standards") [**Read more**](/blog/resilient-build-ps1.md) --- ## [psake 5.0.0 Is Here: Declarative Tasks, Caching, and Structured Output](/blog/psake-5-0-0-released.md) April 15, 2026 · 6 min read [![Gilbert Sanchez](https://github.com/heyitsgilbert.png)](https://gilbertsanchez.com) [Gilbert Sanchez](https://gilbertsanchez.com) Core Contributor [](https://x.com/HeyItsGilbertS "X")[](https://www.linkedin.com/in/gilbertsanchez/ "LinkedIn")[](https://github.com/HeyItsGilbert "GitHub") After years of steady iteration on the v4 line, we're thrilled to announce the release of **psake 5.0.0**—the biggest update to the PowerShell build automation framework since its inception. This release introduces a declarative task syntax, two-phase compilation, local file-based caching, structured output, and first-class CI integration. Here's everything that's new. **Tags:** * [Release](/blog/tags/release.md "Product releases and version announcements") * [Announcement](/blog/tags/announcement.md "Important announcements and updates") * [psake](/blog/tags/psake.md "psake build automation framework content") * [PowerShell](/blog/tags/powershell.md "PowerShell scripting and automation content") * [Build Automation](/blog/tags/build-automation.md "Automated build processes and continuous integration") * [CI/CD](/blog/tags/ci-cd.md "Continuous Integration and Continuous Deployment") [**Read more**](/blog/psake-5-0-0-released.md) --- ## [psake 5.0.0 Is Here: Declarative Tasks, Caching, and Structured Output](/blog/psake-5-0-0-released.md) April 15, 2026 · 6 min read [![Gilbert Sanchez](https://github.com/heyitsgilbert.png)](https://gilbertsanchez.com) [Gilbert Sanchez](https://gilbertsanchez.com) Core Contributor [](https://x.com/HeyItsGilbertS "X")[](https://www.linkedin.com/in/gilbertsanchez/ "LinkedIn")[](https://github.com/HeyItsGilbert "GitHub") After years of steady iteration on the v4 line, we're thrilled to announce the release of **psake 5.0.0**—the biggest update to the PowerShell build automation framework since its inception. This release introduces a declarative task syntax, two-phase compilation, local file-based caching, structured output, and first-class CI integration. Here's everything that's new. **Tags:** * [Release](/blog/tags/release.md "Product releases and version announcements") * [Announcement](/blog/tags/announcement.md "Important announcements and updates") * [psake](/blog/tags/psake.md "psake build automation framework content") * [PowerShell](/blog/tags/powershell.md "PowerShell scripting and automation content") * [Build Automation](/blog/tags/build-automation.md "Automated build processes and continuous integration") * [CI/CD](/blog/tags/ci-cd.md "Continuous Integration and Continuous Deployment") [**Read more**](/blog/psake-5-0-0-released.md) --- ## [PowerShellBuild v0.8.0: Authenticode Signing & New Documentation](/blog/powershellbuild-v0.8.0-signing-and-docs.md) February 21, 2026 · 3 min read [![Gilbert Sanchez](https://github.com/heyitsgilbert.png)](https://gilbertsanchez.com) [Gilbert Sanchez](https://gilbertsanchez.com) Core Contributor [](https://x.com/HeyItsGilbertS "X")[](https://www.linkedin.com/in/gilbertsanchez/ "LinkedIn")[](https://github.com/HeyItsGilbert "GitHub") Two exciting updates to share today: **PowerShellBuild v0.8.0** has been released with built-in Authenticode code-signing support, and we've added a comprehensive [PowerShellBuild documentation section](/docs/powershellbuild/introduction.md) right here on the psake docs site. **Tags:** * [Announcement](/blog/tags/announcement.md "Important announcements and updates") * [Release](/blog/tags/release.md "Product releases and version announcements") * [PowerShell](/blog/tags/powershell.md "PowerShell scripting and automation content") * [Build Automation](/blog/tags/build-automation.md "Automated build processes and continuous integration") * [psake](/blog/tags/psake.md "psake build automation framework content") * [Deployment](/blog/tags/deployment.md "Application deployment and release management") [**Read more**](/blog/powershellbuild-v0.8.0-signing-and-docs.md) --- ## [psake 5.0.0 Is Here: Declarative Tasks, Caching, and Structured Output](/blog/psake-5-0-0-released.md) April 15, 2026 · 6 min read [![Gilbert Sanchez](https://github.com/heyitsgilbert.png)](https://gilbertsanchez.com) [Gilbert Sanchez](https://gilbertsanchez.com) Core Contributor [](https://x.com/HeyItsGilbertS "X")[](https://www.linkedin.com/in/gilbertsanchez/ "LinkedIn")[](https://github.com/HeyItsGilbert "GitHub") After years of steady iteration on the v4 line, we're thrilled to announce the release of **psake 5.0.0**—the biggest update to the PowerShell build automation framework since its inception. This release introduces a declarative task syntax, two-phase compilation, local file-based caching, structured output, and first-class CI integration. Here's everything that's new. **Tags:** * [Release](/blog/tags/release.md "Product releases and version announcements") * [Announcement](/blog/tags/announcement.md "Important announcements and updates") * [psake](/blog/tags/psake.md "psake build automation framework content") * [PowerShell](/blog/tags/powershell.md "PowerShell scripting and automation content") * [Build Automation](/blog/tags/build-automation.md "Automated build processes and continuous integration") * [CI/CD](/blog/tags/ci-cd.md "Continuous Integration and Continuous Deployment") [**Read more**](/blog/psake-5-0-0-released.md) --- ## [psake 5.0.0 Is Here: Declarative Tasks, Caching, and Structured Output](/blog/psake-5-0-0-released.md) April 15, 2026 · 6 min read [![Gilbert Sanchez](https://github.com/heyitsgilbert.png)](https://gilbertsanchez.com) [Gilbert Sanchez](https://gilbertsanchez.com) Core Contributor [](https://x.com/HeyItsGilbertS "X")[](https://www.linkedin.com/in/gilbertsanchez/ "LinkedIn")[](https://github.com/HeyItsGilbert "GitHub") After years of steady iteration on the v4 line, we're thrilled to announce the release of **psake 5.0.0**—the biggest update to the PowerShell build automation framework since its inception. This release introduces a declarative task syntax, two-phase compilation, local file-based caching, structured output, and first-class CI integration. Here's everything that's new. **Tags:** * [Release](/blog/tags/release.md "Product releases and version announcements") * [Announcement](/blog/tags/announcement.md "Important announcements and updates") * [psake](/blog/tags/psake.md "psake build automation framework content") * [PowerShell](/blog/tags/powershell.md "PowerShell scripting and automation content") * [Build Automation](/blog/tags/build-automation.md "Automated build processes and continuous integration") * [CI/CD](/blog/tags/ci-cd.md "Continuous Integration and Continuous Deployment") [**Read more**](/blog/psake-5-0-0-released.md) --- ## [psake 5.0.0 Is Here: Declarative Tasks, Caching, and Structured Output](/blog/psake-5-0-0-released.md) April 15, 2026 · 6 min read [![Gilbert Sanchez](https://github.com/heyitsgilbert.png)](https://gilbertsanchez.com) [Gilbert Sanchez](https://gilbertsanchez.com) Core Contributor [](https://x.com/HeyItsGilbertS "X")[](https://www.linkedin.com/in/gilbertsanchez/ "LinkedIn")[](https://github.com/HeyItsGilbert "GitHub") After years of steady iteration on the v4 line, we're thrilled to announce the release of **psake 5.0.0**—the biggest update to the PowerShell build automation framework since its inception. This release introduces a declarative task syntax, two-phase compilation, local file-based caching, structured output, and first-class CI integration. Here's everything that's new. **Tags:** * [Release](/blog/tags/release.md "Product releases and version announcements") * [Announcement](/blog/tags/announcement.md "Important announcements and updates") * [psake](/blog/tags/psake.md "psake build automation framework content") * [PowerShell](/blog/tags/powershell.md "PowerShell scripting and automation content") * [Build Automation](/blog/tags/build-automation.md "Automated build processes and continuous integration") * [CI/CD](/blog/tags/ci-cd.md "Continuous Integration and Continuous Deployment") [**Read more**](/blog/psake-5-0-0-released.md) --- ## [psake Meets VS Code: The Official Extension Hits v1.0](/blog/psake-vscode-extension-v1.md) March 10, 2026 · 4 min read [![Gilbert Sanchez](https://github.com/heyitsgilbert.png)](https://gilbertsanchez.com) [Gilbert Sanchez](https://gilbertsanchez.com) Core Contributor [](https://x.com/HeyItsGilbertS "X")[](https://www.linkedin.com/in/gilbertsanchez/ "LinkedIn")[](https://github.com/HeyItsGilbert "GitHub") We're thrilled to announce the v1.0 release of the [psake VS Code extension](https://github.com/psake/psake-vscode)—bringing first-class psake support directly into the world's most popular code editor. If you've been running psake from the terminal, you can now discover, navigate, and execute your build tasks without ever leaving VS Code. **Tags:** * [Announcement](/blog/tags/announcement.md "Important announcements and updates") * [Release](/blog/tags/release.md "Product releases and version announcements") * [psake](/blog/tags/psake.md "psake build automation framework content") * [PowerShell](/blog/tags/powershell.md "PowerShell scripting and automation content") * [VS Code](/blog/tags/vscode.md "Visual Studio Code extension and editor integration") [**Read more**](/blog/psake-vscode-extension-v1.md) --- [Skip to main content](#__docusaurus_skipToContent_fallback) [![psake Logo](/img/logo.svg)![psake Logo](/img/logo.svg)](/index.md) [**psake**](/index.md)[Get Started](/docs/intro.md)[Guides](/docs/tutorial-basics/default-build-files.md)[PowerShellBuild](/docs/powershellbuild/introduction.md)[Reference](/docs/commands/Assert.md)[Blog](/blog.md) [GitHub](https://github.com/psake/psake) Search # Search the documentation Type your search here Powered by[](https://www.algolia.com/) Docs * [Get Started](/docs/intro.md) * [Guides](/docs/tutorial-basics/default-build-files.md) * [PowerShellBuild](/docs/powershellbuild/introduction.md) * [Command Reference](/docs/commands/Invoke-Psake.md) * [Code of Conduct](/docs/code_of_conduct.md) Community * [PowerShell Discord: #psake](https://aka.ms/psdiscord) * [PowerShell Slack: #psake](https://aka.ms/psslack) * [GitHub Discussions](https://github.com/orgs/psake/discussions) * [Stack Overflow](https://stackoverflow.com/questions/tagged/psake) More * [Blog](/blog.md) * [GitHub](https://github.com/psake) * [OpenCollective - Donate!](https://opencollective.com/psake) * [![Deploys by Netlify](https://www.netlify.com/v3/img/components/netlify-color-accent.svg)](https://www.netlify.com) Copyright © 2026 psake Org. Built with Docusaurus. --- # Environment Management Managing multiple environments (development, staging, production) is crucial for reliable software delivery. This guide shows you how to configure psake builds for different environments using properties, configuration files, and conditional task execution. ## Quick Start[​](#quick-start "Direct link to Quick Start") Here's a basic environment-aware build: ``` Properties { $Environment = if ($env:BUILD_ENV) { $env:BUILD_ENV } else { 'dev' } $Configuration = if ($Environment -eq 'prod') { 'Release' } else { 'Debug' } # Environment-specific settings $ApiUrl = switch ($Environment) { 'dev' { 'https://api.dev.example.com' } 'staging' { 'https://api.staging.example.com' } 'prod' { 'https://api.example.com' } } } Task Build { Write-Host "Building for environment: $Environment" -ForegroundColor Green Write-Host " Configuration: $Configuration" -ForegroundColor Gray Write-Host " API URL: $ApiUrl" -ForegroundColor Gray exec { dotnet build -c $Configuration /p:ApiUrl=$ApiUrl } } ``` Run for different environments: ``` # Development (default) Invoke-psake # Staging $env:BUILD_ENV = 'staging' Invoke-psake # Production $env:BUILD_ENV = 'prod' Invoke-psake ``` ## Environment Configuration Patterns[​](#environment-configuration-patterns "Direct link to Environment Configuration Patterns") ### Pattern 1: Inline Environment Properties[​](#pattern-1-inline-environment-properties "Direct link to Pattern 1: Inline Environment Properties") Simple projects with few environment differences: ``` Properties { $Environment = if ($env:BUILD_ENV) { $env:BUILD_ENV } else { 'dev' } $environmentSettings = @{ dev = @{ Configuration = 'Debug' DatabaseServer = 'localhost' ApiUrl = 'http://localhost:5000' EnableTelemetry = $false LogLevel = 'Debug' } staging = @{ Configuration = 'Release' DatabaseServer = 'db-staging.internal' ApiUrl = 'https://api-staging.example.com' EnableTelemetry = $true LogLevel = 'Information' } prod = @{ Configuration = 'Release' DatabaseServer = 'db-prod.internal' ApiUrl = 'https://api.example.com' EnableTelemetry = $true LogLevel = 'Warning' } } if (-not $environmentSettings.ContainsKey($Environment)) { throw "Unknown environment: $Environment" } $settings = $environmentSettings[$Environment] $Configuration = $settings.Configuration $DatabaseServer = $settings.DatabaseServer $ApiUrl = $settings.ApiUrl $EnableTelemetry = $settings.EnableTelemetry $LogLevel = $settings.LogLevel } Task Build { Write-Host "Building for: $Environment" -ForegroundColor Cyan Write-Host " Configuration: $Configuration" -ForegroundColor Gray Write-Host " Database: $DatabaseServer" -ForegroundColor Gray Write-Host " API: $ApiUrl" -ForegroundColor Gray Write-Host " Telemetry: $EnableTelemetry" -ForegroundColor Gray Write-Host " Log Level: $LogLevel" -ForegroundColor Gray exec { dotnet build -c $Configuration ` /p:DatabaseServer=$DatabaseServer ` /p:ApiUrl=$ApiUrl ` /p:EnableTelemetry=$EnableTelemetry ` /p:LogLevel=$LogLevel } } ``` ### Pattern 2: External Configuration Files[​](#pattern-2-external-configuration-files "Direct link to Pattern 2: External Configuration Files") For complex projects with many environment-specific settings: ``` my-project/ ├── build/ │ └── config/ │ ├── dev.ps1 │ ├── staging.ps1 │ └── prod.ps1 └── psakefile.ps1 ``` **build/config/dev.ps1:** ``` # Development environment configuration Properties { # Build settings $Configuration = 'Debug' $Platform = 'AnyCPU' $SkipTests = $false # Infrastructure $DatabaseServer = 'localhost' $DatabaseName = 'MyApp_Dev' $RedisServer = 'localhost:6379' # API endpoints $ApiBaseUrl = 'http://localhost:5000' $AuthServiceUrl = 'http://localhost:5001' # Feature flags $EnableCaching = $false $EnableTelemetry = $false $EnableAuthentication = $false # Logging $LogLevel = 'Debug' $LogToFile = $true $LogToConsole = $true # Deployment $DeploymentTarget = 'local' $SkipHealthChecks = $true } ``` **build/config/staging.ps1:** ``` # Staging environment configuration Properties { # Build settings $Configuration = 'Release' $Platform = 'AnyCPU' $SkipTests = $false # Infrastructure $DatabaseServer = 'db-staging.internal.example.com' $DatabaseName = 'MyApp_Staging' $RedisServer = 'redis-staging.internal.example.com:6379' # API endpoints $ApiBaseUrl = 'https://api-staging.example.com' $AuthServiceUrl = 'https://auth-staging.example.com' # Feature flags $EnableCaching = $true $EnableTelemetry = $true $EnableAuthentication = $true # Logging $LogLevel = 'Information' $LogToFile = $true $LogToConsole = $false # Deployment $DeploymentTarget = 'azure-staging' $SkipHealthChecks = $false $AzureResourceGroup = 'rg-myapp-staging' $AzureWebAppName = 'myapp-staging' } ``` **build/config/prod.ps1:** ``` # Production environment configuration Properties { # Build settings $Configuration = 'Release' $Platform = 'AnyCPU' $SkipTests = $false # Infrastructure $DatabaseServer = 'db-prod.internal.example.com' $DatabaseName = 'MyApp_Production' $RedisServer = 'redis-prod.internal.example.com:6379' # API endpoints $ApiBaseUrl = 'https://api.example.com' $AuthServiceUrl = 'https://auth.example.com' # Feature flags $EnableCaching = $true $EnableTelemetry = $true $EnableAuthentication = $true # Logging $LogLevel = 'Warning' $LogToFile = $true $LogToConsole = $false # Deployment $DeploymentTarget = 'azure-production' $SkipHealthChecks = $false $AzureResourceGroup = 'rg-myapp-prod' $AzureWebAppName = 'myapp-prod' $RequireApproval = $true } ``` **psakefile.ps1:** ``` $selectedEnvironment = if ($env:BUILD_ENV) { $env:BUILD_ENV } else { 'dev' } $configDirectory = Join-Path $PSScriptRoot 'build/config' $environmentConfig = Join-Path $configDirectory "${selectedEnvironment}.ps1" if (-not (Test-Path $environmentConfig)) { throw "Environment configuration not found: $environmentConfig. Valid environments: dev, staging, prod" } Write-Host "Loading configuration for: $selectedEnvironment" -ForegroundColor Cyan Include $environmentConfig Properties { $ProjectRoot = $PSScriptRoot $BuildDir = Join-Path $ProjectRoot 'build/output' $Environment = if ($env:BUILD_ENV) { $env:BUILD_ENV } else { 'dev' } $ConfigDir = Join-Path $ProjectRoot 'build/config' } Task Default -depends Build Task Build { Write-Host "Building for $Environment environment..." -ForegroundColor Green Write-Host " Configuration: $Configuration" -ForegroundColor Gray Write-Host " Database: $DatabaseServer/$DatabaseName" -ForegroundColor Gray Write-Host " API: $ApiBaseUrl" -ForegroundColor Gray exec { dotnet build -c $Configuration } } function Deploy-AzureWebApp { $packageFile = Get-ChildItem "$BuildDir/*.zip" | Select-Object -First 1 if (-not $packageFile) { throw "Deployment package not found in $BuildDir" } exec { az webapp deploy ` --resource-group $AzureResourceGroup ` --name $AzureWebAppName ` --src-path $packageFile.FullName ` --type zip } } Task Deploy -depends Build { if ($RequireApproval -and $env:DEPLOY_APPROVED -ne 'true') { throw "Deployment to $Environment requires DEPLOY_APPROVED=true" } switch ($DeploymentTarget) { 'local' { Copy-Item "$BuildDir/*" -Destination "C:\Deploy\$Environment" -Recurse -Force } 'azure-staging' { Deploy-AzureWebApp } 'azure-production' { Deploy-AzureWebApp } default { throw "Unknown deployment target: $DeploymentTarget" } } } ``` ### Pattern 3: JSON/YAML Configuration Files[​](#pattern-3-jsonyaml-configuration-files "Direct link to Pattern 3: JSON/YAML Configuration Files") Use structured configuration files for complex settings: **build/config/environments.json:** ``` { "dev": { "configuration": "Debug", "database": { "server": "localhost", "name": "MyApp_Dev", "port": 5432 }, "services": { "api": "http://localhost:5000", "auth": "http://localhost:5001" }, "features": { "caching": false, "telemetry": false } }, "staging": { "configuration": "Release", "database": { "server": "db-staging.internal.example.com", "name": "MyApp_Staging", "port": 5432 }, "services": { "api": "https://api-staging.example.com", "auth": "https://auth-staging.example.com" }, "features": { "caching": true, "telemetry": true } }, "prod": { "configuration": "Release", "database": { "server": "db-prod.internal.example.com", "name": "MyApp_Production", "port": 5432 }, "services": { "api": "https://api.example.com", "auth": "https://auth.example.com" }, "features": { "caching": true, "telemetry": true } } } ``` **build/config/environments.yaml:** ``` dev: configuration: Debug services: api: http://localhost:5000 staging: configuration: Release services: api: https://api-staging.example.com prod: configuration: Release services: api: https://api.example.com ``` For YAML, install and import `powershell-yaml`, then replace the JSON-loading expression with: ``` Import-Module powershell-yaml $configurationFile = Join-Path $PSScriptRoot 'build/config/environments.yaml' $allConfigs = Get-Content $configurationFile -Raw | ConvertFrom-Yaml ``` **psakefile.ps1:** ``` $selectedEnvironment = if ($env:BUILD_ENV) { $env:BUILD_ENV } else { 'dev' } $configurationFile = Join-Path $PSScriptRoot 'build/config/environments.json' if (-not (Test-Path $configurationFile)) { throw "Configuration file not found: $configurationFile" } $allConfigs = Get-Content $configurationFile -Raw | ConvertFrom-Json $selectedConfig = $allConfigs.$selectedEnvironment if ($null -eq $selectedConfig) { throw "Configuration for environment '$selectedEnvironment' not found in $configurationFile" } Properties { $ProjectRoot = $PSScriptRoot $Environment = if ($env:BUILD_ENV) { $env:BUILD_ENV } else { 'dev' } $ConfigFile = Join-Path $ProjectRoot 'build/config/environments.json' $Configuration = $selectedConfig.configuration $DatabaseServer = $selectedConfig.database.server $DatabaseName = $selectedConfig.database.name $DatabasePort = $selectedConfig.database.port $ApiUrl = $selectedConfig.services.api $AuthUrl = $selectedConfig.services.auth $EnableCaching = $selectedConfig.features.caching $EnableTelemetry = $selectedConfig.features.telemetry } Task Build { Write-Host "Building with configuration from: $ConfigFile" -ForegroundColor Green Write-Host " Environment: $Environment" -ForegroundColor Cyan Write-Host " Configuration: $Configuration" -ForegroundColor Gray Write-Host " Database: ${DatabaseServer}:${DatabasePort}/${DatabaseName}" -ForegroundColor Gray Write-Host " API: $ApiUrl" -ForegroundColor Gray # Generate configuration file for application $appConfig = @{ ConnectionStrings = @{ DefaultConnection = "Server=$DatabaseServer;Port=$DatabasePort;Database=$DatabaseName;" } Services = @{ ApiBaseUrl = $ApiUrl AuthServiceUrl = $AuthUrl } Features = @{ EnableCaching = $EnableCaching EnableTelemetry = $EnableTelemetry } } $appConfigPath = Join-Path $ProjectRoot "src/appsettings.$Environment.json" $appConfig | ConvertTo-Json -Depth 10 | Set-Content $appConfigPath exec { dotnet build -c $Configuration } } ``` ## Conditional Task Execution[​](#conditional-task-execution "Direct link to Conditional Task Execution") Execute tasks based on environment: ### Using Preconditions[​](#using-preconditions "Direct link to Using Preconditions") ``` Properties { $Environment = if ($env:BUILD_ENV) { $env:BUILD_ENV } else { 'dev' } } Task RunTests { exec { dotnet test } } Task DeployToStaging -depends Build -precondition { $Environment -eq 'staging' } { Write-Host "Deploying to staging..." -ForegroundColor Green # Staging deployment logic } Task DeployToProduction -depends Build -precondition { $Environment -eq 'prod' } { Write-Host "Deploying to production..." -ForegroundColor Green # Production deployment logic # Additional production-only verification exec { dotnet test --filter Category=Smoke } } Task TestOutsideDevelopment -precondition { $Environment -ne 'dev' } { exec { dotnet test } } ``` ### Environment-Specific Task Lists[​](#environment-specific-task-lists "Direct link to Environment-Specific Task Lists") ``` Properties { $Environment = if ($env:BUILD_ENV) { $env:BUILD_ENV } else { 'dev' } } Task Build -depends Clean, Compile Task Dev -depends Build, RunDevServer { Write-Host "Development build complete" -ForegroundColor Green } Task Staging -depends Build, RunTests, Package, DeployStaging { Write-Host "Staging deployment complete" -ForegroundColor Green } Task Production -depends Build, RunAllTests, SecurityScan, Package, DeployProduction { Write-Host "Production deployment complete" -ForegroundColor Green } $environmentTasks = @{ dev = 'Dev' staging = 'Staging' prod = 'Production' } $selectedEnvironment = if ($env:BUILD_ENV) { $env:BUILD_ENV } else { 'dev' } if (-not $environmentTasks.ContainsKey($selectedEnvironment)) { throw "Unknown environment: $selectedEnvironment" } Task Default -depends $environmentTasks[$selectedEnvironment] ``` ### Conditional Build Steps[​](#conditional-build-steps "Direct link to Conditional Build Steps") ``` Task Build { # Always compile exec { dotnet build -c $Configuration } # Environment-specific build steps if ($Environment -eq 'prod') { Write-Host "Running production-specific optimizations..." -ForegroundColor Cyan # Minify JavaScript/CSS exec { npm run minify } # Optimize images exec { npm run optimize-images } # Generate source maps exec { npm run sourcemaps } } if ($Environment -ne 'dev') { Write-Host "Running AOT compilation..." -ForegroundColor Cyan exec { dotnet publish -c $Configuration /p:PublishAot=true } } if ($EnableTelemetry) { $env:OTEL_SERVICE_NAME = 'MyApp' $env:OTEL_RESOURCE_ATTRIBUTES = "deployment.environment.name=$Environment" Write-Host "Configured OpenTelemetry resource attributes" -ForegroundColor Cyan } } ``` ## Complete Environment Management Example[​](#complete-environment-management-example "Direct link to Complete Environment Management Example") Here's a comprehensive example combining all patterns: **psakefile.ps1:** ``` $selectedEnvironment = if ($env:BUILD_ENV) { $env:BUILD_ENV } else { 'dev' } $validEnvironments = @('dev', 'staging', 'prod') if ($selectedEnvironment -notin $validEnvironments) { throw "Invalid environment: $selectedEnvironment. Valid options: $($validEnvironments -join ', ')" } $configDirectory = Join-Path $PSScriptRoot 'build/config' $environmentConfig = Join-Path $configDirectory "${selectedEnvironment}.ps1" if (-not (Test-Path $environmentConfig)) { throw "Environment configuration not found: $environmentConfig" } Write-Host "Loading environment configuration: $selectedEnvironment" -ForegroundColor Cyan Include $environmentConfig Properties { $ProjectRoot = $PSScriptRoot $SrcDir = Join-Path $ProjectRoot 'src' $BuildDir = Join-Path $ProjectRoot 'build/output' $ConfigDir = Join-Path $ProjectRoot 'build/config' $Environment = if ($env:BUILD_ENV) { $env:BUILD_ENV } else { 'dev' } } FormatTaskName { param($taskName) Write-Host "" Write-Host "[$Environment] Executing: $taskName" -ForegroundColor Cyan Write-Host ("=" * 80) -ForegroundColor Gray } Task Default -depends Build Task Clean { Write-Host "Cleaning build artifacts..." -ForegroundColor Green if (Test-Path $BuildDir) { Remove-Item $BuildDir -Recurse -Force } New-Item -ItemType Directory -Path $BuildDir | Out-Null } Task Compile -depends Clean { Write-Host "Compiling for $Environment..." -ForegroundColor Green Write-Host " Configuration: $Configuration" -ForegroundColor Gray exec { dotnet build $SrcDir ` -c $Configuration ` -o $BuildDir ` /p:Environment=$Environment } } Task Test -depends Compile -precondition { -not $SkipTests } { Write-Host "Running tests..." -ForegroundColor Green exec { dotnet test $SrcDir ` --configuration $Configuration ` --no-build } } Task IntegrationTests -depends Test -precondition { $Environment -ne 'dev' } { Write-Host "Running integration tests..." -ForegroundColor Green exec { dotnet test $SrcDir ` --filter "Category=Integration" ` --configuration $Configuration } } Task SecurityScan -depends Compile -precondition { $Environment -eq 'prod' } { Write-Host "Running security scan..." -ForegroundColor Green # Run security scanning tools exec { dotnet tool run security-scan } } Task Package -depends Test { Write-Host "Creating deployment package..." -ForegroundColor Green $packageName = "MyApp-${Environment}-$(Get-Date -Format 'yyyyMMdd-HHmmss').zip" $packagePath = Join-Path $BuildDir $packageName Compress-Archive -Path "$BuildDir/*" -DestinationPath $packagePath Write-Host "Package created: $packagePath" -ForegroundColor Green } function Test-DeploymentHealth { if ($SkipHealthChecks) { return } $response = Invoke-WebRequest -Uri "$ApiBaseUrl/health" -TimeoutSec 30 if ($response.StatusCode -ne 200) { throw "Health check failed with status: $($response.StatusCode)" } Write-Host " Health check passed" -ForegroundColor Green } Task Deploy -depends Package { if ($RequireApproval -and $env:DEPLOY_APPROVED -ne 'true') { throw "Deployment to $Environment requires DEPLOY_APPROVED=true" } Write-Host "Deploying to $DeploymentTarget..." -ForegroundColor Green switch ($DeploymentTarget) { 'local' { Copy-Item "$BuildDir/*" -Destination "C:\Deploy\$Environment" -Recurse -Force } { $_ -in 'azure-staging', 'azure-production' } { $packageFile = Get-ChildItem "$BuildDir/*.zip" | Select-Object -First 1 if (-not $packageFile) { throw "Deployment package not found in $BuildDir" } exec { az webapp deploy ` --resource-group $AzureResourceGroup ` --name $AzureWebAppName ` --src-path $packageFile.FullName ` --type zip } if ($DeploymentTarget -eq 'azure-production') { Start-Sleep -Seconds 10 Test-DeploymentHealth } } default { throw "Unknown deployment target: $DeploymentTarget" } } Write-Host "Deployment to $Environment complete!" -ForegroundColor Green } Task HealthCheck -precondition { -not $SkipHealthChecks } { Write-Host "Running health checks..." -ForegroundColor Green $healthUrl = "$ApiBaseUrl/health" try { $response = Invoke-WebRequest -Uri $healthUrl -TimeoutSec 30 if ($response.StatusCode -eq 200) { Write-Host " Health check passed" -ForegroundColor Green } else { throw "Health check failed with status: $($response.StatusCode)" } } catch { throw "Health check failed: $_" } } Task ShowConfig { Write-Host "" Write-Host "Current Environment Configuration" -ForegroundColor Cyan Write-Host ("=" * 80) -ForegroundColor Gray Write-Host " Environment: $Environment" -ForegroundColor White Write-Host " Configuration: $Configuration" -ForegroundColor Gray Write-Host " Database: $DatabaseServer/$DatabaseName" -ForegroundColor Gray Write-Host " API Base URL: $ApiBaseUrl" -ForegroundColor Gray Write-Host " Auth Service: $AuthServiceUrl" -ForegroundColor Gray Write-Host " Enable Caching: $EnableCaching" -ForegroundColor Gray Write-Host " Enable Telemetry: $EnableTelemetry" -ForegroundColor Gray Write-Host " Log Level: $LogLevel" -ForegroundColor Gray Write-Host " Deployment Target: $DeploymentTarget" -ForegroundColor Gray Write-Host " Skip Tests: $SkipTests" -ForegroundColor Gray Write-Host ("=" * 80) -ForegroundColor Gray Write-Host "" } ``` ## CI/CD Integration[​](#cicd-integration "Direct link to CI/CD Integration") ### GitHub Actions[​](#github-actions "Direct link to GitHub Actions") ``` name: Multi-Environment Build on: push: branches: [main, develop] pull_request: branches: [main] jobs: build-dev: runs-on: windows-latest steps: - uses: actions/checkout@v4 - name: Install psake shell: pwsh run: Install-Module -Name psake -Force - name: Build for Development shell: pwsh run: Invoke-psake -buildFile .\psakefile.ps1 -taskList Build env: BUILD_ENV: dev build-staging: runs-on: windows-latest if: github.ref == 'refs/heads/develop' steps: - uses: actions/checkout@v4 - name: Install psake shell: pwsh run: Install-Module -Name psake -Force - name: Sign in to Azure uses: azure/login@v2 with: creds: ${{ secrets.AZURE_CREDENTIALS_STAGING }} - name: Build and Deploy to Staging shell: pwsh run: Invoke-psake -buildFile .\psakefile.ps1 -taskList Deploy env: BUILD_ENV: staging DEPLOY_APPROVED: 'true' build-production: runs-on: windows-latest if: github.ref == 'refs/heads/main' environment: production steps: - uses: actions/checkout@v4 - name: Install psake shell: pwsh run: Install-Module -Name psake -Force - name: Sign in to Azure uses: azure/login@v2 with: creds: ${{ secrets.AZURE_CREDENTIALS_PROD }} - name: Build and Deploy to Production shell: pwsh run: Invoke-psake -buildFile .\psakefile.ps1 -taskList Deploy env: BUILD_ENV: prod DEPLOY_APPROVED: 'true' ``` ### Azure Pipelines[​](#azure-pipelines "Direct link to Azure Pipelines") Use an Azure Resource Manager service connection so credentials never enter the build script: ``` trigger: branches: include: - main - develop stages: - stage: Build jobs: - job: Build pool: vmImage: windows-latest steps: - checkout: self - pwsh: Install-Module -Name psake -Force displayName: Install psake - pwsh: Invoke-psake -BuildFile .\psakefile.ps1 -TaskList Build displayName: Build env: BUILD_ENV: dev - stage: DeployProduction dependsOn: Build condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main')) jobs: - deployment: Deploy environment: production pool: vmImage: windows-latest strategy: runOnce: deploy: steps: - checkout: self - task: AzureCLI@2 inputs: azureSubscription: MyProductionServiceConnection scriptType: pscore scriptLocation: inlineScript inlineScript: | Install-Module -Name psake -Force Invoke-psake -BuildFile .\psakefile.ps1 -TaskList Deploy env: BUILD_ENV: prod DEPLOY_APPROVED: 'true' ``` ## Best Practices[​](#best-practices "Direct link to Best Practices") 1. **Use environment variables** - Set `BUILD_ENV` via environment variables, not hardcoded 2. **Validate early** - Check environment names at the start of the build 3. **Externalize configuration** - Use separate config files for complex environments 4. **Default to development** - Make the safest environment (dev) the default 5. **Require approval for production** - Use protected CI environments and a noninteractive approval signal 6. **Use preconditions** - Leverage psake preconditions for environment-specific tasks 7. **Keep secrets separate** - Never put secrets in environment config files (see [Secret Management](/docs/best-practices/secret-management.md)) 8. **Test all environments** - Validate builds for all environments in CI/CD 9. **Document environment settings** - Maintain clear documentation of environment differences 10. **Use consistent naming** - Stick to standard names: dev, staging, prod ## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") ### Environment Not Loading[​](#environment-not-loading "Direct link to Environment Not Loading") **Problem:** Environment configuration not applied **Solution:** Check environment variable and file paths: ``` Task Debug:ShowEnvironment { $environmentConfig = Join-Path $PSScriptRoot "build/config/$Environment.ps1" Write-Host "BUILD_ENV: $($env:BUILD_ENV)" -ForegroundColor Yellow Write-Host "Environment: $Environment" -ForegroundColor Yellow Write-Host "Config File: $environmentConfig" -ForegroundColor Yellow Write-Host "File Exists: $(Test-Path $environmentConfig)" -ForegroundColor Yellow } ``` ### Wrong Configuration Applied[​](#wrong-configuration-applied "Direct link to Wrong Configuration Applied") **Problem:** Production settings used in development **Solution:** Add validation and defaults: ``` Properties { $Environment = if ($env:BUILD_ENV) { $env:BUILD_ENV } else { 'dev' } # Validate environment if ($Environment -notin @('dev', 'staging', 'prod')) { throw "Invalid environment: $Environment" } # Safety check - prevent accidental production deployments if ($Environment -eq 'prod' -and -not $env:ALLOW_PROD_DEPLOY) { throw "Production deployment requires ALLOW_PROD_DEPLOY=true" } } ``` ## See Also[​](#see-also "Direct link to See Also") * [Secret Management](/docs/best-practices/secret-management.md) - Handling secrets and credentials * [Organizing Large Scripts](/docs/best-practices/organizing-large-scripts.md) - Modular build organization * [Parameters and Properties](/docs/tutorial-basics/parameters-properties.md) - Using psake properties * [GitHub Actions](/docs/ci-examples/github-actions.md) - CI/CD integration examples * [Azure Pipelines](/docs/ci-examples/azure-pipelines.md) - Azure DevOps integration --- # Organizing Large Build Scripts As your project grows, build scripts can become complex and difficult to maintain. This guide shows you how to organize large psake builds using modular task files, includes, shared utilities, and clear file structures. ## Quick Start[​](#quick-start "Direct link to Quick Start") Here's a basic modular build structure: ``` my-project/ ├── build/ │ ├── tasks/ │ │ ├── build.ps1 │ │ ├── test.ps1 │ │ └── deploy.ps1 │ └── utils/ │ └── helpers.ps1 ├── psakefile.ps1 └── build.ps1 ``` Main `psakefile.ps1`: ``` Properties { $ProjectRoot = $PSScriptRoot } # Include paths must be resolved while the build file is loaded. $tasksDirectory = Join-Path $PSScriptRoot 'build/tasks' Include (Join-Path $tasksDirectory 'build.ps1') Include (Join-Path $tasksDirectory 'test.ps1') Include (Join-Path $tasksDirectory 'deploy.ps1') Task Default -depends Build, Test ``` ## File Structure Patterns[​](#file-structure-patterns "Direct link to File Structure Patterns") ### Pattern 1: Tasks by Category[​](#pattern-1-tasks-by-category "Direct link to Pattern 1: Tasks by Category") Organize tasks by functional area: ``` my-project/ ├── build/ │ ├── tasks/ │ │ ├── compile.ps1 # Compilation tasks │ │ ├── test.ps1 # Testing tasks │ │ ├── package.ps1 # Packaging tasks │ │ ├── deploy.ps1 # Deployment tasks │ │ └── cleanup.ps1 # Cleanup tasks │ ├── utils/ │ │ ├── fileops.ps1 # File operations │ │ ├── versioning.ps1 # Version management │ │ └── logging.ps1 # Custom logging │ └── config/ │ ├── dev.ps1 # Development config │ ├── staging.ps1 # Staging config │ └── prod.ps1 # Production config ├── psakefile.ps1 # Main orchestrator └── build.ps1 # Bootstrap script ``` **psakefile.ps1:** ``` $buildRoot = Join-Path $PSScriptRoot 'build' $utilsDirectory = Join-Path $buildRoot 'utils' $tasksDirectory = Join-Path $buildRoot 'tasks' $configDirectory = Join-Path $buildRoot 'config' $selectedEnvironment = if ($env:BUILD_ENV) { $env:BUILD_ENV } else { 'dev' } # Includes are resolved while the build file is loaded. Include (Join-Path $utilsDirectory 'logging.ps1') Include (Join-Path $utilsDirectory 'fileops.ps1') Include (Join-Path $utilsDirectory 'versioning.ps1') Include (Join-Path $configDirectory "${selectedEnvironment}.ps1") Include (Join-Path $tasksDirectory 'compile.ps1') Include (Join-Path $tasksDirectory 'test.ps1') Include (Join-Path $tasksDirectory 'package.ps1') Include (Join-Path $tasksDirectory 'deploy.ps1') Include (Join-Path $tasksDirectory 'cleanup.ps1') Properties { $ProjectRoot = $PSScriptRoot $BuildRoot = Join-Path $ProjectRoot 'build' $Environment = if ($env:BUILD_ENV) { $env:BUILD_ENV } else { 'dev' } $Configuration = 'Release' } FormatTaskName { param($taskName) Write-LogHeader "Executing: $taskName" } Task Default -depends Build Task Build -depends Compile, Test, Package Task CI -depends Build, Deploy Task Full -depends Clean, Build, Deploy ``` ### Pattern 2: Tasks by Build Type[​](#pattern-2-tasks-by-build-type "Direct link to Pattern 2: Tasks by Build Type") For projects with multiple build types (library, service, tools): ``` my-project/ ├── build/ │ ├── tasks/ │ │ ├── library/ │ │ │ ├── build.ps1 │ │ │ ├── test.ps1 │ │ │ └── publish.ps1 │ │ ├── service/ │ │ │ ├── build.ps1 │ │ │ ├── docker.ps1 │ │ │ └── deploy.ps1 │ │ └── tools/ │ │ ├── build.ps1 │ │ └── package.ps1 │ └── shared/ │ └── common.ps1 └── psakefile.ps1 ``` **psakefile.ps1:** ``` $buildRoot = Join-Path $PSScriptRoot 'build' $buildType = if ($env:BUILD_TYPE) { $env:BUILD_TYPE } else { 'all' } $buildTasks = @() Include (Join-Path $buildRoot 'shared/common.ps1') if ($buildType -in 'library', 'all') { Include (Join-Path $buildRoot 'tasks/library/build.ps1') Include (Join-Path $buildRoot 'tasks/library/test.ps1') Include (Join-Path $buildRoot 'tasks/library/publish.ps1') $buildTasks += 'Library:Build' } if ($buildType -in 'service', 'all') { Include (Join-Path $buildRoot 'tasks/service/build.ps1') Include (Join-Path $buildRoot 'tasks/service/docker.ps1') Include (Join-Path $buildRoot 'tasks/service/deploy.ps1') $buildTasks += 'Service:Build' } if ($buildType -in 'tools', 'all') { Include (Join-Path $buildRoot 'tasks/tools/build.ps1') Include (Join-Path $buildRoot 'tasks/tools/package.ps1') $buildTasks += 'Tools:Build' } Properties { $ProjectRoot = $PSScriptRoot $BuildRoot = Join-Path $ProjectRoot 'build' $BuildType = if ($env:BUILD_TYPE) { $env:BUILD_TYPE } else { 'all' } } Task Default -depends Build Task Build -depends $buildTasks ``` ## Modular Task Files[​](#modular-task-files "Direct link to Modular Task Files") Break down complex builds into focused, reusable task files. ### Example: Compilation Tasks[​](#example-compilation-tasks "Direct link to Example: Compilation Tasks") **build/tasks/compile.ps1:** ``` Properties { # These can reference properties from main psakefile $SrcDir = Join-Path $ProjectRoot 'src' $BuildDir = Join-Path $ProjectRoot 'build/output' } function Invoke-SolutionBuild { param([string]$BuildConfiguration) $solutionFile = Get-ChildItem "$SrcDir/*.sln" | Select-Object -First 1 if (-not $solutionFile) { throw "No solution file found in $SrcDir" } exec { dotnet build $solutionFile.FullName ` -c $BuildConfiguration ` -o $BuildDir ` /p:Version=$Version ` --no-incremental } } Task Compile -depends Clean { Invoke-SolutionBuild -BuildConfiguration $Configuration Write-Host "Compilation complete: $BuildDir" -ForegroundColor Green } Task CompileDebug -depends Clean { Invoke-SolutionBuild -BuildConfiguration Debug } Task CompileRelease -depends Clean { Invoke-SolutionBuild -BuildConfiguration Release } Task Restore { Write-Host "Restoring NuGet packages..." -ForegroundColor Green $solutionFile = Get-ChildItem "$SrcDir/*.sln" | Select-Object -First 1 exec { dotnet restore $solutionFile.FullName } } Task Clean { Write-Host "Cleaning build artifacts..." -ForegroundColor Green if (Test-Path $BuildDir) { Remove-Item $BuildDir -Recurse -Force Write-Host " Removed: $BuildDir" -ForegroundColor Gray } # Clean obj and bin directories Get-ChildItem $SrcDir -Include bin,obj -Recurse -Directory | ForEach-Object { Remove-Item $_.FullName -Recurse -Force Write-Host " Removed: $($_.FullName)" -ForegroundColor Gray } } ``` ### Example: Testing Tasks[​](#example-testing-tasks "Direct link to Example: Testing Tasks") **build/tasks/test.ps1:** ``` Properties { $TestDir = Join-Path $ProjectRoot 'tests' $TestResultsDir = Join-Path $ProjectRoot 'TestResults' $CoverageThreshold = 80 } Task Test -depends Compile { Write-Host "Running unit tests..." -ForegroundColor Green if (-not (Test-Path $TestDir)) { Write-Warning "No tests directory found at $TestDir" return } exec { dotnet test $TestDir ` --configuration $Configuration ` --no-build ` --logger "trx;LogFileName=test-results.trx" ` --results-directory $TestResultsDir } } Task TestWithCoverage -depends Compile { Write-Host "Running tests with coverage..." -ForegroundColor Green exec { dotnet test $TestDir ` --configuration $Configuration ` --no-build ` --collect:"XPlat Code Coverage" ` --results-directory $TestResultsDir } # Check coverage threshold $coverageFile = Get-ChildItem "$TestResultsDir/**/coverage.cobertura.xml" -Recurse | Select-Object -First 1 if ($coverageFile) { [xml]$coverage = Get-Content $coverageFile.FullName -Raw $lineRate = [double]$coverage.coverage.'line-rate' * 100 Write-Host "Code coverage: ${lineRate}%" -ForegroundColor Cyan if ($lineRate -lt $CoverageThreshold) { throw "Coverage ${lineRate}% is below threshold ${CoverageThreshold}%" } } } Task TestUnit { exec { dotnet test $TestDir ` --filter "Category=Unit" ` --configuration $Configuration } } Task TestIntegration -depends Build { exec { dotnet test $TestDir ` --filter "Category=Integration" ` --configuration $Configuration } } ``` ### Example: Deployment Tasks[​](#example-deployment-tasks "Direct link to Example: Deployment Tasks") **build/tasks/deploy.ps1:** ``` $deployTaskByTarget = @{ azure = 'Deploy:Azure' aws = 'Deploy:AWS' local = 'Deploy:Local' } $selectedDeployTarget = if ($env:DEPLOY_TARGET) { $env:DEPLOY_TARGET } else { 'local' } if (-not $deployTaskByTarget.ContainsKey($selectedDeployTarget)) { throw "Unknown deployment target: $selectedDeployTarget" } Properties { $DeployTarget = if ($env:DEPLOY_TARGET) { $env:DEPLOY_TARGET } else { 'local' } $DeploymentDir = Join-Path $ProjectRoot 'deployment' $AwsDeploymentBucket = $env:AWS_DEPLOYMENT_BUCKET $AwsApplicationName = $env:AWS_APPLICATION_NAME $AwsDeploymentGroup = $env:AWS_DEPLOYMENT_GROUP } Task Deploy -depends $deployTaskByTarget[$selectedDeployTarget] Task Deploy:Azure { $packageFile = Get-ChildItem "$BuildDir/*.zip" | Select-Object -First 1 if (-not $packageFile) { throw "Deployment package not found in $BuildDir" } exec { az webapp deploy ` --resource-group $AzureResourceGroup ` --name $AzureWebAppName ` --src-path $packageFile.FullName ` --type zip } } Task Deploy:AWS { $packageFile = Get-ChildItem "$BuildDir/*.zip" | Select-Object -First 1 if (-not $packageFile) { throw "Deployment package not found in $BuildDir" } exec { aws s3 cp $packageFile.FullName "s3://$AwsDeploymentBucket/$($packageFile.Name)" } exec { aws deploy create-deployment ` --application-name $AwsApplicationName ` --deployment-group-name $AwsDeploymentGroup ` --s3-location "bucket=$AwsDeploymentBucket,key=$($packageFile.Name),bundleType=zip" } } Task Deploy:Local { Write-Host "Deploying to local environment..." -ForegroundColor Green $targetDir = Join-Path $DeploymentDir $Environment if (Test-Path $targetDir) { Remove-Item $targetDir -Recurse -Force } Copy-Item $BuildDir -Destination $targetDir -Recurse Write-Host "Deployed to: $targetDir" -ForegroundColor Green } ``` ## Using Include Effectively[​](#using-include-effectively "Direct link to Using Include Effectively") The `Include` function allows you to split build logic across multiple files. ### Include with Path Validation[​](#include-with-path-validation "Direct link to Include with Path Validation") ``` $buildRoot = Join-Path $PSScriptRoot 'build' function Include-TaskFile { param([string]$RelativePath) $fullPath = Join-Path $buildRoot $RelativePath if (-not (Test-Path $fullPath)) { throw "Task file not found: $fullPath" } Include $fullPath } Include-TaskFile 'tasks/build.ps1' Include-TaskFile 'tasks/test.ps1' Include-TaskFile 'tasks/deploy.ps1' ``` ### Dynamic Includes Based on Configuration[​](#dynamic-includes-based-on-configuration "Direct link to Dynamic Includes Based on Configuration") ``` $projectType = if ($env:PROJECT_TYPE) { $env:PROJECT_TYPE } else { 'dotnet' } $tasksDirectory = Join-Path $PSScriptRoot 'build/tasks' Include (Join-Path $tasksDirectory 'common.ps1') $projectTaskFile = Join-Path $tasksDirectory "${projectType}.ps1" if (-not (Test-Path $projectTaskFile)) { throw "No task file found for project type: $projectType" } Include $projectTaskFile $optionalTasks = @('docker.ps1', 'kubernetes.ps1', 'terraform.ps1') foreach ($taskFile in $optionalTasks) { $fullPath = Join-Path $tasksDirectory $taskFile if (Test-Path $fullPath) { Write-Host "Loading optional tasks: $taskFile" -ForegroundColor Gray Include $fullPath } } ``` ### Include Order Matters[​](#include-order-matters "Direct link to Include Order Matters") ``` # 1. Include utilities first (they define helper functions) Include (Join-Path $BuildRoot 'utils/logging.ps1') Include (Join-Path $BuildRoot 'utils/helpers.ps1') # 2. Include configuration (depends on utilities) Include (Join-Path $BuildRoot 'config/settings.ps1') # 3. Include tasks (depend on utilities and config) Include (Join-Path $BuildRoot 'tasks/build.ps1') Include (Join-Path $BuildRoot 'tasks/test.ps1') Include (Join-Path $BuildRoot 'tasks/deploy.ps1') ``` ## Shared Utilities[​](#shared-utilities "Direct link to Shared Utilities") Create reusable utility functions that can be shared across all task files. ### Example: File Operations Utility[​](#example-file-operations-utility "Direct link to Example: File Operations Utility") **build/utils/fileops.ps1:** ``` # File operation utilities function Remove-DirectorySafe { param( [string]$Path, [switch]$Quiet ) if (Test-Path $Path) { Remove-Item $Path -Recurse -Force if (-not $Quiet) { Write-Host " Removed: $Path" -ForegroundColor Gray } return $true } return $false } function New-DirectorySafe { param( [string]$Path, [switch]$Quiet ) if (-not (Test-Path $Path)) { New-Item -ItemType Directory -Path $Path -Force | Out-Null if (-not $Quiet) { Write-Host " Created: $Path" -ForegroundColor Gray } return $true } return $false } function Copy-DirectoryContents { param( [string]$Source, [string]$Destination, [string[]]$Exclude = @() ) if (-not (Test-Path $Source)) { throw "Source directory not found: $Source" } New-DirectorySafe -Path $Destination -Quiet $items = Get-ChildItem $Source -Recurse foreach ($item in $items) { $skip = $false foreach ($pattern in $Exclude) { if ($item.FullName -like "*$pattern*") { $skip = $true break } } if ($skip) { continue } $relativePath = $item.FullName.Substring($Source.Length) $targetPath = Join-Path $Destination $relativePath if ($item.PSIsContainer) { New-DirectorySafe -Path $targetPath -Quiet } else { Copy-Item $item.FullName -Destination $targetPath -Force } } } function Get-FileHash256 { param([string]$FilePath) if (-not (Test-Path $FilePath)) { throw "File not found: $FilePath" } return (Get-FileHash -Path $FilePath -Algorithm SHA256).Hash } ``` ### Example: Logging Utility[​](#example-logging-utility "Direct link to Example: Logging Utility") **build/utils/logging.ps1:** ``` # Logging utilities function Write-LogHeader { param([string]$Message) $separator = "=" * 80 Write-Host $separator -ForegroundColor Cyan Write-Host " $Message" -ForegroundColor Cyan Write-Host $separator -ForegroundColor Cyan } function Write-LogSection { param([string]$Message) Write-Host "" Write-Host ">>> $Message" -ForegroundColor Green } function Write-LogInfo { param([string]$Message) Write-Host " [INFO] $Message" -ForegroundColor Gray } function Write-LogSuccess { param([string]$Message) Write-Host " [SUCCESS] $Message" -ForegroundColor Green } function Write-LogWarning { param([string]$Message) Write-Host " [WARNING] $Message" -ForegroundColor Yellow } function Write-LogError { param([string]$Message) Write-Host " [ERROR] $Message" -ForegroundColor Red } function Write-LogStep { param( [int]$Step, [int]$Total, [string]$Message ) Write-Host " [$Step/$Total] $Message" -ForegroundColor Cyan } ``` ### Example: Versioning Utility[​](#example-versioning-utility "Direct link to Example: Versioning Utility") **build/utils/versioning.ps1:** ``` # Version management utilities function Get-BuildVersion { param( [string]$BaseVersion = '1.0.0', [string]$BuildNumber = $null ) if ([string]::IsNullOrEmpty($BuildNumber)) { $BuildNumber = if ($env:BUILD_NUMBER) { $env:BUILD_NUMBER } else { '0' } } if ($BaseVersion -notmatch '^\d+\.\d+\.\d+$') { throw "Base version is not valid SemVer: $BaseVersion" } return "$BaseVersion-ci.$BuildNumber" } function Set-AssemblyVersion { param( [string]$ProjectFile, [string]$Version ) if (-not (Test-Path $ProjectFile)) { throw "Project file not found: $ProjectFile" } [xml]$project = Get-Content $ProjectFile -Raw $propertyGroup = $project.Project.PropertyGroup | Select-Object -First 1 if ($null -eq $propertyGroup.Version) { $versionNode = $project.CreateElement("Version") $propertyGroup.AppendChild($versionNode) | Out-Null } $propertyGroup.Version = $Version $project.Save($ProjectFile) Write-Host "Updated version to $Version in $ProjectFile" -ForegroundColor Green } ``` ## Complete Example: Large Project[​](#complete-example-large-project "Direct link to Complete Example: Large Project") Here's a complete example combining all patterns: **psakefile.ps1:** ``` $buildRoot = Join-Path $PSScriptRoot 'build' $selectedEnvironment = if ($env:BUILD_ENV) { $env:BUILD_ENV } else { 'dev' } Include (Join-Path $buildRoot 'utils/logging.ps1') Include (Join-Path $buildRoot 'utils/fileops.ps1') Include (Join-Path $buildRoot 'utils/versioning.ps1') $environmentConfig = Join-Path $buildRoot "config/${selectedEnvironment}.ps1" if (-not (Test-Path $environmentConfig)) { throw "Environment configuration not found: $environmentConfig" } Include $environmentConfig Include (Join-Path $buildRoot 'tasks/compile.ps1') Include (Join-Path $buildRoot 'tasks/test.ps1') Include (Join-Path $buildRoot 'tasks/package.ps1') Include (Join-Path $buildRoot 'tasks/deploy.ps1') Include (Join-Path $buildRoot 'tasks/cleanup.ps1') Properties { $ProjectRoot = $PSScriptRoot $BuildRoot = Join-Path $ProjectRoot 'build' $SrcDir = Join-Path $ProjectRoot 'src' $TestDir = Join-Path $ProjectRoot 'tests' $BuildDir = Join-Path $ProjectRoot 'build/output' $Configuration = if ($env:BUILD_CONFIGURATION) { $env:BUILD_CONFIGURATION } else { 'Debug' } $Environment = if ($env:BUILD_ENV) { $env:BUILD_ENV } else { 'dev' } $BuildNumber = if ($env:BUILD_NUMBER) { $env:BUILD_NUMBER } else { '0' } $Version = Get-BuildVersion -BaseVersion '1.0.0' -BuildNumber $BuildNumber } # Custom task formatter FormatTaskName { param($taskName) Write-LogHeader "Task: $taskName" } # Main orchestration tasks Task Default -depends Build Task Build -depends Restore, Compile, Test { Write-LogSuccess "Build completed successfully" } Task CI -depends Build, Package { Write-LogSuccess "CI build completed" } Task Release -depends Clean, Build, Package, Deploy { Write-LogSuccess "Release completed" } Task Full -depends Clean, Restore, Compile, TestWithCoverage, Package, Deploy { Write-LogSuccess "Full build and deployment completed" } ``` The environment and utility files are the complete implementations shown earlier in this guide. Add these remaining task files: **build/tasks/package.ps1:** ``` Task Package -depends Build { if (-not (Test-Path $BuildDir)) { throw "Build output not found: $BuildDir" } $packagePath = Join-Path $BuildRoot "MyApp-$Version.zip" Compress-Archive -Path "$BuildDir/*" -DestinationPath $packagePath -Force Write-LogSuccess "Created $packagePath" } ``` **build/tasks/cleanup.ps1:** ``` Task Clean { if (Test-Path $BuildDir) { Remove-Item $BuildDir -Recurse -Force } New-Item -ItemType Directory -Path $BuildDir -Force | Out-Null } ``` Use the `compile.ps1`, `test.ps1`, and `deploy.ps1` implementations from the modular task-file examples above. Together, these files satisfy every `Include` in the orchestrator. ## Best Practices Summary[​](#best-practices-summary "Direct link to Best Practices Summary") 1. **Use a clear directory structure** - Organize by category or build type 2. **Keep task files focused** - One responsibility per file 3. **Load utilities before tasks** - Ensure dependencies are available 4. **Use Include for modularization** - Split large builds into manageable pieces 5. **Create shared utilities** - Avoid duplicating code across task files 6. **Validate file paths** - Check that included files exist 7. **Use meaningful names** - Make task files and functions self-documenting 8. **Document complex logic** - Add comments explaining non-obvious decisions 9. **Keep the main psakefile simple** - It should orchestrate, not implement 10. **Test modular components** - Ensure each task file works independently ## See Also[​](#see-also "Direct link to See Also") * [Access Functions in Another File](/docs/tutorial-advanced/access-functions-in-another-file.md) - Using Include and dot-sourcing * [Creating Shared Task Modules](/docs/tutorial-advanced/creating-shared-task-modules.md) - Distributing versioned tasks across repositories * [Structure of a psake Build Script](/docs/tutorial-advanced/structure-of-a-psake-build-script.md) - Basic script structure * [Environment Management](/docs/best-practices/environment-management.md) - Managing multiple environments * [Testing Build Scripts](/docs/best-practices/testing-build-scripts.md) - Testing your psake scripts * [.NET Solution Builds](/docs/build-types/dot-net-solution.md) - Complete .NET examples --- # Secret Management Proper secret management is critical for secure build automation. This guide shows you how to handle API keys, passwords, certificates, and other sensitive data in psake builds without exposing them in source control or logs. ## Quick Start[​](#quick-start "Direct link to Quick Start") Here's a basic secure approach using environment variables: ``` Properties { # Never hardcode secrets! # BAD: $ApiKey = "sk-1234567890abcdef" # GOOD: Read from environment variables $ApiKey = $env:API_KEY $DatabasePassword = $env:DB_PASSWORD } Task Deploy { # Validate secrets exist if ([string]::IsNullOrEmpty($ApiKey)) { throw "API_KEY environment variable is required" } if ([string]::IsNullOrEmpty($DatabasePassword)) { throw "DB_PASSWORD environment variable is required" } # Expose the secret only through the child process environment. exec { $env:MYAPP_API_KEY = $ApiKey try { dotnet publish } finally { Remove-Item Env:MYAPP_API_KEY } } -errorMessage "Publish failed (credentials redacted)" } ``` ## Secret Management Patterns[​](#secret-management-patterns "Direct link to Secret Management Patterns") ### Pattern 1: Environment Variables[​](#pattern-1-environment-variables "Direct link to Pattern 1: Environment Variables") The simplest and most common approach: ``` Properties { $ProjectRoot = $PSScriptRoot # API credentials $NuGetApiKey = $env:NUGET_API_KEY $DockerHubToken = $env:DOCKER_TOKEN $GitHubToken = $env:GITHUB_TOKEN # Database credentials $DbUsername = $env:DB_USERNAME $DbPassword = $env:DB_PASSWORD # Cloud provider credentials $AzureClientId = $env:AZURE_CLIENT_ID $AzureClientSecret = $env:AZURE_CLIENT_SECRET $AwsAccessKeyId = $env:AWS_ACCESS_KEY_ID $AwsSecretAccessKey = $env:AWS_SECRET_ACCESS_KEY # Certificate passwords $SigningCertPassword = $env:SIGNING_CERT_PASSWORD } Task ValidateSecrets { Write-Host "Validating required secrets..." -ForegroundColor Green $requiredSecrets = @{ 'NUGET_API_KEY' = $NuGetApiKey 'DB_PASSWORD' = $DbPassword 'AZURE_CLIENT_SECRET' = $AzureClientSecret } $missing = @() foreach ($secret in $requiredSecrets.GetEnumerator()) { if ([string]::IsNullOrEmpty($secret.Value)) { $missing += $secret.Key } } if ($missing.Count -gt 0) { throw "Missing required secrets: $($missing -join ', ')" } Write-Host " All required secrets are present" -ForegroundColor Green } Task PublishPackage -depends Build, ValidateSecrets { Write-Host "Publishing NuGet package..." -ForegroundColor Green # Use the secret without logging it $packages = Get-ChildItem "$BuildDir/*.nupkg" foreach ($package in $packages) { exec { dotnet nuget push $package.FullName ` --api-key $NuGetApiKey ` --source https://api.nuget.org/v3/index.json } -errorMessage "Failed to publish package (check API key)" } } ``` ### Pattern 2: Secure Strings (PowerShell)[​](#pattern-2-secure-strings-powershell "Direct link to Pattern 2: Secure Strings (PowerShell)") For Windows-specific scenarios using PowerShell secure strings: ``` Properties { $ProjectRoot = $PSScriptRoot $CredentialsFile = Join-Path $ProjectRoot '.credentials/encrypted.xml' } Task SaveCredentials { Write-Host "Saving encrypted credentials..." -ForegroundColor Green # This only works on Windows with DPAPI $credential = Get-Credential -Message "Enter deployment credentials" $credentialsDir = Split-Path $CredentialsFile -Parent if (-not (Test-Path $credentialsDir)) { New-Item -ItemType Directory -Path $credentialsDir | Out-Null } # Export encrypted (only readable by current user on current machine) $credential | Export-Clixml -Path $CredentialsFile Write-Host "Credentials saved to: $CredentialsFile" -ForegroundColor Green Write-Warning "Add .credentials/ to .gitignore!" } Task LoadCredentials { if (-not (Test-Path $CredentialsFile)) { throw "Credentials file not found: $CredentialsFile. Run SaveCredentials task first." } # Import encrypted credentials $script:DeploymentCredential = Import-Clixml -Path $CredentialsFile Write-Host "Loaded credentials from: $CredentialsFile" -ForegroundColor Green } Task Deploy -depends Build, LoadCredentials { Write-Host "Deploying with saved credentials..." -ForegroundColor Green $arguments = @( '-verb:sync' "-source:package=$BuildDir\package.zip" '-dest:auto,computerName=https://server.example.com:8172/msdeploy.axd' '-allowUntrusted' ) $process = Start-Process msdeploy ` -ArgumentList $arguments ` -Credential $DeploymentCredential ` -Wait -PassThru -NoNewWindow if ($process.ExitCode -ne 0) { throw "Deployment failed with exit code $($process.ExitCode)" } } ``` ### Pattern 3: Azure Key Vault[​](#pattern-3-azure-key-vault "Direct link to Pattern 3: Azure Key Vault") For Azure-hosted secrets: ``` Properties { $ProjectRoot = $PSScriptRoot $KeyVaultName = $env:AZURE_KEYVAULT_NAME } Task AzureLogin { Write-Host "Authenticating with Azure..." -ForegroundColor Green if ($env:CI) { # CI authenticates before psake with azure/login or an equivalent workload identity. exec { az account show --output none } } else { exec { az login } } Write-Host "Azure authentication successful" -ForegroundColor Green } Task GetSecretsFromKeyVault -depends AzureLogin { Write-Host "Retrieving secrets from Azure Key Vault: $KeyVaultName" -ForegroundColor Green if ([string]::IsNullOrEmpty($KeyVaultName)) { throw "AZURE_KEYVAULT_NAME environment variable is required" } # Retrieve secrets from Key Vault $script:DatabasePassword = az keyvault secret show ` --name "DatabasePassword" ` --vault-name $KeyVaultName ` --query value -o tsv $script:ApiKey = az keyvault secret show ` --name "ApiKey" ` --vault-name $KeyVaultName ` --query value -o tsv $script:CertificatePassword = az keyvault secret show ` --name "SigningCertPassword" ` --vault-name $KeyVaultName ` --query value -o tsv # Validate retrieved secrets if ([string]::IsNullOrEmpty($DatabasePassword)) { throw "Failed to retrieve DatabasePassword from Key Vault" } Write-Host " Retrieved secrets successfully" -ForegroundColor Green } Task Deploy -depends Build, AzureLogin { $databaseSecretUri = az keyvault secret show ` --name DatabasePassword --vault-name $KeyVaultName --query id -o tsv $apiKeySecretUri = az keyvault secret show ` --name ApiKey --vault-name $KeyVaultName --query id -o tsv $packageFile = Join-Path $BuildDir 'package.zip' exec { az webapp config appsettings set ` --resource-group $AzureResourceGroup ` --name $AzureWebAppName ` --settings ` "ConnectionStrings__DefaultConnection=@Microsoft.KeyVault(SecretUri=$databaseSecretUri)" ` "ExternalServices__ApiKey=@Microsoft.KeyVault(SecretUri=$apiKeySecretUri)" } exec { az webapp deploy ` --resource-group $AzureResourceGroup ` --name $AzureWebAppName ` --src-path $packageFile ` --type zip } } ``` ### Pattern 4: AWS Secrets Manager[​](#pattern-4-aws-secrets-manager "Direct link to Pattern 4: AWS Secrets Manager") For AWS-hosted secrets: ``` Properties { $ProjectRoot = $PSScriptRoot $AwsRegion = if ($env:AWS_REGION) { $env:AWS_REGION } else { 'us-east-1' } $SecretsPath = 'myapp/prod' # Path in Secrets Manager } Task VerifyAwsCli { try { $awsVersion = aws --version Write-Host "AWS CLI: $awsVersion" -ForegroundColor Gray } catch { throw "AWS CLI is not installed. Install from https://aws.amazon.com/cli/" } } Task GetSecretsFromAWS -depends VerifyAwsCli { Write-Host "Retrieving secrets from AWS Secrets Manager..." -ForegroundColor Green # Get secret from AWS Secrets Manager $secretJson = aws secretsmanager get-secret-value ` --secret-id $SecretsPath ` --region $AwsRegion ` --query SecretString ` --output text if ([string]::IsNullOrEmpty($secretJson)) { throw "Failed to retrieve secrets from AWS Secrets Manager: $SecretsPath" } # Parse secrets JSON $secrets = $secretJson | ConvertFrom-Json # Extract individual secrets $script:DatabasePassword = $secrets.DatabasePassword $script:ApiKey = $secrets.ApiKey $script:EncryptionKey = $secrets.EncryptionKey # Validate if ([string]::IsNullOrEmpty($DatabasePassword)) { throw "DatabasePassword not found in secrets" } Write-Host " Retrieved secrets successfully" -ForegroundColor Green } Task SetAwsSecret { param( [string]$SecretName = 'myapp/prod', [SecureString]$SecretValue ) if ($null -eq $SecretValue) { $SecretValue = Read-Host "Enter secret value" -AsSecureString } Import-Module AWS.Tools.SecretsManager $credential = [PSCredential]::new('secret', $SecretValue) $plainText = $credential.GetNetworkCredential().Password try { $existingSecret = Get-SECSecretList | Where-Object Name -eq $SecretName | Select-Object -First 1 if ($existingSecret) { Update-SECSecret -SecretId $SecretName -SecretString $plainText Write-Host "Secret updated: $SecretName" -ForegroundColor Green } else { New-SECSecret -Name $SecretName -SecretString $plainText Write-Host "Secret created: $SecretName" -ForegroundColor Green } } finally { $plainText = $null $credential = $null $SecretValue.Dispose() } } Task Deploy -depends Build, GetSecretsFromAWS { Write-Host "Deploying with AWS Secrets..." -ForegroundColor Green # Use secrets in deployment exec { aws deploy create-deployment ` --application-name MyApp ` --deployment-config-name CodeDeployDefault.OneAtATime ` --deployment-group-name Production ` --s3-location bucket=my-deployments,key=app.zip,bundleType=zip } } ``` ### Pattern 5: HashiCorp Vault[​](#pattern-5-hashicorp-vault "Direct link to Pattern 5: HashiCorp Vault") For HashiCorp Vault integration: ``` Properties { $VaultAddr = $env:VAULT_ADDR # e.g., https://vault.example.com:8200 $VaultToken = $env:VAULT_TOKEN $VaultSecretsPath = 'secret/data/myapp/prod' } Task GetSecretsFromVault { Write-Host "Retrieving secrets from HashiCorp Vault..." -ForegroundColor Green if ([string]::IsNullOrEmpty($VaultAddr)) { throw "VAULT_ADDR environment variable is required" } if ([string]::IsNullOrEmpty($VaultToken)) { throw "VAULT_TOKEN environment variable is required" } # Retrieve secret from Vault $headers = @{ 'X-Vault-Token' = $VaultToken } try { $response = Invoke-RestMethod ` -Uri "$VaultAddr/v1/$VaultSecretsPath" ` -Method Get ` -Headers $headers $secretData = $response.data.data # Extract secrets $script:DatabasePassword = $secretData.database_password $script:ApiKey = $secretData.api_key $script:EncryptionKey = $secretData.encryption_key Write-Host " Retrieved secrets successfully" -ForegroundColor Green } catch { throw "Failed to retrieve secrets from Vault: $_" } } ``` ## Security Best Practices[​](#security-best-practices "Direct link to Security Best Practices") ### Never Commit Secrets[​](#never-commit-secrets "Direct link to Never Commit Secrets") **Always add these to .gitignore:** ``` # Secrets and credentials *.key *.pem *.pfx *.p12 *.cer *.crt .env .env.local .env.*.local secrets.json appsettings.*.json *.credentials .credentials/ *.secret # Configuration with secrets config/prod.ps1 config/staging.ps1 **/appsettings.Production.json **/appsettings.Staging.json # Build artifacts that may contain secrets publish/ deploy/ ``` ### Avoid Logging Secrets[​](#avoid-logging-secrets "Direct link to Avoid Logging Secrets") **Bad - Secret appears in logs:** ``` Task Deploy { Write-Host "Using API key: $ApiKey" -ForegroundColor Gray # NEVER DO THIS! exec { dotnet publish } } ``` **Good - Secrets redacted:** ``` Task Deploy { Write-Host "Using API key: [REDACTED]" -ForegroundColor Gray exec { $env:MYAPP_API_KEY = $ApiKey try { dotnet publish } finally { Remove-Item Env:MYAPP_API_KEY } } -errorMessage "Publish failed (check API key configuration)" } ``` ### Secret Validation[​](#secret-validation "Direct link to Secret Validation") Always validate secrets exist before using them: ``` function Test-SecretExists { param( [string]$SecretName, [string]$SecretValue ) if ([string]::IsNullOrEmpty($SecretValue)) { throw "Required secret '$SecretName' is not set" } # Optionally validate format if ($SecretName -like '*API_KEY' -and $SecretValue.Length -lt 20) { Write-Warning "Secret '$SecretName' appears to be invalid (too short)" } } Task ValidateSecrets { Write-Host "Validating secrets..." -ForegroundColor Green Test-SecretExists -SecretName 'NUGET_API_KEY' -SecretValue $env:NUGET_API_KEY Test-SecretExists -SecretName 'DB_PASSWORD' -SecretValue $env:DB_PASSWORD Test-SecretExists -SecretName 'SIGNING_CERT_PASSWORD' -SecretValue $env:SIGNING_CERT_PASSWORD Write-Host " All secrets validated" -ForegroundColor Green } ``` ### Secure Certificate Handling[​](#secure-certificate-handling "Direct link to Secure Certificate Handling") **For code signing certificates:** ``` Properties { $CertificateThumbprint = $env:SIGNING_CERT_THUMBPRINT } Task SignAssemblies -depends Build { Write-Host "Signing assemblies..." -ForegroundColor Green if ([string]::IsNullOrEmpty($CertificateThumbprint)) { throw "SIGNING_CERT_THUMBPRINT environment variable is required" } $assemblies = Get-ChildItem "$BuildDir/*.dll" -Recurse foreach ($assembly in $assemblies) { exec { signtool sign /sha1 $CertificateThumbprint ` /tr http://timestamp.digicert.com ` /td SHA256 ` /fd SHA256 ` $assembly.FullName } -errorMessage "Failed to sign $($assembly.Name)" Write-Host " Signed: $($assembly.Name)" -ForegroundColor Gray } } ``` ### Minimize Secret Lifetime[​](#minimize-secret-lifetime "Direct link to Minimize Secret Lifetime") Strings cannot be reliably erased from managed memory. Prefer APIs that accept `SecureString`, scope secrets narrowly, and dispose sensitive objects deterministically: ``` Task InstallSigningCertificate { $password = ConvertTo-SecureString $env:PFX_PASSWORD -AsPlainText -Force $certificate = $null try { $certificate = Import-PfxCertificate ` -FilePath $CertificatePath ` -CertStoreLocation Cert:\CurrentUser\My ` -Password $password exec { signtool sign /sha1 $certificate.Thumbprint /fd SHA256 $PackagePath } } finally { if ($certificate) { Remove-Item $certificate.PSPath -Force } $password.Dispose() } } ``` ## CI/CD Integration[​](#cicd-integration "Direct link to CI/CD Integration") ### GitHub Actions[​](#github-actions "Direct link to GitHub Actions") ``` name: Deploy with Secrets on: push: branches: [main] jobs: deploy: runs-on: windows-latest steps: - uses: actions/checkout@v4 - name: Install psake shell: pwsh run: Install-Module -Name psake -Force - name: Deploy shell: pwsh run: | Invoke-psake -buildFile .\psakefile.ps1 -taskList Deploy env: # Pass secrets as environment variables NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }} DB_PASSWORD: ${{ secrets.DB_PASSWORD }} AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }} SIGNING_CERT_PASSWORD: ${{ secrets.SIGNING_CERT_PASSWORD }} ``` ### Azure Pipelines[​](#azure-pipelines "Direct link to Azure Pipelines") ``` trigger: - main pool: vmImage: 'windows-latest' variables: - group: production-secrets # Variable group with secrets steps: - task: PowerShell@2 displayName: 'Install psake' inputs: targetType: 'inline' script: 'Install-Module -Name psake -Force' - task: PowerShell@2 displayName: 'Deploy' inputs: targetType: 'inline' script: 'Invoke-psake -buildFile .\psakefile.ps1 -taskList Deploy' env: NUGET_API_KEY: $(NuGetApiKey) DB_PASSWORD: $(DatabasePassword) AZURE_CLIENT_SECRET: $(AzureClientSecret) ``` ## Complete Secure Build Example[​](#complete-secure-build-example "Direct link to Complete Secure Build Example") **psakefile.ps1:** ``` Properties { $ProjectRoot = $PSScriptRoot $BuildDir = Join-Path $ProjectRoot 'build/output' # Secrets from environment variables $NuGetApiKey = $env:NUGET_API_KEY $AzureKeyVaultName = $env:AZURE_KEYVAULT_NAME # Flags $UseKeyVault = -not [string]::IsNullOrEmpty($AzureKeyVaultName) } Task ValidateSecrets { Write-Host "Validating secrets configuration..." -ForegroundColor Green if ($UseKeyVault) { Write-Host " Using Azure Key Vault: $AzureKeyVaultName" -ForegroundColor Gray if ([string]::IsNullOrEmpty($env:AZURE_CLIENT_SECRET)) { throw "AZURE_CLIENT_SECRET is required for Key Vault access" } } else { Write-Host " Using environment variables" -ForegroundColor Gray if ([string]::IsNullOrEmpty($NuGetApiKey)) { throw "NUGET_API_KEY environment variable is required" } } Write-Host " Secrets validation passed" -ForegroundColor Green } Task GetSecrets -depends ValidateSecrets, GetSecretsFromKeyVault { if (-not $UseKeyVault) { Write-Host "Using secrets from environment variables" -ForegroundColor Gray } } Task GetSecretsFromKeyVault -precondition { $UseKeyVault } { Write-Host "Retrieving secrets from Azure Key Vault..." -ForegroundColor Green # CI authenticates before psake with azure/login or an equivalent workload identity. $script:NuGetApiKey = az keyvault secret show ` --name "NuGetApiKey" ` --vault-name $AzureKeyVaultName ` --query value -o tsv Write-Host " Secrets retrieved successfully" -ForegroundColor Green } Task Build { Write-Host "Building project..." -ForegroundColor Green exec { dotnet build -c Release -o $BuildDir } } Task Pack -depends Build { Write-Host "Creating NuGet packages..." -ForegroundColor Green exec { dotnet pack -c Release -o $BuildDir --no-build } } Task Publish -depends Pack, GetSecrets { Write-Host "Publishing packages to NuGet..." -ForegroundColor Green try { $packages = Get-ChildItem "$BuildDir/*.nupkg" foreach ($package in $packages) { Write-Host " Publishing: $($package.Name)" -ForegroundColor Gray exec { dotnet nuget push $package.FullName ` --api-key $NuGetApiKey ` --source https://api.nuget.org/v3/index.json } -errorMessage "Failed to publish package (credentials redacted)" } Write-Host "Publishing complete" -ForegroundColor Green } finally { $script:NuGetApiKey = $null } } ``` ## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") ### Secret Not Found[​](#secret-not-found "Direct link to Secret Not Found") **Problem:** Environment variable not set **Solution:** ``` Task Debug:ShowSecrets { Write-Host "Secret Status:" -ForegroundColor Yellow Write-Host " NUGET_API_KEY: $(if ($env:NUGET_API_KEY) { '[SET]' } else { '[NOT SET]' })" Write-Host " DB_PASSWORD: $(if ($env:DB_PASSWORD) { '[SET]' } else { '[NOT SET]' })" Write-Host " AZURE_CLIENT_SECRET: $(if ($env:AZURE_CLIENT_SECRET) { '[SET]' } else { '[NOT SET]' })" } ``` ### Secrets Appearing in Logs[​](#secrets-appearing-in-logs "Direct link to Secrets Appearing in Logs") **Problem:** Sensitive data in build output **Solution:** Use `-errorMessage` with exec and avoid Write-Host with secret values ### Key Vault Access Denied[​](#key-vault-access-denied "Direct link to Key Vault Access Denied") **Problem:** Cannot access Azure Key Vault **Solution:** Check service principal permissions: ``` Task GrantKeyVaultAccess { $servicePrincipalId = $env:AZURE_CLIENT_ID exec { az keyvault set-policy ` --name $env:AZURE_KEYVAULT_NAME ` --spn $servicePrincipalId ` --secret-permissions get list } } ``` ## See Also[​](#see-also "Direct link to See Also") * [Environment Management](/docs/best-practices/environment-management.md) - Managing multiple environments * [GitHub Actions](/docs/ci-examples/github-actions.md) - CI/CD with GitHub * [Azure Pipelines](/docs/ci-examples/azure-pipelines.md) - CI/CD with Azure DevOps * [Docker Builds](/docs/build-types/docker.md) - Container builds with secrets --- # Testing Build Scripts Build scripts are code and should be tested like any other code. This guide shows you how to write tests for psake scripts using Pester, mock external dependencies, validate task execution, and integrate testing into your CI/CD pipeline. ## Quick Start[​](#quick-start "Direct link to Quick Start") Here's a basic Pester test for a psake build script: ``` # tests/Build.Tests.ps1 Describe 'psake Build Script' { BeforeAll { # Import psake Import-Module psake -Force # Set up test environment $script:BuildFile = Join-Path $PSScriptRoot '../psakefile.ps1' } It 'Build file exists' { Test-Path $BuildFile | Should -Be $true } It 'Build file defines valid tasks' { { Get-PSakeScriptTasks -BuildFile $BuildFile } | Should -Not -Throw } It 'Default task executes successfully' { $result = Invoke-psake -buildFile $BuildFile -nologo $result.Success | Should -BeTrue } } ``` Run the tests: ``` Invoke-Pester -Path ./tests/Build.Tests.ps1 ``` ## Setting Up Pester[​](#setting-up-pester "Direct link to Setting Up Pester") ### Installation[​](#installation "Direct link to Installation") ``` # Install Pester (v5+) Install-Module -Name Pester -Force -SkipPublisherCheck # Verify installation Get-Module -Name Pester -ListAvailable ``` ### Basic Test Structure[​](#basic-test-structure "Direct link to Basic Test Structure") **tests/Build.Tests.ps1:** ``` BeforeAll { # Import required modules Import-Module psake -Force # Define paths $script:ProjectRoot = Split-Path $PSScriptRoot -Parent $script:BuildFile = Join-Path $ProjectRoot 'psakefile.ps1' $script:BuildDir = Join-Path $ProjectRoot 'build/output' # Mock external commands if needed Mock -CommandName 'dotnet' -MockWith { return 0 } } Describe 'psake Build Configuration' { It 'Build file exists' { Test-Path $BuildFile | Should -Be $true } It 'Build file defines valid tasks' { { Get-PSakeScriptTasks -BuildFile $BuildFile } | Should -Not -Throw } } Describe 'Build Tasks' { Context 'Clean Task' { It 'Removes build directory' { # Create test build directory New-Item -ItemType Directory -Path $BuildDir -Force # Run Clean task Invoke-psake -buildFile $BuildFile -taskList Clean -nologo # Verify directory removed Test-Path $BuildDir | Should -Be $false } } Context 'Build Task' { It 'Executes without errors' { $result = Invoke-psake -buildFile $BuildFile -taskList Build -nologo $result.Success | Should -BeTrue } It 'Creates build artifacts' { Invoke-psake -buildFile $BuildFile -taskList Build -nologo (Get-ChildItem $BuildDir).Count | Should -BeGreaterThan 0 } } } AfterAll { # Clean up test artifacts if (Test-Path $BuildDir) { Remove-Item $BuildDir -Recurse -Force } } ``` ## Testing Task Dependencies[​](#testing-task-dependencies "Direct link to Testing Task Dependencies") Ensure tasks execute in the correct order: ``` # tests/TaskDependencies.Tests.ps1 Describe 'Task Dependencies' { BeforeAll { $script:BuildFile = Join-Path $PSScriptRoot '../psakefile.ps1' $script:Tasks = Get-PSakeScriptTasks -BuildFile $BuildFile } It 'Build depends on Compile' { $buildTask = $Tasks | Where-Object Name -eq 'Build' $buildTask.DependsOn | Should -Contain 'Compile' } It 'Deploy depends on Build and Test' { $deployTask = $Tasks | Where-Object Name -eq 'Deploy' $deployTask.DependsOn | Should -Contain 'Build' $deployTask.DependsOn | Should -Contain 'Test' } It 'Executes dependencies before Deploy' { $result = Invoke-psake -BuildFile $BuildFile -TaskList Deploy -NoLogo $result.Success | Should -BeTrue $executedTasks = @($result.Tasks | Where-Object Status -eq 'Executed' | ForEach-Object Name) $executedTasks.IndexOf('Compile') | Should -BeLessThan $executedTasks.IndexOf('Build') $executedTasks.IndexOf('Build') | Should -BeLessThan $executedTasks.IndexOf('Deploy') } } ``` ## Mocking External Commands[​](#mocking-external-commands "Direct link to Mocking External Commands") Mock external tools to test build logic without side effects: ### Mocking dotnet CLI[​](#mocking-dotnet-cli "Direct link to Mocking dotnet CLI") ``` Describe 'Build with Mocked dotnet' { BeforeAll { $script:BuildFile = Join-Path $PSScriptRoot '../psakefile.ps1' # Mock dotnet commands Mock -CommandName 'dotnet' -MockWith { switch ($args[0]) { 'build' { 'Build succeeded' } 'test' { 'Tests passed: 50 passed, 0 failed' } 'publish' { 'Publish succeeded' } } } } It 'Compile task calls dotnet build' { Invoke-psake -buildFile $BuildFile -taskList Compile -nologo # Verify dotnet build was called Should -Invoke -CommandName 'dotnet' -ParameterFilter { $args[0] -eq 'build' } -Times 1 } It 'Test task calls dotnet test' { Invoke-psake -buildFile $BuildFile -taskList Test -nologo Should -Invoke -CommandName 'dotnet' -ParameterFilter { $args[0] -eq 'test' } -Times 1 } } ``` ### Mocking File System Operations[​](#mocking-file-system-operations "Direct link to Mocking File System Operations") ``` Describe 'File Operations' { BeforeAll { $script:BuildFile = Join-Path $PSScriptRoot '../psakefile.ps1' # Mock file system commands Mock -CommandName 'Remove-Item' -MockWith { return $true } Mock -CommandName 'New-Item' -MockWith { param($Path, $ItemType) return [PSCustomObject]@{ FullName = $Path Exists = $true } } Mock -CommandName 'Copy-Item' -MockWith { return $true } } It 'Clean task removes build directory' { Invoke-psake -buildFile $BuildFile -taskList Clean -nologo Should -Invoke -CommandName 'Remove-Item' -Times 1 } It 'Package task creates deployment package' { Invoke-psake -buildFile $BuildFile -taskList Package -nologo Should -Invoke -CommandName 'Compress-Archive' -Times 1 } } ``` ### Mocking Cloud CLI Tools[​](#mocking-cloud-cli-tools "Direct link to Mocking Cloud CLI Tools") ``` Describe 'Azure Deployment' { BeforeAll { $script:BuildFile = Join-Path $PSScriptRoot '../psakefile.ps1' # Mock az CLI Mock -CommandName 'az' -MockWith { if ($args[0] -eq 'login') { return '[{"cloudName":"AzureCloud","state":"Enabled"}]' } if ($args[0] -eq 'webapp') { return '{"status":"Success"}' } } } It 'Deploy task authenticates with Azure' { Invoke-psake -buildFile $BuildFile -taskList Deploy -nologo Should -Invoke -CommandName 'az' -ParameterFilter { $args[0] -eq 'login' } -Times 1 } It 'Deploy task deploys to Azure Web App' { Invoke-psake -buildFile $BuildFile -taskList Deploy -nologo Should -Invoke -CommandName 'az' -ParameterFilter { $args[0] -eq 'webapp' } -Times 1 } } ``` ### Mocking AWS CLI[​](#mocking-aws-cli "Direct link to Mocking AWS CLI") ``` Describe 'AWS Deployment' { BeforeAll { $script:BuildFile = Join-Path $PSScriptRoot '../psakefile.ps1' Mock -CommandName 'aws' -MockWith { if ($args[0] -eq 'deploy' -and $args[1] -eq 'create-deployment') { return '{"deploymentId":"d-1234567890"}' } } } It 'Creates a CodeDeploy deployment' { $result = Invoke-psake -BuildFile $BuildFile -TaskList Deploy:AWS -NoLogo $result.Success | Should -BeTrue Should -Invoke -CommandName 'aws' -ParameterFilter { $args[0] -eq 'deploy' -and $args[1] -eq 'create-deployment' } -Times 1 } } ``` ## Testing Properties and Parameters[​](#testing-properties-and-parameters "Direct link to Testing Properties and Parameters") Put assertions that depend on build properties inside tasks, where psake properties are in scope: ``` # psakefile.ps1 Properties { $Configuration = 'Debug' $Environment = 'dev' } Task ValidateDebugConfiguration { Assert ($Configuration -eq 'Debug') 'Expected Debug configuration' } Task ValidateReleaseConfiguration { Assert ($Configuration -eq 'Release') 'Expected Release configuration' } Task ValidateDevelopmentEnvironment { Assert ($Environment -eq 'dev') 'Expected dev environment' } Task ValidateStagingEnvironment { Assert ($Environment -eq 'staging') 'Expected staging environment' } ``` ``` Describe 'Build Properties' { BeforeAll { $script:BuildFile = Join-Path $PSScriptRoot '../psakefile.ps1' } It 'Uses the default Debug configuration' { $result = Invoke-psake -BuildFile $BuildFile -TaskList ValidateDebugConfiguration -NoLogo $result.Success | Should -BeTrue } It 'Accepts a Release configuration override' { $result = Invoke-psake -BuildFile $BuildFile ` -Properties @{ Configuration = 'Release' } ` -TaskList ValidateReleaseConfiguration -NoLogo $result.Success | Should -BeTrue } It 'Uses the default dev environment' { $result = Invoke-psake -BuildFile $BuildFile -TaskList ValidateDevelopmentEnvironment -NoLogo $result.Success | Should -BeTrue } } ``` ## Integration Tests[​](#integration-tests "Direct link to Integration Tests") Test the complete build pipeline: ``` # tests/Integration.Tests.ps1 Describe 'Complete Build Pipeline' { BeforeAll { $script:ProjectRoot = Split-Path $PSScriptRoot -Parent $script:BuildFile = Join-Path $ProjectRoot 'psakefile.ps1' $script:BuildDir = Join-Path $ProjectRoot 'build/output' $script:TestResultsDir = Join-Path $ProjectRoot 'TestResults' } Context 'Full Build' { It 'Completes without errors' { $result = Invoke-psake -buildFile $BuildFile -nologo $result.Success | Should -BeTrue } It 'Creates build artifacts' { Test-Path $BuildDir | Should -Be $true (Get-ChildItem $BuildDir -Recurse -File).Count | Should -BeGreaterThan 0 } It 'Runs tests and generates results' { Test-Path $TestResultsDir | Should -Be $true } It 'Build artifacts are valid' { $dlls = Get-ChildItem "$BuildDir/*.dll" -Recurse foreach ($dll in $dlls) { # Verify DLL can be loaded { [System.Reflection.Assembly]::LoadFrom($dll.FullName) } | Should -Not -Throw } } } Context 'Different Configurations' { It 'Debug build succeeds' { $result = Invoke-psake -buildFile $BuildFile -properties @{ Configuration = 'Debug' } -nologo $result.Success | Should -BeTrue } It 'Release build succeeds' { $result = Invoke-psake -buildFile $BuildFile -properties @{ Configuration = 'Release' } -nologo $result.Success | Should -BeTrue } } AfterAll { # Clean up if (Test-Path $BuildDir) { Remove-Item $BuildDir -Recurse -Force } if (Test-Path $TestResultsDir) { Remove-Item $TestResultsDir -Recurse -Force } } } ``` ## Testing Error Handling[​](#testing-error-handling "Direct link to Testing Error Handling") Ensure build fails gracefully: ``` Describe 'Error Handling' { BeforeAll { $script:BuildFile = Join-Path $PSScriptRoot '../psakefile.ps1' } It 'Build fails when compilation fails' { Mock -CommandName 'dotnet' -MockWith { throw "Compilation failed" } $result = Invoke-psake -buildFile $BuildFile -taskList Compile -nologo $result.Success | Should -BeFalse } It 'Build fails when tests fail' { Mock -CommandName 'dotnet' -MockWith { if ($args[0] -eq 'test') { throw "Tests failed" } } $result = Invoke-psake -buildFile $BuildFile -taskList Test -nologo $result.Success | Should -BeFalse } It 'Build validates required secrets' { $originalApiKey = $env:API_KEY $env:API_KEY = $null try { $result = Invoke-psake -buildFile $BuildFile -taskList Deploy -nologo $result.Success | Should -BeFalse } finally { $env:API_KEY = $originalApiKey } } } ``` ## Test-Friendly Build Scripts[​](#test-friendly-build-scripts "Direct link to Test-Friendly Build Scripts") Make your build scripts easier to test: **psakefile.ps1:** ``` Properties { $ProjectRoot = $PSScriptRoot $TestMode = $env:PSAKE_TEST_MODE -eq 'true' } # Helper function for testable external commands function Invoke-ExternalCommand { param( [string]$Command, [string[]]$Arguments ) if ($TestMode) { # In test mode, just log what would be executed Write-Host "TEST MODE: Would execute: $Command $($Arguments -join ' ')" return $true } # Normal execution & $Command @Arguments return $LASTEXITCODE -eq 0 } Task Build { Write-Host "Building..." -ForegroundColor Green $success = Invoke-ExternalCommand -Command 'dotnet' -Arguments @('build', '-c', $Configuration) if (-not $success) { throw "Build failed" } } Task Test -depends Build { Write-Host "Running tests..." -ForegroundColor Green $success = Invoke-ExternalCommand -Command 'dotnet' -Arguments @('test') if (-not $success) { throw "Tests failed" } } # Expose task information for testing Task ShowTasks { Get-PSakeScriptTasks | ForEach-Object { Write-Host "Task: $($_.Name)" -ForegroundColor Cyan Write-Host " Depends: $($_.DependsOn -join ', ')" -ForegroundColor Gray Write-Host " Precondition: $($null -ne $_.Precondition)" -ForegroundColor Gray } } ``` ## CI/CD Integration[​](#cicd-integration "Direct link to CI/CD Integration") ### GitHub Actions[​](#github-actions "Direct link to GitHub Actions") ``` name: Test Build Scripts on: [push, pull_request] jobs: test: runs-on: windows-latest steps: - uses: actions/checkout@v4 - name: Install dependencies shell: pwsh run: | Install-Module -Name psake -Force Install-Module -Name Pester -Force -SkipPublisherCheck - name: Run build script tests shell: pwsh run: | Invoke-Pester -Path ./tests -Output Detailed -CI - name: Upload test results if: always() uses: actions/upload-artifact@v4 with: name: test-results path: ./testResults.xml ``` ### Complete Test Configuration[​](#complete-test-configuration "Direct link to Complete Test Configuration") **PesterConfiguration.ps1:** ``` # Configure Pester $config = New-PesterConfiguration # General settings $config.Run.Path = './tests' $config.Run.PassThru = $true $config.Run.Exit = $true # Output settings $config.Output.Verbosity = 'Detailed' # Test result export $config.TestResult.Enabled = $true $config.TestResult.OutputFormat = 'NUnitXml' $config.TestResult.OutputPath = './testResults.xml' # Code coverage $config.CodeCoverage.Enabled = $true $config.CodeCoverage.Path = './psakefile.ps1', './build/**/*.ps1' $config.CodeCoverage.OutputFormat = 'JaCoCo' $config.CodeCoverage.OutputPath = './coverage.xml' # Run tests $result = Invoke-Pester -Configuration $config # Exit with test result status exit $result.FailedCount ``` ## Complete Test Suite Example[​](#complete-test-suite-example "Direct link to Complete Test Suite Example") **tests/BuildScript.Tests.ps1:** ``` BeforeAll { # Import modules Import-Module psake -Force Import-Module Pester -Force # Set up paths $script:ProjectRoot = Split-Path $PSScriptRoot -Parent $script:BuildFile = Join-Path $ProjectRoot 'psakefile.ps1' $script:BuildDir = Join-Path $ProjectRoot 'build/output' # Enable test mode $env:PSAKE_TEST_MODE = 'true' } Describe 'Build Script Validation' { Context 'File Structure' { It 'Build file exists' { Test-Path $BuildFile | Should -Be $true } It 'Build file defines valid tasks' { { Get-PSakeScriptTasks -BuildFile $BuildFile } | Should -Not -Throw } It 'Build tasks directory exists' { $tasksDir = Join-Path $ProjectRoot 'build/tasks' Test-Path $tasksDir | Should -BeTrue } } Context 'Task Definitions' { BeforeAll { $script:Tasks = Get-PSakeScriptTasks -BuildFile $BuildFile } It 'Defines Default task' { $Tasks.Name | Should -Contain 'Default' } It 'Defines Build task' { $Tasks.Name | Should -Contain 'Build' } It 'Defines Test task' { $Tasks.Name | Should -Contain 'Test' } It 'Test task depends on Build' { $testTask = $Tasks | Where-Object Name -eq 'Test' $testTask.DependsOn | Should -Contain 'Build' } } Context 'Task Execution' { BeforeEach { # Clean before each test if (Test-Path $BuildDir) { Remove-Item $BuildDir -Recurse -Force } } It 'Clean task executes successfully' { $result = Invoke-psake -buildFile $BuildFile -taskList Clean -nologo $result.Success | Should -BeTrue } It 'Build task executes successfully' { $result = Invoke-psake -buildFile $BuildFile -taskList Build -nologo $result.Success | Should -BeTrue } It 'Full pipeline executes successfully' { $result = Invoke-psake -buildFile $BuildFile -nologo $result.Success | Should -BeTrue } } Context 'Properties and Configuration' { It 'Respects Configuration property' { $result = Invoke-psake -buildFile $BuildFile -properties @{ Configuration = 'Release' } -taskList ValidateReleaseConfiguration -nologo $result.Success | Should -BeTrue } It 'Respects Environment property' { $result = Invoke-psake -buildFile $BuildFile -properties @{ Environment = 'staging' } -taskList ValidateStagingEnvironment -nologo $result.Success | Should -BeTrue } } Context 'Error Handling' { It 'Fails gracefully on invalid task' { $result = Invoke-psake -buildFile $BuildFile -taskList InvalidTask -nologo $result.Success | Should -BeFalse } It 'Validates required environment variables' { $originalEnv = $env:REQUIRED_VAR $env:REQUIRED_VAR = $null try { $result = Invoke-psake -buildFile $BuildFile -taskList Deploy -nologo $result.Success | Should -BeFalse } finally { $env:REQUIRED_VAR = $originalEnv } } } } AfterAll { # Clean up $env:PSAKE_TEST_MODE = $null if (Test-Path $BuildDir) { Remove-Item $BuildDir -Recurse -Force } } ``` ## Best Practices[​](#best-practices "Direct link to Best Practices") 1. **Test early and often** - Run tests during development 2. **Mock external dependencies** - Don't rely on external services in tests 3. **Test both success and failure paths** - Ensure proper error handling 4. **Use test mode flags** - Allow build scripts to run in test mode 5. **Test task dependencies** - Verify tasks execute in correct order 6. **Test all configurations** - Validate Debug, Release, and different environments 7. **Keep tests fast** - Mock slow operations 8. **Use meaningful test names** - Describe what's being tested 9. **Clean up after tests** - Remove test artifacts 10. **Integrate with CI/CD** - Run tests automatically on every commit ## See Also[​](#see-also "Direct link to See Also") * [Organizing Large Scripts](/docs/best-practices/organizing-large-scripts.md) - Modular build organization * [Environment Management](/docs/best-practices/environment-management.md) - Testing multiple environments * [GitHub Actions](/docs/ci-examples/github-actions.md) - CI/CD integration * [Debug Script](/docs/tutorial-advanced/debug-script.md) - Debugging psake scripts * [Logging and Errors](/docs/tutorial-advanced/logging-errors.md) - Error handling --- # Build Versioning Strategies Proper version management ensures traceability, reproducibility, and clear release history. This guide shows you how to implement versioning strategies in psake using semantic versioning, git tags, CI build numbers, and automated assembly updates. ## Quick Start[​](#quick-start "Direct link to Quick Start") Here's a basic versioning setup using git tags and build numbers: ``` function Get-GitVersion { try { $tag = git describe --tags --abbrev=0 2>$null if ($tag -match '^v?(\d+\.\d+\.\d+)$') { return $matches[1] } } catch { } return '1.0.0' } Properties { $BaseVersion = Get-GitVersion $BuildNumber = if ($env:BUILD_NUMBER) { $env:BUILD_NUMBER } else { '0' } $Version = "$BaseVersion-ci.$BuildNumber" } Task Build { Write-Host "Building version: $Version" -ForegroundColor Cyan exec { dotnet build -c Release /p:Version=$Version } } ``` ## Semantic Versioning (SemVer)[​](#semantic-versioning-semver "Direct link to Semantic Versioning (SemVer)") Semantic versioning (MAJOR.MINOR.PATCH) is the industry standard: * **MAJOR** - Breaking changes (incompatible API changes) * **MINOR** - New features (backward-compatible functionality) * **PATCH** - Bug fixes (backward-compatible bug fixes) ### Manual Semantic Versioning[​](#manual-semantic-versioning "Direct link to Manual Semantic Versioning") ``` Properties { # Manually maintained version $MajorVersion = 1 $MinorVersion = 5 $PatchVersion = 2 # CI build number $BuildNumber = if ($env:BUILD_NUMBER) { $env:BUILD_NUMBER } else { '0' } # Construct versions $SemanticVersion = "$MajorVersion.$MinorVersion.$PatchVersion" $AssemblyVersion = "$MajorVersion.$MinorVersion.0.0" $FileVersion = "$MajorVersion.$MinorVersion.$PatchVersion.$BuildNumber" $InformationalVersion = "$SemanticVersion+build.$BuildNumber" } Task ShowVersion { Write-Host "Version Information:" -ForegroundColor Cyan Write-Host " Semantic Version: $SemanticVersion" -ForegroundColor Gray Write-Host " Assembly Version: $AssemblyVersion" -ForegroundColor Gray Write-Host " File Version: $FileVersion" -ForegroundColor Gray Write-Host " Informational Version: $InformationalVersion" -ForegroundColor Gray } Task Build { exec { dotnet build ` /p:Version=$SemanticVersion ` /p:AssemblyVersion=$AssemblyVersion ` /p:FileVersion=$FileVersion ` /p:InformationalVersion=$InformationalVersion } } ``` ### Pre-release Versions[​](#pre-release-versions "Direct link to Pre-release Versions") ``` Properties { $MajorVersion = 2 $MinorVersion = 0 $PatchVersion = 0 $BuildNumber = if ($env:BUILD_NUMBER) { $env:BUILD_NUMBER } else { '0' } # Determine pre-release label $Branch = if ($env:BRANCH_NAME) { $env:BRANCH_NAME } else { 'develop' } $PreReleaseLabel = switch -Regex ($Branch) { '^main$|^master$' { '' } # Production release '^release/.*' { 'rc' } # Release candidate '^develop$' { 'beta' } # Beta release '^feature/.*' { 'alpha' } # Alpha release default { 'dev' } # Development build } # Construct version if ([string]::IsNullOrEmpty($PreReleaseLabel)) { $Version = "$MajorVersion.$MinorVersion.$PatchVersion" } else { $Version = "$MajorVersion.$MinorVersion.$PatchVersion-$PreReleaseLabel.$BuildNumber" } } Task Build { Write-Host "Building version: $Version" -ForegroundColor Cyan Write-Host " Branch: $Branch" -ForegroundColor Gray Write-Host " Pre-release: $PreReleaseLabel" -ForegroundColor Gray exec { dotnet build ` -c Release ` /p:Version=$Version ` /p:VersionPrefix="$MajorVersion.$MinorVersion.$PatchVersion" ` /p:VersionSuffix=$PreReleaseLabel } } ``` ## Git-Based Versioning[​](#git-based-versioning "Direct link to Git-Based Versioning") Derive versions from git tags and commit history: ### Using Git Tags[​](#using-git-tags "Direct link to Using Git Tags") The `Get-GitVersion` helper from the quick start validates tags as `X.Y.Z`. Add commit distance when the checkout is ahead of the tag: ``` Properties { $LatestTag = git describe --tags --abbrev=0 2>$null $BaseVersion = Get-GitVersion $CommitsSinceTag = if ($LatestTag) { git rev-list "$LatestTag..HEAD" --count 2>$null } else { 0 } $Version = if ($CommitsSinceTag -gt 0) { "$BaseVersion-dev.$CommitsSinceTag" } else { $BaseVersion } } Task Build { Write-Host "Git-based version: $Version" -ForegroundColor Cyan exec { dotnet build -c Release /p:Version=$Version } } Task CreateTag { param( [string]$TagVersion = '1.0.0', [string]$Message = 'Release' ) Write-Host "Creating git tag: v$TagVersion" -ForegroundColor Green # Validate version format if ($TagVersion -notmatch '^\d+\.\d+\.\d+$') { throw "Invalid version format: $TagVersion (expected: MAJOR.MINOR.PATCH)" } # Create annotated tag exec { git tag -a "v$TagVersion" -m $Message } Write-Host "Tag created successfully. Push with: git push origin v$TagVersion" -ForegroundColor Yellow } ``` ### Using GitVersion Tool[​](#using-gitversion-tool "Direct link to Using GitVersion Tool") ``` Properties { $GitVersionExe = 'gitversion' } Task InstallGitVersion { Write-Host "Installing GitVersion..." -ForegroundColor Green exec { dotnet tool install --global GitVersion.Tool } } Task GetGitVersion { Write-Host "Calculating version with GitVersion..." -ForegroundColor Green # Run GitVersion and parse output $versionJson = & $GitVersionExe | ConvertFrom-Json # Extract version components $script:Version = $versionJson.SemVer $script:MajorMinorPatch = $versionJson.MajorMinorPatch $script:InformationalVersion = $versionJson.InformationalVersion $script:AssemblyVersion = $versionJson.AssemblySemVer $script:FileVersion = $versionJson.AssemblySemFileVer $script:NuGetVersion = $versionJson.NuGetVersionV2 Write-Host " SemVer: $Version" -ForegroundColor Cyan Write-Host " NuGet: $NuGetVersion" -ForegroundColor Gray Write-Host " Assembly: $AssemblyVersion" -ForegroundColor Gray } Task Build -depends GetGitVersion { exec { dotnet build ` /p:Version=$NuGetVersion ` /p:AssemblyVersion=$AssemblyVersion ` /p:FileVersion=$FileVersion ` /p:InformationalVersion=$InformationalVersion } } ``` **GitVersion.yml:** ``` mode: Mainline branches: main: label: '' develop: label: 'beta' feature: label: 'alpha.{BranchName}' release: label: 'rc' hotfix: label: 'hotfix' ignore: sha: [] ``` ## CI Build Number Versioning[​](#ci-build-number-versioning "Direct link to CI Build Number Versioning") Leverage CI/CD build numbers: ### GitHub Actions[​](#github-actions "Direct link to GitHub Actions") ``` Properties { $BaseVersion = '1.0.0' $BuildNumber = if ($env:GITHUB_RUN_NUMBER) { $env:GITHUB_RUN_NUMBER } else { '0' } $GitSha = if ($env:GITHUB_SHA) { $env:GITHUB_SHA.Substring(0, 7) } else { 'local' } # SemVer build identity and informational metadata $Version = "$BaseVersion-ci.$BuildNumber" $InformationalVersion = "$Version+$GitSha" } Task Build { Write-Host "Building version $Version" -ForegroundColor Cyan Write-Host " Build: $BuildNumber" -ForegroundColor Gray Write-Host " Commit: $GitSha" -ForegroundColor Gray exec { dotnet build ` /p:Version=$Version ` /p:InformationalVersion=$InformationalVersion } } ``` **.github/workflows/build.yml:** ``` name: Build on: [push] jobs: build: runs-on: windows-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 0 # Get full history for versioning - name: Install psake shell: pwsh run: Install-Module -Name psake -Force - name: Build shell: pwsh run: | Invoke-psake -buildFile .\psakefile.ps1 -taskList Build env: GITHUB_RUN_NUMBER: ${{ github.run_number }} GITHUB_SHA: ${{ github.sha }} ``` ### Azure Pipelines[​](#azure-pipelines "Direct link to Azure Pipelines") ``` Properties { $BaseVersion = '1.0.0' $BuildNumber = if ($env:BUILD_BUILDID) { $env:BUILD_BUILDID } else { '0' } $Version = "$BaseVersion-ci.$BuildNumber" } Task Build { Write-Host "Building version $Version" -ForegroundColor Cyan # Update Azure Pipelines build number if ($env:BUILD_BUILDNUMBER) { Write-Host "##vso[build.updatebuildnumber]$Version" } exec { dotnet build /p:Version=$Version } } ``` **azure-pipelines.yml:** ``` trigger: - main pool: vmImage: 'windows-latest' name: '1.0.$(Rev:r)' steps: - task: PowerShell@2 displayName: 'Build' inputs: targetType: 'inline' script: | Install-Module -Name psake -Force Invoke-psake -buildFile .\psakefile.ps1 -taskList Build ``` ## Assembly Version Updates[​](#assembly-version-updates "Direct link to Assembly Version Updates") Automatically update project file versions: ### Updating .NET Project Files[​](#updating-net-project-files "Direct link to Updating .NET Project Files") ``` function Update-ProjectVersion { param( [string]$ProjectFile, [string]$Version, [string]$AssemblyVersion ) if (-not (Test-Path $ProjectFile)) { throw "Project file not found: $ProjectFile" } [xml]$project = Get-Content $ProjectFile -Raw $propertyGroup = $project.Project.PropertyGroup | Select-Object -First 1 if ($null -eq $propertyGroup) { $propertyGroup = $project.CreateElement('PropertyGroup') $project.Project.AppendChild($propertyGroup) | Out-Null } $versions = @{ Version = $Version InformationalVersion = $Version AssemblyVersion = $AssemblyVersion FileVersion = $AssemblyVersion } foreach ($entry in $versions.GetEnumerator()) { if ($null -eq $propertyGroup.($entry.Key)) { $propertyGroup.AppendChild($project.CreateElement($entry.Key)) | Out-Null } $propertyGroup.($entry.Key) = $entry.Value } $project.Save($ProjectFile) Write-Host " Version updated to: $Version" -ForegroundColor Gray } Task UpdateProjectVersions { Write-Host "Updating project versions..." -ForegroundColor Green $projects = Get-ChildItem -Path $SrcDir -Filter *.csproj -Recurse foreach ($project in $projects) { Update-ProjectVersion ` -ProjectFile $project.FullName ` -Version $Version ` -AssemblyVersion "$MajorVersion.$MinorVersion.$PatchVersion.0" } Write-Host "Updated $($projects.Count) project files" -ForegroundColor Green } Task Build -depends UpdateProjectVersions { exec { dotnet build -c Release } } ``` ### Updating AssemblyInfo.cs (Legacy)[​](#updating-assemblyinfocs-legacy "Direct link to Updating AssemblyInfo.cs (Legacy)") ``` function Update-AssemblyInfo { param( [string]$AssemblyInfoPath, [string]$Version, [string]$AssemblyVersion ) if (-not (Test-Path $AssemblyInfoPath)) { throw "AssemblyInfo.cs not found: $AssemblyInfoPath" } $content = Get-Content $AssemblyInfoPath $content = $content -replace '\[assembly: AssemblyVersion\(".*?"\)\]', "[assembly: AssemblyVersion(""$AssemblyVersion"")]" $content = $content -replace '\[assembly: AssemblyFileVersion\(".*?"\)\]', "[assembly: AssemblyFileVersion(""$AssemblyVersion"")]" $content = $content -replace '\[assembly: AssemblyInformationalVersion\(".*?"\)\]', "[assembly: AssemblyInformationalVersion(""$Version"")]" Set-Content -Path $AssemblyInfoPath -Value $content Write-Host " Updated to version: $Version" -ForegroundColor Gray } Task UpdateAssemblyInfoFiles { $assemblyInfoFiles = Get-ChildItem "$SrcDir/**/AssemblyInfo.cs" -Recurse foreach ($file in $assemblyInfoFiles) { Update-AssemblyInfo ` -AssemblyInfoPath $file.FullName ` -Version $Version ` -AssemblyVersion "$MajorVersion.$MinorVersion.$PatchVersion.0" } } ``` ### Updating package.json (Node.js)[​](#updating-packagejson-nodejs "Direct link to Updating package.json (Node.js)") ``` function Update-PackageVersion { param( [string]$PackageJsonPath, [string]$Version ) if (-not (Test-Path $PackageJsonPath)) { throw "package.json not found: $PackageJsonPath" } Write-Host "Updating package.json version to $Version" -ForegroundColor Green $package = Get-Content $PackageJsonPath | ConvertFrom-Json $package.version = $Version $package | ConvertTo-Json -Depth 100 | Set-Content $PackageJsonPath Write-Host " Updated package.json" -ForegroundColor Gray } Task UpdateNodeVersion { $packageJson = Join-Path $ProjectRoot 'package.json' Update-PackageVersion -PackageJsonPath $packageJson -Version $Version } ``` ## Complete Versioning Example[​](#complete-versioning-example "Direct link to Complete Versioning Example") **psakefile.ps1:** ``` Properties { $ProjectRoot = $PSScriptRoot $SrcDir = Join-Path $ProjectRoot 'src' $BuildDir = Join-Path $ProjectRoot 'build/output' # Version configuration $MajorVersion = 1 $MinorVersion = 0 $PatchVersion = 0 # CI/CD integration $BuildNumber = if ($env:BUILD_NUMBER) { $env:BUILD_NUMBER } else { '0' } $GitSha = Get-GitCommitSha $Branch = Get-GitBranch # Determine version based on branch $Version = Get-BuildVersion } function Get-GitCommitSha { try { $sha = git rev-parse --short HEAD 2>$null return if ($sha) { $sha } else { 'unknown' } } catch { return 'unknown' } } function Get-GitBranch { try { if ($env:BRANCH_NAME) { return $env:BRANCH_NAME } $branch = git rev-parse --abbrev-ref HEAD 2>$null return if ($branch) { $branch } else { 'unknown' } } catch { return 'unknown' } } function Get-BuildVersion { $baseVersion = "$MajorVersion.$MinorVersion.$PatchVersion" # Determine pre-release label $preRelease = switch -Regex ($Branch) { '^main$|^master$' { return "$baseVersion-ci.$BuildNumber" } '^release/.*' { # Release candidate return "$baseVersion-rc.$BuildNumber" } '^develop$' { # Beta release return "$baseVersion-beta.$BuildNumber" } '^hotfix/.*' { # Hotfix release return "$baseVersion-hotfix.$BuildNumber" } default { # Development/feature build $safeBranch = $Branch -replace '[^a-zA-Z0-9]', '-' return "$baseVersion-dev.$safeBranch.$BuildNumber" } } return $preRelease } FormatTaskName { param($taskName) Write-Host "" Write-Host "Executing: $taskName" -ForegroundColor Cyan Write-Host ("=" * 80) -ForegroundColor Gray } Task Default -depends Build Task ShowVersion { Write-Host "" Write-Host "Version Information" -ForegroundColor Cyan Write-Host ("=" * 80) -ForegroundColor Gray Write-Host " Version: $Version" -ForegroundColor White Write-Host " Base: $MajorVersion.$MinorVersion.$PatchVersion" -ForegroundColor Gray Write-Host " Build Number: $BuildNumber" -ForegroundColor Gray Write-Host " Git SHA: $GitSha" -ForegroundColor Gray Write-Host " Branch: $Branch" -ForegroundColor Gray Write-Host ("=" * 80) -ForegroundColor Gray Write-Host "" } Task UpdateVersions -depends ShowVersion { Write-Host "Updating project versions..." -ForegroundColor Green # Update .NET projects $projects = Get-ChildItem -Path $SrcDir -Filter *.csproj -Recurse foreach ($project in $projects) { [xml]$proj = Get-Content $project.FullName -Raw $propertyGroup = $proj.Project.PropertyGroup | Select-Object -First 1 if ($null -eq $propertyGroup.Version) { $versionNode = $proj.CreateElement('Version') $propertyGroup.AppendChild($versionNode) | Out-Null } $propertyGroup.Version = $Version $proj.Save($project.FullName) Write-Host " Updated: $($project.Name)" -ForegroundColor Gray } Write-Host "Version updates complete" -ForegroundColor Green } Task Clean { Write-Host "Cleaning build artifacts..." -ForegroundColor Green if (Test-Path $BuildDir) { Remove-Item $BuildDir -Recurse -Force } New-Item -ItemType Directory -Path $BuildDir | Out-Null } Task Build -depends UpdateVersions, Clean { Write-Host "Building version $Version..." -ForegroundColor Green exec { dotnet build $SrcDir ` -c Release ` -o $BuildDir ` /p:Version=$Version ` /p:InformationalVersion="$Version+$GitSha" } Write-Host "Build complete: $BuildDir" -ForegroundColor Green } Task Pack -depends Build { Write-Host "Creating NuGet packages..." -ForegroundColor Green exec { dotnet pack $SrcDir ` -c Release ` -o $BuildDir ` --no-build ` /p:PackageVersion=$Version } $packages = Get-ChildItem "$BuildDir/*.nupkg" Write-Host "Created $($packages.Count) package(s)" -ForegroundColor Green } Task Tag { param([string]$TagVersion) if ([string]::IsNullOrEmpty($TagVersion)) { $TagVersion = "$MajorVersion.$MinorVersion.$PatchVersion" } Write-Host "Creating git tag: v$TagVersion" -ForegroundColor Green # Ensure we're on main/master if ($Branch -notmatch '^(main|master)$') { throw "Tags should only be created from main/master branch (current: $Branch)" } # Check if tag already exists $existingTag = git tag -l "v$TagVersion" if ($existingTag) { throw "Tag v$TagVersion already exists" } # Create annotated tag exec { git tag -a "v$TagVersion" -m "Release $TagVersion" } Write-Host "Tag created: v$TagVersion" -ForegroundColor Green Write-Host "Push tag with: git push origin v$TagVersion" -ForegroundColor Yellow } ``` ## Best Practices[​](#best-practices "Direct link to Best Practices") 1. **Use semantic versioning** - Follow MAJOR.MINOR.PATCH conventions 2. **Tag releases in git** - Create git tags for all releases 3. **Include build metadata** - Add commit SHA and build number to informational version 4. **Automate version bumps** - Don't manually edit version numbers 5. **Use pre-release labels** - Distinguish beta, alpha, and RC versions 6. **Keep version in one place** - Single source of truth for version number 7. **Version all artifacts** - DLLs, packages, containers should all have same version 8. **Document version strategy** - Team should understand versioning scheme 9. **Test version updates** - Ensure version updates don't break builds 10. **Archive version history** - Maintain changelog with version history ## See Also[​](#see-also "Direct link to See Also") * [GitHub Actions](/docs/ci-examples/github-actions.md) - CI/CD with versioning * [Azure Pipelines](/docs/ci-examples/azure-pipelines.md) - Azure DevOps versioning * [Node.js Builds](/docs/build-types/nodejs.md) - Versioning Node.js packages * [.NET Solution Builds](/docs/build-types/dot-net-solution.md) - .NET versioning * [Organizing Large Scripts](/docs/best-practices/organizing-large-scripts.md) - Version utilities organization --- # Docker Container Builds psake can orchestrate Docker-based build workflows, providing a consistent PowerShell automation layer for containerized applications. This guide shows you how to build Docker images, use multi-stage builds, push to registries, and integrate with Docker Compose. ## Quick Start[​](#quick-start "Direct link to Quick Start") Here's a basic psake build script for Docker: ``` Properties { $ImageName = 'myapp' $ImageTag = 'latest' $ContainerName = 'myapp-container' } Task Default -depends Build Task Build { Write-Host "Building Docker image..." -ForegroundColor Green exec { docker build -t "${ImageName}:${ImageTag}" . } } Task Run -depends Build { Write-Host "Running Docker container..." -ForegroundColor Green exec { docker run -d --name $ContainerName -p 8080:80 "${ImageName}:${ImageTag}" } } Task Stop { Write-Host "Stopping container..." -ForegroundColor Green exec { docker stop $ContainerName } -errorMessage "Container not running" exec { docker rm $ContainerName } -errorMessage "Container not found" } ``` Run the build: ``` Invoke-psake -buildFile .\psakefile.ps1 ``` ## Complete Docker Build Example[​](#complete-docker-build-example "Direct link to Complete Docker Build Example") Here's a comprehensive psakefile.ps1 for Docker-based builds: ``` Properties { $ProjectRoot = $PSScriptRoot $DockerfilePath = Join-Path $ProjectRoot 'Dockerfile' $DockerComposeFile = Join-Path $ProjectRoot 'docker-compose.yml' # Image configuration $ImageName = 'myapp' $ImageTag = if ($env:BUILD_NUMBER) { "1.0.$env:BUILD_NUMBER" } else { 'latest' } $ImageFullName = "${ImageName}:${ImageTag}" # Registry configuration $Registry = 'docker.io' $RegistryUsername = $env:DOCKER_USERNAME $RegistryToken = $env:DOCKER_TOKEN $RegistryImage = "${Registry}/${RegistryUsername}/${ImageName}:${ImageTag}" # Container configuration $ContainerName = 'myapp-container' $ContainerPort = 8080 $HostPort = 8080 # Build configuration $BuildArgs = @{} $Platform = 'linux/amd64' $NoCache = $false } FormatTaskName { param($taskName) Write-Host "Executing task: $taskName" -ForegroundColor Cyan } Task Default -depends Build Task Verify { Write-Host "Verifying Docker installation..." -ForegroundColor Green try { $dockerVersion = docker --version Write-Host " Docker: $dockerVersion" -ForegroundColor Gray } catch { throw "Docker is not installed or not in PATH. Install from https://docker.com/" } if (-not (Test-Path $DockerfilePath)) { throw "Dockerfile not found at: $DockerfilePath" } } Task Clean { Write-Host "Cleaning up Docker resources..." -ForegroundColor Green # Stop and remove container if exists $container = docker ps -a --filter "name=$ContainerName" --format "{{.Names}}" 2>$null if ($container -eq $ContainerName) { Write-Host " Stopping container: $ContainerName" -ForegroundColor Gray exec { docker stop $ContainerName } -errorMessage "Failed to stop container" exec { docker rm $ContainerName } -errorMessage "Failed to remove container" } # Remove dangling images $danglingImages = docker images -f "dangling=true" -q 2>$null if ($danglingImages) { Write-Host " Removing dangling images" -ForegroundColor Gray docker rmi $danglingImages 2>$null } } Task Build -depends Verify { Write-Host "Building Docker image: $ImageFullName" -ForegroundColor Green $buildCmd = "docker build" # Add platform if specified if ($Platform) { $buildCmd += " --platform $Platform" } # Add no-cache flag if specified if ($NoCache) { $buildCmd += " --no-cache" } # Add build args foreach ($key in $BuildArgs.Keys) { $buildCmd += " --build-arg ${key}=$($BuildArgs[$key])" } # Add tag and context $buildCmd += " -t $ImageFullName ." Write-Host " Build command: $buildCmd" -ForegroundColor Gray exec { Invoke-Expression $buildCmd } Write-Host "Docker image built successfully: $ImageFullName" -ForegroundColor Green } Task BuildNoCache { $script:NoCache = $true Invoke-psake -taskList Build } Task Tag -depends Build { Write-Host "Tagging image for registry..." -ForegroundColor Green if ([string]::IsNullOrEmpty($RegistryUsername)) { throw "DOCKER_USERNAME environment variable is required" } exec { docker tag $ImageFullName $RegistryImage } Write-Host "Tagged: $RegistryImage" -ForegroundColor Green } Task Inspect -depends Build { Write-Host "Inspecting Docker image..." -ForegroundColor Green $imageInfo = docker inspect $ImageFullName | ConvertFrom-Json Write-Host " Image ID: $($imageInfo[0].Id)" -ForegroundColor Gray Write-Host " Created: $($imageInfo[0].Created)" -ForegroundColor Gray Write-Host " Size: $([math]::Round($imageInfo[0].Size / 1MB, 2)) MB" -ForegroundColor Gray Write-Host " Architecture: $($imageInfo[0].Architecture)" -ForegroundColor Gray # List image layers Write-Host " Layers: $($imageInfo[0].RootFS.Layers.Count)" -ForegroundColor Gray } Task Scan -depends Build { Write-Host "Scanning image for vulnerabilities..." -ForegroundColor Green # Check if docker scan is available (requires Docker Desktop) try { exec { docker scan $ImageFullName } } catch { Write-Warning "Docker scan not available. Consider using Trivy or Snyk for vulnerability scanning." } } Task Run -depends Build, Clean { Write-Host "Running Docker container: $ContainerName" -ForegroundColor Green exec { docker run -d ` --name $ContainerName ` -p "${HostPort}:${ContainerPort}" ` $ImageFullName } Write-Host "Container started: $ContainerName" -ForegroundColor Green Write-Host "Access application at: http://localhost:${HostPort}" -ForegroundColor Yellow } Task RunInteractive -depends Build { Write-Host "Running Docker container interactively..." -ForegroundColor Green exec { docker run -it --rm -p "${HostPort}:${ContainerPort}" $ImageFullName } } Task Exec { Write-Host "Executing shell in running container..." -ForegroundColor Green $container = docker ps --filter "name=$ContainerName" --format "{{.Names}}" 2>$null if ($container -ne $ContainerName) { throw "Container $ContainerName is not running. Start it first with 'Run' task." } exec { docker exec -it $ContainerName /bin/sh } } Task Logs { Write-Host "Viewing container logs..." -ForegroundColor Green $container = docker ps -a --filter "name=$ContainerName" --format "{{.Names}}" 2>$null if ($container -ne $ContainerName) { throw "Container $ContainerName not found" } exec { docker logs -f $ContainerName } } Task Stop { Write-Host "Stopping container: $ContainerName" -ForegroundColor Green $container = docker ps --filter "name=$ContainerName" --format "{{.Names}}" 2>$null if ($container -eq $ContainerName) { exec { docker stop $ContainerName } Write-Host "Container stopped: $ContainerName" -ForegroundColor Green } else { Write-Warning "Container $ContainerName is not running" } } Task Remove -depends Stop { Write-Host "Removing container: $ContainerName" -ForegroundColor Green $container = docker ps -a --filter "name=$ContainerName" --format "{{.Names}}" 2>$null if ($container -eq $ContainerName) { exec { docker rm $ContainerName } Write-Host "Container removed: $ContainerName" -ForegroundColor Green } } Task Login { Write-Host "Logging in to Docker registry..." -ForegroundColor Green if ([string]::IsNullOrEmpty($RegistryUsername)) { throw "DOCKER_USERNAME environment variable is required" } if ([string]::IsNullOrEmpty($RegistryToken)) { throw "DOCKER_TOKEN environment variable is required" } # Use token authentication (recommended for CI/CD) $env:DOCKER_TOKEN | docker login $Registry --username $RegistryUsername --password-stdin Write-Host "Successfully logged in to $Registry" -ForegroundColor Green } Task Push -depends Tag, Login { Write-Host "Pushing image to registry..." -ForegroundColor Green exec { docker push $RegistryImage } Write-Host "Successfully pushed: $RegistryImage" -ForegroundColor Green } Task Pull { Write-Host "Pulling image from registry..." -ForegroundColor Green if ([string]::IsNullOrEmpty($RegistryUsername)) { throw "DOCKER_USERNAME environment variable is required" } exec { docker pull $RegistryImage } Write-Host "Successfully pulled: $RegistryImage" -ForegroundColor Green } Task Prune { Write-Host "Pruning unused Docker resources..." -ForegroundColor Green $confirmation = Read-Host "This will remove all unused images, containers, and networks. Continue? (yes/no)" if ($confirmation -eq 'yes') { exec { docker system prune -a -f --volumes } Write-Host "Docker system pruned" -ForegroundColor Green } else { Write-Host "Prune cancelled" -ForegroundColor Yellow } } ``` ## Multi-Stage Builds[​](#multi-stage-builds "Direct link to Multi-Stage Builds") Multi-stage builds create smaller, more secure production images: ``` Properties { $ImageName = 'myapp' $ImageTag = 'latest' $BuildStage = 'production' # Options: development, production } Task BuildDevelopment { Write-Host "Building development image..." -ForegroundColor Green exec { docker build ` --target development ` -t "${ImageName}:dev" ` . } } Task BuildProduction { Write-Host "Building production image..." -ForegroundColor Green exec { docker build ` --target production ` -t "${ImageName}:${ImageTag}" ` . } } Task BuildAll { Write-Host "Building all stages..." -ForegroundColor Green # Build development stage Invoke-psake -taskList BuildDevelopment # Build production stage Invoke-psake -taskList BuildProduction } ``` Example multi-stage `Dockerfile`: ``` # Stage 1: Build stage FROM node:18-alpine AS builder WORKDIR /app # Copy dependency files COPY package*.json ./ # Install all dependencies (including dev dependencies) RUN npm ci # Copy source code COPY . . # Run tests and build RUN npm run test RUN npm run build # Stage 2: Development stage (includes dev tools) FROM node:18-alpine AS development WORKDIR /app COPY package*.json ./ RUN npm ci COPY . . EXPOSE 3000 CMD ["npm", "run", "dev"] # Stage 3: Production stage (optimized) FROM node:18-alpine AS production WORKDIR /app # Copy only production dependencies COPY package*.json ./ RUN npm ci --only=production # Copy built artifacts from builder COPY --from=builder /app/dist ./dist # Create non-root user RUN addgroup -g 1001 -S nodejs && \ adduser -S nodejs -u 1001 USER nodejs EXPOSE 3000 CMD ["node", "dist/index.js"] ``` For .NET applications: ``` # Stage 1: Build FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build WORKDIR /src COPY ["MyApp/MyApp.csproj", "MyApp/"] RUN dotnet restore "MyApp/MyApp.csproj" COPY . . WORKDIR "/src/MyApp" RUN dotnet build "MyApp.csproj" -c Release -o /app/build # Stage 2: Publish FROM build AS publish RUN dotnet publish "MyApp.csproj" -c Release -o /app/publish # Stage 3: Runtime FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS runtime WORKDIR /app COPY --from=publish /app/publish . EXPOSE 80 EXPOSE 443 ENTRYPOINT ["dotnet", "MyApp.dll"] ``` ## Pushing to Container Registries[​](#pushing-to-container-registries "Direct link to Pushing to Container Registries") ### Docker Hub[​](#docker-hub "Direct link to Docker Hub") ``` Properties { $DockerHubUsername = $env:DOCKER_USERNAME $DockerHubToken = $env:DOCKER_TOKEN $Repository = 'myapp' $Tag = 'latest' } Task PushDockerHub -depends Build { Write-Host "Pushing to Docker Hub..." -ForegroundColor Green if ([string]::IsNullOrEmpty($DockerHubUsername) -or [string]::IsNullOrEmpty($DockerHubToken)) { throw "DOCKER_USERNAME and DOCKER_TOKEN environment variables are required" } # Login $env:DOCKER_TOKEN | docker login --username $DockerHubUsername --password-stdin # Tag image $fullImage = "${DockerHubUsername}/${Repository}:${Tag}" exec { docker tag "${Repository}:${Tag}" $fullImage } # Push exec { docker push $fullImage } Write-Host "Successfully pushed to Docker Hub: $fullImage" -ForegroundColor Green } ``` ### AWS Elastic Container Registry (ECR)[​](#aws-elastic-container-registry-ecr "Direct link to AWS Elastic Container Registry (ECR)") ``` Properties { $AwsRegion = if ($env:AWS_REGION) { $env:AWS_REGION } else { 'us-east-1' } $AwsAccountId = $env:AWS_ACCOUNT_ID $EcrRepository = 'myapp' $ImageTag = 'latest' } Task VerifyAwsCli { try { $awsVersion = aws --version Write-Host "AWS CLI: $awsVersion" -ForegroundColor Gray } catch { throw "AWS CLI is not installed. Install from https://aws.amazon.com/cli/" } } Task EcrLogin -depends VerifyAwsCli { Write-Host "Logging in to AWS ECR..." -ForegroundColor Green if ([string]::IsNullOrEmpty($AwsAccountId)) { throw "AWS_ACCOUNT_ID environment variable is required" } # Get ECR login password and login to Docker $loginCmd = "aws ecr get-login-password --region $AwsRegion | docker login --username AWS --password-stdin ${AwsAccountId}.dkr.ecr.${AwsRegion}.amazonaws.com" exec { Invoke-Expression $loginCmd } Write-Host "Successfully logged in to ECR" -ForegroundColor Green } Task EcrPush -depends Build, EcrLogin { Write-Host "Pushing to AWS ECR..." -ForegroundColor Green $ecrImage = "${AwsAccountId}.dkr.ecr.${AwsRegion}.amazonaws.com/${EcrRepository}:${ImageTag}" # Tag image exec { docker tag "${EcrRepository}:${ImageTag}" $ecrImage } # Push image exec { docker push $ecrImage } Write-Host "Successfully pushed to ECR: $ecrImage" -ForegroundColor Green } Task EcrCreateRepo -depends VerifyAwsCli { Write-Host "Creating ECR repository..." -ForegroundColor Green try { exec { aws ecr create-repository ` --repository-name $EcrRepository ` --region $AwsRegion ` --image-scanning-configuration scanOnPush=true } Write-Host "Repository created: $EcrRepository" -ForegroundColor Green } catch { Write-Warning "Repository may already exist or creation failed" } } ``` ### Azure Container Registry (ACR)[​](#azure-container-registry-acr "Direct link to Azure Container Registry (ACR)") ``` Properties { $AcrName = $env:ACR_NAME # e.g., 'myregistry' $AcrResourceGroup = $env:ACR_RESOURCE_GROUP $Repository = 'myapp' $ImageTag = 'latest' } Task VerifyAzCli { try { $azVersion = az --version | Select-Object -First 1 Write-Host "Azure CLI: $azVersion" -ForegroundColor Gray } catch { throw "Azure CLI is not installed. Install from https://docs.microsoft.com/cli/azure/" } } Task AcrLogin -depends VerifyAzCli { Write-Host "Logging in to Azure Container Registry..." -ForegroundColor Green if ([string]::IsNullOrEmpty($AcrName)) { throw "ACR_NAME environment variable is required" } exec { az acr login --name $AcrName } Write-Host "Successfully logged in to ACR: $AcrName" -ForegroundColor Green } Task AcrPush -depends Build, AcrLogin { Write-Host "Pushing to Azure Container Registry..." -ForegroundColor Green $acrImage = "${AcrName}.azurecr.io/${Repository}:${ImageTag}" # Tag image exec { docker tag "${Repository}:${ImageTag}" $acrImage } # Push image exec { docker push $acrImage } Write-Host "Successfully pushed to ACR: $acrImage" -ForegroundColor Green } Task AcrCreateRepo -depends VerifyAzCli { Write-Host "Creating ACR..." -ForegroundColor Green if ([string]::IsNullOrEmpty($AcrResourceGroup)) { throw "ACR_RESOURCE_GROUP environment variable is required" } try { exec { az acr create ` --resource-group $AcrResourceGroup ` --name $AcrName ` --sku Basic } Write-Host "ACR created: $AcrName" -ForegroundColor Green } catch { Write-Warning "ACR may already exist or creation failed" } } ``` ### Google Container Registry (GCR)[​](#google-container-registry-gcr "Direct link to Google Container Registry (GCR)") ``` Properties { $GcpProject = $env:GCP_PROJECT_ID $GcrHostname = 'gcr.io' # Options: gcr.io, us.gcr.io, eu.gcr.io, asia.gcr.io $Repository = 'myapp' $ImageTag = 'latest' } Task VerifyGcloud { try { $gcloudVersion = gcloud --version | Select-Object -First 1 Write-Host "Google Cloud SDK: $gcloudVersion" -ForegroundColor Gray } catch { throw "Google Cloud SDK is not installed. Install from https://cloud.google.com/sdk/" } } Task GcrLogin -depends VerifyGcloud { Write-Host "Configuring Docker for GCR..." -ForegroundColor Green exec { gcloud auth configure-docker $GcrHostname } Write-Host "Successfully configured Docker for GCR" -ForegroundColor Green } Task GcrPush -depends Build, GcrLogin { Write-Host "Pushing to Google Container Registry..." -ForegroundColor Green if ([string]::IsNullOrEmpty($GcpProject)) { throw "GCP_PROJECT_ID environment variable is required" } $gcrImage = "${GcrHostname}/${GcpProject}/${Repository}:${ImageTag}" # Tag image exec { docker tag "${Repository}:${ImageTag}" $gcrImage } # Push image exec { docker push $gcrImage } Write-Host "Successfully pushed to GCR: $gcrImage" -ForegroundColor Green } ``` ## Docker Compose Integration[​](#docker-compose-integration "Direct link to Docker Compose Integration") For multi-container applications, integrate Docker Compose: ``` Properties { $ProjectRoot = $PSScriptRoot $ComposeFile = Join-Path $ProjectRoot 'docker-compose.yml' $ComposeProjectName = 'myapp' $Environment = 'development' } Task VerifyCompose { Write-Host "Verifying Docker Compose installation..." -ForegroundColor Green try { $composeVersion = docker compose version Write-Host " Docker Compose: $composeVersion" -ForegroundColor Gray } catch { throw "Docker Compose is not available. Install Docker Desktop or Docker Compose plugin." } if (-not (Test-Path $ComposeFile)) { throw "docker-compose.yml not found at: $ComposeFile" } } Task ComposeUp -depends VerifyCompose { Write-Host "Starting Docker Compose services..." -ForegroundColor Green $env:COMPOSE_PROJECT_NAME = $ComposeProjectName $env:ENVIRONMENT = $Environment exec { docker compose -f $ComposeFile up -d } Write-Host "Services started. Use 'docker compose ps' to view status." -ForegroundColor Green } Task ComposeBuild -depends VerifyCompose { Write-Host "Building Docker Compose services..." -ForegroundColor Green $env:COMPOSE_PROJECT_NAME = $ComposeProjectName exec { docker compose -f $ComposeFile build --no-cache } Write-Host "Services built successfully" -ForegroundColor Green } Task ComposeDown { Write-Host "Stopping Docker Compose services..." -ForegroundColor Green $env:COMPOSE_PROJECT_NAME = $ComposeProjectName exec { docker compose -f $ComposeFile down } Write-Host "Services stopped" -ForegroundColor Green } Task ComposeDownVolumes { Write-Host "Stopping services and removing volumes..." -ForegroundColor Green $env:COMPOSE_PROJECT_NAME = $ComposeProjectName exec { docker compose -f $ComposeFile down -v } Write-Host "Services stopped and volumes removed" -ForegroundColor Green } Task ComposeLogs { Write-Host "Viewing Docker Compose logs..." -ForegroundColor Green $env:COMPOSE_PROJECT_NAME = $ComposeProjectName exec { docker compose -f $ComposeFile logs -f } } Task ComposePs { Write-Host "Listing Docker Compose services..." -ForegroundColor Green $env:COMPOSE_PROJECT_NAME = $ComposeProjectName exec { docker compose -f $ComposeFile ps } } Task ComposeRestart { Write-Host "Restarting Docker Compose services..." -ForegroundColor Green $env:COMPOSE_PROJECT_NAME = $ComposeProjectName exec { docker compose -f $ComposeFile restart } Write-Host "Services restarted" -ForegroundColor Green } ``` Example `docker-compose.yml`: ``` version: '3.8' services: web: build: context: . dockerfile: Dockerfile target: ${ENVIRONMENT:-production} ports: - "8080:80" environment: - NODE_ENV=${ENVIRONMENT:-production} - DATABASE_URL=postgresql://postgres:password@db:5432/myapp depends_on: - db - redis volumes: - ./logs:/app/logs networks: - app-network db: image: postgres:15-alpine environment: - POSTGRES_USER=postgres - POSTGRES_PASSWORD=password - POSTGRES_DB=myapp volumes: - db-data:/var/lib/postgresql/data networks: - app-network ports: - "5432:5432" redis: image: redis:7-alpine volumes: - redis-data:/data networks: - app-network ports: - "6379:6379" volumes: db-data: redis-data: networks: app-network: driver: bridge ``` ## Cross-Platform Builds[​](#cross-platform-builds "Direct link to Cross-Platform Builds") Build images for multiple architectures: ``` Properties { $ImageName = 'myapp' $ImageTag = 'latest' $Registry = 'docker.io' $Username = $env:DOCKER_USERNAME $Platforms = 'linux/amd64,linux/arm64,linux/arm/v7' $BuilderName = 'multiplatform-builder' } Task CreateBuilder { Write-Host "Creating buildx builder..." -ForegroundColor Green # Check if builder exists $existingBuilder = docker buildx ls | Select-String $BuilderName if (-not $existingBuilder) { exec { docker buildx create --name $BuilderName --use } Write-Host "Builder created: $BuilderName" -ForegroundColor Green } else { exec { docker buildx use $BuilderName } Write-Host "Using existing builder: $BuilderName" -ForegroundColor Gray } # Bootstrap builder exec { docker buildx inspect --bootstrap } } Task BuildMultiPlatform -depends CreateBuilder { Write-Host "Building multi-platform image..." -ForegroundColor Green $fullImage = "${Registry}/${Username}/${ImageName}:${ImageTag}" exec { docker buildx build ` --platform $Platforms ` --tag $fullImage ` --push ` . } Write-Host "Multi-platform image built and pushed: $fullImage" -ForegroundColor Green } Task RemoveBuilder { Write-Host "Removing buildx builder..." -ForegroundColor Green exec { docker buildx rm $BuilderName } -errorMessage "Builder not found" } ``` ## Best Practices[​](#best-practices "Direct link to Best Practices") ### 1. Use .dockerignore[​](#1-use-dockerignore "Direct link to 1. Use .dockerignore") Create a `.dockerignore` file to exclude unnecessary files: ``` node_modules npm-debug.log dist build .git .gitignore .env .env.local *.md coverage .vscode .idea ``` ### 2. Optimize Layer Caching[​](#2-optimize-layer-caching "Direct link to 2. Optimize Layer Caching") ``` # Good: Copy dependency files first (cached unless they change) COPY package*.json ./ RUN npm ci # Then copy source code (changes frequently) COPY . . ``` ### 3. Use Specific Base Image Tags[​](#3-use-specific-base-image-tags "Direct link to 3. Use Specific Base Image Tags") ``` # Bad: Latest can change unexpectedly FROM node:latest # Good: Pin specific version FROM node:18.17.1-alpine # Better: Use digest for immutability FROM node:18.17.1-alpine@sha256:abc123... ``` ### 4. Run as Non-Root User[​](#4-run-as-non-root-user "Direct link to 4. Run as Non-Root User") ``` # Create and use non-root user RUN addgroup -g 1001 -S nodejs && \ adduser -S nodejs -u 1001 USER nodejs ``` ### 5. Health Checks[​](#5-health-checks "Direct link to 5. Health Checks") ``` HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ CMD wget --no-verbose --tries=1 --spider http://localhost:8080/health || exit 1 ``` ### 6. Small Image Sizes[​](#6-small-image-sizes "Direct link to 6. Small Image Sizes") ``` Task AnalyzeSize -depends Build { Write-Host "Analyzing image sizes..." -ForegroundColor Green $images = docker images --format "{{.Repository}}:{{.Tag}}\t{{.Size}}" | Where-Object { $_ -like "*${ImageName}*" } foreach ($image in $images) { Write-Host " $image" -ForegroundColor Gray } # Use dive tool for detailed analysis if (Get-Command dive -ErrorAction SilentlyContinue) { exec { dive $ImageFullName } } else { Write-Warning "Install 'dive' tool for detailed layer analysis: https://github.com/wagoodman/dive" } } ``` ## CI/CD Integration[​](#cicd-integration "Direct link to CI/CD Integration") ### GitHub Actions[​](#github-actions "Direct link to GitHub Actions") ``` name: Docker Build on: [push, pull_request] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Login to Docker Hub uses: docker/login-action@v3 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_TOKEN }} - name: Install psake shell: pwsh run: Install-Module -Name psake -Scope CurrentUser -Force - name: Build and push with psake shell: pwsh run: | Invoke-psake -buildFile .\psakefile.ps1 -taskList Push env: DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} DOCKER_TOKEN: ${{ secrets.DOCKER_TOKEN }} ``` ## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") ### Docker Daemon Not Running[​](#docker-daemon-not-running "Direct link to Docker Daemon Not Running") **Problem:** `Cannot connect to the Docker daemon` **Solution:** ``` Task VerifyDocker { try { exec { docker info } | Out-Null Write-Host "Docker daemon is running" -ForegroundColor Green } catch { throw "Docker daemon is not running. Start Docker Desktop or Docker service." } } ``` ### Build Cache Issues[​](#build-cache-issues "Direct link to Build Cache Issues") **Problem:** Changes not reflected in build **Solution:** Force rebuild without cache: ``` Task RebuildNoCache { exec { docker build --no-cache -t $ImageFullName . } } ``` ### Permission Denied in Container[​](#permission-denied-in-container "Direct link to Permission Denied in Container") **Problem:** EACCES or permission denied errors **Solution:** Fix file ownership: ``` # Change ownership to app user COPY --chown=nodejs:nodejs . . # Or use chmod RUN chmod -R 755 /app ``` ### Large Image Sizes[​](#large-image-sizes "Direct link to Large Image Sizes") **Problem:** Images are too large **Solution:** Use Alpine base images and multi-stage builds: ``` # Use Alpine variants FROM node:18-alpine AS base # Use multi-stage builds FROM build AS production COPY --from=build /app/dist ./dist # Remove unnecessary files RUN rm -rf /tmp/* /var/cache/apk/* ``` ### Port Already in Use[​](#port-already-in-use "Direct link to Port Already in Use") **Problem:** Container fails to start due to port conflict **Solution:** ``` Task CheckPort { param([int]$Port = 8080) $listener = Get-NetTCPConnection -LocalPort $Port -ErrorAction SilentlyContinue if ($listener) { Write-Warning "Port $Port is already in use by process $($listener.OwningProcess)" throw "Port conflict on $Port" } } Task Run -depends CheckPort, Build { exec { docker run -p "${HostPort}:${ContainerPort}" $ImageFullName } } ``` ### Registry Authentication Failures[​](#registry-authentication-failures "Direct link to Registry Authentication Failures") **Problem:** Push fails with authentication error **Solution:** Use token authentication: ``` Task SecureLogin { # Use token from environment variable if ([string]::IsNullOrEmpty($env:DOCKER_TOKEN)) { throw "DOCKER_TOKEN environment variable is required" } # Use stdin to avoid exposing token in command $env:DOCKER_TOKEN | docker login --username $env:DOCKER_USERNAME --password-stdin } ``` ## See Also[​](#see-also "Direct link to See Also") * [Installing psake](/docs/tutorial-basics/installing.md) - Installation guide * [GitHub Actions Integration](/docs/ci-examples/github-actions.md) - CI/CD automation with Docker * [Node.js Builds](/docs/build-types/nodejs.md) - Node.js build examples * [.NET Solution Builds](/docs/build-types/dot-net-solution.md) - .NET build examples * [Error Handling](/docs/tutorial-advanced/logging-errors.md) - Build error management --- # .Net Solution Here's an example of a basic script you can write to build a Visual Studio.Net solution: ``` Task Default -depends Build Task Build { Exec { msbuild "helloWorld.sln" } } ``` Builds tend to be more complex than what's shown in the example above. Most builds will need to know what the current directory is relative to where the build script was executed or where to look for code, where to deploy build artifacts or svn settings, etc... If you haven't guessed already, I'm referring to build properties. Here's a script that uses properties: ``` #This build assumes the following directory structure # # \Build - This is where the project build code lives # \BuildArtifacts - This folder is created if it is missing and contains output of the build # \Code - This folder contains the source code or solutions you want to build # Properties { $build_dir = Split-Path $psake.build_script_file $build_artifacts_dir = "$build_dir\..\BuildArtifacts\" $code_dir = "$build_dir\..\Code" } FormatTaskName (("-"*25) + "[{0}]" + ("-"*25)) Task Default -Depends BuildHelloWorld Task BuildHelloWorld -Depends Clean, Build Task Build -Depends Clean { Write-Host "Building helloWorld.sln" -ForegroundColor Green Exec { msbuild "$code_dir\helloWorld\helloWorld.sln" /t:Build /p:Configuration=Release /v:quiet /p:OutDir=$build_artifacts_dir } } Task Clean { Write-Host "Creating BuildArtifacts directory" -ForegroundColor Green if (Test-Path $build_artifacts_dir) { rd $build_artifacts_dir -rec -force | out-null } mkdir $build_artifacts_dir | out-null Write-Host "Cleaning helloWorld.sln" -ForegroundColor Green Exec { msbuild "$code_dir\helloWorld\helloWorld.sln" /t:Clean /p:Configuration=Release /v:quiet } } ``` Here's a helper script "run-build.ps1" that I use to load the psake module and execute the build: ``` $scriptPath = Split-Path $MyInvocation.InvocationName Import-Module (join-path $scriptPath psake.psm1) invoke-psake -framework '4.0' ``` I run the build in PowerShell by just running the script: ``` PS > .\run-build.ps1 ``` The output from running the above build: ``` -------------------------[Clean]------------------------- Creating BuildArtifacts directory Cleaning helloWorld.sln Microsoft (R) Build Engine Version 4.0.30319.1 [Microsoft .NET Framework, Version 4.0.30319.1] Copyright (C) Microsoft Corporation 2007. All rights reserved. -------------------------[Build]------------------------- Building helloWorld.sln Microsoft (R) Build Engine Version 4.0.30319.1 [Microsoft .NET Framework, Version 4.0.30319.1] Copyright (C) Microsoft Corporation 2007. All rights reserved. Build Succeeded! ---------------------------------------------------------------------- Build Time Report ---------------------------------------------------------------------- Name Duration ---- -------- Clean 00:00:09.4624557 Build 00:00:12.2191711 BuildHelloWorld 00:00:21.6931903 Total: 00:00:21.8308190 ``` --- # Node.js and npm Projects psake can orchestrate Node.js and npm-based builds, providing a consistent PowerShell-based build automation layer across your development workflow. This guide shows you how to build, test, bundle, and publish Node.js projects using psake. ## Quick Start[​](#quick-start "Direct link to Quick Start") Here's a basic psake build script for a Node.js project: ``` Properties { $ProjectRoot = $PSScriptRoot $NodeModules = Join-Path $ProjectRoot 'node_modules' $BuildDir = Join-Path $ProjectRoot 'build' $DistDir = Join-Path $ProjectRoot 'dist' } Task Default -depends Test Task Clean { if (Test-Path $BuildDir) { Remove-Item $BuildDir -Recurse -Force } if (Test-Path $DistDir) { Remove-Item $DistDir -Recurse -Force } } Task Install { exec { npm install } } Task Build -depends Install, Clean { exec { npm run build } } Task Test -depends Build { exec { npm test } } ``` Run the build: ``` Invoke-psake -buildFile .\psakefile.ps1 ``` ## Complete Node.js Build Example[​](#complete-nodejs-build-example "Direct link to Complete Node.js Build Example") Here's a comprehensive psakefile.ps1 for a production Node.js application: ``` Properties { $ProjectRoot = $PSScriptRoot $SrcDir = Join-Path $ProjectRoot 'src' $TestDir = Join-Path $ProjectRoot 'tests' $BuildDir = Join-Path $ProjectRoot 'build' $DistDir = Join-Path $ProjectRoot 'dist' $CoverageDir = Join-Path $ProjectRoot 'coverage' $NodeModules = Join-Path $ProjectRoot 'node_modules' $Environment = 'development' $Version = '1.0.0' $Verbose = $false } FormatTaskName { param($taskName) Write-Host "Executing task: $taskName" -ForegroundColor Cyan } Task Default -depends Test Task Clean { Write-Host "Cleaning build artifacts..." -ForegroundColor Green @($BuildDir, $DistDir, $CoverageDir) | ForEach-Object { if (Test-Path $_) { Remove-Item $_ -Recurse -Force Write-Host " Removed: $_" -ForegroundColor Gray } } New-Item -ItemType Directory -Path $BuildDir -Force | Out-Null New-Item -ItemType Directory -Path $DistDir -Force | Out-Null } Task Install { Write-Host "Installing npm dependencies..." -ForegroundColor Green if (-not (Test-Path $NodeModules)) { exec { npm install } } else { exec { npm ci } } } Task Lint -depends Install { Write-Host "Running ESLint..." -ForegroundColor Green exec { npm run lint } } Task Build -depends Install, Clean { Write-Host "Building application..." -ForegroundColor Green $env:NODE_ENV = $Environment if ($Verbose) { exec { npm run build -- --verbose } } else { exec { npm run build } } Write-Host "Build complete: $BuildDir" -ForegroundColor Green } Task Test -depends Build { Write-Host "Running tests..." -ForegroundColor Green exec { npm test } } Task TestWatch { Write-Host "Running tests in watch mode..." -ForegroundColor Green exec { npm run test:watch } } Task Coverage -depends Install { Write-Host "Running tests with coverage..." -ForegroundColor Green exec { npm run test:coverage } if (Test-Path (Join-Path $CoverageDir 'lcov-report/index.html')) { Write-Host "Coverage report: $CoverageDir/lcov-report/index.html" -ForegroundColor Yellow } } Task Bundle -depends Test { Write-Host "Creating production bundle..." -ForegroundColor Green $env:NODE_ENV = 'production' exec { npm run bundle } Write-Host "Bundle complete: $DistDir" -ForegroundColor Green } Task Package -depends Bundle { Write-Host "Creating package..." -ForegroundColor Green exec { npm pack --pack-destination $DistDir } $packageFile = Get-ChildItem "$DistDir/*.tgz" | Select-Object -First 1 Write-Host "Package created: $($packageFile.Name)" -ForegroundColor Green } Task Verify { Write-Host "Verifying package.json..." -ForegroundColor Green if (-not (Test-Path 'package.json')) { throw "package.json not found" } $packageJson = Get-Content 'package.json' | ConvertFrom-Json if ([string]::IsNullOrEmpty($packageJson.name)) { throw "Package name is required in package.json" } if ([string]::IsNullOrEmpty($packageJson.version)) { throw "Package version is required in package.json" } Write-Host " Package: $($packageJson.name)@$($packageJson.version)" -ForegroundColor Gray } Task Publish -depends Test, Verify, Package { Write-Host "Publishing to npm registry..." -ForegroundColor Green $npmToken = $env:NPM_TOKEN if ([string]::IsNullOrEmpty($npmToken)) { throw "NPM_TOKEN environment variable is required for publishing" } # Configure npm authentication exec { npm config set //registry.npmjs.org/:_authToken $npmToken } try { exec { npm publish --access public } Write-Host "Successfully published to npm registry" -ForegroundColor Green } finally { # Clean up authentication exec { npm config delete //registry.npmjs.org/:_authToken } } } Task Dev { Write-Host "Starting development server..." -ForegroundColor Green exec { npm run dev } } Task Serve -depends Build { Write-Host "Starting production server..." -ForegroundColor Green $env:NODE_ENV = 'production' exec { npm start } } Task Audit { Write-Host "Running security audit..." -ForegroundColor Green exec { npm audit } } Task AuditFix { Write-Host "Fixing security vulnerabilities..." -ForegroundColor Green exec { npm audit fix } } Task Outdated { Write-Host "Checking for outdated packages..." -ForegroundColor Green exec { npm outdated } -errorMessage "Some packages are outdated (this is informational)" } Task UpdateDeps { Write-Host "Updating dependencies..." -ForegroundColor Green exec { npm update } exec { npm outdated } -errorMessage "Dependencies updated" } ``` ## TypeScript Compilation[​](#typescript-compilation "Direct link to TypeScript Compilation") For TypeScript projects, add TypeScript-specific tasks: ``` Properties { $ProjectRoot = $PSScriptRoot $SrcDir = Join-Path $ProjectRoot 'src' $OutDir = Join-Path $ProjectRoot 'dist' $TsConfig = Join-Path $ProjectRoot 'tsconfig.json' } Task TypeCheck -depends Install { Write-Host "Running TypeScript type checking..." -ForegroundColor Green if (-not (Test-Path $TsConfig)) { throw "tsconfig.json not found" } exec { npx tsc --noEmit } Write-Host "Type checking passed" -ForegroundColor Green } Task CompileTS -depends Install, Clean { Write-Host "Compiling TypeScript..." -ForegroundColor Green exec { npx tsc --project $TsConfig } Write-Host "TypeScript compilation complete: $OutDir" -ForegroundColor Green } Task CompileTSWatch { Write-Host "Compiling TypeScript in watch mode..." -ForegroundColor Green exec { npx tsc --watch --project $TsConfig } } Task Build -depends TypeCheck, CompileTS { Write-Host "Build complete" -ForegroundColor Green } ``` Update your `tsconfig.json`: ``` { "compilerOptions": { "target": "ES2020", "module": "commonjs", "outDir": "./dist", "rootDir": "./src", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "declaration": true, "declarationMap": true, "sourceMap": true }, "include": ["src/**/*"], "exclude": ["node_modules", "dist", "tests"] } ``` ## Webpack Bundling[​](#webpack-bundling "Direct link to Webpack Bundling") For projects using Webpack, integrate bundling tasks: ``` Properties { $ProjectRoot = $PSScriptRoot $SrcDir = Join-Path $ProjectRoot 'src' $DistDir = Join-Path $ProjectRoot 'dist' $WebpackConfig = Join-Path $ProjectRoot 'webpack.config.js' $Environment = 'development' } Task WebpackBuild -depends Install { Write-Host "Running Webpack build ($Environment)..." -ForegroundColor Green if (-not (Test-Path $WebpackConfig)) { throw "webpack.config.js not found" } $env:NODE_ENV = $Environment if ($Environment -eq 'production') { exec { npx webpack --config $WebpackConfig --mode production } } else { exec { npx webpack --config $WebpackConfig --mode development } } Write-Host "Webpack bundle complete: $DistDir" -ForegroundColor Green } Task WebpackWatch -depends Install { Write-Host "Running Webpack in watch mode..." -ForegroundColor Green exec { npx webpack --config $WebpackConfig --mode development --watch } } Task WebpackAnalyze -depends Install { Write-Host "Analyzing Webpack bundle..." -ForegroundColor Green $env:ANALYZE = 'true' exec { npx webpack --config $WebpackConfig --mode production } } Task OptimizeBundle -depends WebpackBuild { Write-Host "Optimizing bundle size..." -ForegroundColor Green # Run bundle size analysis exec { npx bundlesize } # Check bundle sizes $jsFiles = Get-ChildItem "$DistDir/*.js" -File foreach ($file in $jsFiles) { $sizeKB = [math]::Round($file.Length / 1KB, 2) Write-Host " $($file.Name): ${sizeKB} KB" -ForegroundColor Gray if ($sizeKB -gt 500) { Write-Warning "Bundle size exceeds 500 KB: $($file.Name)" } } } ``` Example `webpack.config.js`: ``` const path = require('path'); const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer'); module.exports = { entry: './src/index.js', output: { filename: 'bundle.js', path: path.resolve(__dirname, 'dist'), }, module: { rules: [ { test: /\.tsx?$/, use: 'ts-loader', exclude: /node_modules/, }, { test: /\.css$/, use: ['style-loader', 'css-loader'], }, ], }, resolve: { extensions: ['.tsx', '.ts', '.js'], }, plugins: [ ...(process.env.ANALYZE ? [new BundleAnalyzerPlugin()] : []), ], }; ``` ## Testing with Jest[​](#testing-with-jest "Direct link to Testing with Jest") Integrate Jest testing into your psake build: ``` Properties { $ProjectRoot = $PSScriptRoot $TestDir = Join-Path $ProjectRoot 'tests' $CoverageDir = Join-Path $ProjectRoot 'coverage' $CoverageThreshold = 80 } Task Test -depends Install { Write-Host "Running Jest tests..." -ForegroundColor Green exec { npx jest --coverage=false } } Task TestWatch -depends Install { Write-Host "Running Jest in watch mode..." -ForegroundColor Green exec { npx jest --watch } } Task TestCoverage -depends Install { Write-Host "Running tests with coverage..." -ForegroundColor Green exec { npx jest --coverage --coverageReporters=text --coverageReporters=html } # Parse coverage summary $coverageSummary = Join-Path $CoverageDir 'coverage-summary.json' if (Test-Path $coverageSummary) { $coverage = Get-Content $coverageSummary | ConvertFrom-Json $totalCoverage = $coverage.total.lines.pct Write-Host "Total line coverage: ${totalCoverage}%" -ForegroundColor Cyan if ($totalCoverage -lt $CoverageThreshold) { throw "Coverage ${totalCoverage}% is below threshold ${CoverageThreshold}%" } } } Task TestCI -depends Install { Write-Host "Running tests for CI..." -ForegroundColor Green # Use CI-friendly options exec { npx jest --ci --coverage --maxWorkers=2 } } ``` Example `jest.config.js`: ``` module.exports = { preset: 'ts-jest', testEnvironment: 'node', roots: ['/src', '/tests'], testMatch: ['**/__tests__/**/*.ts', '**/?(*.)+(spec|test).ts'], collectCoverageFrom: [ 'src/**/*.ts', '!src/**/*.d.ts', '!src/**/*.test.ts', ], coverageThreshold: { global: { branches: 80, functions: 80, lines: 80, statements: 80, }, }, }; ``` ## Linting with ESLint[​](#linting-with-eslint "Direct link to Linting with ESLint") Add linting tasks to maintain code quality: ``` Task Lint -depends Install { Write-Host "Running ESLint..." -ForegroundColor Green exec { npx eslint src --ext .js,.ts,.tsx } } Task LintFix -depends Install { Write-Host "Running ESLint with auto-fix..." -ForegroundColor Green exec { npx eslint src --ext .js,.ts,.tsx --fix } } Task Format -depends Install { Write-Host "Formatting code with Prettier..." -ForegroundColor Green exec { npx prettier --write "src/**/*.{js,ts,tsx,json,css,md}" } } Task FormatCheck -depends Install { Write-Host "Checking code formatting..." -ForegroundColor Green exec { npx prettier --check "src/**/*.{js,ts,tsx,json,css,md}" } } ``` ## Publishing to npm Registry[​](#publishing-to-npm-registry "Direct link to Publishing to npm Registry") Here's a complete workflow for publishing packages to npm: ``` Properties { $ProjectRoot = $PSScriptRoot $DistDir = Join-Path $ProjectRoot 'dist' $Registry = 'https://registry.npmjs.org/' $NpmToken = $env:NPM_TOKEN $DryRun = $false } Task ValidatePackage { Write-Host "Validating package..." -ForegroundColor Green # Check package.json if (-not (Test-Path 'package.json')) { throw "package.json not found" } $pkg = Get-Content 'package.json' | ConvertFrom-Json # Validate required fields $requiredFields = @('name', 'version', 'description', 'main', 'license') foreach ($field in $requiredFields) { if ([string]::IsNullOrEmpty($pkg.$field)) { throw "package.json is missing required field: $field" } } # Check if version already exists $packageName = $pkg.name $version = $pkg.version Write-Host " Package: $packageName" -ForegroundColor Gray Write-Host " Version: $version" -ForegroundColor Gray Write-Host " License: $($pkg.license)" -ForegroundColor Gray try { $existingVersions = npm view $packageName versions --json | ConvertFrom-Json if ($existingVersions -contains $version) { throw "Version $version already exists in registry" } } catch { Write-Host " Package not yet published (this is OK for first release)" -ForegroundColor Yellow } } Task PrepareRelease -depends Test, ValidatePackage { Write-Host "Preparing release..." -ForegroundColor Green # Clean and build exec { Invoke-psake -taskList Clean, Build } # Verify dist directory exists if (-not (Test-Path $DistDir)) { throw "Distribution directory not found: $DistDir" } # Check for required files $requiredFiles = @('package.json', 'README.md') foreach ($file in $requiredFiles) { if (-not (Test-Path $file)) { throw "Required file not found: $file" } } } Task PublishPackage -depends PrepareRelease { Write-Host "Publishing package to npm..." -ForegroundColor Green if ([string]::IsNullOrEmpty($NpmToken)) { throw "NPM_TOKEN environment variable is required" } # Configure authentication $npmrcPath = Join-Path $ProjectRoot '.npmrc' try { # Create temporary .npmrc @" //registry.npmjs.org/:_authToken=$NpmToken registry=$Registry "@ | Set-Content $npmrcPath if ($DryRun) { Write-Host "DRY RUN: Would publish package" -ForegroundColor Yellow exec { npm publish --dry-run --access public } } else { exec { npm publish --access public } $pkg = Get-Content 'package.json' | ConvertFrom-Json Write-Host "Successfully published $($pkg.name)@$($pkg.version)" -ForegroundColor Green } } finally { # Clean up .npmrc if (Test-Path $npmrcPath) { Remove-Item $npmrcPath -Force } } } Task PublishBeta -depends Test, ValidatePackage { Write-Host "Publishing beta version to npm..." -ForegroundColor Green if ([string]::IsNullOrEmpty($NpmToken)) { throw "NPM_TOKEN environment variable is required" } # Configure authentication exec { npm config set //registry.npmjs.org/:_authToken $NpmToken } try { exec { npm publish --tag beta --access public } Write-Host "Successfully published beta version" -ForegroundColor Green } finally { exec { npm config delete //registry.npmjs.org/:_authToken } } } Task UnpublishPackage { Write-Host "WARNING: Unpublishing package..." -ForegroundColor Red $pkg = Get-Content 'package.json' | ConvertFrom-Json $packageName = $pkg.name $version = $pkg.version $confirmation = Read-Host "Are you sure you want to unpublish ${packageName}@${version}? (yes/no)" if ($confirmation -ne 'yes') { Write-Host "Unpublish cancelled" -ForegroundColor Yellow return } if ([string]::IsNullOrEmpty($NpmToken)) { throw "NPM_TOKEN environment variable is required" } exec { npm config set //registry.npmjs.org/:_authToken $NpmToken } try { exec { npm unpublish "${packageName}@${version}" } Write-Host "Successfully unpublished ${packageName}@${version}" -ForegroundColor Green } finally { exec { npm config delete //registry.npmjs.org/:_authToken } } } ``` ## Monorepo Support (npm Workspaces)[​](#monorepo-support-npm-workspaces "Direct link to Monorepo Support (npm Workspaces)") For monorepo projects using npm workspaces: ``` Properties { $ProjectRoot = $PSScriptRoot $Workspaces = @('packages/core', 'packages/cli', 'packages/utils') } Task InstallAll { Write-Host "Installing all workspace dependencies..." -ForegroundColor Green exec { npm install } } Task BuildAll { Write-Host "Building all workspaces..." -ForegroundColor Green foreach ($workspace in $Workspaces) { Write-Host " Building $workspace..." -ForegroundColor Cyan exec { npm run build --workspace=$workspace } } } Task TestAll { Write-Host "Testing all workspaces..." -ForegroundColor Green exec { npm test --workspaces } } Task BuildWorkspace { param([string]$Name) if ([string]::IsNullOrEmpty($Name)) { throw "Workspace name is required. Usage: Invoke-psake BuildWorkspace -parameters @{Name='packages/core'}" } Write-Host "Building workspace: $Name" -ForegroundColor Green exec { npm run build --workspace=$Name } } Task PublishWorkspace { param([string]$Name) if ([string]::IsNullOrEmpty($Name)) { throw "Workspace name is required" } Write-Host "Publishing workspace: $Name" -ForegroundColor Green exec { npm publish --workspace=$Name --access public } } ``` ## Docker Integration[​](#docker-integration "Direct link to Docker Integration") Combine psake with Docker for containerized Node.js builds: ``` Properties { $ImageName = 'myapp' $ImageTag = 'latest' $DockerRegistry = 'docker.io' } Task DockerBuild -depends Test { Write-Host "Building Docker image..." -ForegroundColor Green $fullImageName = "${DockerRegistry}/${ImageName}:${ImageTag}" exec { docker build -t $fullImageName . } Write-Host "Docker image built: $fullImageName" -ForegroundColor Green } Task DockerRun { Write-Host "Running Docker container..." -ForegroundColor Green $fullImageName = "${DockerRegistry}/${ImageName}:${ImageTag}" exec { docker run -p 3000:3000 $fullImageName } } Task DockerPush -depends DockerBuild { Write-Host "Pushing Docker image to registry..." -ForegroundColor Green $fullImageName = "${DockerRegistry}/${ImageName}:${ImageTag}" exec { docker push $fullImageName } } ``` Example `Dockerfile` for Node.js: ``` FROM node:18-alpine AS builder WORKDIR /app COPY package*.json ./ RUN npm ci --only=production COPY . . RUN npm run build FROM node:18-alpine WORKDIR /app COPY --from=builder /app/dist ./dist COPY --from=builder /app/node_modules ./node_modules COPY package*.json ./ EXPOSE 3000 CMD ["node", "dist/index.js"] ``` ## CI/CD Integration[​](#cicd-integration "Direct link to CI/CD Integration") Example integration with CI/CD platforms: ### GitHub Actions[​](#github-actions "Direct link to GitHub Actions") ``` name: Build on: [push, pull_request] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: '18' cache: 'npm' - name: Install psake shell: pwsh run: Install-Module -Name psake -Scope CurrentUser -Force - name: Run psake build shell: pwsh run: | Invoke-psake -buildFile .\psakefile.ps1 -taskList Test - name: Publish to npm if: github.ref == 'refs/heads/main' shell: pwsh run: | Invoke-psake -buildFile .\psakefile.ps1 -taskList PublishPackage env: NPM_TOKEN: ${{ secrets.NPM_TOKEN }} ``` ## Best Practices[​](#best-practices "Direct link to Best Practices") ### 1. Lock Dependencies[​](#1-lock-dependencies "Direct link to 1. Lock Dependencies") Always commit `package-lock.json` and use `npm ci` in CI/CD: ``` Task Install { if ($env:CI -eq 'true') { exec { npm ci } # Clean install from lockfile } else { exec { npm install } # Allow updates locally } } ``` ### 2. Environment-Specific Builds[​](#2-environment-specific-builds "Direct link to 2. Environment-Specific Builds") Use environment variables and different configurations: ``` Properties { $Environment = if ($env:NODE_ENV) { $env:NODE_ENV } else { 'development' } } Task Build { Write-Host "Building for environment: $Environment" -ForegroundColor Green $env:NODE_ENV = $Environment exec { npm run build } } ``` ### 3. Version Bumping[​](#3-version-bumping "Direct link to 3. Version Bumping") Automate version bumping: ``` Task BumpVersion { param([string]$Type = 'patch') Write-Host "Bumping $Type version..." -ForegroundColor Green exec { npm version $Type --no-git-tag-version } $pkg = Get-Content 'package.json' | ConvertFrom-Json Write-Host "New version: $($pkg.version)" -ForegroundColor Green } ``` ### 4. Clean Node Modules[​](#4-clean-node-modules "Direct link to 4. Clean Node Modules") Periodically clean and reinstall: ``` Task CleanInstall { Write-Host "Cleaning node_modules..." -ForegroundColor Green if (Test-Path $NodeModules) { Remove-Item $NodeModules -Recurse -Force } if (Test-Path 'package-lock.json') { Remove-Item 'package-lock.json' -Force } exec { npm install } } ``` ## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") ### npm Command Not Found[​](#npm-command-not-found "Direct link to npm Command Not Found") **Problem:** `exec: npm: The term 'npm' is not recognized` **Solution:** Ensure Node.js is installed and in PATH: ``` Task VerifyNode { try { $nodeVersion = node --version $npmVersion = npm --version Write-Host "Node.js: $nodeVersion" -ForegroundColor Green Write-Host "npm: $npmVersion" -ForegroundColor Green } catch { throw "Node.js and npm are required. Install from https://nodejs.org/" } } Task Build -depends VerifyNode { exec { npm run build } } ``` ### Module Not Found Errors[​](#module-not-found-errors "Direct link to Module Not Found Errors") **Problem:** Build fails with "Cannot find module" errors **Solution:** Ensure dependencies are installed: ``` Task Build -depends Install { if (-not (Test-Path $NodeModules)) { throw "node_modules not found. Run Install task first." } exec { npm run build } } ``` ### Permission Errors on Linux/macOS[​](#permission-errors-on-linuxmacos "Direct link to Permission Errors on Linux/macOS") **Problem:** EACCES errors when installing global packages **Solution:** Use `--prefix` or configure npm properly: ``` Task InstallGlobal { $npmPrefix = if ($IsLinux -or $IsMacOS) { "$HOME/.npm-global" } else { "$env:APPDATA\npm" } exec { npm config set prefix $npmPrefix } exec { npm install -g typescript } } ``` ### Build Fails Due to Memory Issues[​](#build-fails-due-to-memory-issues "Direct link to Build Fails Due to Memory Issues") **Problem:** JavaScript heap out of memory **Solution:** Increase Node.js memory limit: ``` Task Build { $env:NODE_OPTIONS = '--max-old-space-size=4096' exec { npm run build } } ``` ### TypeScript Compilation Errors[​](#typescript-compilation-errors "Direct link to TypeScript Compilation Errors") **Problem:** Type errors break the build **Solution:** Add separate type-checking task: ``` Task TypeCheck { Write-Host "Type checking..." -ForegroundColor Green exec { npx tsc --noEmit } } Task Build -depends TypeCheck { exec { npx tsc } } ``` ## See Also[​](#see-also "Direct link to See Also") * [Installing psake](/docs/tutorial-basics/installing.md) - Installation guide * [GitHub Actions Integration](/docs/ci-examples/github-actions.md) - CI/CD automation * [Docker Builds](/docs/build-types/docker.md) - Docker integration * [.NET Solution Builds](/docs/build-types/dot-net-solution.md) - .NET build examples * [Error Handling](/docs/tutorial-advanced/logging-errors.md) - Build error management --- # Azure Pipelines Azure Pipelines is a cloud-based CI/CD service that's part of Azure DevOps. This guide shows you how to run psake builds in Azure Pipelines, including multi-stage pipelines, secret management with variable groups, and publishing to Azure Artifacts. ## Quick Start[​](#quick-start "Direct link to Quick Start") Here's a basic Azure Pipeline that runs a psake build: ``` # azure-pipelines.yml trigger: branches: include: - main - develop pool: vmImage: 'windows-latest' steps: - pwsh: | Install-Module -Name psake -Scope CurrentUser -Force Invoke-psake -buildFile .\psakefile.ps1 -taskList Build displayName: 'Run psake build' ``` ## Installing psake in Azure Pipelines[​](#installing-psake-in-azure-pipelines "Direct link to Installing psake in Azure Pipelines") ### Option 1: Install from PowerShell Gallery (Recommended)[​](#option-1-install-from-powershell-gallery-recommended "Direct link to Option 1: Install from PowerShell Gallery (Recommended)") ``` - pwsh: | Install-Module -Name psake -Scope CurrentUser -Force displayName: 'Install psake' ``` This works on all agent types (Windows, Linux, macOS). ### Option 2: Install Specific Version[​](#option-2-install-specific-version "Direct link to Option 2: Install Specific Version") ``` - pwsh: | Install-Module -Name psake -RequiredVersion 4.9.0 -Scope CurrentUser -Force displayName: 'Install psake 4.9.0' ``` ### Option 3: Using requirements.psd1 with PSDepend[​](#option-3-using-requirementspsd1-with-psdepend "Direct link to Option 3: Using requirements.psd1 with PSDepend") If your project uses a `requirements.psd1` file: ``` - pwsh: | Install-Module -Name PSDepend -Scope CurrentUser -Force Invoke-PSDepend -Path ./requirements.psd1 -Install -Force displayName: 'Install dependencies with PSDepend' ``` Your `requirements.psd1`: ``` @{ psake = @{ Version = '4.9.0' } Pester = @{ Version = '5.5.0' } } ``` ### Option 4: Cache PowerShell Modules[​](#option-4-cache-powershell-modules "Direct link to Option 4: Cache PowerShell Modules") Speed up builds by caching the PowerShell modules directory: ``` - task: Cache@2 inputs: key: 'psmodules | "$(Agent.OS)" | requirements.psd1' restoreKeys: | psmodules | "$(Agent.OS)" psmodules path: $(Pipeline.Workspace)/.psmodules displayName: 'Cache PowerShell modules' - pwsh: | $modulePath = "$(Pipeline.Workspace)/.psmodules" if (-not (Test-Path $modulePath)) { New-Item -ItemType Directory -Path $modulePath -Force | Out-Null } if ($env:PSModulePath -notlike "*$modulePath*") { $env:PSModulePath = "$modulePath$([System.IO.Path]::PathSeparator)$env:PSModulePath" } if (-not (Get-Module -ListAvailable -Name psake)) { Save-Module -Name psake -Path $modulePath -Force } Import-Module psake displayName: 'Setup psake with caching' ``` ## Complete Pipeline Example[​](#complete-pipeline-example "Direct link to Complete Pipeline Example") Here's a comprehensive pipeline demonstrating psake integration: ``` # azure-pipelines.yml trigger: branches: include: - main - develop tags: include: - v* pr: branches: include: - main variables: buildConfiguration: 'Release' pool: vmImage: 'windows-latest' stages: - stage: Build displayName: 'Build and Test' jobs: - job: BuildJob displayName: 'Build with psake' steps: - checkout: self fetchDepth: 0 # Full history for versioning displayName: 'Checkout source code' - task: Cache@2 inputs: key: 'psmodules | "$(Agent.OS)" | requirements.psd1' path: $(Pipeline.Workspace)/.psmodules displayName: 'Cache PowerShell modules' - pwsh: | 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 } displayName: 'Install psake and dependencies' - pwsh: | Invoke-psake -buildFile .\psakefile.ps1 ` -taskList Build, Test ` -parameters @{ Configuration = "$(buildConfiguration)" BuildNumber = "$(Build.BuildNumber)" BranchName = "$(Build.SourceBranchName)" } displayName: 'Run psake build and test' env: NUGET_API_KEY: $(NuGetApiKey) # From variable group - task: PublishTestResults@2 condition: succeededOrFailed() inputs: testResultsFormat: 'NUnit' testResultsFiles: '**/TestResults/*.xml' mergeTestResults: true testRunTitle: 'Unit Tests' displayName: 'Publish test results' - task: PublishCodeCoverageResults@2 condition: succeededOrFailed() inputs: codeCoverageTool: 'Cobertura' summaryFileLocation: '$(System.DefaultWorkingDirectory)/**/coverage.xml' displayName: 'Publish code coverage' - publish: $(System.DefaultWorkingDirectory)/build artifact: BuildOutput displayName: 'Publish build artifacts' - stage: Deploy displayName: 'Deploy to Production' dependsOn: Build condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main')) jobs: - deployment: DeployJob displayName: 'Deploy with psake' environment: 'production' strategy: runOnce: deploy: steps: - download: current artifact: BuildOutput displayName: 'Download build artifacts' - pwsh: | Install-Module -Name psake -Scope CurrentUser -Force displayName: 'Install psake' - pwsh: | Invoke-psake -buildFile .\psakefile.ps1 ` -taskList Deploy ` -parameters @{ Environment = 'Production' DeploymentPath = "$(Pipeline.Workspace)/BuildOutput" } displayName: 'Run psake deployment' env: AZURE_CONNECTION_STRING: $(AzureConnectionString) ``` ## Multi-Stage Pipelines[​](#multi-stage-pipelines "Direct link to Multi-Stage Pipelines") Azure Pipelines supports multi-stage pipelines for complex workflows: ``` stages: - stage: Build jobs: - job: CompileAndTest steps: - pwsh: | Invoke-psake -taskList Build, Test displayName: 'Build and Test' - stage: QA dependsOn: Build condition: succeeded() jobs: - job: DeployToQA steps: - pwsh: | Invoke-psake -taskList Deploy -parameters @{ Environment = 'QA' } displayName: 'Deploy to QA' - stage: Production dependsOn: QA condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main')) jobs: - deployment: DeployToProduction environment: 'production' strategy: runOnce: deploy: steps: - pwsh: | Invoke-psake -taskList Deploy -parameters @{ Environment = 'Production' } displayName: 'Deploy to Production' ``` ## Cross-Platform Matrix Builds[​](#cross-platform-matrix-builds "Direct link to Cross-Platform Matrix Builds") Run psake builds across multiple operating systems and PowerShell versions: ``` trigger: - main strategy: matrix: Windows_PS7: imageName: 'windows-latest' psTask: 'pwsh' Linux_PS7: imageName: 'ubuntu-latest' psTask: 'pwsh' macOS_PS7: imageName: 'macOS-latest' psTask: 'pwsh' Windows_PS51: imageName: 'windows-2019' psTask: 'powershell' pool: vmImage: $(imageName) steps: - task: PowerShell@2 inputs: targetType: 'inline' pwsh: ${{ eq(variables.psTask, 'pwsh') }} script: | Write-Host "OS: $env:AGENT_OS" Write-Host "PowerShell Version: $($PSVersionTable.PSVersion)" displayName: 'Display environment info' - task: PowerShell@2 inputs: targetType: 'inline' pwsh: ${{ eq(variables.psTask, 'pwsh') }} script: | Install-Module -Name psake -Scope CurrentUser -Force displayName: 'Install psake' - task: PowerShell@2 inputs: targetType: 'inline' pwsh: ${{ eq(variables.psTask, 'pwsh') }} script: | Invoke-psake -buildFile .\psakefile.ps1 -taskList Build, Test displayName: 'Run psake build' - publish: '$(System.DefaultWorkingDirectory)/build' artifact: 'build-$(imageName)' displayName: 'Upload artifacts' ``` **Note:** The `pwsh` parameter in PowerShell\@2 task controls which PowerShell version runs: * `pwsh: true` = PowerShell 7+ (cross-platform) * `pwsh: false` = Windows PowerShell 5.1 (Windows only) ## Variable Groups and Secrets[​](#variable-groups-and-secrets "Direct link to Variable Groups and Secrets") Azure Pipelines provides variable groups for managing secrets and configuration. ### Creating Variable Groups[​](#creating-variable-groups "Direct link to Creating Variable Groups") 1. Go to **Pipelines** → **Library** in Azure DevOps 2. Click **+ Variable group** 3. Name your group (e.g., `BuildSecrets`) 4. Add variables: * `NuGetApiKey` (click lock icon to mark as secret) * `AzureConnectionString` (secret) * `Environment` (plain text) 5. Save the variable group ### Using Variable Groups in Pipelines[​](#using-variable-groups-in-pipelines "Direct link to Using Variable Groups in Pipelines") ``` variables: - group: BuildSecrets # Reference the variable group - name: buildConfiguration value: 'Release' steps: - pwsh: | Invoke-psake -buildFile .\psakefile.ps1 ` -taskList Deploy ` -parameters @{ NuGetApiKey = $env:NUGET_API_KEY Environment = $env:ENVIRONMENT } displayName: 'Deploy with secrets' env: NUGET_API_KEY: $(NuGetApiKey) ENVIRONMENT: $(Environment) AZURE_CONNECTION_STRING: $(AzureConnectionString) ``` ### Using Azure Key Vault[​](#using-azure-key-vault "Direct link to Using Azure Key Vault") For enhanced security, integrate with Azure Key Vault: ``` variables: - group: BuildSecrets - name: KeyVaultName value: 'my-keyvault' steps: - task: AzureKeyVault@2 inputs: azureSubscription: 'Azure Subscription Connection' KeyVaultName: '$(KeyVaultName)' SecretsFilter: '*' RunAsPreJob: true displayName: 'Fetch secrets from Key Vault' - pwsh: | # Secrets are now available as environment variables Invoke-psake -taskList Deploy displayName: 'Deploy with Key Vault secrets' env: NUGET_API_KEY: $(NuGetApiKey) # From Key Vault ``` ### Security Best Practices[​](#security-best-practices "Direct link to Security Best Practices") * **Mark secrets as secret** in variable groups (use the lock icon) * **Use Azure Key Vault** for production secrets * **Limit access** with variable group permissions * **Use service connections** for Azure/AWS credentials instead of manual secrets * **Enable Azure DevOps auditing** to track secret access ## Publishing to Azure Artifacts[​](#publishing-to-azure-artifacts "Direct link to Publishing to Azure Artifacts") ### Publishing NuGet Packages[​](#publishing-nuget-packages "Direct link to Publishing NuGet Packages") ``` steps: - pwsh: | Invoke-psake -taskList Build, Pack displayName: 'Build and pack NuGet packages' - task: NuGetCommand@2 inputs: command: 'push' packagesToPush: '$(Build.SourcesDirectory)/build/*.nupkg' nuGetFeedType: 'internal' publishVstsFeed: 'MyProject/MyFeed' displayName: 'Publish to Azure Artifacts feed' ``` ### Publishing PowerShell Modules[​](#publishing-powershell-modules "Direct link to Publishing PowerShell Modules") ``` steps: - pwsh: | Invoke-psake -taskList Build, Test displayName: 'Build PowerShell module' - task: PowerShell@2 inputs: targetType: 'inline' script: | $apiKey = $env:ARTIFACTS_PAT Register-PSRepository -Name AzureArtifacts ` -SourceLocation "https://pkgs.dev.azure.com/myorg/_packaging/myfeed/nuget/v2" ` -PublishLocation "https://pkgs.dev.azure.com/myorg/_packaging/myfeed/nuget/v2" ` -InstallationPolicy Trusted Publish-Module -Path ./build/MyModule -Repository AzureArtifacts -NuGetApiKey $apiKey displayName: 'Publish module to Azure Artifacts' env: ARTIFACTS_PAT: $(System.AccessToken) ``` ### Publishing Universal Packages[​](#publishing-universal-packages "Direct link to Publishing Universal Packages") ``` steps: - pwsh: | Invoke-psake -taskList Build displayName: 'Build application' - task: UniversalPackages@0 inputs: command: 'publish' publishDirectory: '$(Build.SourcesDirectory)/build' feedsToUsePublish: 'internal' vstsFeedPublish: 'MyProject/MyFeed' vstsFeedPackagePublish: 'myapp' versionOption: 'patch' displayName: 'Publish Universal Package' ``` ## Example psakefile.ps1 for Azure Pipelines[​](#example-psakefileps1-for-azure-pipelines "Direct link to Example psakefile.ps1 for Azure Pipelines") ``` Properties { $Configuration = 'Debug' $BuildNumber = '0' $BranchName = 'unknown' $Version = "1.0.$BuildNumber" $SrcDir = Join-Path $PSScriptRoot 'src' $TestDir = Join-Path $PSScriptRoot 'tests' $BuildDir = Join-Path $PSScriptRoot 'build' $Environment = 'Development' } Task Default -depends Build, Test Task Clean { if (Test-Path $BuildDir) { Remove-Item $BuildDir -Recurse -Force } New-Item -ItemType Directory -Path $BuildDir | Out-Null Write-Host "Build directory cleaned: $BuildDir" -ForegroundColor Green } Task Build -depends Clean { Write-Host "Building version $Version from branch $BranchName" -ForegroundColor Cyan exec { dotnet build $SrcDir ` -c $Configuration ` -o $BuildDir ` /p:Version=$Version ` /p:AssemblyVersion=$Version } } Task Test -depends Build { Write-Host "Running tests..." -ForegroundColor Cyan exec { dotnet test $TestDir ` -c $Configuration ` --no-build ` --logger "trx;LogFileName=TestResults.xml" ` --results-directory "$BuildDir/TestResults" ` /p:CollectCoverage=true ` /p:CoverletOutputFormat=cobertura ` /p:CoverletOutput="$BuildDir/" } } Task Pack -depends Build { Write-Host "Creating NuGet packages..." -ForegroundColor Cyan exec { dotnet pack $SrcDir ` -c $Configuration ` -o $BuildDir ` --no-build ` /p:Version=$Version } } Task Deploy -depends Pack { $apiKey = $env:NUGET_API_KEY if ([string]::IsNullOrEmpty($apiKey)) { throw "NUGET_API_KEY environment variable is required for deployment" } Write-Host "Deploying to $Environment environment..." -ForegroundColor Cyan if ($Environment -eq 'Production') { # Deploy to NuGet.org Get-ChildItem "$BuildDir/*.nupkg" | ForEach-Object { exec { dotnet nuget push $_.FullName ` --api-key $apiKey ` --source https://api.nuget.org/v3/index.json } } } else { Write-Host "Skipping deployment for non-production environment: $Environment" } } Task Publish -depends Deploy { Write-Host "Publishing artifacts to Azure Artifacts..." -ForegroundColor Cyan # Additional publishing logic here } ``` ## Common Troubleshooting[​](#common-troubleshooting "Direct link to Common Troubleshooting") ### psake Module Not Found on Agent[​](#psake-module-not-found-on-agent "Direct link to psake Module Not Found on Agent") **Problem:** `Import-Module: The specified module 'psake' was not loaded` **Solution:** Ensure you're using `pwsh` (PowerShell Core) and install with `-Scope CurrentUser`: ``` - pwsh: | Install-Module -Name psake -Scope CurrentUser -Force -Verbose Get-Module -ListAvailable psake displayName: 'Install psake with verbose output' ``` ### Build Fails but Pipeline Shows Success[​](#build-fails-but-pipeline-shows-success "Direct link to Build Fails but Pipeline Shows Success") **Problem:** psake build fails but Azure Pipeline doesn't detect the failure **Solution:** Use the `exec` function in psake for external commands: ``` Task Build { # This will fail the build on non-zero exit codes exec { dotnet build } } ``` Or explicitly check for errors: ``` - pwsh: | Invoke-psake -buildFile .\psakefile.ps1 if ($LASTEXITCODE -ne 0) { Write-Error "psake build failed" exit $LASTEXITCODE } displayName: 'Run psake with error checking' ``` ### Variable Group Not Available[​](#variable-group-not-available "Direct link to Variable Group Not Available") **Problem:** Variables from variable group are empty **Solution:** Reference the variable group at the pipeline or stage level: ``` # At pipeline level variables: - group: BuildSecrets # Or at stage level stages: - stage: Build variables: - group: BuildSecrets ``` ### Secrets Not Passed to Child Processes[​](#secrets-not-passed-to-child-processes "Direct link to Secrets Not Passed to Child Processes") **Problem:** Environment variables with secrets aren't available in psake **Solution:** Explicitly pass them using the `env` parameter: ``` - pwsh: | Invoke-psake -taskList Deploy env: NUGET_API_KEY: $(NuGetApiKey) # Explicitly map to environment variable displayName: 'Deploy with secrets' ``` ### Agent Pool Capacity Issues[​](#agent-pool-capacity-issues "Direct link to Agent Pool Capacity Issues") **Problem:** Builds queued for a long time waiting for agents **Solution:** Use Microsoft-hosted agents or scale your self-hosted agent pool: ``` pool: vmImage: 'windows-latest' # Microsoft-hosted agent # Or for self-hosted: # name: 'Default' # demands: # - Agent.OS -equals Windows_NT ``` ### Path Issues with Self-Hosted Agents[​](#path-issues-with-self-hosted-agents "Direct link to Path Issues with Self-Hosted Agents") **Problem:** Paths don't resolve correctly on self-hosted agents **Solution:** Use PowerShell's built-in path cmdlets: ``` Properties { # Use cross-platform path construction $BuildDir = Join-Path $PSScriptRoot 'build' $OutputPath = Join-Path $BuildDir 'bin' } ``` ## Advanced Patterns[​](#advanced-patterns "Direct link to Advanced Patterns") ### Parameterized Builds with Runtime Parameters[​](#parameterized-builds-with-runtime-parameters "Direct link to Parameterized Builds with Runtime Parameters") ``` parameters: - name: buildConfiguration displayName: 'Build Configuration' type: string default: 'Release' values: - Debug - Release - name: taskList displayName: 'psake Tasks' type: string default: 'Build, Test' steps: - pwsh: | Invoke-psake -buildFile .\psakefile.ps1 ` -taskList "${{ parameters.taskList }}" ` -parameters @{ Configuration = "${{ parameters.buildConfiguration }}" } displayName: 'Run psake with parameters' ``` ### Template-Based Reusable Pipelines[​](#template-based-reusable-pipelines "Direct link to Template-Based Reusable Pipelines") Create `templates/psake-build.yml`: ``` # templates/psake-build.yml parameters: - name: taskList type: string default: 'Build, Test' - name: configuration type: string default: 'Release' steps: - pwsh: | Install-Module -Name psake -Scope CurrentUser -Force displayName: 'Install psake' - pwsh: | Invoke-psake -buildFile .\psakefile.ps1 ` -taskList "${{ parameters.taskList }}" ` -parameters @{ Configuration = "${{ parameters.configuration }}" } displayName: 'Run psake ${{ parameters.taskList }}' ``` Use the template: ``` # azure-pipelines.yml trigger: - main pool: vmImage: 'windows-latest' jobs: - job: BuildAndTest steps: - template: templates/psake-build.yml parameters: taskList: 'Build, Test' configuration: 'Release' ``` ### Conditional Deployment Based on Branch[​](#conditional-deployment-based-on-branch "Direct link to Conditional Deployment Based on Branch") ``` stages: - stage: Build jobs: - job: BuildJob steps: - pwsh: Invoke-psake -taskList Build displayName: 'Build' - stage: DeployDev condition: eq(variables['Build.SourceBranch'], 'refs/heads/develop') jobs: - job: DeployToDevJob steps: - pwsh: | Invoke-psake -taskList Deploy -parameters @{ Environment = 'Development' } - stage: DeployProd condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main')) jobs: - deployment: DeployToProdJob environment: 'production' strategy: runOnce: deploy: steps: - pwsh: | Invoke-psake -taskList Deploy -parameters @{ Environment = 'Production' } ``` ## See Also[​](#see-also "Direct link to See Also") * [Installing psake](/docs/tutorial-basics/installing.md) - Installation guide * [Running psake](/docs/tutorial-basics/run-psake.md) - Basic usage * [Parameters and Properties](/docs/tutorial-basics/parameters-properties.md) - Parameterizing builds * [GitHub Actions](/docs/ci-examples/github-actions.md) - GitHub Actions integration * [.NET Solution Builds](/docs/build-types/dot-net-solution.md) - .NET build examples --- # Cruise Control Below are some sample tasks that worked for me (fschwiet), though I'm new to cruise control. Note that using a single apostrophe in the properties was necessary, double quotes would not work. Note that to make this work on older versions of psake, I needed to add a line to psake.ps1. Older versions may be missing a call to "exit $LastErrorCode" as the last line. For this example, the source control provider has been configured to put the code at c:\build\ProjectName.git. ``` c:\build\ProjectName.git powershell.exe .\psakefile.ps1 -properties @{ buildDirectory = 'c:\build\ProjectName.msbuild\'; tempPath = 'c:\build\ProjectName.TestDatabases'; sqlConnectionString = 'Database=''MyDB'';Data Source=.\;Integrated Security=True' } 0 Run psake script ``` The build output is hard to read and I don't think any of Cruise Control's built in view templates address that. I created a minimalistic stylesheet so the result is somewhat readable: ```
\
``` --- # 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[​](#quick-start "Direct link to 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 ``` ## PowerShell on GitHub-Hosted Runners[​](#powershell-on-github-hosted-runners "Direct link to PowerShell on GitHub-Hosted Runners") GitHub-hosted runners come with PowerShell pre-installed on all platforms: * **PowerShell 7.4+ (`pwsh`)**: Available on Windows, Linux, and macOS runners * **Windows PowerShell 5.1 (`powershell`)**: Only available on Windows runners **Important:** Always use `shell: pwsh` in your workflows for cross-platform compatibility. Only use `shell: powershell` if you specifically need Windows PowerShell 5.1 features. ## Installing psake in GitHub Actions[​](#installing-psake-in-github-actions "Direct link to Installing psake in GitHub Actions") There are several approaches to installing psake in your workflow: ### Option 1: Install from PowerShell Gallery (Recommended)[​](#option-1-install-from-powershell-gallery-recommended "Direct link to 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[​](#option-2-install-specific-version "Direct link to 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[​](#option-3-using-requirementspsd1-with-psdepend "Direct link to 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[​](#option-4-cache-psake-installation "Direct link to 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[​](#complete-workflow-example "Direct link to 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[​](#cross-platform-matrix-builds "Direct link to 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] runs-on: ${{ matrix.os }} steps: - name: Checkout code uses: actions/checkout@v4 - 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 }} path: ./build/ ``` **Testing with Windows PowerShell 5.1:** If you need to test specifically with Windows PowerShell 5.1, add a separate job: ``` jobs: build-cross-platform: # ... pwsh matrix build from above ... build-windows-powershell: runs-on: windows-latest steps: - name: Checkout code uses: actions/checkout@v4 - name: Install psake shell: powershell # Windows PowerShell 5.1 run: Install-Module -Name psake -Scope CurrentUser -Force - name: Run psake build shell: powershell run: Invoke-psake -buildFile .\psakefile.ps1 -taskList Build, Test ``` ## Secret Management[​](#secret-management "Direct link to Secret Management") GitHub Actions provides a secure way to store and use secrets in your workflows. ### Setting Up Secrets[​](#setting-up-secrets "Direct link to Setting Up Secrets") 1. Go to your repository **Settings** → **Secrets and variables** → **Actions** 2. Click **New repository secret** 3. Add secrets like `NUGET_API_KEY`, `SIGNING_CERT_PASSWORD`, etc. ### Using Secrets in psake[​](#using-secrets-in-psake "Direct link to 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[​](#security-best-practices "Direct link to 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[​](#artifact-publishing "Direct link to Artifact Publishing") ### Publishing Build Artifacts[​](#publishing-build-artifacts "Direct link to 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[​](#publishing-nuget-packages "Direct link to 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[​](#publishing-to-github-releases "Direct link to 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@v2 with: files: | ./build/*.zip ./build/*.nupkg generate_release_notes: true env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} ``` ## Example Repository Structure[​](#example-repository-structure "Direct link to 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[​](#common-troubleshooting "Direct link to Common Troubleshooting") ### psake Module Not Found[​](#psake-module-not-found "Direct link to 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[​](#powershell-execution-policy-issues "Direct link to PowerShell Execution Policy Issues") **Problem:** Scripts fail due to execution policy on Windows runners **Solution:** Use `shell: pwsh` (PowerShell 7) instead of `shell: powershell` (Windows PowerShell 5.1): ``` - name: Run psake shell: pwsh # PowerShell 7 - available on all platforms run: Invoke-psake ``` **Note:** The `pwsh` shell is available on Windows, Linux, and macOS runners. The `powershell` shell is only available on Windows and may have stricter execution policies. ### Build Failures Not Failing the Workflow[​](#build-failures-not-failing-the-workflow "Direct link to 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[​](#path-issues-on-cross-platform-builds "Direct link to 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[​](#module-caching-not-working "Direct link to Module Caching Not Working") **Problem:** Cache doesn't restore modules correctly **Solution:** List both Windows and Unix paths—GitHub Actions will cache what exists: ``` - name: Cache PowerShell modules uses: actions/cache@v4 with: path: | ~/.local/share/powershell/Modules ~/Documents/PowerShell/Modules key: ${{ runner.os }}-psake-${{ hashFiles('**/requirements.psd1') }} ``` ### Secrets Not Available in Forked PRs[​](#secrets-not-available-in-forked-prs "Direct link to 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_target` event (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[​](#advanced-patterns "Direct link to Advanced Patterns") ### Conditional Task Execution Based on Changed Files[​](#conditional-task-execution-based-on-changed-files "Direct link to 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[​](#reusable-workflows "Direct link to 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[​](#see-also "Direct link to See Also") * [Installing psake](/docs/tutorial-basics/installing.md) - Installation guide * [Running psake](/docs/tutorial-basics/run-psake.md) - Basic usage * [Parameters and Properties](/docs/tutorial-basics/parameters-properties.md) - Parameterizing builds * [Team City](/docs/ci-examples/team-city.md) - TeamCity integration * [.NET Solution Builds](/docs/build-types/dot-net-solution.md) - .NET build examples --- # GitLab CI/CD GitLab CI/CD is a powerful, integrated CI/CD platform built into GitLab. This guide shows you how to run psake builds in GitLab CI/CD pipelines, including multi-stage pipelines, secret management with CI/CD variables, and Docker-based builds. ## Quick Start[​](#quick-start "Direct link to Quick Start") Here's a basic GitLab CI/CD pipeline that runs a psake build: ``` # .gitlab-ci.yml image: mcr.microsoft.com/powershell:latest stages: - build build: stage: build script: - pwsh -Command "Install-Module -Name psake -Scope CurrentUser -Force" - pwsh -Command "Invoke-psake -buildFile ./psakefile.ps1 -taskList Build" ``` ## Docker Images for GitLab CI/CD[​](#docker-images-for-gitlab-cicd "Direct link to Docker Images for GitLab CI/CD") GitLab CI/CD runs jobs in Docker containers. Choose an appropriate PowerShell image: ### Option 1: Official PowerShell Image (Recommended)[​](#option-1-official-powershell-image-recommended "Direct link to Option 1: Official PowerShell Image (Recommended)") ``` image: mcr.microsoft.com/powershell:7.4-ubuntu-22.04 before_script: - pwsh -Command "Install-Module -Name psake -Scope CurrentUser -Force" ``` ### Option 2: Windows Server Core (For Windows-Specific Builds)[​](#option-2-windows-server-core-for-windows-specific-builds "Direct link to Option 2: Windows Server Core (For Windows-Specific Builds)") Requires Windows-based GitLab runners: ``` image: mcr.microsoft.com/powershell:nanoserver-ltsc2022 variables: DOCKER_PLATFORM: windows/amd64 before_script: - pwsh -Command "Install-Module -Name psake -Scope CurrentUser -Force" ``` ### Option 3: Custom Docker Image with psake Pre-Installed[​](#option-3-custom-docker-image-with-psake-pre-installed "Direct link to Option 3: Custom Docker Image with psake Pre-Installed") Create a `Dockerfile`: ``` FROM mcr.microsoft.com/powershell:7.4-ubuntu-22.04 # Install psake and common dependencies RUN pwsh -Command "Set-PSRepository -Name PSGallery -InstallationPolicy Trusted; \ Install-Module -Name psake -Scope AllUsers -Force; \ Install-Module -Name Pester -Scope AllUsers -Force; \ Install-Module -Name PSDepend -Scope AllUsers -Force" WORKDIR /builds ``` Build and push to GitLab Container Registry: ``` docker build -t registry.gitlab.com/yourorg/yourproject/psake-build:latest . docker push registry.gitlab.com/yourorg/yourproject/psake-build:latest ``` Use in `.gitlab-ci.yml`: ``` image: registry.gitlab.com/yourorg/yourproject/psake-build:latest build: script: - pwsh -Command "Invoke-psake -taskList Build" ``` ## Installing psake in GitLab CI/CD[​](#installing-psake-in-gitlab-cicd "Direct link to Installing psake in GitLab CI/CD") ### Option 1: Install from PowerShell Gallery[​](#option-1-install-from-powershell-gallery "Direct link to Option 1: Install from PowerShell Gallery") ``` before_script: - pwsh -Command "Install-Module -Name psake -Scope CurrentUser -Force" build: script: - pwsh -Command "Invoke-psake -buildFile ./psakefile.ps1 -taskList Build" ``` ### Option 2: Install Specific Version[​](#option-2-install-specific-version "Direct link to Option 2: Install Specific Version") ``` before_script: - pwsh -Command "Install-Module -Name psake -RequiredVersion 4.9.0 -Scope CurrentUser -Force" ``` ### Option 3: Using requirements.psd1 with PSDepend[​](#option-3-using-requirementspsd1-with-psdepend "Direct link to Option 3: Using requirements.psd1 with PSDepend") ``` before_script: - pwsh -Command "Install-Module -Name PSDepend -Scope CurrentUser -Force" - pwsh -Command "Invoke-PSDepend -Path ./requirements.psd1 -Install -Force" build: script: - pwsh -Command "Invoke-psake -taskList Build" ``` ### Option 4: Cache PowerShell Modules[​](#option-4-cache-powershell-modules "Direct link to Option 4: Cache PowerShell Modules") Speed up pipeline execution by caching modules: ``` variables: PSMODULE_CACHE: "$CI_PROJECT_DIR/.psmodules" cache: key: psake-modules paths: - .psmodules/ before_script: - pwsh -Command " if (-not (Test-Path '$env:PSMODULE_CACHE')) { New-Item -ItemType Directory -Path '$env:PSMODULE_CACHE' -Force | Out-Null } \$env:PSModulePath = '$env:PSMODULE_CACHE' + [System.IO.Path]::PathSeparator + \$env:PSModulePath; if (-not (Get-Module -ListAvailable -Name psake)) { Save-Module -Name psake -Path '$env:PSMODULE_CACHE' -Force }" ``` ## Complete Pipeline Example[​](#complete-pipeline-example "Direct link to Complete Pipeline Example") Here's a comprehensive GitLab CI/CD pipeline: ``` # .gitlab-ci.yml image: mcr.microsoft.com/powershell:7.4-ubuntu-22.04 variables: BUILD_CONFIGURATION: Release PSMODULE_CACHE: "$CI_PROJECT_DIR/.psmodules" stages: - build - test - deploy cache: key: ${CI_COMMIT_REF_SLUG} paths: - .psmodules/ - build/ before_script: - pwsh -Command " \$ErrorActionPreference = 'Stop'; Set-PSRepository -Name PSGallery -InstallationPolicy Trusted; if (-not (Test-Path '$env:PSMODULE_CACHE')) { New-Item -ItemType Directory -Path '$env:PSMODULE_CACHE' -Force | Out-Null } if (-not (Get-Module -ListAvailable -Name psake)) { Save-Module -Name psake -Path '$env:PSMODULE_CACHE' -Force } if (-not (Get-Module -ListAvailable -Name PSDepend)) { Save-Module -Name PSDepend -Path '$env:PSMODULE_CACHE' -Force } Import-Module psake" build: stage: build script: - pwsh -Command " Invoke-psake -buildFile ./psakefile.ps1 \ -taskList Build \ -parameters @{ Configuration = '$env:BUILD_CONFIGURATION' BuildNumber = '$env:CI_PIPELINE_IID' BranchName = '$env:CI_COMMIT_REF_NAME' }" artifacts: name: "build-$CI_COMMIT_SHORT_SHA" paths: - build/ expire_in: 1 week only: - branches test: stage: test dependencies: - build script: - pwsh -Command "Invoke-psake -taskList Test" artifacts: when: always reports: junit: TestResults/*.xml coverage_report: coverage_format: cobertura path: coverage.xml paths: - TestResults/ expire_in: 30 days coverage: '/Total\s+\|\s+(\d+\.?\d*)%/' only: - branches deploy:production: stage: deploy script: - pwsh -Command " Invoke-psake -taskList Deploy \ -parameters @{ Environment = 'Production' NuGetApiKey = \$env:NUGET_API_KEY }" environment: name: production url: https://www.nuget.org/packages/YourPackage dependencies: - build only: - main when: manual ``` ## Multi-Stage Pipelines[​](#multi-stage-pipelines "Direct link to Multi-Stage Pipelines") GitLab CI/CD supports sophisticated multi-stage workflows: ``` stages: - build - test - qa - production build: stage: build script: - pwsh -Command "Invoke-psake -taskList Build" artifacts: paths: - build/ unit-tests: stage: test dependencies: - build script: - pwsh -Command "Invoke-psake -taskList Test" integration-tests: stage: test dependencies: - build script: - pwsh -Command "Invoke-psake -taskList IntegrationTest" deploy:qa: stage: qa script: - pwsh -Command "Invoke-psake -taskList Deploy -parameters @{ Environment = 'QA' }" environment: name: qa url: https://qa.example.com only: - develop deploy:production: stage: production script: - pwsh -Command "Invoke-psake -taskList Deploy -parameters @{ Environment = 'Production' }" environment: name: production url: https://example.com only: - main when: manual ``` ## Parallel Jobs[​](#parallel-jobs "Direct link to Parallel Jobs") Run psake tasks in parallel for faster builds: ``` stages: - build - test build: stage: build script: - pwsh -Command "Invoke-psake -taskList Build" artifacts: paths: - build/ test:windows: stage: test image: mcr.microsoft.com/powershell:nanoserver-ltsc2022 tags: - windows script: - pwsh -Command "Invoke-psake -taskList Test" parallel: matrix: - POWERSHELL_VERSION: ["7.2", "7.4"] test:linux: stage: test image: mcr.microsoft.com/powershell:7.4-ubuntu-22.04 tags: - docker script: - pwsh -Command "Invoke-psake -taskList Test" test:macos: stage: test tags: - macos script: - pwsh -Command "Invoke-psake -taskList Test" ``` ## CI/CD Variables and Secrets[​](#cicd-variables-and-secrets "Direct link to CI/CD Variables and Secrets") GitLab provides CI/CD variables for managing configuration and secrets. ### Setting Up CI/CD Variables[​](#setting-up-cicd-variables "Direct link to Setting Up CI/CD Variables") 1. Go to **Settings** → **CI/CD** → **Variables** 2. Click **Add variable** 3. Set key (e.g., `NUGET_API_KEY`) 4. Set value 5. Configure options: * **Protect variable**: Only available in protected branches * **Mask variable**: Hide value in job logs * **Expand variable**: Allow variable expansion ### Using Variables in Pipeline[​](#using-variables-in-pipeline "Direct link to Using Variables in Pipeline") **Method 1: Environment Variables** ``` deploy: script: - pwsh -Command "Invoke-psake -taskList Deploy" variables: NUGET_API_KEY: $NUGET_API_KEY AZURE_CONNECTION: $AZURE_CONNECTION_STRING ``` In `psakefile.ps1`: ``` Task Deploy { $apiKey = $env:NUGET_API_KEY if ([string]::IsNullOrEmpty($apiKey)) { throw "NUGET_API_KEY environment variable is required" } dotnet nuget push "*.nupkg" --api-key $apiKey } ``` **Method 2: Pass as Parameters** ``` deploy: script: - pwsh -Command " Invoke-psake -taskList Deploy \ -parameters @{ NuGetApiKey = \$env:NUGET_API_KEY Environment = 'Production' }" ``` ### Using GitLab Secrets Management[​](#using-gitlab-secrets-management "Direct link to Using GitLab Secrets Management") For enhanced security, use GitLab's secrets management: ``` deploy: script: - pwsh -Command "Invoke-psake -taskList Deploy" id_tokens: GITLAB_OIDC_TOKEN: aud: https://gitlab.com secrets: VAULT_TOKEN: vault: production/nuget/api_key@secrets file: false ``` ### Security Best Practices[​](#security-best-practices "Direct link to Security Best Practices") * **Mask sensitive variables** to prevent them from appearing in logs * **Protect variables** for production branches only * **Use external secrets managers** (HashiCorp Vault, AWS Secrets Manager) * **Limit variable scope** to specific environments or jobs * **Rotate credentials regularly** and audit access ## Artifacts and Dependencies[​](#artifacts-and-dependencies "Direct link to Artifacts and Dependencies") ### Publishing Build Artifacts[​](#publishing-build-artifacts "Direct link to Publishing Build Artifacts") ``` build: script: - pwsh -Command "Invoke-psake -taskList Build, Pack" artifacts: name: "build-$CI_COMMIT_REF_NAME-$CI_COMMIT_SHORT_SHA" paths: - build/*.dll - build/*.exe - build/*.nupkg exclude: - build/temp/ expire_in: 1 week when: on_success ``` ### Publishing to GitLab Package Registry[​](#publishing-to-gitlab-package-registry "Direct link to Publishing to GitLab Package Registry") **NuGet Packages:** ``` publish:nuget: script: - pwsh -Command " Invoke-psake -taskList Build, Pack; Get-ChildItem ./build/*.nupkg | ForEach-Object { dotnet nuget push \$_.FullName \ --source '${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/nuget/index.json' \ --api-key ${CI_JOB_TOKEN} }" only: - main ``` **PowerShell Modules:** ``` publish:psmodule: script: - pwsh -Command " \$repoParams = @{ Name = 'GitLabPackages' SourceLocation = '${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/nuget/index.json' PublishLocation = '${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/nuget/index.json' InstallationPolicy = 'Trusted' } Register-PSRepository @repoParams; Invoke-psake -taskList Build; Publish-Module -Path ./build/MyModule \ -Repository GitLabPackages \ -NuGetApiKey ${CI_JOB_TOKEN}" ``` **Generic Packages:** ``` publish:generic: script: - pwsh -Command "Invoke-psake -taskList Build" - 'curl --header "JOB-TOKEN: $CI_JOB_TOKEN" --upload-file build/myapp.zip "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/myapp/${CI_COMMIT_TAG}/myapp.zip"' only: - tags ``` ## Example psakefile.ps1 for GitLab CI/CD[​](#example-psakefileps1-for-gitlab-cicd "Direct link to Example psakefile.ps1 for GitLab CI/CD") ``` Properties { $Configuration = 'Debug' $BuildNumber = '0' $BranchName = 'unknown' $Version = "1.0.$BuildNumber" $SrcDir = Join-Path $PSScriptRoot 'src' $TestDir = Join-Path $PSScriptRoot 'tests' $BuildDir = Join-Path $PSScriptRoot 'build' $Environment = 'Development' $NuGetApiKey = $null } Task Default -depends Build, Test Task Clean { if (Test-Path $BuildDir) { Remove-Item $BuildDir -Recurse -Force } New-Item -ItemType Directory -Path $BuildDir | Out-Null Write-Host "✓ Build directory cleaned" -ForegroundColor Green } Task Build -depends Clean { Write-Host "Building $Version from $BranchName" -ForegroundColor Cyan exec { dotnet build $SrcDir ` -c $Configuration ` -o $BuildDir ` /p:Version=$Version } Write-Host "✓ Build completed successfully" -ForegroundColor Green } Task Test -depends Build { Write-Host "Running tests..." -ForegroundColor Cyan $testResultsDir = Join-Path $BuildDir 'TestResults' if (-not (Test-Path $testResultsDir)) { New-Item -ItemType Directory -Path $testResultsDir | Out-Null } exec { dotnet test $TestDir ` -c $Configuration ` --no-build ` --logger "junit;LogFilePath=$testResultsDir/results.xml" ` --results-directory $testResultsDir ` /p:CollectCoverage=true ` /p:CoverletOutputFormat=cobertura ` /p:CoverletOutput="$PSScriptRoot/coverage.xml" } Write-Host "✓ Tests completed successfully" -ForegroundColor Green } Task IntegrationTest -depends Build { Write-Host "Running integration tests..." -ForegroundColor Cyan exec { dotnet test $TestDir ` -c $Configuration ` --filter "Category=Integration" ` --logger "junit" } } Task Pack -depends Build, Test { Write-Host "Creating NuGet packages..." -ForegroundColor Cyan exec { dotnet pack $SrcDir ` -c $Configuration ` -o $BuildDir ` --no-build ` /p:Version=$Version ` /p:PackageVersion=$Version } Write-Host "✓ Packages created successfully" -ForegroundColor Green } Task Deploy -depends Pack { if ([string]::IsNullOrEmpty($NuGetApiKey)) { throw "NuGetApiKey parameter is required for deployment" } Write-Host "Deploying to $Environment..." -ForegroundColor Cyan $nugetSource = if ($Environment -eq 'Production') { 'https://api.nuget.org/v3/index.json' } else { $env:CI_API_V4_URL + '/projects/' + $env:CI_PROJECT_ID + '/packages/nuget/index.json' } Get-ChildItem "$BuildDir/*.nupkg" | ForEach-Object { Write-Host "Publishing $($_.Name)..." -ForegroundColor Yellow exec { dotnet nuget push $_.FullName ` --api-key $NuGetApiKey ` --source $nugetSource } } Write-Host "✓ Deployment completed successfully" -ForegroundColor Green } Task Publish -depends Deploy { Write-Host "Creating GitLab release..." -ForegroundColor Cyan # Additional publishing logic } ``` ## Common Troubleshooting[​](#common-troubleshooting "Direct link to Common Troubleshooting") ### PowerShell Module Not Found[​](#powershell-module-not-found "Direct link to PowerShell Module Not Found") **Problem:** `Import-Module: The specified module 'psake' was not loaded` **Solution:** Ensure psake is installed in the `before_script`: ``` before_script: - pwsh -Command "Install-Module -Name psake -Scope CurrentUser -Force -Verbose" - pwsh -Command "Get-Module -ListAvailable psake" ``` ### Docker Image Pull Authentication[​](#docker-image-pull-authentication "Direct link to Docker Image Pull Authentication") **Problem:** Cannot pull private Docker images **Solution:** Configure Docker authentication in GitLab CI/CD variables: ``` variables: DOCKER_AUTH_CONFIG: $DOCKER_AUTH_CONFIG # JSON with registry credentials image: registry.gitlab.com/yourorg/private-image:latest ``` ### Build Failures Not Failing Pipeline[​](#build-failures-not-failing-pipeline "Direct link to Build Failures Not Failing Pipeline") **Problem:** psake build fails but pipeline shows success **Solution:** Use `$ErrorActionPreference = 'Stop'` and `exec`: ``` # In psakefile.ps1 Task Build { $ErrorActionPreference = 'Stop' exec { dotnet build } # Will fail on non-zero exit code } ``` Or check exit codes explicitly: ``` script: - pwsh -Command "\$ErrorActionPreference = 'Stop'; Invoke-psake -taskList Build" ``` ### Artifacts Not Found[​](#artifacts-not-found "Direct link to Artifacts Not Found") **Problem:** Dependent jobs can't find artifacts from previous jobs **Solution:** Specify dependencies explicitly: ``` test: dependencies: - build # Explicitly depend on build job script: - pwsh -Command "Invoke-psake -taskList Test" ``` ### Windows Runner Issues[​](#windows-runner-issues "Direct link to Windows Runner Issues") **Problem:** Windows-specific builds failing on Linux runners **Solution:** Use tags to specify Windows runners: ``` windows-build: tags: - windows - powershell image: mcr.microsoft.com/powershell:nanoserver-ltsc2022 script: - pwsh -Command "Invoke-psake -taskList Build" ``` ### Cache Not Working[​](#cache-not-working "Direct link to Cache Not Working") **Problem:** Module cache not restoring correctly **Solution:** Use unique cache keys and verify paths: ``` cache: key: ${CI_COMMIT_REF_SLUG}-psmodules paths: - .psmodules/ policy: pull-push # Default: download and upload ``` ### CI/CD Variables Empty[​](#cicd-variables-empty "Direct link to CI/CD Variables Empty") **Problem:** Protected variables not available in pipeline **Solution:** Ensure branch is protected, or uncheck "Protect variable": 1. **Settings** → **Repository** → **Protected branches** 2. Or in **CI/CD** → **Variables**, uncheck **Protect variable** ## Advanced Patterns[​](#advanced-patterns "Direct link to Advanced Patterns") ### Dynamic Child Pipelines[​](#dynamic-child-pipelines "Direct link to Dynamic Child Pipelines") Generate pipelines dynamically based on changes: ``` generate-pipeline: stage: build script: - pwsh -File ./scripts/generate-pipeline.ps1 > pipeline.yml artifacts: paths: - pipeline.yml trigger-pipeline: stage: build trigger: include: - artifact: pipeline.yml job: generate-pipeline strategy: depend ``` ### Multi-Project Pipelines[​](#multi-project-pipelines "Direct link to Multi-Project Pipelines") Trigger pipelines in other projects: ``` deploy:downstream: stage: deploy trigger: project: yourorg/downstream-project branch: main strategy: depend variables: UPSTREAM_VERSION: $CI_COMMIT_TAG ``` ### Scheduled Pipelines[​](#scheduled-pipelines "Direct link to Scheduled Pipelines") Run psake tasks on a schedule: 1. **CI/CD** → **Schedules** → **New schedule** 2. Set cron schedule (e.g., `0 2 * * *` for daily at 2 AM) ``` nightly-build: script: - pwsh -Command "Invoke-psake -taskList Build, Test, Deploy" only: - schedules ``` ## See Also[​](#see-also "Direct link to See Also") * [Installing psake](/docs/tutorial-basics/installing.md) - Installation guide * [Running psake](/docs/tutorial-basics/run-psake.md) - Basic usage * [Parameters and Properties](/docs/tutorial-basics/parameters-properties.md) - Parameterizing builds * [GitHub Actions](/docs/ci-examples/github-actions.md) - GitHub Actions integration * [Azure Pipelines](/docs/ci-examples/azure-pipelines.md) - Azure DevOps integration * [.NET Solution Builds](/docs/build-types/dot-net-solution.md) - .NET build examples --- # Hudson [Hudson](http://hudson-ci.org/) is a popular continuous integration server, it can be used to run pretty much any kind of build. To have Hudson run your psake build script you just need to create a Hudson job that will launch PowerShell with a parameter that is a PowerShell helper script that will in turn load the psake.psm1 module and then call the Invoke-psake function. You can use the psake.ps1 helper script but you will need to modify it so that the $psake.use\_exit\_on\_error is set to $true. Here's the modified psake.ps1 file ``` # Helper script for those who want to run psake without importing the module. # Example: # .\psake.ps1 "psakefile.ps1" "BuildHelloWord" "4.0" # Must match parameter definitions for psake.psm1/invoke-psake # otherwise named parameter binding fails param( [Parameter(Position=0,Mandatory=0)] [string]$buildFile = 'psakefile.ps1', [Parameter(Position=1,Mandatory=0)] [string[]]$taskList = @(), [Parameter(Position=2,Mandatory=0)] [string]$framework = '3.5', [Parameter(Position=3,Mandatory=0)] [switch]$docs = $false, [Parameter(Position=4,Mandatory=0)] [System.Collections.Hashtable]$parameters = @{}, [Parameter(Position=5, Mandatory=0)] [System.Collections.Hashtable]$properties = @{} ) try { $scriptPath = Split-Path $script:MyInvocation.MyCommand.Path import-module (join-path $scriptPath psake.psm1) $psake.use_exit_on_error = $true invoke-psake $buildFile $taskList $framework $docs $parameters $properties } finally { remove-module psake -ea 'SilentlyContinue' } ``` Configure Hudson to call PowerShell passing in the helper script as a parameter: ``` powershell.exe "& 'psake.ps1 psakefile.ps1'" ``` I've written a blog [post](http://matosjorge.spaces.live.com/blog/cns!2E0DA1D30B684DA8!710.entry) that shows how to do this without using a helper script. It uses a Hudson PowerShell plug-in that allows you to enter PowerShell commands directly into the Hudson job configuration. --- # Team City TeamCity 6.5 has a bundled PowerShell runner, which can be used to run psake builds. For older versions of TeamCity, the [PowerShell runner plug-in](http://confluence.jetbrains.net/display/TW/PowerShell) will do the job; you can download the plug-in's binaries [here](http://teamcity.jetbrains.net/repository/download/bt268/50151:id/teamcity-powershell.zip). See [Installing Additional Plugins](http://confluence.jetbrains.net/display/TCD6/Installing+Additional+Plugins) for general help on installing plug-ins for TeamCity. Alternatively you can just run PowerShell.exe from Command Line Runner and then run psake. See example in [psake.cmd](https://github.com/psake/psake/blob/master/psake.cmd). ## Setup of PowerShell runner[​](#setup-of-powershell-runner "Direct link to Setup of PowerShell runner") Add a new build step and select **PowerShell** runner. * Set your run mode (x86 or x64) * Set your working directory (typically the directory where your build scripts are located) * Select *Source code* mode in Script input * Insert into Script Source ``` Import-Module 'PATH_TO_PSAKE_ROOT_FOLDER\psake.psm1' Invoke-psake .\PSAKE_BUILD_FILENAME.ps1 RunTests ``` * For 4.0.0: Make sure you have added **`$psake.use_exit_on_error = $true`** somewhere before the Invoke-psake call (e.g., amend the `psake.ps1` file) * For 4.0.1 unofficial release on NuGet no additional steps are required * For current development version append to `& .\psake.ps1` also `if ($psake.build_success -eq $false) { exit 1 } else { exit 0 }` to notify TeamCity about build failure Now you are ready. ## Parameters[​](#parameters "Direct link to Parameters") If you need parameterize your build script, you can use the predefined [TeamCity parameters and variables](http://confluence.jetbrains.net/display/TCD6/Defining+and+Using+Build+Parameters+in+Build+Configuration). Pass in the parameters as a hash table using the following syntax: ``` & .\psake.ps1 -parameters @{build_number=%build.number%} ``` You can pass multiple parameters by separating them with semicolons: ``` & .\psake.ps1 -parameters @{build_number=%build.number%; personal_build=%build.is.personal%} ``` If you are using PowerShell v3.0 or greater, you can get all of the TeamCity parameters into a hash with the following code in your .ps1 file: ``` $TCParams = ConvertFrom-StringData (Get-Content $env:TEAMCITY_BUILD_PROPERTIES_FILE -Raw); ``` After this code executes, you can access any TeamCity parameter as `$TCParams['parameter.name']` e.g. `$TCParams['build.number']` --- # Contributor Covenant Code of Conduct ## Our Pledge[​](#our-pledge "Direct link to Our Pledge") We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. ## Our Standards[​](#our-standards "Direct link to Our Standards") Examples of behavior that contributes to a positive environment for our community include: * Demonstrating empathy and kindness toward other people * Being respectful of differing opinions, viewpoints, and experiences * Giving and gracefully accepting constructive feedback * Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience * Focusing on what is best not just for us as individuals, but for the overall community Examples of unacceptable behavior include: * The use of sexualized language or imagery, and sexual attention or advances of any kind * Trolling, insulting or derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or email address, without their explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Enforcement Responsibilities[​](#enforcement-responsibilities "Direct link to Enforcement Responsibilities") Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. ## Scope[​](#scope "Direct link to Scope") This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. ## Enforcement[​](#enforcement "Direct link to Enforcement") Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at . All complaints will be reviewed and investigated promptly and fairly. All community leaders are obligated to respect the privacy and security of the reporter of any incident. ## Enforcement Guidelines[​](#enforcement-guidelines "Direct link to Enforcement Guidelines") Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: ### 1. Correction[​](#1-correction "Direct link to 1. Correction") **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. ### 2. Warning[​](#2-warning "Direct link to 2. Warning") **Community Impact**: A violation through a single incident or series of actions. **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. ### 3. Temporary Ban[​](#3-temporary-ban "Direct link to 3. Temporary Ban") **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior. **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. ### 4. Permanent Ban[​](#4-permanent-ban "Direct link to 4. Permanent Ban") **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. **Consequence**: A permanent ban from any sort of public interaction within the project community. ## Attribution[​](#attribution "Direct link to Attribution") This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org), version 2.0, available at . Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity). For answers to common questions about this code of conduct, see the FAQ at . Translations are available at . --- # Assert This page was generated Contributions are welcome in [Psake-repo](https://github.com/psake/psake). ## SYNOPSIS[​](#synopsis "Direct link to SYNOPSIS") Helper function for "Design by Contract" assertion checking. ## SYNTAX[​](#syntax "Direct link to SYNTAX") ### \_\_AllParameterSets[​](#__allparametersets "Direct link to __AllParameterSets") ``` Assert [-ConditionToCheck] [-FailureMessage] [] ``` ## DESCRIPTION[​](#description "Direct link to DESCRIPTION") This is a helper function that makes the code less noisy by eliminating many of the "if" statements that are normally required to verify assumptions in the code. ## EXAMPLES[​](#examples "Direct link to EXAMPLES") ### EXAMPLE 1[​](#example-1 "Direct link to EXAMPLE 1") ``` Assert $false "This always throws an exception" ``` Example of an assertion that will always fail. ### EXAMPLE 2[​](#example-2 "Direct link to EXAMPLE 2") ``` Assert ( ($i % 2) -eq 0 ) "$i is not an even number" ``` This example may throw an exception if $i is not an even number It might be necessary to wrap the condition with parentheses to force PowerShell to evaluate the condition so that a boolean value is calculated and passed into the 'ConditionToCheck' parameter. ### EXAMPLE 3[​](#example-3 "Direct link to EXAMPLE 3") ``` Assert 1 -eq 2 "1 doesn't equal 2" ``` PowerShell will pass 1 into the ConditionToCheck variable and PS will look for a parameter called "eq" and throw an exception with the following message "A parameter cannot be found that matches parameter name 'eq'" The solution is to wrap the condition in () so that PowerShell will evaluate it first. ``` Assert (1 -eq 2) "1 doesn't equal 2" ``` ## PARAMETERS[​](#parameters "Direct link to PARAMETERS") ### -ConditionToCheck[​](#-conditiontocheck "Direct link to -ConditionToCheck") The boolean condition to evaluate ``` Type: System.Object DefaultValue: '' SupportsWildcards: false Aliases: [] ParameterSets: - Name: (All) Position: 0 IsRequired: true ValueFromPipeline: false ValueFromPipelineByPropertyName: false ValueFromRemainingArguments: false DontShow: false AcceptedValues: [] HelpMessage: '' ``` ### -FailureMessage[​](#-failuremessage "Direct link to -FailureMessage") The error message used for the exception if the ConditionToCheck parameter is false ``` Type: System.String DefaultValue: '' SupportsWildcards: false Aliases: [] ParameterSets: - Name: (All) Position: 1 IsRequired: true ValueFromPipeline: false ValueFromPipelineByPropertyName: false ValueFromRemainingArguments: false DontShow: false AcceptedValues: [] HelpMessage: '' ``` ### CommonParameters[​](#commonparameters "Direct link to CommonParameters") This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutBuffer, -OutVariable, -PipelineVariable, -ProgressAction, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about\_CommonParameters](https://go.microsoft.com/fwlink/?LinkID=113216). ## INPUTS[​](#inputs "Direct link to INPUTS") ## OUTPUTS[​](#outputs "Direct link to OUTPUTS") ## NOTES[​](#notes "Direct link to NOTES") ## RELATED LINKS[​](#related-links "Direct link to RELATED LINKS") ## VERSION[​](#version "Direct link to VERSION") *This page was generated using comment-based help in [Psake 5.0.4](https://github.com/psake/psake).* --- # BuildSetup This page was generated Contributions are welcome in [Psake-repo](https://github.com/psake/psake). Since Psake 5.0 This command was introduced in Psake 5.0 and is not available in earlier versions. ## SYNOPSIS[​](#synopsis "Direct link to SYNOPSIS") Adds a scriptblock that will be executed once at the beginning of the build ## SYNTAX[​](#syntax "Direct link to SYNTAX") ### \_\_AllParameterSets[​](#__allparametersets "Direct link to __AllParameterSets") ``` BuildSetup [-Setup] [] ``` ## DESCRIPTION[​](#description "Direct link to DESCRIPTION") Runs once before the first task executes. Use this to set up shared state, logging, or environment validation for the whole build. ## EXAMPLES[​](#examples "Direct link to EXAMPLES") ### EXAMPLE 1[​](#example-1 "Direct link to EXAMPLE 1") ``` Task default -Depends Test Task Test -Depends Compile, Clean {} Task Compile -Depends Clean {} Task Clean {} BuildSetup { "Running 'BuildSetup'" } ``` The script above produces the following output: ``` Running 'BuildSetup' Executing task, Clean... Executing task, Compile... Executing task, Test... Build Succeeded ``` ## PARAMETERS[​](#parameters "Direct link to PARAMETERS") ### -Setup[​](#-setup "Direct link to -Setup") Executed once before any tasks run. ``` Type: System.Management.Automation.ScriptBlock DefaultValue: '' SupportsWildcards: false Aliases: [] ParameterSets: - Name: (All) Position: 0 IsRequired: true ValueFromPipeline: false ValueFromPipelineByPropertyName: false ValueFromRemainingArguments: false DontShow: false AcceptedValues: [] HelpMessage: '' ``` ### CommonParameters[​](#commonparameters "Direct link to CommonParameters") This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutBuffer, -OutVariable, -PipelineVariable, -ProgressAction, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about\_CommonParameters](https://go.microsoft.com/fwlink/?LinkID=113216). ## INPUTS[​](#inputs "Direct link to INPUTS") ## OUTPUTS[​](#outputs "Direct link to OUTPUTS") ## NOTES[​](#notes "Direct link to NOTES") ## RELATED LINKS[​](#related-links "Direct link to RELATED LINKS") ## VERSION[​](#version "Direct link to VERSION") *This page was generated using comment-based help in [Psake 5.0.4](https://github.com/psake/psake).* --- # BuildTearDown This page was generated Contributions are welcome in [Psake-repo](https://github.com/psake/psake). Since Psake 5.0 This command was introduced in Psake 5.0 and is not available in earlier versions. ## SYNOPSIS[​](#synopsis "Direct link to SYNOPSIS") Adds a scriptblock that will be executed once at the end of the build ## SYNTAX[​](#syntax "Direct link to SYNTAX") ### \_\_AllParameterSets[​](#__allparametersets "Direct link to __AllParameterSets") ``` BuildTearDown [-Setup] [] ``` ## DESCRIPTION[​](#description "Direct link to DESCRIPTION") Runs after all tasks complete, even if the build failed. Use this for cleanup, notifications, or post-build reporting. ## EXAMPLES[​](#examples "Direct link to EXAMPLES") ### EXAMPLE 1[​](#example-1 "Direct link to EXAMPLE 1") ``` Task default -Depends Test Task Test -Depends Compile, Clean {} Task Compile -Depends Clean {} Task Clean {} BuildTearDown { "Running 'BuildTearDown'" } ``` The script above produces the following output: ``` Executing task, Clean... Executing task, Compile... Executing task, Test... Running 'BuildTearDown' Build Succeeded ``` ### EXAMPLE 2[​](#example-2 "Direct link to EXAMPLE 2") ``` Task default -Depends Test Task Test -Depends Compile, Clean { throw "forced error" } Task Compile -Depends Clean {} Task Clean {} BuildTearDown { "Running 'BuildTearDown'" } ``` The script above produces the following output: ``` Executing task, Clean... Executing task, Compile... Executing task, Test... Running 'BuildTearDown' forced error At line:x char:x ... ``` ## PARAMETERS[​](#parameters "Direct link to PARAMETERS") ### -Setup[​](#-setup "Direct link to -Setup") Executed after all tasks finish, whether or not the build succeeded. ``` Type: System.Management.Automation.ScriptBlock DefaultValue: '' SupportsWildcards: false Aliases: [] ParameterSets: - Name: (All) Position: 0 IsRequired: true ValueFromPipeline: false ValueFromPipelineByPropertyName: false ValueFromRemainingArguments: false DontShow: false AcceptedValues: [] HelpMessage: '' ``` ### CommonParameters[​](#commonparameters "Direct link to CommonParameters") This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutBuffer, -OutVariable, -PipelineVariable, -ProgressAction, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about\_CommonParameters](https://go.microsoft.com/fwlink/?LinkID=113216). ## INPUTS[​](#inputs "Direct link to INPUTS") ## OUTPUTS[​](#outputs "Direct link to OUTPUTS") ## NOTES[​](#notes "Direct link to NOTES") ## RELATED LINKS[​](#related-links "Direct link to RELATED LINKS") ## VERSION[​](#version "Direct link to VERSION") *This page was generated using comment-based help in [Psake 5.0.4](https://github.com/psake/psake).* --- # Clear-PsakeCache This page was generated Contributions are welcome in [Psake-repo](https://github.com/psake/psake). Since Psake 5.0 This command was introduced in Psake 5.0 and is not available in earlier versions. ## SYNOPSIS[​](#synopsis "Direct link to SYNOPSIS") Clears the psake build cache. ## SYNTAX[​](#syntax "Direct link to SYNTAX") ### \_\_AllParameterSets[​](#__allparametersets "Direct link to __AllParameterSets") ``` Clear-PsakeCache [[-Path] ] [[-TaskName] ] [] ``` ## DESCRIPTION[​](#description "Direct link to DESCRIPTION") Removes cached task state from the .psake/cache/ directory. This forces all tasks to re-execute on the next build. ## EXAMPLES[​](#examples "Direct link to EXAMPLES") ### EXAMPLE 1[​](#example-1 "Direct link to EXAMPLE 1") ``` Clear-PsakeCache ``` Clears all cached task state in the current directory. ### EXAMPLE 2[​](#example-2 "Direct link to EXAMPLE 2") ``` Clear-PsakeCache -TaskName 'Build' ``` Clears cached state for the 'Build' task only. ## PARAMETERS[​](#parameters "Direct link to PARAMETERS") ### -Path[​](#-path "Direct link to -Path") The directory containing the .psake/cache/ folder. Defaults to the current directory. ``` Type: System.String DefaultValue: . SupportsWildcards: false Aliases: [] ParameterSets: - Name: (All) Position: 0 IsRequired: false ValueFromPipeline: false ValueFromPipelineByPropertyName: false ValueFromRemainingArguments: false DontShow: false AcceptedValues: [] HelpMessage: '' ``` ### -TaskName[​](#-taskname "Direct link to -TaskName") Optional: clear cache for a specific task only. ``` Type: System.String DefaultValue: '' SupportsWildcards: false Aliases: [] ParameterSets: - Name: (All) Position: 1 IsRequired: false ValueFromPipeline: false ValueFromPipelineByPropertyName: false ValueFromRemainingArguments: false DontShow: false AcceptedValues: [] HelpMessage: '' ``` ### CommonParameters[​](#commonparameters "Direct link to CommonParameters") This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutBuffer, -OutVariable, -PipelineVariable, -ProgressAction, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about\_CommonParameters](https://go.microsoft.com/fwlink/?LinkID=113216). ## INPUTS[​](#inputs "Direct link to INPUTS") ## OUTPUTS[​](#outputs "Direct link to OUTPUTS") ## NOTES[​](#notes "Direct link to NOTES") ## RELATED LINKS[​](#related-links "Direct link to RELATED LINKS") * [Invoke-Psake](/docs/commands/Invoke-Psake.md) ## VERSION[​](#version "Direct link to VERSION") *This page was generated using comment-based help in [Psake 5.0.4](https://github.com/psake/psake).* --- # Execute This page was generated Contributions are welcome in [Psake-repo](https://github.com/psake/psake). Since Psake 5.0 This command was introduced in Psake 5.0 and is not available in earlier versions. ## SYNOPSIS[​](#synopsis "Direct link to SYNOPSIS") Helper function for executing command-line programs. ## SYNTAX[​](#syntax "Direct link to SYNTAX") ### \_\_AllParameterSets[​](#__allparametersets "Direct link to __AllParameterSets") ``` Execute [-Cmd] [[-ErrorMessage] ] [[-MaxRetries] ] [[-RetryTriggerErrorPattern] ] [[-WorkingDirectory] ] [[-TimeoutSeconds] ] [-NewProcess] [] ``` ## DESCRIPTION[​](#description "Direct link to DESCRIPTION") Throws a terminating error if $lastexitcode is non-zero after the command runs, so callers don't need to check it after each invocation. ## EXAMPLES[​](#examples "Direct link to EXAMPLES") ### EXAMPLE 1[​](#example-1 "Direct link to EXAMPLE 1") ``` Execute { svn info $repository_trunk } "Error executing SVN. Please verify SVN command-line client is installed" ``` This example calls the svn command-line client. ## PARAMETERS[​](#parameters "Direct link to PARAMETERS") ### -Cmd[​](#-cmd "Direct link to -Cmd") The scriptblock to execute. This scriptblock will typically contain the command-line invocation. ``` Type: System.Management.Automation.ScriptBlock DefaultValue: '' SupportsWildcards: false Aliases: [] ParameterSets: - Name: (All) Position: 0 IsRequired: true ValueFromPipeline: false ValueFromPipelineByPropertyName: false ValueFromRemainingArguments: false DontShow: false AcceptedValues: [] HelpMessage: '' ``` ### -ErrorMessage[​](#-errormessage "Direct link to -ErrorMessage") The error message to display if the external command returned a non-zero exit code. ``` Type: System.String DefaultValue: ($msgs.error_bad_command -f $Cmd.ToString().Trim()) SupportsWildcards: false Aliases: [] ParameterSets: - Name: (All) Position: 1 IsRequired: false ValueFromPipeline: false ValueFromPipelineByPropertyName: false ValueFromRemainingArguments: false DontShow: false AcceptedValues: [] HelpMessage: '' ``` ### -MaxRetries[​](#-maxretries "Direct link to -MaxRetries") The maximum number of times to retry the command before failing. ``` Type: System.Int32 DefaultValue: 0 SupportsWildcards: false Aliases: [] ParameterSets: - Name: (All) Position: 2 IsRequired: false ValueFromPipeline: false ValueFromPipelineByPropertyName: false ValueFromRemainingArguments: false DontShow: false AcceptedValues: [] HelpMessage: '' ``` ### -NewProcess[​](#-newprocess "Direct link to -NewProcess") If set, the command will be executed in a new process. This can be used to isolate the command's environment from the current process. ``` Type: System.Management.Automation.SwitchParameter DefaultValue: False SupportsWildcards: false Aliases: [] ParameterSets: - Name: (All) Position: Named IsRequired: false ValueFromPipeline: false ValueFromPipelineByPropertyName: false ValueFromRemainingArguments: false DontShow: false AcceptedValues: [] HelpMessage: '' ``` ### -RetryTriggerErrorPattern[​](#-retrytriggererrorpattern "Direct link to -RetryTriggerErrorPattern") If the external command raises an exception, match the exception against this regex to determine if the command can be retried. If a match is found, the command will be retried provided \[MaxRetries] has not been reached. ``` Type: System.String DefaultValue: '' SupportsWildcards: false Aliases: [] ParameterSets: - Name: (All) Position: 3 IsRequired: false ValueFromPipeline: false ValueFromPipelineByPropertyName: false ValueFromRemainingArguments: false DontShow: false AcceptedValues: [] HelpMessage: '' ``` ### -TimeoutSeconds[​](#-timeoutseconds "Direct link to -TimeoutSeconds") If set, the command will be terminated if it runs longer than this number of seconds. Defaults to 300 seconds (5 minutes). ``` Type: System.Int32 DefaultValue: 300 SupportsWildcards: false Aliases: [] ParameterSets: - Name: (All) Position: 5 IsRequired: false ValueFromPipeline: false ValueFromPipelineByPropertyName: false ValueFromRemainingArguments: false DontShow: false AcceptedValues: [] HelpMessage: '' ``` ### -WorkingDirectory[​](#-workingdirectory "Direct link to -WorkingDirectory") The working directory to set before running the external command. ``` Type: System.String DefaultValue: '' SupportsWildcards: false Aliases: - wd ParameterSets: - Name: (All) Position: 4 IsRequired: false ValueFromPipeline: false ValueFromPipelineByPropertyName: false ValueFromRemainingArguments: false DontShow: false AcceptedValues: [] HelpMessage: '' ``` ### CommonParameters[​](#commonparameters "Direct link to CommonParameters") This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutBuffer, -OutVariable, -PipelineVariable, -ProgressAction, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about\_CommonParameters](https://go.microsoft.com/fwlink/?LinkID=113216). ## INPUTS[​](#inputs "Direct link to INPUTS") ## OUTPUTS[​](#outputs "Direct link to OUTPUTS") ## NOTES[​](#notes "Direct link to NOTES") ## RELATED LINKS[​](#related-links "Direct link to RELATED LINKS") ## VERSION[​](#version "Direct link to VERSION") *This page was generated using comment-based help in [Psake 5.0.4](https://github.com/psake/psake).* --- # FormatTaskName This page was generated Contributions are welcome in [Psake-repo](https://github.com/psake/psake). ## SYNOPSIS[​](#synopsis "Direct link to SYNOPSIS") This function allows you to change how psake renders the task name during a build. ## SYNTAX[​](#syntax "Direct link to SYNTAX") ### \_\_AllParameterSets[​](#__allparametersets "Direct link to __AllParameterSets") ``` FormatTaskName [-Format] [] ``` ## DESCRIPTION[​](#description "Direct link to DESCRIPTION") Useful for adding visual separators or color to task names in the build output. Accepts a -f format string or a scriptblock that receives the task name as its only argument. ## EXAMPLES[​](#examples "Direct link to EXAMPLES") ### EXAMPLE 1[​](#example-1 "Direct link to EXAMPLE 1") ``` Task default -depends TaskA, TaskB, TaskC FormatTaskName "-------- {0} --------" Task TaskA { "TaskA is executing" } Task TaskB { "TaskB is executing" } Task TaskC { "TaskC is executing" } ``` A sample build script that uses a format string. The script above produces the following output: ``` -------- TaskA -------- TaskA is executing -------- TaskB -------- TaskB is executing -------- TaskC -------- TaskC is executing Build Succeeded! ``` ### EXAMPLE 2[​](#example-2 "Direct link to EXAMPLE 2") ``` Task default -depends TaskA, TaskB, TaskC FormatTaskName { param($taskName) write-host "Executing Task: $taskName" -ForegroundColor blue } Task TaskA { "TaskA is executing" } Task TaskB { "TaskB is executing" } Task TaskC { "TaskC is executing" } ``` A sample build script that uses a ScriptBlock. The above example uses the scriptblock parameter to the FormatTaskName function to render each task name in the color blue. Note: the $taskName parameter name is arbitrary, it could be named anything. ## PARAMETERS[​](#parameters "Direct link to PARAMETERS") ### -Format[​](#-format "Direct link to -Format") A format string or a scriptblock to execute ``` Type: System.Object DefaultValue: '' SupportsWildcards: false Aliases: [] ParameterSets: - Name: (All) Position: 0 IsRequired: true ValueFromPipeline: false ValueFromPipelineByPropertyName: false ValueFromRemainingArguments: false DontShow: false AcceptedValues: [] HelpMessage: '' ``` ### CommonParameters[​](#commonparameters "Direct link to CommonParameters") This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutBuffer, -OutVariable, -PipelineVariable, -ProgressAction, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about\_CommonParameters](https://go.microsoft.com/fwlink/?LinkID=113216). ## INPUTS[​](#inputs "Direct link to INPUTS") ## OUTPUTS[​](#outputs "Direct link to OUTPUTS") ## NOTES[​](#notes "Direct link to NOTES") ## RELATED LINKS[​](#related-links "Direct link to RELATED LINKS") ## VERSION[​](#version "Direct link to VERSION") *This page was generated using comment-based help in [Psake 5.0.4](https://github.com/psake/psake).* --- # Framework This page was generated Contributions are welcome in [Psake-repo](https://github.com/psake/psake). ## SYNOPSIS[​](#synopsis "Direct link to SYNOPSIS") Sets the version of the .NET framework you want to use during build. ## SYNTAX[​](#syntax "Direct link to SYNTAX") ### \_\_AllParameterSets[​](#__allparametersets "Direct link to __AllParameterSets") ``` Framework [-Framework] [] ``` ## DESCRIPTION[​](#description "Direct link to DESCRIPTION") Overrides the psake default framework, determining which MSBuild and runtime directories are used by all tasks in the build. ## EXAMPLES[​](#examples "Direct link to EXAMPLES") ### EXAMPLE 1[​](#example-1 "Direct link to EXAMPLE 1") ``` Framework "4.0" Task default -depends Compile Task Compile -depends Clean { msbuild /version } ``` Uses MSBuild v4.0 for the build. ## PARAMETERS[​](#parameters "Direct link to PARAMETERS") ### -Framework[​](#-framework "Direct link to -Framework") Framework version string. Append 'x86' or 'x64' to force bitness; otherwise bitness matches the current PowerShell process. Supported values: '1.0', '1.1', '2.0', '2.0x86', '2.0x64', '3.0', '3.0x86', '3.0x64', '3.5', '3.5x86', '3.5x64', '4.0', '4.0x86', '4.0x64', '4.5', '4.5x86', '4.5x64', '4.5.1', '4.5.1x86', '4.5.1x64'. Default is '3.5\*' (bitness auto-detected). ``` Type: System.String DefaultValue: '' SupportsWildcards: false Aliases: [] ParameterSets: - Name: (All) Position: 0 IsRequired: true ValueFromPipeline: false ValueFromPipelineByPropertyName: false ValueFromRemainingArguments: false DontShow: false AcceptedValues: [] HelpMessage: '' ``` ### CommonParameters[​](#commonparameters "Direct link to CommonParameters") This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutBuffer, -OutVariable, -PipelineVariable, -ProgressAction, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about\_CommonParameters](https://go.microsoft.com/fwlink/?LinkID=113216). ## INPUTS[​](#inputs "Direct link to INPUTS") ## OUTPUTS[​](#outputs "Direct link to OUTPUTS") ## NOTES[​](#notes "Direct link to NOTES") ## RELATED LINKS[​](#related-links "Direct link to RELATED LINKS") ## VERSION[​](#version "Direct link to VERSION") *This page was generated using comment-based help in [Psake 5.0.4](https://github.com/psake/psake).* --- # Get-PsakeBuildPlan This page was generated Contributions are welcome in [Psake-repo](https://github.com/psake/psake). Since Psake 5.0 This command was introduced in Psake 5.0 and is not available in earlier versions. ## SYNOPSIS[​](#synopsis "Direct link to SYNOPSIS") Compiles a build file and returns the build plan without executing any tasks. ## SYNTAX[​](#syntax "Direct link to SYNTAX") ### \_\_AllParameterSets[​](#__allparametersets "Direct link to __AllParameterSets") ``` Get-PsakeBuildPlan [[-BuildFile] ] [[-TaskList] ] [] ``` ## DESCRIPTION[​](#description "Direct link to DESCRIPTION") This is the primary testability API for psake v5. It loads a build file, validates the dependency graph, and returns a PsakeBuildPlan object that can be inspected in tests. This function always returns a \[PsakeBuildPlan]. If the build file cannot be loaded or the dependency graph is invalid, an invalid plan is returned with IsValid = $false and ValidationErrors populated. The returned plan can be piped into Invoke-Psake for execution. Note that when piping, the build file is re-loaded during the execution phase to resolve properties, setup, and teardown blocks. ## EXAMPLES[​](#examples "Direct link to EXAMPLES") ### EXAMPLE 1[​](#example-1 "Direct link to EXAMPLE 1") ``` $plan = Get-PsakeBuildPlan -BuildFile './psakefile.ps1' $plan.Tasks | Should -HaveCount 4 $plan.ExecutionOrder | Should -Be @('Clean', 'Compile', 'Test', 'Default') ``` This example compiles the build file and asserts that there are 4 tasks and that the execution order is correct. ### EXAMPLE 2[​](#example-2 "Direct link to EXAMPLE 2") ``` $plan = Get-PsakeBuildPlan $plan.TaskMap['build'].DependsOn | Should -Contain 'Clean' $plan.IsValid | Should -BeTrue ``` This example compiles the default build file and asserts that the 'build' task depends on the 'Clean' task and that the plan is valid. ### EXAMPLE 3[​](#example-3 "Direct link to EXAMPLE 3") ``` Get-PsakeBuildPlan -BuildFile './psakefile.ps1' | Invoke-Psake ``` Compiles the build plan and pipes it into Invoke-Psake for execution. Note: the build file is re-loaded during the execution phase. ## PARAMETERS[​](#parameters "Direct link to PARAMETERS") ### -BuildFile[​](#-buildfile "Direct link to -BuildFile") The path to the psake build script. Defaults to 'psakefile.ps1'. ``` Type: System.String DefaultValue: '' SupportsWildcards: false Aliases: [] ParameterSets: - Name: (All) Position: 0 IsRequired: false ValueFromPipeline: false ValueFromPipelineByPropertyName: false ValueFromRemainingArguments: false DontShow: false AcceptedValues: [] HelpMessage: '' ``` ### -TaskList[​](#-tasklist "Direct link to -TaskList") A list of task names to include in the plan. Defaults to 'default'. ``` Type: System.String[] DefaultValue: "@('default')" SupportsWildcards: false Aliases: [] ParameterSets: - Name: (All) Position: 1 IsRequired: false ValueFromPipeline: false ValueFromPipelineByPropertyName: false ValueFromRemainingArguments: false DontShow: false AcceptedValues: [] HelpMessage: '' ``` ### CommonParameters[​](#commonparameters "Direct link to CommonParameters") This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutBuffer, -OutVariable, -PipelineVariable, -ProgressAction, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about\_CommonParameters](https://go.microsoft.com/fwlink/?LinkID=113216). ## INPUTS[​](#inputs "Direct link to INPUTS") ## OUTPUTS[​](#outputs "Direct link to OUTPUTS") ## NOTES[​](#notes "Direct link to NOTES") ## RELATED LINKS[​](#related-links "Direct link to RELATED LINKS") ## VERSION[​](#version "Direct link to VERSION") *This page was generated using comment-based help in [Psake 5.0.4](https://github.com/psake/psake).* --- # Get-PSakeScriptTasks This page was generated Contributions are welcome in [Psake-repo](https://github.com/psake/psake). ## SYNOPSIS[​](#synopsis "Direct link to SYNOPSIS") Returns meta data about all the tasks defined in the provided psake script. ## SYNTAX[​](#syntax "Direct link to SYNTAX") ### \_\_AllParameterSets[​](#__allparametersets "Direct link to __AllParameterSets") ``` Get-PSakeScriptTasks [[-BuildFile] ] [] ``` ## DESCRIPTION[​](#description "Direct link to DESCRIPTION") Loads the build file and evaluates task definitions without executing any tasks. Useful for tooling, IDE integrations, and tab completion. ## EXAMPLES[​](#examples "Direct link to EXAMPLES") ### EXAMPLE 1[​](#example-1 "Direct link to EXAMPLE 1") ``` Get-PSakeScriptTasks -BuildFile '.\build.ps1' ``` DependsOn Alias Name Description *** {} Compile {} Clean {Test} Default {Clean, Compile} Test Gets the psake tasks contained in the 'build.ps1' file. ## PARAMETERS[​](#parameters "Direct link to PARAMETERS") ### -BuildFile[​](#-buildfile "Direct link to -BuildFile") The path to the psake build script to read the tasks from. ``` Type: System.String DefaultValue: '' SupportsWildcards: false Aliases: [] ParameterSets: - Name: (All) Position: 0 IsRequired: false ValueFromPipeline: false ValueFromPipelineByPropertyName: false ValueFromRemainingArguments: false DontShow: false AcceptedValues: [] HelpMessage: '' ``` ### CommonParameters[​](#commonparameters "Direct link to CommonParameters") This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutBuffer, -OutVariable, -PipelineVariable, -ProgressAction, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about\_CommonParameters](https://go.microsoft.com/fwlink/?LinkID=113216). ## INPUTS[​](#inputs "Direct link to INPUTS") ## OUTPUTS[​](#outputs "Direct link to OUTPUTS") ## NOTES[​](#notes "Direct link to NOTES") ## RELATED LINKS[​](#related-links "Direct link to RELATED LINKS") ## VERSION[​](#version "Direct link to VERSION") *This page was generated using comment-based help in [Psake 5.0.4](https://github.com/psake/psake).* --- # Include This page was generated Contributions are welcome in [Psake-repo](https://github.com/psake/psake). ## SYNOPSIS[​](#synopsis "Direct link to SYNOPSIS") Include the functions or code of another powershell script file into the current build script's scope ## SYNTAX[​](#syntax "Direct link to SYNTAX") ### Path (Default)[​](#path-default "Direct link to Path (Default)") ``` Include [-Path] [] ``` ### LiteralPath[​](#literalpath "Direct link to LiteralPath") ``` Include [-LiteralPath] [] ``` ## DESCRIPTION[​](#description "Direct link to DESCRIPTION") Included scripts are dot-sourced into the build script's scope after the build file finishes loading. You can call Include more than once. ## EXAMPLES[​](#examples "Direct link to EXAMPLES") ### EXAMPLE 1[​](#example-1 "Direct link to EXAMPLE 1") ``` Include ".\build_utils.ps1" Task default -depends Test Task Test -depends Compile, Clean { } Task Compile -depends Clean { } Task Clean { } ``` Includes all functions and variables from build\_utils.ps1 in the build script's scope. ### EXAMPLE 2[​](#example-2 "Direct link to EXAMPLE 2") ``` @("File1.ps1","File2.ps1") | Include Get-ChildItem | Include ``` Strings or FileInfo objects can be piped to the Include function ## PARAMETERS[​](#parameters "Direct link to PARAMETERS") ### -LiteralPath[​](#-literalpath "Direct link to -LiteralPath") Path to the script file to include. No wildcard expansion. ``` Type: System.String DefaultValue: '' SupportsWildcards: false Aliases: [] ParameterSets: - Name: LiteralPath Position: 0 IsRequired: true ValueFromPipeline: false ValueFromPipelineByPropertyName: true ValueFromRemainingArguments: false DontShow: false AcceptedValues: [] HelpMessage: '' ``` ### -Path[​](#-path "Direct link to -Path") Path to the script file(s) to include. Supports wildcards. ``` Type: System.String DefaultValue: '' SupportsWildcards: true Aliases: - fileNamePathToInclude ParameterSets: - Name: Path Position: 0 IsRequired: true ValueFromPipeline: true ValueFromPipelineByPropertyName: true ValueFromRemainingArguments: false DontShow: false AcceptedValues: [] HelpMessage: '' ``` ### CommonParameters[​](#commonparameters "Direct link to CommonParameters") This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutBuffer, -OutVariable, -PipelineVariable, -ProgressAction, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about\_CommonParameters](https://go.microsoft.com/fwlink/?LinkID=113216). ## INPUTS[​](#inputs "Direct link to INPUTS") ### System.String[​](#systemstring "Direct link to System.String") The path(s) to the script file(s) to include in the build. ### System.String[​](#systemstring-1 "Direct link to System.String") ## OUTPUTS[​](#outputs "Direct link to OUTPUTS") ## NOTES[​](#notes "Direct link to NOTES") ## RELATED LINKS[​](#related-links "Direct link to RELATED LINKS") ## VERSION[​](#version "Direct link to VERSION") *This page was generated using comment-based help in [Psake 5.0.4](https://github.com/psake/psake).* --- # Invoke-Psake This page was generated Contributions are welcome in [Psake-repo](https://github.com/psake/psake). ## SYNOPSIS[​](#synopsis "Direct link to SYNOPSIS") Runs a psake build script. ## SYNTAX[​](#syntax "Direct link to SYNTAX") ### \_\_AllParameterSets[​](#__allparametersets "Direct link to __AllParameterSets") ``` Invoke-Psake [[-BuildFile] ] [[-TaskList] ] [[-Framework] ] [-Docs] [[-Parameters] ] [[-Properties] ] [[-Initialization] ] [-NoLogo] [-DetailedDocs] [-NoTimeReport] [-OutputFormat ] [-NoCache] [-CompileOnly] [-Quiet] [-BuildPlan ] [] ``` ## DESCRIPTION[​](#description "Direct link to DESCRIPTION") This function runs a psake build script using a two-phase compile/run model. The compile phase loads the build file and validates the dependency graph. The run phase executes tasks in the resolved order. A pre-compiled \[PsakeBuildPlan] from Get-PsakeBuildPlan can be piped in to skip the compile phase. When doing so, the build file is re-loaded for the execution phase to resolve properties, setup, and teardown blocks. ## EXAMPLES[​](#examples "Direct link to EXAMPLES") ### EXAMPLE 1[​](#example-1 "Direct link to EXAMPLE 1") ``` Invoke-psake ``` Runs the 'default' task in the 'psakefile.ps1' build script ### EXAMPLE 2[​](#example-2 "Direct link to EXAMPLE 2") ``` Invoke-psake '.\build.ps1' Tests,Package ``` Runs the 'Tests' and 'Package' tasks in the 'build.ps1' build script ### EXAMPLE 3[​](#example-3 "Direct link to EXAMPLE 3") ``` Invoke-psake -CompileOnly ``` Returns the build plan without executing any tasks. ### EXAMPLE 4[​](#example-4 "Direct link to EXAMPLE 4") ``` Get-PsakeBuildPlan | Invoke-Psake ``` Compiles the build plan then executes it. The build file is re-loaded during the execution phase. ### EXAMPLE 5[​](#example-5 "Direct link to EXAMPLE 5") ``` Invoke-psake -OutputFormat JSON ``` Runs the build and outputs the result as JSON. ## PARAMETERS[​](#parameters "Direct link to PARAMETERS") ### -BuildFile[​](#-buildfile "Direct link to -BuildFile") The path to the psake build script to execute ``` Type: System.String DefaultValue: '' SupportsWildcards: false Aliases: [] ParameterSets: - Name: (All) Position: 0 IsRequired: false ValueFromPipeline: false ValueFromPipelineByPropertyName: false ValueFromRemainingArguments: false DontShow: false AcceptedValues: [] HelpMessage: '' ``` ### -BuildPlan[​](#-buildplan "Direct link to -BuildPlan") A pre-compiled \[PsakeBuildPlan] to execute, typically from Get-PsakeBuildPlan via the pipeline. Compile-phase parameters (BuildFile, TaskList, Framework, Docs, DetailedDocs, CompileOnly) are ignored when a plan is provided. Note: the build file is re-loaded during the execution phase. ``` Type: PsakeBuildPlan DefaultValue: '' SupportsWildcards: false Aliases: [] ParameterSets: - Name: (All) Position: Named IsRequired: false ValueFromPipeline: true ValueFromPipelineByPropertyName: false ValueFromRemainingArguments: false DontShow: false AcceptedValues: [] HelpMessage: '' ``` ### -CompileOnly[​](#-compileonly "Direct link to -CompileOnly") Return the build plan without executing any tasks. Delegates to Get-PsakeBuildPlan. Useful for tooling and testing. ``` Type: System.Management.Automation.SwitchParameter DefaultValue: False SupportsWildcards: false Aliases: [] ParameterSets: - Name: (All) Position: Named IsRequired: false ValueFromPipeline: false ValueFromPipelineByPropertyName: false ValueFromRemainingArguments: false DontShow: false AcceptedValues: [] HelpMessage: '' ``` ### -DetailedDocs[​](#-detaileddocs "Direct link to -DetailedDocs") Prints a more descriptive list of tasks and their descriptions. ``` Type: System.Management.Automation.SwitchParameter DefaultValue: False SupportsWildcards: false Aliases: [] ParameterSets: - Name: (All) Position: 8 IsRequired: false ValueFromPipeline: false ValueFromPipelineByPropertyName: false ValueFromRemainingArguments: false DontShow: false AcceptedValues: [] HelpMessage: '' ``` ### -Docs[​](#-docs "Direct link to -Docs") Prints a list of tasks and their descriptions ``` Type: System.Management.Automation.SwitchParameter DefaultValue: False SupportsWildcards: false Aliases: [] ParameterSets: - Name: (All) Position: 3 IsRequired: false ValueFromPipeline: false ValueFromPipelineByPropertyName: false ValueFromRemainingArguments: false DontShow: false AcceptedValues: [] HelpMessage: '' ``` ### -Framework[​](#-framework "Direct link to -Framework") The version of the .NET framework you want to use during build. You can append x86 or x64 to force a specific framework. If not specified, x86 or x64 will be detected based on the bitness of the PowerShell process. Possible values: '4.0', '4.0x86', '4.0x64', '4.5', '4.5x86', '4.5x64', '4.5.1', '4.5.1x86', '4.5.1x64', '4.6', '4.6.1', '4.6.2', '4.7', '4.7.1', '4.7.2', '4.8', '4.8.1' ``` Type: System.String DefaultValue: '' SupportsWildcards: false Aliases: [] ParameterSets: - Name: (All) Position: 2 IsRequired: false ValueFromPipeline: false ValueFromPipelineByPropertyName: false ValueFromRemainingArguments: false DontShow: false AcceptedValues: [] HelpMessage: '' ``` ### -Initialization[​](#-initialization "Direct link to -Initialization") A script block that will be executed before the tasks are executed. ``` Type: System.Management.Automation.ScriptBlock DefaultValue: '{}' SupportsWildcards: false Aliases: - init ParameterSets: - Name: (All) Position: 6 IsRequired: false ValueFromPipeline: false ValueFromPipelineByPropertyName: false ValueFromRemainingArguments: false DontShow: false AcceptedValues: [] HelpMessage: '' ``` ### -NoCache[​](#-nocache "Direct link to -NoCache") Bypass task caching. All tasks will execute regardless of cache state. ``` Type: System.Management.Automation.SwitchParameter DefaultValue: False SupportsWildcards: false Aliases: [] ParameterSets: - Name: (All) Position: Named IsRequired: false ValueFromPipeline: false ValueFromPipelineByPropertyName: false ValueFromRemainingArguments: false DontShow: false AcceptedValues: [] HelpMessage: '' ``` ### -NoLogo[​](#-nologo "Direct link to -NoLogo") Do not display the startup banner and copyright message. ``` Type: System.Management.Automation.SwitchParameter DefaultValue: False SupportsWildcards: false Aliases: [] ParameterSets: - Name: (All) Position: 7 IsRequired: false ValueFromPipeline: false ValueFromPipelineByPropertyName: false ValueFromRemainingArguments: false DontShow: false AcceptedValues: [] HelpMessage: '' ``` ### -NoTimeReport[​](#-notimereport "Direct link to -NoTimeReport") Do not display the time report. ``` Type: System.Management.Automation.SwitchParameter DefaultValue: False SupportsWildcards: false Aliases: - notr ParameterSets: - Name: (All) Position: 9 IsRequired: false ValueFromPipeline: false ValueFromPipelineByPropertyName: false ValueFromRemainingArguments: false DontShow: false AcceptedValues: [] HelpMessage: '' ``` ### -OutputFormat[​](#-outputformat "Direct link to -OutputFormat") Output format. 'Default' for console, 'JSON' for stdout JSON, 'GitHubActions' for workflow annotations (::error::, :⚠️:, ::debug::), 'Annotated' for console + annotation lines (VS Code problem matcher). Falls back to PSAKE\_OUTPUT\_FORMAT env variable. ``` Type: System.String DefaultValue: Default SupportsWildcards: false Aliases: [] ParameterSets: - Name: (All) Position: Named IsRequired: false ValueFromPipeline: false ValueFromPipelineByPropertyName: false ValueFromRemainingArguments: false DontShow: false AcceptedValues: [] HelpMessage: '' ``` ### -Parameters[​](#-parameters "Direct link to -Parameters") A hashtable containing parameters to be passed into the current build script. These parameters will be processed before the 'Properties' function of the script is processed. ``` Type: System.Collections.Hashtable DefaultValue: '@{}' SupportsWildcards: false Aliases: [] ParameterSets: - Name: (All) Position: 4 IsRequired: false ValueFromPipeline: false ValueFromPipelineByPropertyName: false ValueFromRemainingArguments: false DontShow: false AcceptedValues: [] HelpMessage: '' ``` ### -Properties[​](#-properties "Direct link to -Properties") A hashtable containing properties to be passed into the current build script. These properties will override matching properties that are found in the 'Properties' function of the script. ``` Type: System.Collections.Hashtable DefaultValue: '@{}' SupportsWildcards: false Aliases: [] ParameterSets: - Name: (All) Position: 5 IsRequired: false ValueFromPipeline: false ValueFromPipelineByPropertyName: false ValueFromRemainingArguments: false DontShow: false AcceptedValues: [] HelpMessage: '' ``` ### -Quiet[​](#-quiet "Direct link to -Quiet") Suppress all console output. The PsakeBuildResult is still returned. ``` Type: System.Management.Automation.SwitchParameter DefaultValue: False SupportsWildcards: false Aliases: [] ParameterSets: - Name: (All) Position: Named IsRequired: false ValueFromPipeline: false ValueFromPipelineByPropertyName: false ValueFromRemainingArguments: false DontShow: false AcceptedValues: [] HelpMessage: '' ``` ### -TaskList[​](#-tasklist "Direct link to -TaskList") A comma-separated list of task names to execute ``` Type: System.String[] DefaultValue: "@('default')" SupportsWildcards: false Aliases: [] ParameterSets: - Name: (All) Position: 1 IsRequired: false ValueFromPipeline: false ValueFromPipelineByPropertyName: false ValueFromRemainingArguments: false DontShow: false AcceptedValues: [] HelpMessage: '' ``` ### CommonParameters[​](#commonparameters "Direct link to CommonParameters") This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutBuffer, -OutVariable, -PipelineVariable, -ProgressAction, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about\_CommonParameters](https://go.microsoft.com/fwlink/?LinkID=113216). ## INPUTS[​](#inputs "Direct link to INPUTS") ### PsakeBuildPlan[​](#psakebuildplan "Direct link to PsakeBuildPlan") A pre-compiled build plan from Get-PsakeBuildPlan ### PsakeBuildPlan[​](#psakebuildplan-1 "Direct link to PsakeBuildPlan") ## OUTPUTS[​](#outputs "Direct link to OUTPUTS") ## NOTES[​](#notes "Direct link to NOTES") ## RELATED LINKS[​](#related-links "Direct link to RELATED LINKS") ## VERSION[​](#version "Direct link to VERSION") *This page was generated using comment-based help in [Psake 5.0.4](https://github.com/psake/psake).* --- # Invoke-Task This page was generated Contributions are welcome in [Psake-repo](https://github.com/psake/psake). ## SYNOPSIS[​](#synopsis "Direct link to SYNOPSIS") Executes another task in the current build script. ## SYNTAX[​](#syntax "Direct link to SYNTAX") ### \_\_AllParameterSets[​](#__allparametersets "Direct link to __AllParameterSets") ``` Invoke-Task [-TaskName] [] ``` ## DESCRIPTION[​](#description "Direct link to DESCRIPTION") Use this inside a task's Action when you need to sequence tasks programmatically rather than declaring them with -Depends. ## EXAMPLES[​](#examples "Direct link to EXAMPLES") ### EXAMPLE 1[​](#example-1 "Direct link to EXAMPLE 1") ``` Invoke-Task "Compile" ``` This example calls the "Compile" task. ## PARAMETERS[​](#parameters "Direct link to PARAMETERS") ### -TaskName[​](#-taskname "Direct link to -TaskName") The name of the task to execute. ``` Type: System.String DefaultValue: '' SupportsWildcards: false Aliases: [] ParameterSets: - Name: (All) Position: 0 IsRequired: true ValueFromPipeline: false ValueFromPipelineByPropertyName: false ValueFromRemainingArguments: false DontShow: false AcceptedValues: [] HelpMessage: '' ``` ### CommonParameters[​](#commonparameters "Direct link to CommonParameters") This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutBuffer, -OutVariable, -PipelineVariable, -ProgressAction, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about\_CommonParameters](https://go.microsoft.com/fwlink/?LinkID=113216). ## INPUTS[​](#inputs "Direct link to INPUTS") ## OUTPUTS[​](#outputs "Direct link to OUTPUTS") ## NOTES[​](#notes "Direct link to NOTES") ## RELATED LINKS[​](#related-links "Direct link to RELATED LINKS") ## VERSION[​](#version "Direct link to VERSION") *This page was generated using comment-based help in [Psake 5.0.4](https://github.com/psake/psake).* --- # Properties This page was generated Contributions are welcome in [Psake-repo](https://github.com/psake/psake). ## SYNOPSIS[​](#synopsis "Direct link to SYNOPSIS") Define a scriptblock that contains assignments to variables that will be available within all tasks in the build script ## SYNTAX[​](#syntax "Direct link to SYNTAX") ### ScriptBlock (Default)[​](#scriptblock-default "Direct link to ScriptBlock (Default)") ``` Properties [-Properties] [] ``` ### Hashtable[​](#hashtable "Direct link to Hashtable") ``` Properties [-Hashtable] [] ``` ## DESCRIPTION[​](#description "Direct link to DESCRIPTION") A build script may declare a "Properties" function which allows you to define variables that will be available within all the "Task" functions in the build script. ## EXAMPLES[​](#examples "Direct link to EXAMPLES") ### EXAMPLE 1[​](#example-1 "Direct link to EXAMPLE 1") ``` Properties { $build_dir = "c:\build" $connection_string = "datasource=localhost;initial catalog=northwind;integrated security=sspi" } Task default -depends Test Task Test -depends Compile, Clean { } Task Compile -depends Clean { } Task Clean { } ``` Note: You can have more than one "Properties" function defined in the build script. ### EXAMPLE 2[​](#example-2 "Direct link to EXAMPLE 2") ``` Properties { $script:build_dir = "c:\build" $script:connection_string = "datasource=localhost;initial catalog=northwind;integrated security=sspi" } Task Compile { "Building to: $build_dir" # No PSScriptAnalyzer warning, variable is recognized } ``` Recommended: Use script-scoped variables to avoid PSScriptAnalyzer warnings The $script: prefix has identical runtime behavior but satisfies PSScriptAnalyzer's static analysis requirements. ### EXAMPLE 3[​](#example-3 "Direct link to EXAMPLE 3") ``` Properties { $build_dir = "c:\build" # Warning: PSUseDeclaredVarsMoreThanAssignments } Task Compile { "Building to: $build_dir" # Works at runtime, but PSScriptAnalyzer warns } ``` Alternative: Non-scoped variables (generates PSScriptAnalyzer warnings) Variables still work correctly at runtime, but PSScriptAnalyzer cannot detect that they will be used in tasks. ## PARAMETERS[​](#parameters "Direct link to PARAMETERS") ### -Hashtable[​](#-hashtable "Direct link to -Hashtable") An alternative to the scriptblock parameter, you can provide a hashtable of key-value pairs that will be converted into variables. This is useful when you want to define properties programmatically or from an external source. ``` Type: System.Collections.Hashtable DefaultValue: '' SupportsWildcards: false Aliases: [] ParameterSets: - Name: Hashtable Position: 0 IsRequired: true ValueFromPipeline: false ValueFromPipelineByPropertyName: false ValueFromRemainingArguments: false DontShow: false AcceptedValues: [] HelpMessage: '' ``` ### -Properties[​](#-properties "Direct link to -Properties") The script block containing all the variable assignment statements ``` Type: System.Management.Automation.ScriptBlock DefaultValue: '' SupportsWildcards: false Aliases: [] ParameterSets: - Name: ScriptBlock Position: 0 IsRequired: true ValueFromPipeline: false ValueFromPipelineByPropertyName: false ValueFromRemainingArguments: false DontShow: false AcceptedValues: [] HelpMessage: '' ``` ### CommonParameters[​](#commonparameters "Direct link to CommonParameters") This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutBuffer, -OutVariable, -PipelineVariable, -ProgressAction, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about\_CommonParameters](https://go.microsoft.com/fwlink/?LinkID=113216). ## INPUTS[​](#inputs "Direct link to INPUTS") ## OUTPUTS[​](#outputs "Direct link to OUTPUTS") ## NOTES[​](#notes "Direct link to NOTES") Properties scriptblocks are dot-sourced into each task's scope at runtime. Variables are not accessible outside of task scriptblocks. PSScriptAnalyzer may flag declared variables as unused — this is a false positive. Use the $script: prefix to suppress it (see examples). ## RELATED LINKS[​](#related-links "Direct link to RELATED LINKS") ## VERSION[​](#version "Direct link to VERSION") *This page was generated using comment-based help in [Psake 5.0.4](https://github.com/psake/psake).* --- # Task This page was generated Contributions are welcome in [Psake-repo](https://github.com/psake/psake). ## SYNOPSIS[​](#synopsis "Direct link to SYNOPSIS") Defines a build task to be executed by psake ## SYNTAX[​](#syntax "Direct link to SYNTAX") ### Normal (Default)[​](#normal-default "Direct link to Normal (Default)") ``` Task [-Name] [[-Action] ] [-PreAction ] [-PostAction ] [-PreCondition ] [-PostCondition ] [-ContinueOnError] [-Depends ] [-RequiredVariables ] [-Description ] [-Alias ] [] ``` ### SharedTask[​](#sharedtask "Direct link to SharedTask") ``` Task [-Name] [[-Action] ] -FromModule [-PreAction ] [-PostAction ] [-PreCondition ] [-PostCondition ] [-ContinueOnError] [-Depends ] [-RequiredVariables ] [-Description ] [-Alias ] [-RequiredVersion ] [-MinimumVersion ] [-MaximumVersion ] [-LessThanVersion ] [] ``` ### Declarative[​](#declarative "Direct link to Declarative") ``` Task [-Name] [[-Definition] ] [] ``` ## DESCRIPTION[​](#description "Direct link to DESCRIPTION") Defines a named task in the build script. Every script must include at least one task named 'default'. ## EXAMPLES[​](#examples "Direct link to EXAMPLES") ### EXAMPLE 1[​](#example-1 "Direct link to EXAMPLE 1") ``` Task default -Depends Test Task Test -Depends Compile, Clean { "This is a test" } Task Compile -Depends Clean { "Compile" } Task Clean { "Clean" } ``` The trailing scriptblock is equivalent to -Action { ... }. The 'default' task must exist and should declare no Action of its own. ## PARAMETERS[​](#parameters "Direct link to PARAMETERS") ### -Action[​](#-action "Direct link to -Action") A scriptblock containing the statements to execute for the task. ``` Type: System.Management.Automation.ScriptBlock DefaultValue: '' SupportsWildcards: false Aliases: [] ParameterSets: - Name: SharedTask Position: 1 IsRequired: false ValueFromPipeline: false ValueFromPipelineByPropertyName: false ValueFromRemainingArguments: false - Name: Normal Position: 1 IsRequired: false ValueFromPipeline: false ValueFromPipelineByPropertyName: false ValueFromRemainingArguments: false DontShow: false AcceptedValues: [] HelpMessage: '' ``` ### -Alias[​](#-alias "Direct link to -Alias") An alternate name for the task. ``` Type: System.String DefaultValue: '' SupportsWildcards: false Aliases: [] ParameterSets: - Name: SharedTask Position: Named IsRequired: false ValueFromPipeline: false ValueFromPipelineByPropertyName: false ValueFromRemainingArguments: false - Name: Normal Position: Named IsRequired: false ValueFromPipeline: false ValueFromPipelineByPropertyName: false ValueFromRemainingArguments: false DontShow: false AcceptedValues: [] HelpMessage: '' ``` ### -ContinueOnError[​](#-continueonerror "Direct link to -ContinueOnError") If this switch parameter is set then the task will not cause the build to fail when an exception is thrown by the task ``` Type: System.Management.Automation.SwitchParameter DefaultValue: False SupportsWildcards: false Aliases: [] ParameterSets: - Name: SharedTask Position: Named IsRequired: false ValueFromPipeline: false ValueFromPipelineByPropertyName: false ValueFromRemainingArguments: false - Name: Normal Position: Named IsRequired: false ValueFromPipeline: false ValueFromPipelineByPropertyName: false ValueFromRemainingArguments: false DontShow: false AcceptedValues: [] HelpMessage: '' ``` ### -Definition[​](#-definition "Direct link to -Definition") Declarative hashtable alternative to named parameters. Supported keys: Action, PreAction, PostAction, PreCondition, PostCondition, ContinueOnError, Description, Alias, RequiredVariables. ``` Type: System.Collections.Hashtable DefaultValue: '' SupportsWildcards: false Aliases: [] ParameterSets: - Name: Declarative Position: 1 IsRequired: false ValueFromPipeline: false ValueFromPipelineByPropertyName: false ValueFromRemainingArguments: false DontShow: false AcceptedValues: [] HelpMessage: '' ``` ### -Depends[​](#-depends "Direct link to -Depends") An array of task names that this task depends on. These tasks will be executed before the current task is executed. ``` Type: System.String[] DefaultValue: '@()' SupportsWildcards: false Aliases: [] ParameterSets: - Name: SharedTask Position: Named IsRequired: false ValueFromPipeline: false ValueFromPipelineByPropertyName: false ValueFromRemainingArguments: false - Name: Normal Position: Named IsRequired: false ValueFromPipeline: false ValueFromPipelineByPropertyName: false ValueFromRemainingArguments: false DontShow: false AcceptedValues: [] HelpMessage: '' ``` ### -Description[​](#-description "Direct link to -Description") A description of the task. ``` Type: System.String DefaultValue: '' SupportsWildcards: false Aliases: [] ParameterSets: - Name: SharedTask Position: Named IsRequired: false ValueFromPipeline: false ValueFromPipelineByPropertyName: false ValueFromRemainingArguments: false - Name: Normal Position: Named IsRequired: false ValueFromPipeline: false ValueFromPipelineByPropertyName: false ValueFromRemainingArguments: false DontShow: false AcceptedValues: [] HelpMessage: '' ``` ### -FromModule[​](#-frommodule "Direct link to -FromModule") Load in the task from the specified PowerShell module. ``` Type: System.String DefaultValue: '' SupportsWildcards: false Aliases: [] ParameterSets: - Name: SharedTask Position: Named IsRequired: true ValueFromPipeline: false ValueFromPipelineByPropertyName: false ValueFromRemainingArguments: false DontShow: false AcceptedValues: [] HelpMessage: '' ``` ### -LessThanVersion[​](#-lessthanversion "Direct link to -LessThanVersion") The version of the PowerShell module to load in the task from that should not be met or exceeded. eg -LessThanVersion 2.0.0 will reject anything 2.0.0 or higher, allowing any module in the 1.x.x series. ``` Type: System.String DefaultValue: '' SupportsWildcards: false Aliases: [] ParameterSets: - Name: SharedTask Position: Named IsRequired: false ValueFromPipeline: false ValueFromPipelineByPropertyName: false ValueFromRemainingArguments: false DontShow: false AcceptedValues: [] HelpMessage: '' ``` ### -MaximumVersion[​](#-maximumversion "Direct link to -MaximumVersion") The maximum (inclusive) version of the PowerShell module to load in the task from. ``` Type: System.String DefaultValue: '' SupportsWildcards: false Aliases: [] ParameterSets: - Name: SharedTask Position: Named IsRequired: false ValueFromPipeline: false ValueFromPipelineByPropertyName: false ValueFromRemainingArguments: false DontShow: false AcceptedValues: [] HelpMessage: '' ``` ### -MinimumVersion[​](#-minimumversion "Direct link to -MinimumVersion") The minimum (inclusive) version of the PowerShell module to load in the task from. ``` Type: System.String DefaultValue: '' SupportsWildcards: false Aliases: [] ParameterSets: - Name: SharedTask Position: Named IsRequired: false ValueFromPipeline: false ValueFromPipelineByPropertyName: false ValueFromRemainingArguments: false DontShow: false AcceptedValues: [] HelpMessage: '' ``` ### -Name[​](#-name "Direct link to -Name") The name of the task ``` Type: System.String DefaultValue: '' SupportsWildcards: false Aliases: [] ParameterSets: - Name: (All) Position: 0 IsRequired: true ValueFromPipeline: false ValueFromPipelineByPropertyName: false ValueFromRemainingArguments: false DontShow: false AcceptedValues: [] HelpMessage: '' ``` ### -PostAction[​](#-postaction "Direct link to -PostAction") A scriptblock to be executed after the 'Action' scriptblock. Note: This parameter is ignored if the 'Action' scriptblock is not defined. ``` Type: System.Management.Automation.ScriptBlock DefaultValue: '' SupportsWildcards: false Aliases: [] ParameterSets: - Name: SharedTask Position: Named IsRequired: false ValueFromPipeline: false ValueFromPipelineByPropertyName: false ValueFromRemainingArguments: false - Name: Normal Position: Named IsRequired: false ValueFromPipeline: false ValueFromPipelineByPropertyName: false ValueFromRemainingArguments: false DontShow: false AcceptedValues: [] HelpMessage: '' ``` ### -PostCondition[​](#-postcondition "Direct link to -PostCondition") A scriptblock that is executed to determine if the task completed its job correctly. An exception is thrown if the scriptblock returns $false. ``` Type: System.Management.Automation.ScriptBlock DefaultValue: '{ $true }' SupportsWildcards: false Aliases: [] ParameterSets: - Name: SharedTask Position: Named IsRequired: false ValueFromPipeline: false ValueFromPipelineByPropertyName: false ValueFromRemainingArguments: false - Name: Normal Position: Named IsRequired: false ValueFromPipeline: false ValueFromPipelineByPropertyName: false ValueFromRemainingArguments: false DontShow: false AcceptedValues: [] HelpMessage: '' ``` ### -PreAction[​](#-preaction "Direct link to -PreAction") A scriptblock to be executed before the 'Action' scriptblock. Note: This parameter is ignored if the 'Action' scriptblock is not defined. ``` Type: System.Management.Automation.ScriptBlock DefaultValue: '' SupportsWildcards: false Aliases: [] ParameterSets: - Name: SharedTask Position: Named IsRequired: false ValueFromPipeline: false ValueFromPipelineByPropertyName: false ValueFromRemainingArguments: false - Name: Normal Position: Named IsRequired: false ValueFromPipeline: false ValueFromPipelineByPropertyName: false ValueFromRemainingArguments: false DontShow: false AcceptedValues: [] HelpMessage: '' ``` ### -PreCondition[​](#-precondition "Direct link to -PreCondition") A scriptblock that is executed to determine if the task is executed or skipped. This scriptblock should return $true or $false ``` Type: System.Management.Automation.ScriptBlock DefaultValue: '{ $true }' SupportsWildcards: false Aliases: [] ParameterSets: - Name: SharedTask Position: Named IsRequired: false ValueFromPipeline: false ValueFromPipelineByPropertyName: false ValueFromRemainingArguments: false - Name: Normal Position: Named IsRequired: false ValueFromPipeline: false ValueFromPipelineByPropertyName: false ValueFromRemainingArguments: false DontShow: false AcceptedValues: [] HelpMessage: '' ``` ### -RequiredVariables[​](#-requiredvariables "Direct link to -RequiredVariables") An array of names of variables that must be set to run this task. ``` Type: System.String[] DefaultValue: '@()' SupportsWildcards: false Aliases: [] ParameterSets: - Name: SharedTask Position: Named IsRequired: false ValueFromPipeline: false ValueFromPipelineByPropertyName: false ValueFromRemainingArguments: false - Name: Normal Position: Named IsRequired: false ValueFromPipeline: false ValueFromPipelineByPropertyName: false ValueFromRemainingArguments: false DontShow: false AcceptedValues: [] HelpMessage: '' ``` ### -RequiredVersion[​](#-requiredversion "Direct link to -RequiredVersion") The specific version of a module to load the task from ``` Type: System.String DefaultValue: '' SupportsWildcards: false Aliases: - Version ParameterSets: - Name: SharedTask Position: Named IsRequired: false ValueFromPipeline: false ValueFromPipelineByPropertyName: false ValueFromRemainingArguments: false DontShow: false AcceptedValues: [] HelpMessage: '' ``` ### CommonParameters[​](#commonparameters "Direct link to CommonParameters") This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutBuffer, -OutVariable, -PipelineVariable, -ProgressAction, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about\_CommonParameters](https://go.microsoft.com/fwlink/?LinkID=113216). ## INPUTS[​](#inputs "Direct link to INPUTS") ## OUTPUTS[​](#outputs "Direct link to OUTPUTS") ## NOTES[​](#notes "Direct link to NOTES") ## RELATED LINKS[​](#related-links "Direct link to RELATED LINKS") ## VERSION[​](#version "Direct link to VERSION") *This page was generated using comment-based help in [Psake 5.0.4](https://github.com/psake/psake).* --- # TaskSetup This page was generated Contributions are welcome in [Psake-repo](https://github.com/psake/psake). ## SYNOPSIS[​](#synopsis "Direct link to SYNOPSIS") Adds a scriptblock that will be executed before each task ## SYNTAX[​](#syntax "Direct link to SYNTAX") ### \_\_AllParameterSets[​](#__allparametersets "Direct link to __AllParameterSets") ``` TaskSetup [-Setup] [] ``` ## DESCRIPTION[​](#description "Direct link to DESCRIPTION") Use this for per-task setup that applies across all tasks, such as logging, timing, or environment checks. The scriptblock receives the current \[PsakeTask] as an optional argument. ## EXAMPLES[​](#examples "Direct link to EXAMPLES") ### EXAMPLE 1[​](#example-1 "Direct link to EXAMPLE 1") ``` Task default -Depends Test Task Test -Depends Compile, Clean { } Task Compile -Depends Clean { } Task Clean { } TaskSetup { "Running 'TaskSetup' for task $context.Peek().currentTaskName" } ``` The script above produces the following output: ``` Running 'TaskSetup' for task Clean Executing task, Clean... Running 'TaskSetup' for task Compile Executing task, Compile... Running 'TaskSetup' for task Test Executing task, Test... Build Succeeded ``` ### EXAMPLE 2[​](#example-2 "Direct link to EXAMPLE 2") ``` Task default -Depends Test Task Test -Depends Compile, Clean {} Task Compile -Depends Clean {} Task Clean {} TaskSetup { param($task) "Running 'TaskSetup' for task $($task.Name)" } ``` The script above produces the following output: ``` Running 'TaskSetup' for task Clean Executing task, Clean... Running 'TaskSetup' for task Compile Executing task, Compile... Running 'TaskSetup' for task Test Executing task, Test... Build Succeeded ``` ## PARAMETERS[​](#parameters "Direct link to PARAMETERS") ### -Setup[​](#-setup "Direct link to -Setup") Receives the current task as an optional \[PsakeTask] argument. ``` Type: System.Management.Automation.ScriptBlock DefaultValue: '' SupportsWildcards: false Aliases: [] ParameterSets: - Name: (All) Position: 0 IsRequired: true ValueFromPipeline: false ValueFromPipelineByPropertyName: false ValueFromRemainingArguments: false DontShow: false AcceptedValues: [] HelpMessage: '' ``` ### CommonParameters[​](#commonparameters "Direct link to CommonParameters") This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutBuffer, -OutVariable, -PipelineVariable, -ProgressAction, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about\_CommonParameters](https://go.microsoft.com/fwlink/?LinkID=113216). ## INPUTS[​](#inputs "Direct link to INPUTS") ## OUTPUTS[​](#outputs "Direct link to OUTPUTS") ## NOTES[​](#notes "Direct link to NOTES") ## RELATED LINKS[​](#related-links "Direct link to RELATED LINKS") ## VERSION[​](#version "Direct link to VERSION") *This page was generated using comment-based help in [Psake 5.0.4](https://github.com/psake/psake).* --- # TaskTearDown This page was generated Contributions are welcome in [Psake-repo](https://github.com/psake/psake). ## SYNOPSIS[​](#synopsis "Direct link to SYNOPSIS") Adds a scriptblock to the build that will be executed after each task ## SYNTAX[​](#syntax "Direct link to SYNTAX") ### \_\_AllParameterSets[​](#__allparametersets "Direct link to __AllParameterSets") ``` TaskTearDown [-TearDown] [] ``` ## DESCRIPTION[​](#description "Direct link to DESCRIPTION") Use this for per-task teardown such as logging duration, checking postconditions, or cleaning up temporary state. The completed \[PsakeTask] is passed as an optional argument with success/error info. ## EXAMPLES[​](#examples "Direct link to EXAMPLES") ### EXAMPLE 1[​](#example-1 "Direct link to EXAMPLE 1") ``` Task default -Depends Test Task Test -Depends Compile, Clean { } Task Compile -Depends Clean { } Task Clean { } TaskTearDown { "Running 'TaskTearDown' for task $context.Peek().currentTaskName" } The script above produces the following output: ``` ``` Executing task, Clean... Running 'TaskTearDown' for task Clean Executing task, Compile... Running 'TaskTearDown' for task Compile Executing task, Test... Running 'TaskTearDown' for task Test Build Succeeded ``` ### EXAMPLE 2[​](#example-2 "Direct link to EXAMPLE 2") ``` Task default -Depends Test Task Test -Depends Compile, Clean {} Task Compile -Depends Clean {} Task Clean {} TaskTearDown { param($task) if ($task.Success) { "Running 'TaskTearDown' for task $($task.Name) - success!" } else { "Running 'TaskTearDown' for task $($task.Name) - failed: $($task.ErrorMessage)" } } ``` The script above produces the following output: ``` Executing task, Clean... Running 'TaskTearDown' for task Clean - success! Executing task, Compile... Running 'TaskTearDown' for task Compile - success! Executing task, Test... Running 'TaskTearDown' for task Test - success! Build Succeeded ``` ## PARAMETERS[​](#parameters "Direct link to PARAMETERS") ### -TearDown[​](#-teardown "Direct link to -TearDown") Receives the completed task as an optional \[PsakeTask] argument. ``` Type: System.Management.Automation.ScriptBlock DefaultValue: '' SupportsWildcards: false Aliases: [] ParameterSets: - Name: (All) Position: 0 IsRequired: true ValueFromPipeline: false ValueFromPipelineByPropertyName: false ValueFromRemainingArguments: false DontShow: false AcceptedValues: [] HelpMessage: '' ``` ### CommonParameters[​](#commonparameters "Direct link to CommonParameters") This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutBuffer, -OutVariable, -PipelineVariable, -ProgressAction, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about\_CommonParameters](https://go.microsoft.com/fwlink/?LinkID=113216). ## INPUTS[​](#inputs "Direct link to INPUTS") ## OUTPUTS[​](#outputs "Direct link to OUTPUTS") ## NOTES[​](#notes "Direct link to NOTES") ## RELATED LINKS[​](#related-links "Direct link to RELATED LINKS") ## VERSION[​](#version "Direct link to VERSION") *This page was generated using comment-based help in [Psake 5.0.4](https://github.com/psake/psake).* --- # Test-BuildEnvironment This page was generated Contributions are welcome in [Psake-repo](https://github.com/psake/psake). Since Psake 5.0 This command was introduced in Psake 5.0 and is not available in earlier versions. ## SYNOPSIS[​](#synopsis "Direct link to SYNOPSIS") Tests whether the .NET framework required by a build is available on the current machine. ## SYNTAX[​](#syntax "Direct link to SYNTAX") ### Framework (Default)[​](#framework-default "Direct link to Framework (Default)") ``` Test-BuildEnvironment [[-Framework] ] [] ``` ### BuildFile[​](#buildfile "Direct link to BuildFile") ``` Test-BuildEnvironment -BuildFile [] ``` ## DESCRIPTION[​](#description "Direct link to DESCRIPTION") Resolves the MSBuild and .NET runtime directories for the specified framework version and checks that every required directory exists. Returns $true if the environment is ready, $false otherwise. The framework to check is determined in order of precedence: 1. The -Framework parameter, if supplied. 2. The framework declared in -BuildFile, if supplied. 3. The active psake build context (if one is on the stack). 4. The psake default framework (4.7.2). This is useful in Pester specs to skip tests that require a framework toolchain that is not installed: It 'compiles with MSBuild 4.8' { if (-not (Test-BuildEnvironment -Framework '4.8')) { Set-ItResult -Skipped -Because 'Framework 4.8 not available' } # ... rest of test } It 'uses the project framework' { if (-not (Test-BuildEnvironment -BuildFile './psakefile.ps1')) { Set-ItResult -Skipped -Because 'Required framework not available' } # ... rest of test } ## EXAMPLES[​](#examples "Direct link to EXAMPLES") ### EXAMPLE 1[​](#example-1 "Direct link to EXAMPLE 1") ``` Test-BuildEnvironment -Framework '4.8' ``` Returns $true when MSBuild 17.0 or 16.0 and the v4.0.30319 runtime directory are both present. ### EXAMPLE 2[​](#example-2 "Direct link to EXAMPLE 2") ``` Test-BuildEnvironment -BuildFile './psakefile.ps1' ``` Loads the build file, reads its Framework setting, and returns $true if that framework is installed. ### EXAMPLE 3[​](#example-3 "Direct link to EXAMPLE 3") ``` if (-not (Test-BuildEnvironment)) { Write-Warning "Build environment not ready for current framework" } ``` Tests the framework configured in the active psake context. ## PARAMETERS[​](#parameters "Direct link to PARAMETERS") ### -BuildFile[​](#-buildfile "Direct link to -BuildFile") Path to a psake build script. The framework declared in that file is read and tested. Ignored when -Framework is also supplied. ``` Type: System.String DefaultValue: '' SupportsWildcards: false Aliases: [] ParameterSets: - Name: BuildFile Position: Named IsRequired: true ValueFromPipeline: false ValueFromPipelineByPropertyName: false ValueFromRemainingArguments: false DontShow: false AcceptedValues: [] HelpMessage: '' ``` ### -Framework[​](#-framework "Direct link to -Framework") The .NET framework version string to test (e.g. '4.8', '3.5', '4.7.2x64'). Takes precedence over -BuildFile and the active context. ``` Type: System.String DefaultValue: '' SupportsWildcards: false Aliases: [] ParameterSets: - Name: Framework Position: 0 IsRequired: false ValueFromPipeline: false ValueFromPipelineByPropertyName: false ValueFromRemainingArguments: false DontShow: false AcceptedValues: [] HelpMessage: '' ``` ### CommonParameters[​](#commonparameters "Direct link to CommonParameters") This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutBuffer, -OutVariable, -PipelineVariable, -ProgressAction, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about\_CommonParameters](https://go.microsoft.com/fwlink/?LinkID=113216). ## INPUTS[​](#inputs "Direct link to INPUTS") ## OUTPUTS[​](#outputs "Direct link to OUTPUTS") ### System.Boolean[​](#systemboolean "Direct link to System.Boolean") Returns $true if all required framework directories exist; $false otherwise. ### System.Boolean[​](#systemboolean-1 "Direct link to System.Boolean") ## NOTES[​](#notes "Direct link to NOTES") ## RELATED LINKS[​](#related-links "Direct link to RELATED LINKS") ## VERSION[​](#version "Direct link to VERSION") *This page was generated using comment-based help in [Psake 5.0.4](https://github.com/psake/psake).* --- # Test-PsakeTask This page was generated Contributions are welcome in [Psake-repo](https://github.com/psake/psake). Since Psake 5.0 This command was introduced in Psake 5.0 and is not available in earlier versions. ## SYNOPSIS[​](#synopsis "Direct link to SYNOPSIS") Runs a single task's Action in isolation, without triggering dependencies. ## SYNTAX[​](#syntax "Direct link to SYNTAX") ### \_\_AllParameterSets[​](#__allparametersets "Direct link to __AllParameterSets") ``` Test-PsakeTask [[-BuildFile] ] [-TaskName] [[-Variables] ] [] ``` ## DESCRIPTION[​](#description "Direct link to DESCRIPTION") Compiles the build file, finds the specified task, and executes only its Action scriptblock with the provided variables. Dependencies are NOT executed.This enables unit-testing individual tasks. ## EXAMPLES[​](#examples "Direct link to EXAMPLES") ### EXAMPLE 1[​](#example-1 "Direct link to EXAMPLE 1") ``` $result = Test-PsakeTask -BuildFile './psakefile.ps1' -TaskName 'Build' -Variables @{ Configuration = 'Debug' OutputDir = './test-output' } $result.Success | Should -BeTrue ``` This example runs the 'Build' task from 'psakefile.ps1' with two variables injected into the task's scope. The result object contains details about the execution, including success status, duration, and any error messages. ## PARAMETERS[​](#parameters "Direct link to PARAMETERS") ### -BuildFile[​](#-buildfile "Direct link to -BuildFile") The path to the psake build script. Defaults to 'psakefile.ps1'. ``` Type: System.String DefaultValue: '' SupportsWildcards: false Aliases: [] ParameterSets: - Name: (All) Position: 0 IsRequired: false ValueFromPipeline: false ValueFromPipelineByPropertyName: false ValueFromRemainingArguments: false DontShow: false AcceptedValues: [] HelpMessage: '' ``` ### -TaskName[​](#-taskname "Direct link to -TaskName") The name of the task to execute. ``` Type: System.String DefaultValue: '' SupportsWildcards: false Aliases: [] ParameterSets: - Name: (All) Position: 1 IsRequired: true ValueFromPipeline: false ValueFromPipelineByPropertyName: false ValueFromRemainingArguments: false DontShow: false AcceptedValues: [] HelpMessage: '' ``` ### -Variables[​](#-variables "Direct link to -Variables") A hashtable of variables to inject into the task's execution scope. ``` Type: System.Collections.Hashtable DefaultValue: '@{}' SupportsWildcards: false Aliases: [] ParameterSets: - Name: (All) Position: 2 IsRequired: false ValueFromPipeline: false ValueFromPipelineByPropertyName: false ValueFromRemainingArguments: false DontShow: false AcceptedValues: [] HelpMessage: '' ``` ### CommonParameters[​](#commonparameters "Direct link to CommonParameters") This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutBuffer, -OutVariable, -PipelineVariable, -ProgressAction, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about\_CommonParameters](https://go.microsoft.com/fwlink/?LinkID=113216). ## INPUTS[​](#inputs "Direct link to INPUTS") ## OUTPUTS[​](#outputs "Direct link to OUTPUTS") ## NOTES[​](#notes "Direct link to NOTES") Dependencies, PreAction, and PostAction are skipped — only the task's Action scriptblock is executed. ## RELATED LINKS[​](#related-links "Direct link to RELATED LINKS") ## VERSION[​](#version "Direct link to VERSION") *This page was generated using comment-based help in [Psake 5.0.4](https://github.com/psake/psake).* --- # Version This page was generated Contributions are welcome in [Psake-repo](https://github.com/psake/psake). Since Psake 5.0 This command was introduced in Psake 5.0 and is not available in earlier versions. ## SYNOPSIS[​](#synopsis "Direct link to SYNOPSIS") Declares the required psake version for the build script. ## SYNTAX[​](#syntax "Direct link to SYNTAX") ### \_\_AllParameterSets[​](#__allparametersets "Direct link to __AllParameterSets") ``` Version [-RequiredVersion] [] ``` ## DESCRIPTION[​](#description "Direct link to DESCRIPTION") Use this function at the top of a psake build script to declare which major version of psake the script requires. The compile phase will validate that the running psake version matches. ## EXAMPLES[​](#examples "Direct link to EXAMPLES") ### EXAMPLE 1[​](#example-1 "Direct link to EXAMPLE 1") ``` Version 5 ``` Declares that this build script requires psake v5. ## PARAMETERS[​](#parameters "Direct link to PARAMETERS") ### -RequiredVersion[​](#-requiredversion "Direct link to -RequiredVersion") The major version number required (e.g. 5). ``` Type: System.Int32 DefaultValue: 0 SupportsWildcards: false Aliases: [] ParameterSets: - Name: (All) Position: 0 IsRequired: true ValueFromPipeline: false ValueFromPipelineByPropertyName: false ValueFromRemainingArguments: false DontShow: false AcceptedValues: [] HelpMessage: '' ``` ### CommonParameters[​](#commonparameters "Direct link to CommonParameters") This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutBuffer, -OutVariable, -PipelineVariable, -ProgressAction, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about\_CommonParameters](https://go.microsoft.com/fwlink/?LinkID=113216). ## INPUTS[​](#inputs "Direct link to INPUTS") ## OUTPUTS[​](#outputs "Direct link to OUTPUTS") ## NOTES[​](#notes "Direct link to NOTES") ## RELATED LINKS[​](#related-links "Direct link to RELATED LINKS") ## VERSION[​](#version "Direct link to VERSION") *This page was generated using comment-based help in [Psake 5.0.4](https://github.com/psake/psake).* --- # LLM Tools & Marketplace The [psake-llm-tools](https://github.com/psake/psake-llm-tools) repository is a Claude skill marketplace that brings psake build automation knowledge directly into Claude Code and VS Code Copilot Chat. Install a skill once and Claude will understand your psake build files, suggest tasks, and generate idiomatic psake code. ## Available Skills[​](#available-skills "Direct link to Available Skills") | Skill | Description | | ------------------- | ----------------------------------------------------------------------------- | | **psake** | Task-based build automation for .NET, Node.js, Docker, and more | | **PowerShellBuild** | Standardized build, test, and publish tasks for PowerShell module development | ## Installation[​](#installation "Direct link to Installation") ### Marketplace (recommended)[​](#marketplace-recommended "Direct link to Marketplace (recommended)") Add the psake marketplace in Claude Code, then install whichever skills you need: ``` /plugin marketplace add psake/psake-llm-tools ``` Once the marketplace is added, install individual skills: ``` /plugin install psake@psake-tools /plugin install powershellbuild@psake-tools ``` ### VS Code[​](#vs-code "Direct link to VS Code") If you use VS Code, paste the following link into your browser's address bar to add the marketplace directly: ``` vscode://chat-plugin/install?source=https://github.com/psake/psake-llm-tools/ ``` ### Direct download[​](#direct-download "Direct link to Direct download") Download a `.skill` file from the [releases page](https://github.com/psake/psake-llm-tools/releases) and upload it directly to Claude. Both `.skill` files and marketplace installation are kept in sync. ## psake Skill[​](#psake-skill "Direct link to psake Skill") The **psake** skill teaches Claude the full psake DSL and common build patterns. ### What's Included[​](#whats-included "Direct link to What's Included") * **Core patterns** — task definitions, dependencies, properties, and the `Invoke-psake` API * **Build types** — .NET, Node.js, and Docker build patterns * **PowerShell modules** — integration with the PowerShellBuild module * **Advanced topics** — dynamic tasks, custom logging, and CI/CD integration ### Usage Examples[​](#usage-examples "Direct link to Usage Examples") Once installed, ask Claude to: * "Create a psakefile for my PowerShell module with Pester tests" * "Help me set up a psake build for my .NET solution" * "Generate a psakefile that creates tasks dynamically from a config file" * "Add CI/CD integration to my existing psakefile" ## PowerShellBuild Skill[​](#powershellbuild-skill "Direct link to PowerShellBuild Skill") The **PowerShellBuild** skill covers the [PowerShellBuild](https://github.com/psake/PowerShellBuild) module, which provides a standard set of psake tasks for building, testing, and publishing PowerShell modules. ### What's Included[​](#whats-included-1 "Direct link to What's Included") * **PSBPreference settings** — all configuration properties with defaults and descriptions * **Task reference** — every built-in task and when to use it * **Complete project examples** — from bare module to fully configured build * **CI/CD patterns** — Azure Pipelines, GitHub Actions, and more ### Usage Examples[​](#usage-examples-1 "Direct link to Usage Examples") Once installed, ask Claude to: * "Set up PowerShellBuild for my PowerShell module" * "Configure code coverage thresholds in my PowerShellBuild project" * "Help me publish my module to PSGallery using PowerShellBuild" * "Generate a psakeFile.ps1 using PowerShellBuild tasks" ## Further Reading[​](#further-reading "Direct link to Further Reading") * [psake-llm-tools on GitHub](https://github.com/psake/psake-llm-tools) * [VS Code Extension](/docs/integrations/vscode-extension.md) * [psake Agent Skill for AI-assisted builds](/blog/introducing-psake-agent-skill.md) --- # VS Code Extension The [psake VS Code extension](https://github.com/psake/psake-vscode) integrates psake's build system directly into Visual Studio Code. It automatically discovers your tasks, provides a dedicated explorer panel, and lets you run builds without leaving the editor. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * [Visual Studio Code](https://code.visualstudio.com/) v1.90 or later * [PowerShell extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode.PowerShell) for VS Code ## Installation[​](#installation "Direct link to Installation") Install the extension from the [VS Code Marketplace](https://marketplace.visualstudio.com/publishers/psake), or search for **psake** in the Extensions panel (`Ctrl+Shift+X`). The extension activates automatically when your workspace contains a `psakefile.ps1`, `psake.ps1`, or `build.ps1`. ## Features[​](#features "Direct link to Features") ### Task Provider[​](#task-provider "Direct link to Task Provider") The extension scans your `psakefile.ps1` and registers all tasks with VS Code's task system. Your default task is mapped to the Build group, so pressing `Ctrl+Shift+B` runs it immediately. Tasks refresh automatically when you save your build file. You can also reference psake tasks in `.vscode/tasks.json`: ``` { "version": "2.0.0", "tasks": [ { "type": "psake", "task": "Build" } ] } ``` ### psake Tasks Explorer[​](#psake-tasks-explorer "Direct link to psake Tasks Explorer") A sidebar panel in the Explorer view lists all discovered tasks with their descriptions and dependencies. From here you can: * **Run** any task with a single click * **Navigate** to a task's definition in your build file * **View** task dependencies at a glance ### CodeLens[​](#codelens "Direct link to CodeLens") Each `Task` declaration in your build file displays a **Run Task** action inline. Click it to execute the task directly from the editor. To disable CodeLens, set `psake.codeLens.enabled` to `false`. ### Build Script Detection[​](#build-script-detection "Direct link to Build Script Detection") If your project includes a wrapper script such as `build.ps1`, the extension routes execution through it automatically. This ensures bootstrapping, dependency installation, and other setup steps run correctly. Set `psake.buildScript` to `"none"` to disable wrapper detection and call `Invoke-psake` directly. ### Task Sync & IntelliSense[​](#task-sync--intellisense "Direct link to Task Sync & IntelliSense") * **Sync command**: Run `psake: Sync Tasks to tasks.json` to add all discovered tasks to your workspace configuration while preserving existing customizations. * **IntelliSense**: The `"task"` property in `.vscode/tasks.json` provides autocomplete for psake task names when using the `psake` type. ### Snippets[​](#snippets "Direct link to Snippets") Nine built-in snippets help you scaffold build files quickly: | Prefix | Description | | ---------------------- | ------------------------------------ | | `psakeTask` | Basic task definition | | `psakeTaskFull` | Task with all options | | `psakeTaskDependsOnly` | Task that only declares dependencies | | `psakeProperties` | Properties block | | `psakeInclude` | Include statement | | `psakeFramework` | Framework block | | `psakeFormatTaskName` | FormatTaskName block | | `psakeTaskSetup` | TaskSetup block | | `psakeTaskTearDown` | TaskTearDown block | ### Scaffold Command[​](#scaffold-command "Direct link to Scaffold Command") Run `psake: Install sample build file` to generate a starter `psakefile.ps1` with example tasks in your workspace. ## Configuration[​](#configuration "Direct link to Configuration") All settings are under the `psake` namespace and update dynamically without restarting VS Code. | Setting | Default | Description | | -------------------------------- | ------------------ | ------------------------------------------------------- | | `psake.buildFile` | `psakefile.ps1` | Default build file name | | `psake.taskProvider.enabled` | `true` | Enable automatic task detection | | `psake.codeLens.enabled` | `true` | Show Run Task CodeLens above task declarations | | `psake.buildScript` | `""` (auto-detect) | Path to wrapper build script, or `"none"` to disable | | `psake.buildScriptTaskParameter` | `Task` | Parameter name the build script uses for task selection | | `psake.buildScriptParameters` | `""` | Additional parameters passed to the build script | | `psake.invokeParameters` | `""` | Additional parameters passed to `Invoke-psake` | | `psake.powershellExecutable` | `""` (auto-detect) | Path to PowerShell executable | | `psake.shellArgs` | `["-NoProfile"]` | Arguments passed to the PowerShell executable | ## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") **Tasks not appearing?** * Verify your build file is named `psakefile.ps1` (or matches your `psake.buildFile` setting) * Check that `psake.taskProvider.enabled` is `true` * Run `psake: Refresh Tasks` from the Command Palette **Build script not detected?** * The extension looks for `build.ps1` in the workspace root by default * Set `psake.buildScript` explicitly if your wrapper has a different name or location **Using a non-standard PowerShell?** * Set `psake.powershellExecutable` to the full path of your PowerShell binary ## Further Reading[​](#further-reading "Direct link to Further Reading") * [psake VS Code extension on GitHub](https://github.com/psake/psake-vscode) * [Debugging psake in VS Code](/docs/troubleshooting/debugging-guide.md) * [psake Agent Skill for AI-assisted builds](/blog/introducing-psake-agent-skill.md) --- # Quick Start *psake* is a build automation tool written in PowerShell. It avoids the angle-bracket tax associated with executable XML by leveraging the PowerShell syntax in your build scripts. psake has a syntax inspired by rake (aka make in Ruby) and bake (aka make in Boo), but is easier to script because it leverages your existent command-line knowledge. psake is pronounced sake - as in Japanese rice wine. It does NOT rhyme with make, bake, or rake. ## Installing psake[​](#installing-psake "Direct link to Installing psake") ### Via PowerShell[​](#via-powershell "Direct link to Via PowerShell") To install psake use the built-in PowerShell package manager. Launch PowerShell and run the following command. ``` # PowerShellGet Install-Module psake # PSResourceGet Install-PSResource psake ``` ### Via Chocolatey[​](#via-chocolatey "Direct link to Via Chocolatey") ``` choco install psake ``` ### Via NuGet[​](#via-nuget "Direct link to Via NuGet") You can download through the various mechanisms available for NuGet [psake - nuget.org](https://www.nuget.org/packages/psake/) ### Via GitHub[​](#via-github "Direct link to Via GitHub") You can also download ZIP files of the project "binaries" from GitHub by selecting the appropriate [release](https://github.com/psake/psake/releases). ## What can psake be used for?[​](#what-can-psake-be-used-for "Direct link to What can psake be used for?") You can use **psake** for many things, building software, deploying software, back-end processing, etc... psake is written in PowerShell, so you have access to the entire .NET framework. ## How does psake work?[​](#how-does-psake-work "Direct link to How does psake work?") **psake** is a domain specific language to create builds using a dependency pattern just like Ant, NAnt, Rake or MSBuild. You create a build script using PowerShell that consists of `Tasks` which are simply function calls. Each `Task` function can define dependencies on other `Task` functions. In the example script below, Task `Compile` depends on Tasks `Clean` and `Init`, which means that before Task `Compile` can execute, both tasks `Clean` and `Init` have to execute. psake ensures that this is done. ``` Task Compile -Depends Init,Clean { "compile" } Task Clean -Depends Init { "clean" } Task Init { "init" } ``` psake reads in your build script and executes the `Task` functions that are defined within it and enforces the dependencies between tasks. The great thing about psake is that it is written in PowerShell and that means you have the power of .NET and all the features of PowerShell at your disposal within your build script. Not to mention that you don't have to pay the *XML* bracket tax anymore. ## Who is using psake?[​](#who-is-using-psake "Direct link to Who is using psake?") The following is a list (at the time of writing) of projects who are using psake to orchestrate their build process. * [ChocolateyGUI](https://github.com/chocolatey/ChocolateyGUI) * [BoxStarter](https://github.com/mwrock/boxstarter) * [ravendb](https://github.com/ravendb/ravendb) * [Hangfire](https://github.com/HangfireIO/Hangfire) * [Json.Net](https://github.com/JamesNK/Newtonsoft.Json) * [Lucene.NET](https://github.com/apache/lucenenet) * [Spatial4n](https://github.com/Spatial4n/Spatial4n) * [J2N](https://github.com/NightOwl888/J2N) * [ICU4N](https://github.com/NightOwl888/ICU4N) * [RandomizedTesting](https://github.com/NightOwl888/RandomizedTesting) ## Next Steps[​](#next-steps "Direct link to Next Steps") Ready to start using psake? Here's what to do next: 1. **[Install psake](/docs/tutorial-basics/installing.md)** - Get psake set up on your system 2. **[Run your first build](/docs/tutorial-basics/run-psake.md)** - Create and execute your first psake script 3. **[Explore the Guides](/docs/tutorial-basics/default-build-files.md)** - Learn best practices and advanced techniques 4. **[Check the Command Reference](/docs/commands/Invoke-Psake.md)** - Look up specific commands and their parameters Need help? Visit our [Troubleshooting](/docs/troubleshooting/common-errors.md) section or join the [PowerShell Discord](https://aka.ms/psdiscord) #psake channel. --- # Configuration Reference PowerShellBuild exposes all of its settings through a single hashtable called `$PSBPreference`. Override values in the `properties` block of your `psakeFile.ps1`. Task dependency chains can also be customized via separate variables set outside the `properties` block. ## Setting Preferences[​](#setting-preferences "Direct link to Setting Preferences") psakeFile.ps1 ``` properties { # Override any $PSBPreference keys here $PSBPreference.General.ModuleName = 'MyModule' $PSBPreference.Build.OutDir = "$PSScriptRoot/build" $PSBPreference.Test.ScriptAnalysis.Enabled = $true $PSBPreference.Test.CodeCoverage.Enabled = $true $PSBPreference.Test.CodeCoverage.Threshold = 0.80 $PSBPreference.Publish.PSRepositoryApiKey = $env:PSGALLERY_API_KEY } task default -depends Build task Build -FromModule PowerShellBuild -Version '0.7.1' ``` Values not overridden retain their defaults as defined in PowerShellBuild's `build.properties.ps1`. *** ## General[​](#general "Direct link to General") | Setting | Default | Description | | ---------------------------- | --------------------- | ---------------------------------------- | | `General.ProjectRoot` | (auto-detected) | Root directory of the project | | `General.SrcRootDir` | `$ProjectRoot/src` | Directory containing module source files | | `General.ModuleName` | (basename of `.psd1`) | Name of the module | | `General.ModuleVersion` | (from manifest) | Module version number | | `General.ModuleManifestPath` | (auto-detected) | Path to the `.psd1` manifest | *** ## Build[​](#build "Direct link to Build") | Setting | Default | Description | | -------------------------- | ------------------------------------- | --------------------------------------------------------------------- | | `Build.OutDir` | `$ProjectRoot/build` | Root output directory | | `Build.ModuleOutDir` | `$OutDir/$ModuleName` | Module-specific output subdirectory (computed) | | `Build.CompileModule` | `$false` | If `$true`, concatenate all `.ps1` source files into a single `.psm1` | | `Build.CompileDirectories` | `'Enum','Classes','Private','Public'` | Directories whose `.ps1` files are merged when compiling | | `Build.CopyDirectories` | `@()` | Directories copied as-is (not compiled) to the output | | `Build.Exclude` | `@()` | File patterns excluded from the output | ### Module Compilation[​](#module-compilation "Direct link to Module Compilation") Setting `CompileModule = $true` merges all `.ps1` files from `CompileDirectories` into the root `.psm1`. This can improve module load time and is common for published modules. ``` properties { $PSBPreference.Build.CompileModule = $true $PSBPreference.Build.CompileDirectories = @('Enum', 'Classes', 'Private', 'Public') $PSBPreference.Build.CopyDirectories = @('data', 'lib') } ``` *** ## Test[​](#test "Direct link to Test") | Setting | Default | Description | | ----------------------------- | -------------------- | --------------------------------------------------- | | `Test.Enabled` | `$true` | Enable or disable Pester tests | | `Test.RootDir` | `$ProjectRoot/tests` | Directory containing Pester test files | | `Test.OutputFile` | `$null` | Path to write NUnitXml test results (useful for CI) | | `Test.OutputFormat` | `'NUnitXml'` | Test result format | | `Test.ImportModule` | `$false` | Import the built module before running tests | | `Test.OutputVerbosity` | `'Detailed'` | Pester output verbosity level | | `Test.SkipRemainingOnFailure` | `'None'` | Skip strategy after a test failure | ### Script Analysis[​](#script-analysis "Direct link to Script Analysis") | Setting | Default | Description | | ---------------------------------------------- | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Test.ScriptAnalysis.Enabled` | `$true` | Run PSScriptAnalyzer | | `Test.ScriptAnalysis.FailBuildOnSeverityLevel` | `'Error'` | Build fails on violations at or above this severity (`'Error'`, `'Warning'`, `'Information'`) | | `Test.ScriptAnalysis.SettingsPath` | `./PSScriptAnalyzerSettings.psd1` | Path to a PSScriptAnalyzer settings file. Defaults to `PSScriptAnalyzerSettings.psd1` in the project root — if the file exists it is used automatically | ``` properties { # Fail on warnings too $PSBPreference.Test.ScriptAnalysis.FailBuildOnSeverityLevel = 'Warning' # Override the default settings file location $PSBPreference.Test.ScriptAnalysis.SettingsPath = "$PSScriptRoot/build/analyzersettings.psd1" } ``` ### Code Coverage[​](#code-coverage "Direct link to Code Coverage") | Setting | Default | Description | | ------------------------------------ | --------------------- | -------------------------------------------------------- | | `Test.CodeCoverage.Enabled` | `$false` | Enable code coverage reporting | | `Test.CodeCoverage.Threshold` | `0.75` | Minimum coverage ratio (0.0–1.0); build fails below this | | `Test.CodeCoverage.Files` | (staged module files) | Files included in coverage analysis | | `Test.CodeCoverage.OutputFile` | `$null` | Path to write the coverage report | | `Test.CodeCoverage.OutputFileFormat` | `'JaCoCo'` | Coverage report format | ``` properties { $PSBPreference.Test.CodeCoverage.Enabled = $true $PSBPreference.Test.CodeCoverage.Threshold = 0.80 $PSBPreference.Test.CodeCoverage.OutputFile = "$PSScriptRoot/coverage.xml" $PSBPreference.Test.CodeCoverage.OutputFileFormat = 'JaCoCo' } ``` *** ## Help[​](#help "Direct link to Help") | Setting | Default | Description | | ------------------------------- | --------- | ------------------------------------------------------------ | | `Help.DefaultLocale` | `'en-US'` | Locale used when generating MAML and updatable help | | `Help.UpdatableHelpOutDir` | `$null` | Directory where updatable help `.cab` files are written | | `Help.ConvertReadMeToAboutHelp` | `$false` | Convert `README.md` to an `about_.help.txt` file | *** ## Docs[​](#docs "Direct link to Docs") | Setting | Default | Description | | ---------------------------- | ------------------- | ----------------------------------------------------- | | `Docs.RootDir` | `$ProjectRoot/docs` | Directory for PlatyPS markdown source files | | `Docs.Overwrite` | `$false` | Overwrite existing markdown files when regenerating | | `Docs.AlphabeticParamsOrder` | `$false` | Sort parameters alphabetically in generated markdown | | `Docs.ExcludeDontShow` | `$false` | Exclude parameters marked `[DontShow]` | | `Docs.UseFullTypeName` | `$false` | Use fully-qualified type names instead of short names | *** ## Publish[​](#publish "Direct link to Publish") | Setting | Default | Description | | -------------------------------- | ------------- | ----------------------------------------------------- | | `Publish.PSRepository` | `'PSGallery'` | Name of the PowerShell repository to publish to | | `Publish.PSRepositoryApiKey` | `$null` | API key for authenticating with the repository | | `Publish.PSRepositoryCredential` | `$null` | `PSCredential` for authenticating with the repository | ``` properties { $PSBPreference.Publish.PSRepository = 'PSGallery' $PSBPreference.Publish.PSRepositoryApiKey = $env:PSGALLERY_API_KEY } ``` *** ## Sign[​](#sign "Direct link to Sign") | Setting | Default | Description | | -------------------------------- | ------------------------------ | --------------------------------------------------------------------------------------------------------- | | `Sign.Enabled` | `$false` | Enable Authenticode signing | | `Sign.CertificateSource` | `'Auto'` | How to resolve the signing certificate (`'Auto'`, `'CertStore'`, `'Thumbprint'`, `'EnvVar'`, `'PfxFile'`) | | `Sign.CertStoreLocation` | `$null` | Certificate store path (e.g., `'Cert:\CurrentUser\My'`) | | `Sign.Thumbprint` | `$null` | Specific certificate thumbprint | | `Sign.CertificateEnvVar` | `'SIGNCERTIFICATE'` | Environment variable containing a Base64-encoded PFX | | `Sign.CertificatePasswordEnvVar` | `$null` | Environment variable containing the PFX password | | `Sign.PfxFilePath` | `$null` | Path to a PFX/P12 file | | `Sign.PfxFilePassword` | `$null` | Password for the PFX file | | `Sign.SkipCertificateValidation` | `$false` | Skip certificate validity checks (not recommended for production) | | `Sign.TimestampServer` | `$null` | RFC 3161 timestamp server URI | | `Sign.HashAlgorithm` | `'SHA256'` | Authenticode hash algorithm | | `Sign.FilesToSign` | `@('*.psd1','*.psm1','*.ps1')` | File patterns to sign in the output directory | ### Catalog[​](#catalog "Direct link to Catalog") | Setting | Default | Description | | ----------------------- | ------------- | ------------------------------------------- | | `Sign.Catalog.Enabled` | `$false` | Create a Windows catalog (`.cat`) file | | `Sign.Catalog.Version` | `2` | Catalog hash version | | `Sign.Catalog.FileName` | (module name) | Catalog filename (without `.cat` extension) | ``` properties { $PSBPreference.Sign.Enabled = $true $PSBPreference.Sign.CertificateSource = 'EnvVar' $PSBPreference.Sign.TimestampServer = 'http://timestamp.digicert.com' $PSBPreference.Sign.Catalog.Enabled = $true } ``` *** ## Task Dependency Variables[​](#task-dependency-variables "Direct link to Task Dependency Variables") To change which tasks a given task depends on, set these variables **outside** the `properties` block, before any PowerShellBuild task references: | Variable | Controls dependencies of | Default | | ------------------------------------- | ------------------------ | ------------------------------------ | | `$PSBCleanDependency` | `Clean` | `'Init'` | | `$PSBStageFilesDependency` | `StageFiles` | `'Clean'` | | `$PSBBuildHelpDependency` | `BuildHelp` | `'GenerateMarkdown', 'GenerateMAML'` | | `$PSBGenerateMarkdownDependency` | `GenerateMarkdown` | `'StageFiles'` | | `$PSBGenerateMAMLDependency` | `GenerateMAML` | `'GenerateMarkdown'` | | `$PSBGenerateUpdatableHelpDependency` | `GenerateUpdatableHelp` | `'BuildHelp'` | | `$PSBBuildDependency` | `Build` | `'StageFiles', 'BuildHelp'` | | `$PSBAnalyzeDependency` | `Analyze` | `'Build'` | | `$PSBPesterDependency` | `Pester` | `'Build'` | | `$PSBTestDependency` | `Test` | `'Pester', 'Analyze'` | | `$PSBPublishDependency` | `Publish` | `'Test'` | | `$PSBSignModuleDependency` | `SignModule` | `'Build'` | | `$PSBBuildCatalogDependency` | `BuildCatalog` | `'SignModule'` | | `$PSBSignCatalogDependency` | `SignCatalog` | `'BuildCatalog'` | | `$PSBSignDependency` | `Sign` | `'SignCatalog'` | ### Example: Remove Analyze from the Test pipeline[​](#example-remove-analyze-from-the-test-pipeline "Direct link to Example: Remove Analyze from the Test pipeline") psakeFile.ps1 ``` # Set outside properties block $PSBTestDependency = 'Pester' # Test only depends on Pester, not Analyze properties { $PSBPreference.Test.ScriptAnalysis.Enabled = $false } task default -depends Test task Test -FromModule PowerShellBuild -Version '0.7.1' ``` ## See Also[​](#see-also "Direct link to See Also") * [Task Reference](/docs/powershellbuild/task-reference.md) — What each task does * [Getting Started](/docs/powershellbuild/getting-started.md) — Minimal project setup * [Real-World Example](/docs/powershellbuild/real-world-example.md) — Configuration in context --- # Getting Started with PowerShellBuild This guide walks through adding PowerShellBuild to a PowerShell module project from scratch. By the end you will have a working build pipeline that cleans, stages, analyzes, tests, and packages your module. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * PowerShell 5.1 or PowerShell 7+ * psake >= 5.0.4 * PowerShellBuild >= 0.8.2 These versions match PowerShellBuild 0.8.2. Check the [current module manifest](https://github.com/psake/PowerShellBuild/blob/main/PowerShellBuild/PowerShellBuild.psd1) when selecting a newer release. Install both modules from the PowerShell Gallery: ``` Install-Module -Name psake -Repository PSGallery Install-Module -Name PowerShellBuild -Repository PSGallery ``` ## Expected Directory Structure[​](#expected-directory-structure "Direct link to Expected Directory Structure") PowerShellBuild expects a conventional PowerShell module layout. The defaults work with the following structure (all paths are configurable — see [Configuration](/docs/powershellbuild/configuration.md)): ``` MyModule/ ├── src/ │ ├── MyModule.psd1 # Module manifest │ ├── MyModule.psm1 # Root module file │ ├── Public/ # Exported functions │ │ └── Get-Thing.ps1 │ └── Private/ # Internal functions │ └── Invoke-Helper.ps1 ├── tests/ │ └── MyModule.Tests.ps1 # Pester tests ├── docs/ # PlatyPS markdown (generated) ├── build/ # Output directory (generated) ├── psakeFile.ps1 # Your build task file ├── build.ps1 # Build bootstrap script └── requirements.psd1 # Module dependencies ``` ## Minimal Setup[​](#minimal-setup "Direct link to Minimal Setup") ### 1. Create `psakeFile.ps1`[​](#1-create-psakefileps1 "Direct link to 1-create-psakefileps1") The simplest possible build file just references the `Build` task from PowerShellBuild: psakeFile.ps1 ``` properties { $PSBPreference.Build.OutDir = "$PSScriptRoot/build" $PSBPreference.General.SrcRootDir = "$PSScriptRoot/src" } task default -depends Build task Build -FromModule PowerShellBuild -RequiredVersion '0.8.2' ``` That is all you need. psake finds PowerShellBuild, dot-sources its module-root `psakeFile.ps1`, and registers the provider tasks in the current build context. Running `Build` then executes its reachable dependency chain: ``` Init → Clean → StageFiles → GenerateMarkdown → GenerateMAML → BuildHelp → Build ``` ### 2. Create `build.ps1`[​](#2-create-buildps1 "Direct link to 2-create-buildps1") A bootstrap script that installs dependencies and invokes psake: build.ps1 ``` [CmdletBinding()] param( [string[]]$Task = 'default', [switch]$Bootstrap ) if ($Bootstrap) { Get-PackageProvider -Name NuGet -ForceBootstrap | Out-Null Set-PSRepository -Name PSGallery -InstallationPolicy Trusted Install-Module -Name psake, PowerShellBuild -Scope CurrentUser -Force } Import-Module psake Invoke-psake -buildFile "$PSScriptRoot/psakeFile.ps1" -taskList $Task -nologo exit ([int](-not $psake.build_success)) ``` ### 3. Run the Build[​](#3-run-the-build "Direct link to 3. Run the Build") ``` # First-time setup .\build.ps1 -Bootstrap # Build the module .\build.ps1 # Run tests only .\build.ps1 -Task Test # Publish to PSGallery .\build.ps1 -Task Publish ``` ## What Each Task Does[​](#what-each-task-does "Direct link to What Each Task Does") When you run `.\build.ps1` (which runs the `default` task → `Build`), here is what happens: | Step | Task | What it does | | ---- | ------------------ | ------------------------------------------------------------ | | 1 | `Init` | Calls `Initialize-PSBuild` to set up build variables | | 2 | `Clean` | Removes the output directory | | 3 | `StageFiles` | Copies (or compiles) module source into the output directory | | 4 | `GenerateMarkdown` | Generates PlatyPS markdown from module help comments | | 5 | `GenerateMAML` | Converts PlatyPS markdown to MAML help XML | | 6 | `BuildHelp` | Meta-task — signals that help generation is complete | | 7 | `Build` | Meta-task — signals that the build is complete | Running `.\build.ps1 -Task Test` additionally runs: | Step | Task | What it does | | ---- | --------- | ------------------------------------------------ | | 8 | `Analyze` | Runs PSScriptAnalyzer against the staged module | | 9 | `Pester` | Runs Pester tests, optionally with code coverage | | 10 | `Test` | Meta-task — signals that all tests passed | ## Using with Invoke-Build[​](#using-with-invoke-build "Direct link to Using with Invoke-Build") If you prefer Invoke-Build over psake, PowerShellBuild supports it via a dot-sourced task file: .build.ps1 ``` Import-Module PowerShellBuild # Dot-source the Invoke-Build task definitions . PowerShellBuild.IB.Tasks # Override preferences after import $PSBPreference.Build.OutDir = "$PSScriptRoot/build" $PSBPreference.General.SrcRootDir = "$PSScriptRoot/src" $PSBPreference.Test.ScriptAnalysis.Enabled = $true ``` Run it with: ``` Invoke-Build Build Invoke-Build Test ``` ## Next Steps[​](#next-steps "Direct link to Next Steps") * [Task Reference](/docs/powershellbuild/task-reference.md) — Full list of tasks and their dependencies * [Creating Shared Task Modules](/docs/tutorial-advanced/creating-shared-task-modules.md) — Build your own reusable task provider * [Configuration](/docs/powershellbuild/configuration.md) — Customize `$PSBPreference` to fit your project * [Real-World Example](/docs/powershellbuild/real-world-example.md) — A complete project with CI/CD integration --- # Introduction to PowerShellBuild [PowerShellBuild](https://github.com/psake/PowerShellBuild) is the official companion module to psake. Where psake is a general-purpose build automation engine, PowerShellBuild is a curated library of pre-built psake tasks that handle every step of a PowerShell module's lifecycle — building, testing, analysis, help generation, signing, and publishing — so you don't have to write that scaffolding from scratch. ## The Problem It Solves[​](#the-problem-it-solves "Direct link to The Problem It Solves") Every PowerShell module project needs roughly the same build pipeline: 1. Clean the output directory 2. Stage (copy/compile) module files to the output 3. Run PSScriptAnalyzer for code quality 4. Run Pester tests 5. Generate MAML help from PlatyPS markdown 6. Publish to the PowerShell Gallery Writing this pipeline from scratch in every project is repetitive. If you discover a better pattern (say, a smarter way to compute code coverage), you'd have to update every project manually. PowerShellBuild solves this by packaging the pipeline as a versioned, distributable module — update the module version and all your projects get the improvement. ## How It Relates to psake[​](#how-it-relates-to-psake "Direct link to How It Relates to psake") PowerShellBuild lives under the same GitHub organization as psake (`psake/PowerShellBuild`) and is built on a feature introduced in **psake v4.8.0**: the ability to reference shared tasks distributed inside a PowerShell module. ``` ┌─────────────────────────────────────┐ │ Your project's psakeFile.ps1 │ │ │ │ Task Build -FromModule │ │ PowerShellBuild │ │ -RequiredVersion 0.8.2 │ └────────────────┬────────────────────┘ │ psake discovers task definitions ┌─────────────────────────────────────┐ │ PowerShellBuild module │ │ (Init → Clean → StageFiles → │ │ BuildHelp → Build → ...) │ └─────────────────────────────────────┘ ``` When psake encounters a task declared with `-FromModule`, it finds the selected module and dot-sources the module-root `psakeFile.ps1` in the current build context. That file registers all its provider tasks; psake later runs only `Build` and its reachable dependencies. This process does not itself import the module's `.psm1`. ## Also Supports Invoke-Build[​](#also-supports-invoke-build "Direct link to Also Supports Invoke-Build") PowerShellBuild is not limited to psake. It ships Invoke-Build task files as well, accessible through the `PowerShellBuild.IB.Tasks` PowerShell alias. See the [PowerShellBuild README](https://github.com/psake/PowerShellBuild/blob/main/README.md) for Invoke-Build usage. The rest of this section focuses on the psake integration. ## When to Use PowerShellBuild[​](#when-to-use-powershellbuild "Direct link to When to Use PowerShellBuild") **Use PowerShellBuild when:** * You are building a PowerShell module (`.psd1` / `.psm1`) and want a standardized pipeline * You want to avoid writing boilerplate psake tasks that every PS module project needs * You want your build pipeline versioned and shareable across multiple module projects **Write custom psake tasks instead when:** * You are building something other than a PowerShell module (a .NET solution, a Node.js app, a Docker image, etc.) * You need a workflow that significantly deviates from the standard module structure * You are using psake purely for orchestration of external tools ## Installation[​](#installation "Direct link to Installation") Install PowerShellBuild from the PowerShell Gallery: ``` Install-Module -Name PowerShellBuild -Repository PSGallery ``` PowerShellBuild 0.8.2 requires psake 5.0.4 or later. Check the [current module manifest](https://github.com/psake/PowerShellBuild/blob/main/PowerShellBuild/PowerShellBuild.psd1) when selecting a newer version: ``` Install-Module -Name psake -Repository PSGallery ``` Or declare both as dependencies in a `requirements.psd1` (managed via [PSDepend](https://github.com/RamblingCookieMonster/PSDepend)): ``` @{ psake = 'latest' PowerShellBuild = 'latest' } ``` ## See Also[​](#see-also "Direct link to See Also") * [Getting Started with PowerShellBuild](/docs/powershellbuild/getting-started.md) — Set up your first project * [Creating Shared Task Modules](/docs/tutorial-advanced/creating-shared-task-modules.md) — Build your own reusable task provider * [Task Reference](/docs/powershellbuild/task-reference.md) — All available tasks and their dependencies * [Configuration Reference](/docs/powershellbuild/configuration.md) — `$PSBPreference` settings * [Real-World Example](/docs/powershellbuild/real-world-example.md) — Complete project walkthrough * [psake GitHub](https://github.com/psake/psake) — psake source and issues * [PowerShellBuild GitHub](https://github.com/psake/PowerShellBuild) — PowerShellBuild source --- # Real-World Example This page shows a production-ready PowerShell module project that uses PowerShellBuild for its build pipeline. It covers the full lifecycle: local development, automated testing, and publishing to the PowerShell Gallery via GitHub Actions. ## Project Structure[​](#project-structure "Direct link to Project Structure") ``` MyModule/ ├── src/ │ ├── MyModule.psd1 │ ├── MyModule.psm1 │ ├── Public/ │ │ ├── Get-Widget.ps1 │ │ └── New-Widget.ps1 │ └── Private/ │ └── Invoke-WidgetHelper.ps1 ├── tests/ │ ├── Get-Widget.Tests.ps1 │ └── New-Widget.Tests.ps1 ├── docs/ # PlatyPS markdown (auto-generated by GenerateMarkdown) ├── .github/ │ └── workflows/ │ └── build.yml ├── psakeFile.ps1 ├── build.ps1 ├── PSScriptAnalyzerSettings.psd1 # Auto-detected by PowerShellBuild └── requirements.psd1 ``` ## `requirements.psd1`[​](#requirementspsd1 "Direct link to requirementspsd1") Declare all PowerShell module dependencies. [PSDepend](https://github.com/RamblingCookieMonster/PSDepend) installs these during bootstrap. requirements.psd1 ``` @{ psake = 'latest' PowerShellBuild = 'latest' Pester = 'latest' PSScriptAnalyzer = 'latest' platyPS = '0.14.2' } ``` ## `build.ps1`[​](#buildps1 "Direct link to buildps1") The bootstrap entry point that handles dependency installation and task dispatch. build.ps1 ``` #Requires -Version 5.1 [CmdletBinding()] param( [string[]]$Task = 'default', [switch]$Bootstrap, [hashtable]$Properties = @{} ) Set-StrictMode -Version Latest if ($Bootstrap) { Write-Host 'Bootstrapping build dependencies...' -ForegroundColor Cyan Get-PackageProvider -Name NuGet -ForceBootstrap | Out-Null Set-PSRepository -Name PSGallery -InstallationPolicy Trusted if (-not (Get-Module -Name PSDepend -ListAvailable)) { Install-Module -Name PSDepend -Scope CurrentUser -Force } Import-Module PSDepend Invoke-PSDepend -Path "$PSScriptRoot/requirements.psd1" -Install -Import -Force Write-Host 'Bootstrap complete.' -ForegroundColor Green } Import-Module psake -ErrorAction Stop Invoke-psake ` -buildFile "$PSScriptRoot/psakeFile.ps1" ` -taskList $Task ` -properties $Properties ` -nologo exit ([int](-not $psake.build_success)) ``` ## `psakeFile.ps1`[​](#psakefileps1 "Direct link to psakefileps1") A realistic build file with customized preferences, custom tasks alongside PowerShellBuild tasks, and a `Deploy` task that wraps the release workflow. psakeFile.ps1 ``` #Requires -Version 5.1 # --- Task dependency overrides (must be set before referencing PowerShellBuild tasks) --- # Publish only requires a successful build, not the full test suite in this example. # Tests are enforced in CI; the Publish task is only invoked from GitHub Actions. $PSBPublishDependency = 'Build' # --- properties { # General $PSBPreference.General.SrcRootDir = "$PSScriptRoot/src" $PSBPreference.General.ModuleName = 'MyModule' # Build $PSBPreference.Build.OutDir = "$PSScriptRoot/build" $PSBPreference.Build.CompileModule = $true $PSBPreference.Build.CompileDirectories = @('Enum', 'Classes', 'Private', 'Public') # Test — Pester $PSBPreference.Test.Enabled = $true $PSBPreference.Test.RootDir = "$PSScriptRoot/tests" $PSBPreference.Test.OutputFile = "$PSScriptRoot/build/TestResults.xml" $PSBPreference.Test.OutputFormat = 'NUnitXml' $PSBPreference.Test.ImportModule = $true # Test — PSScriptAnalyzer # SettingsPath defaults to ./PSScriptAnalyzerSettings.psd1 in the project root $PSBPreference.Test.ScriptAnalysis.Enabled = $true $PSBPreference.Test.ScriptAnalysis.FailBuildOnSeverityLevel = 'Error' # Test — Code Coverage $PSBPreference.Test.CodeCoverage.Enabled = $true $PSBPreference.Test.CodeCoverage.Threshold = 0.80 $PSBPreference.Test.CodeCoverage.OutputFile = "$PSScriptRoot/build/coverage.xml" $PSBPreference.Test.CodeCoverage.OutputFileFormat = 'JaCoCo' # Help $PSBPreference.Help.DefaultLocale = 'en-US' $PSBPreference.Docs.RootDir = "$PSScriptRoot/docs" # Publish — API key supplied by CI; falls back to env var for local releases $PSBPreference.Publish.PSRepository = 'PSGallery' $PSBPreference.Publish.PSRepositoryApiKey = $env:PSGALLERY_API_KEY } # --- # Entry points # --- task default -depends Test # Import PowerShellBuild tasks task Init -FromModule PowerShellBuild -Version '0.7.1' task Clean -FromModule PowerShellBuild -Version '0.7.1' task StageFiles -FromModule PowerShellBuild -Version '0.7.1' task Build -FromModule PowerShellBuild -Version '0.7.1' task Analyze -FromModule PowerShellBuild -Version '0.7.1' task Pester -FromModule PowerShellBuild -Version '0.7.1' task Test -FromModule PowerShellBuild -Version '0.7.1' task Publish -FromModule PowerShellBuild -Version '0.7.1' # --- # Custom tasks # --- task BumpVersion -depends Init { param([string]$BumpType = 'Patch') $manifestPath = $PSBPreference.General.ModuleManifestPath $manifest = Import-PowerShellDataFile $manifestPath $current = [System.Version]$manifest.ModuleVersion $next = switch ($BumpType) { 'Major' { [System.Version]::new($current.Major + 1, 0, 0) } 'Minor' { [System.Version]::new($current.Major, $current.Minor + 1, 0) } 'Patch' { [System.Version]::new($current.Major, $current.Minor, $current.Build + 1) } } Update-ModuleManifest -Path $manifestPath -ModuleVersion $next.ToString() Write-Host "Version bumped $current -> $next" -ForegroundColor Green } task ValidateReadme -precondition { Test-Path "$PSScriptRoot/README.md" } { $content = Get-Content "$PSScriptRoot/README.md" -Raw $requiredSections = @('## Installation', '## Usage', '## Contributing') foreach ($section in $requiredSections) { if ($content -notmatch [regex]::Escape($section)) { throw "README.md is missing section: $section" } } Write-Host 'README.md validation passed.' -ForegroundColor Green } # Deploy = bump version + validate docs + publish task Deploy -depends BumpVersion, ValidateReadme, Publish { $manifest = Import-PowerShellDataFile $PSBPreference.General.ModuleManifestPath Write-Host "MyModule v$($manifest.ModuleVersion) deployed to PSGallery." -ForegroundColor Green } ``` ## `PSScriptAnalyzerSettings.psd1`[​](#psscriptanalyzersettingspsd1 "Direct link to psscriptanalyzersettingspsd1") PowerShellBuild looks for `PSScriptAnalyzerSettings.psd1` in the project root by default. Place your custom rules here and they will be picked up automatically — no need to set `SettingsPath`. PSScriptAnalyzerSettings.psd1 ``` @{ ExcludeRules = @( 'PSAvoidUsingWriteHost' # Write-Host is acceptable in build scripts ) Severity = @('Error', 'Warning') } ``` ## GitHub Actions Workflow[​](#github-actions-workflow "Direct link to GitHub Actions Workflow") This workflow runs the full test suite on every push and pull request, and publishes to PSGallery when a tag is pushed. .github/workflows/build.yml ``` name: Build on: push: branches: [main] tags: ['v*'] pull_request: branches: [main] jobs: test: name: Test (PS ${{ matrix.ps-version }} on ${{ matrix.os }}) runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-latest, windows-latest] ps-version: ['7.4'] steps: - uses: actions/checkout@v4 - name: Bootstrap dependencies shell: pwsh run: .\build.ps1 -Bootstrap - name: Run tests shell: pwsh run: .\build.ps1 -Task Test - name: Upload test results if: always() uses: actions/upload-artifact@v4 with: name: test-results-${{ matrix.os }} path: build/TestResults.xml - name: Upload coverage report if: always() uses: actions/upload-artifact@v4 with: name: coverage-${{ matrix.os }} path: build/coverage.xml publish: name: Publish to PSGallery needs: test runs-on: ubuntu-latest if: startsWith(github.ref, 'refs/tags/v') steps: - uses: actions/checkout@v4 - name: Bootstrap dependencies shell: pwsh run: .\build.ps1 -Bootstrap - name: Publish module shell: pwsh env: PSGALLERY_API_KEY: ${{ secrets.PSGALLERY_API_KEY }} run: .\build.ps1 -Task Publish ``` ## Local Development Workflow[​](#local-development-workflow "Direct link to Local Development Workflow") ``` # First-time setup .\build.ps1 -Bootstrap # Day-to-day: run tests .\build.ps1 # Check code quality only .\build.ps1 -Task Analyze # Rebuild from scratch .\build.ps1 -Task Clean, Build # Bump the patch version and publish a release .\build.ps1 -Task Deploy ``` ## See Also[​](#see-also "Direct link to See Also") * [Getting Started](/docs/powershellbuild/getting-started.md) — Minimal project setup * [Task Reference](/docs/powershellbuild/task-reference.md) — All available tasks * [Configuration](/docs/powershellbuild/configuration.md) — Full `$PSBPreference` reference * [GitHub Actions Integration](/docs/ci-examples/github-actions.md) — General psake CI/CD patterns * [Publishing PowerShell Modules](https://learn.microsoft.com/en-us/powershell/gallery/how-to/publishing-packages/publishing-a-package) — PSGallery publishing guide --- # Task Reference PowerShellBuild provides two categories of tasks: **primary tasks** that you invoke directly, and **secondary tasks** that are called as dependencies but can also be invoked directly. ## Task Dependency Graph[​](#task-dependency-graph "Direct link to Task Dependency Graph") ## Primary Tasks[​](#primary-tasks "Direct link to Primary Tasks") These are the tasks you will typically invoke directly. ### `Init`[​](#init "Direct link to init") Initializes the build environment by calling `Initialize-PSBuild`, which sets up `$PSBPreference` defaults and resolves paths relative to the project root. **Default dependencies:** none **Dependency variable:** `$PSBCleanDependency` controls what `Clean` depends on (defaults to `'Init'`) ``` Invoke-psake -taskList Init ``` *** ### `Clean`[​](#clean "Direct link to clean") Removes the module output directory (`$PSBPreference.Build.ModuleOutDir`) to ensure a fresh build. **Default dependencies:** `Init` **Dependency variable:** `$PSBStageFilesDependency` ``` Invoke-psake -taskList Clean ``` *** ### `Build`[​](#build "Direct link to build") Meta-task that represents a fully built module. Runs `StageFiles` and `BuildHelp`. This is the task you reference with `-FromModule PowerShellBuild` in your `psakeFile.ps1`. **Default dependencies:** `StageFiles`, `BuildHelp` **Dependency variable:** `$PSBBuildDependency` ``` # In your psakeFile.ps1 task Build -FromModule PowerShellBuild -Version '0.7.1' ``` *** ### `Analyze`[​](#analyze "Direct link to analyze") Runs [PSScriptAnalyzer](https://github.com/PowerShell/PSScriptAnalyzer) against the staged module files. Fails the build if any rule violations exceed the configured severity threshold (default: `'Error'`). **Default dependencies:** `Build` **Dependency variable:** `$PSBAnalyzeDependency` **Precondition:** Only runs if `$PSBPreference.Test.ScriptAnalysis.Enabled` is `$true` and PSScriptAnalyzer is installed. ``` Invoke-psake -taskList Analyze ``` *** ### `Pester`[​](#pester "Direct link to pester") Runs [Pester](https://pester.dev) tests found under `$PSBPreference.Test.RootDir`. Optionally generates a code coverage report. **Default dependencies:** `Build` **Dependency variable:** `$PSBPesterDependency` **Precondition:** Only runs if `$PSBPreference.Test.Enabled` is `$true`. ``` Invoke-psake -taskList Pester ``` *** ### `Test`[​](#test "Direct link to test") Meta-task that runs both `Pester` and `Analyze`. Use this as your standard "did everything pass?" task. **Default dependencies:** `Pester`, `Analyze` **Dependency variable:** `$PSBTestDependency` ``` Invoke-psake -taskList Test ``` *** ### `Publish`[​](#publish "Direct link to publish") Publishes the staged module to the configured PowerShell repository (default: PSGallery). Requires `$PSBPreference.Publish.PSRepositoryApiKey` or `$PSBPreference.Publish.PSRepositoryCredential`. **Default dependencies:** `Test` **Dependency variable:** `$PSBPublishDependency` ``` Invoke-psake -taskList Publish ``` *** ### `Sign`[​](#sign "Direct link to sign") Meta-task that signs the module files and catalog. Runs the full signing chain: `SignModule` → `BuildCatalog` → `SignCatalog`. **Default dependencies:** `SignCatalog` **Dependency variable:** `$PSBSignDependency` ``` Invoke-psake -taskList Sign ``` *** ## Secondary Tasks[​](#secondary-tasks "Direct link to Secondary Tasks") These tasks are called as dependencies by the primary tasks. You can also invoke them directly for targeted operations. ### `StageFiles`[​](#stagefiles "Direct link to stagefiles") Copies or compiles module source files into the output directory. If `$PSBPreference.Build.CompileModule` is `$true`, it concatenates all `.ps1` files from `CompileDirectories` into a single `.psm1`. Otherwise it copies them as-is. **Default dependencies:** `Clean` *** ### `BuildHelp`[​](#buildhelp "Direct link to buildhelp") Meta-task that coordinates help generation. Depends on `GenerateMarkdown` and `GenerateMAML`. **Default dependencies:** `GenerateMarkdown`, `GenerateMAML` **Dependency variable:** `$PSBBuildHelpDependency` *** ### `GenerateMarkdown`[​](#generatemarkdown "Direct link to generatemarkdown") Uses [PlatyPS](https://github.com/PowerShell/platyPS) to generate markdown help files from the module's comment-based help. Output goes to `$PSBPreference.Docs.RootDir`. **Default dependencies:** `StageFiles` **Dependency variable:** `$PSBGenerateMarkdownDependency` *** ### `GenerateMAML`[​](#generatemaml "Direct link to generatemaml") Converts PlatyPS markdown files into MAML-format XML help (the format that `Get-Help` reads). Output is placed in the staged module directory. **Default dependencies:** `GenerateMarkdown` **Dependency variable:** `$PSBGenerateMAMLDependency` *** ### `GenerateUpdatableHelp`[​](#generateupdatablehelp "Direct link to generateupdatablehelp") Creates a `.cab` file for [Updatable Help](https://learn.microsoft.com/en-us/powershell/scripting/developer/help/supporting-updatable-help) using `Build-PSBuildUpdatableHelp`. **Default dependencies:** `BuildHelp` **Dependency variable:** `$PSBGenerateUpdatableHelpDependency` ``` Invoke-psake -taskList GenerateUpdatableHelp ``` *** ### `SignModule`[​](#signmodule "Direct link to signmodule") Signs all module files (`.psd1`, `.psm1`, `.ps1`) in the output directory with an Authenticode signature. Controlled by `$PSBPreference.Sign.*` settings. **Default dependencies:** `Build` **Dependency variable:** `$PSBSignModuleDependency` *** ### `BuildCatalog`[​](#buildcatalog "Direct link to buildcatalog") Creates a Windows catalog (`.cat`) file for the built module using `New-FileCatalog`. **Default dependencies:** `SignModule` **Dependency variable:** `$PSBBuildCatalogDependency` *** ### `SignCatalog`[​](#signcatalog "Direct link to signcatalog") Signs the module catalog (`.cat`) file with an Authenticode signature. **Default dependencies:** `BuildCatalog` **Dependency variable:** `$PSBSignCatalogDependency` *** ### `?` Lists all available tasks (psake's built-in help task). ``` Invoke-psake -taskList ? ``` *** ## Customizing Task Dependencies[​](#customizing-task-dependencies "Direct link to Customizing Task Dependencies") To change which tasks a given task depends on, set the corresponding dependency variable **outside** the `properties` block in your `psakeFile.ps1`, before any PowerShellBuild task references: psakeFile.ps1 ``` # Override before referencing PowerShellBuild tasks $PSBTestDependency = 'Pester' # Skip Analyze $PSBPublishDependency = 'Build' # Publish without running tests properties { $PSBPreference.Test.ScriptAnalysis.Enabled = $false } task default -depends Build task Build -FromModule PowerShellBuild -Version '0.7.1' task Test -FromModule PowerShellBuild -Version '0.7.1' task Publish -FromModule PowerShellBuild -Version '0.7.1' ``` warning Removing `Test` from the `Publish` dependency chain means you can publish without passing tests. Only do this in controlled scenarios such as a pre-release pipeline where tests are already enforced upstream. ## See Also[​](#see-also "Direct link to See Also") * [Configuration](/docs/powershellbuild/configuration.md) — Full `$PSBPreference` reference * [Getting Started](/docs/powershellbuild/getting-started.md) — Set up your first project * [Real-World Example](/docs/powershellbuild/real-world-example.md) — Custom tasks alongside PowerShellBuild tasks --- # psake Cheat Sheet Quick reference for common psake tasks, patterns, and commands. ## Installation & Setup[​](#installation--setup "Direct link to Installation & Setup") ``` # Install psake from PowerShell Gallery Install-Module -Name psake -Scope CurrentUser # Install globally (requires admin) Install-Module -Name psake -Scope AllUsers # Update to latest version Update-Module -Name psake # Check installed version Get-Module psake -ListAvailable | Select-Object Version # Import module Import-Module psake ``` ## Basic Invocation[​](#basic-invocation "Direct link to Basic Invocation") ``` # Run default task from psakefile.ps1 Invoke-psake # Run specific task Invoke-psake -taskList Build # Run multiple tasks Invoke-psake -taskList Clean, Build, Test # Use custom build file Invoke-psake -buildFile ./custom-build.ps1 # Pass parameters Invoke-psake -parameters @{ Configuration = 'Release'; Version = '1.0.0' } # Show available tasks Invoke-psake -docs # Show detailed task information Invoke-psake -detaileddocs # Verbose output Invoke-psake -Verbose ``` ## Task Definition Patterns[​](#task-definition-patterns "Direct link to Task Definition Patterns") ### Basic Task[​](#basic-task "Direct link to Basic Task") ``` Task TaskName { Write-Host "Doing work..." } ``` ### Task with Dependencies[​](#task-with-dependencies "Direct link to Task with Dependencies") ``` Task Build -depends Clean, Restore { exec { dotnet build } } ``` ### Task with Description[​](#task-with-description "Direct link to Task with Description") ``` Task Deploy -description "Deploy application to production" { exec { ./deploy.ps1 } } ``` ### Conditional Task (Precondition)[​](#conditional-task-precondition "Direct link to Conditional Task (Precondition)") ``` Task DeployProd -precondition { $Environment -eq 'Production' } { exec { ./deploy.ps1 } } ``` ### Task with Validation (Postcondition)[​](#task-with-validation-postcondition "Direct link to Task with Validation (Postcondition)") ``` Task Build -postcondition { Test-Path './build/app.dll' } { exec { dotnet build -o ./build } } ``` ### Task with Required Properties[​](#task-with-required-properties "Direct link to Task with Required Properties") ``` Task Deploy -requiredVariables 'Environment', 'Version' { exec { ./deploy.ps1 -Env $Environment -Ver $Version } } ``` ### Continuous Task (Watches for Changes)[​](#continuous-task-watches-for-changes "Direct link to Continuous Task (Watches for Changes)") ``` Task Watch -continueOnError { while ($true) { exec { dotnet watch run } Start-Sleep -Seconds 1 } } ``` ## Property Patterns[​](#property-patterns "Direct link to Property Patterns") ### Basic Properties[​](#basic-properties "Direct link to Basic Properties") ``` Properties { $Configuration = 'Release' $OutputPath = './build' $Version = '1.0.0' } ``` ### Properties from Script Location[​](#properties-from-script-location "Direct link to Properties from Script Location") ``` Properties { $BuildScriptDir = Split-Path $psake.build_script_file $ProjectRoot = Split-Path $BuildScriptDir $SrcDir = Join-Path $ProjectRoot 'src' } ``` ### Properties with Environment Variables[​](#properties-with-environment-variables "Direct link to Properties with Environment Variables") ``` Properties { $ApiKey = $env:API_KEY $BuildNumber = $env:BUILD_NUMBER ?? '0' $Environment = $env:DEPLOY_ENV ?? 'Development' } ``` ### Properties with Fallback Values[​](#properties-with-fallback-values "Direct link to Properties with Fallback Values") ``` Properties { # Use environment variable or default $Configuration = if ($env:BUILD_CONFIG) { $env:BUILD_CONFIG } else { 'Debug' } # PowerShell 7+ null-coalescing $Version = $env:VERSION ?? '1.0.0' } ``` ### Computed Properties[​](#computed-properties "Direct link to Computed Properties") ``` Properties { $Configuration = 'Release' } Properties { # Computed from other properties $OutputPath = "./build/$Configuration" $PackageName = "myapp-$Version.zip" } ``` ## Common Task Patterns[​](#common-task-patterns "Direct link to Common Task Patterns") ### Clean Task[​](#clean-task "Direct link to Clean Task") ``` Task Clean { if (Test-Path $OutputPath) { Remove-Item $OutputPath -Recurse -Force } New-Item -ItemType Directory -Path $OutputPath -Force | Out-Null } ``` ### Restore/Install Dependencies[​](#restoreinstall-dependencies "Direct link to Restore/Install Dependencies") ``` # .NET Task Restore { exec { dotnet restore } } # Node.js Task Install { exec { npm install } } # Python Task InstallDeps { exec { pip install -r requirements.txt } } ``` ### Build Tasks[​](#build-tasks "Direct link to Build Tasks") ``` # .NET Build Task Build -depends Restore { exec { dotnet build --configuration $Configuration --no-restore } } # Node.js Build Task Build -depends Install { exec { npm run build } } # Go Build Task Build { exec { go build -o ./build/app } } ``` ### Test Tasks[​](#test-tasks "Direct link to Test Tasks") ``` # .NET Tests Task Test -depends Build { exec { dotnet test --no-build --no-restore } } # With Coverage Task TestCoverage -depends Build { exec { dotnet test --collect:"XPlat Code Coverage" } } # Node.js Tests Task Test { exec { npm test } } ``` ### Package/Publish Tasks[​](#packagepublish-tasks "Direct link to Package/Publish Tasks") ``` # Create NuGet Package Task Pack -depends Build { exec { dotnet pack --no-build --output $OutputPath } } # Create Zip Archive Task Package -depends Build { Compress-Archive -Path "$OutputPath/*" -DestinationPath "./dist/app.zip" } # Publish to Folder Task Publish -depends Build { exec { dotnet publish --configuration $Configuration --output ./publish } } ``` ## Exec Function Patterns[​](#exec-function-patterns "Direct link to Exec Function Patterns") ### Basic Execution[​](#basic-execution "Direct link to Basic Execution") ``` Task Build { exec { dotnet build } } ``` ### Custom Error Message[​](#custom-error-message "Direct link to Custom Error Message") ``` Task Build { exec { dotnet build } -errorMessage "Build failed with error" } ``` ### Continue on Error[​](#continue-on-error "Direct link to Continue on Error") ``` Task Test { exec { dotnet test } -continueOnError } ``` ### With Retries[​](#with-retries "Direct link to With Retries") ``` Task Deploy { exec { ./deploy.ps1 } -maxRetries 3 } ``` ### With Retry Pattern Matching[​](#with-retry-pattern-matching "Direct link to With Retry Pattern Matching") ``` Task Deploy { exec { ./deploy.ps1 } ` -maxRetries 3 ` -retryTriggerErrorPattern "timeout|connection" } ``` ### Working Directory[​](#working-directory "Direct link to Working Directory") ``` Task BuildSubproject { Push-Location ./subproject try { exec { dotnet build } } finally { Pop-Location } } ``` ## Assert Patterns[​](#assert-patterns "Direct link to Assert Patterns") ### File Existence[​](#file-existence "Direct link to File Existence") ``` Task Validate { Assert (Test-Path './src') "Source directory not found" Assert (Test-Path './config.json') "Configuration file missing" } ``` ### Variable Validation[​](#variable-validation "Direct link to Variable Validation") ``` Task Deploy { Assert (![string]::IsNullOrEmpty($ApiKey)) "API_KEY is required" Assert ($Version -match '^\d+\.\d+\.\d+$') "Invalid version format" } ``` ### Condition Checks[​](#condition-checks "Direct link to Condition Checks") ``` Task DeployProd { Assert ($Environment -eq 'Production') "This task only runs in Production" exec { ./deploy.ps1 } } ``` ## Include Patterns[​](#include-patterns "Direct link to Include Patterns") ### Include Shared Tasks[​](#include-shared-tasks "Direct link to Include Shared Tasks") ``` # In psakefile.ps1 Include ./build/shared-tasks.ps1 Include ./build/deploy-tasks.ps1 Task Default -depends SharedClean, Build, Deploy ``` ### Relative Includes[​](#relative-includes "Direct link to Relative Includes") ``` Properties { $BuildDir = Split-Path $psake.build_script_file } Include (Join-Path $BuildDir 'tasks/common.ps1') ``` ## Format Task Name Patterns[​](#format-task-name-patterns "Direct link to Format Task Name Patterns") ### Simple Format[​](#simple-format "Direct link to Simple Format") ``` FormatTaskName { param($taskName) Write-Host "===== $taskName =====" -ForegroundColor Cyan } ``` ### Detailed Format[​](#detailed-format "Direct link to Detailed Format") ``` FormatTaskName { param($taskName) $line = "-" * 70 Write-Host $line -ForegroundColor Blue Write-Host " Task: $taskName" -ForegroundColor Yellow Write-Host $line -ForegroundColor Blue } ``` ### Timestamp Format[​](#timestamp-format "Direct link to Timestamp Format") ``` FormatTaskName { param($taskName) $timestamp = Get-Date -Format "HH:mm:ss" Write-Host "[$timestamp] Starting task: $taskName" -ForegroundColor Green } ``` ## Parameter Passing[​](#parameter-passing "Direct link to Parameter Passing") ### Via Parameters Hashtable[​](#via-parameters-hashtable "Direct link to Via Parameters Hashtable") ``` Invoke-psake -parameters @{ Configuration = 'Release' Version = '2.0.0' Environment = 'Production' } ``` ### Via Properties (Alternative)[​](#via-properties-alternative "Direct link to Via Properties (Alternative)") ``` Invoke-psake -properties @{ Configuration = 'Release' Version = '2.0.0' } ``` ### Via Environment Variables[​](#via-environment-variables "Direct link to Via Environment Variables") ``` # Set in shell $env:BUILD_CONFIG = 'Release' $env:VERSION = '2.0.0' # Read in build script Properties { $Configuration = $env:BUILD_CONFIG ?? 'Debug' $Version = $env:VERSION ?? '1.0.0' } Invoke-psake ``` ## Nested Build Patterns[​](#nested-build-patterns "Direct link to Nested Build Patterns") ### Sequential Subproject Builds[​](#sequential-subproject-builds "Direct link to Sequential Subproject Builds") ``` Task BuildAll { Invoke-psake ./projects/ProjectA/psakefile.ps1 -taskList Build Invoke-psake ./projects/ProjectB/psakefile.ps1 -taskList Build } ``` ### Parallel Subproject Builds[​](#parallel-subproject-builds "Direct link to Parallel Subproject Builds") ``` Task BuildAll { $jobs = @( Start-Job { Invoke-psake ./projects/ProjectA/psakefile.ps1 -taskList Build } Start-Job { Invoke-psake ./projects/ProjectB/psakefile.ps1 -taskList Build } ) $jobs | Wait-Job | Receive-Job $jobs | Remove-Job } ``` ### Pass Parameters to Nested Build[​](#pass-parameters-to-nested-build "Direct link to Pass Parameters to Nested Build") ``` Task BuildSubproject { Invoke-psake ./subproject/psakefile.ps1 ` -taskList Build ` -parameters @{ Configuration = $Configuration } } ``` ## Common Build Workflows[​](#common-build-workflows "Direct link to Common Build Workflows") ### Standard .NET Workflow[​](#standard-net-workflow "Direct link to Standard .NET Workflow") ``` Properties { $Configuration = 'Release' $OutputPath = './build' } Task Default -depends Test Task Clean { Remove-Item $OutputPath -Recurse -Force -ErrorAction SilentlyContinue } Task Restore { exec { dotnet restore } } Task Build -depends Clean, Restore { exec { dotnet build --configuration $Configuration --no-restore } } Task Test -depends Build { exec { dotnet test --configuration $Configuration --no-build } } Task Pack -depends Test { exec { dotnet pack --configuration $Configuration --no-build --output $OutputPath } } ``` ### Standard Node.js Workflow[​](#standard-nodejs-workflow "Direct link to Standard Node.js Workflow") ``` Properties { $ProjectRoot = $PSScriptRoot $DistDir = Join-Path $ProjectRoot 'dist' } Task Default -depends Test Task Clean { Remove-Item $DistDir -Recurse -Force -ErrorAction SilentlyContinue } Task Install { exec { npm install } } Task Lint -depends Install { exec { npm run lint } } Task Build -depends Install, Lint { exec { npm run build } } Task Test -depends Build { exec { npm test } } Task Package -depends Test { exec { npm pack --pack-destination $DistDir } } ``` ### CI/CD Workflow[​](#cicd-workflow "Direct link to CI/CD Workflow") ``` Properties { $Configuration = 'Release' $Version = $env:VERSION ?? '1.0.0' $BuildNumber = $env:BUILD_NUMBER ?? '0' } Task CI -depends Clean, Restore, Build, Test, Pack, Publish Task Clean { Remove-Item ./build -Recurse -Force -ErrorAction SilentlyContinue } Task Restore { exec { dotnet restore } } Task Build -depends Restore { exec { dotnet build --configuration $Configuration --no-restore } } Task Test -depends Build { exec { dotnet test --configuration $Configuration --no-build --logger trx } } Task Pack -depends Test { exec { dotnet pack --configuration $Configuration --no-build -p:Version=$Version } } Task Publish -depends Pack { exec { dotnet nuget push "./bin/Release/*.nupkg" --api-key $env:NUGET_API_KEY } } ``` ## Error Handling Patterns[​](#error-handling-patterns "Direct link to Error Handling Patterns") ### Try-Catch in Tasks[​](#try-catch-in-tasks "Direct link to Try-Catch in Tasks") ``` Task Deploy { try { exec { ./deploy.ps1 } Write-Host "Deployment successful" -ForegroundColor Green } catch { Write-Host "Deployment failed: $_" -ForegroundColor Red throw } } ``` ### Graceful Failure[​](#graceful-failure "Direct link to Graceful Failure") ``` Task OptionalTask { try { exec { ./optional-operation.ps1 } } catch { Write-Warning "Optional operation failed: $_" # Continue build } } ``` ### Cleanup on Failure[​](#cleanup-on-failure "Direct link to Cleanup on Failure") ``` Task Deploy { try { exec { ./deploy.ps1 } } catch { Write-Host "Deployment failed, rolling back..." -ForegroundColor Yellow exec { ./rollback.ps1 } throw } } ``` ## Useful One-Liners[​](#useful-one-liners "Direct link to Useful One-Liners") ``` # Run psake from any directory with specific build file Invoke-psake -buildFile C:\projects\myapp\psakefile.ps1 # Run with .NET Framework 4.8 Invoke-psake -framework '4.8' # List all tasks without running Invoke-psake -docs # Run and show task execution time Invoke-psake -Verbose # Run specific task in specific file Invoke-psake -buildFile ./psakefile.ps1 -taskList Deploy -parameters @{Environment='Prod'} # Check if psake is installed Get-Module psake -ListAvailable # Get psake commands Get-Command -Module psake # View psake help Get-Help Invoke-psake -Full # Run psake and capture output $result = Invoke-psake -taskList Build 2>&1 # Run psake with specific error action Invoke-psake -ErrorAction Stop ``` ## Environment-Specific Patterns[​](#environment-specific-patterns "Direct link to Environment-Specific Patterns") ### Multi-Environment Configuration[​](#multi-environment-configuration "Direct link to Multi-Environment Configuration") ``` Properties { $Environment = $env:DEPLOY_ENV ?? 'Development' $Config = @{ Development = @{ ApiUrl = 'http://localhost:5000' DbConnection = 'Server=localhost;Database=DevDB' } Staging = @{ ApiUrl = 'https://staging.example.com' DbConnection = 'Server=staging-db;Database=StagingDB' } Production = @{ ApiUrl = 'https://api.example.com' DbConnection = 'Server=prod-db;Database=ProdDB' } } $CurrentConfig = $Config[$Environment] } Task Deploy { Write-Host "Deploying to $Environment" -ForegroundColor Cyan Write-Host " API URL: $($CurrentConfig.ApiUrl)" -ForegroundColor Gray exec { ./deploy.ps1 -Environment $Environment -ApiUrl $CurrentConfig.ApiUrl } } ``` ### Environment-Specific Tasks[​](#environment-specific-tasks "Direct link to Environment-Specific Tasks") ``` Task DeployDev -precondition { $Environment -eq 'Development' } { exec { ./deploy-dev.ps1 } } Task DeployStaging -precondition { $Environment -eq 'Staging' } { exec { ./deploy-staging.ps1 } } Task DeployProd -precondition { $Environment -eq 'Production' } { Assert ($Version -ne 'dev') "Cannot deploy dev version to production" exec { ./deploy-production.ps1 } } Task Deploy -depends DeployDev, DeployStaging, DeployProd { # At least one will run based on preconditions } ``` ## Docker Build Patterns[​](#docker-build-patterns "Direct link to Docker Build Patterns") ### Build Docker Image[​](#build-docker-image "Direct link to Build Docker Image") ``` Properties { $ImageName = 'myapp' $ImageTag = $env:VERSION ?? 'latest' } Task DockerBuild { $fullTag = "${ImageName}:${ImageTag}" exec { docker build -t $fullTag . } } Task DockerPush -depends DockerBuild { $fullTag = "${ImageName}:${ImageTag}" exec { docker push $fullTag } } ``` ### Multi-Stage Docker Build[​](#multi-stage-docker-build "Direct link to Multi-Stage Docker Build") ``` Task DockerBuildDev { exec { docker build --target development -t myapp:dev . } } Task DockerBuildProd { exec { docker build --target production -t myapp:prod . } } ``` ## Debugging Tips[​](#debugging-tips "Direct link to Debugging Tips") ``` # Add breakpoints Set-PSBreakpoint -Script .\psakefile.ps1 -Line 42 # Run with verbose output Invoke-psake -Verbose # Add debug output in tasks Task Build { Write-Host "Configuration: $Configuration" -ForegroundColor Yellow Write-Host "OutputPath: $OutputPath" -ForegroundColor Yellow exec { dotnet build } } # Check psake context Task Debug { Write-Host "Build script: $($psake.build_script_file)" Write-Host "Version: $($psake.version)" Write-Host "Context: $($psake.context | Out-String)" } # Test task execution order without running tasks Invoke-psake -docs # Shows task dependencies ``` ## See Also[​](#see-also "Direct link to See Also") * [Glossary](/docs/reference/glossary.md) - Term definitions * [Configuration Reference](/docs/reference/configuration-reference.md) - All configuration options * [FAQ](/docs/troubleshooting/faq.md) - Common questions and answers * [Tasks Tutorial](/docs/tutorial-basics/tasks.md) - Detailed task documentation * [Parameters and Properties](/docs/tutorial-basics/parameters-properties.md) - Parameter handling --- # Configuration Reference Complete reference for all psake configuration options, build script settings, and the `$psake` context object. ## Invoke-psake Parameters[​](#invoke-psake-parameters "Direct link to Invoke-psake Parameters") ### Core Parameters[​](#core-parameters "Direct link to Core Parameters") #### -buildFile[​](#-buildfile "Direct link to -buildFile") **Type:** `string` **Default:** `psakefile.ps1` or `default.ps1` **Description:** Path to the psake build script to execute. ``` # Use default build file (psakefile.ps1) Invoke-psake # Use custom build file Invoke-psake -buildFile ./build/custom-build.ps1 # Use absolute path Invoke-psake -buildFile C:\projects\myapp\build.ps1 ``` **Search order for default build files:** 1. `psakefile.ps1` 2. `default.ps1` #### -taskList[​](#-tasklist "Direct link to -taskList") **Type:** `string[]` **Default:** `@('Default')` **Description:** Array of task names to execute. Tasks run in the order specified. ``` # Run single task Invoke-psake -taskList Build # Run multiple tasks in order Invoke-psake -taskList Clean, Build, Test # Run default task (equivalent to omitting parameter) Invoke-psake -taskList Default ``` **Notes:** * Task dependencies are resolved automatically * Each task runs at most once per invocation * Circular dependencies cause build failure #### -framework[​](#-framework "Direct link to -framework") **Type:** `string` **Default:** `$null` **Description:** .NET Framework version to target. Primarily for legacy compatibility. ``` # Target .NET Framework 4.8 Invoke-psake -framework '4.8' # Target .NET Framework 4.0 Invoke-psake -framework '4.0' # Target .NET 6.0 Invoke-psake -framework 'net6.0' ``` **Common values:** * `'3.5'` - .NET Framework 3.5 * `'4.0'` - .NET Framework 4.0 * `'4.5'` - .NET Framework 4.5 * `'4.8'` - .NET Framework 4.8 * `'net6.0'`, `'net7.0'`, `'net8.0'` - Modern .NET **Note:** Most modern builds handle framework targeting via project files rather than psake. #### -docs[​](#-docs "Direct link to -docs") **Type:** `switch` **Default:** `$false` **Description:** Display task documentation without executing tasks. ``` # Show all tasks with descriptions Invoke-psake -docs # Output: # Task Name Description # --------- ----------- # Build Compiles the solution # Test Runs unit tests # Deploy Deploys to production ``` **Related:** Use `-detaileddocs` for more information including dependencies. #### -detailedDocs[​](#-detaileddocs "Direct link to -detailedDocs") **Type:** `switch` **Default:** `$false` **Description:** Display detailed task documentation including dependencies. ``` # Show detailed task information Invoke-psake -detailedDocs # Output includes: # - Task names # - Descriptions # - Dependencies # - Preconditions # - Postconditions ``` #### -parameters[​](#-parameters "Direct link to -parameters") **Type:** `hashtable` **Default:** `@{}` **Description:** Hashtable of parameters to pass to the build script. Parameters override property defaults. ``` # Pass single parameter Invoke-psake -parameters @{ Configuration = 'Release' } # Pass multiple parameters Invoke-psake -parameters @{ Configuration = 'Release' Version = '2.0.0' Environment = 'Production' } # Parameters override properties # In psakefile.ps1: Properties { $Configuration = 'Debug' # Default } # Invoke with parameter: Invoke-psake -parameters @{ Configuration = 'Release' } # Overrides to Release ``` **Best practices:** * Use for values that change between builds * Provide sensible defaults in Properties block * Document required parameters in build script #### -properties[​](#-properties "Direct link to -properties") **Type:** `hashtable` **Default:** `@{}` **Description:** Alternative name for `-parameters`. Functionally identical. ``` # These are equivalent: Invoke-psake -parameters @{ Configuration = 'Release' } Invoke-psake -properties @{ Configuration = 'Release' } ``` #### -initialization[​](#-initialization "Direct link to -initialization") **Type:** `scriptblock` **Default:** `$null` **Description:** Script block to execute before any tasks run. Used for custom initialization logic. ``` Invoke-psake -initialization { Write-Host "Initializing build environment..." $env:BUILD_START_TIME = Get-Date Import-Module CustomBuildHelpers } ``` **Use cases:** * Load custom modules * Set environment variables * Verify prerequisites * Initialize logging #### -nologo[​](#-nologo "Direct link to -nologo") **Type:** `switch` **Default:** `$false` **Description:** Suppress the psake logo and version information. ``` # Hide psake logo Invoke-psake -nologo # Useful in CI/CD to reduce log noise ``` #### -notr[​](#-notr "Direct link to -notr") **Type:** `switch` **Default:** `$false` **Description:** Suppress the build time report at the end of execution. ``` # Hide time report Invoke-psake -notr # Normal output includes: # ---------------------------------------------------------------------- # Build Time Report # ---------------------------------------------------------------------- # Name Duration # ---- -------- # Clean 00:00:01.234 # Build 00:00:15.678 # Total: 00:00:16.912 ``` ## Build Script Configuration[​](#build-script-configuration "Direct link to Build Script Configuration") ### Properties Block[​](#properties-block "Direct link to Properties Block") The `Properties` block defines variables used throughout the build script. ``` Properties { # Simple properties $Configuration = 'Release' $Version = '1.0.0' # Computed properties $BuildDir = Split-Path $psake.build_script_file $OutputPath = Join-Path $BuildDir 'build' # Properties from environment $ApiKey = $env:API_KEY $BuildNumber = $env:BUILD_NUMBER ?? '0' } ``` **Key characteristics:** * Can appear multiple times in a script * Later definitions can reference earlier properties * Can be overridden via `-parameters` * Available to all tasks ### Task Definition[​](#task-definition "Direct link to Task Definition") Tasks are defined using the `Task` keyword with various optional parameters. #### Basic Syntax[​](#basic-syntax "Direct link to Basic Syntax") ``` Task TaskName { # Task body } ``` #### Full Syntax[​](#full-syntax "Direct link to Full Syntax") ``` Task TaskName ` -depends DependencyTask1, DependencyTask2 ` -requiredVariables 'Variable1', 'Variable2' ` -description "Task description for documentation" ` -precondition { $Environment -eq 'Production' } ` -postcondition { Test-Path $OutputFile } ` -continueOnError ` -action { # Task implementation } ``` ### Task Parameters[​](#task-parameters "Direct link to Task Parameters") #### -depends[​](#-depends "Direct link to -depends") **Type:** `string[]` **Default:** `@()` **Description:** Array of task names that must execute before this task. ``` Task Test -depends Build { exec { dotnet test } } # Multiple dependencies Task Deploy -depends Clean, Build, Test { exec { ./deploy.ps1 } } ``` **Behavior:** * Dependencies run in declaration order * Each dependency runs exactly once * Transitive dependencies are resolved automatically * Circular dependencies cause error #### -requiredVariables[​](#-requiredvariables "Direct link to -requiredVariables") **Type:** `string[]` **Default:** `@()` **Description:** Array of property names that must be defined and non-null. ``` Properties { $Version = '1.0.0' $ApiKey = $env:API_KEY } Task Deploy -requiredVariables 'Version', 'ApiKey' { # Build fails if Version or ApiKey is null/undefined exec { ./deploy.ps1 -Version $Version -ApiKey $ApiKey } } ``` **Validation:** * Checked before task execution * Fails build if any variable is undefined or null * Provides clear error message #### -description[​](#-description "Direct link to -description") **Type:** `string` **Default:** `''` **Description:** Human-readable description shown by `-docs` and `-detailedDocs`. ``` Task Build -description "Compiles the solution in Release mode" { exec { dotnet build --configuration Release } } # View descriptions Invoke-psake -docs ``` **Best practices:** * Write clear, concise descriptions * Describe what the task does, not how * Include any important prerequisites or side effects #### -precondition[​](#-precondition "Direct link to -precondition") **Type:** `scriptblock` **Default:** `$null` **Description:** Script block that must return `$true` for task to execute. If `$false`, task is skipped. ``` Properties { $Environment = 'Development' } Task DeployProd -precondition { $Environment -eq 'Production' } { exec { ./deploy-production.ps1 } } # Task is skipped if precondition returns $false ``` **Use cases:** * Environment-specific tasks * Conditional execution based on configuration * Skip tasks when prerequisites aren't met **Note:** Skipped tasks don't fail the build. #### -postcondition[​](#-postcondition "Direct link to -postcondition") **Type:** `scriptblock` **Default:** `$null` **Description:** Script block that must return `$true` after task completes. If `$false`, build fails. ``` Task Build -postcondition { Test-Path './build/app.dll' } { exec { dotnet build -o ./build } } # Build fails if postcondition returns $false ``` **Use cases:** * Verify task produced expected output * Validate build artifacts * Ensure task side effects occurred **Note:** Failed postconditions fail the build. #### -continueOnError[​](#-continueonerror "Direct link to -continueOnError") **Type:** `switch` **Default:** `$false` **Description:** Continue build execution even if task throws an error. ``` Task OptionalCleanup -continueOnError { Remove-Item ./temp -Recurse -Force # Build continues even if this fails } ``` **Use cases:** * Optional cleanup operations * Best-effort tasks * Non-critical operations **Warning:** Use sparingly. Most tasks should fail the build on error. #### -action[​](#-action "Direct link to -action") **Type:** `scriptblock` **Default:** `$null` **Description:** The script block containing task logic. Alternative to inline script block. ``` # These are equivalent: # Inline form Task Build { exec { dotnet build } } # Action parameter form Task Build -action { exec { dotnet build } } ``` ### FormatTaskName[​](#formattaskname "Direct link to FormatTaskName") Customizes how task names are displayed during execution. #### Syntax[​](#syntax "Direct link to Syntax") ``` FormatTaskName { param($taskName) # Return string or use Write-Host } ``` #### Examples[​](#examples "Direct link to Examples") ``` # Simple separator FormatTaskName { param($taskName) Write-Host "===== $taskName =====" -ForegroundColor Cyan } # Box format FormatTaskName { param($taskName) $line = "=" * 70 Write-Host $line -ForegroundColor Blue Write-Host " TASK: $taskName" -ForegroundColor Yellow Write-Host $line -ForegroundColor Blue } # With timestamp FormatTaskName { param($taskName) $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss" Write-Host "[$timestamp] $taskName" -ForegroundColor Green } # Return string (psake will output it) FormatTaskName { param($taskName) return ">>> Executing: $taskName <<<" } ``` ### Include[​](#include "Direct link to Include") Includes tasks and functions from another PowerShell file. #### Syntax[​](#syntax-1 "Direct link to Syntax") ``` Include ``` #### Examples[​](#examples-1 "Direct link to Examples") ``` # Include from relative path Include ./shared-tasks.ps1 Include ./build/common-tasks.ps1 # Include from computed path Properties { $BuildDir = Split-Path $psake.build_script_file } Include (Join-Path $BuildDir 'tasks/deploy-tasks.ps1') # Include multiple files Include ./tasks/build-tasks.ps1 Include ./tasks/test-tasks.ps1 Include ./tasks/deploy-tasks.ps1 ``` **Behavior:** * Included files can define tasks, properties, and functions * Included tasks are available to all tasks in main script * Include statements are processed before tasks execute * Relative paths are relative to the build script directory ## The $psake Context Object[​](#the-psake-context-object "Direct link to The $psake Context Object") The `$psake` variable is automatically available in all build scripts and contains context information. ### $psake Properties[​](#psake-properties "Direct link to $psake Properties") #### $psake.version[​](#psakeversion "Direct link to $psake.version") **Type:** `string` **Description:** The version of the psake module. ``` Task Info { Write-Host "psake version: $($psake.version)" } ``` #### $psake.build\_script\_file[​](#psakebuild_script_file "Direct link to $psake.build_script_file") **Type:** `string` **Description:** Full path to the current build script file. ``` Properties { $ScriptPath = $psake.build_script_file # Example: C:\projects\myapp\psakefile.ps1 } ``` **Common use:** ``` Properties { $BuildDir = Split-Path $psake.build_script_file $ProjectRoot = Split-Path $BuildDir } ``` #### $psake.build\_script\_dir[​](#psakebuild_script_dir "Direct link to $psake.build_script_dir") **Type:** `string` **Description:** Directory containing the build script file. ``` Properties { $ScriptDir = $psake.build_script_dir # Example: C:\projects\myapp } ``` #### $psake.context[​](#psakecontext "Direct link to $psake.context") **Type:** `object` **Description:** Current build execution context, including current task and call stack. ``` Task Debug { Write-Host "Current context:" $psake.context | Format-List } ``` **Properties of $psake.context:** * `currentTaskName` - Name of currently executing task * `tasks` - Hashtable of all defined tasks * `properties` - Hashtable of all properties * `success` - Boolean indicating build success/failure #### $psake.build\_success[​](#psakebuild_success "Direct link to $psake.build_success") **Type:** `boolean` **Description:** Indicates whether the build has succeeded so far. ``` Task Cleanup { if ($psake.build_success) { Write-Host "Build succeeded, cleaning up..." } else { Write-Host "Build failed, preserving artifacts for debugging" } } ``` ## Exec Function[​](#exec-function "Direct link to Exec Function") The `exec` function executes external commands and automatically handles errors. ### Syntax[​](#syntax-2 "Direct link to Syntax") ``` exec { # Script block to execute } [-errorMessage ] [-maxRetries ] [-retryTriggerErrorPattern ] [-continueOnError] ``` ### Parameters[​](#parameters "Direct link to Parameters") #### Script Block (Required)[​](#script-block-required "Direct link to Script Block (Required)") The command(s) to execute. ``` exec { dotnet build } exec { msbuild MySolution.sln /p:Configuration=Release } exec { npm run build } ``` #### -errorMessage[​](#-errormessage "Direct link to -errorMessage") Custom error message if command fails. ``` exec { dotnet build } -errorMessage "Build failed with errors" ``` #### -maxRetries[​](#-maxretries "Direct link to -maxRetries") Number of times to retry on failure. ``` # Retry up to 3 times exec { ./deploy.ps1 } -maxRetries 3 ``` #### -retryTriggerErrorPattern[​](#-retrytriggererrorpattern "Direct link to -retryTriggerErrorPattern") Regular expression pattern. Only retry if error message matches pattern. ``` exec { ./deploy.ps1 } ` -maxRetries 3 ` -retryTriggerErrorPattern "timeout|connection" ``` #### -continueOnError[​](#-continueonerror-1 "Direct link to -continueOnError") Don't fail build if command fails. ``` exec { dotnet test } -continueOnError ``` ### Examples[​](#examples-2 "Direct link to Examples") ``` # Basic usage Task Build { exec { dotnet build } } # With error message Task Build { exec { dotnet build } -errorMessage "Failed to compile solution" } # With retries Task Deploy { exec { ./deploy.ps1 } -maxRetries 3 } # Conditional retry Task Deploy { exec { ./deploy.ps1 } ` -maxRetries 5 ` -retryTriggerErrorPattern "timeout|429|503" } # Multiple commands Task Build { exec { dotnet restore } exec { dotnet build --no-restore } exec { dotnet test --no-build } } ``` ## Assert Function[​](#assert-function "Direct link to Assert Function") The `assert` function validates conditions and fails the build if condition is false. ### Syntax[​](#syntax-3 "Direct link to Syntax") ``` Assert ``` ### Parameters[​](#parameters-1 "Direct link to Parameters") #### Condition (Required)[​](#condition-required "Direct link to Condition (Required)") Boolean expression that must be true. #### Error Message (Required)[​](#error-message-required "Direct link to Error Message (Required)") Message to display if assertion fails. ### Examples[​](#examples-3 "Direct link to Examples") ``` # File existence Task Validate { Assert (Test-Path './src') "Source directory not found" Assert (Test-Path './config.json') "Configuration file missing" } # Variable validation Task Deploy { Assert (![string]::IsNullOrEmpty($ApiKey)) "API_KEY is required" Assert ($Version -match '^\d+\.\d+\.\d+$') "Invalid version format: $Version" } # Condition checks Task DeployProd { Assert ($Environment -eq 'Production') "This task only runs in Production environment" Assert (Test-Path $ArtifactPath) "Build artifacts not found at: $ArtifactPath" } # Multiple assertions Task Validate { Assert ($Configuration -in @('Debug', 'Release')) "Configuration must be Debug or Release" Assert ($PSVersionTable.PSVersion.Major -ge 7) "PowerShell 7+ is required" Assert (Get-Command dotnet -ErrorAction SilentlyContinue) "dotnet CLI not found in PATH" } ``` ## Environment Variables[​](#environment-variables "Direct link to Environment Variables") Common environment variables used with psake builds. ### CI/CD Environment Variables[​](#cicd-environment-variables "Direct link to CI/CD Environment Variables") ``` Properties { # Common CI indicators $IsCI = $env:CI -eq 'true' $IsCIServer = $env:TF_BUILD -eq 'True' -or $env:GITHUB_ACTIONS -eq 'true' # Build number $BuildNumber = $env:BUILD_NUMBER ?? $env:BUILD_BUILDNUMBER ?? $env:GITHUB_RUN_NUMBER ?? '0' # Branch name $Branch = $env:GIT_BRANCH ?? $env:BUILD_SOURCEBRANCHNAME ?? $env:GITHUB_REF_NAME ?? 'main' # Commit SHA $CommitSha = $env:GIT_COMMIT ?? $env:BUILD_SOURCEVERSION ?? $env:GITHUB_SHA ?? 'unknown' } ``` ### Common Custom Environment Variables[​](#common-custom-environment-variables "Direct link to Common Custom Environment Variables") ``` Properties { # Configuration $Configuration = $env:BUILD_CONFIGURATION ?? 'Debug' $Environment = $env:DEPLOY_ENVIRONMENT ?? 'Development' # Versioning $Version = $env:VERSION ?? '1.0.0' # Secrets (never hardcode!) $ApiKey = $env:API_KEY $NuGetApiKey = $env:NUGET_API_KEY $AwsAccessKey = $env:AWS_ACCESS_KEY_ID $AwsSecretKey = $env:AWS_SECRET_ACCESS_KEY # Paths $ArtifactPath = $env:ARTIFACT_PATH ?? './artifacts' $OutputPath = $env:OUTPUT_PATH ?? './build' } ``` ## PowerShell Version Considerations[​](#powershell-version-considerations "Direct link to PowerShell Version Considerations") psake works with multiple PowerShell versions. Consider compatibility: ``` Properties { $PSVersion = $PSVersionTable.PSVersion.Major # Use null-coalescing only in PS 7+ if ($PSVersion -ge 7) { $Configuration = $env:BUILD_CONFIG ?? 'Debug' } else { $Configuration = if ($env:BUILD_CONFIG) { $env:BUILD_CONFIG } else { 'Debug' } } } Task ValidatePowerShell { $minVersion = 5 Assert ($PSVersionTable.PSVersion.Major -ge $minVersion) ` "PowerShell $minVersion or later is required (current: $($PSVersionTable.PSVersion))" } ``` ## See Also[​](#see-also "Direct link to See Also") * [Glossary](/docs/reference/glossary.md) - Term definitions * [Cheat Sheet](/docs/reference/cheat-sheet.md) - Quick reference * [Exit Codes](/docs/reference/exit-codes.md) - Exit code meanings * [Parameters and Properties](/docs/tutorial-basics/parameters-properties.md) - Parameter handling guide * [Tasks](/docs/tutorial-basics/tasks.md) - Task definition guide --- # Exit Codes Understanding exit codes is crucial for integrating psake with CI/CD systems and handling build failures correctly. ## Overview[​](#overview "Direct link to 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[​](#exit-code-behavior "Direct link to Exit Code Behavior") ### Success (Exit Code 0)[​](#success-exit-code-0 "Direct link to Success (Exit Code 0)") psake returns exit code `0` when: * All specified tasks complete without errors * All task dependencies run successfully * No `exec` commands 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)[​](#failure-exit-code-1 "Direct link to Failure (Exit Code 1)") psake returns exit code `1` when: * Any task throws an unhandled exception * Any `exec` command 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[​](#checking-exit-codes "Direct link to Checking Exit Codes") ### In PowerShell[​](#in-powershell "Direct link to 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[​](#in-bashshell-scripts "Direct link to 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)[​](#in-batch-files-windows "Direct link to 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[​](#cicd-integration "Direct link to CI/CD Integration") ### GitHub Actions[​](#github-actions "Direct link to 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[​](#azure-pipelines "Direct link to 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[​](#gitlab-ci "Direct link to 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[​](#jenkins "Direct link to 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[​](#teamcity "Direct link to TeamCity") ``` Install-Module -Name psake -Scope CurrentUser -Force Invoke-psake -taskList Build if ($LASTEXITCODE -ne 0) { Write-Error "Build failed" exit 1 } default ``` ## Common Exit Code Scenarios[​](#common-exit-code-scenarios "Direct link to Common Exit Code Scenarios") ### Scenario 1: External Command Fails[​](#scenario-1-external-command-fails "Direct link to 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[​](#scenario-2-assertion-fails "Direct link to 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[​](#scenario-3-postcondition-fails "Direct link to 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[​](#scenario-4-task-with-continueonerror "Direct link to 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[​](#scenario-5-multiple-tasks-one-fails "Direct link to 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[​](#exit-code-best-practices "Direct link to Exit Code Best Practices") ### 1. Always Check Exit Codes in CI/CD[​](#1-always-check-exit-codes-in-cicd "Direct link to 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[​](#2-use-exec-for-external-commands "Direct link to 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[​](#3-fail-fast-with-assertions "Direct link to 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[​](#4-provide-meaningful-error-messages "Direct link to 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[​](#5-dont-swallow-errors "Direct link to 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[​](#6-use-exit-codes-in-wrapper-scripts "Direct link to 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[​](#7-handle-exit-codes-in-multi-stage-builds "Direct link to 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[​](#troubleshooting-exit-codes "Direct link to Troubleshooting Exit Codes") ### Problem: Build Succeeds Locally but Fails in CI[​](#problem-build-succeeds-locally-but-fails-in-ci "Direct link to Problem: Build Succeeds Locally but Fails in CI") **Possible causes:** 1. Environment variables missing in CI 2. Different PowerShell version in CI 3. Missing dependencies in CI environment 4. 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[​](#problem-exit-code-is-0-but-build-should-fail "Direct link to Problem: Exit Code is 0 but Build Should Fail") **Possible causes:** 1. Not using `exec` for external commands 2. Using `-continueOnError` inappropriately 3. 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[​](#problem-cant-determine-why-build-failed "Direct link to 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[​](#exit-codes-in-docker "Direct link to 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[​](#see-also "Direct link to See Also") * [Glossary](/docs/reference/glossary.md) - Exit code and error handling terms * [Configuration Reference](/docs/reference/configuration-reference.md) - exec and assert functions * [Common Errors](/docs/troubleshooting/common-errors.md) - Troubleshooting build failures * [GitHub Actions Integration](/docs/ci-examples/github-actions.md) - CI/CD exit code handling * [Error Handling](/docs/tutorial-advanced/logging-errors.md) - Advanced error handling patterns --- # Glossary A comprehensive glossary of terms and concepts used throughout psake documentation. ## Build Terms[​](#build-terms "Direct link to Build Terms") ### Build Script[​](#build-script "Direct link to Build Script") A PowerShell script containing psake task definitions, typically named `psakefile.ps1` or `default.ps1`. The build script defines all tasks, properties, and build logic for your project. ``` # Example build script Properties { $BuildDir = './build' } Task Default -depends Build Task Build { exec { dotnet build } } ``` ### Build File[​](#build-file "Direct link to Build File") Another term for a [build script](#build-script). The file that contains your psake build definitions. ### Build Artifacts[​](#build-artifacts "Direct link to Build Artifacts") Output files generated by the build process, such as compiled binaries, packages, documentation, or deployment files. Typically stored in a dedicated directory like `build/`, `dist/`, or `artifacts/`. ## Core Concepts[​](#core-concepts "Direct link to Core Concepts") ### Task[​](#task "Direct link to Task") A named unit of work in a psake build script. Tasks are the building blocks of your build automation. Each task can have dependencies, preconditions, postconditions, and actions to perform. ``` Task Build { # Task action Write-Host "Building project..." exec { dotnet build } } ``` **Key characteristics:** * Has a unique name * Contains PowerShell code to execute * Can depend on other tasks * Runs exactly once per build (unless called via `Invoke-Task`) See also: [Task Dependency](#task-dependency), [Default Task](#default-task) ### Task Dependency[​](#task-dependency "Direct link to Task Dependency") A relationship between tasks where one task requires another task to run first. Dependencies are declared using the `-depends` parameter. ``` Task Test -depends Build { exec { dotnet test } } ``` In this example, the `Build` task must complete successfully before `Test` runs. **Dependency resolution:** * Dependencies run before the dependent task * Each dependency runs only once per build * Circular dependencies cause build failure * Dependencies can be chained (transitive) See also: [Task](#task) ### Default Task[​](#default-task "Direct link to Default Task") The task that runs when you invoke psake without specifying a task name. Typically used to run the most common build workflow. ``` Task Default -depends Clean, Build, Test # These are equivalent: Invoke-psake Invoke-psake -taskList Default ``` **Best practices:** * Always define a Default task * Make it run your standard build workflow * Use it for the most common developer scenario ### Property[​](#property "Direct link to Property") A variable defined in the `Properties` block of a build script. Properties hold configuration values that can be overridden via parameters. ``` Properties { $Configuration = 'Release' $OutputPath = './build' $Version = '1.0.0' } ``` Properties are available throughout the build script and can be referenced in any task. See also: [Parameter](#parameter), [Properties Block](#properties-block) ### Parameter[​](#parameter "Direct link to Parameter") A value passed to the build script at invocation time. Parameters override default property values. ``` # In psakefile.ps1 Properties { $Configuration = 'Debug' # Default value } # Invoke with parameter Invoke-psake -parameters @{ Configuration = 'Release' # Override default } ``` **Common uses:** * Environment selection (Dev, Staging, Production) * Build configuration (Debug, Release) * Version numbers * Feature flags See also: [Property](#property) ### Properties Block[​](#properties-block "Direct link to Properties Block") A special block in the build script where properties are defined and initialized. Can appear multiple times in a script, and properties can be defined in any order. ``` Properties { $BuildDir = Split-Path $psake.build_script_file $Configuration = 'Release' } Properties { $ArtifactsDir = Join-Path $BuildDir 'artifacts' } ``` See also: [Property](#property) ## Execution Control[​](#execution-control "Direct link to Execution Control") ### Precondition[​](#precondition "Direct link to Precondition") A test that must pass before a task executes. If the precondition fails, the task is skipped (not executed, not failed). ``` Task Deploy -precondition { $Environment -eq 'Production' } { # Only runs if Environment is Production exec { ./deploy.ps1 } } ``` **Use cases:** * Environment-specific tasks * Conditional execution based on configuration * Skip tasks when prerequisites aren't met See also: [Postcondition](#postcondition) ### Postcondition[​](#postcondition "Direct link to Postcondition") A test that must pass after a task completes. If the postcondition fails, the build fails. ``` Task Build -postcondition { Test-Path './build/app.dll' } { exec { dotnet build -o ./build } } ``` **Use cases:** * Verify task produced expected output * Validate build artifacts exist * Ensure task side effects occurred See also: [Precondition](#precondition) ### Exec[​](#exec "Direct link to Exec") A psake function that executes an external command and automatically fails the build if the command exits with a non-zero exit code. ``` Task Build { exec { dotnet build } # Fails build if dotnet returns error } ``` Without `exec`, you'd need to manually check `$LASTEXITCODE`: ``` Task Build { dotnet build if ($LASTEXITCODE -ne 0) { throw "Build failed" } } ``` **Key features:** * Automatic error detection * Custom error messages * Retry support with `-retry` parameter * Working directory control See also: [Exit Code](#exit-code) ### Assert[​](#assert "Direct link to Assert") A psake function that tests a condition and fails the build if the condition is false. ``` Task Validate { Assert (Test-Path './src') "Source directory not found" Assert ($Version -match '^\d+\.\d+\.\d+$') "Invalid version format" } ``` **Use cases:** * Validate prerequisites * Check configuration values * Verify file existence * Test build assumptions ### Include[​](#include "Direct link to Include") A psake directive that includes tasks and functions from another PowerShell file into your build script. ``` # In psakefile.ps1 Include ./tasks/common-tasks.ps1 Include ./tasks/deploy-tasks.ps1 Task Build -depends CommonClean { # Can use tasks from included files } ``` **Use cases:** * Share tasks across multiple projects * Organize large build scripts * Reuse common build logic * Team-wide build standards See also: [Nested Build](#nested-build) ### Nested Build[​](#nested-build "Direct link to Nested Build") Invoking one psake build script from within another. Used to build multi-project solutions or orchestrate complex builds. ``` Task BuildSubprojects { Invoke-psake ./projectA/psakefile.ps1 -taskList Build Invoke-psake ./projectB/psakefile.ps1 -taskList Build } ``` **When to use:** * Multi-project solutions * Monorepo builds * Independent but related projects * Coordinated build orchestration See also: [Include](#include) ## Context and State[​](#context-and-state "Direct link to Context and State") ### $psake Variable[​](#psake-variable "Direct link to $psake Variable") A special variable available in all build scripts that contains psake context information and configuration. **Common properties:** * `$psake.build_script_file` - Full path to current build script * `$psake.build_script_dir` - Directory containing build script * `$psake.version` - psake version number * `$psake.context` - Build execution context ``` Properties { $ProjectRoot = Split-Path $psake.build_script_file $BuildNumber = $psake.build_number } Task Info { Write-Host "psake version: $($psake.version)" Write-Host "Build script: $($psake.build_script_file)" } ``` ### Build Context[​](#build-context "Direct link to Build Context") The execution environment and state of a psake build, including: * Current task being executed * Task call stack * Properties and their values * Build success/failure state Accessible via `$psake.context`. ### Build Script File[​](#build-script-file "Direct link to Build Script File") The currently executing psake build file. Accessible via `$psake.build_script_file`. ``` Properties { $ScriptDir = Split-Path $psake.build_script_file $ProjectRoot = Split-Path $ScriptDir } ``` ## Output and Formatting[​](#output-and-formatting "Direct link to Output and Formatting") ### FormatTaskName[​](#formattaskname "Direct link to FormatTaskName") A configuration function that customizes how task names are displayed during build execution. ``` FormatTaskName { param($taskName) Write-Host "===== $taskName =====" -ForegroundColor Cyan } ``` **Use cases:** * Custom task headers * Visual separation of tasks * Branding/styling build output * Integration with logging systems ### Task Documentation[​](#task-documentation "Direct link to Task Documentation") The `-description` parameter on tasks that provides help text. Displayed when running `Invoke-psake -docs`. ``` Task Build -description "Compiles the solution" { exec { dotnet build } } ``` ``` # View documentation Invoke-psake -docs # Output: # Build Compiles the solution # Test Runs all unit tests # Deploy Deploys to production ``` ## Error Handling[​](#error-handling "Direct link to Error Handling") ### Exit Code[​](#exit-code "Direct link to Exit Code") A numeric value returned by a program indicating success (0) or failure (non-zero). The `exec` function automatically checks exit codes. ``` Task Build { exec { dotnet build } # Checks $LASTEXITCODE automatically } ``` **Common exit codes:** * `0` - Success * `1` - General error * Other values - Application-specific errors See also: [Exec](#exec), [Exit Code Reference](/docs/reference/exit-codes.md) ### ContinueOnError[​](#continueonerror "Direct link to ContinueOnError") A parameter for the `exec` function that allows build to continue even if a command fails. ``` Task Test { # Continue even if tests fail exec { dotnet test } -continueOnError Write-Host "Tests completed (may have failures)" } ``` **Use cases:** * Optional operations * Best-effort tasks * Collecting multiple errors before failing ### MaxRetries[​](#maxretries "Direct link to MaxRetries") A parameter for the `exec` function that automatically retries failed commands. ``` Task Deploy { # Retry up to 3 times on failure exec { ./deploy.ps1 } -maxRetries 3 -retryTriggerErrorPattern "timeout" } ``` **Use cases:** * Network operations * Flaky external services * Deployment to cloud resources * Package publishing ## Build Framework[​](#build-framework "Direct link to Build Framework") ### Framework[​](#framework "Direct link to Framework") The .NET Framework version to target. Specified via the `-framework` parameter when invoking psake. ``` Invoke-psake -framework '4.8' Invoke-psake -framework 'net6.0' ``` **Note:** This is primarily for legacy compatibility. Modern builds typically handle framework targeting via project files. ### psake Module[​](#psake-module "Direct link to psake Module") The PowerShell module that provides the psake build automation functionality. Installed from the PowerShell Gallery. ``` # Install the module Install-Module -Name psake -Scope CurrentUser # Import the module Import-Module psake # Check version Get-Module psake | Select-Object Version ``` ### Invoke-psake[​](#invoke-psake "Direct link to Invoke-psake") The main command that executes a psake build script. ``` # Basic usage Invoke-psake # Specify build file and task Invoke-psake -buildFile ./build.ps1 -taskList Build, Test # Pass parameters Invoke-psake -parameters @{ Configuration = 'Release' } # Verbose output Invoke-psake -Verbose # Documentation mode Invoke-psake -docs ``` **Key parameters:** * `-buildFile` - Path to build script (default: psakefile.ps1) * `-taskList` - Tasks to execute (default: Default) * `-parameters` - Hashtable of parameters * `-properties` - Alternative way to pass parameters * `-framework` - .NET Framework version * `-docs` - Show task documentation * `-detailedDocs` - Show detailed task information ## CI/CD Terms[​](#cicd-terms "Direct link to CI/CD Terms") ### Continuous Integration (CI)[​](#continuous-integration-ci "Direct link to Continuous Integration (CI)") The practice of automatically building and testing code changes. psake is commonly used as the build engine in CI pipelines. **CI platforms that work with psake:** * GitHub Actions * Azure Pipelines * GitLab CI * Jenkins * TeamCity See also: [CI Examples](/docs/ci-examples/github-actions.md) ### Build Agent[​](#build-agent "Direct link to Build Agent") A machine (physical or virtual) that executes CI/CD builds. Build agents run your psake scripts in automated fashion. **Considerations for psake on build agents:** * PowerShell availability * psake module installation * Environment variables * Authentication/secrets ### Build Artifact[​](#build-artifact "Direct link to Build Artifact") See [Build Artifacts](#build-artifacts) ### Environment Variable[​](#environment-variable "Direct link to Environment Variable") Operating system variables used to pass configuration to build scripts. Commonly used in CI/CD to provide secrets and environment-specific values. ``` Properties { $ApiKey = $env:API_KEY $Environment = $env:BUILD_ENVIRONMENT ?? 'Development' } Task Deploy { if ([string]::IsNullOrEmpty($ApiKey)) { throw "API_KEY environment variable is required" } exec { ./deploy.ps1 -ApiKey $ApiKey } } ``` **Best practices:** * Never hardcode secrets in build scripts * Use environment variables for sensitive data * Provide sensible defaults for local development * Document required environment variables ## Advanced Concepts[​](#advanced-concepts "Direct link to Advanced Concepts") ### Task Alias[​](#task-alias "Direct link to Task Alias") An alternative name for a task, created by defining a task that depends on another task without additional logic. ``` Task Build { exec { dotnet build } } # Create alias Task Compile -depends Build { # No additional logic - just an alias } ``` ### Task Hook[​](#task-hook "Direct link to Task Hook") Using preconditions and postconditions to hook into task execution lifecycle. ``` Task Build ` -precondition { Write-Host "About to build..." } ` -postcondition { Write-Host "Build complete!" } ` { exec { dotnet build } } ``` ### Shared Task Library[​](#shared-task-library "Direct link to Shared Task Library") A collection of reusable tasks that can be included in multiple build scripts. ``` # In shared-tasks.ps1 Task CommonClean { Remove-Item ./build -Recurse -Force -ErrorAction SilentlyContinue } Task CommonRestore { exec { dotnet restore } } # In psakefile.ps1 Include ./shared-tasks.ps1 Task Build -depends CommonClean, CommonRestore { exec { dotnet build } } ``` ### Build Configuration[​](#build-configuration "Direct link to Build Configuration") A set of build settings, typically `Debug` or `Release`, but can be any custom configuration name. ``` Properties { $Configuration = 'Release' } Task Build { exec { dotnet build --configuration $Configuration } } # Invoke with different configuration Invoke-psake -parameters @{ Configuration = 'Debug' } ``` ### Task Graph[​](#task-graph "Direct link to Task Graph") The directed acyclic graph (DAG) of task dependencies. psake resolves this graph to determine task execution order. ``` Task Default -depends Test Task Test -depends Build Task Build -depends Clean, Restore # Task graph: Clean, Restore → Build → Test → Default # Execution order: Clean → Restore → Build → Test ``` **Key properties:** * Must be acyclic (no circular dependencies) * Determines execution order * Each node executes at most once * Respects declared dependencies ## Common Patterns[​](#common-patterns "Direct link to Common Patterns") ### Clean Build[​](#clean-build "Direct link to Clean Build") Removing all build artifacts before building, ensuring a fresh build from source. ``` Task Clean { Remove-Item ./build -Recurse -Force -ErrorAction SilentlyContinue } Task Build -depends Clean { exec { dotnet build } } ``` ### Incremental Build[​](#incremental-build "Direct link to Incremental Build") Building only what has changed since the last build. Faster than clean builds but requires careful change detection. ``` Task IncrementalBuild { # Only rebuild if source is newer than output if ((Get-Item ./src).LastWriteTime -gt (Get-Item ./build).LastWriteTime) { exec { dotnet build --no-restore } } } ``` ### Build Pipeline[​](#build-pipeline "Direct link to Build Pipeline") A series of tasks that represent the complete build workflow from clean to deploy. ``` Task Default -depends Clean, Restore, Build, Test, Package, Deploy ``` ### Conditional Task[​](#conditional-task "Direct link to Conditional Task") A task that only runs when certain conditions are met, using preconditions. ``` Properties { $Environment = 'Development' } Task DeployProd -precondition { $Environment -eq 'Production' } { exec { ./deploy-production.ps1 } } ``` ## See Also[​](#see-also "Direct link to See Also") * [Cheat Sheet](/docs/reference/cheat-sheet.md) - Quick reference for common patterns * [Configuration Reference](/docs/reference/configuration-reference.md) - All configuration options * [Exit Codes](/docs/reference/exit-codes.md) - Exit code meanings * [FAQ](/docs/troubleshooting/faq.md) - Frequently asked questions * [Tasks Tutorial](/docs/tutorial-basics/tasks.md) - Learn about tasks in detail --- # Common Errors This guide covers the most common errors you'll encounter when using psake and provides step-by-step solutions to resolve them. ## Task Not Found Errors[​](#task-not-found-errors "Direct link to Task Not Found Errors") ### Error: "Task \[TaskName] does not exist"[​](#error-task-taskname-does-not-exist "Direct link to Error: \"Task \[TaskName] does not exist\"") **Problem:** psake cannot find the task you're trying to execute. **Common Causes:** * Typo in the task name * Task is defined in a different build file * Task name is case-sensitive in some scenarios **Solution:** ``` # List all available tasks Invoke-psake -docs # Or get detailed task information Invoke-psake -buildFile .\psakefile.ps1 -docs # Verify task name spelling (case matters) Task Build { # This is "Build" # ... } # This will fail: Invoke-psake -taskList build # Wrong case ``` **Best Practice:** Always use the exact task name as defined: ``` # Define task Task CompileApp { # Task implementation } # Correct invocation Invoke-psake -taskList CompileApp ``` ### Error: "No default task specified"[​](#error-no-default-task-specified "Direct link to Error: \"No default task specified\"") **Problem:** You didn't specify which task to run and there's no default task defined. **Solution:** Define a default task in your psakefile.ps1: ``` # Option 1: Use the Task Default Task Default -depends Build, Test # Option 2: Specify the task when invoking Invoke-psake -taskList Build # Option 3: Set default in properties Properties { $DefaultTask = 'Build' } ``` ## Module Loading Issues[​](#module-loading-issues "Direct link to Module Loading Issues") ### Error: "The specified module 'psake' was not loaded"[​](#error-the-specified-module-psake-was-not-loaded "Direct link to Error: \"The specified module 'psake' was not loaded\"") **Problem:** PowerShell cannot find the psake module. **Solution:** ``` # Check if psake is installed Get-Module -ListAvailable psake # If not installed, install it Install-Module -Name psake -Scope CurrentUser -Force # If installed but not loading, import explicitly Import-Module psake -Force # Check the module path $env:PSModulePath -split [System.IO.Path]::PathSeparator ``` ### Error: "Assembly with same name is already loaded"[​](#error-assembly-with-same-name-is-already-loaded "Direct link to Error: \"Assembly with same name is already loaded\"") **Problem:** psake module or dependencies are already loaded in the session with different versions. **Solution:** ``` # Start a fresh PowerShell session pwsh # Or force reload in a new scope & { Import-Module psake -Force Invoke-psake -buildFile .\psakefile.ps1 } # For persistent issues, remove and reinstall Remove-Module psake -Force -ErrorAction SilentlyContinue Uninstall-Module psake -AllVersions Install-Module psake -Scope CurrentUser -Force ``` ### Error: "Could not load file or assembly"[​](#error-could-not-load-file-or-assembly "Direct link to Error: \"Could not load file or assembly\"") **Problem:** psake or a dependency has a corrupted installation or version mismatch. **Solution:** ``` # Reinstall psake cleanly Uninstall-Module psake -AllVersions -Force Remove-Item "$env:LOCALAPPDATA\Microsoft\Windows\PowerShell\PowerShellGet\NuGetPackages\psake*" -Recurse -Force -ErrorAction SilentlyContinue # Clear the module cache Remove-Item "$env:LOCALAPPDATA\Microsoft\Windows\PowerShell\ModuleAnalysisCache" -Force -ErrorAction SilentlyContinue # Install fresh Install-Module psake -Scope CurrentUser -Force # Verify installation Get-Module psake -ListAvailable ``` ## Path Resolution Problems[​](#path-resolution-problems "Direct link to Path Resolution Problems") ### Error: "Cannot find path" or "Path does not exist"[​](#error-cannot-find-path-or-path-does-not-exist "Direct link to Error: \"Cannot find path\" or \"Path does not exist\"") **Problem:** psake tasks reference files or directories that don't exist or use incorrect path formats. **Solution:** ``` # Use absolute paths or paths relative to $PSScriptRoot Properties { # Bad: Assumes current directory $BuildDir = '.\build' # Good: Relative to script location $BuildDir = Join-Path $PSScriptRoot 'build' # Also good: Use Resolve-Path for existing paths $SrcDir = Resolve-Path (Join-Path $PSScriptRoot 'src') } Task Build { # Verify path exists before using if (-not (Test-Path $BuildDir)) { New-Item -ItemType Directory -Path $BuildDir -Force | Out-Null } # Use cross-platform path handling $outputPath = Join-Path $BuildDir 'output' } ``` ### Error: Cross-Platform Path Issues[​](#error-cross-platform-path-issues "Direct link to Error: Cross-Platform Path Issues") **Problem:** Build scripts fail on Linux/macOS due to Windows-style paths. **Solution:** ``` Properties { # Bad: Windows-only backslashes $BuildDir = "$PSScriptRoot\build\output" # Good: Cross-platform path construction $BuildDir = Join-Path $PSScriptRoot 'build' | Join-Path -ChildPath 'output' # Or use forward slashes (works on all platforms) $BuildDir = "$PSScriptRoot/build/output" # Best: Use Path cmdlets $BuildDir = [System.IO.Path]::Combine($PSScriptRoot, 'build', 'output') } Task Clean { # Use platform-agnostic commands if (Test-Path $BuildDir) { Remove-Item $BuildDir -Recurse -Force } } ``` ### Error: "Access to the path is denied"[​](#error-access-to-the-path-is-denied "Direct link to Error: \"Access to the path is denied\"") **Problem:** Insufficient permissions or file locks prevent psake from accessing files. **Solution:** ``` Task Clean { # Handle locked files gracefully try { if (Test-Path $BuildDir) { # Wait and retry if files are locked $retries = 3 $delay = 1 for ($i = 0; $i -lt $retries; $i++) { try { Remove-Item $BuildDir -Recurse -Force -ErrorAction Stop break } catch { if ($i -eq ($retries - 1)) { throw } Write-Host "Retrying in $delay seconds..." Start-Sleep -Seconds $delay $delay *= 2 } } } } catch { Write-Warning "Could not remove $BuildDir : $_" # Optionally continue or fail # throw } } # Or run with elevated permissions (Windows) # Start-Process pwsh -Verb RunAs -ArgumentList "-Command Invoke-psake" ``` ## PowerShell Version Conflicts[​](#powershell-version-conflicts "Direct link to PowerShell Version Conflicts") ### Error: "Parameter set cannot be resolved"[​](#error-parameter-set-cannot-be-resolved "Direct link to Error: \"Parameter set cannot be resolved\"") **Problem:** Using syntax or cmdlets not available in the current PowerShell version. **Solution:** ``` # Check PowerShell version in your build script Properties { $PSVersion = $PSVersionTable.PSVersion } Task Init { Write-Host "PowerShell Version: $($PSVersion.ToString())" # Require minimum version if ($PSVersion.Major -lt 5) { throw "This build requires PowerShell 5.0 or higher. Current version: $($PSVersion.ToString())" } } # Use version-specific code Task Build { if ($PSVersion.Major -ge 7) { # PowerShell 7+ features $items = Get-ChildItem -Recurse -File -ErrorAction SilentlyContinue } else { # PowerShell 5.x compatible $items = Get-ChildItem -Recurse | Where-Object { -not $_.PSIsContainer } } } ``` ### Error: "The term 'pwsh' is not recognized"[​](#error-the-term-pwsh-is-not-recognized "Direct link to Error: \"The term 'pwsh' is not recognized\"") **Problem:** Trying to use PowerShell 7+ (pwsh) when only Windows PowerShell (powershell) is installed. **Solution:** ``` # Check which PowerShell is available if (Get-Command pwsh -ErrorAction SilentlyContinue) { # PowerShell 7+ is available $psExe = 'pwsh' } else { # Fall back to Windows PowerShell $psExe = 'powershell' } Task RunInNewSession { exec { & $psExe -NoProfile -Command "Write-Host 'Running in new session'" } } ``` ### Error: ExecutionPolicy Restrictions (Windows PowerShell)[​](#error-executionpolicy-restrictions-windows-powershell "Direct link to Error: ExecutionPolicy Restrictions (Windows PowerShell)") **Problem:** Script execution is blocked by PowerShell execution policy. **Solution:** ``` # Check current execution policy Get-ExecutionPolicy -List # Set execution policy for current user (doesn't require admin) Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser # Or bypass for a single session powershell -ExecutionPolicy Bypass -File .\build.ps1 # Or use PowerShell 7+ which has a more permissive default pwsh -File .\build.ps1 # In CI/CD, use pwsh which doesn't have this restriction # GitHub Actions example: # - shell: pwsh # run: Invoke-psake ``` ## Dependency Cycle Errors[​](#dependency-cycle-errors "Direct link to Dependency Cycle Errors") ### Error: "Circular dependency detected"[​](#error-circular-dependency-detected "Direct link to Error: \"Circular dependency detected\"") **Problem:** Tasks depend on each other in a circular manner. **Solution:** ``` # Bad: Circular dependency Task A -depends B { # Task A implementation } Task B -depends A { # Error: B depends on A, which depends on B # Task B implementation } # Good: Restructure dependencies Task A -depends Common { # Task A specific code } Task B -depends Common { # Task B specific code } Task Common { # Shared functionality } ``` ### Error: "Maximum dependency depth exceeded"[​](#error-maximum-dependency-depth-exceeded "Direct link to Error: \"Maximum dependency depth exceeded\"") **Problem:** Too many levels of task dependencies. **Solution:** ``` # Visualize task dependencies Invoke-psake -buildFile .\psakefile.ps1 -docs -detaileddocs # Simplify dependency chain # Bad: Deep nesting Task Deploy -depends Package Task Package -depends Test Task Test -depends Build Task Build -depends Restore Task Restore -depends Clean Task Clean -depends Init # Better: Flatten where possible Task Deploy -depends Package Task Package -depends Test Task Test -depends Build Task Build -depends Clean, Restore { # Build implementation } Task Clean { } Task Restore { } ``` ## Script Errors and Failures[​](#script-errors-and-failures "Direct link to Script Errors and Failures") ### Error: "Execution of \[TaskName] was aborted"[​](#error-execution-of-taskname-was-aborted "Direct link to Error: \"Execution of \[TaskName] was aborted\"") **Problem:** A task or its dependency failed, stopping execution. **Solution:** ``` # View detailed error information Invoke-psake -buildFile .\psakefile.ps1 -taskList Build -Verbose # Use exec to ensure external commands fail the build Task Build { # Bad: Error might be ignored dotnet build # Good: Use exec to capture exit codes exec { dotnet build } } # Add error handling for specific scenarios Task Build { try { exec { dotnet build -c Release } } catch { Write-Host "Build failed: $_" -ForegroundColor Red Write-Host "Stack trace: $($_.ScriptStackTrace)" throw } } ``` ### Error: "Cannot bind argument to parameter 'Value'"[​](#error-cannot-bind-argument-to-parameter-value "Direct link to Error: \"Cannot bind argument to parameter 'Value'\"") **Problem:** Passing incorrect parameter types to psake or tasks. **Solution:** ``` # Ensure parameters are properly defined Properties { $Configuration = 'Debug' $BuildNumber = 0 $EnableTests = $true } # Pass parameters correctly Invoke-psake -buildFile .\psakefile.ps1 ` -parameters @{ Configuration = 'Release' BuildNumber = 42 EnableTests = $false } # Validate parameter types in tasks Task Build { if ($BuildNumber -isnot [int]) { throw "BuildNumber must be an integer, got: $($BuildNumber.GetType().Name)" } } ``` ## Build File Loading Issues[​](#build-file-loading-issues "Direct link to Build File Loading Issues") ### Error: "Could not find build file"[​](#error-could-not-find-build-file "Direct link to Error: \"Could not find build file\"") **Problem:** psake cannot locate the specified build file. **Solution:** ``` # Specify full path to build file $buildFile = Join-Path $PSScriptRoot 'psakefile.ps1' Invoke-psake -buildFile $buildFile # Or navigate to the directory first Set-Location $PSScriptRoot Invoke-psake # Will find psakefile.ps1 in current directory # Verify the file exists if (-not (Test-Path $buildFile)) { throw "Build file not found: $buildFile" } ``` ### Error: Syntax errors in build file[​](#error-syntax-errors-in-build-file "Direct link to Error: Syntax errors in build file") **Problem:** The psakefile.ps1 has PowerShell syntax errors. **Solution:** ``` # Validate syntax before running $buildFile = '.\psakefile.ps1' $errors = $null $null = [System.Management.Automation.PSParser]::Tokenize( (Get-Content $buildFile -Raw), [ref]$errors ) if ($errors) { Write-Error "Syntax errors in build file:" $errors | ForEach-Object { Write-Error "$($_.Message) at line $($_.Token.StartLine)" } } else { Invoke-psake -buildFile $buildFile } # Or use PSScriptAnalyzer for better validation Install-Module PSScriptAnalyzer -Scope CurrentUser Invoke-ScriptAnalyzer -Path .\psakefile.ps1 ``` ## See Also[​](#see-also "Direct link to See Also") * [Debugging Guide](/docs/troubleshooting/debugging-guide.md) - Comprehensive debugging techniques * [FAQ](/docs/troubleshooting/faq.md) - Frequently asked questions * [How to Fail a Build](/docs/tutorial-basics/how-to-fail-a-build.md) - Controlling build failures * [Logging Errors](/docs/tutorial-advanced/logging-errors.md) - Error logging strategies * [Build Script Resilience](/docs/tutorial-advanced/build-script-resilience.md) - Writing robust builds --- # Debugging Guide This comprehensive guide covers debugging techniques for psake build scripts, from basic output inspection to advanced PowerShell debugging. ## Quick Debugging Checklist[​](#quick-debugging-checklist "Direct link to Quick Debugging Checklist") When your build fails or behaves unexpectedly: 1. **Enable verbose output:** `Invoke-psake -Verbose` 2. **Check task execution order:** `Invoke-psake -docs -detaileddocs` 3. **Verify properties:** Add `Write-Host` statements in your Properties block 4. **Test individual tasks:** Run tasks one at a time 5. **Check external command output:** Use `exec` with verbose flag 6. **Use the PowerShell debugger:** Set breakpoints for complex issues ## Verbose Output[​](#verbose-output "Direct link to Verbose Output") ### Basic Verbose Mode[​](#basic-verbose-mode "Direct link to Basic Verbose Mode") The simplest debugging approach is enabling verbose output: ``` # Enable verbose output Invoke-psake -buildFile .\psakefile.ps1 -Verbose # Combine with specific task Invoke-psake -taskList Build -Verbose # Also enable debug output Invoke-psake -Verbose -Debug ``` This shows: * Task execution order * Dependency resolution * Property values * Detailed execution flow ### Adding Custom Verbose Messages[​](#adding-custom-verbose-messages "Direct link to Adding Custom Verbose Messages") ``` Properties { $Configuration = 'Release' $BuildDir = Join-Path $PSScriptRoot 'build' Write-Verbose "Properties initialized:" Write-Verbose " Configuration: $Configuration" Write-Verbose " BuildDir: $BuildDir" } Task Build { Write-Verbose "Starting build process" Write-Verbose "Current directory: $PWD" exec { dotnet build } -Verbose Write-Verbose "Build completed successfully" } ``` ### Controlling Output Levels[​](#controlling-output-levels "Direct link to Controlling Output Levels") ``` Properties { $VerbosePreference = 'Continue' # Always show verbose # Or: 'SilentlyContinue' to hide verbose } Task Build { # This will show even without -Verbose flag Write-Verbose "This is verbose output" # Different output levels Write-Debug "Debug level information" Write-Information "Informational message" Write-Warning "Warning message" Write-Host "Always visible output" } ``` ## Using PowerShell Debugger[​](#using-powershell-debugger "Direct link to Using PowerShell Debugger") ### Setting Breakpoints[​](#setting-breakpoints "Direct link to Setting Breakpoints") **Method 1: Line breakpoints** ``` # Before running psake Set-PSBreakpoint -Script .\psakefile.ps1 -Line 25 # Run psake (will pause at line 25) Invoke-psake # At the breakpoint: # - Inspect variables: $Configuration, $BuildDir, etc. # - Step through: s (step into), v (step over), o (step out) # - Continue: c # - Quit debugging: q ``` **Method 2: Function/Task breakpoints** ``` # Break when a specific task runs Set-PSBreakpoint -Script .\psakefile.ps1 -Command Build # Run psake Invoke-psake -taskList Build ``` **Method 3: Variable breakpoints** ``` # Break when a variable is read or written Set-PSBreakpoint -Variable Configuration -Mode ReadWrite -Script .\psakefile.ps1 Invoke-psake ``` **Method 4: Inline breakpoint** Add directly in your psakefile.ps1: ``` Task Build { # Execution will pause here Wait-Debugger # Or use the older method Set-PSBreakpoint -Script $PSCommandPath -Line $MyInvocation.ScriptLineNumber exec { dotnet build } } ``` ### Debugger Commands[​](#debugger-commands "Direct link to Debugger Commands") When paused at a breakpoint: ``` # Inspect variables $Configuration $BuildDir Get-Variable # Evaluate expressions Test-Path $BuildDir $PSScriptRoot # List local variables Get-Variable -Scope Local # View call stack Get-PSCallStack # Step commands s # Step into v # Step over (or 'o' in older versions) o # Step out c # Continue execution q # Quit debugger # List breakpoints Get-PSBreakpoint # Remove breakpoints Get-PSBreakpoint | Remove-PSBreakpoint ``` ### Debugging in VS Code[​](#debugging-in-vs-code "Direct link to Debugging in VS Code") If using Visual Studio Code with PowerShell extension: 1. Open your psakefile.ps1 in VS Code 2. Click in the gutter to set breakpoints (red dots) 3. Press F5 or use "Run and Debug" 4. Add launch configuration in `.vscode/launch.json`: ``` { "version": "0.2.0", "configurations": [ { "name": "Debug psake Build", "type": "PowerShell", "request": "launch", "script": "${workspaceFolder}/psakefile.ps1", "args": [], "cwd": "${workspaceFolder}" } ] } ``` Or debug the Invoke-psake call: ``` { "name": "Debug psake", "type": "PowerShell", "request": "launch", "script": "Invoke-psake", "args": [ "-buildFile", "${workspaceFolder}/psakefile.ps1", "-taskList", "Build" ] } ``` ## Inspecting Task Execution[​](#inspecting-task-execution "Direct link to Inspecting Task Execution") ### View Task Documentation[​](#view-task-documentation "Direct link to View Task Documentation") ``` # List all tasks with descriptions Invoke-psake -docs # Detailed documentation with dependencies Invoke-psake -detaileddocs ``` **Example output:** ``` Name Depends On ---- ---------- Build Clean, Restore Test Build Package Test Deploy Package ``` ### Understanding Task Execution Order[​](#understanding-task-execution-order "Direct link to Understanding Task Execution Order") ``` # Add task execution logging FormatTaskName { param($taskName) Write-Host "======================================" -ForegroundColor Cyan Write-Host "Executing Task: $taskName" -ForegroundColor Cyan Write-Host "======================================" -ForegroundColor Cyan } Task Build -depends Clean, Restore { Write-Host "Build task running" } ``` ### Tracing Task Dependencies[​](#tracing-task-dependencies "Direct link to Tracing Task Dependencies") ``` # Create a helper to trace execution Properties { $script:ExecutionTrace = @() } FormatTaskName { param($taskName) $script:ExecutionTrace += @{ Task = $taskName Timestamp = Get-Date } Write-Host "[$(Get-Date -Format 'HH:mm:ss')] Starting: $taskName" -ForegroundColor Green } Task Build -depends Test { # Build logic } Task Cleanup { # Show execution trace at the end Write-Host "`nExecution Trace:" -ForegroundColor Yellow $script:ExecutionTrace | ForEach-Object { Write-Host " $($_.Timestamp.ToString('HH:mm:ss.fff')) - $($_.Task)" } } ``` ### Testing Task Dependencies Individually[​](#testing-task-dependencies-individually "Direct link to Testing Task Dependencies Individually") ``` # Test each task independently Invoke-psake -taskList Clean Invoke-psake -taskList Restore Invoke-psake -taskList Build # This helps identify which task is failing ``` ## Logging Strategies[​](#logging-strategies "Direct link to Logging Strategies") ### Basic File Logging[​](#basic-file-logging "Direct link to Basic File Logging") ``` Properties { $LogFile = Join-Path $PSScriptRoot 'build.log' # Initialize log file "Build started at $(Get-Date)" | Set-Content $LogFile } function Write-BuildLog { param([string]$Message) $timestamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss' $logMessage = "[$timestamp] $Message" # Write to console and file Write-Host $logMessage $logMessage | Add-Content $script:LogFile } Task Build { Write-BuildLog "Starting build for configuration: $Configuration" try { exec { dotnet build } Write-BuildLog "Build succeeded" } catch { Write-BuildLog "Build failed: $_" throw } } ``` ### Structured Logging[​](#structured-logging "Direct link to Structured Logging") ``` Properties { $LogFile = Join-Path $PSScriptRoot 'build.json' $script:BuildLog = @{ StartTime = Get-Date Tasks = @() Properties = @{ Configuration = $Configuration BuildNumber = $BuildNumber } } } function Add-TaskLog { param( [string]$TaskName, [string]$Status, [string]$Message = '', [timespan]$Duration ) $script:BuildLog.Tasks += @{ Task = $TaskName Status = $Status Message = $Message Duration = $Duration.TotalSeconds Timestamp = Get-Date } } FormatTaskName { param($taskName) $script:CurrentTaskStart = Get-Date $script:CurrentTaskName = $taskName } Task Build { $taskStart = Get-Date try { exec { dotnet build } $duration = (Get-Date) - $taskStart Add-TaskLog -TaskName 'Build' -Status 'Success' -Duration $duration } catch { $duration = (Get-Date) - $taskStart Add-TaskLog -TaskName 'Build' -Status 'Failed' -Message $_.Exception.Message -Duration $duration throw } } Task Cleanup { # Save log to file $script:BuildLog.EndTime = Get-Date $script:BuildLog.Duration = ($script:BuildLog.EndTime - $script:BuildLog.StartTime).TotalSeconds $script:BuildLog | ConvertTo-Json -Depth 10 | Set-Content $LogFile Write-Host "Build log saved to: $LogFile" } ``` ### Logging External Command Output[​](#logging-external-command-output "Direct link to Logging External Command Output") ``` Task Build { $logFile = Join-Path $PSScriptRoot 'build-output.log' # Capture and log command output $output = exec { dotnet build } -PassThru $output | Set-Content $logFile # Or redirect output exec { dotnet build 2>&1 | Tee-Object -FilePath $logFile } } ``` ### Integration with Logging Frameworks[​](#integration-with-logging-frameworks "Direct link to Integration with Logging Frameworks") ``` # Using PSFramework (install: Install-Module PSFramework) Import-Module PSFramework Properties { # Configure logging Set-PSFLoggingProvider -Name logfile -FilePath './logs/build.log' -Enabled $true } Task Build { Write-PSFMessage -Level Important "Starting build" try { exec { dotnet build } Write-PSFMessage -Level Significant "Build completed successfully" } catch { Write-PSFMessage -Level Error "Build failed" -ErrorRecord $_ throw } } ``` ## Debugging External Commands[​](#debugging-external-commands "Direct link to Debugging External Commands") ### Using exec with Verbose Output[​](#using-exec-with-verbose-output "Direct link to Using exec with Verbose Output") ``` Task Build { # Show command being executed exec { dotnet build } -Verbose # Or manually log it $cmd = 'dotnet build' Write-Host "Executing: $cmd" -ForegroundColor Yellow exec { & $cmd } } ``` ### Capturing Exit Codes[​](#capturing-exit-codes "Direct link to Capturing Exit Codes") ``` Task Build { # exec will throw on non-zero exit code try { exec { dotnet build } } catch { Write-Host "Command failed with exit code: $LASTEXITCODE" Write-Host "Error: $_" throw } } ``` ### Debugging Command Failures[​](#debugging-command-failures "Direct link to Debugging Command Failures") ``` Task Build { # Run command with error handling $ErrorActionPreference = 'Continue' $output = dotnet build 2>&1 if ($LASTEXITCODE -ne 0) { Write-Host "Build failed with exit code: $LASTEXITCODE" -ForegroundColor Red Write-Host "Output:" -ForegroundColor Red $output | ForEach-Object { Write-Host $_ -ForegroundColor Red } # Optionally save to file for analysis $output | Set-Content './build-error.log' throw "Build failed. See build-error.log for details." } } ``` ### Testing Commands Interactively[​](#testing-commands-interactively "Direct link to Testing Commands Interactively") ``` Task Build { # Use -WhatIf pattern for dry runs if ($WhatIfPreference) { Write-Host "Would execute: dotnet build -c $Configuration" return } exec { dotnet build -c $Configuration } } # Invoke with: # Invoke-psake -WhatIf ``` ## Common Debugging Scenarios[​](#common-debugging-scenarios "Direct link to Common Debugging Scenarios") ### Scenario 1: Task Not Running as Expected[​](#scenario-1-task-not-running-as-expected "Direct link to Scenario 1: Task Not Running as Expected") **Problem:** A task doesn't execute or executes in wrong order. **Debug steps:** ``` # 1. Check if task exists Invoke-psake -docs # 2. View task dependencies Invoke-psake -detaileddocs # 3. Add execution tracing FormatTaskName { param($taskName) Write-Host ">>> Executing: $taskName at $(Get-Date -Format 'HH:mm:ss.fff')" } # 4. Check preconditions Task Build -precondition { $Configuration -eq 'Release' } { Write-Host "Precondition passed: Configuration = $Configuration" # Task logic } ``` ### Scenario 2: Properties Have Unexpected Values[​](#scenario-2-properties-have-unexpected-values "Direct link to Scenario 2: Properties Have Unexpected Values") **Problem:** Properties don't have the values you expect. **Debug steps:** ``` Properties { $Configuration = 'Debug' $BuildNumber = $env:BUILD_NUMBER ?? '0' # Debug output Write-Host "=== Property Values ===" -ForegroundColor Cyan Get-Variable -Scope Script | Where-Object { $_.Name -in @('Configuration', 'BuildNumber') } | ForEach-Object { Write-Host "$($_.Name) = $($_.Value)" } } # Or create a debug task Task ShowProperties { Write-Host "Configuration: $Configuration" Write-Host "BuildNumber: $BuildNumber" Write-Host "PSScriptRoot: $PSScriptRoot" # Show all properties Get-Variable -Scope Script | Format-Table Name, Value } # Run: Invoke-psake -taskList ShowProperties ``` ### Scenario 3: Path Issues[​](#scenario-3-path-issues "Direct link to Scenario 3: Path Issues") **Problem:** Files or directories not found. **Debug steps:** ``` Task Build { # Debug paths Write-Host "Current directory: $PWD" Write-Host "Script root: $PSScriptRoot" Write-Host "Build directory: $BuildDir" # Verify paths exist @($BuildDir, $SrcDir, $TestDir) | ForEach-Object { if (Test-Path $_) { Write-Host "✓ Found: $_" -ForegroundColor Green } else { Write-Host "✗ Missing: $_" -ForegroundColor Red } } # List contents Write-Host "`nContents of $SrcDir:" Get-ChildItem $SrcDir | ForEach-Object { Write-Host " $($_.Name)" } } ``` ### Scenario 4: Dependency Cycle[​](#scenario-4-dependency-cycle "Direct link to Scenario 4: Dependency Cycle") **Problem:** Circular dependency error. **Debug steps:** ``` # Use detailed docs to visualize dependencies Invoke-psake -detaileddocs # Create a dependency graph Task ShowDependencies { $tasks = @{ Build = @('Clean', 'Restore') Test = @('Build') Package = @('Test') Deploy = @('Package') } Write-Host "Task Dependency Graph:" $tasks.GetEnumerator() | ForEach-Object { Write-Host "$($_.Key) -> $($_.Value -join ', ')" } } ``` ## Performance Debugging[​](#performance-debugging "Direct link to Performance Debugging") ### Measuring Task Execution Time[​](#measuring-task-execution-time "Direct link to Measuring Task Execution Time") ``` Properties { $script:TaskTimings = @{} } FormatTaskName { param($taskName) $script:CurrentTask = $taskName $script:TaskTimings[$taskName] = @{ Start = Get-Date } Write-Host "[$([Math]::Round(((Get-Date) - $script:BuildStart).TotalSeconds, 2))s] Starting: $taskName" } # Override TaskTearDown to capture end time TaskTearDown { $end = Get-Date $start = $script:TaskTimings[$script:CurrentTask].Start $duration = ($end - $start).TotalSeconds $script:TaskTimings[$script:CurrentTask].Duration = $duration Write-Host "[$([Math]::Round(((Get-Date) - $script:BuildStart).TotalSeconds, 2))s] Completed: $script:CurrentTask (took $([Math]::Round($duration, 2))s)" } Properties { $script:BuildStart = Get-Date } Task Summary { Write-Host "`n=== Task Performance ===" -ForegroundColor Yellow $script:TaskTimings.GetEnumerator() | Sort-Object { $_.Value.Duration } -Descending | ForEach-Object { $duration = [Math]::Round($_.Value.Duration, 2) Write-Host "$($_.Key): ${duration}s" } $total = [Math]::Round(((Get-Date) - $script:BuildStart).TotalSeconds, 2) Write-Host "`nTotal build time: ${total}s" -ForegroundColor Green } ``` ### Identifying Slow Operations[​](#identifying-slow-operations "Direct link to Identifying Slow Operations") ``` function Measure-BuildStep { param( [string]$Name, [scriptblock]$ScriptBlock ) Write-Host "Starting: $Name..." -NoNewline $start = Get-Date try { & $ScriptBlock $duration = ((Get-Date) - $start).TotalSeconds Write-Host " completed in $([Math]::Round($duration, 2))s" -ForegroundColor Green } catch { $duration = ((Get-Date) - $start).TotalSeconds Write-Host " failed after $([Math]::Round($duration, 2))s" -ForegroundColor Red throw } } Task Build { Measure-BuildStep "Restore packages" { exec { dotnet restore } } Measure-BuildStep "Compile code" { exec { dotnet build --no-restore } } Measure-BuildStep "Run tests" { exec { dotnet test --no-build } } } ``` ## Advanced Debugging Techniques[​](#advanced-debugging-techniques "Direct link to Advanced Debugging Techniques") ### Remote Debugging[​](#remote-debugging "Direct link to Remote Debugging") ``` # On remote machine, enable PowerShell remoting Enable-PSRemoting -Force # From local machine $session = New-PSSession -ComputerName remote-server # Run psake remotely with debugging Invoke-Command -Session $session -ScriptBlock { Import-Module psake Set-PSBreakpoint -Script C:\builds\psakefile.ps1 -Line 50 Invoke-psake -buildFile C:\builds\psakefile.ps1 } # Enter interactive session for debugging Enter-PSSession $session ``` ### Debugging in CI/CD[​](#debugging-in-cicd "Direct link to Debugging in CI/CD") ``` # Add CI-specific debugging Properties { $IsCI = $env:CI -eq 'true' } Task Build { if ($IsCI) { # More verbose output in CI $VerbosePreference = 'Continue' $DebugPreference = 'Continue' # Dump environment Write-Host "=== Environment Variables ===" Get-ChildItem env: | Sort-Object Name | ForEach-Object { Write-Host "$($_.Name) = $($_.Value)" } } exec { dotnet build } -Verbose:$IsCI } ``` ### Creating Debug Snapshots[​](#creating-debug-snapshots "Direct link to Creating Debug Snapshots") ``` Task CreateDebugSnapshot { $snapshotDir = Join-Path $PSScriptRoot 'debug-snapshot' New-Item -ItemType Directory -Path $snapshotDir -Force | Out-Null # Capture environment state @{ PowerShellVersion = $PSVersionTable EnvironmentVariables = Get-ChildItem env: | ForEach-Object { @{$_.Name = $_.Value} } InstalledModules = Get-Module -ListAvailable | Select-Object Name, Version WorkingDirectory = $PWD Properties = @{ Configuration = $Configuration BuildDir = $BuildDir } Timestamp = Get-Date } | ConvertTo-Json -Depth 10 | Set-Content "$snapshotDir/snapshot.json" # Copy relevant files Copy-Item .\psakefile.ps1 $snapshotDir Copy-Item .\*.log $snapshotDir -ErrorAction SilentlyContinue Write-Host "Debug snapshot saved to: $snapshotDir" } ``` ## See Also[​](#see-also "Direct link to See Also") * [Common Errors](/docs/troubleshooting/common-errors.md) - Solutions to frequent errors * [FAQ](/docs/troubleshooting/faq.md) - Frequently asked questions * [Debug Script](/docs/tutorial-advanced/debug-script.md) - Basic debugging tutorial * [Logging Errors](/docs/tutorial-advanced/logging-errors.md) - Error logging strategies * [Build Script Resilience](/docs/tutorial-advanced/build-script-resilience.md) - Writing robust builds --- # Frequently Asked Questions Quick answers to common questions about psake. ## Installation[​](#installation "Direct link to Installation") ### What are the system requirements for psake?[​](#what-are-the-system-requirements-for-psake "Direct link to What are the system requirements for psake?") **psake requires:** * PowerShell 5.0 or later (Windows PowerShell) * OR PowerShell 7+ (PowerShell Core) for cross-platform support * No other external dependencies **Recommended:** * PowerShell 7+ for best cross-platform compatibility * Latest version of psake from the PowerShell Gallery ### How do I install psake?[​](#how-do-i-install-psake "Direct link to How do I install psake?") ``` # Install for current user (no admin required) Install-Module -Name psake -Scope CurrentUser # Install globally (requires admin/sudo) Install-Module -Name psake -Scope AllUsers # Install specific version Install-Module -Name psake -RequiredVersion 4.9.0 ``` See the [Installing psake](/docs/tutorial-basics/installing.md) guide for more details. ### Can I use psake without installing it?[​](#can-i-use-psake-without-installing-it "Direct link to Can I use psake without installing it?") Yes, you can use psake without installing it system-wide: ``` # Option 1: Install to a local directory Save-Module -Name psake -Path ./modules # Then load it Import-Module ./modules/psake # Option 2: Use in a Docker container docker run -v ${PWD}:/workspace -w /workspace mcr.microsoft.com/powershell:latest pwsh -c "Install-Module psake -Force; Invoke-psake" # Option 3: Use PSDepend in your project # Create requirements.psd1: @{ psake = 'latest' } # Install dependencies Install-Module PSDepend -Scope CurrentUser Invoke-PSDepend -Path ./requirements.psd1 -Install ``` ### How do I update psake to the latest version?[​](#how-do-i-update-psake-to-the-latest-version "Direct link to How do I update psake to the latest version?") ``` # Update to latest version Update-Module -Name psake # Or remove old version and install fresh Uninstall-Module -Name psake -AllVersions Install-Module -Name psake ``` ### Can I run multiple versions of psake?[​](#can-i-run-multiple-versions-of-psake "Direct link to Can I run multiple versions of psake?") Yes, but only one version can be loaded in a PowerShell session at a time: ``` # Install multiple versions Install-Module psake -RequiredVersion 4.9.0 Install-Module psake -RequiredVersion 4.8.0 # View installed versions Get-Module psake -ListAvailable # Load specific version Import-Module psake -RequiredVersion 4.9.0 # Or use -MinimumVersion Import-Module psake -MinimumVersion 4.8 ``` ## Basic Usage[​](#basic-usage "Direct link to Basic Usage") ### What's the difference between a task, property, and parameter?[​](#whats-the-difference-between-a-task-property-and-parameter "Direct link to What's the difference between a task, property, and parameter?") **Task:** A named unit of work in your build script ``` Task Build { # Work to perform } ``` **Property:** A variable defined in the build script ``` Properties { $BuildConfiguration = 'Release' } ``` **Parameter:** A value passed from the command line ``` # In psakefile.ps1 Properties { $Version = '1.0.0' # Default value } # Invoke with parameter Invoke-psake -parameters @{ Version = '2.0.0' } ``` See [Parameters and Properties](/docs/tutorial-basics/parameters-properties.md) for details. ### How do I pass parameters to my psake build?[​](#how-do-i-pass-parameters-to-my-psake-build "Direct link to How do I pass parameters to my psake build?") ``` # Define properties with default values Properties { $Configuration = 'Debug' $OutputPath = './build' } # Pass parameters when invoking Invoke-psake -parameters @{ Configuration = 'Release' OutputPath = 'C:/builds/output' } # Or use environment variables $env:BUILD_CONFIG = 'Release' Properties { $Configuration = $env:BUILD_CONFIG ?? 'Debug' } ``` ### Can I run multiple tasks at once?[​](#can-i-run-multiple-tasks-at-once "Direct link to Can I run multiple tasks at once?") Yes, specify multiple tasks in the taskList: ``` # Run tasks in order Invoke-psake -taskList Clean, Build, Test # Tasks will execute in the order specified, along with their dependencies ``` The tasks will run sequentially in the order you specify, and psake will automatically run any task dependencies. ### How do I see all available tasks in a build script?[​](#how-do-i-see-all-available-tasks-in-a-build-script "Direct link to How do I see all available tasks in a build script?") ``` # List all tasks with descriptions Invoke-psake -docs # Detailed documentation including dependencies Invoke-psake -detaileddocs # For a specific build file Invoke-psake -buildFile ./custom.ps1 -docs ``` ### What's the difference between `-depends` and calling tasks directly?[​](#whats-the-difference-between--depends-and-calling-tasks-directly "Direct link to whats-the-difference-between--depends-and-calling-tasks-directly") **Using `-depends`:** Declares a dependency relationship. psake ensures dependencies run first and only once: ``` Task Build -depends Clean, Compile { # Clean and Compile run first (if not already run) } Task Test -depends Build { # Build runs first (which also runs Clean and Compile) } # Invoke-psake -taskList Test # Execution order: Clean → Compile → Build → Test # Each task runs exactly once ``` **Calling tasks directly:** Executes the task every time it's called: ``` Task Build { Invoke-Task Clean Invoke-Task Compile # These run every time Build is called } ``` **Best Practice:** Use `-depends` for declaring task relationships. ### Can I use psake with non-.NET projects?[​](#can-i-use-psake-with-non-net-projects "Direct link to Can I use psake with non-.NET projects?") Absolutely! psake is a general-purpose build tool. It's commonly used for: * **Node.js projects** - npm install, webpack builds, testing * **Python projects** - pip install, pytest, package building * **Go projects** - go build, go test * **Docker** - Building and pushing images * **Documentation** - Static site generators * **Infrastructure** - Terraform, Ansible automation * **Any scripting** - File processing, data transformation Example for Node.js: ``` Task Install { exec { npm install } } Task Build { exec { npm run build } } Task Test { exec { npm test } } ``` ## Performance[​](#performance "Direct link to Performance") ### Why is my build slow?[​](#why-is-my-build-slow "Direct link to Why is my build slow?") Common causes and solutions: **1. Unnecessary dependency resolution** ``` # Slow: Running restore on every build Task Build { exec { dotnet restore } exec { dotnet build } } # Faster: Separate restore task Task Restore { exec { dotnet restore } } Task Build -depends Restore { exec { dotnet build --no-restore } } ``` **2. Not using incremental builds** ``` # Slow: Always clean and rebuild everything Task Build -depends Clean { exec { dotnet build } } # Faster: Add an IncrementalBuild task Task IncrementalBuild { exec { dotnet build } } Task RebuildAll -depends Clean, Build { # Full rebuild only when needed } ``` **3. Serial execution of independent tasks** ``` # Slow: Running tests serially Task Test -depends UnitTest, IntegrationTest # Consider: Use parallel execution for independent tasks Task Test { # Run tests in parallel using PowerShell jobs or dotnet test $jobs = @( Start-Job { dotnet test ./tests/Unit.Tests --no-build } Start-Job { dotnet test ./tests/Integration.Tests --no-build } ) $jobs | Wait-Job | Receive-Job } ``` ### How can I speed up my psake builds?[​](#how-can-i-speed-up-my-psake-builds "Direct link to How can I speed up my psake builds?") **1. Use caching in CI/CD** ``` # GitHub Actions example - uses: actions/cache@v4 with: path: ~/.nuget/packages key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj') }} ``` **2. Skip unnecessary tasks** ``` Properties { $SkipTests = $false } Task Build { exec { dotnet build } } Task Test -depends Build -precondition { -not $SkipTests } { exec { dotnet test } } # Invoke-psake -parameters @{ SkipTests = $true } ``` **3. Use build output caching** ``` Task Compile { $needsRebuild = $false Get-ChildItem src/*.cs | ForEach-Object { $source = $_ $output = Join-Path build "$($_.BaseName).dll" if (-not (Test-Path $output) -or $source.LastWriteTime -gt (Get-Item $output).LastWriteTime) { $needsRebuild = $true } } if ($needsRebuild) { exec { dotnet build } } else { Write-Host "No rebuild needed" } } ``` ### Does psake support parallel task execution?[​](#does-psake-support-parallel-task-execution "Direct link to Does psake support parallel task execution?") psake executes tasks sequentially to maintain dependency order. However, you can: **1. Run independent operations in parallel within a task** ``` Task BuildAll { # Use PowerShell jobs for parallel execution $jobs = @( Start-Job -ScriptBlock { dotnet build ./ProjectA } Start-Job -ScriptBlock { dotnet build ./ProjectB } ) $jobs | Wait-Job | Receive-Job $jobs | Remove-Job # Or use ForEach-Object -Parallel (PowerShell 7+) @('ProjectA', 'ProjectB') | ForEach-Object -Parallel { dotnet build ./$_ } -ThrottleLimit 4 } ``` **2. Use external tools for parallel builds** ``` Task BuildSolution { # MSBuild can build projects in parallel exec { msbuild /m /p:Configuration=Release } # Or dotnet build exec { dotnet build -m } } ``` ## Integration[​](#integration "Direct link to Integration") ### Can I use psake with CI/CD systems?[​](#can-i-use-psake-with-cicd-systems "Direct link to Can I use psake with CI/CD systems?") Yes! psake works with all major CI/CD platforms: * **GitHub Actions** - See [GitHub Actions guide](/docs/ci-examples/github-actions.md) * **Azure Pipelines** - See [Azure Pipelines guide](/docs/ci-examples/azure-pipelines.md) * **GitLab CI** - See [GitLab CI guide](/docs/ci-examples/gitlab-ci.md) * **Jenkins, TeamCity, etc.** - Just run PowerShell scripts Basic pattern: ``` # Install psake pwsh -Command "Install-Module psake -Force" # Run build pwsh -Command "Invoke-psake -buildFile ./psakefile.ps1 -taskList Build" ``` ### How do I integrate psake with MSBuild?[​](#how-do-i-integrate-psake-with-msbuild "Direct link to How do I integrate psake with MSBuild?") ``` # Call MSBuild from psake Task Build { exec { msbuild /t:Build /p:Configuration=Release MySolution.sln } } # Or call psake from MSBuild (in .csproj/.targets) # Better: Use psake to orchestrate MSBuild Properties { $SolutionFile = 'MySolution.sln' $Configuration = 'Release' } Task Restore { exec { msbuild /t:Restore $SolutionFile } } Task Build -depends Restore { exec { msbuild /t:Build /p:Configuration=$Configuration $SolutionFile } } Task Pack -depends Build { exec { msbuild /t:Pack /p:Configuration=$Configuration $SolutionFile } } ``` ### Can I call psake tasks from another psake script?[​](#can-i-call-psake-tasks-from-another-psake-script "Direct link to Can I call psake tasks from another psake script?") Yes, using several methods: **Method 1: Include another build file** ``` # In main psakefile.ps1 Include .\shared-tasks.ps1 Task Build -depends SharedTask { # SharedTask is from shared-tasks.ps1 } ``` **Method 2: Invoke another build file** ``` Task BuildSubproject { Invoke-psake -buildFile ./subproject/psakefile.ps1 -taskList Build } ``` **Method 3: Nested build (legacy)** ``` Task BuildAll { Invoke-psake ./project1/psakefile.ps1 -taskList Build Invoke-psake ./project2/psakefile.ps1 -taskList Build } ``` See [Nested Builds](/docs/tutorial-basics/nested-build.md) for details. ### How do I use psake with Docker?[​](#how-do-i-use-psake-with-docker "Direct link to How do I use psake with Docker?") ``` # Dockerfile 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 CMD ["pwsh", "-Command", "Invoke-psake -taskList Build"] ``` Or in your psakefile.ps1: ``` Task BuildDockerImage { exec { docker build -t myapp:latest . } } Task RunInDocker { exec { docker run --rm -v ${PWD}:/workspace -w /workspace mcr.microsoft.com/powershell pwsh -c "Install-Module psake -Force; Invoke-psake" } } ``` ### Can I use psake with Invoke-Build or other build tools?[​](#can-i-use-psake-with-invoke-build-or-other-build-tools "Direct link to Can I use psake with Invoke-Build or other build tools?") Yes, they can coexist: ``` # Call Invoke-Build from psake Task RunInvokeBuild { exec { Invoke-Build -File ./project.build.ps1 } } # Call psake from Invoke-Build task RunPsake { exec { Invoke-psake -buildFile ./psakefile.ps1 } } # Use the right tool for each scenario: # - psake: Simple, declarative builds with task dependencies # - Invoke-Build: More complex build logic, better for large scripts ``` ## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") ### How do I debug my build script?[​](#how-do-i-debug-my-build-script "Direct link to How do I debug my build script?") See the [Debugging Guide](/docs/troubleshooting/debugging-guide.md) for comprehensive debugging techniques. Quick tips: ``` # Enable verbose output Invoke-psake -Verbose # Add debug output Task Build { Write-Host "Configuration: $Configuration" Write-Host "BuildDir: $BuildDir" exec { dotnet build } -Verbose } # Use PowerShell debugger Set-PSBreakpoint -Script .\psakefile.ps1 -Line 42 Invoke-psake ``` ### Where can I find more help?[​](#where-can-i-find-more-help "Direct link to Where can I find more help?") * **Documentation:** [psake.readthedocs.io](https://psake.readthedocs.io/) * **GitHub Issues:** [github.com/psake/psake/issues](https://github.com/psake/psake/issues) * **Source Code:** [github.com/psake/psake](https://github.com/psake/psake) * **Examples:** Check the `examples/` directory in the psake repository ### How do I report a bug or request a feature?[​](#how-do-i-report-a-bug-or-request-a-feature "Direct link to How do I report a bug or request a feature?") Visit the [psake GitHub repository](https://github.com/psake/psake) and: 1. Search existing issues to avoid duplicates 2. Create a new issue with: * Clear description of the problem or feature * Minimal reproducible example * Your PowerShell version (`$PSVersionTable`) * Your psake version (`Get-Module psake | Select-Object Version`) ## See Also[​](#see-also "Direct link to See Also") * [Common Errors](/docs/troubleshooting/common-errors.md) - Solutions to frequent problems * [Debugging Guide](/docs/troubleshooting/debugging-guide.md) - Debugging techniques * [Installing psake](/docs/tutorial-basics/installing.md) - Installation guide * [Running psake](/docs/tutorial-basics/run-psake.md) - Basic usage * [Parameters and Properties](/docs/tutorial-basics/parameters-properties.md) - Parameter handling --- # Access Functions in Another File Use the *include* function to access functions that are in another script file. The following is an example: ``` Include ".\build_utils.ps1" Task default -depends Test Task Test -depends Compile, Clean { } Task Compile -depends Clean { } Task Clean { } ``` You can have more than 1 include file in your script if you need to include multiple script files. `Include` shares code within one repository. To distribute versioned tasks across repositories, see [Creating Shared Task Modules](/docs/tutorial-advanced/creating-shared-task-modules.md). --- # Build Script Resilience The `Task` function has a switch parameter called "ContinueOnError" that you can set if you want the build to continue running if that particular task throws an exception. Here's an example script that uses the "ContinueOnError" switch: ``` Task default -Depends TaskA Task TaskA -Depends TaskB { "Task - A" } Task TaskB -Depends TaskC -ContinueOnError { "Task - B" throw "I failed on purpose!" } Task TaskC { "Task - C" } ``` When you run the above build script, you should get this output: ``` Executing task: TaskC Task - C Executing task: TaskB Task - B ----------------------------------------------------------------- Error in Task [TaskB] I failed on purpose! ----------------------------------------------------------------- Executing task: TaskA Task - A Build Succeeded! ----------------------------------------------------------------- Build Time Report ----------------------------------------------------------------- Name Duration ---- -------- TaskC 00:00:00.0053110 TaskB 00:00:00.0256725 TaskA 00:00:00.0350228 Total: 00:00:00.1032888 ``` When psake processes a task that throws an exception (TaskB in this example) and the `-ContinueOnError` is set, then psake displays a message to the console indicating that the task had an error and continues processing the rest of the build script. Note: The dependent tasks of the task that threw the exception still execute --- # Creating Shared Task Modules A shared task module distributes reusable psake tasks to multiple projects. The provider is a normal PowerShell module with one additional convention: a file named `psakeFile.ps1` at the module root. Use [`Include`](/docs/best-practices/organizing-large-scripts.md) to split tasks within one repository. Use a shared task module when independently versioned tasks must be installed and reused across repositories. This guide builds `Example.BuildTasks` version `1.0.0`. Its public task, `Example.Prepare`, cleans and recreates a consumer project's `build` directory. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * PowerShell 5.1 or PowerShell 7 * psake 5.x * Pester 5.x for the optional integration test Shared task modules were introduced in psake 4.8.0. This example targets the current psake 5.x line and declares that dependency explicitly. ## How shared tasks are loaded[​](#how-shared-tasks-are-loaded "Direct link to How shared tasks are loaded") A consumer references a task with `Task -FromModule`: ``` Task 'Example.Prepare' -FromModule 'Example.BuildTasks' -RequiredVersion '1.0.0' ``` When psake reads that declaration, it: 1. Finds a loaded or discoverable module matching the name and version constraint. 2. Dot-sources `/psakeFile.ps1` once in the current build context. 3. Registers every task declared by that file alongside the consumer's local tasks. 4. Executes only the requested task graph when the build runs. `-FromModule` does not import the module's `.psm1`. For deterministic loading, the provider task file should import the exact module manifest before declaring task actions that call its functions. Provider and consumer task names share one case-insensitive namespace. psake does not provide private tasks or namespace isolation, so treat task names as public API and use a provider-specific prefix. ## Create the tutorial workspace[​](#create-the-tutorial-workspace "Direct link to Create the tutorial workspace") Create separate module and consumer directories. Add the tutorial's module directory to `PSModulePath` for the current PowerShell session: ``` $tutorialRoot = Join-Path $PWD 'shared-task-tutorial' $moduleRoot = Join-Path $tutorialRoot 'modules/Example.BuildTasks/1.0.0' $consumerRoot = Join-Path $tutorialRoot 'consumer' New-Item -ItemType Directory -Path $moduleRoot, $consumerRoot -Force | Out-Null $previousModulePath = $env:PSModulePath $tutorialModulePath = Join-Path $tutorialRoot 'modules' $env:PSModulePath = $tutorialModulePath + [IO.Path]::PathSeparator + $previousModulePath ``` The versioned directory allows PowerShell to discover multiple installed versions and makes `-RequiredVersion` meaningful. ## Create the provider module[​](#create-the-provider-module "Direct link to Create the provider module") ### 1. Create the manifest and root module[​](#1-create-the-manifest-and-root-module "Direct link to 1. Create the manifest and root module") Generate a manifest that declares the psake dependency: ``` $manifestPath = Join-Path $moduleRoot 'Example.BuildTasks.psd1' New-ModuleManifest ` -Path $manifestPath ` -RootModule 'Example.BuildTasks.psm1' ` -ModuleVersion '1.0.0' ` -PowerShellVersion '5.1' ` -RequiredModules @{ ModuleName = 'psake'; ModuleVersion = '5.0.0' } ` -FunctionsToExport @() ` -CmdletsToExport @() ` -VariablesToExport @() ` -AliasesToExport @() New-Item ` -ItemType File ` -Path (Join-Path $moduleRoot 'Example.BuildTasks.psm1') ` -Force | Out-Null ``` The provider is discoverable as a normal PowerShell module. The shared-task integration itself is defined by the root-level `psakeFile.ps1`, not by a special manifest key. ### 2. Define the shared tasks[​](#2-define-the-shared-tasks "Direct link to 2. Define the shared tasks") Create `Example.BuildTasks/1.0.0/psakeFile.ps1`: psakeFile.ps1 ``` Task 'Example.Clean' { $outputDirectory = Join-Path $psake.build_script_dir 'build' if (Test-Path -LiteralPath $outputDirectory) { Remove-Item -LiteralPath $outputDirectory -Recurse -Force } } Task 'Example.Prepare' -Depends 'Example.Clean' { $outputDirectory = Join-Path $psake.build_script_dir 'build' New-Item -ItemType Directory -Path $outputDirectory -Force | Out-Null } ``` `Example.Prepare` is the public task consumers should reference. `Example.Clean` is a supporting task, but that distinction is only a convention: consumers can invoke any registered provider task directly. Use `$psake.build_script_dir` for paths owned by the consumer project. In a provider task file, `$PSScriptRoot` points to the installed provider module instead. Do not define a `default` task in the provider. The consumer owns its default build entry point, and duplicate task names fail when psake registers the shared file. ## Create the consumer build[​](#create-the-consumer-build "Direct link to Create the consumer build") Create `consumer/psakeFile.ps1`: consumer/psakeFile.ps1 ``` Task default -Depends 'Example.Prepare' Task 'Example.Prepare' ` -FromModule 'Example.BuildTasks' ` -RequiredVersion '1.0.0' ``` A shared task reference has no action. The provider supplies the action and dependency graph. Run the consumer build: ``` $consumerBuild = Join-Path $consumerRoot 'psakeFile.ps1' Import-Module psake -MinimumVersion '5.0.0' Invoke-psake -BuildFile $consumerBuild -TaskList default -NoLogo Test-Path (Join-Path $consumerRoot 'build') ``` The final command returns `True`. `Example.Clean` runs first, followed by `Example.Prepare`. ## Move behavior into module functions[​](#move-behavior-into-module-functions "Direct link to Move behavior into module functions") Inline actions are useful for seeing the loading mechanism. As behavior grows, keep orchestration in `psakeFile.ps1` and move implementation into module functions. Replace `Example.BuildTasks.psm1` with: Example.BuildTasks.psm1 ``` function Remove-ExampleBuildOutput { [CmdletBinding()] param( [Parameter(Mandatory)] [string] $Path ) if (Test-Path -LiteralPath $Path) { Remove-Item -LiteralPath $Path -Recurse -Force } } function New-ExampleBuildOutput { [CmdletBinding()] param( [Parameter(Mandatory)] [string] $Path ) New-Item -ItemType Directory -Path $Path -Force | Out-Null } Export-ModuleMember -Function Remove-ExampleBuildOutput, New-ExampleBuildOutput ``` Update `FunctionsToExport` in `Example.BuildTasks.psd1`: ``` FunctionsToExport = @( 'Remove-ExampleBuildOutput' 'New-ExampleBuildOutput' ) ``` Then replace the provider's `psakeFile.ps1` with: psakeFile.ps1 ``` $manifestPath = Join-Path $PSScriptRoot 'Example.BuildTasks.psd1' Import-Module -Name $manifestPath -ErrorAction Stop Task 'Example.Clean' { $outputDirectory = Join-Path $psake.build_script_dir 'build' Remove-ExampleBuildOutput -Path $outputDirectory } Task 'Example.Prepare' -Depends 'Example.Clean' { $outputDirectory = Join-Path $psake.build_script_dir 'build' New-ExampleBuildOutput -Path $outputDirectory } ``` Here `$PSScriptRoot` is intentionally provider-relative: it identifies the manifest beside the provider task file. Importing that exact path ensures the task actions use functions from the same physical module version psake selected. ## Final layout[​](#final-layout "Direct link to Final layout") ``` shared-task-tutorial/ ├── modules/ │ └── Example.BuildTasks/ │ └── 1.0.0/ │ ├── Example.BuildTasks.psd1 │ ├── Example.BuildTasks.psm1 │ └── psakeFile.ps1 ├── consumer/ │ ├── build/ # Created by Example.Prepare │ └── psakeFile.ps1 └── SharedTaskModule.Tests.ps1 ``` A published package must include all three provider files at the module root. ## Version selection and reference overrides[​](#version-selection-and-reference-overrides "Direct link to Version selection and reference overrides") Use the version parameter that matches the consumer's compatibility policy: ``` Task 'Example.Prepare' -FromModule 'Example.BuildTasks' -RequiredVersion '1.0.0' Task 'Example.Prepare' -FromModule 'Example.BuildTasks' -MinimumVersion '1.0.0' Task 'Example.Prepare' -FromModule 'Example.BuildTasks' -MaximumVersion '1.9.0' Task 'Example.Prepare' -FromModule 'Example.BuildTasks' -LessThanVersion '2.0.0' ``` These are alternatives, not declarations to place together. `-Version` is an alias for `-RequiredVersion`. If a same-named module is already loaded, psake uses it only when it satisfies the constraints; otherwise the reference fails. When none is loaded, psake selects the highest discoverable version that satisfies all supplied constraints. A consumer can replace a provider task's dependencies, but `-Depends` replaces the provider list rather than appending to it: ``` Task 'Example.Prepare' ` -FromModule 'Example.BuildTasks' ` -RequiredVersion '1.0.0' ` -Depends 'Consumer.Prerequisite' ``` This reference no longer depends on `Example.Clean`. Use overrides deliberately and review them whenever the provider's task graph changes. See the [`Task` command reference](/docs/commands/Task.md) for the complete shared-task parameter set. ## Test the consumer contract[​](#test-the-consumer-contract "Direct link to Test the consumer contract") Provider function tests do not prove module discovery, task registration, or dependency execution. Save `SharedTaskModule.Tests.ps1` at the tutorial root, then invoke the separate consumer build and check the observable result: SharedTaskModule.Tests.ps1 ``` Describe 'Example.BuildTasks consumer contract' { BeforeAll { Import-Module psake -MinimumVersion '5.0.0' $consumerRoot = Join-Path $PSScriptRoot 'consumer' $consumerBuild = Join-Path $consumerRoot 'psakeFile.ps1' $outputDirectory = Join-Path $consumerRoot 'build' } BeforeEach { New-Item -ItemType Directory -Path $outputDirectory -Force | Out-Null Set-Content -LiteralPath (Join-Path $outputDirectory 'stale.txt') -Value 'stale' } It 'cleans and prepares the build directory' { $result = Invoke-psake -BuildFile $consumerBuild -TaskList default -NoLogo $result.Success | Should -BeTrue $outputDirectory | Should -Exist (Join-Path $outputDirectory 'stale.txt') | Should -Not -Exist } } ``` Run the test in a process where the provider's module root is on `PSModulePath`. ## Production guidelines[​](#production-guidelines "Direct link to Production guidelines") * **Keep file-scope work declarative.** Loading `psakeFile.ps1` executes its top-level statements in the consumer's build process. Restrict file scope to loading dependencies and declaring properties or tasks. Put filesystem changes, network access, and environment mutation inside task actions. * **Trust the provider.** Installing and referencing a shared task module authorizes its task file to execute code while the build is loaded. Pin and review provider versions as you would other build dependencies. * **Avoid collisions.** Prefix public and supporting task names. Do not define `default` in a provider module. * **Version task contracts.** Removing or renaming a public task, changing required inputs, or incompatibly changing behavior requires a breaking module version. Internal graph changes are compatible only when the public result remains compatible. * **Test through a consumer.** Exercise module discovery and the public task graph, not only implementation functions. * **Keep consumer configuration explicit.** Provider `Properties` blocks share the consumer context and have ordering implications. Document every supported property as part of the module contract. For local packaging tests, publish to a file-based PowerShell repository before publishing the same artifact to the PowerShell Gallery. See Microsoft's documentation for [`New-ModuleManifest`](https://learn.microsoft.com/powershell/module/microsoft.powershell.core/new-modulemanifest), [installing modules on `PSModulePath`](https://learn.microsoft.com/powershell/scripting/developer/module/installing-a-powershell-module), and [`Publish-Module`](https://learn.microsoft.com/powershell/module/powershellget/publish-module). Restore the original module search path when you finish the tutorial: ``` $env:PSModulePath = $previousModulePath ``` ## See also[​](#see-also "Direct link to See also") * [Organizing Large Build Scripts](/docs/best-practices/organizing-large-scripts.md) — Share task files within one repository with `Include` * [`Task` command reference](/docs/commands/Task.md) — Complete `-FromModule` and version-constraint syntax * [Introduction to PowerShellBuild](/docs/powershellbuild/introduction.md) — A production shared task module for PowerShell projects * [PowerShell module manifests](https://learn.microsoft.com/powershell/scripting/developer/module/how-to-write-a-powershell-module-manifest) --- # Custom Logging psake routes all internal messages through configurable output handlers. You can override these handlers in your [`psake-config.ps1`](/docs/tutorial-advanced/psake-config.md) file to integrate with your own logging system. ## Default Handlers[​](#default-handlers "Direct link to Default Handlers") psake ships with handlers for six message types: | Type | Default Behavior | | --------- | --------------------- | | `heading` | Cyan colored output | | `default` | `Write-Output` | | `debug` | `Write-Debug` | | `warning` | Yellow colored output | | `error` | Red colored output | | `success` | Green colored output | Unknown message types fall back to the `default` handler. ## Override Specific Message Types[​](#override-specific-message-types "Direct link to Override Specific Message Types") To customize how individual message types are handled, override entries in `$config.outputHandlers` in your `psake-config.ps1`: ``` # psake-config.ps1 # Send warnings to a log file instead of the console $config.outputHandlers.warning = { Param($output) Add-Content -Path "build-warnings.log" -Value $output } # Suppress debug messages entirely $config.outputHandlers.debug = { Param($output) # do nothing } ``` Each handler receives a single `$output` parameter containing the message string. ## Override All Logging[​](#override-all-logging "Direct link to Override All Logging") To replace the entire routing logic, override `$config.outputHandler` (singular). This script block receives both the message and its type, giving you full control: ``` # psake-config.ps1 $config.outputHandler = { Param($output, $type) # Route everything through your custom logger Write-MyBuildLog -Message $output -Level $type } ``` When you override `outputHandler`, the individual `outputHandlers` entries are bypassed entirely. ## Example: Log to a File[​](#example-log-to-a-file "Direct link to Example: Log to a File") ``` # psake-config.ps1 $config.outputHandler = { Param($output, $type) $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss" $line = "[$timestamp] [$type] $output" Add-Content -Path "build.log" -Value $line # Still write to console Write-Host $line } ``` --- # Debugging You can debug your psake build script in a couple of ways: * Using `Set-PSBreakpoint`, `Get-PSBreakpoint` and `Remove-PSBreakpoint` * Use the **PowerShell Integrated Scripting Environment (ISE)** With `Set-PSBreakpoint` you need to know what line number you want to set a breakpoint on, but the syntax is pretty simple: ``` Set-PSBreakpoint [-Script] [-Line] [[-Column] ] [-Action ] [] ex. Set-PSBreakPoint -script psakefile.ps1 -line 25 ``` Once the breakpoint is set then you call the invoke-psake function on your script as normal and you should get a prompt from the command window: ``` Entering debug mode. Use h or ? for help. Hit Line breakpoint on 'C:\Users\Daddy\Documents\Projects\psake\psakefile.ps1:9' psakefile.ps1:9 "TaskA is executing" [DBG]: PS C:\Users\Daddy\Documents\Projects\psake>>> ``` If you type "h" you will get the following options that will allow you to debug your script: ``` s, stepInto Single step (step into functions, scripts, etc.) v, stepOver Step to next statement (step over functions, scripts, etc.) o, stepOut Step out of the current function, script, etc. c, continue Continue execution q, quit Stop execution and exit the debugger k, Get-PSCallStack Display call stack l, list List source code for the current script. Use "list" to start from the current line, "list " to start from line , and "list " to list lines starting from line Repeat last command if it was stepInto, stepOver or list ?, h Displays this help message ``` While debugging you are able to inspect the values of your variables by just typing them at the prompt and hitting the \[Enter] key Once you are done debugging you can call `Remove-PSBreakpoint` to remove the breakpoints you've added, you can use `Get-PSBreakpoint` to get a list of all the current breakpoints in the current session. With the **PowerShell Integrated Scripting Environment** all you need to do is to load your build script and click on the left margin to set a breakpoint, then run the invoke-psake function from the command window that is within the ISE (its in the bottom pane of the ISE). Then you can use the functions keys to debug your build script (F10, F11, etc.. they are under the "Debug" menu). --- # Logging Errors By default, psake does not log errors to a file. When an error occurs, detailed error information is emitted to the console. Most CI servers capture all console output, so this is usually sufficient. If you need to route errors to a file or custom logger, you can override psake's error output handler. See [Custom Logging](/docs/tutorial-advanced/custom-logging.md) for details. Here is an example of the default error output from a psake build script: ``` ---------------------------------------------------------------------- 4/25/2010 2:36:21 PM: An Error Occurred. See Error Details Below: ---------------------------------------------------------------------- ErrorRecord PSMessageDetails : Exception : System.Management.Automation.RuntimeException: This is a test TargetObject : This is a test CategoryInfo : OperationStopped: (This is a test:String) [], RuntimeException FullyQualifiedErrorId : This is a test ErrorDetails : InvocationInfo : System.Management.Automation.InvocationInfo PipelineIterationInfo : {} ErrorRecord.InvocationInfo MyCommand : BoundParameters : {} UnboundArguments : {} ScriptLineNumber : 4 OffsetInLine : 7 HistoryId : 34 ScriptName : C:\Users\Daddy\Documents\Projects\helloWorld\Build\LogError.ps1 Line : throw "This is a test" PositionMessage : At C:\Users\Daddy\Documents\Projects\helloWorld\Build\LogError.ps1:4 char:7 + throw <<<< "This is a test" InvocationName : throw PipelineLength : 0 PipelinePosition : 0 ExpectingInput : False CommandOrigin : Internal Exception 0000000000000000000000000000000000000000000000000000000000000000000000 ErrorRecord : This is a test StackTrace : WasThrownFromThrowStatement : True Message : This is a test Data : {} InnerException : TargetSite : HelpLink : Source : ---------------------------------------------------------------------- Script Variables ---------------------------------------------------------------------- Name Value ---- ----- _ args {} context {System.Collections.Hashtable} Error {} false False input System.Collections.ArrayList+ArrayListEnumeratorSimple MaximumAliasCount 4096 MaximumDriveCount 4096 MaximumErrorCount 256 MaximumFunctionCount 4096 MaximumVariableCount 4096 MyInvocation System.Management.Automation.InvocationInfo null psake {build_script_file, version, default_build_file_name, use_exit_on_error...} this true True ``` --- # Outputs and Artifacts When building with psake, you often need to pass information between tasks, return data to the calling script, or generate artifacts like compiled binaries or deployment packages. This guide explains the recommended approaches for handling outputs and artifacts. ## Understanding psake's Output Model[​](#understanding-psakes-output-model "Direct link to Understanding psake's Output Model") **Important:** psake executes your build script in its own scope, which means: * The `Invoke-psake` function **does not return custom objects** or data structures * Variables defined inside your psakefile (even with `$script:` scope) are **not accessible** after `Invoke-psake` completes * psake's primary success/failure indicator is the **exit code** (0 = success, 1 = failure) To return data from a psake build, you must use external mechanisms like files or environment variables. ## Recommended Patterns[​](#recommended-patterns "Direct link to Recommended Patterns") ### 1. Output Files (Primary Recommended Approach)[​](#1-output-files-primary-recommended-approach "Direct link to 1. Output Files (Primary Recommended Approach)") **This is the best practice for returning data from psake builds.** Write your outputs to JSON or YAML files that can be read after the build completes. #### JSON Output File[​](#json-output-file "Direct link to JSON Output File") psakefile.ps1 ``` Properties { $OutputFile = "./build-output.json" $ArtifactDir = "./artifacts" } Task Build { exec { dotnet build -o $ArtifactDir } # Create output object $output = @{ ArtifactUrl = "https://cdn.example.com/builds/1.0.0/app.zip" Version = "1.0.0" BuildTime = (Get-Date).ToString("o") Artifacts = @( @{ Name = "app.dll"; Path = "$ArtifactDir/app.dll" } @{ Name = "app.pdb"; Path = "$ArtifactDir/app.pdb" } ) } # Write to JSON file $output | ConvertTo-Json -Depth 10 | Set-Content $OutputFile Write-Host "Build output written to: $OutputFile" -ForegroundColor Green } ``` build.ps1 (calling script) ``` # Import and run psake Import-Module psake Invoke-psake -buildFile ./psakefile.ps1 -taskList Build # Check exit code if ($LASTEXITCODE -ne 0) { Write-Error "Build failed with exit code: $LASTEXITCODE" exit $LASTEXITCODE } # Read the output file if (Test-Path ./build-output.json) { $buildOutput = Get-Content ./build-output.json | ConvertFrom-Json Write-Host "`n========== Build Results ==========" -ForegroundColor Cyan Write-Host "Version: $($buildOutput.Version)" Write-Host "Artifact URL: $($buildOutput.ArtifactUrl)" Write-Host "Build Time: $($buildOutput.BuildTime)" Write-Host "Artifacts:" foreach ($artifact in $buildOutput.Artifacts) { Write-Host " - $($artifact.Name) at $($artifact.Path)" } Write-Host "===================================`n" -ForegroundColor Cyan } exit 0 ``` #### YAML Output File[​](#yaml-output-file "Direct link to YAML Output File") psakefile.ps1 ``` Task Build { # Create output object $output = @{ artifact_url = "https://cdn.example.com/builds/1.0.0/app.zip" version = "1.0.0" build_time = (Get-Date).ToString("o") } # Write to YAML file (requires powershell-yaml module) $output | ConvertTo-Yaml | Set-Content ./build-output.yml } ``` **Pros:** * **Actually works** - data persists after psake completes * Works across different processes and languages * Ideal for CI/CD pipelines * Can be version controlled or uploaded as artifacts * Human-readable (JSON/YAML) * Type-safe when using structured formats **Cons:** * File I/O overhead * Requires parsing in consuming code * Need to handle file paths carefully **When to use:** This should be your default choice for returning build metadata, artifact URLs, or any structured data. ### 2. Environment Variables (For CI/CD Integration)[​](#2-environment-variables-for-cicd-integration "Direct link to 2. Environment Variables (For CI/CD Integration)") Environment variables can pass simple string values to CI/CD systems, but be aware that they may not persist in all PowerShell scenarios. psakefile.ps1 ``` Task Build { exec { dotnet build } # Set environment variables $env:BUILD_ARTIFACT_URL = "https://cdn.example.com/builds/1.0.0/app.zip" $env:BUILD_VERSION = "1.0.0" Write-Host "Set BUILD_ARTIFACT_URL=$env:BUILD_ARTIFACT_URL" Write-Host "Set BUILD_VERSION=$env:BUILD_VERSION" } ``` #### GitHub Actions Integration[​](#github-actions-integration "Direct link to GitHub Actions Integration") .github/workflows/build.yml ``` jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Run psake build shell: pwsh run: | Import-Module psake Invoke-psake -taskList Build # Export environment variables for subsequent steps echo "BUILD_ARTIFACT_URL=$env:BUILD_ARTIFACT_URL" >> $env:GITHUB_ENV echo "BUILD_VERSION=$env:BUILD_VERSION" >> $env:GITHUB_ENV - name: Deploy artifact shell: pwsh run: | Write-Host "Deploying version $env:BUILD_VERSION" Write-Host "Artifact URL: $env:BUILD_ARTIFACT_URL" ``` **Pros:** * Native CI/CD integration * Simple to use * No file management **Cons:** * **Limited to string values** - cannot return complex objects * **May not persist** outside the psake process in all scenarios * Less reliable than output files **When to use:** In CI/CD pipelines where you need to pass simple string values to subsequent steps. For reliability, combine with output files. ### 3. Sharing Data Between Tasks (Within Same Build)[​](#3-sharing-data-between-tasks-within-same-build "Direct link to 3. Sharing Data Between Tasks (Within Same Build)") While you cannot return data from `Invoke-psake` to the caller using variables, you **can** share data between tasks within the same build using script-scoped variables. psakefile.ps1 ``` # Script-scoped variable accessible to all tasks $script:BuildMetadata = @{} Properties { $Configuration = "Release" } Task Init { # Store data for use by other tasks $script:BuildMetadata.StartTime = Get-Date $script:BuildMetadata.Configuration = $Configuration } Task Build -Depends Init { # Access data from previous task Write-Host "Started at: $($script:BuildMetadata.StartTime)" Write-Host "Configuration: $($script:BuildMetadata.Configuration)" exec { dotnet build -c $Configuration } # Add more data $script:BuildMetadata.BuildCompleted = Get-Date } Task Package -Depends Build { # Access accumulated metadata $duration = $script:BuildMetadata.BuildCompleted - $script:BuildMetadata.StartTime Write-Host "Build took: $($duration.TotalSeconds) seconds" # Package artifacts exec { dotnet pack } } ``` **Important:** These variables live in the psake **module scope** and persist across `Invoke-psake` calls — they are not automatically reset between calls. They cannot be accessed from the calling script as regular PowerShell variables. > **Gotcha:** If a variable is declared inside a `Properties` block, that block re-runs on every `Invoke-psake` call and will **silently overwrite** whatever value the previous call left behind. Only variables set inside task bodies (not `Properties`) survive intact between calls. **When to use:** For passing data between tasks within the same build execution. ## Anti-Patterns to Avoid[​](#anti-patterns-to-avoid "Direct link to Anti-Patterns to Avoid") ### ❌ Don't Rely on Script Variables Being Accessible Outside psake[​](#-dont-rely-on-script-variables-being-accessible-outside-psake "Direct link to ❌ Don't Rely on Script Variables Being Accessible Outside psake") ``` # ❌ BAD: This does NOT work # psakefile.ps1 $script:BuildOutput = @{ ArtifactUrl = "https://example.com/app.zip" } Task Build { $script:BuildOutput.Version = "1.0.0" } # build.ps1 Invoke-psake -buildFile ./psakefile.ps1 Write-Host $BuildOutput.ArtifactUrl # ❌ $BuildOutput is not defined! ``` **Why it's bad:** psake executes the build file in its own scope. Variables are not accessible after `Invoke-psake` returns. **Better approach:** Write to an output file (JSON/YAML) ### ❌ Don't Use Write-Host for Structured Data[​](#-dont-use-write-host-for-structured-data "Direct link to ❌ Don't Use Write-Host for Structured Data") ``` # ❌ BAD: Mixing structured data with console output Task Build { Write-Host "ARTIFACT_URL:https://cdn.example.com/builds/1.0.0/app.zip" Write-Host "VERSION:1.0.0" } ``` **Why it's bad:** * Fragile parsing required * Mixes data with informational messages * Hard to distinguish from psake's own output * Not machine-readable **Better approach:** Write to JSON file ### ❌ Don't Use Write-Output for Return Values[​](#-dont-use-write-output-for-return-values "Direct link to ❌ Don't Use Write-Output for Return Values") ``` # ❌ BAD: Attempting to return data via Write-Output Task Build { $result = @{ ArtifactUrl = "https://example.com/app.zip" } Write-Output $result # Gets mixed with psake output } ``` **Why it's bad:** * Output gets mixed with psake's verbose logging * Difficult to capture reliably * Not the intended use of `Invoke-psake` **Better approach:** Write to JSON file ### ❌ Don't Use Global Variables[​](#-dont-use-global-variables "Direct link to ❌ Don't Use Global Variables") ``` # ❌ BAD: Using global scope Task Build { $global:ArtifactUrl = "https://example.com/app.zip" } ``` **Why it's bad:** * Pollutes global namespace * Hard to track and debug * May not work depending on how psake is invoked * Not clear which task sets which globals **Better approach:** Write to JSON file (or use `$script:` for inter-task communication) ## Complete Example: Multi-Task Build with Outputs[​](#complete-example-multi-task-build-with-outputs "Direct link to Complete Example: Multi-Task Build with Outputs") This example shows the **recommended pattern** for returning data from a psake build. psakefile.ps1 ``` #requires -Version 7 Properties { $Configuration = "Release" $ArtifactDir = "./artifacts" $OutputFile = "./build-output.json" } # Internal data sharing between tasks (not accessible outside psake) $script:InternalBuildData = @{ Version = $null TestsPassed = $false } Task Default -Depends Build, Test, Package Task Build { Write-Host "Building solution..." -ForegroundColor Green # Get version from project file [xml]$project = Get-Content ./src/App.csproj $script:InternalBuildData.Version = $project.Project.PropertyGroup.Version # Build exec { dotnet build -c $Configuration } Write-Host "Built version: $($script:InternalBuildData.Version)" } Task Test -Depends Build { Write-Host "Running tests..." -ForegroundColor Green try { exec { dotnet test --no-build -c $Configuration } $script:InternalBuildData.TestsPassed = $true } catch { $script:InternalBuildData.TestsPassed = $false throw } } Task Package -Depends Test { Write-Host "Creating package..." -ForegroundColor Green # Create artifact directory if (-not (Test-Path $ArtifactDir)) { New-Item -ItemType Directory -Path $ArtifactDir | Out-Null } # Publish application exec { dotnet publish -c $Configuration -o $ArtifactDir } # Create zip archive $zipName = "app-v$($script:InternalBuildData.Version).zip" $zipPath = Join-Path $ArtifactDir $zipName Compress-Archive -Path "$ArtifactDir/*" -DestinationPath $zipPath -Force # Simulate upload to CDN (in real scenario, this would actually upload) $artifactUrl = "https://cdn.example.com/builds/$($script:InternalBuildData.Version)/$zipName" # IMPORTANT: Write outputs to file so they're accessible after Invoke-psake $output = @{ Version = $script:InternalBuildData.Version ArtifactUrl = $artifactUrl ArtifactPath = $zipPath TestsPassed = $script:InternalBuildData.TestsPassed BuildTime = (Get-Date).ToString("o") Configuration = $Configuration } $output | ConvertTo-Json -Depth 10 | Set-Content $OutputFile Write-Host "Package created: $zipPath" Write-Host "Artifact URL: $artifactUrl" Write-Host "Output written to: $OutputFile" } ``` build.ps1 (wrapper script) ``` param( [string]$Task = "Default", [hashtable]$Properties = @{} ) # Import psake if (-not (Get-Module psake -ListAvailable)) { Install-Module psake -Scope CurrentUser -Force } Import-Module psake # Run build Write-Host "Running psake build..." -ForegroundColor Cyan Invoke-psake -buildFile ./psakefile.ps1 -taskList $Task -properties $Properties # Check result if ($LASTEXITCODE -ne 0) { Write-Error "Build failed with exit code: $LASTEXITCODE" exit $LASTEXITCODE } # Read and display output file $outputFile = "./build-output.json" if (Test-Path $outputFile) { $output = Get-Content $outputFile | ConvertFrom-Json Write-Host "`n========== Build Results ==========" -ForegroundColor Cyan Write-Host "Version: $($output.Version)" Write-Host "Configuration: $($output.Configuration)" Write-Host "Artifact URL: $($output.ArtifactUrl)" Write-Host "Artifact Path: $($output.ArtifactPath)" Write-Host "Tests Passed: $($output.TestsPassed)" Write-Host "Build Time: $($output.BuildTime)" Write-Host "===================================`n" -ForegroundColor Cyan # Example: Use the output data for subsequent operations if ($output.TestsPassed -and $output.ArtifactUrl) { Write-Host "✓ Build artifacts ready for deployment" -ForegroundColor Green Write-Host " Deploy with: ./deploy.ps1 -ArtifactUrl '$($output.ArtifactUrl)'" } # Make output available to calling code return $output } else { Write-Warning "No build output file found at: $outputFile" } exit 0 ``` deploy.ps1 (example consumer) ``` param( [Parameter(Mandatory)] [string]$ArtifactUrl ) # This script can be called after the build completes # It reads the artifact URL from the output file or receives it as a parameter Write-Host "Deploying artifact from: $ArtifactUrl" -ForegroundColor Green # Read additional metadata from build output if needed if (Test-Path ./build-output.json) { $buildInfo = Get-Content ./build-output.json | ConvertFrom-Json Write-Host "Deploying version: $($buildInfo.Version)" } # Deployment logic here... ``` ## Usage in CI/CD Pipelines[​](#usage-in-cicd-pipelines "Direct link to Usage in CI/CD Pipelines") ### GitHub Actions[​](#github-actions "Direct link to GitHub Actions") .github/workflows/build.yml ``` name: Build and Deploy on: [push, pull_request] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Setup .NET uses: actions/setup-dotnet@v3 with: dotnet-version: '8.0.x' - name: Install psake shell: pwsh run: Install-Module -Name psake -Scope CurrentUser -Force - name: Run build shell: pwsh run: | Import-Module psake Invoke-psake -taskList Default if ($LASTEXITCODE -ne 0) { throw "Build failed" } - name: Read build output id: build-output shell: pwsh run: | $output = Get-Content ./build-output.json | ConvertFrom-Json echo "version=$($output.Version)" >> $env:GITHUB_OUTPUT echo "artifact_url=$($output.ArtifactUrl)" >> $env:GITHUB_OUTPUT - name: Upload artifacts uses: actions/upload-artifact@v3 with: name: build-artifacts-${{ steps.build-output.outputs.version }} path: ./artifacts/ - name: Deploy (on main branch) if: github.ref == 'refs/heads/main' shell: pwsh run: | $output = Get-Content ./build-output.json | ConvertFrom-Json ./deploy.ps1 -ArtifactUrl $output.ArtifactUrl ``` ### Azure Pipelines[​](#azure-pipelines "Direct link to Azure Pipelines") azure-pipelines.yml ``` trigger: - main pool: vmImage: 'ubuntu-latest' steps: - task: UseDotNet@2 inputs: version: '8.0.x' - pwsh: Install-Module -Name psake -Scope CurrentUser -Force displayName: 'Install psake' - pwsh: | Import-Module psake Invoke-psake -taskList Default if ($LASTEXITCODE -ne 0) { Write-Error "Build failed" exit 1 } displayName: 'Run psake build' - pwsh: | $output = Get-Content ./build-output.json | ConvertFrom-Json Write-Host "##vso[task.setvariable variable=BuildVersion]$($output.Version)" Write-Host "##vso[task.setvariable variable=ArtifactUrl]$($output.ArtifactUrl)" displayName: 'Extract build outputs' - task: PublishBuildArtifacts@1 inputs: pathToPublish: './artifacts' artifactName: 'drop-$(BuildVersion)' - pwsh: | Write-Host "Deploying version: $(BuildVersion)" Write-Host "Artifact URL: $(ArtifactUrl)" ./deploy.ps1 -ArtifactUrl "$(ArtifactUrl)" displayName: 'Deploy' condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main')) ``` ## Best Practices Summary[​](#best-practices-summary "Direct link to Best Practices Summary") 1. **Use JSON/YAML output files** - This is the primary recommended approach for returning data 2. **Always check exit codes** - They remain the primary success/failure indicator 3. **Use `$script:` variables for inter-task communication** - They persist in the psake module scope across calls; they are not accessible from the calling script as regular variables 4. **Avoid Write-Host/Write-Output** for structured data - Use files instead 5. **Document your output schema** - So consumers know what to expect 6. **Handle failures gracefully** - Ensure output files contain meaningful error information 7. **Upload output files as CI artifacts** - Makes them available across pipeline stages ## Quick Reference[​](#quick-reference "Direct link to Quick Reference") | Need to... | Use... | Example | | ------------------------- | ------------------------ | ---------------------------------------------------- | | Return data from psake | JSON output file | `$data \| ConvertTo-Json \| Set-Content output.json` | | Share data between tasks | Script-scoped variables | `$script:BuildData = @{}` | | Pass simple strings to CI | Environment variables | `$env:BUILD_VERSION = "1.0.0"` | | Pass data INTO psake | Properties or parameters | `Invoke-psake -properties @{Version="1.0"}` | ## See Also[​](#see-also "Direct link to See Also") * [Exit Codes](/docs/reference/exit-codes.md) - Understanding psake's primary output mechanism * [Structure of a psake Build Script](/docs/tutorial-advanced/structure-of-a-psake-build-script.md) - Build script components * [Parameters & Properties](/docs/tutorial-basics/parameters-properties.md) - Passing data INTO builds * [Build Script Resilience](/docs/tutorial-advanced/build-script-resilience.md) - Error handling patterns * [GitHub Actions Integration](/docs/ci-examples/github-actions.md) - CI/CD examples --- # Formatting Task Name Use the psake `FormatTaskName` function. This function takes either a string which represents a format string (formats using the -f format operator see "help about\_operators") or it can accept a script block that has a single parameter that is the name of the task that will be executed. ## String Format[​](#string-format "Direct link to String Format") Here is an example using the format string parameter of the `FormatTaskName` parameter: ``` Task default -depends TaskA, TaskB, TaskC FormatTaskName "-------- {0} --------" Task TaskA { "TaskA is executing" } Task TaskB { "TaskB is executing" } Task TaskC { "TaskC is executing" } ``` The output looks like the following: ``` -------- TaskA -------- TaskA is executing -------- TaskB -------- TaskB is executing -------- TaskC -------- TaskC is executing Build Succeeded! ---------------------------------------------------------------------- Build Time Report ---------------------------------------------------------------------- Name Duration ---- -------- TaskA 00:00:00.0058887 TaskB 00:00:00.0057582 TaskC 00:00:00.0043072 Total: 00:00:00.1919297 ``` ## Format with Function[​](#format-with-function "Direct link to Format with Function") The following example uses the scriptblock parameter of the `FormatTaskName` function: ``` Task default -depends TaskA, TaskB, TaskC FormatTaskName { param($taskName) Write-Host "Executing Task: $taskName" -ForegroundColor blue } Task TaskA { "TaskA is executing" } Task TaskB { "TaskB is executing" } Task TaskC { "TaskC is executing" } ``` The preceding example uses the scriptblock parameter to the `FormatTaskName` function to render each task name in the color blue. **Note:** the $taskName parameter is arbitrary it could be named anything --- # psake Configuration File psake loads a `psake-config.ps1` file at the start of every build to set default values for your build environment. You can use this file to change psake's default build file name, framework version, task name format, output handlers, and more. note Most projects do not need a `psake-config.ps1` file. psake's built-in defaults work well for the majority of use cases. Only create one if you need to change a specific default. ## How psake Finds the Config File[​](#how-psake-finds-the-config-file "Direct link to How psake Finds the Config File") psake searches for `psake-config.ps1` in two locations, in order: 1. **The build script's directory** — the folder containing the build file passed to `Invoke-psake` 2. **The psake module directory** — the folder where `psake.psm1` is installed The first file found wins. If neither location contains a config file, psake uses its built-in defaults. This means you can place a `psake-config.ps1` next to your `psakefile.ps1` to customize settings per-project, or place one alongside the psake module for machine-wide defaults. ## Partial Overrides[​](#partial-overrides "Direct link to Partial Overrides") Your config file does not need to set every property. psake initializes all properties to their defaults before loading your config file, so any property you omit keeps its default value. You only need to set the properties you want to change. ## Configuration Properties[​](#configuration-properties "Direct link to Configuration Properties") Inside `psake-config.ps1`, you set properties on the `$config` variable. Here is every available property: | Property | Type | Default | Description | | --------------------- | ------------------------- | ---------------------------- | -------------------------------------------------- | | `buildFileName` | `string` | `"psakefile.ps1"` | Default build file name when none is specified | | `legacyBuildFileName` | `string` | `"default.ps1"` | Fallback build file name (legacy support) | | `framework` | `string` | `"4.0"` | .NET Framework version to target | | `taskNameFormat` | `string` or `scriptblock` | `"Executing {0}"` | Format string or scriptblock for task name display | | `verboseError` | `bool` | `$false` | Show detailed error information | | `coloredOutput` | `bool` | `$true` | Enable colored console output | | `modules` | `string[]` | `$null` | Module paths to auto-load before build execution | | `moduleScope` | `string` | — | Scope for loaded modules | | `outputHandler` | `scriptblock` | *(routes to outputHandlers)* | Master handler that receives all output | | `outputHandlers` | `hashtable` | *(see below)* | Per-type output handlers | ## Minimal Example[​](#minimal-example "Direct link to Minimal Example") psake-config.ps1 ``` # Use a different default build file name $config.buildFileName = "build.ps1" # Target .NET 4.8 $config.framework = "4.8" # Show detailed errors $config.verboseError = $true ``` ## Task Name Formatting[​](#task-name-formatting "Direct link to Task Name Formatting") `taskNameFormat` accepts either a format string or a scriptblock: psake-config.ps1 ``` # Format string — {0} is replaced with the task name $config.taskNameFormat = "Executing {0}" # Scriptblock — receives the task name as a parameter $config.taskNameFormat = { param($taskName) "Executing $taskName at $(Get-Date)" } ``` ## Auto-Loading Modules[​](#auto-loading-modules "Direct link to Auto-Loading Modules") Use `$config.modules` to load PowerShell modules before any tasks run: psake-config.ps1 ``` # Load all modules from a folder $config.modules = @(".\modules\*.psm1") # Load specific modules $config.modules = @(".\modules\*.psm1", ".\my_module.psm1") ``` ## Output Handlers[​](#output-handlers "Direct link to Output Handlers") psake routes all internal messages through configurable output handlers. For a full guide on customizing logging, see [Custom Logging](/docs/tutorial-advanced/custom-logging.md). ## See Also[​](#see-also "Direct link to See Also") * [Custom Logging](/docs/tutorial-advanced/custom-logging.md) — Override psake's output handlers * [Configuration Reference](/docs/reference/configuration-reference.md) — Full reference for `Invoke-psake` parameters and build script settings * [Structure of a psake Build Script](/docs/tutorial-advanced/structure-of-a-psake-build-script.md) — How build scripts are organized --- # Retry Rules You can set retry rules for a **task** exec function by using the **maxRetries** parameter. For example, to have your **task** exec retry 3 times due to **any** unhandled exception: ``` exec -maxRetries 3 ``` You can also optionally specify a **task** exec to be retried only when a certain error message occurs using the **retryTriggerErrorPattern** parameter in conjunction with **maxRetries**: ``` exec -maxRetries 3 -retryTriggerErrorPattern "Service is not currently available." ``` ## Notes[​](#notes "Direct link to Notes") 1. psake will wait 1 second between retries. 2. The default for the maxRetries parameter if not specified is 0, i.e. the task will not be retried. --- # Structure of a psake Build Script A psake build script contains calls to functions that the psake build engine will execute. The functions are the following: | Function | Description | Required | | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | | *Include()* | Call this function to have psake include the functions of another file into your script | no | | *Properties()* | Call this function to set your properties | no | | *Task()* | This is the main function that you write to execute a step in your build script. NOTE: There can be only one task function that is named "default" in your psake script and it cannot contain any code. psake will throw an exception if it finds more than one default task function or if the default task function contains code | yes | | *Exec()* | Call a command-line program and throw an exception if it returns a non-zero DOS exit code | no | | *Assert()* | Use to simplify writing conditional statements | no | | *FormatTaskName()* | Allows you to reformat how psake displays the currently running task | no | | *TaskSetup()* | A function that will run before each task is executed | no | | *TaskTearDown()* | A function that will run after each task | no | | *BuildSetup()* | A script block that will run before the first task starts | no | | *BuildTearDown()* | A script block that will run when either all tasks have completed, or the build has failed | no | An example psake script: ``` Task default -Depends Test Task Test -Depends Compile, Clean { "This is a test" } Task Compile -Depends Clean { "Compile" } Task Clean { "Clean" } ``` The following is a BNF for a psake build script: ``` ::= | | | | | | ::= Include | ::= Properties | ::= FormatTaskName ::= BuildSetup ::= BuildTearDown ::= TaskSetup ::= TaskTearDown ::= Task | ::= -Name | -Action | -PreAction | -PostAction | -PreCondition | -PostCondition | -ContinueOnError | -Depends | -Description ::= , | ::= { } ::= $true | $false ``` --- # Variable Scope and References Since **psake** is written in PowerShell, all variables follow the PowerShell scoping rules (see [about\_scopes](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_scopes)). All the variables declared in the "properties" function have the "script" level scope and any variables declared within a "task" function are local variables. This means you can reference the "properties" variables from any "task" function but all variables declared within "task" function are not accessible from other "task" functions in the build script. The following is an example build script that uses a property variable from 2 different task functions: ``` properties { $x = 1 } task default -depends TaskA, TaskB task TaskA { '$x = ' + $x } task TaskB { '$x = ' + $x } ``` The output from running the above build is: ``` Executing task: TaskA $x = 1 Executing task: TaskB $x = 1 Build Succeeded! ---------------------------------------------------------------------- Build Time Report ---------------------------------------------------------------------- Name Duration ---- -------- TaskA 00:00:00.0086706 TaskB 00:00:00.0056644 Total: 00:00:00.0801045 ``` Sometimes you may want/need to set a variable or update a property within a task and then refer to that variable/property from another task. The following example build script shows how to do that using the "script" scope modifier ``` properties { $x = 1 } task default -depends TaskA, TaskB task TaskA { $script:x = 100 '$x = ' + $script:x } task TaskB { '$x = ' + $script:x } ``` The output from the above script: ``` Executing task: TaskA $x = 100 Executing task: TaskB $x = 100 Build Succeeded! ---------------------------------------------------------------------- Build Time Report ---------------------------------------------------------------------- Name Duration ---- -------- TaskA 00:00:00.0057736 TaskB 00:00:00.0054096 Total: 00:00:00.0820067 ``` The following example does not update a property, it sets a "script" level variable ("y") within one task and references it in another task. ``` task default -depends TaskA, TaskB task TaskA { $script:y = 100 '$y = ' + $script:y } task TaskB { '$y = ' + $script:y } ``` The output from the above build script: ``` Executing task: TaskA $y = 100 Executing task: TaskB $y = 100 Build Succeeded! ---------------------------------------------------------------------- Build Time Report ---------------------------------------------------------------------- Name Duration ---- -------- TaskA 00:00:00.0047757 TaskB 00:00:00.0048073 Total: 00:00:00.1244049 ``` In case you create a variable with script scope, the variable is kept inside the psake module. This means, that if you run Invoke-psake again, the variable is available. That’s why variables with script scope should be used carefully. Consider them as global variables accessible for all scripts running in psake. > **Gotcha:** If a `$script:` variable is declared inside a `Properties` block, that block re-runs on every `Invoke-psake` call and will **silently overwrite** whatever value the previous call left behind. Only variables set inside task bodies (not `Properties`) survive intact between calls. --- # Default Build File If you call psake but do not specify a build file, psake will instead use a default build file name. The example below run the default task in the default build file: ``` Import-Module psake Invoke-psake ``` ## What is the default build file called?[​](#what-is-the-default-build-file-called "Direct link to What is the default build file called?") Previously the default build file name was called `default.ps1` however psake will now prefer to use a file called `psakefile.ps1` ## Why the change?[​](#why-the-change "Direct link to Why the change?") One of psake's main benefits is making it easier to find and execute build tasks, however the default build file name of `default.ps1` was not very descriptive. Instead psake now uses `psakefile.ps1` as default, which clearly describes the file's intention. ## But what about my old default build files?[​](#but-what-about-my-old-default-build-files "Direct link to But what about my old default build files?") psake will continue to use `default.ps1`. However if both a `default.ps1` and `psakefile.ps1` file are detected, it will prefer the newer `psakefile.ps1` file. Note that a psake outputs a warning about using the older `default.ps1` file. --- # Getting Help ## Built-in Help[​](#built-in-help "Direct link to Built-in Help") ### View Available Tasks[​](#view-available-tasks "Direct link to View Available Tasks") To see a list of all tasks defined in your psake build script, use the `-Docs` parameter: ``` Invoke-psake -Docs ``` This will print the current tasks list from your psake file, showing all available tasks and their descriptions. ### PowerShell Help System[​](#powershell-help-system "Direct link to PowerShell Help System") You can use the PowerShell `Get-Help` command on the `Invoke-psake` function to get detailed help: ``` # First import the psake module Import-Module psake # View full help for Invoke-psake Get-Help Invoke-psake -Full ``` ### List Available Commands[​](#list-available-commands "Direct link to List Available Commands") To see all functions available in the psake module: ``` Get-Command -Module psake # Output: # CommandType Name Definition # ----------- ---- ---------- # Function Assert ... # Function Exec ... # Function FormatTaskName ... # Function Include ... # Function Invoke-psake ... # Function Properties ... # Function Task ... # Function TaskSetup ... # Function TaskTearDown ... ``` ### Get Examples for Specific Commands[​](#get-examples-for-specific-commands "Direct link to Get Examples for Specific Commands") Use `Get-Help` with the `-Examples` parameter to see usage examples: ``` Get-Help Assert -Examples # Output: # NAME # Assert # # SYNOPSIS # Helper function for "Design by Contract" assertion checking. # # -------------------------- EXAMPLE 1 -------------------------- # Assert $false "This always throws an exception" # # -------------------------- EXAMPLE 2 -------------------------- # Assert ( ($i % 2) -eq 0 ) "$i is not an even number" ``` ## Community Support[​](#community-support "Direct link to Community Support") Need help with your build scripts? The psake community is here to help! ### Chat & Discussion[​](#chat--discussion "Direct link to Chat & Discussion") * **[PowerShell Discord](https://aka.ms/psdiscord)** - Join the #psake channel for real-time chat * **[PowerShell Slack](https://aka.ms/psslack)** - Join the #psake channel for team collaboration * **[GitHub Discussions](https://github.com/orgs/psake/discussions)** - For long-form questions and discussions ### Q\&A[​](#qa "Direct link to Q\&A") * **[Stack Overflow](https://stackoverflow.com/questions/tagged/psake)** - Search existing questions or ask new ones using the `psake` tag ### Report Issues[​](#report-issues "Direct link to Report Issues") Found a bug or have a feature request? * **[GitHub Issues](https://github.com/psake/psake/issues)** - Report bugs or request features in the main psake repository ## Documentation[​](#documentation "Direct link to Documentation") For comprehensive guides and reference materials: * **[Command Reference](/docs/commands/Invoke-Psake.md)** - Detailed documentation for all psake commands * **[Troubleshooting](/docs/troubleshooting/common-errors.md)** - Solutions to common problems * **[FAQ](/docs/troubleshooting/faq.md)** - Frequently asked questions --- # How to Fail a Build When you run a command-line program, you need to check the `$LastExitCode` PowerShell variable to determine if the command-line program failed. If the `$LastExitCode` is non-zero that usually indicates a failure condition and you should throw a PowerShell exception so that your build script will fail appropriately. Here is an example: ``` task default -depends TaskA task TaskA { #use cmd.exe and the DOS exit() function to simulate a failed command-line execution "Executing command-line program" cmd /c exit (1) } ``` The output from the above build script: ``` Executing task: TaskA Executing command-line program Build Succeeded! ---------------------------------------------------------------------- Build Time Report ---------------------------------------------------------------------- Name Duration ---- -------- TaskA 00:00:00.1059589 Total: 00:00:00.2233691 ``` The above build script will always succeed, even though we know the build should have failed Here is an example that does fail correctly: ``` task default -depends TaskA task TaskA { #use cmd.exe and the DOS exit() function to simulate a failed command-line execution "Executing command-line program" cmd /c exit (1) if ($LastExitCode -ne 0) { throw "Command-line program failed" } } ``` A simpler option is to use the psake "exec" function to execute command-line programs The following is an example: ``` task default -depends TaskA task TaskA { #use cmd.exe and the DOS exit() function to simulate a failed command-line execution "Executing command-line program" exec { cmd /c exit (1) } } ``` --- # Installing psake ## Via PowerShell[​](#via-powershell "Direct link to Via PowerShell") To install psake use the built-in PowerShell package manager. Launch PowerShell and run the following command. ``` # PowerShellGet Install-Module psake # PSResourceGet Install-PSResource psake ``` ## Via Chocolatey[​](#via-chocolatey "Direct link to Via Chocolatey") ``` choco install psake ``` ## Via NuGet[​](#via-nuget "Direct link to Via NuGet") You can download through the various mechanisms available for NuGet [psake - nuget.org](https://www.nuget.org/packages/psake/) ## Via GitHub[​](#via-github "Direct link to Via GitHub") You can also download ZIP files of the project "binaries" from GitHub by selecting the appropriate [release](https://github.com/psake/psake/releases). --- # Nested Builds Yes, the Invoke-psake function can be called recursively from within a "task" function The following is an example build script that has tasks that call the invoke-psake function to run other build scripts. ``` Properties { $x = 1 } Task default -Depends RunNested1, RunNested2, CheckX Task RunNested1 { Invoke-psake .\nested\nested1.ps1 } Task RunNested2 { Invoke-psake .\nested\nested2.ps1 } Task CheckX{ Assert ($x -eq 1) '$x was not 1' } ``` --- # Parameters You can pass parameters to your build script using the "parameters" parameter of the Invoke-psake function. The following is an example: ``` Invoke-psake .\parameters.ps1 -parameters @{"p1"="v1";"p2"="v2"} ``` The example above runs the build script called "parameters.ps1" and passes in parameters 'p1' and 'p2' with values 'v1' and 'v2'. The parameter value for the "parameters" parameter (say that 10 times really fast!) is a PowerShell hashtable where the name and value of each parameter is specified. Note: You don't need to use the "$" character when specifying the parameter names in the hashtable. parameters.ps1 ``` properties { $my_property = $p1 + $p2 } task default -depends TestParams task TestParams { Assert ($my_property -ne $null) '$my_property should not be null' } ``` --- # Parameters & Properties Parameters and properties are two different ways to pass values into your psake build scripts. Understanding when to use each one is crucial for creating flexible, maintainable build automation. ## Quick Decision Guide[​](#quick-decision-guide "Direct link to Quick Decision Guide") **Use Parameters when:** * You need to pass external values that your properties blocks will use to compute derived values * You want to provide inputs that determine how properties are calculated * You're passing configuration from CI/CD systems or command-line arguments **Use Properties when:** * You need to override final property values after all computations are complete * You want to replace default values defined in your build script * You're overriding environment-specific settings (like connection strings or paths) ## Execution Flow & Lifespan[​](#execution-flow--lifespan "Direct link to Execution Flow & Lifespan") Understanding the execution order is key to choosing between parameters and properties: **Key Insight:** Parameters are set **before** properties blocks run, while property overrides are applied **after** all properties blocks have executed. ## When to Use Each[​](#when-to-use-each "Direct link to When to Use Each") ### Use Parameters For: Input Values[​](#use-parameters-for-input-values "Direct link to Use Parameters For: Input Values") Parameters provide **input values** that your build script can use during the properties block execution phase. They're ideal for: * Build configuration values (Debug/Release) * Version numbers to be incorporated into file paths * Feature flags that affect property calculations * CI/CD system variables **Example:** Using parameters to compute derived properties parameters.ps1 ``` properties { $config = $configuration # Use parameter value $outputPath = "bin\$config" # Compute derived path $version = "$majorVersion.$minorVersion.0" # Build version string } task default -depends Build task Build { Write-Host "Building to: $outputPath" Write-Host "Version: $version" } ``` Invocation ``` # Pass input values that properties blocks will use Invoke-psake .\parameters.ps1 -parameters @{ "configuration" = "Release" "majorVersion" = "2" "minorVersion" = "1" } # Output: # Building to: bin\Release # Version: 2.1.0 ``` ### Use Properties For: Final Value Overrides[​](#use-properties-for-final-value-overrides "Direct link to Use Properties For: Final Value Overrides") Properties provide **final value overrides** that replace values after all properties blocks have run. They're ideal for: * Overriding default values for different environments * Replacing computed values in specific scenarios * Emergency hotfix values that bypass normal logic **Example:** Overriding final property values properties.ps1 ``` properties { $server = "localhost" # Default value $database = "DevDB" # Default value $connectionString = "Server=$server;Database=$database" } task default -depends Deploy task Deploy { Write-Host "Deploying to: $connectionString" } ``` Invocation ``` # Override final values after properties blocks run Invoke-psake .\properties.ps1 -properties @{ "server" = "prod-sql-01" "database" = "ProductionDB" } # Output: # Deploying to: Server=prod-sql-01;Database=ProductionDB # Note: $connectionString still has the OLD computed value! ``` Properties Don't Recompute Derived Values When you override a property, any derived values that were computed from it in the properties block are **NOT** automatically recalculated. In the example above, `$connectionString` would still contain the original computed value unless you override it directly as well. ## Comparison: Side by Side[​](#comparison-side-by-side "Direct link to Comparison: Side by Side") Let's see the difference with a concrete example: build.ps1 ``` properties { $env = $environment # Will be set if passed as parameter $deployPath = "C:\Deploy\$env" # Computed from $env } task default -depends Info task Info { Write-Host "Environment: $env" Write-Host "Deploy Path: $deployPath" } ``` ### Using Parameters (Input Values)[​](#using-parameters-input-values "Direct link to Using Parameters (Input Values)") ``` Invoke-psake .\build.ps1 -parameters @{"environment"="Production"} # Output: # Environment: Production # Deploy Path: C:\Deploy\Production ``` ✅ The `$deployPath` property is correctly computed using the parameter value. ### Using Properties (Override After Computation)[​](#using-properties-override-after-computation "Direct link to Using Properties (Override After Computation)") ``` Invoke-psake .\build.ps1 -properties @{"environment"="Production"} # Output: # Environment: Production # Deploy Path: C:\Deploy\ ``` ❌ The `$deployPath` is computed BEFORE the override, so it uses `$null` for `$env`. ### Using Both Together[​](#using-both-together "Direct link to Using Both Together") ``` Invoke-psake .\build.ps1 ` -parameters @{"environment"="Production"} ` -properties @{"deployPath"="\\FileServer\Builds\Prod"} # Output: # Environment: Production # Deploy Path: \\FileServer\Builds\Prod ``` ✅ Parameter provides input for computation, property overrides the final value. ## Comparison Table[​](#comparison-table "Direct link to Comparison Table") | Aspect | Parameters | Properties | | ------------------ | --------------------------------------- | -------------------------------------------- | | **Timing** | Set before properties blocks run | Applied after all properties blocks complete | | **Purpose** | Provide inputs for computation | Override final computed values | | **Derived Values** | Can be used to compute other properties | Do NOT trigger recomputation | | **Best For** | Configuration inputs, build settings | Environment-specific overrides | | **Use Case** | "I need this value to calculate others" | "I need to replace this final value" | ## Best Practices[​](#best-practices "Direct link to Best Practices") ### 1. Prefer Parameters for Inputs[​](#1-prefer-parameters-for-inputs "Direct link to 1. Prefer Parameters for Inputs") When you need to pass values that affect multiple properties, use parameters: ``` # ✅ Good: Use parameters for inputs Invoke-psake .\build.ps1 -parameters @{ "configuration" = "Release" "platform" = "x64" } # Properties block can use these to compute paths properties { $config = $configuration $plat = $platform $outputPath = "bin\$plat\$config" } ``` ### 2. Use Properties for Environment-Specific Overrides[​](#2-use-properties-for-environment-specific-overrides "Direct link to 2. Use Properties for Environment-Specific Overrides") When deploying to different environments, use properties to override specific values: ``` # ✅ Good: Override environment-specific values Invoke-psake .\deploy.ps1 ` -parameters @{"environment"="Production"} ` -properties @{ "serverName" = "prod-web-01" "connectionString" = "Server=prod-sql-01;..." } ``` ### 3. Document Which Approach Your Script Expects[​](#3-document-which-approach-your-script-expects "Direct link to 3. Document Which Approach Your Script Expects") In your build script, document which values should be passed as parameters vs properties: ``` <# .SYNOPSIS Build automation script .PARAMETER parameters Expected parameters: - configuration: Build configuration (Debug/Release) - version: Version number for the build .PARAMETER properties Optional property overrides: - outputPath: Override computed output path - deployServer: Override target deployment server #> properties { $config = $configuration # From -parameters $ver = $version # From -parameters $outputPath = "bin\$config\$ver" # Computed $deployServer = "localhost" # Default (can override with -properties) } ``` ### 4. Avoid Overriding Properties That Other Properties Depend On[​](#4-avoid-overriding-properties-that-other-properties-depend-on "Direct link to 4. Avoid Overriding Properties That Other Properties Depend On") If you override a property that other properties are computed from, the dependent properties won't update: ``` # ❌ Bad: Overriding $buildRoot won't affect $outputPath properties { $buildRoot = "C:\Builds" $outputPath = "$buildRoot\Output" } Invoke-psake .\build.ps1 -properties @{"buildRoot"="D:\Builds"} # Result: $buildRoot = "D:\Builds", but $outputPath = "C:\Builds\Output" # ✅ Good: Pass as parameter so computation uses the right value Invoke-psake .\build.ps1 -parameters @{"buildRoot"="D:\Builds"} # Result: $buildRoot = "D:\Builds" AND $outputPath = "D:\Builds\Output" ``` ## Summary[​](#summary "Direct link to Summary") * **Parameters** = inputs set **before** properties blocks run → use for values that affect computations * **Properties** = overrides applied **after** properties blocks run → use to replace final values * Both can be used together for maximum flexibility * When in doubt, prefer parameters for inputs and reserve properties for environment-specific overrides Rule of Thumb Ask yourself: "Does this value need to be used to calculate other properties?" * **Yes** → Use `-parameters` * **No** → Use `-properties` --- # Run psake *psake* is a PowerShell module and is contained in a file named `psake.psm1`. There are 2 ways to run psake: 1. Import the psake module and call the Invoke-psake function 2. Call the psake.ps1 helper script Following is the first option: ``` Import-Module psake Invoke-psake .\psakefile.ps1 ``` Second option: ``` # call the psake.ps1 file directly .\psake.ps1 .\psakefile.ps1 ``` When you call the psake.ps1 script, it forwards the parameters on to the Invoke-psake function. The benefit of option 1 is that you can get detailed help on the Invoke-psake function: ``` Import-Module psake Get-Help Invoke-psake -full ``` You may also consider making a helper script for your builds so that you can configure any psake options (-framework, etc) The following is an example helper script that configures psake to use the .NET 4.0 framework. ``` Import-Module (join-path $PSScriptRoot psake.psm1) -force Invoke-psake -framework '4.0' ``` --- # Tasks At the core of psake is the task. In it's simplest form you have a task with a name and an action. ``` task Hello { "Hello world" } ``` This is the equivalent: ``` Task -Name "Hello" -Action { "Hello world" } ``` ## Dependencies[​](#dependencies "Direct link to Dependencies") The power of psake is in it's ability to support dependencies. You can add `-Depends` to your task and give it a list of other tasks that need to run before it does. ``` Task PBJ -Depends Toast, PB, Jelly { "Enjoy sandwich!" } Task Toast { "Toast 2 pieces of bread" } Task PB { "Spread some peanut butter" } Task Jelly { "Spread some jelly" } ``` ## Conditional Tasks[​](#conditional-tasks "Direct link to Conditional Tasks") You can conditionally run a task by using the "precondition" parameter of the "task" function. The "precondition" parameter expects a scriptblock as its value and that scriptblock should return a $true or $false. The following is an example build script that uses the "precondition" parameter of the task function: ``` task default -depends A,B,C task A { "TaskA" } task B -precondition { return $false } { "TaskB" } task C -precondition { return $true } { "TaskC" } ``` The output from running the above build script looks like the following: ``` Executing task: A TaskA Precondition was false not executing B Executing task: C TaskC Build Succeeded! ---------------------------------------------------------------------- Build Time Report ---------------------------------------------------------------------- Name Duration ---- -------- A 00:00:00.0231283 B 0 C 00:00:00.0043444 Total: 00:00:00.1405840 ``` Notice how task "B" was not executed and its run-time duration was 0 secs. ---