[WIP]Create an chocolatey package using templates

Table of contents

  1. Package content
  2. Package creation
    2.1 Templates
  3. Updating a package
    3.1 Semantic versioning

Package content

The basic structure of a chocolatey package is:

  • packageName.nuspec: metadata of the package
  • tools: folder containing content and scripts
    • chocolateyinstall.ps1: executed when a new package or version is installed (choco install packageName)
    • [chocolateyuninstall.ps1]: executed when the package is uninstalled (choco uninstall packageName)
    • [chocolateyBeforeModify.ps1]: executed when instaling a new version before the new chocolateyinstall.ps1 (from the older version).
    • [other files]: installers (exe, msi, etc), zips, etc

NOTE: Only the chocolateyinstall.ps1 script is strictly required.

Package creation

In order to create multiple packages, we created a script that is able to create multiple (or one) packages.
We decided to organize the packages into 3 main groups: system, apps and pipeline.
The list of packages that belong to each group and their parameters are detailed in system.yml, pipeline.yml and apps.yml.

The scripts:

  1. Reads the YAML files, assumed to be defined at the same level.
  2. Creates a list of the packages that are going to be created
  3. Creates the package directory using the templates (also defined at the same level, but with a symbolic link to C:\ProgramData\chocolatey\templates)
  4. Copies the missing files from the package’s correspoding content directory (PATH_TO_SCRIPT \ PACKAGE_GROUP \ PACKAGE_NAME).
    For example, for the maya2014 package, its corresponding content directory is: .\contents\apps\maya2014
  5. Creates the package from its directory (basically zip)
  6. Finally, copies the package to the package repository

The script can be called in 3 different ways:

  1. Without any parameters, meaning it will create ALL packages defined in all 3 YAML files:
     $ python packageCreator.py
  1. It can also create ONLY the packages from a group by calling the script with the group’s name as a parameter.
     $ python packageCreator.py system 
      OR
     $ python packageCreator.py apps.yml 
  1. The same is possible if a list of package names are passed as parameters.
     $ python packageCreator.py maya2014 maya2016
     OR
     $ python packageCreator.py vztrayservice

Templates

The following are the chocolateyinstall.ps1 files of the different templates currently used in our Repository.
All the [[someName]] are variables that will be replaced by those detailed in the yaml files.

Unique installer

- name: mediareader
  version: 1.0
  template: installer\unique
  InstallerName: MediaReader
  FileType: msi
  SilentArgs: /QB- ALLUSERS=1
  ValidExitCodes: "@(0,3010)"

In the unique installer the main function is Install-ChocolateyInstallPackage which uses the parameters in packageArgs to execute the installer.

$groupName = '[[GroupName]]'
$packageName = '[[PackageName]]'
$fileType = '[[FileType]]'
$silentArgs = '[[SilentArgs]]'
$validExitCodes = [[ValidExitCodes]]

$toolsDir = "$(Split-Path -parent $MyInvocation.MyCommand.Definition)"
$installerName = '[[InstallerName]]'
$fileLocation = Join-Path $toolsDir "$installerName"

Write-Host "About to install $groupName $packageName"
  
$packageArgs = @{
  packageName   = "$packageName"
  fileType      = "$fileType"
  file          = $fileLocation
  silentArgs    = $silentArgs
  validExitCodes = $validExitCodes
}

Install-ChocolateyInstallPackage @packageArgs

Dual installer

- name: vcredist2015  
  version: 14.0.24215.1  
  template: installer\dual  
  InstallerName: vcredist_x86
  InstallerName64: vcredist_x64
  FileType: exe
  SilentArgs: /install /passive /norestart
  ValidExitCodes: "@(0,3010)"

When there is a version for a 32-bit system and 64-bit system and both are required for the 64-bit system (all vcredists work this way). We install the 32/64 according to the machine’s system and IF the system is 64 bits, we force the execution of the 32 bit installer.

$groupName = '[[GroupName]]'
$packageName = '[[PackageName]]'
$fileType = '[[FileType]]'
$silentArgs = '[[SilentArgs]]'
$validExitCodes = [[ValidExitCodes]]

$toolsDir = "$(Split-Path -parent $MyInvocation.MyCommand.Definition)"

$installerName = '[[InstallerName]]'
$installerName64 = '[[InstallerName64]]'
$fileLocation = Join-Path $toolsDir "$installerName"
$fileLocation64 = Join-Path $toolsDir "$installerName64"

Write-Host "About to install $groupName $packageName"

$packageArgs = @{
  packageName   = "$packageName"
  fileType      = "$fileType"
  file          = $fileLocation
  file64		= $fileLocation64
  silentArgs    = $silentArgs
  validExitCodes = $validExitCodes
}

Install-ChocolateyInstallPackage @packageArgs

if (Get-ProcessorBits 64) {  #Install x86 as well for 64 bits machines
	$packageArgs = @{
	  packageName   = "$packageName"
	  fileType      = "$fileType"
	  file          = $fileLocation
	  silentArgs    = $silentArgs
	  validExitCodes = $validExitCodes
	}
	Install-ChocolateyInstallPackage @packageArgs
}

Multiple installer

