Warren F bio photo

Warren F

Systems Engineer with a penchant for PowerShell, science, cooking, information security, family, cookies, and the Oxford comma.


@pscookiemonster LinkedIn Github Stackoverflow TechNet RSS Feed My old blog

My GitHub Repos

AppVReporting BuildHelpers Citrix.NetScaler Git-Presentation InfoBlox Invoke-Parallel PowerShell PSDepend PSDeploy PSDiskPart PSExcel PSHTMLTable PSRabbitMQ PSSlack PSSQLite PSStash RabbitMqTools SecretServer



I like this new Microsoft. Not only are they open sourcing more components, taking pull requests, and contributing upstream, they’re doing a great job reaching out and interacting with the community.

In the past year or so, I went from occasionally throwing code on GitHub, to collaborating on open source projects, sharing what I learned, and somehow having a fun project of mine appear in a few sessions on release pipelines.

Release Pipeline Projects

I owe much of this to Sergei V. and Michael G., two Microsoftees who helped motivate and prod me (and others) along the way.

This post is a follow up to the series on GitHub, Pester, and AppVeyor, and the post on building PowerShell modules. We’re going to wrap up the module writing process and demonstrate how to automatically deploy your modules to the PowerShell Gallery.

Why Continuous Deployment for Modules?

The importance of writing modules is covered here. This misses a few pieces of the puzzle though: now that we have a module up on GitHub and published to the gallery, we’re not quite done. If we want to reap some of the benefits of open source, we need to make it easy to pull in suggestions from helpful folks on the Internet.

For those of you writing modules, how often have you taken a pull request or committed a change, thinking I’ll get around to publishing this on the PowerShell gallery later? Publishing to the gallery is fairly painless, but it’s another thing to do and to remember. We’re all human, so we occasionally forget, or get lazy and skip that step.

You might even add those extra steps to your calculation of how long it will take to integrate someone’s suggestions. I’ll have to merge that in, and do a bunch of things afterwards to publish it, I’ll just tackle it all some other time.

Having a pipeline to automatically test and deploy our changes can remove the barriers that might dissuade us from these changes. It’s also a great learning opportunity for CI/CD ideas that you could apply to your infrastructure.

So! Where do we start?

The Ingredients

Let’s look at the ingredients for a recipe that gives us continuous deployment for a PowerShell module. We’ll use content from PSDeploy itself as an example:

  • A module built roughly following this guide
  • A GitHub account
  • An AppVeyor account (sign in with GitHub)
  • A PowerShell gallery account
  • Your PowerShell gallery key, encrypted by AppVeyor
  • appveyor.yml. This tells AppVeyor what to run when you make a change to your module
  • Build.ps1. This is a simple script to pull in dependencies and kick off psake, which does the real work
  • psake.ps1. This organizes your build into “tasks”. It runs your tests and deployments
  • deploy.psdeploy.ps1. This tells PSDeploy how to deploy your project - in this case, publishing a module

Let’s put these together!

The Recipe

Let’s boil this down into a few steps:

  • Create your module
  • Sign up for AppVeyor and PowerShellGallery.com
  • Add scaffolding for a release pipeline
  • Profit

That’s it! Once you get your module set up from the previous article, you can just layer a little scaffolding on top to automatically publish to the PowerShell gallery.

Following the Recipe

So! You should already have a GitHub account. We need two more accounts:

  • Register at PowerShellGallery.com. Sign in
  • Copy your PowerShell Gallery API key from your account page. Keep this key a secret
  • Sign in to AppVeyor with your GitHub account
  • Create a secure variable: Click your AppVeyor account drop down, Encrypt data. Paste in your API key and Encrypt! Copy out the resulting encrypted value

Note that GitLab CI and other build systems often have ways to inject secure data like this.

That’s it for the GUI stuff, it’s time to dive into some PowerShell!

Release Pipeline Scaffolding

