Menu Close

DDM OS Reminder (1.4.0)

Mac Admins’ new favorite, MDM-agnostic, “set-it-and-forget-it” end-user messaging for Apple’s Declarative Device Management-enforced macOS update deadlines

DDM OS Reminder is many Mac Admins’ new favorite, MDM-agnostic, “set-it-and-forget-it” end-user messaging for Apple’s Declarative Device Management-enforced macOS update deadlines

Overview

While Apple’s Declarative Device Management (DDM) provides Mac Admins a powerful method to enforce macOS updates, its built-in notification tends to be too subtle for most Mac Admins:

While Apple's Declarative Device Management (DDM) provides Mac Admins a powerful method to enforce macOS updates, its built-in notification tends to be too subtle for most Mac Admins

DDM OS Reminder evaluates the most recent EnforcedInstallDate and setPastDuePaddedEnforcementDate entries in /var/log/install.log, then leverages a swiftDialog-enabled script and LaunchDaemon pair to dynamically deliver a more prominent end-user message of when the user’s Mac needs to be updated to comply with DDM-enforced macOS update deadlines:

Features

Mac Admins can configure daysBeforeDeadlineBlurscreen to control how many days before the DDM-specified deadline the screen blurs when displaying your customized message

Mac Admins can configure daysBeforeDeadlineBlurscreen to control how many days before the DDM-specified deadline the screen blurs when displaying your customized message:

  • Customizable: Easily customize the swiftDialog message’s title, message, icon, and button text to fit your organization’s needs by editing the provided DDM-OS-Reminder End-user Message.zsh script. (See Step A.)
  • Set-it-and-forget-it: Once installed, the LaunchDaemon executes your customized ddmOSReminder.zsh script, which automatically checks the installed version of macOS against the DDM-enforced macOS version twice daily and displays your customized message if an update is required. (See Step B.)
  • Deadline Awareness: Each time a DDM-enforced macOS version and deadline is updated via your MDM solution, the message will dynamically include an updated countdown to the deadline, creating a sense of urgency for end-users to update their Macs.
  • Tastefully Intrusive: The message is designed to be informative without being overly disruptive — first checking for the user’s Display Sleep Assertions — allowing users to continue their work while being reminded of the need to update.
  • Easy Installation: A new assembleDDMOSReminder.zsh script makes it easy to deploy via any MDM solution so you can quickly roll out across your entire organization.
  • Logging: The script logs its actions to your specified log file, allowing Mac Admins to monitor its activity and troubleshoot if necessary.

76-second Test-drive

76-second Test-drive Steps

Complete the following test-drive on a non-production Mac, previously configured with a pending Declarative Device Management-enforced macOS update; caveat emptor.

  1. Open Terminal and change to your Downloads directory
cd ~/Downloads
  1. Clone the DDM OS Reminder repository to your Downloads directory:
git clone https://github.com/dan-snelson/DDM-OS-Reminder.git
  1. Change to the DDM-OS-Reminder > Resources directory:
cd DDM-OS-Reminder/Resources
  1. Assemble the ddmOSReminder.zsh script:
zsh assembleDDMOSReminder.zsh
  1. Execute the assembled ddmOSReminder.zsh script (with elevated privileges)
sudo zsh ddmOSReminder.Assembled.*.zsh
  1. Review the client-side logs:
tail -f /var/log/org.churchofjesuschrist.log
  1. Kickstart the LaunchDaemon:
launchctl kickstart -kp system/org.churchofjesuschrist.dor

Implementation

A. Customize DDM-OS-Reminder End-user Message.zsh for your organization

If you didn’t take the 76-second Test-drive, please do so now to download the GitHub repository to your Downloads folder.

The GitHub repository includes a sample DDM-OS-Reminder End-user Message.zsh script which you can use to jump-start your user-facing messaging (and which you’ll later insert into the ddmOSReminder.zsh script, but not deploy to your endpoints; see Step B).

You’ll want to first update the Organization Variables for your environment:

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Organization Variables
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

# Script Human-readable Name
humanReadableScriptName="DDM OS Reminder End-user Message"

# Organization's Script Name
organizationScriptName="dorm"

# Organization's Days Before Deadline Blur Screen 
daysBeforeDeadlineBlurscreen="3"

Please note that when testing, the script includes logic in the installedOSvsDDMenforcedOS function to display only when a pending update is available and the recommendation is to test on a purposely outdated Mac (and to comment-out the script’s sleep "${sleepSeconds}").

Script output for a pending DDM macOS update
root@Dan ~ # zsh /Users/dan/Downloads/DDM-OS-Reminder\ End-user\ Message.zsh 
dorm (1.3.0): 2025-11-09 14:37:44 - [PRE-FLIGHT]      Created specified scriptLog: /var/log/org.churchofjesuschrist.log
dorm (1.3.0): 2025-11-09 14:37:44 - [PRE-FLIGHT]      

