After switching from munki to Jamf Pro some years ago, I really disliked the process to update and maintain software installations on the clients. The method of creating multiple Smart Groups and Install Policies felt pretty cumbersome (with clever AutoPkg workflows it’s at least tolerable, but still fills up your Jamf instance pretty fast).

Jamf Pro Patch Management

When Jamf introduced a built-in Patch Management I quickly started to take a look and play around with it. The workflow of creating patches was still clunky and needed many clicks (overall a pretty nested GUI experience), but I still preferred it for multiple reasons:

  • Built-In workflows (Self Service notification or automatic installation with notification) which create a better user experience. No ‘shadow installations’ executing in the background, which could interfere with the user’s current workflow. Also no need to point users to the Self Service, and “encourage” them to install the newest version by hand ;)
  • A better overview of the installed versions on clients which could also be placed on the Jamf dashboard: Jamf lists all installed versions in an easy to read table which can also be filtered, which makes spotting ‘problems’ easier etc.
  • Patches are built on top of patch (title) definitions, which list every software version and some more informations about the update like which applications need to quit, or if the update is an incremental one.
  • Since patches have their own place in the Web-GUI, Smart Groups and Policies stay more clean and slim. I really value this point, since every Jamf admin has already more than enough policies. With less items, it’s simply easier to keep an overview.

Of course there are also drawbacks:

  • Patch Policy are less flexible than normal Policies. For example, it’s not possible to add an post- or preinstall script. It’s only possible to initiate a package installation. If I need complex workflows (including scripts or different targets etc.), I create dummy installers which trigger other policies. Sadly this will be again less clean and create once again multiple policies. But I don’t often encounter this problem.
  • Patch Policies depent on ‘Patch Softwaretitle Definitions’. Jamf provides quite a big list, but if you want to patch a software which isn’t on this list, you need to ether find and add an external definition (which maybe doesn’t get updated in time or get’s dropped etc.), or use Jamf’s Title Editor, which results in even more manual work. But maybe this get’s solved with another Post Processor in the future :)
  • Like already mentioned, the Patch Management Workflow is also pretty click-heavy in the GUI. But like you maybe already guessed, we are gonna skip this point, and simply automate it with AutoPkg! :)

AutoPkg Custom Processor: JamfPatchUploader

Jamf’s API provides endpoints to link installers to versions and craete patch policies just like normal policies. After discovering this, I HAD to built a custom Processor based on Graham’s great JamfUploader-Base!

Working with the JamfPatchUploader

First you need to create the Patch Softwaretitle in Jamf by Hand. This step only needs to be done one time for each software you want to patch:

Jamf Pro Patch Softwaretitle

Note: Jamf lists some softwaretitles multiple times. Better select the non-legacy one.

Jamf Pro Patch Softwaretitle Firefox

2nd Note: Some softwaretitles, like the Mozilla Firefox one, need additional Extension Attributes to function. You have to accept the EA, otherwise the softwaretitle will not work!

All other steps, can be managed with the Processor:

Process:
  - Processor: com.github.grahampugh.jamf-upload.processors/JamfPatchUploader
    Arguments:
      patch_name: "%PATCH_NAME%"
      patch_template: "%PATCH_TEMPLATE%"
      patch_softwaretitle: "%PATCH_SOFTWARE_TITLE%"
      patch_icon_policy_name: "%POLICY_NAME%"
      replace_patch: True

patch_softwaretitle is the name of the Software Title in your Jamf Instance e.g. Mozilla Firefox. patch_name is the name of the patch policy. If no variable is provided, the name defaults to %NAME% - %version%. You can read more about all variables in the provided README: autopkg/grahampugh-recipes/JamfPatchUploader

Just like the JamfPolicyUploader the JamfPatchUploader uses a xml-Template to define the resulting patch policy in Jamf. It’s important to include <software_title_configuration_id>%patch_softwaretitle_id%</software_title_configuration_id> and <id>%patch_icon_id%</id>. You don’t need to provide these two variables - the Processor will set them - but they need to be in the template. Otherwise Jamf will reject them.

