This page looks best with JavaScript enabled

Getting started with AppVeyor.

 ·  ☕ 6 min read  ·  ✍️ Javy de Koning

AppVeyor is a CI/CD (Continuous Integration / Continuous Deployment) platform that’s aimed at .NET developers. In this blog-post I’ll take you through the basics for setting up your first Project. We will be building, testing and deploying a PowerShell Module to the PowerShell Gallery using AppVeyor and GitHub.

Getting started

Before we go into the details you will need fork my AppVeyorDemo repository OR setup your own GitHub repository. Next sign-in and add your project. Every build runs on a fresh (currently Windows 2012r2 x64) virtual machine which is not shared with other builds and the state of which is not preserved between consequent builds. After the build is finished its virtual machine is decommissioned.

Build config (appveyor.yml)

Project builds can be configured by either YAML formatted file or using the AppVeyor user interface. I prefer to use YAML file because it makes it easier to fork/clone or contribute to a project. A minimal YAML is displayed below:

 1#---------------------------------# 
 2#      environment configuration  # 
 3#---------------------------------# 
 4version: 1.0.{build}
 5os: WMF 5
 6install:
 7  - ps: . .\AppVeyor\AppVeyorInstall.ps1
 8
 9environment:
10  MySecureVar:
11    secure: 2miEAGV72ED32TvZbXUbQA== #This is secured
12  MyNonSecureVar: NonSecure
13  ModuleName: appVeyorDemo
14#---------------------------------# 
15#      build configuration        # 
16#---------------------------------# 
17build_script: 
18  - ps: . .\AppVeyor\AppVeyorBuild.ps1
19
20#---------------------------------# 
21#      test configuration         # 
22#---------------------------------# 
23test_script: 
24  - ps: . .\AppVeyor\AppVeyorTest.ps1
25
26#---------------------------------# 
27#      deployment configuration   # 
28#---------------------------------# 
29deploy_script: 
30  - ps: . .\AppVeyor\AppveyorDeploy.ps1

There is not much we need to configure in the YAML file because most parts are handled in linked PowerShell scripts. Nevertheless lets go over the details:

  • version: this is the version number that the build will get. {build} is automatically incremented by AppVeyor with every build. You can change/reset this value in the Projects General settings page
  • os: the Build worker image to use. We will use ‘WMF 5’
  • environment: the environment variables to use. You can store both ‘secure’ and ‘non-secure’ variables here. You can encrypt a string here. The encrypted string can be stored in the yml file and only your account can decrypt that string in the build process. If another user tries to access it (for example in a pull request) it will not exist. From PowerShell we can access those variables using $env:var and we will do so later on.
  • Install, Build, Test, Deploy: we will handle all of these phases from PowerShell. These lines just execute the PowerShell scripts during the build process.

Install

As noted before, the ‘install’ phase will be handled from PowerShell. Because a new VM is launched for every build, we will need to set some prerequisites. In this example we are going to add the NuGet package provider and install the Pester and PSScriptAnalyzer modules from the PowerShell Gallery as we will require these modules for testing our own modules and/or scripts.

 1#---------------------------------# 
 2# Header                          # 
 3#---------------------------------# 
 4Write-Host 'Running AppVeyor install script' -ForegroundColor Yellow
 5
 6#---------------------------------# 
 7# Install NuGet                   # 
 8#---------------------------------# 
 9Write-Host 'Installing NuGet PackageProvide'
10$pkg = Install-PackageProvider -Name NuGet -Force
11Write-Host "Installed NuGet version '$($pkg.version)'" 
12
13#---------------------------------# 
14# Install Pester                  # 
15#---------------------------------# 
16Write-Host 'Installing Pester'
17Install-Module -Name Pester -Repository PSGallery -Force
18
19#---------------------------------# 
20# Install PSScriptAnalyzer        # 
21#---------------------------------# 
22Write-Host 'Installing PSScriptAnalyzer'
23Install-Module PSScriptAnalyzer -Repository PSGallery -force

Build

Since we are deploying a simple PowerShell module there is nothing to build. We will just print some info here:

 1#---------------------------------# 
 2# Header                          # 
 3#---------------------------------# 
 4Write-Host 'Running AppVeyor build script' -ForegroundColor Yellow
 5Write-Host "ModuleName    : $env:ModuleName"
 6Write-Host "Build version : $env:APPVEYOR_BUILD_VERSION"
 7Write-Host "Author        : $env:APPVEYOR_REPO_COMMIT_AUTHOR"
 8Write-Host "Branch        : $env:APPVEYOR_REPO_BRANCH"
 9
10#---------------------------------# 
11# BuildScript                     # 
12#---------------------------------# 
13Write-Host 'Nothing to build, skipping.....'

Successful Build

Test

Here is where our testing occurs. We will call the Pester tests from this script (line 11) and upload the results to the AppVeyor API (line 14). Using this approach has the benefit of including the test results as an XML in your repository. If any of the tests fail we will stop the build and will not continue with the deployment phase.

 1#---------------------------------# 
 2# Header                          # 
 3#---------------------------------# 
 4Write-Host 'Running AppVeyor test script' -ForegroundColor Yellow
 5Write-Host "Current working directory: $pwd"
 6
 7#---------------------------------# 
 8# Run Pester Tests                # 
 9#---------------------------------# 