We’re going to use the following components for our release pipeline:

  • AppVeyor.yml. Instructions for AppVeyor. We’ll still use this, but we’ll try to move as much of the build as possible into PowerShell tooling that will work in other build systems.
  • Build.ps1. A build script that sets up our dependencies and kicks off psake. Portable across build systems. We install and use a few dependencies:
    • BuildHelpers. A module to help with portability and some common build needs
    • Psake. A build automation tool. Lets us define a series of tasks for our build
    • Pester. A testing framework for PowerShell
    • PSDeploy. A module to simplify PowerShell based deployments - Modules, in this case
  • Psake.ps1. Tasks to run - testing, build (e.g. bump version number), and deployment to the PowerShell gallery
  • deploy.psdeploy.ps1. Instructions that tell PSDeploy how to deploy our module

This combination meets two goals:

  • It’s generalized and can be dropped into a new project
  • Outside of adding a build-system-config that kicks off build.ps1, it can be used on build systems like Jenkins, GitLab CI, or even your own PC

This might seem complex, but it’s really just a few steps - let’s walk through the code:


There are two key bits in the yaml:

    secure: oqMFzG8F65K5l572V7VzlZIWU7xnSYDLtSXECJAAURrXe8M2+BAp9vHLT+1h1lR0

This decrypts our API key and creates an environmental value that we can call in PowerShell as $ENV:NuGetApiKey. Be careful not to display this in any output.

  - ps: . .\build.ps1

This runs our build.ps1 file. AppVeyor runs from the project root, so we know the relative path to this script.


In build.ps1, we use Resolve-Module from Brandon Padgett to pull in dependencies: BuildHelpers, psake, Pester, and PSDeploy:

# Grab nuget bits, install modules, set build variables, start build.
Get-PackageProvider -Name NuGet -ForceBootstrap | Out-Null

Resolve-Module Psake, PSDeploy, Pester, BuildHelpers

Once these are in place, we use Set-BuildEnvironment to create some environment variables for our project. Here’s some example output:

Name                 Value
----                 -----
BHProjectName        psdeploy
BHProjectPath        C:\projects\psdeploy
BHPSModuleManifest   C:\projects\psdeploy\psdeploy\psdeploy.psd1
BHPSModulePath       C:\projects\psdeploy\psdeploy
BHCommitMessage      !Deploy Brandon's changes
BHBuildSystem        AppVeyor
BHBranchName         master
BHBuildNumber        132

You can use these details to simplify the rest of your build code, and to normalize variables across build systems. At the moment BuildHelpers supports AppVeyor, Jenkins, GitLab CI, and VSTS (Thanks Stijn!); pull requests would be welcome.

Lastly, we kick of psake, and if it fails (e.g. a failed test), we exit with a non-zero code to tell our build system that we didn’t succeed.

Invoke-psake .\psake.ps1
exit ( [int]( -not $psake.build_success ) )


This is where the real work starts to happen.

First things first, we set up a few variables that we use later - we use $PSScriptRoot if BuildHelpers hasn’t run, set a verbose flag if we see !verbose in a commit message, and run an Init task that doesn’t do much.

Test Phase

Next up we start running some tests:

$TestResults = Invoke-Pester -Path $ProjectRoot\Tests -PassThru -OutputFormat NUnitXml -OutputFile "$ProjectRoot\$TestFile"

If you want a re-usable header for your *.tests.ps1, you can use the output from BuildHelpers to generalize things:

# [Content from *.tests.ps1, not psake.ps1] #
$PSVersion = $PSVersionTable.PSVersion.Major
$ModuleName = $ENV:BHProjectName
$ModulePath = Join-Path $ENV:BHProjectPath $ModuleName

# Verbose output for non-master builds on appveyor
# Handy for troubleshooting.
# Splat @Verbose against commands as needed
$Verbose = @{}
if($ENV:BHBranchName -notlike "master" -or $env:BHCommitMessage -match "!verbose")

