How To Setup A Github Actions Pipeline For Publishing Your Powershell Module

In this post, I will walk through how I set up a Github Actions Workflow Pipeline for publishing a Powershell module to the PowerShell Gallery.

What is a Github Actions Pipeline

Github Actions is an automation tool available in Github. It is a tool for automating software workflow, such as building, testing, or publishing your software. The good thing about Github Actions is that it is you will get the following with a free Github membership:

– Unlimited public/private repositories

– 2000 automation minutes/month

– 500MB of package storage

To explain how you can use Github Actions with your Powershell code I will give you an example:

You are developing a Powershell module and releasing it to either the PowerShell Gallery or a private repository. Your process of releasing new updates to your module consists of building, testing, and publishing your module and is all done manually or maybe with some scripts. The entire can be completely automated and set to work in Github, so when you create a Pull-Request or Merge into your main branch the automation process will begin. You can even configure it to cancel the deployment if either your tests or the build fails.

Prerequisites

To follow along with this guide you will need a Github account and an account to the PowerShell Gallery.

To get started with Github you can read my blog post: How To Add Source Control To Your PowerShell Module
To get started with PowerShell Gallery you can read my blog post: How To Get Started Publishing To The PowerShell Gallery

Creating the pipeline YAML file in GitHub

To create a new CI/CD Pipeline in GitHub you need to go to your GitHub repository and go to the “Actions” pane.

github actions

Inside the “Actions” pane you will have the ability to create different kinds of pipelines, you can choose between all sorts of different templates to get started. For this tutorial, I will create the pipeline from scratch, and there I will just choose “set up a workflow yourself”

github actions

Once you click the link, a file editor will open with a standard template for a GitHub Actions pipeline. I will go ahead and erase all pre-made code in the pipeline, so I have a fresh.yaml file.

Now you can either chose to edit the yaml file directly in the GitHub editor or you can save the file and pull the file to your repository on your local machine and edit the yaml file directly in Visual Studio Code.

I Choose to edit the yaml file in Visual Studio Code. So I will erase all the code, rename the file and then press the button “Start commit”. Once the file is committed to the repository I will, on my Mac, pull the main branch so I should now have a new folder in my repository named .github/workflows/pipeline.yml

github workflow
github pull

Now I am are ready to create the pipeline through the yaml file.

Setting up the pipeline

The pipeline’s purpose is to automate the entire process of:

– Testing the module code with Pester tests

– Linting the code (Analysing the code) with PSScriptAnalyser

– Building the module with a new version

– Publishing the module to Powershell Gallery

The pipeline will rely on some specific GitHub Actions already made and available on GitHub and then it will rely on the InvokeBuild Module and the build.ps1 script in the module. You can read about how the module folder structure is set up and how the build.ps1 file works together with the InvokeBuild module here: How to Get Started Developing a Powershell Module

Starting the Pipeline

To Start the pipeline there are a few things we need to set. First, we need to give it a name, and then we need to define how the pipeline can be triggered.

I will start by naming the Pipeline: Build Module.

name: Build Module

Then I will set the trigger to be on a pull request. This means that my process of developing and releasing the module will be:

I will develop the module in a branch called “dev”, then once I am ready to make a release, I will push the changes to the “dev” branch and then inside GitHub make the pull request. This way it forces me to make a certain action to publish the code. It also gives me the ability to push to the main branch without it publishing a new version of the module to Powershell Gallery.

on:
    pull_request: # Only trigger the workflow if there is a pull request to the main branch
        branches: [ main ]

    workflow_dispatch: # Enables the possibility to trigger the workflow manually
yaml pipeline

Jobs inside the pipeline

Inside the pipeline, you can define different jobs. The reason for this is that it enables you to define both a Build job and a Release job, or even a Test Job. The Job is just a set of actions that you want to perform.

The way I use it, I will have two jobs. A Build job and a Release job. The Build job will also handle the testing of my code.

The way to define jobs in the yaml file is:

Jobs:
    job1:
        runs-on: ubuntu-latest
        steps:
            #step 1 in job1
        
    job2:
        runs-on: ubuntu-latest
        steps:
            #step 1 in job2

