Planet PowerShell logo

Contents

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.

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.

/images/how-to-setup-a-github-actions-pipeline-for-publishing-your-powershell-module/image1.png

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”

/images/how-to-setup-a-github-actions-pipeline-for-publishing-your-powershell-module/image2.png

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

/images/how-to-setup-a-github-actions-pipeline-for-publishing-your-powershell-module/image3.png

/images/how-to-setup-a-github-actions-pipeline-for-publishing-your-powershell-module/image4.png

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.

1
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.

1
2
3
4
5
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

/images/how-to-setup-a-github-actions-pipeline-for-publishing-your-powershell-module/image5.png

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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
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

1
2
3
4
5
jobs:
    build:
    runs-on: ubuntu-latest
    steps:
        - uses: actions/[email protected]

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/[email protected]” 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: [email protected], [email protected] 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: [email protected]

1
2
3
4
5
- name: Set required Powershell modules
  id: psmodulecache
  uses: potatoqualitee/[email protected]
  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: [email protected]

1
2
3
4
5
6
- name: Setup PowerShell module cache
  id: cacher
  uses: actions/[email protected]
  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:

1
2
3
4
5
6
- 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.

/images/how-to-setup-a-github-actions-pipeline-for-publishing-your-powershell-module/image6.png

Running the build.ps1 script

Here is a quick overview of what the script will do:

– 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.

1
2
3
- 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.

/images/how-to-setup-a-github-actions-pipeline-for-publishing-your-powershell-module/image7.png

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:

1
2
3
4
5
6
7
8
- name: Push changes to Git Repository
  run: |
    git config --global user.name 'ScriptingChris'
    git config --global user.email '[email protected]'
    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.

/images/how-to-setup-a-github-actions-pipeline-for-publishing-your-powershell-module/image8.png

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:

1
2
3
4
5
- name: Upload Build Artifact
  uses: actions/[email protected]
  with:
    name: module-artifact # Naming the powershell module artifact
    path: ./Output/ # Saving the powershell module artifact to the path ./Output/

/images/how-to-setup-a-github-actions-pipeline-for-publishing-your-powershell-module/image9.png

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:

1
2
3
4
5
6
release:
needs: build
runs-on: ubuntu-latest
steps:
    # Check out the main branch
    - uses: actions/[email protected]

Downloading the Module Artifact to the Release Job

To download the Module artifact I will use the GitHub action: [email protected]:

1
2
3
4
5
6
- name: Download build artifact
  uses: aochmann/[email protected]
  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/

To publish the module to the powershell gallery I will use GitHub action named: [email protected]

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:

1
2
3
4
5
6
- 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:

1
2
3
4
5
- name: Publish to Powershell Gallery
  uses: pcgeek86/[email protected]
  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:

/images/how-to-setup-a-github-actions-pipeline-for-publishing-your-powershell-module/image10.png

The complete Yaml Pipeline code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
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/[email protected]

        # Setting up required powershell modules
    - name: Set required PowerShell modules
        id: psmodulecache
        uses: potatoqualitee/[email protected]
        with:
        modules-to-cache: Pester, PSScriptAnalyzer, InvokeBuild, platyPS

        # Setting up the powershell module cache
    - name: Setup PowerShell module cache
        id: cacher
        uses: actions/[email protected]
        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 '[email protected]'
        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/[email protected]
        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/[email protected]

        # Downloads the powershell module build artifact made in the build step
    - name: Download build artifact
        uses: aochmann/[email protected]
        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/[email protected]44238edb7
        uses: pcgeek86/[email protected]
        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