In a previous back-to-basics blog post we covered manually creating Chocolatey packages. In this post we'll take your package creation skills to the next level by automating the update process.

A Quick Refresher

It's always good to keep in mind what a Chocolatey package is, so we'll start with a quick refresher. For a deeper dive please refer back to our first back-to-basics blog post.

First, it's important to remember that a package is not software. Instead, a package is a "container" that contains something that needs to be enacted on or delivered to a computer. This could include a software installer that needs to be executed, a font to be installed, or a file that needs to be stored in a specific directory. The possibilities are endless.

Another feature of a package is metadata about what is contained inside of it. This includes a unique identifier for the package, and a version number allowing you to update or rollback the package to a known point in time.

What Is Automatic Packaging?

Automatic packaging, as you might expect, automatically updates a Chocolatey package to reflect and update to its contents. This is most often done with packages that contain software installers as there is a very obvious link between a new version of the software and the need for a new version of its package.

In order to set up automatic packaging of a package that contains/manages a software installer, you will need:

  • Some way of detecting that an update is required.
  • Collection of metadata including the new version number and the new installer download location.
  • The ability to update the files in your package, specifically the .nuspec file and any PowerShell scripts.

The first two items are generally closely coupled, as you're likely to get your metadata from the same place as you're looking to tell if there is an update available. This could be the download page on a given piece of software's official webpage, release notes RSS feed, or the releases or tags listed on a GitHub repository.

To actually carry out the update of the files in your package, you could create your own script. However, a lot of the hard work has already been done for you via the Chocolatey-AU PowerShell Module (also available as a Chocolatey package). Originally created by Miodrag Milić, it has recently been adopted by the Chocolatey Community and contains functions that enable easy automatic packaging.

How Do You Automate Package Updates?

When we covered manual packaging, we created a package for Elk Native. You can find the result of this work on GitHub.

We will continue by taking the elk-native package from manual to automatic.

Get Your Metadata

As mentioned above, we need to both determine that an update is available and also collect metadata about the update. Elk Native is open source and is released via the project's GitHub repository, meaning that we can get the information we need from the GitHub API.

The generic form of the API endpoint we're using is: https://api.github.com/repos/<org>/<project>/releases/latest

To query this API, specifically for the Elk Native software, using PowerShell we can run:

$LatestRelease = Invoke-RestMethod -UseBasicParsing -Uri "https://api.github.com/repos/elk-zone/elk-native/releases/latest"

This command stores the response from GitHub in a variable called $LatestRelease so that we can pull information from it without needing to request the information from GitHub multiple times. You'll also notice the use of the -UseBasicParsing parameter. This is included simply because I will be executing this script via GitHub Actions and this parameter tells PowerShell not to attempt to use the Internet Explorer engine to parse the API response, as it won't be available.

:choco-info: NOTE

In this example we are not providing any form of authentication to the GitHub API. This means that we are restricted to 60 requests per hour. Given this, you should consider authenticating to the API which increases the applicable rate limit to 5,000 requests per hour.

From this release information we need to get the version number, the download URL, and a link to the release notes. Your package may need more or less information that this, read through your .nuspec file and ensure you're aware of everything that needs to be updated from version to version.

# Version Number
$Version = $LatestRelease.tag_name.Replace('elk-native-v', '')

# Download URL
$URL64 = ($LatestRelease.assets | Where-Object {$_.name.EndsWith("_windows_x86_64.msi")}).browser_download_url

# Release Notes URL
$ReleaseNotes = $LatestRelease.html_url

Here you'll see that we are referencing the "tag name" from the GitHub release to determine the version number. Often, you'll find that the version number is prefixed with a "v" that needs to be trimmed so that you just have "the version number" itself, but in the case of Elk Native, there is much more text that needs to be removed.

For the download URL, there is often a large number of "assets" attached to a GitHub release. These are for different Operating Systems, such as Windows, Linux, and macOS. They're also for different processor architectures, like x64, x86, or even ARM. Your package may require multiple installers, but the Elk Native installer only comes in a 64-bit variant and so we're only getting a single installer.

We now have our metadata, but how do we know that there's actually an update available? In short, you compare the version number we've collected in this step to the version number in our .nuspec file. You could write a script to make this comparison, or we can lean on the Chocolatey-AU module to do this for us.

Put Chocolatey-AU to Work

Now that we know how to get out metadata, we can start working on our actual Chocolatey-AU update script. We'll start the script by explicitly importing the module:

Import-Module Chocolatey-AU

Following this we'll populate a specially named function called au_GetLatest that is executed to gather metadata. We've largely already done this, however instead of saving the values into variables, we'll instead build and return a hash table. The data in this hash table will be assigned to an automatic variable called $Latest by the Chocolatey-AU module.

