Setting up a PowerShell CI Pipeline using Gitlab with Taylor Gibb

Written by Taylor Gibb, Developer with Derivco.

I have been doing a lot of PowerShell work over the past six month for a big internal project. This encouraged me to do some open source PowerShell work, and ultimately resulted in me setting up a CI pipeline for my open source PowerShell modules. The pipeline is responsible for:

  • Linting
  • Running tests
  • Conditional Deployment

Linting is easily accomplished with PSScriptAnalyzer and tests with Pester. Deploying PowerShell modules to the PowerShell Gallery on the other hand is admin, and i dont like admin. So the idea was that every time a commit on the master branch contained [deploy] in the commit message, we would kick off a deployment task that updated the modules.

Repository Structure

For the simplicity of this article, i am going to assume your repository is set up as follows.

You can probably edit my build script to work for other configurations, but some values, like the path in which to look for scripts to be included in the code coverage results are hard-coded to the layout of my repository. Nevertheless, you are probably wondering what some of those files are, and rightly so. In this article we will look at creating:

  • .gitlab-ci.yml – the Gitlab CI file
  • build.ps1 – the file that is executed by the GitLab runner
  • *.PSDeploy.ps1 – a file used by PSDeploy to publish our module to the PowerShell Gallery
  • *.Tests.ps1 – unit tests which are run by Pester

Gitlab CI

I am a big fan of the Gitlab CI solution. I have a build runner sitting in a VM on Azure and it is very easy to set up. You can check out the instructions on how to set it up over on their website, but be sure to choose PowerShell as the executor during the installation. Once the runner is set up, create a .gitlab-ci.yml in the root of your repository and populate it as follows.


This tells the build runner to call a file called build.ps1 in the same directory, with a list of tasks. Its important to notice that the release task is only run on the master branch, while all other branches only run the analyze and test tasks. So lets take a look at what build.ps1 looks like:

Most of the above code is self explanatory, so i am not going to get into to much detail. One thing i will note is that if you want your build to output a zip file, you can look at using the artifact section in your .gitlab-ci.yml file. This isn’t something i wanted to do, but it is well documented on the Gitlab website.


So far we have a build that doesn’t do much except do some lint checking via PSScriptAnalyzer, so lets take it one step further and add a test. Since i already had the code, i just added a test to my Test-LevenshteinDistancefunction, which as you can imagine, tests the Levenshtein Distance between two strings. All i needed to do was add a file along side the script, and append .Tests.ps1 to the name of the script. This left me with the Test-LevenshteinDistance.ps1 and Test-LevenshteinDistance.Tests.ps1files you see in the repository layout screenshot. Below you can see what my Test-LevenshteinDistance.Tests.ps1 file looks like, notice that Pester has its own DSL, but there is plenty documentation on the Pester wiki to help you write your tests.

You should now be able to run the build script on your local machine and get some test coverage. If for some reason your tests fail, it will list the failed tests as well. In our case, all is looking good.

We also need to add a regex so that Gitlab can pick up our test coverage and report it using one of those labels that all the cool kids are using. So head into the Gitlab web interface, open your project and choose to edit the CI\CD Pipelines. You will need to enter the following regex.

  • Code Coverage: (\d+.\d+%)

Now when a build, runs you will be able to see your test coverage in the build report.


We will also need to add a PSDeploy file. I just added one for my algorithms module. There are a lot of deployment targets, but i needed the PowerShell Gallery. If you would like to publish to something like the file system, be sure to check our their documentation. Here is the one i put together, once again you can consult the repository layout to see exactly where it goes.

You will notice that i am using an environment variable, we dont want to commit our api key to source control after all. This does however mean i need to configure it in the Gitlab web interface, but there is no magic here. We just need to be sure to name it NugetApiKey.

That is pretty much all there is to it.

Next time we look at how to add custom Script Analyzer rules, or override the default ones. As always, i welcome feedback in the comments!