10$testResultsFile = '.\TestsResults.xml'
11$res             = Invoke-Pester -Script .\Tests\AppVeyorDemo.Tests.ps1 -OutputFormat NUnitXml -OutputFile $testResultsFile -PassThru
12
13Write-Host 'Uploading results'
14(New-Object 'System.Net.WebClient').UploadFile("https://ci.appveyor.com/api/testresults/nunit/$($env:APPVEYOR_JOB_ID)", (Resolve-Path $testResultsFile))
15
16#---------------------------------# 
17# Validate                        # 
18#---------------------------------# 
19if (($res.FailedCount -gt 0) -or ($res.PassedCount -eq 0)) { 
20    throw "$($res.FailedCount) tests failed."
21} else {
22  Write-Host 'All tests passed' -ForegroundColor Green
23}

Pester and PSScriptAnalyzer tests

A template Pester and PSScriptAnalyzer tests is shared below (Credits to Ryan Yates). You should be able to keep most of it. Make sure you create some unit tests for your module and replace the ‘custom’ part of the script below.

 1#---------------------------------# 
 2# PSScriptAnalyzer tests          # 
 3#---------------------------------# 
 4$Scripts = Get-ChildItem "$PSScriptRoot\..\" -Filter '*.ps1' -Recurse | Where-Object {$_.name -NotMatch 'Tests.ps1'}
 5$Modules = Get-ChildItem "$PSScriptRoot\..\" -Filter '*.psm1' -Recurse
 6$Rules   = Get-ScriptAnalyzerRule
 7
 8if ($Modules.count -gt 0) {
 9  Describe 'Testing all Modules against default PSScriptAnalyzer rule-set' {
10    foreach ($module in $modules) {
11      Context "Testing Module '$($module.FullName)'" {
12        foreach ($rule in $rules) {
13          It "passes the PSScriptAnalyzer Rule $rule" {
14            (Invoke-ScriptAnalyzer -Path $module.FullName -IncludeRule $rule.RuleName ).Count | Should Be 0
15          }
16        }
17      }
18    }
19  }
20}
21if ($Scripts.count -gt 0) {
22  Describe 'Testing all Script against default PSScriptAnalyzer rule-set' {
23    foreach ($Script in $scripts) {
24      Context "Testing Script '$($script.FullName)'" {
25        foreach ($rule in $rules) {
26          It "passes the PSScriptAnalyzer Rule $rule" {
27            if (-not ($module.BaseName -match 'AppVeyor') -and -not ($rule.Rulename -eq 'PSAvoidUsingWriteHost') ) {
28              (Invoke-ScriptAnalyzer -Path $script.FullName -IncludeRule $rule.RuleName ).Count | Should Be 0
29            }
30          }
31        }
32      }
33    }
34  }
35}
36
37#---------------------------------# 
38# Custom Pester tests (replace)   # 
39#---------------------------------# 
40
41$PSVersion    = $PSVersionTable.PSVersion.Major
42$AppVeyorDemo = "$PSScriptRoot\..\AppVeyorDemo.psm1"
43
44Describe "AppVeyorDemo PS$PSVersion" {
45    Copy-Item $AppVeyorDemo TestDrive:\script.ps1 -Force
46    Mock Export-ModuleMember {return $true}
47    . 'TestDrive:\script.ps1'
48
49    Context 'Strict mode' { 
50        Set-StrictMode -Version latest
51
52        It 'Get-SomeInt should return int' {
53          Get-SomeInt | Should BeOfType System.Int32
54        }
55    }
56}

Successful Tests

Deploy

When all tests have passed we can continue with the deployment. Some things that we take care of here:

  1. Update the module manifest version. We use regex inject the ‘APPVEYOR_BUILD_VERSION’ environment variable that we configured in the appveyor.yml. (line 12)
  2. Verify the GitHub branch. If the GitHub branch is NOT the master branch we do not want to deploy to the PowerShell Gallery. (line 17)
  3. Adding the project path to the ‘psmodulepath’ PATH variable”. This is required for the ‘Publish Module’ cmdlet (line 25)
  4. Publish the module. Lastly we publish the module to the PowerShell Gallery. This requires your NuGetApiKey to be correctly configured encrypted and stored in the appveyor.yml file.
 1#---------------------------------# 
 2# Header                          # 
 3#---------------------------------# 
 4Write-Host 'Running AppVeyor deploy script' -ForegroundColor Yellow
 5
 6#---------------------------------# 
 7# Update module manifest          # 
 8#---------------------------------# 
 9Write-Host 'Creating new module manifest'
10$ModuleManifestPath = Join-Path -path "$pwd" -ChildPath ("$env:ModuleName"+'.psd1')
11$ModuleManifest     = Get-Content $ModuleManifestPath -Raw
12[regex]::replace($ModuleManifest,'(ModuleVersion = )(.*)',"`$1'$env:APPVEYOR_BUILD_VERSION'") | Out-File -LiteralPath $ModuleManifestPath
13
14#---------------------------------# 
15# Publish to PS Gallery           # 
16#---------------------------------# 
17if ($env:APPVEYOR_REPO_BRANCH -notmatch 'master')
18{
19    Write-Host "Finished testing of branch: $env:APPVEYOR_REPO_BRANCH - Exiting"
20    exit;
21}
22
23$ModulePath = Split-Path $pwd
24Write-Host "Adding $ModulePath to 'psmodulepath' PATH variable"
25$env:psmodulepath = $env:psmodulepath + ';' + $ModulePath
26
27Write-Host 'Publishing module to Powershell Gallery'
28#Uncomment the below line, make sure you set the variables in appveyor.yml
29#Publish-Module -Name $env:ModuleName -NuGetApiKey $env:NuGetApiKey

Successful Deploy


Javy de Koning
WRITTEN BY
Javy de Koning
Geek 🤓, Love sports 🏃‍♂️🏋️‍♂️, Food 🍛, Tech 💻, @Amsterdam ❌❌❌.