A major update to Mac Admins’ favorite MDM-agnostic, “set-it-and-forget-it” reminder now adds multiple language support, significantly more robust reminder display logic and streamlined upgrade functionality
Overview
While Apple’s Declarative Device Management (DDM) provides Mac Admins with a powerful way to enforce macOS updates, its built-in notification is often too subtle for most administrators:
🆕 DDM OS Reminder now resolves DDM-enforced macOS update deadlines from recent /var/log/install.log activity using a declaration-aware resolver that prioritizes applicable enforced-install signals over generic matches, suppressing reminders when declaration state is missing, conflicting, invalid, or no longer maps to an available update, and only honors setPastDuePaddedEnforcementDate when it safely matches the resolved declaration, before using a swiftDialog-enabled script and LaunchDaemon to deliver a more prominent end-user reminder dialog.
Features
Customizable: Easily customize the reminder dialog’s title, message, icons and button text to fit your organization’s requirements by distributing a Configuration Profile via any MDM solution.
Easy Installation: The assemble.zsh script makes it easy to deploy your reminder dialog and display frequency customizations via any MDM solution, enabling quick rollout of DDM OS Reminder organization-wide.
Set-it-and-forget-it: Once configured and installed, a LaunchDaemon displays your customized reminder dialog — automatically checking the installed macOS version against the DDM-required version — to remind users if an update is required.
Deadline Awareness: Whenever a DDM-enforced macOS version or its deadline is updated via your MDM solution, the reminder dialog dynamically updates the countdown to both the deadline and required macOS version to drive timely compliance.
Intelligently Intrusive: The reminder dialog is designed to be informative without being disruptive. Before displaying, it checks for active display-sleep assertions from an allowlist of approved meeting apps, helping users stay productive while still being reminded to update.
Logging: The script logs its actions to your specified log file, allowing Mac Admins to monitor its activity and troubleshoot as necessary.
Demonstration Mode: A built-in demo mode allows Mac Admins to test the appearance and functionality of the reminder dialog with ease.
Configurable Post-Deadline Restart Policy: Choose whether past-deadline devices are left alone, prompted to restart, or forced to restart (Off, Prompt, Force) after your defined grace period, balancing user flexibility with reliable compliance.
🆕 Upgrade-friendly: assemble.zsh can now import supported settings from a previously generated DDM OS Reminder .plist, infer the RDNN and deployment lane (dev, test, prod), and generate a matched assembled script, organizational .plist, and unsigned .mobileconfig in a single pass. (See 4. Upgrading.)
🆕 Full Multi-language Experience: Version 3.0.0 fully supports English, German, French, Spanish, Portuguese, and Japanese across the reminder experience, with localized dialog content, support messaging, and human-readable deadline dates that automatically match the resolved language for a more polished, native-feeling user experience.
🆕 Localization Contributions
For additional language support, contributors only need to edit Resources/sample.plist. The runtime defaults and generated plist/mobileconfig output are derived from that localization surface.
Jumpstart your DDM OS Reminder implementation by first conducting a test deployment on a non-production Mac which has swiftDialog installed. (Ideally, use a non-production Mac which is already in-scope of a pending Declarative Device Management-enforced macOS update from your MDM server.)
Re-executezsh reminderDialog.zsh demo and confirm you now observe the word “Sample” in various places in the reminder dialog, as configured in sample.plist:
2. Basic Deployment
A basic deployment of DDM OS Reminder starts by specifying your organization’s Reverse Domain Name Notation value with the assemble.zsh script
Generate customized deployment artifacts for your organization by using the assemble.zshscript, specifying your organization’s Reverse Domain Name Notation (i.e., com.company) with interactive mode:
zsh assemble.zsh us.snelson --interactive
zsh assemble.zsh us.snelson --interactive
===============================================================
🧩 Assemble DDM OS Reminder (3.0.0)
===============================================================
Full Paths:
Reminder Dialog: ~/Downloads/DDM-OS-Reminder/reminderDialog.zsh
LaunchDaemon Management: ~/Downloads/DDM-OS-Reminder/launchDaemonManagement.zsh
Working Directory: ~/Downloads/DDM-OS-Reminder
Resources Directory: ~/Downloads/DDM-OS-Reminder/Resources
🔍 Checking Reverse Domain Name Notation …
Reminder Dialog (reminderDialog.zsh):
reverseDomainNameNotation = org.churchofjesuschrist
organizationScriptName = dorm
LaunchDaemon Management (launchDaemonManagement.zsh):
reverseDomainNameNotation = org.churchofjesuschrist
organizationScriptName = dor
📥 RDNN provided via command-line argument: 'us.snelson'
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Interactive Configuration
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Drag-and-drop an earlier DOR .plist to import [Return to skip] (or ‘X’ to exit):
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Using 'us.snelson' as the Reverse Domain Name Notation
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
IT Support, Branding & Restart Policy (Interactive)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Support Team Name [IT Support] (or ‘X’ to exit): Support
Support Team Phone [+1 (801) 555-1212] (or ‘X’ to exit): +1 (937) 555-1212
Support Team Email [rescue@snelson.us] (or ‘X’ to exit): support@snelson.us
Support Team Website [https://support.snelson.us] (or ‘X’ to exit):
Knowledge Base ('YES' to specify; 'NO' to hide) [YES] (or ‘X’ to exit): YES
Support KB Title [Update macOS on Mac] (or ‘X’ to exit): KB8675309
Info Button Action [https://support.snelson.us/KB8675309] (or ‘X’ to exit):
Support KB Markdown Link [[KB8675309](https://support.snelson.us/KB8675309)] (or ‘X’ to exit):
Overlay Icon URL (Light) [https://use2.ics.services.jamfcloud.com/icon/hash_2d64ce7f0042ad68234a2515211adb067ad6714703dd8ebd6f33c1ab30354b1d] (or ‘X’ to exit):
Overlay Icon URL (Dark) [https://use2.ics.services.jamfcloud.com/icon/hash_d3a3bc5e06d2db5f9697f9b4fa095bfecb2dc0d22c71aadea525eb38ff981d39] (or ‘X’ to exit):
Swap Overlay and Logo (YES/NO) [NO] (or ‘X’ to exit): NO
Past-deadline Restart Behavior (Off / [P]rompt / [F]orce) [Off] (or ‘X’ to exit): P
Days Past Deadline Before Restart Workflow (0-999) [2] (or ‘X’ to exit):
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Select Deployment Mode:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1) Development - Keep placeholder text for local testing
2) Testing - Replace placeholder text with 'TEST' for staging
3) Production - Remove placeholder text for clean deployment
[Press ‘X’ to exit]
Enter mode [1/2/3]: 2
📦 Deployment Mode: test
🔧 Inserting reminderDialog.zsh into launchDaemonManagement.zsh …
✅ Assembly complete [2026-03-29-163015]
→ Artifacts/ddm-os-reminder-assembled-2026-03-29-163015.zsh
🔁 Updating reverseDomainNameNotation to 'us.snelson' in assembled script …
🔍 Performing syntax check on 'Artifacts/ddm-os-reminder-assembled-2026-03-29-163015.zsh' …
✅ Syntax check passed.
🗂 Generating LaunchDaemon plist …
🗂 Creating us.snelson.dorm plist from ~/Downloads/DDM-OS-Reminder/Resources/sample.plist …
🔧 Updating internal plist content …
🧪 Testing mode: replacing placeholder text → 'TEST'
🔧 Applying IT support, branding and restart policy values …
→ Artifacts/us.snelson.dorm-2026-03-29-163015-test.plist
🧩 Generating Configuration Profile (.mobileconfig) …
→ Artifacts/us.snelson.dorm-2026-03-29-163015-test-unsigned.mobileconfig
🔍 Performing syntax check on 'Artifacts/us.snelson.dorm-2026-03-29-163015-test-unsigned.mobileconfig' …
✅ Profile syntax check passed.
🔁 Renaming assembled script …
🔁 Updating scriptLog path based on RDNN …
🏁 Done.
Deployment Artifacts:
Assembled Script: Artifacts/ddm-os-reminder-us.snelson-2026-03-29-163015-test.zsh
Organizational Plist: Artifacts/us.snelson.dorm-2026-03-29-163015-test.plist
Configuration Profile: Artifacts/us.snelson.dorm-2026-03-29-163015-test-unsigned.mobileconfig
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
⚠️ Important Next Steps:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Testing Artifacts Generated:
- All placeholder text replaced with 'TEST'
- Suitable for staging/QA environments
- NOT suitable for production use
===============================================================
Carefullyreview each deployment artifact and distribute the appropriate files to a single test Mac via your MDM.
Monitor the client-side log file, using the following as an example (substitute your organization’s Reverse Domain Name Notation and use Control-C to break):
tail -f /var/log/org.churchofjesuschrist.log
Kickstart the DDM OS Reminder LaunchDaemon, using the following as an example (substitute your organization’s Reverse Domain Name Notation):
In your preferred code editor — being careful to maintain the XML-escaped text — modify the various string values in your customized .plist or .mobileconfig to suit your organization:
Logging
ScriptLog
Reminder Timing
DaysBeforeDeadlineDisplayReminder
DaysBeforeDeadlineBlurscreen
DaysBeforeDeadlineHidingButton2
DaysOfExcessiveUptimeWarning
PastDeadlineRestartBehavior
DaysPastDeadlineRestartWorkflow
MinimumDiskFreePercentage
MeetingDelay
AcceptableAssertionApplicationNames
Branding
OrganizationOverlayIconURL
OrganizationOverlayIconURLdark
SwapOverlayAndLogo
Support
SupportTeamName
SupportTeamPhone
SupportTeamEmail
SupportTeamWebsite
SupportKB
InfoButtonAction
SupportKBURL
Dialog
Title
Button1Text
Button2Text (set to <string></string> to suppress displaying)
InfoButtonText (set to hide to supress displaying)
ExcessiveUptimeWarningMessage
DiskSpaceWarningMessage
Message
Infobox
InfoBox
Help
HelpMessage
HelpImage (set to hide to supress displaying)
Distribute the updated .plist or .mobileconfig to your test Mac
Kickstart the DDM OS Reminder LaunchDaemon, using the following as an example (substitute your organization’s Reverse Domain Name Notation):
Monitor the client-side log file, using the following as an example (substitute your organization’s Reverse Domain Name Notation and use Control-C to break):
tail -f /var/log/org.churchofjesuschrist.log
3. Advanced Deployment
An advanced deployment of DDM OS Reminder leverages a customized LaunchDaemon for fine-grained control of when the reminder dialog is displayed to your users
The default StartCalendarInterval is tuned for our environment: We’re expected to be at our desks between 8 a.m. to 5 p.m., with meetings starting at 9 a.m. and concluding by 4 p.m. So, users will be prompted once during the start of their work day and again at the close of their workday. (RunAtLoad is also set to true, so a restart may also trigger a reminder to be displayed.)
In your preferred code editor, modify the StartCalendarInterval in launchDaemonManagement.zsh to match your organization’s requirements; launchd.info is a great reference
Next, use your preferred code editor to modify the hard-coded random delay in reminderDialog.zsh to match the modifications you made in Step 1.
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# If Update Required, Display Dialog Window (respecting Display Reminder threshold)
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
if [[ "${versionComparisonResult}" == "Update Required" ]]; then
# -------------------------------------------------------------------------
# Deadline window and periodic reminder logic (thanks for the suggestion, @kristian!)
# -------------------------------------------------------------------------
quietPeriodSeconds=4560 # 76 minutes (60 minutes + margin)
periodicReminderDays=28 # 28 days
periodicReminderSeconds=$(( periodicReminderDays * 86400 ))
# Look for the most recent user interaction by Return Code
# Return Code 0: User clicked Button 1 (Open Software Update)
# Return Code 2: User clicked Button 2 (Remind Me Later)
# Return Code 3: User clicked Info Button
# Return Code 4: User allowed timer to expire
# Return Code 10: User quit dialog with keyboard shortcut
# These are the events that indicate the user consciously dismissed / acknowledged the dialog
lastInteraction=$(grep -E '\[INFO\].*Return Code: (0|2|3|4|10)' "${scriptLog}" | \
tail -1 | \
sed -E 's/^[^:]+: ([0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}).*/\1/')
if (( ddmVersionStringDaysRemaining > daysBeforeDeadlineDisplayReminder )); then
# Outside the deadline window; check if we should display initial/periodic reminder
if [[ -z "${lastInteraction}" ]]; then
# No interaction history; display the initial reminder dialog
notice "No reminder interaction history found; displaying initial reminder dialog"
else
# Validate the extracted timestamp matches expected format
if [[ "${lastInteraction}" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}\ [0-9]{2}:[0-9]{2}:[0-9]{2}$ ]]; then
nowEpoch=$(date +%s)
lastEpoch=$( date -j -f "%Y-%m-%d %H:%M:%S" "${lastInteraction}" +"%s" 2>/dev/null )
if [[ -n "${lastEpoch}" ]]; then
delta=$(( nowEpoch - lastEpoch ))
if (( delta >= periodicReminderSeconds )); then
# Last interaction was 28+ days ago; display periodic reminder
daysAgo=$(( delta / 86400 ))
notice "Last reminder interaction was ${daysAgo} day(s) ago; displaying periodic reminder dialog"
else
# Last interaction was within 28 days; skip
daysAgo=$(( delta / 86400 ))
quitOut "Deadline still ${ddmVersionStringDaysRemaining} days away and last reminder was ${daysAgo} day(s) ago; exiting quietly."
quitScript "0"
fi
else
info "Could not parse last interaction timestamp; proceeding with display"
fi
else
info "Last interaction timestamp format invalid; proceeding with display"
fi
fi
else
notice "Within ${daysBeforeDeadlineDisplayReminder}-day reminder window; proceeding …"
fi
# -------------------------------------------------------------------------
# Short quiet period: skip dialog if user interacted very recently
# -------------------------------------------------------------------------
if [[ -n "${lastInteraction}" ]]; then
# Validate the extracted timestamp matches expected format
if [[ "${lastInteraction}" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}\ [0-9]{2}:[0-9]{2}:[0-9]{2}$ ]]; then
nowEpoch=$(date +%s)
lastEpoch=$( date -j -f "%Y-%m-%d %H:%M:%S" "${lastInteraction}" +"%s" 2>/dev/null )
if [[ -n "${lastEpoch}" ]]; then
delta=$(( nowEpoch - lastEpoch ))
if (( delta < quietPeriodSeconds )); then
minutesAgo=$(( delta / 60 ))
quitOut "User last interacted with reminder dialog ${minutesAgo} minute(s) ago; exiting quietly."
quitScript "0"
fi
fi
fi
fi
# -------------------------------------------------------------------------
# Confirm the currently logged-in user is “available” to be reminded
# -------------------------------------------------------------------------
if [[ "${ddmVersionStringDaysRemaining}" -gt 1 ]]; then
if checkUserDisplaySleepAssertions; then
notice "No active Display Sleep Assertions detected; proceeding …"
else
quitOut "Presentation still active after ${meetingDelay} minutes; exiting quietly."
quitScript "0"
fi
else
info "Deadline is within 24 hours; ignoring ${loggedInUser}’s Display Sleep Assertions; proceeding …"
fi
# -------------------------------------------------------------------------
# Random pause depending on launch context (hourly vs login)
# -------------------------------------------------------------------------
currentHour=$(( $(date +%H) ))
currentMinute=$(( $(date +%M) ))
if (( currentHour == 8 || currentHour == 16 )) && (( currentMinute == 0 )); then
notice "Daily Trigger Pause: Random 0 to 20 minutes"
sleepSeconds=$(( RANDOM % 1200 ))
else
notice "Login Trigger Pause: Random 30 to 90 seconds"
sleepSeconds=$(( 30 + RANDOM % 61 ))
fi
if (( sleepSeconds >= 60 )); then
(( pauseMinutes = sleepSeconds / 60 ))
(( pauseSeconds = sleepSeconds % 60 ))
if (( pauseSeconds == 0 )); then
humanReadablePause="${pauseMinutes} minute(s)"
else
humanReadablePause="${pauseMinutes} minute(s), ${pauseSeconds} second(s)"
fi
else
humanReadablePause="${sleepSeconds} second(s)"
fi
info "Pausing for ${humanReadablePause} …"
sleep "${sleepSeconds}"
# -------------------------------------------------------------------------
# Continue with normal processing
# -------------------------------------------------------------------------
updateRequiredVariables
displayReminderDialog --ontop
else
notice "Version Comparison Result: ${versionComparisonResult}"
fi
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Exit
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
quitScript "0"
Execute the assemble.zsh script to insert your customized reminderDialog.zsh script into your customized launchDaemonManagement.zsh script and to create the deployment artifacts
Carefullyreview each deployment artifact and distribute the appropriate files to a single test Mac via your MDM
Kickstart the DDM OS Reminder LaunchDaemon
Monitor the client-side log file
4. Upgrading
2.2.0 (and later)
Version 3.0.0 of assemble.zsh can import supported settings from a previously generated DDM OS Reminder .plist, infer the RDNN and deployment lane (dev, test, prod), and generate a matched assembled script, organizational .plist, and unsigned .mobileconfig in a single pass.
zsh assemble.zsh drag-and-drop prior .plist
zsh assemble.zsh '/Users/dan/Downloads/DDM-OS-Reminder-2.2.0/Artifacts/us.snelson.dorm-2026-01-06-073608.plist'
===============================================================
🧩 Assemble DDM OS Reminder (3.0.0)
===============================================================
Full Paths:
Reminder Dialog: ~/Downloads/DDM-OS-Reminder-main/reminderDialog.zsh
LaunchDaemon Management: ~/Downloads/DDM-OS-Reminder-main/launchDaemonManagement.zsh
Working Directory: ~/Downloads/DDM-OS-Reminder-main
Resources Directory: ~/Downloads/DDM-OS-Reminder-main/Resources
🔍 Checking Reverse Domain Name Notation …
Reminder Dialog (reminderDialog.zsh):
reverseDomainNameNotation = org.churchofjesuschrist
organizationScriptName = dorm
LaunchDaemon Management (launchDaemonManagement.zsh):
reverseDomainNameNotation = org.churchofjesuschrist
organizationScriptName = dor
📥 Prior plist provided via command-line argument: '/Users/dan/Downloads/DDM-OS-Reminder-2.2.0/Artifacts/us.snelson.dorm-2026-01-06-073608.plist'
ℹ️ Importing supported values from: /Users/dan/Downloads/DDM-OS-Reminder-2.2.0/Artifacts/us.snelson.dorm-2026-01-06-073608.plist
🔎 Inferred RDNN from prior plist: 'us.snelson'
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Using 'us.snelson' as the Reverse Domain Name Notation
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Interactive Configuration
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
ℹ️ Prior plist supplied; skipping IT support, branding and restart policy prompts.
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Select Deployment Mode:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1) Development - Keep placeholder text for local testing
2) Testing - Replace placeholder text with 'TEST' for staging
3) Production - Remove placeholder text for clean deployment
[Press ‘X’ to exit]
Enter mode [1/2/3]: 3
📦 Deployment Mode: prod
🔧 Inserting reminderDialog.zsh into launchDaemonManagement.zsh …
✅ Assembly complete [2026-03-28-151200]
→ Artifacts/ddm-os-reminder-assembled-2026-03-28-151200.zsh
🔁 Updating reverseDomainNameNotation to 'us.snelson' in assembled script …
🔍 Performing syntax check on 'Artifacts/ddm-os-reminder-assembled-2026-03-28-151200.zsh' …
✅ Syntax check passed.
🗂 Generating LaunchDaemon plist …
🗂 Creating us.snelson.dorm plist from /Users/dan/Documents/GitHub/dan-snelson/DDM-OS-Reminder/Resources/sample.plist …
🔧 Updating internal plist content …
🔓 Production mode: removing placeholder text for clean deployment
🔧 Importing supported values from prior plist …
ℹ️ Preserving imported ScriptLog: /var/log/us.snelson.log
→ Artifacts/us.snelson.dorm-2026-03-28-151200-prod.plist
🧩 Generating Configuration Profile (.mobileconfig) …
→ Artifacts/us.snelson.dorm-2026-03-28-151200-prod-unsigned.mobileconfig
🔍 Performing syntax check on 'Artifacts/us.snelson.dorm-2026-03-28-151200-prod-unsigned.mobileconfig' …
✅ Profile syntax check passed.
🔁 Renaming assembled script …
🔁 Updating scriptLog path based on RDNN …
🏁 Done.
Deployment Artifacts:
Assembled Script: Artifacts/ddm-os-reminder-us.snelson-2026-03-28-151200-prod.zsh
Organizational Plist: Artifacts/us.snelson.dorm-2026-03-28-151200-prod.plist
Configuration Profile: Artifacts/us.snelson.dorm-2026-03-28-151200-prod-unsigned.mobileconfig
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
⚠️ Important Next Steps:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Production Artifacts Generated:
- All placeholder text removed (clean output)
- Supported configuration values imported from prior plist
- Prior plist: /Users/dan/Downloads/DDM-OS-Reminder-2.2.0/Artifacts/us.snelson.dorm-2026-01-06-073608.plist
- ScriptLog resolved to '/var/log/us.snelson.log'
Recommended review items:
- Support team name, phone, email, website
- Imported ScriptLog path and any carried-forward KB/help visibility
- Organization overlay icon URLs
- Button labels and dialog messages
Files to review:
- Artifacts/us.snelson.dorm-2026-03-28-151200-prod.plist
- Artifacts/us.snelson.dorm-2026-03-28-151200-prod-unsigned.mobileconfig
===============================================================
As always, carefullyreview each deployment artifact and distribute the appropriate files to a single test Mac via your MDM. (See Steps 2.2 through 2.4.)
2.1.0 (and earlier)
Author’s Highly Opinionated Thought: There are enough changes in version 2.1.0 and prior that it’s probably easier to just start-from-scratch — optionally uninstalling earlier versions — rather than attempting to update from a previous version of DDM OS Reminder.
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.
Download the latest assembled version from the Resources directory on GitHub
You may also wish to review the various branches for pre-release versions
Make note of scriptVersion in the freshly downloaded script
Using a backup of your current, known-working version, open both files in VS Code
Click the Explorer button (Command-Shift-E)
If necessary, enable “Open Editors” in Explorer’s options
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
Close the Explorer
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
Test
Deploy
5. Resources
5.1 AI-generated Documentation
AI-generated documentation is available in the GitHub repository
Diagrams: Comprehensive, AI-generated visual diagrams to augment this documentation
Executive Overview: High-level lifecycle view for Mac Admins who need the big picture first
System Architecture: Complete ecosystem overview from development through runtime execution
The following have proved helpful during development and testing
XTRACE
Execute the client-side reminder dialog script — under xtrace with a custom prompt — using the following as an example, substituting your organization’s Reverse Domain Name Notation:
Note: Once the reminder dialog appears, the last several output blocks tend to be the most informative
Conduct the following troubleshooting steps in an elevated Terminal session, on a test Mac in-scope of a pending Declarative Device Management-enforced macOS update from your MDM server.)
System Settings > General > Device Management > MDM Profile >Device Declarations
1. Core Functionality
Define a rdnn variable to aid in completing the following steps:
rdnn="org.churchofjesuschrist"
Review the client-side LaunchDaemon for any obvious issues:
Review the /var/log/install.log for relevant DDM and Software Update entries:
Space : next page
Arrow keys : scroll horizontally or vertically
q : quit
tail -n 1000 /var/log/install.log | grep -nE 'declarationFromKeys\]: Falling back to default applicable declaration|Found DDM enforced install \(|EnforcedInstallDate:|requestedPMV=|MADownloadNoMatchFound|pallasNoPMVMatchFound=true|No available updates found\. Please try again later\.|setPastDuePaddedEnforcementDate|Removed [0-9]+ invalid declarations' | less -S
To more easily validate the 3.1.0 DDM declaration parsing logic, run the following Jamf Pro Extension Attribute scripts locally and confirm the reported date and version match the pending DDM-enforced macOS update from your MDM server:
When troubleshooting user-interface issues, leverage the dialog binary and your client-side managed preferences .plist values.
2. User-interface Issues
Confirm dialog is installed and working as expected:
dialog --title "Dialog Test" --message "swiftDialog is installed and can render a basic dialog." --icon /System/Library/CoreServices/Finder.app --infotext
Define a rdnn variable to aid in completing the following steps:
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.