The binary package manager Chocolatey is a fork of the NuGet specification. This includes a nuspec definition file and support tools or libraries. Chocolatey's primary focus is to install binary/compiled installers on the intended machine(s). With Chocolatey and Windows PowerShell, you can install up to 5,869 (at the time of writing this post) community-maintained packages.

These packages range from Google Chrome to Microsoft Office to PuTTY. If you are responsible for installing dependencies or software on machines, you should definitely check out the available Chocolatey Packages.

Sometimes Chocolatey may not have the packages you need, or you may have specialized software that needs configuring before deploying it to your machines. In such cases, you can create local (internal) Chocolatey packages and install them using PowerShell.

In this post, I am creating a sample customized Microsoft Office 2013 administrative installation package. The first step was to mount the Microsoft Office 2013 ISO and copy the items to a folder on my local machine. Next, I downloaded the Office Customization Tool for Office 2013 and generated an MSP file. After I generated the MSP, I placed it in the Updates folder, zipped the entire folder, and uploaded it to my internal package repository.

Before diving too deep into the world of NuGet packages, let's go over some keywords you may be unfamiliar with. Chocolatey is a fork of the NuGet gallery, and that comes with some additional terms we should understand:

  1. Nuspec: This NuGet package specification describes a package. It's an XML manifest file that helps build a package and provides information to services using the package. You can find more information about nuspec here.
  2. NuPkg: You use this generated package to install your package. Your nuspec manifest file generates this.
  3. Choco: This is Chocolatey.

To generate a new local Chocolatey package, you'll need to have a file structure similar to the following example:

|__Packages
	    |__MyLocalChocolateyPackage
	        |__MyLocalChocolateyPackage.nuspec
	        |__tools
	            |__chocolateyInstall.ps1
	    |__AnotherPackage
	        |__AnotherPackage.nuspec
	        |__tools
	            |__chocolateyInstall.ps1

Let's dive into a practical example. Here's a layout example for two customized Microsoft Office 2013 install packages.

Folder layout for building local Chocolatey packages

Folder layout for building local Chocolatey packages

You can modify the structure, but the best practice is to use this folder structure for all internal packages you plan to build or create. Let's first take a look at the en_office_professional_plus_2013_x64.nuspec file:

<?xml version="1.0"?>
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
  <metadata>
    <id>OfficeProfessionalPlus2013x64</id>
    <version>2013.64.20180717</version>
    <title>OfficeProfessionalPlus2013x64</title>
    <authors>Microsoft</authors>
    <owners>Josh Rickard</owners>
    <licenseUrl>https://technet.microsoft.com/en-us/library/gg982959.aspx</licenseUrl>
    <projectUrl>http://office.microsoft.com/</projectUrl>
    <requireLicenseAcceptance>false</requireLicenseAcceptance>
    <description>This will download and silently install Microsoft Office 2013 x64 Professional Plus edition.</description>
    <summary>OfficeProfessionalPlus2013x64</summary>
    <releaseNotes />
    <copyright />
	<iconUrl>http://icons.iconarchive.com/icons/martz90/circle/256/office-2013-icon.png</iconUrl>
  </metadata>
  <files>
    <file src="tools\**" target="tools" />
  </files>
</package>

Yep, nuspec is just an XML definition file. This file specifically is the reason I previously brought up the fact you'll need to follow a specific format. I'll break down this file, but please understand there are many other options available for nuspec files. However, I am only using a very limited number of properties.

The first section we encounter is the metadata section. You define specific metadata about your new package here. Most of these are self-explanatory, but I want to talk about a few a bit further.

I like to have both my ID and title properties the same so I don't have differences when I build my packages or when they install. In this example, my ID and title values match and are OfficeProfessionalPlus2013x64.

<id>OfficeProfessionalPlus2013x64</id>
<title>OfficeProfessionalPlus2013x64</title>

A version value is located between these two property values. The version value has strict requirements for formatting it. I have adjusted my versioning scheme to work for me, but it's still within the requirements. My format for these packages is:

<version>2013.64.20180717</version>
<version>{product_year}.{bitness}.{date_packaged}</version>

The remaining properties and values are straightforward, but to help understand them, I have created the following chart:

List of additional metadata properties and a brief description

List of additional metadata properties and a brief description

The final section is the definition of our folder locations. These folder locations define the locations for additional resources to build our package definition. You can change these, but if you do, make sure you have a default structure for your internal Chocolatey packages. As you begin to create packages, believe me, you will not regret it.

<files>
    <file src="tools\**" target="tools" />
  </files>