Creating the build job

So to create the build job, I will start by defining what should actually happen through the Build job.

So I want the following to happen:

– Setting up the required Powershell module

– Run my build.ps1 script

– Save the new updated version of my module(Output from the build.ps1) to the main branch in my repository

– The last thing I want to do is to save the build module as a GitHub Artifact. The reason for this I will explain later.

Setting up the required Powershell modules in the pipeline

So to get started will add the following to my pipeline

jobs:
    build:
    runs-on: ubuntu-latest
    steps:
        - uses: actions/checkout@main

This yaml code will define the Jobs section. It then defines the first job which is named “build”. It then defines that the pipeline should be run on a virtual machine running the latest version on ubuntu, with the command: “runs-on: ubuntu-latest”. It then defines the section for all the steps inside this job with the command: “steps:”. at the end it states the command: “- uses actions/checkout@main” which calls a premade GitHub Action which checkout the main branch into the ubuntu virtual machine.

To set up the required Powershell modules on the ubuntu virtual machine running my pipeline I will use the following GitHub actions named: psmodulecache@v1, cache@v2. These two GitHub actions will try and cache the Powershell modules, this can help with the performance of your pipeline and make it a bit faster. Then at the end, I will just use a Powershell command to install any Powershell modules that are not found in the cache.

using the: psmodulechache@v1

- name: Set required Powershell modules
  id: psmodulecache
  uses: potatoqualitee/psmodulecache@v1
  with:
      modules-to-cache: Pester, PSSCriptAnalyzer, InvokeBuild, platyPS

The only thing I am setting in this action is which modules should be cached, so what modules am I using in my pipeline.

using the: cache@v2

- name: Setup PowerShell module cache
  id: cacher
  uses: actions/cache@v2
  with:
    path: ${{ steps.psmodulecache.outputs.modulepath }}
    key: ${{ steps.psmodulecache.outputs.keygen }}

This is all standard and i am not setting any parameters for this action

using the powershell command:

- name: Install required PowerShell modules
  if: steps.cacher.outputs.cache-hit != 'true'
  shell: pwsh
  run: |
    Set-PSRepository PSGallery -InstallationPolicy Trusted
    Install-Module ${{ steps.psmodulecache.outputs.needed }} -ErrorAction Stop 

This is all standard. It will if the caching outputs ‘true’ run a Powershell command. It will at the Powershell gallery as a trusted PSRepository and then it will install all the modules that is not already cached.

yaml pipeline

Running the build.ps1 script

To see the help files on how to get the build script and how it works read the Docs here: https://scriptingchris.tech/new-moduleproject_ps1/. But a quick overview is that the script will:

– Run pester tests if any has been made

– Run PSScriptAnalyzser on all the functions in your module to check linting.

– Compile all the Private and Public functions in your module into a single .psm1 file.

– Update the module version to the number of functions you have and the next build number.

– Create Markdown help files from the comment help in your functions.

So to use the build script we can just call a Powershell command which will run the script. The command will utilize the module InvokeBuild which was downloaded to the module cache in the previous step.

- name: Invoke Build
  shell: pwsh
  run: pwsh -command "Invoke-Build -File ./build.ps1 -Configuration 'Release' -ExportAlias"

This step will run the Powershell command: “Invoke-Build -File ./build.ps1” and it will add the parameters “-Configuration ‘Release'” to activate the module version updates and, “-ExportAlias” to export alias’s from your functions to your module.

yaml pipeline

Save the build version of the module to my main branch repository

Now I want to save the compiled module to the main branch I see in GitHub. So to make it clear where I am right now:

in the pipeline I have:

– created a virtual machine running ubuntu

– pulled the main branch onto the machine

– compiled a new version of the Powershell module

Now to save the compiled version of the module I will need to push the changes to the branch I have made on the ubuntu machine, to the main branch in GitHub.

To do this I will add the following to the yaml file:

- name: Push changes to Git Repository
  run: |
    git config --global user.name 'ScriptingChris'
    git config --global user.email 'christian@scriptingchris.tech'
    git remote set-url origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}
    git add .
    git commit -am "Pushing Artifacts"
    git push  

