/powershell-module-cicd-templates-azure-pipelines

Some Azure Pipelines templates I find handy when I need to lint, test, build, and/or publish a PowerShell module

Azure Pipelines templates to help you author PowerShell modules

Let's say you're writing a PowerShell module called HelloWorld that you'd like to publish into your company's private PowerShellGet gallery.

You've got a copy of your source code saved into Azure Repos version control (Git), now you'd like to author an Azure Pipelines CI/CD pipeline to "lint," "test," "build," and "publish" your module each time you update the contents of the Git repository.

Starting codebase

Your source code's file structure might look something like this:

/
├─ .cicd
│  └─ my_pipeline.yml
├─ src
│  └─ my_module
│     ├─ Private
│     │  └─ Get-CatFactArray.ps1
│     ├─ Public
│     │  ├─ Get-CatFact.ps1
│     │  └─ Get-Greeting.ps1
│     ├─ Tests
│     │  └─ Public
│     │     ├─ Get-CatFact.Tests.ps1
│     │     └─ Get-Greeting.Tests.ps1
│     ├─ HelloWorld.psd1
│     └─ HelloWorld.psm1
├─ .gitignore
└─ README.md

Writing a CI/CD pipeline YAML file

Here's what you might write into the YAML file that defines your Azure Pipeline (in this case, /.cicd/my_pipeline.yml) if you wanted to automatically build and publish your module to your company's gallery every time you edit the main branch of your repository:

name: "Build out the HelloWorld module"


trigger:
  # Fire this CI/CD pipeline every time there are updates to the main branch of its associated Git repository
  branches:
    include:
      - "main"


pool:
  # Run on Linux because it's cheaper than Windows
  vmImage: "ubuntu-latest"


resources:
  repositories: 
    # Handy templates from Katie Kodes
    - repository: "psCicdHelpersRepo"
      type: "github"
      name: 'kkgthb/powershell-module-cicd-templates-azure-pipelines'
      ref: 'refs/heads/main'
      endpoint: 'REPLACE_THIS_WITH_THE_NAME_OF_A_GITHUB_TYPED_SERVICE_CONNECTION_IN_YOUR_AZURE_DEVOPS_PROJECT'


steps:

  # Lint.
  - template: "/templates/lint/run_and_report_psscriptanalyzer_linting.yml@psCicdHelpersRepo"
    parameters:
      sourceCodeFolderPath: "$(Build.SourcesDirectory)/src/all_my_modules/HelloWorld"

  # Test.
  - template: "/templates/test/run_and_report_pester_tests.yml@psCicdHelpersRepo"
    parameters:
      sourceCodeFolderPath: "$(Build.SourcesDirectory)/src/all_my_modules/HelloWorld"

  # Generate a version number based on sysdate at time of this CI/CD pipeline running.
  - template: "/templates/version/suggest_version_number_from_sysdate.yml@psCicdHelpersRepo"

  # Build, using chosen version number.
  - template: "/templates/build/build_module_and_cache_as_artifact.yml@psCicdHelpersRepo"
    parameters:
      sourceCodeFolderPath: "$(Build.SourcesDirectory)/src/all_my_modules/HelloWorld"
      versionNumber: $(suggest_version_number_based_on_sysdate.suggested_version_number)

  # Publish built module to Azure Container Registry.
  - template: "/templates/publish/publish_module_to_acr.yml@psCicdHelpersRepo"
    parameters:
      acrUrl: "https://REPLACE_THIS_WITH_YOUR_ACR_URL.azurecr.io"
      ado_service_connection_name: "REPLACE_THIS_WITH_YOUR_SERVICE_CONNECTION_NAME"

Pick and choose

Feel free to pick and choose as many or as few of the Azure Pipelines templates I've shared here as are useful to you.

  • Warning: please store a copy of these templates somewhere safe. I can't guarantee I'll be on the internet forever.

Identity and Access Management

For publishing

The only one of the Azure Pipelines templates I've shared here that requires special security steps is the one that publishes to Azure Container Registry.

If you're using it, be sure to set up an Azure DevOps service connection that uses federated credentials to log into an Entra app registration serving as the "principal" of an Azure RBAC role assignment of type acrPush scoped to the Azure Container Registry resource your company is using as an internal PowerShellGet gallery.

  • Note: in a corporate setting, you might need to open a ticket to have this done.

For consuming

Be sure to create an Azure RBAC role assignment of type acrPull scoped to your Azure Container Registry whose "principal" is an Entra group full of human Entra users who need to be able to install and run your HelloWorld module.

(And maybe another role assignment whose "principal" is an Entra group full of nonhuman Entra identities that also need to be able to install and run your HelloWorld module.)

  • Note: in a corporate setting, you might need to open a ticket to have this done.

Registering your Azure Container Registry as a PowerShell gallery on a desktop computer

Anyone with acrPull access to the corporate gallery should be able to run this PowerShell command:

Register-PSResourceRepository -Name 'ANY_NICKNAME_THEY_LIKE' -Uri 'https://REPLACE_THIS_WITH_YOUR_ACR_URL.azurecr.io'

Installing your module from Azure Container Registry on a desktop computer

Anyone with the Azure CLI already installed onto their coroprate desktop computer (often available in your company's Windows Software Center) and authenticated into your company's Entra tenant through az login (likely via a single sign on popup in their web browser) should be able to successfully confirm that your HelloWorld module exists in the corporate PowerShell gallery with this PowerShell command:

Find-PSResource -Repository 'ANY_NICKNAME_THEY_LIKE' -Name 'HelloWorld'

They can install HelloWorld onto their computer with this PowerShell command:

Install-PSResource -Repository 'ANY_NICKNAME_THEY_LIKE' -Name 'HelloWorld' -Scope 'CurrentUser'
  • Note: If at any point they get a "Response returned status code: Unauthorized" error message, they should make sure their Azure CLI is actually logged into the correct Entra tenant with az account show, az account list, az account set --name 'THEIR_RELEVANT_AZURE_SUBSCRIPTION_NAME', etc.

Running your function

Now when they run this PowerShell function:

Get-Greeting

They might see something like this output, if that's how you coded Get-Greeting:

Hello, World.

Links