Scheduled macOS software updates using MDM with good user communication
/Skip to the end if you want to see the Instructions and Code…
Background
Apple’s Startup Disk security policy control for a Mac with Apple silicon article states that “remote management of […] automatic software updates” requires setting Security Policy to Reduced Security in Startup Security Utility using recoveryOS.
Addigy’s System Updates via MDM and DDM article explicitly states that Apple Silicon Macs require either ADE or Reduced Security Mode to allow System Updates via MDM or DDM.
Therefore, I haven’t relied on System Updates via MDM and DDM because most of my client Macs are user-enrolled (non-ADE), and I don’t want to walk individual users through booting to Recovery mode – which would be onerous at scale. And asking users to choose an option clearly labeled “Reduced Security” would prompt a nuanced discussion with certain clients about why we’re asking them to “reduce” their security when in fact our intention is to increase their security.
However, in my experience, MDM software updates seem to work fine – even for Macs that were not enrolled using ADE, and where you don’t need to have the client reboot into Recovery mode and change to Reduced Security.
The old method
I recently decided to learn more about System Updates via MDM and thoroughly test these workflows since my usual method relying on Nudge and the openToMoreInfo GUI scripting AppleScript app stopped working reliably. The first issue is that Nudge relies on the user navigating the Software Update window, skipping past the Sonoma Upgrade to find the “More Info” button under “Other updates available” subheading, then clicking Install Now, and Agree. Too many steps!
openToMoreInfo solved this fairly elegantly for awhile, but in Ventura and Sonoma, Apple made a change to the underlying GUI elements making the More Info “button” into a static text element which doesn’t respond to a GUI scripting “click” command, and appears to be specifically blocked from any kind of “synthetic click”. So we’re back to only being able to provide guidance to the user, telling them where to look and what to click. Not ideal.
That was less of an issue when we were able to defer the major upgrade using an MDM configuration profile. This would hide Sonoma from appearing in Software Update, so the only updates listed would be the minor updates we wanted the user to install. However, the maximum deferral is 90 days – which, based on the Sonoma release date, happens to be Christmas Day.
Since we can’t programmatically guide the user past the tempting Sonoma Upgrade with its pretty icon, and since Nudge inherently relies on using the Software Update window, this began to cause more problems than it solved. We were actively directing users into Software Update, making it more likely that they would begin an upgrade to Sonoma before we were ready to support it.
The new method
The new method leverages scheduled MDM software updates rather than using Nudge to guide the user to the Software Update window and seems to work well despite Apple’s guidance noted above.
I tested this on several user-enrolled (non-ADE) Macs with Full Security, and I was able to successfully push updates via MDM using GoLive > Updates > Download and/or Install:
Ventura 13.2 (VM): Updated to 13.6.3.
Ventura 13.6.1 (iMac 24-inch M1): Updated to 13.6.3. First prompt was awaiting user confirmation of Restart. Declined first restart prompt as a test, sent Download and/or Install again, saw countdown, let it restart on its own.
I also tested a few using using Policy > Updates > System Updates > Enable macOS Updates > Schedule Updates:
Monterey 12.6.3 (VM): Updated to 12.7.2.
Monterey 12.7.1 (iMac Retina 4K, 21.5-inch, 2019) – Mac at login window, no user logged in: Updated to 12.7.2.
Ventura 13.6.1 (iMac 24-inch M1) - Mac at Lock Screen with user logged in: Updated to 13.6.3.
Ventura 13.6.1 (MacBook Pro 13-inch M1) - MacBook Pro with user logged in, lid closed, with power adapter connected: Updated to 13.6.3.
So we have a functional workflow using scheduled macOS software updates via MDM, allowing Macs to update with minimal user interaction.
How to use the new method
Simply enable macOS Updates, set a maximum version, and schedule accordingly. See Addigy’s System Updates via MDM and DDM article for more instruction about how to enable and schedule this method.
User communication
What are some reasons that a Mac could fail to update using this method?
A couple of issues we can foresee would be when a laptop isn’t connected to power during the scheduled update window, or when a Mac doesn’t have enough space available to update.
We can handle those potential issues with good user communication:
Instructions:
Create a Flex Policy, e.g. “macOS Software Updates Tuesday 7pm” and follow Addigy’s guidance to enable Scheduled System Updates via MDM and DDM. Specify a long enough scheduled update window, e.g. 4 hours.
Create a Custom Fact in Addigy > Catalog called “Software Update Needed” of type Boolean. This reports TRUE if the current macOS version is less than the latest macOS update e.g. running 13.6.1 when 13.6.3 is released, and also saves the value (TRUE/FALSE) to a file on the Mac which the Scripts below can reference.
Create a Script in Devices > Scripts > Manage called “Laptop on Battery Update Prompt”. This checks if Software Update Needed, and if so, checks if laptop is on battery, and prompts the user.
Create a Script in Devices > Scripts > Manage called “Low Space Update Prompt”. This checks if Software Update Needed, and if so, checks if free space > 25GB, otherwise prompts the user.
Create a Maintenance Item in Addigy > Catalog called “Update Checks 5pm Tuesday”, include both scripts above, and schedule accordingly.
Add your Software Update Needed Custom Fact and Update Checks 5pm Tuesday Maintenance Item to your “macOS Software Updates Tuesday 7pm” Flex Policy.
Optional: enable Microsoft Office updates scheduled for 5:15pm in the same Flex Policy – might as well prompt the user to quit Office apps if needed.
Assign your Flex Policy to some test Macs, or use auto-assignment to add your Flex Policy to some of your existing test policies. Deploy the policy to ensure the Macs check in and run the Software Update Needed Custom Fact before the Maintenance Item runs your scripts ahead of the scheduled Software Update window. Adjust the schedules accordingly for testing purposes.
Code for Custom Fact & Scripts
Software Update Needed – Custom Fact
Based on work by Ross Matsuda | Sudoade 2023
#!/bin/bash
# by Joe Saponare, CommandControlPower.com: https://commandcontrolpower.com/podcast/2023/12/16/scheduled-macos-software-updates-using-mdm-with-good-user-communication
software_update_needed_file="/Library/Addigy/software_update_needed.txt"
### Define latest macOS versions
# Get list of OS versions
softwareupdatelist="$(curl -m 5 -sfL 'https://gdmf.apple.com/v2/pmv')"
# echo "$softwareupdatelist"
### Truncate list - leaving comments to explain function of each section
macOSonly=${softwareupdatelist#*macOS} #Strip out all content before first mention of "macOS"
# macOStruncated="$(echo "$macOSonly" | tr , '\n' | grep "ProductVersion")" # Use commas for line breaks, remove all lines other than ProductVersion entries
# macOStruncated1="$(echo "$macOStruncated" | sed '/iOS/q' | sed '$d')" #Remove all content after macOS versions listed
# macOStruncated2="$(echo "$macOStruncated1" | awk -FProductVersion '{print $2}')" #Remove extraneous text
# macOStruncated3="$(echo "$macOStruncated2" | sed 's/"//g' | sed 's/://g')" #Remove extraneous punctuation
speedTruncation="$(echo "$macOSonly" | tr , '\n' | grep "ProductVersion" | sed '/iOS/q' | sed '$d' | awk -FProductVersion '{print $2}' | sed 's/"//g' | sed 's/://g')"
#echo " speed truncation output: $speedTruncation"
### Version Extractions
latest11="$(echo $speedTruncation | tr ' ' '\n' | grep 11)"
#echo "Big Sur: $latest11"
latest12="$(echo $speedTruncation | tr ' ' '\n' | grep 12)"
#echo "Monterey: $latest12"
latest13="$(echo $speedTruncation | tr ' ' '\n' | grep 13)"
#echo "Ventura: $latest13"
latest14="$(echo $speedTruncation | tr ' ' '\n' | grep 14)"
#echo "Ventura: $latest14"
# Get current OS
currentOS=$(sw_vers -productVersion)
currentOSMajor=$(echo $currentOS | awk -F. '{print $1}') # outputs 10, 11, 12, 13, 14
software_update_needed=""
if [[ $currentOSMajor = 14 ]]; then
# macOS Sonoma (14) detected
if [[ $currentOS = $latest14 ]]; then
software_update_needed="FALSE"
else
software_update_needed="TRUE"
fi
elif [[ $currentOSMajor = 13 ]]; then
# macOS Ventura (13) detected
if [[ $currentOS = $latest13 ]]; then
software_update_needed="FALSE"
else
software_update_needed="TRUE"
fi
elif [[ $currentOSMajor = 12 ]]; then
# macOS Monterey (12) detected
if [[ $currentOS = $latest12 ]]; then
software_update_needed="FALSE"
else
software_update_needed="TRUE"
fi
elif [[ $currentOSMajor = 11 ]]; then
# macOS Monterey (11) detected
if [[ $currentOS = $latest11 ]]; then
software_update_needed="FALSE"
else
software_update_needed="TRUE"
fi
else
# if the system is not running macOS 11, 12, 13, or 14, then we assume no updates are needed
software_update_needed="FALSE"
fi
echo $software_update_needed
echo $software_update_needed > $software_update_needed_file
exit 0
Laptop on Battery Update Prompt – Device Script
#!/bin/bash
# by Joe Saponare, CommandControlPower.com: https://commandcontrolpower.com/podcast/2023/12/16/scheduled-macos-software-updates-using-mdm-with-good-user-communication
# Known Limitations: no known limitations
# Requires Custom Fact "Software Update Needed" as noted in link above.
software_update_needed_file="/Library/Addigy/software_update_needed.txt"
software_update_needed=$(cat $software_update_needed_file)
#for testing:
#software_update_needed="TRUE"
[[ $(pmset -g ps|grep "AC Power") ]] && state="AC" || state="BATT"
model=$(sh /Library/Addigy/auditor-facts/scripts/device_model_name)
forefront="True"
title=$"🪫 Please connect to power"
description=$"Your ${model} needs to be connected to power to install scheduled updates.
Please connect your power adapter (charger) and leave your ${model} screen open."
# Get logged in user.
username=$(stat -f %Su /dev/console)
if [[ $software_update_needed == "TRUE" ]]; then
echo "Software update needed: $software_update_needed."
if [ "$state" == "BATT" ]; then
echo "$model is on battery."
if [ "$username" != "root" ]; then
echo "User $username is logged in. Prompting user."
if /Library/Addigy/macmanage/MacManage.app/Contents/MacOS/MacManage action=notify title="${title}" description="${description}" forefront="$forefront"; then
echo "User $username clicked OK."
exit 0
fi
else
echo "No user is logged in. Unable to display prompt."
fi
elif [ "$state" == "AC" ]; then
echo "$model is on AC power."
fi
else
echo "Software update needed: $software_update_needed."
fi
Low Space Update Prompt – Device Script
#!/bin/bash
# by Joe Saponare, CommandControlPower.com: https://commandcontrolpower.com/podcast/2023/12/16/scheduled-macos-software-updates-using-mdm-with-good-user-communication
# Known Limitations: no known limitations
# Requires Custom Fact "Software Update Needed" as noted in link above.
software_update_needed_file="/Library/Addigy/software_update_needed.txt"
software_update_needed=$(cat $software_update_needed_file)
#for testing:
#software_update_needed="TRUE"
model=$(sh /Library/Addigy/auditor-facts/scripts/device_model_name)
free_space=$(sh /Library/Addigy/auditor-facts/scripts/free_disk_space_gb)
#for testing:
#free_space=15
space_needed="30"
forefront="True"
title=$"🔻 Need more space to update"
description=$"Your ${model} needs more space to install scheduled updates.
Please free up at least ${space_needed} GB or contact us for help."
# Get logged in user.
username=$(stat -f %Su /dev/console)
if [[ $software_update_needed == "TRUE" ]]; then
echo "Software update needed: $software_update_needed."
if [ "$free_space" -le "$space_needed" ]; then
echo "$model has $free_space GB which is less than $space_needed GB space needed."
if [ "$username" != "root" ]; then
echo "User $username is logged in. Prompting user."
if /Library/Addigy/macmanage/MacManage.app/Contents/MacOS/MacManage action=notify title="${title}" description="${description}" forefront="$forefront"; then
echo "User $username clicked OK."
exit 0
fi
else
echo "No user is logged in. Unable to display prompt."
fi
else
echo "$model has $free_space GB free space. (Minimum $space_needed GB needed.)"
fi
else
echo "Software update needed: $software_update_needed."
fi