I am setting my git config to my user in the ubuntu machine, adding the Github token through a Github environment variable, and adding it to the git URL. Then I will add all the changes, make a commit and push to the main branch.

yaml pipeline

Saving the module as a GitHub build artifact.

The reason for this step is that I have now built the module but I still need to release the module to the Powershell Gallery. Now the Release part is handled in a separate job and therefore actually on a separate virtual machine running ubuntu. So for the Release job to access the build module, I will need to upload the Module as what is called an Artifact. The artifact is stored I a special place on my Github account where I can download it from, in another Job.

I will add the following to the pipeline:

- name: Upload Build Artifact
  uses: actions/upload-artifact@v2.2.3
  with:
    name: module-artifact # Naming the powershell module artifact
    path: ./Output/ # Saving the powershell module artifact to the path ./Output/
yaml pipeline

Creating the Release Job

The Release job is a bit simpler than the Build job. All I need to do here is:

– Download the module artifact created in the Build job

– Publish the module to the Powershell Gallery

To initiate the Release job I will add the following to the pipeline:

release:
  needs: build
  runs-on: ubuntu-latest
  steps:
      # Check out the main branch
    - uses: actions/checkout@main

Downloading the Module Artifact to the Release Job

To download the Module artifact I will use the GitHub action: actions-download-artifact@1.0.4:

- name: Download build artifact
  uses: aochmann/actions-download-artifact@1.0.4
  with:
    repo: ${{github.repository}}
    name: module-artifact # Name of the powershell module artifact
    path: Artifact/ # Downloads the module to the path ./Artifact/

Here I just state that the artifact should be downloaded into a folder name ./Artifact.

So in the root of the branch, I have checked out on the new Ubuntu machine I will now have a folder named /Artiface/<ModuleName>

Publishing the module to Powershell Gallery

To publish the module to the powershell gallery I will use GitHub action named: publish-powershell-module-action@v19

Now when I use this GitHub action I will need to provide the exact path to where I downloaded the Artifact. This requires me to know the module name. I could hard code the module name in the pipeline, but instead, to make it work with any module I have chosen to make a Powershell command, which will take the module name and save it into a GitHub Environment Variable. To do this I have added the following:

- name: Get Module Name
  run: |
    Write-Host $Env:GITHUB_REF
    $ModuleName = (Get-ChildItem -Path ./Artifact/).Name
    echo "MODULE_NAME=$ModuleName" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf-8 -Append    
  shell: pwsh

This will use the Write-Host to write out all the Environment Variables available. Then it will get the name of the Artifact just download by using: Get-ChildItem in the folder ./Artifact/ and selecting the Name property. Then it will echo the name into MODULE_NAME and save it as a GitHub Environment variable.

Now I can Publish the module with the following:

- name: Publish to Powershell Gallery
  uses: pcgeek86/publish-powershell-module-action@v19
  with:
    modulePath: ./Artifact/${{ env.MODULE_NAME }} # Using the environment variable to find the module name
    NuGetApiKey: ${{ secrets.NUGETAPIKEY }} # Using the NugetAPI key set in GitHub Secrets

in the modulePath I will set the location of where I downloaded the Artifact followed by the module name environment variable I just created.

In the end, I will set the NuGetApiKey environment secret which you find on the PowerShell Gallery.

Your Release job should look like this:

pipeline yaml

The complete Yaml Pipeline code:

name: Build Module
on:
  pull_request: # Only trigger the workflow if there is a pull request to the main branch
    branches: [ main ]
    
  workflow_dispatch: # Enables the possibility to trigger the workflow manually