###
# DDM OS Reminder End-user Message (1.3.0)
# https://snelson.us
####

dorm (1.3.0): 2025-11-09 14:37:44 - [PRE-FLIGHT]      Initiating …
dorm (1.3.0): 2025-11-09 14:37:44 - [PRE-FLIGHT]      Complete
dorm (1.3.0): 2025-11-09 14:37:44 - [NOTICE]          Installed OS Version: 26.0.1
dorm (1.3.0): 2025-11-09 14:37:44 - [INFO]            DDM-enforced OS Version: 26.1
dorm (1.3.0): 2025-11-09 14:37:44 - [INFO]            DDM-enforced OS Version Deadline: 2025-11-14
dorm (1.3.0): 2025-11-09 14:37:44 - [NOTICE]          Update Required
dorm (1.3.0): 2025-11-09 14:37:44 - [NOTICE]          Login Trigger Pause: Random 30 to 90 seconds
dorm (1.3.0): 2025-11-09 14:37:44 - [INFO]            Pausing for 78 seconds …
dorm (1.3.0): 2025-11-09 14:39:02 - [NOTICE]          Downloading icon from 'https://ics.services.jamfcloud.com/icon/hash_4555d9dc8fecb4e2678faffa8bdcf43cba110e81950e07a4ce3695ec2d5579ee' …
dorm (1.3.0): 2025-11-09 14:39:03 - [NOTICE]          Display Dialog Window
dorm (1.3.0): 2025-11-09 14:39:15 - [INFO]            Return Code: 0
dorm (1.3.0): 2025-11-09 14:39:15 - [NOTICE]          User clicked Open Software Update
dorm (1.3.0): 2025-11-09 14:39:16 - [QUIT]            Exiting …
dorm (1.3.0): 2025-11-09 14:39:16 - [QUIT]            Shine on, you crazy diamond!
Script output on an up-to-date Mac
root@Dan ~ # zsh /Users/dan/Downloads/DDM-OS-Reminder\ End-user\ Message.zsh 
dorm (1.3.0): 2025-11-09 14:34:25 - [PRE-FLIGHT]      

###
# DDM OS Reminder End-user Message (1.3.0)
# https://snelson.us
####

dorm (1.3.0): 2025-11-09 14:34:25 - [PRE-FLIGHT]      Initiating …
dorm (1.3.0): 2025-11-09 14:34:25 - [PRE-FLIGHT]      Complete
dorm (1.3.0): 2025-11-09 14:34:25 - [NOTICE]          Installed OS Version: 26.1
dorm (1.3.0): 2025-11-09 14:34:26 - [INFO]            DDM-enforced OS Version: 26.1
dorm (1.3.0): 2025-11-09 14:34:26 - [NOTICE]          Up-to-date
B. Assemble ddmOSReminder.zsh for your organization

If you didn’t take the 76-second Test-drive, please do so now to download the GitHub repository to your Downloads folder.

The GitHub repository includes a new assembleDDMOSReminder.zsh script which combines the logic for creating the “set-in-and-forget-it” LaunchDaemon and your customized messaging (from Step A):

cd Resources
zsh assembleDDMOSReminder.zsh

Please remember to also update the Organization Variables for your environment in the assembled ddmOSReminder.zsh script:

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Organization Variables
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

# Organization's Reverse Domain Name Notation (i.e., com.company.division)
reverseDomainNameNotation="org.churchofjesuschrist"

# Script Human-readabale Name
humanReadableScriptName="DDM OS Reminder"

# Organization's Script Name
organizationScriptName="dor"

# Organization's Directory (i.e., where your client-side scripts reside)
organizationDirectory="/Library/Management/org.churchofjesuschrist"

# LaunchDaemon Name & Path
launchDaemonName="${reverseDomainNameNotation}.${organizationScriptName}.plist"
launchDaemonPath="/Library/LaunchDaemons/${launchDaemonName}"

You’ll also most likely want to adjust the LaunchDaemon’s StartCalendarInterval for your environment (and launchd.info may prove helpful).

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
#
# CREATE LAUNCHDAEMON
#
#   The following function creates the LaunchDaemon which executes the previously created
#   client-side DDM OS Reminder script.
#
#   We've elected to prompt our users twice a day (8 a.m. and 4 p.m.) to ensure they see the message.
#
#   NOTE: Leave a full return at the end of the content before the "ENDOFLAUNCHDAEMON" line.
#
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

