Handling macOS Software Updates with Jamf Pro

Jamf Pro has not handled software updates successfully on all Mac hardware since Apple introduced the T2 processor with the iMac Pro back in December 2017. It’s been requested that they address this issue in a feature request, but it’s gone completely unacknowledged (Edit: As of November 11, 2019, the feature request is now marked as Under Review).

The problem with the software update process on Macs with T2 processors is that sometimes there is a bridgeOS update (the OS on the T2 processor) which requires a shutdown instead of a restart. The Mac will read the shutdown and automatically power back on to apply the bridgeOS update. However, not all software updates have a bridegeOS update which would mean a shutdown in those situations would actually leave the computer powered down. Unfortunately, Jamf Pro does not know how to handle this situation. Apple did introduce the --restart option for softwareupdate but that also comes with its own problems in that it hasn’t worked reliably in all scenarios. Since the solution to this isn’t particularly difficult to work around, I created a script to address this workflow in our environment.

Before continuing, I’d like to mention that we do leverage macOS’s ability to do automatic updates. This has one benefit of doing automated authenticated restarts which is important on Macs with FileVault enabled. However, we’ve found in our environment that after a month only 60% of computers running macOS 10.14 are up to date on the latest version. It’s a bit of a black box as to how macOS determines when to do automatic updates. Needless to say, the rate of updates is unacceptable.

This script is meant to be used with Jamf Pro and makes use of Jamf Helper. The idea behind this script is that it alerts the user that there are required OS updates that need to be installed. Rather than forcing updates to take place through the command line using “softwareupdate”, the user is encouraged to use the macOS GUI to update. When I say macOS GUI, I’m referring to the Software Update mechanism that Apple refers consumers to: https://support.apple.com/en-us/HT201541

In recent OS versions, Apple has done a poor job of testing command line-based workflows of updates and failed to account for scenarios where an end-user may or may not be logged in. The update process through the GUI has not suffered from these kind of issues. The script will allow end users to postpone/defer updates X amount of times and then will give them one last chance to postpone. We run this script using the Once A Day policy frequency which means the user will get this once a day so long as it checks in.

This script should work rather reliably going back to 10.12 and maybe further, but at
this point the real testing has only been done on 10.14. Please note, that this script does NOT cache updates in advance. Sometimes Apple releases updates that get superseded in a short time frame. This can result in downloaded updates that are in the /Library/Updates path that cannot be removed in 10.14+ due to System Integrity Protection.

This script does make use of Jamf Pro Script Parameters:
Parameter 4: Optional. Number of postponements allowed. Default: 3
Parameter 5: Optional. Number of seconds dialog should remain up. Default: 900 seconds
Parameter 6: Optional. Contact email, number, or department name used in messaging. Default: IT

Here is the expected workflow with this script:

  1. If no user is logged in, the script will install updates through the command line and
    shutdown/restart as required.
  2. If a user is logged in and there are updates that require a restart, the user will get
    prompted to update or to postpone.
  3. If a user is logged in and there are no updates that require a restart, the updates will get installed in the background (unless either Safari or iTunes are running.)

There are a few exit codes in this script that may indicate points of failure:
11: No power source detected while doing CLI update.
12: Software Update failed.
13: FV encryption is still in progress.
14: Incorrect deferral type used.

Below are some screenshots for what you will see on macOS Mojave. However the text is aware of at least 10.8 and higher where the instructions to get to Software Update might differ.

This is the initial message you will see when prompted to update:


When you click Continue, you will be taken to Apple’s Software Update:


This is the final message you will get when you’ve postponed the maximum number times:


Note: “Please make selection in HH:MM:SS” is not text I can modify. It serves as a countdown for the end user to know how much time they have before they are forced to update.

And lastly when the forced update is taking place, a headsup display window pops up:


The script is easy to modify if you don’t like the verbiage or if you want to use it for inspiration on other workflows. The script can be found here on my Github page.

How to set the icon for a folder or file with a little bit of PyObjC

There may be situations in which you need to set an icon for a folder or file. If you start searching online, you might run into a bunch of recommendations that rely on Xcode command line tools or methods that simply no longer work.

The following method is quite simple because it only relies on the Python Objective-C bridge which has been bundled with macOS for many years now. The relevant API is in the NSWorkspace.

For just the PyObjC code, here it is taken from a StackOverFlow answer:

# https://developer.apple.com/documentation/appkit/nsworkspace/1529882-seticon?language=objc
# Argument 1: Path to icon
# Argument 2: Path to folder/file to set icon for

import Cocoa
import sys

Cocoa.NSWorkspace.sharedWorkspace().setIcon_forFile_options_(Cocoa.NSImage.alloc().initWithContentsOfFile_(sys.argv[1].decode('utf-8')), sys.argv[2].decode('utf-8'), 0) or sys.exit("Unable to set file icon")

And if you want to make use of that code in a Bash script, it’s not too hard:

# This is PyObjC code that can be used in a bash script
# https://developer.apple.com/documentation/appkit/nsworkspace/1529882-seticon?language=objc


setFolderIcon (){
/usr/bin/python - "$1" "$2" << EOF
import Cocoa
import sys
Cocoa.NSWorkspace.sharedWorkspace().setIcon_forFile_options_(Cocoa.NSImage.alloc().initWithContentsOfFile_(sys.argv[1].decode('utf-8')), sys.argv[2].decode('utf-8'), 0)
setFolderIcon "$FolderIcon" "$PathToSetIconFor"