jobs:
  # 1st Job -- Building the module
  build:
    runs-on: ubuntu-latest
    steps:
        # Checkout the main branch
      - uses: actions/checkout@main 
        
        # Setting up required powershell modules
      - name: Set required PowerShell modules
        id: psmodulecache
        uses: potatoqualitee/psmodulecache@v1
        with:
          modules-to-cache: Pester, PSScriptAnalyzer, InvokeBuild, platyPS
      
        # Setting up the powershell module cache
      - name: Setup PowerShell module cache
        id: cacher
        uses: actions/cache@v2
        with:
            path: ${{ steps.psmodulecache.outputs.modulepath }}
            key: ${{ steps.psmodulecache.outputs.keygen }}
        
        # Installing the required powershell module, if not cached
      - name: Install required PowerShell modules
        if: steps.cacher.outputs.cache-hit != 'true'
        shell: pwsh
        run: |
            Set-PSRepository PSGallery -InstallationPolicy Trusted
            Install-Module ${{ steps.psmodulecache.outputs.needed }} -ErrorAction Stop
        
        # Running the InvokeBuild Module Invoke-Build command with "Release configuration"
      - name: Invoke Build
        shell: pwsh
        run: pwsh -command "Invoke-Build -File ./build.ps1 -Configuration 'Release' -ExportAlias"
        
        # Pushing the changes from InvokeBuild to the main branch
      - name: Push changes to Git Repository
        run: |
          git config --global user.name 'ScriptingChris'
          git config --global user.email 'christian@scriptingchris.tech'
          git remote set-url origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}
          git add .
          git commit -am "Pushing Artifacts"
          git push
        
        # Uploads the build powershell module as an artifact
      - name: Upload Build Artifact
        uses: actions/upload-artifact@v2.2.3
        with:
          name: module-artifact # Naming the powershell module artifact
          path: ./Output/ # Saving the powershell module artifact to the path ./Output/
  
  # 2nd Job -- Releasing the module
  release:
    needs: build
    runs-on: ubuntu-latest
    steps:
        # Check out the main branch
      - uses: actions/checkout@main
        
        # Downloads the powershell module build artifact made in the build step
      - name: Download build artifact
        uses: aochmann/actions-download-artifact@1.0.4
        with:
          repo: ${{github.repository}}
          name: module-artifact # Name of the powershell module artifact
          path: Artifact/ # Downloads the module to the path ./Artifact/
        
        # Running a powershell command to save the module name as an Environment Variable
      - name: Get Module Name
        run: |
          Write-Host $Env:GITHUB_REF
          $ModuleName = (Get-ChildItem -Path ./Artifact/).Name
          echo "MODULE_NAME=$ModuleName" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf-8 -Append          
        shell: pwsh
        
        # Publishing the module to powershell gallery
      - name: Publish to Powershell Gallery
        # You may pin to the exact commit or the version.
        # uses: pcgeek86/publish-powershell-module-action@2a7837ce0746ea58c40574d8d6cbc6c44238edb7
        uses: pcgeek86/publish-powershell-module-action@v19
        with:
          modulePath: ./Artifact/${{ env.MODULE_NAME }} # Using the environment variable to find the module name
          NuGetApiKey: ${{ secrets.NUGETAPIKEY }} # Using the NugetAPI key set in GitHub Secrets

7 Comments

  1. Helⅼo just wanted to give you a quick headѕ up.
    The text in your article seem to be runnіng off the screen in Firefox.
    I’m not sure if this is a fοrmatting issue or something to
    do with browser compatibiⅼity but I figured I’d post to ⅼet yoս know.

    Ꭲhe stʏle and desіgn look great though! Hope you
    get thе issue fixed soon. Many thanks

    • Hi, thank you very much.
      I’ve just converted to a different theme and made some changes overall.
      I tested on Safari, Chrome and Firefox and and found no issues after I made the changes.
      So hopefully that fixed the issue.

      Thanks for letting me know.

  2. I’m truly enjoying the design ɑnd layout of your
    blog. It’s a very еɑsy on the eyes which makes it much more enjoyable for me to come here and visіt more often. Did you
    hire out a designer to create your tһeme? Great work!

    • Hi,
      Thanks for liking my theming. I’m glad you are finding it enjoyable.

      I just made some changes for the theme I was runnings, so hope you still like it. I am now running Blocksy WordPress theme.
      The only custom changes I have made is a bit of CSS, and then the Home and About pages was created with Elementor.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

This website uses cookies. By continuing to use this site, you accept our use of cookies.