function global:au_GetLatest {
    $LatestRelease = Invoke-RestMethod -UseBasicParsing -Uri "https://api.github.com/repos/elk-zone/elk-native/releases/latest"

    @{
        URL64        = ($LatestRelease.assets | Where-Object {$_.name.EndsWith("_windows_x86_64.msi")}).browser_download_url
        Version      = $LatestRelease.tag_name.Replace('elk-native-v', '')
        ReleaseNotes = $LatestRelease.html_url
    }
}

Next we'll define a second specially named function called au_SearchReplace. This function is executed to update the files in your package. The content of this function is a hash table of paths to files, with a nested hash table per file that specifies a RegEx to search for inside said file and what to replace the matching content with.

function global:au_SearchReplace {
    @{
        ".\tools\chocolateyInstall.ps1" = @{
            "(?i)(^\s*(\$)url64\s*=\s*)('.*')"      = "`$1'$($Latest.URL64)'"
            "(?i)(^\s*checksum64\s*=\s*)('.*')"     = "`$1'$($Latest.Checksum64)'"
            "(?i)(^\s*checksumType64\s*=\s*)('.*')" = "`$1'$($Latest.ChecksumType64)'"
        }

        "elk-native.nuspec" = @{
            "(\<releaseNotes\>).*?(\</releaseNotes\>)" = "`$1$($Latest.ReleaseNotes)`$2"
        }
    }
}

In this example, you'll see that we're updating both the chocolateyInstall.ps1 and elk-native.nuspec files. You may find yourself asking two questions, however?

  1. Why haven't we specified a Search and Replace pair for the Version number? This is because Chocolatey-AU does this for us automatically, and so we don't need to worry about it.
  2. Where did those Checksum64 and ChecksumType64 properties come from? Chocolatey-AU automatically downloads the installer and calculates the checksum for us, then adds these values to the $Latest variable alongside the properties we gathered in the au_GetLatest function.

The final step is to actually execute the update. By default, Chocolatey-AU will assume that there is a 32-bit installed included in the package and so the Elk Native update will error because it only has a 64-bit installer. We avoid this error, but specifying what we need a checksum calculated for.

update -ChecksumFor 64

Save this script as update.ps1 into the root of the directory containing your package. It should be alongside your package's .nuspec file.

Updating the Package

Now we can run our update script and generate an updated package:

.\update.ps1

If any update is available, you should see a number of things happen. You will have an updated .nuspec file as well as any PowerShell scripts that required updating, and your package will be packed into the Chocolatey package file (.nupkg). This can then be pushed to the Chocolatey Community Repository, if it's a community package, or your own internal repository.

If an update isn't available, Chocolatey-AU will tell you so and won't create a new package.

In some cases you may need to force an update to your package, for example if the developer of the software has changed the installer without releasing a new version of the software, then the checksum in the original package will no longer match. You can force an update by setting the $au_Force variable to $true and then running your update script.

$au_Force = $true; .\update.ps1

The result will be your package updated with a version number that uses package fix version notation.

Next Steps

You now have a script you can run to generate an updated package. From here you can either set your API key in the $ENV:api_key environment variable and have Chocolatey-AU push the updated package to the Chocolatey Community Repository, or use the choco push command to push the package after Chocolatey-AU has completed.

You can also run your update script on a schedule to automate the update process. You could do this via a Scheduled Job on your computer, or use a service like GitHub Actions.

GitHub Actions is how I have personally automated the packages that I maintain, and while setting this up is beyond the scope of this blog post you can inspect the configuration for this here.

Common Questions

I covered one example of automating package updates in this post, but there are a few common questions that will be raised as you have been reading.

Are There Other Chocolatey-AU Functions?

The update script in this post only used two Chocolatey-AU functions to define how to update our package: au_GetLatest and au_SearchReplace. But there are two other useful functions:

  • au_BeforeUpdate can be used to perform actions like manually generating the checksum for files when the automatic checksum is not suitable. It can also be used to download files that you wish to embed in your package rather than just specifying a download URL.
  • au_AfterUpdate can be used to execute commands after the package update has completed, but before the package is packed. You can use this for manipulating files above and beyond what is possible with a RegEx search and replace.

Are There Good Examples of Chocolatey-AU Usage?

The biggest source of examples is the Chocolatey Community Chocolatey Packages repository. This is a repository maintained by the Chocolatey Community, rather than any one individual and hosts many automatically updated packages ranging from simple to much more complex packages.

When I was learning, I took a lot of inspiration from Maurice Kevenaar's Chocolatey Packages repository.

I Have More Questions!

Check the Chocolatey Packaging FAQ, leave a comment below, or reach out for community assistance on our Community Hub.

Wrap Up

This is the fifth post in our back-to-basics series. We do hope you've found this series interesting and helpful. We look forward to continuing to expand the series. Please do let us know if there are specific topics you would like to see covered!

If you have any more questions, please reach out for community assistance on our Community Hub.


comments powered by Disqus