Now that we understand the basics of our .nuspec file, let's take a look at the workhorse behind the scenes. This is the chocolateyInstall.ps1 file located under our tools subfolder.

$ErrorActionPreference = 'Stop'


$packageName   = 'OfficeProfessionalPlus2013x64'
$toolsDir      = "$(Split-Path -parent $MyInvocation.MyCommand.Definition)"
$ZipName       = 'en_office_professional_plus_2013_with_sp1_x64.zip'
$url           = "https://s3.amazonaws.com/some-package-location/$ZipName"



$packageArgs = @{
    PackageName   = $packageName
    FileFullPath  = "$toolsDir\$ZipName"
    url           = $url
    checksum      = 'D46F54ECF5A64FB1649A2B20A0F4DD4882E54E7259BFF98BAE2DE0397A85379B'
    checksumType  = 'sha256'
}


if (-not(Get-Module -Name chocolateyInstaller))
{
    try{
        Import-Module C:\ProgramData\chocolatey\helpers\chocolateyInstaller.psm1 -Force
    }
    catch{
        Write-Warning -Message 'Unable to import chocolateyInstaller'
        Write-Error -ErrorRecord $Error[0]
        exit -1
    }
}


try{
    Get-ChocolateyWebFile @packageArgs
}
catch{
    Write-Warning -Message "Unable to download $PackageName"
    Write-Error -ErrorRecord $Error[0]
    exit -1
}

try{
    $ZipPath = Get-Item "$toolsDir\$ZipName"

    Expand-Archive -Path $ZipPath -DestinationPath "$toolsDir\$packageName"

    $props = @{
        Wait         = $true
        FilePath     = "$toolsDir\$packageName\setup.exe"
        PassThru     = $true
    }

    Start-Process @props
}
catch{
    Write-Warning -Message "Unable to install $PackageName"
    Write-Error -ErrorRecord $Error[0]
    exit -1
}

This file is known as a Helpers Reference. Chocolatey will determine whether this file is present in your tools directory and will use it as the package workflow or package install provider source. This script can use all available PowerShell cmdlets on the machine in your install logic. This script gets packaged into your generated NuGet package (nupkg) when built.

Chocolatey will take this nuspec manifest, and we can use it to generate a nupkg. A NuGet package is essentially a zipped container renamed with the .nupkg extension. The nuspec manifest file is the important part when creating nupkgs.

Our chocolateyInstall.ps1 file contains some basic PowerShell logic. We first set some variables at the top of our script. These are the packageName, toolsDir, ZipName, and url (location of our package). In this example, I am hosting a custom Microsoft Office installer as a .zip folder on Amazon S3, but you can host it in a Git repository, network fileshare, or web server. Whichever you choose, you need to provide the location of your packages.

Next, we set our packageArgs by specifying our package name, directory, and checksum values. In this example, I am using SHA256 as my checksum, but you can use other checksums if wanted.

We also make sure we have the chocolateyInstaller PowerShell module installed. If not, we install it and import the module into the current session.

if (-not(Get-Module -Name chocolateyInstaller))
{
    try{
        Import-Module C:\ProgramData\chocolatey\helpers\chocolateyInstaller.psm1 -Force
    }
    catch{
        Write-Warning -Message 'Unable to import chocolateyInstaller'
        Write-Error -ErrorRecord $Error[0]
        exit -1
    }
}

Once we've installed the chocolateyInstaller module, we begin to download our .zip file. I choose to use the Get-ChocolateyWebFile function. This function will do the hard work of downloading our .zip and placing it in our tools directory.

try{
    Get-ChocolateyWebFile @packageArgs
}
catch{
    Write-Warning -Message "Unable to download $PackageName"
    Write-Error -ErrorRecord $Error[0]
    exit -1
}

Get-ChocolateyWebFile will download our custom package and verify checksums based on our packageArgs hash table we invoked when we called the function.

Finally, our chocolateyInstall.ps1 file expands the .zip and runs the setup.exe of our custom Office package.

Understanding the nuspec specification is required when generating your own internal Chocolatey packages. Once we understand the basics of nuspec, we can fully exploit the power that is Chocolatey, the Windows package manager.

Subscribe to 4sysops newsletter!

In my next post I will explain how to build and install local Chocolatey packages with PowerShell.

0 Comments

Leave a reply

Please enclose code in pre tags

Your email address will not be published. Required fields are marked *

*

© 4sysops 2006 - 2021

CONTACT US

Please ask IT administration questions in the forums. Any other messages are welcome.

Sending

Log in with your credentials

or    

Forgot your details?

Create Account