Import-Module $ModulePath -Force

Back in psake.ps1, we check to see if $ENV:BHBuildSystem is AppVeyor, and push up test results if so. This isn’t critical, but it gives us a list of test results on AppVeyor.

Build Phase

After the tests run, we can start a build phase. Not everyone will want to use this, but I like to run two shortcuts:

  • Bump the module version
  • Update FunctionsToExport = '*' in the module manifest to include all exported functions
# Load the module, read the exported functions, update the psd1 FunctionsToExport to include exported functions

# Bump the module version
Update-Metadata -Path $env:BHPSModuleManifest

Deploy Phase

Now for the fun part! We deploy the module:

$Params = @{
    Path = $ProjectRoot
    Force = $true
    Recurse = $false # We keep psdeploy.ps1 test artifacts, avoid deploying those : )
Invoke-PSDeploy @Verbose @Params

That was a bit anti-climactic! We’ve offloaded all the logic for publishing our module to PSDeploy, leaving us cleaner code.


We have two deployments in here. If we’re in a recognized build system ($env:BHBuildSystem), in the master branch ($env:BHBranchName), and have a commit message that includes !deploy ($env:BHCommitMessage), we publish our module to the gallery:

Deploy Module {
    By PSGalleryModule {
        FromSource $ENV:BHPSModulePath
        To PSGallery
        WithOptions @{
            ApiKey = $ENV:NugetApiKey

This is great, but wouldn’t it be nice to have development builds that folks could test and try out? We can borrow from the PowerShell team’s idea (and code!) of deploying NuGet packages to AppVeyor and add a second deployment - sounds complicated and fancy, but it’s not too bad:

Deploy DeveloperBuild {
    By AppVeyorModule {
        FromSource $ENV:BHPSModulePath
        To AppVeyor
        WithOptions @{
            Version = $env:APPVEYOR_BUILD_VERSION

Hit the docs for details on how to install a module published to AppVeyor.

That’s it! We can now install our official module from the PowerShell Gallery, and install development builds from AppVeyor.

What Happens Now?

This whole process sounds complicated, but it’s driven by four generic files you can add or update in your repository: appveyor.yml, build.ps1, psake.ps1, and something.psdeploy.ps1.

Here’s what happens automatically with every commit we push to GitHub:

  • GitHub sends AppVeyor a notification of your commit
  • AppVeyor parses your appveyor.yml and starts a build on a fresh VM
  • build.ps1 installs dependencies, sets up environment variables with BuildHelpers, and kicks off psake.ps1
  • psake.ps1 does the real work. It runs your Pester tests, and if they pass, runs PSDeploy against your psdeploy.ps1

That’s about it! Once this scaffolding is in place, you can let GitHub and AppVeyor do the work for you, and start thinking about applying release pipelines like this to your infrastructure!

Today, the PowerShell Gallery has fewer than 1,000 modules, many of them focusing on Azure and DSC. Hardly comparable to repositories like CPAN, PyPI, or RubyGems:

Module Counts

Hopefully we can get to a point where more folks are contributing to the gallery. There are a few things that might move this along:

  • Using tools like this pipeline could simplify and automate publication, encouraging lazy folks like myself to use the gallery
  • Educating our peers and outdated legal teams on the importance of open source could unlock a trove of internal tools that would be quite valuable to the community. Chef offers a webinar for lawyers; more education targeted at this common road-block-to-sharing might be helpful
  • Microsoft could lead by example. Do you see OperationsManager, SqlServer, SqlPs, or Microsoft.SharePoint.Powershell on the gallery?
  • You could pester your vendors to distribute modules on the gallery. More should be writing modules, and those that do rarely publish them on the gallery. Props to teams like Amazon’s who already do this
  • Open sourcing PowerShell could open the doors to pragmatic OSS contributors who have no motivation to work with an OS-specific language