Recently I released a new PowerShell Module called PSJwt to the PowerShell Gallery. This is a PowerShell Module for JWT (JSON Web Tokens). The PowerShell Module is using the Jwt.Net library. This library supports generating and decoding JSON Web Tokens.
Part of the code for this PowerShell Module are the build steps and Azure DevOps YAML Build Pipeline.
In this blog post I explain how to use an Azure DevOps YAML Build Pipeline to Publish your PowerShell Module to the PowerShell Gallery.
For the Build Steps I used the Invoke-Build PowerShell Module from Roman Kuzmin.
Invoke-Build/InvokeBuild is a build and test automation tool which invokes tasks defined in PowerShell v2.0+ scripts. It is similar to psake but according to Roman easier to use and more powerful. It is complete, bug free, well covered by tests.
Run:
Install-Module -Name InvokeBuild -scope CurrentUser
And when you use Visual Studio Code I recommend you to also install the following helper PowerShell scripts:
Install-Script -Name Invoke-TaskFromVSCode
Install-Script -Name New-VSCodeTask
For development of the PowerShell Module I created the following tasks in the PSJwt.build.ps1 script:
Note:
Not all the task defined in the Build script will be used in the Azure DevOps Build Pipeline.
We'll be using the following tasks in the Build Pipeline:
* Clean
* Copy PowerShell Modules files to output folder
* Publish Module to PowerShell Gallery
For Publishing the PowerShell Module we need to use the PowerShell Cmdlet Publish-Module from the PowerShellGet PowerShell Module.
PublishModule InvokeBuild Task code:
task PublishModule -If ($Configuration -eq 'Production') {
Try {
# Build a splat containing the required details and make sure to Stop for errors which will trigger the catch
$params = @{
Path = ('{0}\Output\PSJwt' -f $PSScriptRoot )
NuGetApiKey = $env:psgallery
ErrorAction = 'Stop'
}
Publish-Module @params
Write-Output -InputObject ('PSJwt PowerShell Module version published to the PowerShell Gallery')
}
Catch {
throw $_
}
}
I added a parameter to the PSJwt.build.ps1 script to manage when the PowerShell Module should be publised to the Gallery.
If you call the task as follows the module will be published to the PowerShell Gallery:
Invoke-Build -Configuration 'Production' -Task PublishModule
The PowerShell Gallery API Key is stored as an Environment variable called PSGallery. On your local system you can create the environment variable as follows.
[Environment]::SetEnvironmentVariable('PSGallery', '[enter here PowerShell Gallery API Key]', 'User'
On the Azure DevOps (Build) Agent we will use a variable (NuGetAPIKey) to configure the PowerShell Gallery API Key.
Azure Pipelines is a cloud service that you can use to automatically build and test your code project and make it available to other users. It works with just about any language or project type.
Azure Pipelines combines continuous integration (CI) and continuous delivery (CD) to constantly and consistently test and build your code and ship it to any target.
Last year Microsoft introduced YAML builds to give you another option for defining and evolving your continuous integration builds as your code evolves. I defined my Azure DevOps Build Pipeline in a YAML file, part of the code in my Github Repository.
During the Build I want to execute some of the tasks I defined in my Invoke-Build script PSJwt.build.ps1 and that is why I need to do some pre-requisites tasks like installing PowerShell Modules (InvokeBuild) before I can call the Invoke-Build PowerShell Function.
Let’s have a look at the azure-pipelines.yml file:
# Docs: https://aka.ms/yaml
name: $(Build.DefinitionName)_$(Date:yyyyMMdd))
pr:
- master
queue:
name: Hosted VS2017
steps:
- powershell: .\bootstrap.ps1
displayName: 'Install pre-requisites'
- task: richardfennellBM.BM-VSTS-PesterRunner-Task.Pester-Task.Pester@8
displayName: 'Pester Test Runner'
inputs:
scriptFolder: '$(System.DefaultWorkingDirectory)\tests\*'
additionalModulePath: '$(Build.ArtifactStagingDirectory)'
resultsFile: '$(Common.TestResultsDirectory)\Test-$(Build.DefinitionName)_$(Build.BuildNumber).xml'
- task: PublishTestResults@2
displayName: 'Publish Test Results'
condition: always()
inputs:
testRunner: NUnit
searchFolder: '$(Common.TestResultsDirectory)'
- powershell: Invoke-Build -Configuration 'Production' -Task Clean, CopyModuleFiles, PublishModule
displayName: 'Publish PowerShell Module'
env:
psgallery: $(NugetAPIKey)
The following code defines the type of (Build) Agent where the build taks will be run on. In this case a Hosted VS2017 Agent.
queue:
name: Hosted VS2017
The next task in the Build will use the PowerShell Task to run the pre-requisites PowerShell script (bootstrap.ps1) which installs the following PowerShell modules, InvokeBuild, Pester and PlatyPS.
I found this bootstrap script solution on Joel Bennett’s PTUI Github Repository.
- powershell: .\bootstrap.ps1
displayName: 'Install pre-requisites'
The nexts tasks will run the Pester Tests using Richard Fennell’s Pester Test Runner Build Task. This time I’m not using the Test task from my own build script, but you could also use the Test Task from my Build script if you wanted. I choose Richard’s Task because it creates a results file which is used in the next task to publish the test results.
- task: richardfennellBM.BM-VSTS-PesterRunner-Task.Pester-Task.Pester@8
displayName: 'Pester Test Runner'
inputs:
scriptFolder: '$(System.DefaultWorkingDirectory)\tests\*'
additionalModulePath: '$(Build.ArtifactStagingDirectory)'
resultsFile: '$(Common.TestResultsDirectory)\Test-$(Build.DefinitionName)_$(Build.BuildNumber).xml'
- task: PublishTestResults@2
displayName: 'Publish Test Results'
condition: always()
inputs:
testRunner: NUnit
searchFolder: '$(Common.TestResultsDirectory)'
With the last task the Invoke-Build script (PSJwt.build.ps1) is run on the Build Agent. The tasks Clean, CopyModuleFiles and PublishMode are executed.
- powershell: Invoke-Build -Configuration 'Production' -Task Clean, CopyModuleFiles, PublishModule
displayName: 'Publish PowerShell Module'
env:
psgallery: $(NugetAPIKey)
Note:
You also need to define a Azure DevOps variable which contains your PowerShell Gallery NugetAPIKey value.
The NugetAPIKey variable will be configured as an environment variable which will be used during the PublishModule Build Task.
I want to trigger the Build and the Publish PowerShell Module task only for the Master Branch and when the PowerShell Module Manifest file is commited to the Github Repository.
Above configuration triggers the Build when a new Module Manifest (PSJwt.psd1) commit is made. All other file commits don’t trigger a new build.
Hope this inspired you to use Azure DevOps YAML Pipelines to publish your PowerShell Modules to the PowerShell Gallery.
References: