Last week I published a post about publishing a PowerShell module to the artifacts section of Azure DevOps. It was basically an amendment to a script that someone else had written. But what I really want to do is write a bit more of an in-depth post on the pipelines I have used to publish a PowerShell module as an artifact. I have created a repo on GitHub so that people can follow from home if they really want to or take scripts and stick them in their own repos. And because Azure DevOps allows neat things like public projects, there is a corresponding public project that you can view if you want to see the build/release pipelines at work. This will probably be useful when it comes to the release pipeline as I’ve not stored the tasks as yaml in the repo, as I think releases as yaml is not yet ready. I’ll copy and paste the yaml at the end of this post anyway.

Create a Feed

Let’s start by creating a feed in Azure DevOps. This is where we are going to publish PowerShell modules so that we can consume them for later use, like in another build. Go to Artifacts in Azure DevOps and click on the “Create Feed” button

create a feed

There’s not really much to do here except give it a name and set the privacy level. Because this project is public there is only one privacy option.

created feed

Once the feed has been created we can connect to the feed. There’s a variety of feeds that can be hosted; we’re going to use the Nuget.exe feed.

connect to feed

The most important thing, in fact the only thing we really care about is the feed url. Copy this to somewhere like Notepad++ as we will need this for later.

nuget url

Create a PAT Token

We will need a PAT token when publishing out module to the feed. This token only needs very few privileges. In Azure DevOps, go to the User Settings cog (next to your profile pic) and select Personal Access Tokens.

crate a pat

The only privileges it needs are the ones under packaging.

pat created

Save the pat somewhere for when we create our release pipeline.

The Build

I have set up a build in YAML that will run some tests and run PSScriptAnalyzer across the files. I’ve also got a script that will update the version number of the PowerShell module. This will take in the buildID which I spoke about earlier.

Once this is done then the tests results are published and if there are any failed tests then the pipeline will fail at this point. If the tests pass then the artifacts are packaged up and published for the release to consume. I copy and publish sub-folders because publishing everything in one folder is bad practice for larger projects as it can slow build/release times.

The Release Pipeline

The release pipeline is rather straightforward; I set the Nuget version that the script that will publish the artifact to my feed.

- task: [email protected]1
  displayName: 'Use NuGet 5.5.1'
    versionSpec: 5.5.1

The next task is a Powershell task that runs the script in the artifact, erm, artifact folder. This requires the url and the token that I created earlier. Add them as variables in the release pipeline. If you have multiple feeds you want to publish to in the release then use the stages and scope the url to that stage. You’d also need to provide a value for AzDOArtifactFeedName on this script as I have hard-coded it to the one feed I am using. See also the note about the fact that my AzDoPat token is a secret.

#Your build pipeline references a secret variable named ‘azdoPat’. Create or edit the build pipeline for this YAML file, define the variable on the Variables tab, and then select the option to make it secret. See https://go.microsoft.com/fwlink/?linkid=865972

  azdoUrl: 'https://bzzztio.pkgs.visualstudio.com/AzDoSample/_packaging/artifact/nuget/v3/index.json'
- task: [email protected]2
  displayName: 'Publish noddyModule to Artifacts'
    targetType: filePath
    filePath: './$(System.DefaultWorkingDirectory)/_RichieBzzzt.azdo_psmodule_artifact/artifact/publishArtifacts.ps1'
    arguments: '-packageSourceUrl $(azdoUrl) -AzDoPAT $(azdoPat)'

One last thing I do is to set my release number to be that of my build, which is in the yaml as name: 0.0.$(build.buildid). This means that in the real world I have a good flow of what module was published by what release, which in turn corresponds to what build, which in turn corresponds to a check-in or PR.

release id

Once we run a build and a release is created, I can check over to the artifacts section of my project and see that my module is now available.


So here we go, the documentation that Microsoft neglect to write.

Notes From Writing This Pipeline

A few other things that doesn’t have anything to do with publishing an artifact:

Pester Woes

The Pester version on hosted build agents is woefully out of date - 3.4. To update it I need to run this -

try {
    Import-Module Pester -MinimumVersion 4.4.2
catch {
    Install-PackageProvider -Name NuGet -Force -Scope CurrentUser
    Install-Module Pester -MinimumVersion 4.4.2 -Scope CurrentUser -SkipPublisherCheck -Force -Verbose

Thoughts on YAML

This is the first time I’ve set up a YAML build and I don’t know how I feel about them. The idea of keeping a pipeline in sync with a version is useful if you ever need to rollback. I don’t know how complicated it would get with PR’s requiring changes, I’ve not really thought it through. I think also in the scenario of a demo being setup it is somewhat useful especially if someone wants to clone and setup their own. Where I think it prove really useful is many projects I work on at the moment have a cores set of steps that are pretty much identical and only require smallish changes. this would save a lot of tedious clicky clicky. I’d love to be able to switch YAML tasks to view the Classic tasks in the UI, because you can view the YAML of tasks in Classic builds. It’s a shame you cannot as I’d probably be more inclined to try them out a bit more.

When Failing Tests Do Not Fail The Build

Bit of a sidebar here: why do steps in Azure DevOps pass when there are failed tests? This is a concept that does seem a little odd. The things is is that if a test fails, then the tests has still been run successfully, it is merely its output is reporting a failure successfully. If a build stopped on the first failed test then I do not have visibilityo n how many other tests may have passed/failed. So it is better for all tests to be run successfully, there outcomes reported on and then whether a build has passed/failed be determined on the outcome of any tests failing. This is exactly what publishing the test results gives you.