MacOS updates can be painful.
Apple does what Microsoft doesn't: Put out updates that are well tested.
They conversely don't do what Microsoft (or linux) does: Make deploying updates seamless.
That last bit matters most since users aren't really enthusiastic about rebooting their machines and staring at a black screen with a loading bar to finish.
Enter, Nudge. A cool tool that gets users enthusiastic about updating or else.
The concept is simple, deploy an agent that notifies the user of an available update, and then force them to do it if they ignore it for too long and the deadline lapses.
How have we deployed it?
- We grab the latest NudgeSuite PKG file from their github repo, and deploy it via MDM.
- Create a .mobileconfig file, and deploy that via MDM.
As for that mobileconfig file. The nudge wiki gives you a good example template to work off and a whole slew of information regarding all the flags.
For posterity and ease of use, here's the one I've written up:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PayloadContent</key>
<array>
<dict>
<key>PayloadDescription</key>
<string>Configures all Nudge preferences</string>
<key>PayloadDisplayName</key>
<string>Nudge Preferences</string>
<key>PayloadIdentifier</key>
<string>com.github.macadmins.Nudge.preferences.example</string>
<key>PayloadOrganization</key>
<string></string>
<key>PayloadType</key>
<string>com.github.macadmins.Nudge</string>
<key>PayloadUUID</key>
<string>CA02957C-7472-446B-9F77-3E0414405556</string>
<key>PayloadVersion</key>
<integer>1</integer>
<key>optionalFeatures</key>
<dict>
<key>acceptableApplicationBundleIDs</key>
<array/>
<key>acceptableAssertionApplicationNames</key>
<array/>
<key>acceptableAssertionUsage</key>
<false/>
<key>acceptableCameraUsage</key>
<true/>
<key>acceptableScreenSharingUsage</key>
<true/>
<key>aggressiveUserExperience</key>
<true/>
<key>aggressiveUserFullScreenExperience</key>
<true/>
<key>asynchronousSoftwareUpdate</key>
<true/>
<key>attemptToBlockApplicationLaunches</key>
<false/>
<key>attemptToFetchMajorUpgrade</key>
<true/>
<key>blockedApplicationBundleIDs</key>
<array/>
<key>enforceMinorUpdates</key>
<true/>
<key>terminateApplicationsOnLaunch</key>
<false/>
</dict>
<key>osVersionRequirements</key>
<dict>
<key>aboutUpdateURL</key>
<string>https://support.apple.com/en-us/HT211896</string>
<key>requiredInstallationDate</key>
<date>2022-11-28T23:59:00Z</date>
<key>requiredMinimumOSVersion</key>
<string>13.0.1</string>
</dict>
<key>userExperience</key>
<dict>
<key>allowGracePeriods</key>
<false/>
<key>allowUserQuitDeferrals</key>
<true/>
<key>allowedDeferrals</key>
<integer>1000000</integer>
<key>allowedDeferralsUntilForcedSecondaryQuitButton</key>
<integer>14</integer>
<key>approachingRefreshCycle</key>
<integer>6000</integer>
<key>approachingWindowTime</key>
<integer>72</integer>
<key>elapsedRefreshCycle</key>
<integer>300</integer>
<key>gracePeriodInstallDelay</key>
<integer>23</integer>
<key>gracePeriodLaunchDelay</key>
<integer>1</integer>
<key>gracePeriodPath</key>
<string>/private/var/db/.AppleSetupDone</string>
<key>imminentRefreshCycle</key>
<integer>600</integer>
<key>imminentWindowTime</key>
<integer>24</integer>
<key>initialRefreshCycle</key>
<integer>18000</integer>
<key>maxRandomDelayInSeconds</key>
<integer>1200</integer>
<key>noTimers</key>
<false/>
<key>nudgeRefreshCycle</key>
<integer>60</integer>
<key>randomDelay</key>
<false/>
</dict>
<key>userInterface</key>
<dict>
<key>fallbackLanguage</key>
<string>en</string>
<key>forceFallbackLanguage</key>
<false/>
<key>forceScreenShotIcon</key>
<true/>
<key>iconDarkPath</key>
<string>/tmp/nudge/appleRainbow.png</string>
<key>iconLightPath</key>
<string>/tmp/nudge/appleRainbow.png</string>
<key>screenShotDarkPath</key>
<string>/tmp/nudge/updateDark.png</string>
<key>screenShotLightPath</key>
<string>/tmp/nudge/updateLight.png</string>
<key>showDeferralCount</key>
<true/>
<key>simpleMode</key>
<false/>
<key>singleQuitButton</key>
<false/>
<key>updateElements</key>
<dict>
<key>_language</key>
<string>en</string>
<key>actionButtonText</key>
<string>Update Device</string>
<key>customDeferralButtonText</key>
<string>Custom</string>
<key>customDeferralDropdownText</key>
<string>Defer</string>
<key>informationButtonText</key>
<string>More Info</string>
<key>mainContentHeader</key>
<string>Your device will restart during this update</string>
<key>mainContentNote</key>
<string>Important Notes</string>
<key>mainContentSubHeader</key>
<string>Updates can take around 30 minutes to complete</string>
<key>mainContentText</key>
<string>A fully up-to-date device is required to ensure that we on the techops team can protect company data.
If you do not update by the due date, you will effectively be locked out of your system until you complete the update.
While the update has been rigorously tested it's advised you do this as soon as possible to avoid any downtime, as well as help us find any issues early on.
To begin the update, simply click on the Update Device button and follow the prompts.
For more information, check out the #techops-news channel in Slack or hit "More Info" in the bottom left corner of this window.</string>
<key>mainHeader</key>
<string>Your device requires a security update</string>
<key>oneDayDeferralButtonText</key>
<string>One Day</string>
<key>oneHourDeferralButtonText</key>
<string>One Hour</string>
<key>primaryQuitButtonText</key>
<string>Later</string>
<key>secondaryQuitButtonText</key>
<string>I understand</string>
<key>subHeader</key>
<string>A friendly reminder from your TechOps team</string>
</dict>
</dict>
</dict>
</array>
<key>PayloadDescription</key>
<string>Configures Nudge application</string>
<key>PayloadDisplayName</key>
<string>Nudge</string>
<key>PayloadIdentifier</key>
<string>com.github.macadmins.Nudge.example</string>
<key>PayloadOrganization</key>
<string>Nudge</string>
<key>PayloadScope</key>
<string>System</string>
<key>PayloadType</key>
<string>Configuration</string>
<key>PayloadUUID</key>
<string>2F54F734-132D-4539-B583-F1DCF23DB5EB</string>
<key>PayloadVersion</key>
<integer>1</integer>
</dict>
</plist>
Key takeaways if you're going to use my file.
- "acceptableCameraUsage" and "acceptableScreenSharingUsage" are set to "true", as I don't want users to recieve the notification if they're on a video call or meeting.
- The minimum update this wants our uses on is 13.0.1
- The due date is 2022-11-28 at 23:59pm. The date uses ISO formatting (hell yeah).
- I also deploy custom icons and screenshots because I had free time to style, if they can't be found because you don't have the files, they'll default to what nudge uses (shown on their github page).
- For the love of everything, be sure to version number your mobileconfig files with a naming scheme (
nudge-config-v1301.mobileconfig
is perfectly fine) - The notification is scheduled to go off every 30 minutes at the top and bottom of each hour via the launchdaemon file from the PKG deployment. I don't bother changing this, it works well.
Here's how our deployment looks:

Here are the MDM settings we use on Mosyle for deploying the profile/mobileconfig file:

The settings for the PKG file deployment:


Rules of thumb:
- Give your users enough time to update, we use two week periods (or three days if there's a massive bug being actively exploited).
- Do not blame me if the config file doesn't work.
- When you need to do another macOS update, you should really only be changing the due date and the desired minimum version, then pushing out a new .mobileconfig file to users.
- Actively communicate to your users the change you're trying to push outside of the notification, people will freak out over random popups.