Patch Starter Script

Jamf Pro 10.2 is not far. I recently released my Patch Server project for admins in the beta (and after the release) to host their own custom patch definitions from. The server hosts the definitions and provides an API for maintaining them afterwards with automation. A missing piece is a tool to create your initial definitions to upload.

This morning I posted a script that aims to address that gap:

https://github.com/brysontyrrell/Patch-Starter-Script

This command line utility will take an existing application on your Mac and generate a basic (we’ll call it default) patch definition. This is primarily done using the Info.plist file within the app bundle.

Creating a Patch Definition

Using GitHub Desktop.app as an example, the script can output the JSON to stdout:

$ python patchstarter.py /Applications/GitHub\ Desktop.app -p "Github"

Or it can write a JSON file to a directory of your choice (in this example the current working directory):

$ python patchstarter.py /Applications/GitHub\ Desktop.app -p "Github" -o .

The -p or –publisher argument allows you to give the name of the application’s publisher. This was included as this information is not (normally) found in Info,plist.

Below, I’ve shown the GitHub Desktop.app example’s output (the keys print out of order in Python 2, but keep in mind that key order doesn’t matter). Next to each line I’ve included where the value maps from:

{
    "id": "GitHubDesktop",                          <-- CFBundleName without spaces
    "name": "GitHub Desktop",                       <-- CFBundleName
    "appName": "GitHub Desktop.app",                <-- Application filename
    "bundleId": "com.github.GitHub",                <-- CFBundleIdentifier
    "publisher": "GitHub",                          <-- Optional command line argument (see above)
    "currentVersion": "Hasty Things Done Hastily",  <-- CFBundleShortVersionString
    "lastModified": "2018-02-12T18:33:02Z",         <-- UTC Timestamp of when the script ran
    "requirements": [
        {
            "name": "Application Bundle ID", 
            "operator": "is", 
            "value": "com.github.GitHub",  <-- CFBundleIdentifier
            "type": "recon", 
            "and": true
        }
    ], 
    "patches": [
        {
            "version": "Hasty Things Done Hastily",  <-- CFBundleShortVersionString
            "releaseDate": "2017-05-22T20:24:33Z",   <-- Application last modified timestamp
            "standalone": true, 
            "minimumOperatingSystem": "10.9",        <-- LSMinimumSystemVersion
            "reboot": false, 
            "killApps": [
                {
                    "appName": "GitHub Desktop.app",  <-- Application filename
                    "bundleId": "com.github.GitHub"  <-- CFBundleIdentifier
                }
            ], 
            "components": [
                {
                    "version": "Hasty Things Done Hastily",  <-- CFBundleShortVersionString
                    "name": "GitHub Desktop",                <-- CFBundleName
                    "criteria": [
                        {
                            "name": "Application Bundle ID", 
                            "operator": "is", 
                            "value": "com.github.GitHub",  <-- CFBundleIdentifier
                            "type": "recon", 
                            "and": true
                        }, 
                        {
                            "name": "Application Version", 
                            "operator": "is", 
                            "value": "Hasty Things Done Hastily\",  <-- CFBundleShortVersionString
                            "type": "recon" 
                        }
                    ]
                }
            ], 
            "capabilities": [
                {
                    "name": "Operating System Version", 
                    "operator": "greater than or equal", 
                    "value": "10.9",  <-- LSMinimumSystemVersion
                    "type": "recon"
                }
            ],
            "dependencies": []
        }
    ],
    "extensionAttributes": []
}

It is important to understand how the above values map in the event that you create a definition, but it has used incorrect values because the developer assigned them differently than what is considered standard (especially true for version strings).

In the event the CFBundleShortVersionString or LSMinimumSystemVersion keys are missing from Info.plist, the script will prompt you for an alternative.

Add Extension Attributes

You can also include extension attributes that need to be a part of your definition. For example, if you have the following bash script as an extension attribute for GitHub Desktop.app saved as github-ea.sh:

#!/bin/bash

outputVersion="Not Installed"

if [ -d /Applications/GitHub\ Desktop.app ]; then
    outputVersion=$(defaults read /Applications/GitHub\ Desktop.app/Contents/Info.plist CFBundleShortVersionString)
fi

echo "<result>$outputVersion</result>"

You can pass it to the -e or –extension-attribute argument:

$ python patchstarter.py /Applications/GitHub\ Desktop.app -p "Github" -e github-ea.sh

The extension attribute will be appended to the extensionAttributes key in the definition (the key here is the appName in lowercase with spaces replacing dashes):

{
    "...",
    "extensionAttributes": [
        {
            "key": "github-desktop",
            "value": "IyEvYmluL2Jhc2gKCm91dHB1dFZlcnNpb249Ik5vdCBJbnN0YWxsZWQiCgppZiBbIC1kIC9BcHBsaWNhdGlvbnMvR2l0SHViXCBEZXNrdG9wLmFwcCBdOyB0aGVuCiAgICBvdXRwdXRWZXJzaW9uPSQoZGVmYXVsdHMgcmVhZCAvQXBwbGljYXRpb25zL0dpdEh1YlwgRGVza3RvcC5hcHAvQ29udGVudHMvSW5mby5wbGlzdCBDRkJ1bmRsZVNob3J0VmVyc2lvblN0cmluZykKZmkKCmVjaG8gIjxyZXN1bHQ+JG91dHB1dFZlcnNpb248L3Jlc3VsdD4iCg==",
            "displayName": "GitHub Desktop"
        }
    ]
}

This will not update any of the capabilities or components/criteria in the generated definition! It will be up to you to make the edits to reference the extension attribute.

Create Patch Data Only

This script is only meant to create a starter definition for an application. It will only have the one version so there will be no historical data for reporting. However, if you upload it to your own running Patch Server you can maintain it by updating the version through the API.

The –patch-only argument will take an application and only generate that portion of the definition:

python patchstarter.py /Applications/GitHub\ Desktop.app -p "GitHub" --patch-only

The JSON output can be sent to the Patch Server’s API to update the stored definition. An example curl command is in the GitHub readme:

$ curl -X POST http://localhost:5000/api/v1/title/GitHubDesktop/version -d "{\"items\": [$(python patchstarter.py /Applications/GitHub\ Desktop.app -p "GitHub" --patch-only)]}" -H 'Content-Type: application/json'

Try It Out

This script should ease the process of getting started with your own custom patch definitions. If you have access to multiple versions of an application, you can use the –patch-only argument to generate the data for each and place them into your starter definition.

Note that versions must be in descending order! The latest version must always be at the top of your patches array and the older version at the bottom.

If you run into problems or have feature requests, open an issue on the GitHub repository!

Author: Bryson Tyrrell

AWS serverless developer from the Twin Cities. Former benevolent Casper Admin at Jamf, helped cofound Twin Cities Mac Admins @MspMacAdmns,, avid Python coder.

Leave a comment