function createLaunchDaemon() {

    notice "Create LaunchDaemon"

    logComment "Creating '${launchDaemonPath}' …"

(
cat <<ENDOFLAUNCHDAEMON
<?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>Label</key>
    <string>${launchDaemonLabel}</string>
    <key>UserName</key>
    <string>root</string>
    <key>ProgramArguments</key>
    <array>
        <string>/bin/zsh</string>
        <string>${organizationDirectory}/${organizationScriptName}.zsh</string>
    </array>
    <key>RunAtLoad</key>
    <true/>
    <key>EnvironmentVariables</key>
    <dict>
        <key>PATH</key>
        <string>/usr/bin:/bin:/usr/sbin:/sbin:/usr/local:/usr/local/bin</string>
    </dict>
    <key>StartCalendarInterval</key>
    <array>
        <dict>
            <key>Hour</key>
            <integer>8</integer>
            <key>Minute</key>
            <integer>0</integer>
        </dict>
        <dict>
            <key>Hour</key>
            <integer>16</integer>
            <key>Minute</key>
            <integer>0</integer>
        </dict>
    </array>
    <key>StandardErrorPath</key>
    <string>${scriptLog}</string>
    <key>StandardOutPath</key>
    <string>${scriptLog}</string>
</dict>
</plist>

ENDOFLAUNCHDAEMON
)  > "${launchDaemonPath}"

    logComment "Setting permissions for '${launchDaemonPath}' …"
    chmod 644 "${launchDaemonPath}"
    chown root:wheel "${launchDaemonPath}"

    logComment "Loading '${launchDaemonLabel}' …"
    launchctl bootstrap system "${launchDaemonPath}"
    launchctl start "${launchDaemonPath}"

}
C. Add your assembled ddmOSReminder.zsh script to your MDM server

When adding your assembled ddmOSReminder.zsh script to your Jamf Pro server, specify the following for Settings > Computer Management > Scripts > Options > Parameter Labels > Parameter 4

  • Parameter 4: Configuration Files to Reset (i.e., None (blank) | All | LaunchDaemon | Script | Uninstall )

For other MDMs, you can simply “hard-code” the desired setting for the resetConfiguration variable from the following available options:

  • None blank
  • All
  • LaunchDaemon
  • Script
  • Uninstall
# Parameter 4: Configuration Files to Reset (i.e., None (blank) | All | LaunchDaemon | Script | Uninstall )
resetConfiguration="${4:-"All"}"
D. Create a DDM OS Reminder MDM policy

Configure the following when creating the Jamf Pro policy to execute DDM OS Reminder:

  • Options
    • General
      • Trigger: Recurring Check-in
      • Execution Frequency: Once per computer
    • Scripts
      • DDM OS Reminder
  • Scope
    • Targets
      • All Computers
    • Limitations
      • No Limitations
    • Exclusions
      • No Exclusions
F. Add Extension Attributes (optional)

While the following Extension Attributes were created for and tested on Jamf Pro, they most likely can be adapted to other MDMs. (For adaptation assistance, help is available on the Mac Admins Slack #ddm-os-reminders channel, or you can open an issue.)

Resources

2025-11-09 02:53:37 dan clicked Remind Me Later
2025-11-09 02:55:28 dan clicked Open Software Update
2025-11-09 03:01:11 dan clicked Remind Me Later
2025-11-09 03:11:32 dan clicked Remind Me Later
2025-11-09 03:48:27 dan clicked KB0054571
2025-11-14 18:00:00
26.1
G. Upgrading
Screencast 02:00 (no audio; edited for time)

The following is one method of using Visual Studio Code to upgrade your customized script to the latest version.

  1. Download the latest assembled version from the Resources directory on GitHub
    • You may also wish to review the various branches for pre-release versions
  2. Make note of scriptVersion in the freshly downloaded script
  3. Using a backup of your current, known-working version, open both files in VS Code
  4. Click the Explorer button (Command-Shift-E)
    • If necessary, enable “Open Editors” in Explorer’s options
  5. Individually right-click the files listed in the Open Editors to begin the comparison
    • Select for Compare: The freshly downloaded script
    • Compare with Selected: Your duplicated, renamed, customized script
  6. Close the Explorer
  7. Review each difference — shown in red — then click the right-facing arrow to replace the code in your duplicated, renamed, customized script with the code from the freshly downloaded script
    • Notes:
      • You can safely ignore any LaunchDaemon-related differences
      • Enabling View > Word Wrap can interfere with code comparison
  8. Test
  9. Deploy

Support

Community-supplied, best-effort support is available on the Mac Admins Slack (free, registration required) #ddm-os-reminders channel, or you can open an issue.

Posted in Device Management, macOS, Scripts, swiftDialog, Tips & Tricks

Related Posts

1 Comment

  1. Pingback:DDM OS Reminder (1.2.0) - Dan K. Snelson

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.