- name: quicktime
  version: 1.0
  template: installer\multiple
  InstallerNames: "@('AppleApplicationSupport', 'AppleSoftwareUpdate', 'QuickTime')"
  FileTypes: "@('msi', 'msi', 'msi')"
  SilentArgs: "@('/passive', '/passive', '/passive')"
  ValidExitCodes: "@((0,3010), (0,3010), (0,3010))"

When a packages requires the execution of multiple installers, then we can use a template that executes a list of installers; each with its own name, file type and exit codes.

$groupName = '[[GroupName]]'
$packageName = '[[PackageName]]'
$validExitCodes = [[ValidExitCodes]]

$toolsDir = "$(Split-Path -parent $MyInvocation.MyCommand.Definition)"

$installerNames = [[InstallerNames]]
$fileTypes = [[FileTypes]]
$silentArgs = [[SilentArgs]]


For ($i=0; $i -lt $installerNames.Length; $i++) {
	$installer = $installerNames[$i]
    $fileLocation = Join-Path $toolsDir $installer

	Write-Host "About to install $groupName $packageName's $installer"
	  
	$packageArgs = @{
	  packageName   = "$packageName"
	  fileType      = $fileTypes[$i]
	  file          = $fileLocation
	  silentArgs    = $silentArgs[$i]
	  validExitCodes= $validExitCodes[$i]
	}

	Install-ChocolateyInstallPackage @packageArgs
}

Zip General

- name: ftrack
  version: 1.0.0
  template: zip\general
  ComponentExtractDir: "C:\\Program Files\\VetorLobo\\ftrack"

In the general zip template the main function is Get-ChocolateyUnzip which unzips the files in $file and extracts them into $extractDir. Another important feature is the use of a helpers module, which includes tqo empty functions named: Package-Pre-Setup and Package-Post-Setup

$toolsDir = "$(Split-Path -parent $MyInvocation.MyCommand.Definition)"
$packageName = '[[PackageName]]'
$extractDir = "[[ComponentExtractDir]]"
$zipName = "[[PackageName]]"
$groupName = '[[GroupName]]'
$file = Join-Path $toolsDir "$zipName.zip"

Write-Host "About to load helpers $toolsDir"
. $toolsDir\helpers.ps1

Write-Host "About to install the $groupName $packageName inside $extractDir"
Package-Pre-Setup $packageName $toolsDir
Get-ChocolateyUnzip -PackageName $packageName -FileFullPath $file -Destination $extractDir
Package-Post-Setup $packageName $toolsDir

Helpers.ps1:

function Package-Pre-Setup($packageName, $toolsDir)
{

}

function Package-Post-Setup($packageName, $toolsDir)
{

}

Zip Vetorlobo

- name: vztrayservice
  version: 1.0.0
  template: zip\vetorlobo
- name: maya2016modules
  version: 1.0.0
  template: zip\vetorlobo
  SubDirectory: Maya2016

For the vetorlobo template the extractDir defaults to `“C:\Program Files\VetorLobo\VetorLobo”.

$toolsDir = "$(Split-Path -parent $MyInvocation.MyCommand.Definition)"
$packageName = '[[PackageName]]'

$extractDir = 'C:\Program Files\VetorLobo\VetorLobo'
$subDir = '[[SubDirectory]]'
if($subDir -notlike "*SubDirect*"){
	$extractDir += "\$subDir"
}

$zipName = "[[PackageName]]"
$groupName = '[[GroupName]]'
$file = Join-Path $toolsDir "$zipName.zip"

Write-Host "About to load helpers $toolsDir"
. $toolsDir\helpers.ps1

Write-Host "About to install the $groupName $packageName inside $extractDir"
Package-Pre-Setup $packageName $toolsDir
Get-ChocolateyUnzip -PackageName $packageName -FileFullPath $file -Destination $extractDir
Package-Post-Setup $packageName $toolsDir

General

- name: maya2017-standalone
  version: 1.0.0
  template: general

When the logic of chocolateyinstall.ps1 differs from all the previous templates, then the general one can be used. It only defines the package structure. When the content files are copied, a chocolateyinstall.ps1 must be included.

Updating a package

To update a package you need to:

  1. Update the contents of the package (PATH_TO_SCRIPT \ PACKAGE_GROUP \ PACKAGE_NAME).
    1.1.If the type of package is a ZIP, create a new zip with the new modified content.
    1.2 If the type of package is an install, replace the installer with the new one.
    1.3 If the helper.ps1 script needs to be updated, replace it with a new one.
  2. Update the version of the package at the package’s group YAML. Use the semantic versioning section to decide how to update the version of the package.
  3. Update the titel and description of the package’s release at the package’s group YAML

Semantic versioning

We are going to use a semantic versioning approach. This means that each package will be described by a ternary set of numbers: X.Y.Z

  • X: MAJOR
  • Y: MINOR
  • Z: PATCH

We modify each number if

  • PATCH: we make small internal changes that fix bugs, but do not change the public interface of the modules. (backwards compatible)
  • MINOR: new features or substancial improvements are made available to the public interface or if a function is going to be deprecated (deprecation before complete removal!). Resets PATCH number (backwards compatible)
  • MAJOR: if the changes are not backwards compatible (may include MINOR/PATCH changes). Resets MINOR and PATCH numbers.