Software Updates, Part 1 - Device State

We can use locally-stored .plist data to differentiate between available updates and upgrades, then make decisions based on those results.

Software Updates, Part 1 - Device State

Software updates in macOS are exhausting for admins. While we can do our best to control things using MDM, the underlying framework that handles detecting and implementing these updates has been an ongoing source of frustration. Before we can talk about how to make things better, we need to understand the state of our devices.

In the past, we've used the softwareupdate binary itself to populate extension attributes or facts, invoking it intermittently and collecting output.  However, triggering this binary is bandwidth- and CPU-intensive. Let's instead pull some information from a .plist file to keep our queries as lightweight as possible. We'll be looking at /Library/Preferences/com.apple.SoftwareUpdate.plist to snag the info we want. To get started, here are the three facts I want to know about my devices:

  1. Is this device fully updated, or are there any available updates?
  2. If updates are available, how many updates are available?
  3. What are the names of the available updates?

The puzzle of delta upgrades

In days past, we'd assume that any Mac is considered fully updated if it is running the newest version of the newest operating system. In such a situation, any device that reports back zero available updates is considered updated. In some production environments, you may need to have devices running older versions of macOS for compatibility reasons. These Macs on older versions of the OS may be running the newest patch of their current operating system, and be considered updated even if they're not running the newest OS. At the time of this writing, I'd consider a Mac running 12.6.2 fully updated, with an upgrade to macOS Ventura available.

This distinction between updates and upgrades hasn't mattered much in the logic of how we check for software updates, but that's all changed thanks to the advent of delta upgrades. Beginning with macOS Ventura, Apple can now serve a full macOS upgrade using the same softwareupdate framework that they've previously used just for point updates. For many use cases, this is spectacular - the delta upgrade can be a smaller file and it doesn't require admin credentials to run (though it does require authentication from a volume owner on Apple Silicon Macs). The unique challenge when it comes to collecting information on available updates on a workstation is that a Mac running macOS Monterey 12.6.1 may tell you that it has three updates available:

• macOS 12.6.2
• Safari
• macOS 13.1

How you handle this will be unique to your environment - in ours, the goal is to ensure that whatever version of macOS our users have is patched up to the newest point release, regardless of whether they're upgraded to macOS Ventura or not. This means that the facts we author need to differentiate between updates and upgrades when collecting data.

Fact 1 - Device Fully Updated?

For this fact, we're going to return a boolean on whether the Mac is fully updated, or if there's at least one available update for it. We'll use the defaults command to quickly read the .plist file we mentioned above and use some string manipulation to extract just the names of the last detected updates:

namesOfUpdates="$(/usr/bin/defaults read /Library/Preferences/com.apple.SoftwareUpdate.plist RecommendedUpdates | grep "Display Name" | awk -F= '{print $2}' | sed 's/"//g' | sed 's/;//g')"
Variable (string) - names of all available software updates

This variable will hold the name of all the updates available the last time the Mac checked in with Apple's Software Update servers. Given the way we differentiate updates from upgrades, our next task is stripping out any software upgrades that made their way into this list. To do so, we first  need to figure out what operating system this Mac is currently using:

# Get current OS
currentOS=$(sw_vers -productVersion)
currentOSMajor=$(echo $currentOS | awk -F. '{print $1}') # outputs 10, 11, 12, 13
Variable (string) - major macOS version number (10, 11, 12, 13, etc.)

Delta upgrades are currently only a problem/opportunity in macOS 12 and later, so the unique logic we're going to execute here doesn't need to run in macOS 11 and earlier. We also won't be differentiating versions of Mac OS X 10.15 and earlier, they'll all be treated the same.

We'll now run a check against the currentOSMajor variable and define an array of macOS version names that we don't want to display upgrades for, should they be detected in the .plist file. I've annotated the if statement below to explain why we're populating the skipString array the way we are - since macOS 14 hasn't been given a name yet, we can't add more values into the array, but using this structure will make the check easier to upgrade next summer when the new OS is announced:

# Set Upgrade names to skip
if [ "$currentOSMajor" = 13 ]; then
  # echo "13 - Ventura"
  # No skip strings yet, macOS 14 not named
  skipString=("null")
elif [ "$currentOSMajor" = 12 ]; then
  # echo "12 - Monterey"
  skipString=("Ventura")
elif [ "$currentOSMajor" = 11 ]; then
  # echo "11 - Big Sur"
  # No skip strings, deltas not supported
  skipString=("null")
elif [ "$currentOSMajor" = 10 ]; then
  # echo "10.15 - Catalina or earlier"
  # No skip strings, deltas not supported
  skipString=("null")
fi
Variable (array) - major macOS version names to ignore

We now have our string filled with the names of the available updates, and an array that could have a number of macOS version names that we want to hide from our fact. We'll now pipe our list of updates into a new variable in case we need to reference the original output later, and then iterate through the list cutting out any available upgrades that include names from our skipString array:

finalUpdateList="$namesOfUpdates"
for i in "${skipString[@]}"; do
  finalUpdateList="$(echo "$finalUpdateList" | grep -v "$i")"
done
Iterating through the array of names to ignore while reading the list of available software updates

With our curated list of updates, free of any delta upgrades, we can now evaluate whether the list has a nonzero value or not and report our final result:

if [ "$finalUpdateList" = "" ]; then
  # echo "No updates detected"
  echo "TRUE"
  exit 0
else
  # echo "At least one update detected"
  echo "FALSE"
  exit 0
fi
Evaluating whether our final list of updates is blank or not

In this example, our fact "Software Update - Device Fully Updated" will return "TRUE" if there are no available software updates for the device's current operating system. It will return "FALSE" should there be a point update available for the current OS.

Fact 2 - Number of Available Updates

We've now done all the heavy lifting and can use our this engine to power multiple facts. To get the number of available updates, we'll use a grep -c command to look into our final update list and pull the total out. We'll also add a sanity check to bail out if the final list is empty:

# Bailout if finalUpdateList is blank
if [ "$finalUpdateList" = "" ]; then
  echo "0"
  exit 0
fi

finalNumberOfUpdates=$(echo -n "$finalUpdateList" | grep -c '^')
echo "$finalNumberOfUpdates"
Returning the number (count) of available updates in our final list

Fact 3 - Names of Available Updates

Unsurprisingly, this is even easier than the preceding fact, as we can just output the final list (keeping the same bailout for a cleaner output if the list is blank:

# Bailout if finalUpdateList is blank
if [ "$finalUpdateList" = "" ]; then
  # echo "No updates detected"
  exit 0
fi

# echo "Ignoring skip string, updates are"
echo "$finalUpdateList"
exit 0
Returning the names of available updates in our final list

Next steps

Adapt these facts as you see fit to match your work environment, and then decide how to take action. You could use this to set up alerts to users or your help desk when a device is claiming it's fully updated, but you know that it's not running the latest point update. You could reach into the same .plist to get information on the dates of the last successful software update checks (though this can be tricky to execute reliably). When your support desk is reaching out to assist a user, they can offer much more specific expectations about what users will see when they try to run their software updates.  You could use notification tools to let the user know that specific updates are available and that they need to be run.

In the next article, we'll take a look at common problems users will face on their devices as they attempt to detect and apply software updates. There are some tools at our disposal to help make the process a little less painful.