There are two types of patch policy types:

Automatically install Updates

These kind of Updates install the Patch instant, or wait for defined grace period if the application is still running. After the grace period the application gets force closed. Sadly currently the user only get’s a notification when the patch gets detected, but not when the grace period is over. So workflows like ‘Install in the next 4 hours’ feel a bit sluggish:

<?xml version="1.0" encoding="UTF-8"?>
<patch_policy>
  <general>
    <name>%patch_name%</name>
    <enabled>false</enabled>
    <target_version>%version%</target_version>
    <distribution_method>prompt</distribution_method>
    <allow_downgrade>false</allow_downgrade>
    <patch_unknown>true</patch_unknown>
  </general>
  <scope>
    <all_computers>true</all_computers>
    <computers/>
    <computer_groups/>
    <users/>
    <buildings/>
    <departments/>
    <limitations>
      <network_segments/>
      <ibeacons/>
    </limitations>
    <exclusions>
      <computers/>
      <computer_groups/>
      <users/>
      <buildings/>
      <departments/>
      <network_segments/>
      <ibeacons/>
    </exclusions>
  </scope>
  <user_interaction>
    <grace_period>
      <grace_period_duration>240</grace_period_duration>
      <notification_center_subject>Update</notification_center_subject>
      <message>$APP_NAMES will be terminated in $DELAY_MINUTES minutes to update $SOFTWARE_TITLE.</message>
    </grace_period>
  </user_interaction>
  <software_title_configuration_id>%patch_softwaretitle_id%</software_title_configuration_id>
</patch_policy>

Notify User about Update

There is also the possibility to trigger a notification, which also get’s displayed in the Self Service application. In this scenario the user can decide when he wants to start the update. Also a deadline can be defined. In my experience this would be the better workflow, but sadly feels a bit buggy (notifications not getting cleared after successful updates etc.):

<?xml version="1.0" encoding="UTF-8"?>
<patch_policy>
  <general>
    <name>%patch_name%</name>
    <enabled>false</enabled>
    <target_version>%version%</target_version>
    <distribution_method>selfservice</distribution_method>
    <allow_downgrade>false</allow_downgrade>
    <patch_unknown>true</patch_unknown>
  </general>
  <scope>
    <all_computers>true</all_computers>
    <computers/>
    <computer_groups/>
    <users/>
    <buildings/>
    <departments/>
    <limitations>
      <network_segments/>
      <ibeacons/>
    </limitations>
    <exclusions>
      <computers/>
      <computer_groups/>
      <users/>
      <buildings/>
      <departments/>
      <network_segments/>
      <ibeacons/>
    </exclusions>
  </scope>
  <user_interaction>
      <install_button_text>Update</install_button_text>
      <self_service_description/>
      <self_service_icon>
        <id>%patch_icon_id%</id>
      </self_service_icon>
      <notifications>
        <notification_enabled>true</notification_enabled>
        <notification_type>Self Service and Notification Center</notification_type>
        <notification_subject>%NAME% update available</notification_subject>
        <notification_message/>
        <reminders>
          <notification_reminders_enabled>true</notification_reminders_enabled>
          <notification_reminder_frequency>1</notification_reminder_frequency>
        </reminders>
      </notifications>
      <deadlines>
        <deadline_enabled>false</deadline_enabled>
        <deadline_period>7</deadline_period>
      </deadlines>
  </user_interaction>
  <software_title_configuration_id>%patch_softwaretitle_id%</software_title_configuration_id>
</patch_policy>

Icons in Self Service patches

Note that including icons in Self Service Updates is a bit tricky. We currently have no reasonable way to upload icons to the patch policies. So I built a workaround, which extracts the icon from an already uploaded non-patch policy. Simply define the policy e.g. patch_icon_policy_name: "%POLICY_NAME%" and use the following snippet in your template:

