How To Create an Extension Package

How To Create an Extension Package

As we’ve mentioned in other articles, a Chocolatey package can do near anything PowerShell can - including sharing functions and logic between packages.

In this how-to, we’ll talk about sharing functions between packages using extension packages.

Back To Basics Livestream

The creation, and usage, of Chocolatey extension packages was a topic of a recent back-to-basics livestream. The recording of that livestream can be found here:

What Is an Extension Package

In Chocolatey-parlance, an extension package is a package that extends the ability of other Chocolatey packages, by making additional PowerShell functions or cmdlets available.

Most simply put, a given extension package will import one or more PowerShell modules every time choco runs!

Why Would You Use an Extension Package

The main reason we talk about extension packages is to reduce having to add the same lines of code or functions to multiple packages you maintain.

You may be familiar with DRY (“Don’t Repeat Yourself”) coding principles, which (put simply) hold that there are benefits to maintaining your code in a single location and calling that where appropriate.

Extension packages allow you to easily follow this best practice within your Chocolatey packages without copying your lines of code into dozens of places (or packages).

Examples of this include:

You might choose to use this over installing a full PowerShell module if you didn’t want to expose the PowerShell module to the normal users of the computer - for instance, if you never wanted to expose the functions outside of Chocolatey runspaces. If you just wanted to use an available PowerShell module, you might consider having a dependency on a package that installs that module, instead.

Extension Package

An extension package is organised much like any other Chocolatey package - though instead of the scripts within the tools directory you may be familiar with, there should be an extensions directory.

We’ll now run through how to create an extension package, assuming you have an environment set up as in Preparing Your Environment for Package Creation.

Creating an Extension Package

Shortcut: You can use a package template to create an extension package! Try it, by installing the extension.template package and running choco new test.extension --template extension

The extension package is actually different enough to a regular package that using the default template would result in more cleanup than help!

Follow the steps below to get started:

  1. Create a new folder for this package. For the example, we will call it example.extension.
    1. Create a package metadata file named example.extension.nuspec, and add content as shown below.
  2. In the example.extension folder, create a folder named extensions.
    1. Within the extensions folder, create a ExampleModule.psm1 file, and add content as shown below.
Package Metadata Content
<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
  <metadata>
    <id>example.extension</id>
    <version>0.1.0</version>
    <title>Example Extension</title>
    <authors>__REPLACE__</authors>
    <description>A super cool extension package!</description>
    <summary>Extension package for testing.</summary>
    <tags>extension chocolatey package</tags>
  </metadata>
</package>
Simple Module Content
function Set-ConfigValue {
    <#
        .Synopsis
            Sets a value for that program you're using.

        .Example
            Set-ConfigValue -Name SMTP -Value 6
    #>
    [CmdletBinding()]
    param(
        # The name of the config to set
        [Parameter(Mandatory, Position = 0, ValueFromPipelineByPropertyName)]
        [string]$Name,

        # The value to set the config to
        [Parameter(Mandatory, Position = 1, ValueFromPipelineByPropertyName)]
        [string]$Value,

        # The path to the config file to modify
        [string]$Path = "C:\ProgramData\exampleapp\config.json"
    )
    begin {
        # Ensure the Config Directory exists
        if (-not (Test-Path (Split-Path $Path))) {
            $null = New-Item -Path (Split-Path $Path) -ItemType Directory
        }
        $Configuration = if (Test-Path $Path) {
            Get-Content $Path | ConvertFrom-Json
        } else {
            @{}
        }
    }
    process {
        $Configuration.$Name = $Value
    }
    end {
        $Configuration | ConvertTo-Json | Set-Content -Path $Path
    }
}

So, if you browse through the files in VS Code or Explorer, you should see a file structure like this:

example.extension
├── example.extension.nuspec
├── extensions
│   ├── ExampleModule.psm1

Open the example.extension.nuspec file in VS Code and modify the metadata as appropriate:

  • authors: This should be replaced with your name or handle.

That’s all you need!

Creating Your Install Script

