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:

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 as an example, the script can output the JSON to stdout:

$ python /Applications/GitHub\ -p "Github"

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

$ python /Applications/GitHub\ -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 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",                <-- 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",  <-- 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 saved as


outputVersion="Not Installed"

if [ -d /Applications/GitHub\ ]; then
    outputVersion=$(defaults read /Applications/GitHub\ CFBundleShortVersionString)

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

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

$ python /Applications/GitHub\ -p "Github" -e

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 /Applications/GitHub\ -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 /Applications/GitHub\ -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!


Patch Server for Jamf Pro

(TL;DR, gimme the link:

After several months of not getting around to it, my PatchServer project on GitHub is finally nearing a true 1.0 state.

I am openly asking for those who have been following this project, and those who are interested in this project, to download, use, and provide feedback on what should be finished before the release of Jamf Pro 10.2.

Please create issues on GitHub for bugs and feature requests that you would want to make the cut for 1.0.

Some time late last year (and I say some time because it’s all becoming a blur), I was brought into a meeting where I was shown our (Jamf’s) progress on providing a framework for customers to be able to create their own patch definitions. This framework would allow customers to setup their own patch servers and add them to their JSS.

A day or so later, I wrote the first rough version of my own implementation.

Backing up a sec:

What’s a patch definition?

In Jamf Pro v10 we introduced a feature called Patch Management. With this, you could subscribe to a number of software titles that Jamf curates and maintains. Once subscribed, your JSS will, on a schedule, read in the patch definitions of those software titles to stay updated.

For more about Patch Management, see the Jamf Pro Admin Guide (10.1):

These patch definitions (which are JSON data) contain historical information about a software title’s version history and requirements for determining if the software is installed on a managed computer. This allows admins to use the Patch Management feature to create reports and update policies to automatically patch those software titles on computers.

Of course, when these features came out there was one resounding question from nearly everyone:

“Why can’t we make our own patch definitions?”

External Patch Sources

The framework I mentioned above is the answer to this. In Jamf Pro 10.2+ you will have the option of adding External Patch Sources to your JSS. Then, in addition to the official Jamf software titles, you will be able to subscribe to your own and use the same reporting and policy features.


The external patch source must be a server your JSS is able to reach via HTTP/HTTPS. This patch server must expose the following endpoints:

  • /software
    This returns a JSON array of all the software titles that are available on this server. For example:

        "currentVersion": "10.1.1", 
        "id": "JamfAdmin", 
        "lastModified": "2018-02-03T03:34:34Z", 
        "name": "Jamf Admin", 
        "publisher": "Jamf"
        "currentVersion": "10.1.1", 
        "id": "JamfImaging", 
        "lastModified": "2018-02-03T03:34:36Z", 
        "name": "Jamf Imaging", 
        "publisher": "Jamf"
        "currentVersion": "10.1.1", 
        "id": "JamfRemote", 
        "lastModified": "2018-02-03T03:34:40Z", 
        "name": "Jamf Remote", 
        "publisher": "Jamf"
  • /software/TitleId,TitleId
    This returns the same JSON as above, but limited to the comma separated list of software titles. For example (passing JamfAdmin,JamfRemote):

        "currentVersion": "10.1.1", 
        "id": "JamfAdmin", 
        "lastModified": "2018-02-03T03:34:34Z", 
        "name": "Jamf Admin", 
        "publisher": "Jamf"
        "currentVersion": "10.1.1", 
        "id": "JamfRemote", 
        "lastModified": "2018-02-03T03:34:40Z", 
        "name": "Jamf Remote", 
        "publisher": "Jamf"
  • /patch/TitleId
    This returns the full patch definition JSON of the software title. Here is an abbreviated example:

      "id": "JamfAdmin",
      "name": "Jamf Admin",
      "publisher": "Jamf", 
      "appName": "Jamf", 
      "bundleId": "com.jamfsoftware.JamfAdmin", 
      "currentVersion": "10.1.1", 
      "lastModified": "2018-02-03T03:34:34Z", 
      "extensionAttributes": [
      "patches": [
      "requirements": [

If you had a patch server located at, the full URLs would be:

At this time, there is no product that Jamf is providing for customers to install and have a ready to use patch server. The focus has been on opening up the framework that the official patch source uses and allow customers to extend their environments through a little engineering work.

Not all of us are engineers, of course. Thus…

Enter: Patch Server


I wanted to have a working patch server ready for the Jamf community in time for 10.2’s release. My initial patch server implementation (I call it an implementation because it’s one way of providing a patch source) achieved serving the proper JSON data for each of the endpoints described above using a database (SQLite) for the backend.

While my original goals were much grander, including the ability to fully manage a patch definition in a GUI instead of writing out JSON, I had to pare it back in order to get the project into a deliverable state.

In the past week I went through the code and ripped out everything that I felt was not needed, or doable. Then, I went through and added in new features (ported from another project) and streamlined the UI elements that were left.

This patch server features:

  • All required Jamf Pro endpoints to serve as an External Patch Source
  • An API for programmatic management of patch definitions and versions.
    • Create/delete patch definitions.
    • Add versions to existing patch definitions.
    • Create backup archives of all patch definitions.
  • UI for management of patch definitions.
  • Validation of uploaded patch definitions.
  • Full user documentation at

    • UI Overview
    • Setup Instructions
    • API Documentation

Bring the Requests

Until Jamf Pro 10.2 is released, I’m not going to tag the project at a 1.0 version. If you are in Jamf’s beta program and testing 10.2, I invite you to give this a try and let me know what you think. Specifically, I’m asking for you do open up issues on GitHub for:

  • Bugs you find
  • Features you want, such as:
    • Connect to an actual database like MySQL (?)
  • Documentation you want, such as:
    • Instructions for installing on X

Not everything that is reported might get worked on, but the good news is I released the patch server under the MIT license. If you have some Python chops you can fork it and do whatever you want with the codebase to suit your needs!

But, I don’t wanna setup a server…

If you had that reaction to the idea of setting up your own external patch source, ask yourself if you match any of these descriptions:

  1. My JSS can talk to pretty much anything if I want it to,
  2. I want a patch server; I don’t want to host a patch server,
  3. It doesn’t matter where my patches live as long as I can get and manage them,
  4. Can’t this be a cloud thing?

If so… stayed tuned for a future blog post.

Farewell to the Unofficial JSS API Docs

Hey everyone.

With the launch of the Jamf Developer Portal I think it’s time I took down my Unofficial JSS API Docs site on Confluence.

I launched it as a community resource for a lack of API documentation, but now that Jamf has something out there I feel it’s time I save my $10 a month. If you found these resources helpful in the past, great! That was the whole point.

The site will come down after November 17th. For those Google searching and coming across this post, click on the dev portal link I provided above to reach the official documentation provided by Jamf.

Open Distribution Server Technology (w/JNUC Recap)


At JNUC 2017, I was given the opportunity to do a session detailing the progress I’ve made and the vision I have for a new file distribution server that can serve to replace the now discontinued JDS (Jamf Distribution Server).

This was a last minute addition to the conference schedule and we were unable to record it, but the Mac admin community took notes which can be found here. I’ve also uploaded the presentation’s slide deck on SlideShare.

The source code for ODST is available on GitHub. It is currently in an early Alpha state with some of the core functionality complete.

Project Goals

ODST came about with the sunsetting of the JDS. I set out to design my own implementation of an automated file distribution server but with additional features to make it a more powerful component of an administrator’s environment.

The goal of ODST is to provide an on-premise file syncing and distribution server solution that puts automation and integration features first.

The ODS (Open Distribution Server) application itself is modular and being designed to fit into as many deployment models as possible. This ranges from a simple single-server installation on Linux, Windows, or macOS to containerized deployments in Docker or Kubernetes.

While there will be initial support for the ODS to integrate with Jamf Pro it is not a requirement for using the application. This will allow administrators using other management tools to take advantage of the solution and submit feature requests for integrations with them as well.

Planned Features

  • A full web interface (built on top of the Admin API)
  • The Admin API for integrating your ODS instances with existing automations and workflows.
  • Many-to-many registration and syncing which will allow package uploads to any ODS and still replicate throughout your network.
  • Package and ODS staging tags to restrict how certain levels of packages replicate through the network.
  • Webhooks and email to send notifications to other services alerting them to events that are occurring on your ODS instances.
  • LDAP integration for better control and accountability when granting other administrators and techs access to your ODS instances.
  • And more to come…

Package Syncing

Where the JDS synced by running an every five minute loops task to poll another server, the ODS application uses a private ODS API for communicating between instances.

When two ODS instances are registered to each other they will have each others’ keys saved to their databases and use those keys to sign API requests.

The standard order of operations during a package upload would be:

  1. The admin uploads a package to ODS1.
  2. ODS1 generates the SHA1 hash of the package and also generates SHA1 hashes for every 1 megabyte chunk of that package. This information is saved to the database.
  3. ODS1 sends a notification to every registered ODS instance that a new package is available.
  4. ODS2 receives this notification and makes a return API request for the full details of the package.
  5. ODS2 saves the pending package to the database and a download task is sent to the queue.
  6. The ODS2 worker takes the download task off the queue and begins downloading the package in 1 megabyte chunks, comparing hashes for every chunk, and saving them to a temporary location.
  7. Once the ODS2 worker has downloaded all chunks it recombines them to the single file, performs a final SHA1 check, and moves the package to the public download directory.
  8. ODS2 then performs step #3 to propagate the package to other ODS instances it is registered with.

If the download process seems familiar, it is borrowed from how Apple performs MDM initiated application installs.

Application Architecture

The ODS application is more complex than the JDS in order to facilitate the additional features that are being built on top of the file syncing. In addition to the application server, a production deployment would also include a front-end web server (Nginx or Apache), a Redis server for the queuing system, a database server (ODST falls back to a local SQLite database file if there is not a database service to connect to), and workers that process queued actions.

Single Server


Multi-Server or Containerized


The queuing system is an important element as it backgrounds many of the processes that the server will need to perform in reaction to notifications or requests (such as queuing notifications, API requests to other ODS instances, file downloads, and file hashing operations). This frees up the application to continue accepting requests by removes long process blocks.

How the Community Can Help

When I gave the JNUC presentation I only took up half of the allotted time to discuss what was completed with the project and what was planned. The second half was spent in open discussion to take in feedback and guidance from the target audience on what was needed on the road to a 1.0 release.

Adding LDAP support was the first item to come out of this and is my next planned feature to write in after the file syncing framework is finished. I encouraged participants to open GitHub issues on the repo as we discussed their questions and asks. I want to continue to encourage this. The ODST project is meant for the community and should continue to be community driven in its roadmap.

When it comes to contributing to the project I am not asking for code help at this time. Don’t feel that you need to know Python or web development with Flask in order to contribute. There are many other areas that I am in need of help:

  • Testing! As I make new commits to the repository and add in more features you can help ensure everything is working by running the latest version and trying them out. Submit issues, provide logs, provide details on how you’re deploying the application (the provided Docker Compose file is the quickest and easiest way), and by doing so you will help verify features work as expected and solidify the quality of the application.
  • Determine optimal configurations. There are quite a few components to the ODS application and I am learning as I go for how to configure the web server. More experienced administrators who are familiar with these technologies, especially in production environments, can help work towards a baseline for…
  • Installers! The ODS application can be custom setup for almost any kind of deployment, but we still want an easy option where an admin can grab an installer for load it onto a single Linux or Windows server. If you have experience building installers on those platforms please reach out! I’ve also mentioned containerization a few times, and having an official Docker images for the ODS application and worker components should be a part of this initiative.
  • Documentation. Much Documentation. There will be official docs available at which will be generated from the main repository on GitHub. You can help maintain and improve that documentation with pull requests as you find errors or inaccurate instructions/details as the project iterates. The documentation will be especially invaluable when it comes to the aforementioned installers, custom installations, and the administrator user guide portion that will walk user through how to perform actions.

If you haven’t yet, please join the #odst channel in the Mac Admins Slack where you can discuss the project with me directly as well as other admins who are using, testing, and contributing as they can.

I hope to build something that will provide great value to our community and fill the gap the JDS left in a lot of environments. I hope to see you on GitHub and Slack soon!

Build Your Own Jamf Pro Integrations: Part I An Intro to Webhooks and Flask

Welcome back to the BYO Jamf Pro Integrations tutorial! In Part I we will be giving an introduction to both the webhooks feature of Jamf Pro and the Flask microframework that you installed into your virtual environment in the introduction post.

Webhooks in Jamf Pro

Webhooks are a framework introduced in Jamf Pro v9.93. A webhook itself is an HTTP callback: an HTTP POST that occurs when something happens.

A webhook itself is an HTTP request made by a server to a destination, with a payload, in response to an event. Jamf Pro’s webhooks are built directly on top of a pre-existing Java API called the Events API.

The Events API allowed for Java plugins as .jar files to be installed on the Jamf Pro server. While not an option for Jamf Cloud customers, self-hosted users can take advantage of this. Learn more here:

While not 100%, the available events for Jamf Pro Webhooks closely matches the list for the Events API.

  • ComputerAdded
  • ComputerCheckIn
  • ComputerInventoryCompleted
  • ComputerPolicyFinished
  • ComputerPushCapabilityChanged
  • JSSShutdown
  • JSSStartup
  • MobileDeviceCheckIn
  • MobileDeviceCommandCompleted
  • MobileDeviceEnrolled
  • MobileDevicePushSent
  • MobileDeviceUnEnrolled
  • PatchSoftwareTitleUpdated
  • PushSent
  • RestAPIOperation
  • SCEPChallenge
  • SmartGroupComputerMembershipChange
  • SmartGroupMobileDeviceMembershipChange

To setup a webhook for one of these events, log into your Jamf Pro Server and navigate to Settings -> Global Management -> Webhooks. Click the + New button and you will be taken to a screen to set and select the following:

  • Name
    A description.
  • URL
    The address that you want Jamf Pro to send the event data to.
  • Content Type
    Choose whether that data in in XML or JSON format.
  • Event
    The event from the list above that you want to send on.

When you setup your webhook, Jamf Pro will send an HTTP POST with a payload of the content type you selected containing contextual data on the event. These payloads are broken into two parts: the webhook and eventObject/event keys.

Here is an example in XML:


Here is that same example as JSON:

    "webhook": {
        "id": 1,
        "name": "",
        "webhookEvent": "JSSShutdown"
    "event": {
        "institution": "",
        "hostAddress": "",
        "webApplicationPath": "",
        "isClusterMaster": false,
        "jssUrl": ""

The webhook key is about the Jamf Pro Webhook itself. The database ID, name you set and the type of event are contained here. This is data that, later in the tutorials, can be used to identify which events you are receiving from Jamf Pro.

The eventObject (XML) or event (JSON) key contains the contextual data of what triggered the event, or contextual data about the event depending upon which event was triggered. Many of the events all send the exact same data under this key with the difference will be what is contained under the webhook key. This is, for examples, true for Computer* and MobileDevice* events.

You can dive into full examples of every webhook event in XML and JSON formats at the Unofficial JSS API Docs site:


There needs to be something on the receiving end, the destination URL of the Jamf Pro webhook, to receive the payload and process it / take action on. This is where web development enters the picture. The type of integration that works with webhooks is a web app with endpoints that can accept POST requests.

There are many web technologies out there for all kinds of programming languages. The tutorial series will focus on Python (very popular in the Mac admin community for scripting alongside Ruby) and a microframework called Flask.

The “micro” portion means that Flask does not contains many elements of larger frameworks like a pre-defined database interface. Instead, Flask relies on extensions that plug into the framework and extend the functionality of your code. Flask also does not dictate design choices. Flask apps can be hundreds of files in size, structured in nearly any way, or just one single file. The size and complexity of the project is determined by the scope of your work.

Here is the absolute smallest Flask app that you could write (and you can use this as the boilerplate code to start any of your projects from):

import flask

app = flask.Flask(__name__)

def root():
    return "Hello Penn State MacAdmins!"

if __name__ == '__main__':

Seven lines.

You should have Flask installed and available for your project in the virtual environment created in the first post. At the top of our file we are importing the package.

Then we create an app object that is an instance of the flask.Flask() class:

app = flask.Flask(__name__)

This object will represent the web app throughout our code. To add endpoints, or routes, to the web app we will use the route() decorator.

Decorators are a special kind of Python syntax that “wrap” a function around another function.

In this case, the route() decorator will register an endpoint based upon the path we give as it’s first argument and then execute the wrapped function below it whenever that endpoint is requested! You’ll be able to see clearly this in a moment.

def root():
    return "Hello Penn State MacAdmins!"

The“/” path means the root of the web server. Once this app is running you will be able to reach it in your web browser by navigating to http://localhost:5000. The “/” path resolves to that address.

Flask has a built in development server you can start by calling the run() method on the app object. When you call a Python file as a script from the command line the __name__ dunder becomes set to a value of __main__.

If you are a little confused by the word “dunder” at this point don’t worry, you can continue on without understanding some of these concepts, but you may want to brush up on your Python with some online resources.

By checking if the __name__ dunder is __main__ you can control what your Python scripts do based on whether they have been called from the command line or, later on, imported into other Python code. When imported, the __name__ dunder takes on the name of the file!

So, the last two lines of this single file Flask app mean it will only run the app using the development server if it has been called as a script from the command line:

if __name__ == '__main__':
(byojamf) ~$ python /path/to/
 * Running on (Press CTRL+C to quit)

Your web app is now available when you navigate to http://localhost:5000 in your web browser. Give it a try and you should see the text message in the code! See how when you request the “/” or root of the web app the root() function is executing?

Decorators in action.

Next up…

Accepting Webhooks and Testing with Jook

I’ll see you in Part II tomorrow!




Open Distribution Server and JNUC 2017

Two posts in one day! I wanted to do a quick JNUC update and promote a session that I’m really excited for.

This year, as with years past, I will be pretty involved with the conference. Aside from finding me roaming the halls of the Hyatt; I am on the committee for the first ever JNUC Hachathon, participating in the API Scripting and Webhooks labs, and delivering the Webhooks Part Deux! presentation with Oliver Lindsey from our Pro Services team.

But the session I am most excited about is a very late addition that was put onto the JNUC App’s schedule this morning.

The Open Distribution Server

Around July (Penn State), I began work on an alternative distribution server to the JDS. As the community recently learned, the JDS has been discontinued and will no longer be supported by Jamf as cloud-centric options are being focused on. Prior to that announcement, I was involved in some talks with Product Management at Jamf about the JDS, and I took the opportunity to show them what I was working on.

Joe Bloom, our Jamf Pro Product Manager who you will hear talk at several product sessions this year, was very excited about this and urged me to continue working on my distribution server and release it as a free, open source solution.

Joe has secured an additional session slot on Tuesday at 4:00 PM dedicated to the Open Distribution Server. You can find it at the link or in the JNUC App (it is not listed on the website).

During this session I’m going to talk about the goals of this project, what it aims to solve, what features I have implemented and plan to implement, but then turn the rest of the time over to you so we can talk about the key things that will make this a successful solution:

  • What features don’t work as described or need changed to fit your workflows?
  • What features are missing that you need?
  • How can the community contribute to this project?

The current code base for this project was posted to GitHub a couple weeks ago:

The Open Distribution Server (ODS) is an open-source package distribution and syncing solution for IT administrators to serve as a potential alternative for the Jamf Distribution Server.

For those looking for an on-premise, automated distribution point solution, and those who are in need of a replacement for their JDS infrastructure, please attend and be a part of the discussion.

I hope to see you there!

Build Your Own Jamf Pro Integrations: A Tutorial Series

Enter a caption

During Penn State MacAdmins 2017, I delivered my first ever workshop style session entitled “Build Your Own Jamf Pro Integrations.” Going in I felt well prepared and convinced that people would walk away feeling empowered to build new kinds of functionality atop their Jamf Pro environments.

The result was not what I envisioned.

Information about the level of experience with Python that was required coming into the workshop was lost in the posting. My slide pacing was too fast. People weren’t given enough time to write down the examples on screen before I would move on to lab sections which did not contain any code examples. Due to a lot of the group not being at the needed experience level with Python I burned through extra time moving around to help them troubleshoot issues and reach the next step. I ended up not finishing all of the content I had prepared and the workshop was left with an unfinished air about it.

Overall, I wasn’t too happy with how the workshop turned out. Some of the attendees afterwards gave me some feedback about what could have been done to improve for a future version (which I was also encouraged to submit for 2018). Make sure the prerequisite experience for the workshop is clearly communicated. The full code being available prior to the workshop would have made transitioning between sections easier. Volunteer assistants to help others with issues and errors as they arise to allow me to focus on delivery. Do a full day and not a half: I had more than enough content to fill it.

Later, when the form submitted feedback was compiled and provided, I found that on a whole the sentiments above were shared by many. They enjoyed the content, they liked the hands-on approach, but structure and timing prevented them from getting the most out of it. The reception was better than I had expected (coming from an IT background, I know it’s more likely that people will submit complaints than compliments).

While I still intend to submit this workshop again for MacAdmins 2018, I have decided to adapt the slide deck into a multi-part tutorial series that will cover building your first Flask (Python) based integration with Jamf Pro and Slack.

Once this post has gone live, each part of the tutorial will go up once a day until the series has been completed. The full code for the complete sample project will be available on my GitHub on day of the last posting.


You can use either Python 2 or Python 3, but you will need to create a virtual environment and install the required modules from the Python Package Index (PyPI).

If you are going to use the Python installation that came with your Mac (Python 2) you can install pip and virtualenv using the following commands:

~$ sudo easy_install pip
~$ sudo pip install virtualenv

If you have installed another copy of Python (either 2 or 3) on your Mac, or you are installing Python on Windows or Linux, these commands should be available as a part of that.

Create the virtual environment for the project somewhere in your user directory (the root or your Documents folder would both work):

~$ virtualenv byojamf

To use your new virtual environment call it’s activate script and you will see the environment’s name appear in parenthesis in the terminal session:

~$ source /path/to/byojamf/bin/activate
(byojamf) ~$

Now install the three PyPI modules that we will be using for this sample project:

(byojamf) ~$ pip install flask jook requests

Flask is a microframework for writing web applications.


Requests is an HTTP client library that we will be using to make API requests.


Jook is a Jamf Pro Webhooks simulator to allow you to test your integration.


Next up…

An Intro to Webhooks and Flask

I’ll see you in Part I tomorrow!