<self_service_icon>
  <id>%patch_icon_id%</id>
</self_service_icon>

Disabled Patch Policies

Note that I create patch policies disabled, since I often don’t want to distribute the updates instantly. Instead I often create a normal ‘Testing’-Policy which get’s used by specific user groups. After positive reports, I simply enable the already created patch policy via the Web-GUI. Disable replace_patch in the recipe if you want to follow this workflow ;)

Fully automated AutoPkg recipe

The ‘jack of all trades’ recipe would look like this (Note that we first upload the non-patch policy including the icon, so we can use it later in the patch policy):

Description: 'All-in-One' Mozilla Firefox.
Identifier: com.eisenschmiede.recipes.jamf.MozillaFirefox
ParentRecipe: com.github.autopkg.pkg.FirefoxSignedPkg
MinimumVersion: '2.3'

Input:
  NAME: MozillaFirefox
  CATEGORY: Internet
  POLICY_CATEGORY: '%CATEGORY%'
  POLICY_NAME: 'Install_%NAME%'
  POLICY_REPLACE: 'True'
  POLICY_TEMPLATE: 'Policy_SelfService_AllDevices.xml'
  POLICY_SELFSERVICE_NAME: 'Mozilla Firefox'
  POLICY_SELFSERVICE_DESC: 'A webbrowser made by Mozilla.'
  PATCH_TEMPLATE: 'Patch_SelfService_AllDevices.xml'
  DOCK_ITEM_NAME: '%NAME%'
  DOCK_ITEM_TYPE: 'App'
  DOCK_ITEM_PATH: 'file:///Applications/Firefox.app/'

Process:
  - Processor: com.github.grahampugh.jamf-upload.processors/JamfDockItemUploader
    Arguments:
      dock_item_name: '%DOCK_ITEM_NAME%'
      dock_item_type: '%DOCK_ITEM_TYPE%'
      dock_item_path: '%DOCK_ITEM_PATH%'
      replace_dock_item: True

  - Processor: com.github.grahampugh.jamf-upload.processors/JamfCategoryUploader
    Arguments:
      category_name: '%CATEGORY%'

  - Processor: com.github.grahampugh.jamf-upload.processors/JamfPackageUploader
    Arguments:
      pkg_category: '%CATEGORY%'

  - Processor: com.github.grahampugh.jamf-upload.processors/JamfPolicyUploader
    Arguments:
      policy_name: '%POLICY_NAME%'
      policy_template: '%POLICY_TEMPLATE%'
      replace_policy: '%POLICY_REPLACE%'
      icon: '%NAME%.png'

  - Processor: com.github.grahampugh.jamf-upload.processors/JamfPatchUploader
    Arguments:
      patch_softwaretitle: "Mozilla Firefox"
      patch_template: "%PATCH_TEMPLATE%"
      patch_icon_policy_name: "%POLICY_NAME%"
      replace_patch: True

Jamf Pro Patch PKG Link

The package got linked to the version in the Patch Softwaretitle

Jamf Pro Patch Policy

The patch policy get’s created and stays disabled, just like we defined it in the template

Notifications about created patches

Besides creating a wonderful base to create different kinds of processor, Graham Pugh also maintains a JamfUploaderTeamsNotifier and JamfUploaderSlacker which just got an update to include information from the JamfPatchUploader. Besides that the Processor of course also outputs a summary as all the other processors:

The following patch policies were created or updated in Jamf Pro:
    Patch Id  Patch Policy Name         Patch Softwaretitle  Patch Version
    --------  -----------------         -------------------  -------------
    10        Mozilla Firefox - 97.0.1  Mozilla Firefox      97.0.1

I hope some people find this workflow as useful as I do :) If you have any questions, drop me a message via Twitter or on the macAdmins Slack Channel!