As we mentioned earlier, there’s no need for an install script in an extension package - Chocolatey CLI handles putting the extension in the right place!

You can also include a tools directory, with the standard Chocolatey scripts (which could be used for configuration of the module), but it is not required.

Considering Uninstallation

Chocolatey will also handle uninstallation of the extension module automatically, so there’s no need to add a chocolateyUninstall.ps1 script to this package.

Compiling Your Package

You can now use choco pack to compile your Chocolatey package, creating a file with a .nupkg extension, ready for installation!

  1. In VS Code, press Ctrl + Shift + P or use the View menu and click on Command Palette.
  2. Select Chocolatey: Package Chocolatey package(s) from the prompt.
  3. Select example.extension.nuspec from the prompt.
  4. In Additional Arguments enter --output-directory='~\tutorials' (or whichever directory you’re using for these tutorials), and press Enter.

You should have a new package generated in your current working directory.

Installing Your Package

This sort of package isn’t normally installed on it’s own, but you can now test installation of your package!

In an elevated PowerShell command prompt, run the following:

choco install example.extension --source='tutorials' -y

You should then be able to browse to the extensions directory, within your Chocolatey installation, and see that your PowerShell module has been placed there.

# This should show an 'example' directory, along with all of your other installed extensions
Get-ChildItem $env:ChocolateyInstall\extensions

# This should show your module
Get-ChildItem $env:ChocolateyInstall\extensions\example

Where Are Extensions Stored?

Chocolatey packages are stored within the lib folder, of the Chocolatey installation location. This is the C:\ProgramData\chocolatey\lib folder by default. However, Chocolatey CLI understands packages that end with .extension and some additional processing is completed. The actual extension package contents will be extracted, and stored, in the C:\ProgramData\chocolatey\extensions directory. The name of the folder will match the name you gave to the extension package.

For example, an extension package with the name chocolatey-windowsupdate.extension, will, by default, have the package contents extracted to C:\ProgramData\chocolatey\extensions\chocolatey-windowsupdate.

This special handling for packages ending with .extension also applies to packages that end with .hook and .template. For .hook packages, by default, there is a folder at C:\ProgramData\chocolatey\hooks. For .template packages, by default, there is a folder at C:\ProgramData\chocolatey\templates.

Using Shared PowerShell Functions

Once you’ve created your extension package, using the functions contained within is as simple as ensuring the package is installed (for instance, by adding a dependency) and calling the functions within your Chocolatey scripts.

If you’ve already created a config package, why not try adding your newly available functions to it? We’ll use it as an example, here, but you can apply the same ideas to other packages.

Nuspec Dependency Addition

Your final .nuspec file should look something like this:

<?xml version="1.0" encoding="utf-8"?>
<!-- Do not remove this test for UTF-8: if “Ω” doesn’t appear as greek uppercase omega letter enclosed in quotation marks, you should use an editor that supports UTF-8, not this one. -->
<package xmlns="http://schemas.microsoft.com/packaging/2015/06/nuspec.xsd">
  <metadata>
    <id>example-config</id>
    <version>0.1.0</version>
    <title>Example Configuration</title>
    <authors>PackageMaintainer</authors>
    <tags>example config-only</tags>
    <summary>A package that configures 'example'</summary>
    <description>Further detail about the configuration that is applied, and any package parameters.</description>
    <dependencies>
        <dependency id="example.extension" />
    </dependencies>
  </metadata>
  <files>
    <file src="tools\**" target="tools" />
  </files>
</package>

Note the new <dependencies> section, near the bottom! There are a lot of ways to specify specific versions, ranges of versions, and more - see here for more detail.

You should now be able to reference your function within the package, without having defined it there.

PowerShell Dependency Addition

# Write Configuration File
Set-ConfigValue -Name LicensePath -Value (Join-Path $ConfigDirectory "LICENSE.txt")
Set-ConfigValue -Name ComputerName -Value $env:ComputerName

Conclusion

At this point, you have created a package that extends the functionality of Chocolatey CLI.