Menu Close

DDM OS Reminder (5.0.0)

A security-hardened upgrade to Mac Admins’ new favorite “set-it-and-forget-it” DDM macOS update reminder, featuring corroborated DDM declaration validation and “Update Tonight” awareness

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 intelligently resolves DDM-enforced macOS update deadlines from recent /var/log/install.log activity, while using a declaration-aware resolver which prioritizes applicable enforced-install signals. Normal DDM resolution always runs first, and a confirmed declaration wins; when the root-owned SoftwareUpdateDDMStatePersistence.plist is readable, install.log declarations must also match an active declaration persisted by softwareupdated. When resolution returns missing, conflict, noMatch, or invalidVersion, an optional, validated DDM Emergency Fallback requirement can keep reminder workflows active; absent or invalid fallback data preserves fail-safe suppression, and unknown resolver states always fail closed. Failed stale SoftwareUpdateSubscriber attempts are ignored, and enforcement timestamps with full timezone offsets such as +05:30 are accepted before using a swiftDialog-enabled script and LaunchDaemon to deliver a more prominent end-user reminder dialog.

🆕 5.0.0 Highlights

  • Corroborated DDM declarations: When the root-owned /var/db/softwareupdate/SoftwareUpdateDDMStatePersistence.plist is readable, install.log declaration candidates must match an active softwareupdated declaration (TargetOSVersion + TargetLocalDateTime); unmatched candidates are ignored and logged at [WARNING]. Runtime and both Pending OS Update Extension Attributes share this rule; a missing or unrecognized plist keeps install.log-only behavior and logs a [WARNING] (see SECURITY.md).
  • Update Tonight awareness: Confirmed same-day Update Tonight scheduling for the target version pauses normal reminders until local midnight. Evidence must be logged by softwareupdated; pre-deadline thresholds, aggressive mode, and Force mode still apply. (Issue #133)
  • Private runtime files and atomic deployment: Each run keeps downloaded icons, the swiftDialog command file, and threshold markers in its own mktemp -d directory under /var/tmp, removed on exit. dor.zsh and dor-starter.zsh are syntax-checked before atomic replacement, so the heartbeat never launches a partially written script.
  • App-bundle swiftDialog: swiftDialog runs from /Library/Application Support/Dialog/Dialog.app/Contents/MacOS/dialogcli, and /usr/local is no longer in the root PATH.
  • Safer user-session actions: Info button URLs and System Settings activation open in the console user’s session through launchctl asuser, the System Settings wait is capped at 30 seconds, and Setup Assistant (_mbsetupuser) is never targeted.
  • Support kill switch persists: All and Script redeployments keep /Library/Management/<rdnn>/dor-aggressive-kill; only Uninstall removes it.
  • Resilient preference parsing: Mixed valid and invalid DailyReminderTimes or MinutesBeforeDeadlineReminderSchedule entries keep the valid values and log each skipped entry; unrecognized language codes fall back to English. (Issue #139; thanks, @TechTrekkie!)
  • Hardened tooling: assemble.zsh rejects unsafe imported ScriptLog paths and re-prompts on an invalid deployment mode, self-extracting wrappers forward MDM Parameters 4–6, and Resources/Jamf-getDDMstatusFromCSV.zsh keeps API credentials out of process arguments.
  • Includes 4.2.1 and 4.2.2 fixes: HideSupportAssistanceMessage alone controls the support assistance text while InfoButtonText=hide now hides only the info button (if you relied on InfoButtonText=hide to also hide the support assistance text, set HideSupportAssistanceMessage to true), and recovered DDM declarations no longer remain stuck in conflict.

Review the 5.0.0 upgrade notes before deploying to production.

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 heartbeat LaunchDaemon plus lightweight dor-starter.zsh honors your configured DailyReminderTimes baseline schedule and displays your customized reminder dialog only when a reminder is actually due.
  • 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.
  • 🆕 DDM Emergency Fallback: Jamf Pro Script Parameters 5 and 6 can persist an emergency version/deadline requirement at /Library/Management/<rdnn>/dor-fallback-declaration.plist. Runtime can select it after recognized unresolved DDM states; confirmed DDM always wins.
  • 🆕 Update Tonight awareness: When a user schedules the required macOS update with Update Tonight and install.log confirms that softwareupdated queued the target version and armed the overnight scheduler, normal reminders pause until local midnight. Pre-deadline thresholds, aggressive mode, and Force mode still apply, and suppression never applies on deadline day.
  • 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 3. Upgrading.)
  • Full Multi-language Experience: Beginning with version 3.1.0, English dialog defaults are provided in-script, with .plist support for: German, French, Spanish, Italian, Dutch, Portuguese, and Japanese. Additional languages are supported through *Localized_<code> preference keys, with locale-aware dialog content, support messaging, human-readable deadline dates, and past-deadline restart copy that match the resolved language.
  • Granular Control for Displaying IT Support Information: New HideSupport* preferences allow Mac Admins to easily choose which IT Support fields are displayed to their end-users.
  • Use reminderDialogPreferenceTest.zsh when you want to easily validate dialog copy, localization, branding, support contact details, button visibility, and infobox rendering from deployed preferences without waiting for an actual DDM deadline.
  • Lean Artifact Options: Keep the full localization surface, generate a minimal artifact (--minimal = base keys + exact _Localized_en keys only), or retain only selected language families with --languages <csv>. In assemble.zsh --interactive, same choice appears as Full, Minimal, or Selected languages.

Implementation

1. Local Testing

Jumpstart your DDM OS Reminder implementation by first conducting a local test 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.

  1. Visit the DDM OS Reminder repository on GitHub
  2. Download the main branch by selecting Code > Download ZIP
Visit the DDM OS Reminder repository on GitHub and download the main branch by selecting Code > Download ZIP
  1. In an elevated Terminal session, change to the downloaded DDM-OS-Reminder-main directory
cd ~/Downloads/DDM-OS-Reminder-main
  1. Execute the reminderDialog.zsh script in demo mode:
zsh reminderDialog.zsh demo
Execute the reminderDialog.zsh script in demo mode: zsh reminderDialog.zsh demo
zsh reminderDialog.zsh demo prompt # Prompts user for restart confirmation
zsh reminderDialog.zsh demo prompt # Prompts user for restart confirmation
Are you sure you want to restart your computer now?
zsh reminderDialog.zsh demo force # Forces restart; use with caution
zsh reminderDialog.zsh demo force # Forces restart; use with caution
  1. Review the reminder dialog and interact with each of its buttons, re-executing zsh reminderDialog.zsh demo as required
  2. Review the script’s output:
root@XDT8675309 ~ # cd /Users/dan/Downloads/DDM-OS-Reminder-main 
root@XDT8675309 DDM-OS-Reminder-main # zsh reminderDialog.zsh demo
dorm (5.0.0): 2026-10-07 04:47:34 - [PRE-FLIGHT]      Created specified scriptLog: /var/log/org.churchofjesuschrist.log
dorm (5.0.0): 2026-10-07 04:47:35 - [PRE-FLIGHT]      

###
# DDM OS Reminder End-user Message (5.0.0)
# http://snelson.us/ddm
###

dorm (5.0.0): 2026-10-07 04:47:35 - [PRE-FLIGHT]      Initiating …
dorm (5.0.0): 2026-10-07 04:47:35 - [PRE-FLIGHT]      Check for Logged-in System Accounts …
dorm (5.0.0): 2026-10-07 04:47:35 - [PRE-FLIGHT]      Current Logged-in User: dan
dorm (5.0.0): 2026-10-07 04:47:35 - [PRE-FLIGHT]      Current Logged-in User First Name (ID): Dan (502)
dorm (5.0.0): 2026-10-07 04:47:35 - [PRE-FLIGHT]      No client-side preferences found; using script-defined defaults
dorm (5.0.0): 2026-10-07 04:47:35 - [NOTICE]          Resolved DailyReminderTimes: 08:00,12:00,16:00
dorm (5.0.0): 2026-10-07 04:47:35 - [NOTICE]          Resolved MinutesBeforeDeadlineReminderSchedule: 45,30,15,10,5
dorm (5.0.0): 2026-10-07 04:47:35 - [NOTICE]          Detected logged-in user language 'en-US'; using 'en'
dorm (5.0.0): 2026-10-07 04:47:35 - [PRE-FLIGHT]      Complete
dorm (5.0.0): 2026-10-07 04:47:35 - [NOTICE]          Demo mode enabled
dorm (5.0.0): 2026-10-07 04:47:35 - [NOTICE]          Check dan’s Display Sleep Assertions
dorm (5.0.0): 2026-10-07 04:47:35 - [INFO]            Acceptable assertion application names (allowlist): MSTeams zoom.us Webex
dorm (5.0.0): 2026-10-07 04:47:35 - [INFO]            dan’s Display Sleep Assertion has ended after 0 minute(s).
dorm (5.0.0): 2026-10-07 04:47:35 - [NOTICE]          No active Display Sleep Assertions detected; proceeding …
dorm (5.0.0): 2026-10-07 04:47:35 - [INFO]            Using private dialog runtime directory '/var/tmp/dorm.HAJbee'
dorm (5.0.0): 2026-10-07 04:47:35 - [NOTICE]          Dark mode detected; using dark mode overlay icon
dorm (5.0.0): 2026-10-07 04:47:35 - [NOTICE]          Processing overlay icon from 'https://use2.ics.services.jamfcloud.com/icon/hash_d3a3bc5e06d2db5f9697f9b4fa095bfecb2dc0d22c71aadea525eb38ff981d39'
dorm (5.0.0): 2026-10-07 04:47:35 - [INFO]            Overlay icon appears to be a remote URL; downloading with curl
dorm (5.0.0): 2026-10-07 04:47:36 - [INFO]            Successfully downloaded overlay icon
dorm (5.0.0): 2026-10-07 04:47:36 - [NOTICE]          Detected logged-in user language 'en-US'; using 'en'
dorm (5.0.0): 2026-10-07 04:47:38 - [INFO]            swiftDialog 3.1.1.4997 supports markdown color; rendering enforcement sentence in red.
dorm (5.0.0): 2026-10-07 04:47:38 - [NOTICE]          Display Reminder Dialog to dan with additional options: --ontop
dorm (5.0.0): 2026-10-07 04:47:40 - [INFO]            Return Code: 0
dorm (5.0.0): 2026-10-07 04:47:40 - [NOTICE]          dan clicked Open Software Update
dorm (5.0.0): 2026-10-07 04:47:40 - [NOTICE]          Software Update handoff: requirementSource=unknown; normalResolverStatus=unknown; target=27.99; deadline=2026-10-10; action=x-apple.systempreferences:com.apple.preferences.softwareupdate
dorm (5.0.0): 2026-10-07 04:47:40 - [NOTICE]          Checking if System Settings is open …
dorm (5.0.0): 2026-10-07 04:47:40 - [INFO]            Telling System Settings to make a guest appearance …
dorm (5.0.0): 2026-10-07 04:47:40 - [QUIT]            Exiting …
dorm (5.0.0): 2026-10-07 04:47:40 - [QUIT]            Ticking away the moments that make up a dull day …
  1. Simulate the installation of a client-side .plist by manually copying sample.plist to one of its expected locations with its expected filename:
cp -v Resources/sample.plist /Library/Preferences/org.churchofjesuschrist.dorm.plist
  1.  Re-execute zsh reminderDialog.zsh demo and confirm you now observe the word “Sample” in various places in the reminder dialog, as configured in sample.plist:
Re-execute zsh reminderDialog.zsh demo and confirm you now observe the word "Sample" in various places in the reminder dialog, as configured in sample.plist
2. MDM Deployment

The included assemble.zsh script makes MDM deployment easy by generating organization-specific deployment artifacts

Special thanks to Jon Brown for his Jamf-specific write-up: Deploying DDM OS Reminder 4.0.0 in Jamf

  1. Generate customized deployment artifacts for your organization by using the assemble.zsh script, specifying your organization’s Reverse Domain Name Notation (i.e., us.snelson) with --interactive mode:
zsh assemble.zsh us.snelson --interactive
root@XDT8675309 DDM-OS-Reminder-main # zsh assemble.zsh us.snelson --interactive

===============================================================
🧩 Assemble DDM OS Reminder (5.0.0)
===============================================================

📍 Full Paths:

        Reminder Dialog: /Users/dan/Downloads/DDM-OS-Reminder-main/reminderDialog.zsh
LaunchDaemon Management: /Users/dan/Downloads/DDM-OS-Reminder-main/launchDaemonManagement.zsh
      Working Directory: /Users/dan/Downloads/DDM-OS-Reminder-main
    Resources Directory: /Users/dan/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


📥 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 & Aggressive Mode 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
Hide Support Team Phone (YES/NO) [NO] (or ‘X’ to exit): 
Support Team Email [rescue@snelson.us] (or ‘X’ to exit): 
Hide Support Team Email (YES/NO) [NO] (or ‘X’ to exit): 
Support Team Website [https://support.snelson.us] (or ‘X’ to exit): 
Hide Support Team Website (YES/NO) [NO] (or ‘X’ to exit): 
Info Button ('YES' to specify; 'NO' to hide) [YES] (or ‘X’ to exit): 
Info Button Text [Update macOS on Mac] (or ‘X’ to exit): 
Info Button Action [https://support.snelson.us] (or ‘X’ to exit): 
Knowledge Base Row ('YES' to specify; 'NO' to hide) [YES] (or ‘X’ to exit): 
Support KB Title [Update macOS on Mac] (or ‘X’ to exit): KB8675309
Support KB Markdown Link [[KB8675309](https://support.snelson.us)] (or ‘X’ to exit): 
Hide Support Assistance Message (YES/NO) [NO] (or ‘X’ to exit): 
Overlay Icon URL (Light) [https://usw2.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): 
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): 
Aggressive Mode Past Deadline Hours (0-999; use 720 to effectively suppress) [2] (or ‘X’ to exit): 
Aggressive Mode Frequency Minutes (1-999) [20] (or ‘X’ to exit): 17
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
🌐 Localization Artifact Mode
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Localization Output (Full / [M]inimal / [S]elected languages) [Full] (or ‘X’ to exit): M

ℹ️  Localization mode set to: minimal localization surface (base keys + English localized keys)

🌐 Artifact Localization: minimal localization surface (base keys + English localized keys)


━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
🚦 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-10-07-051158]
   → Artifacts/ddm-os-reminder-assembled-2026-10-07-051158.zsh

🔁 Updating reverseDomainNameNotation to 'us.snelson' in assembled script …

🔍 Performing syntax check on 'Artifacts/ddm-os-reminder-assembled-2026-10-07-051158.zsh' …
    ✅ Syntax check passed.

🗂  Generating LaunchDaemon plist …
    🗂  Creating us.snelson.dorm plist from /Users/dan/Downloads/DDM-OS-Reminder-main/Resources/sample.plist …

    🔧 Updating internal plist content …
    🔓 Production mode: removing placeholder text for clean deployment
    🔧 Applying IT support, branding, restart and aggressive mode values …
    🌐 Removed 249 localized key(s) from generated plist
   → Artifacts/us.snelson.dorm-2026-10-07-051158-prod.plist

🧩 Generating Configuration Profile (.mobileconfig) …
   → Artifacts/us.snelson.dorm-2026-10-07-051158-prod-unsigned.mobileconfig

🔍 Performing syntax check on 'Artifacts/us.snelson.dorm-2026-10-07-051158-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-10-07-051158-prod.zsh
    Organizational Plist: Artifacts/us.snelson.dorm-2026-10-07-051158-prod.plist
   Configuration Profile: Artifacts/us.snelson.dorm-2026-10-07-051158-prod-unsigned.mobileconfig
  Deployed Runtime Assets: /Library/Management/<RDNN>/dor-starter.zsh, dor-state.plist, dor.pid
 Optional Fallback Config: /Library/Management/<RDNN>/dor-fallback-declaration.plist

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
⚠️  Important Next Steps:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  Production Artifacts Generated:
    - All placeholder text removed (clean output)
    - Localization artifact mode: minimal localization surface (base keys + English localized keys)
    - IT support, branding, restart and aggressive mode values applied from prompts
    - Past-deadline restart policy set to 'Prompt'
    - Aggressive mode begins 2 hour(s) past deadline and repeats every 17 minute(s)
    - Restart workflow begins 2 day(s) past deadline

  Recommended review items:
    - Support team name, phone, email, website
    - DailyReminderTimes baseline schedule
    - AggressiveModePastDeadlineHours and AggressiveModeFrequencyMinutes
    - Localization key set matches intended artifact mode
    - Support KB title/link and Info button URL
    - Organization overlay icon URLs
    - Button labels and dialog messages

  Files to review:
    - Artifacts/us.snelson.dorm-2026-10-07-051158-prod.plist
    - Artifacts/us.snelson.dorm-2026-10-07-051158-prod-unsigned.mobileconfig

===============================================================
  1. Carefully review deployment artifacts and distribute the appropriate files to a single test Mac via your MDM:
    • Either the .mobileconfig or the .plist (as a Configuration Profile)
    • The assembled .zsh script (install only once)
  1. After the assembled script and configuration profile have installed on the test Mac, use Resources/monitorRemoteSession.zsh during a remote Terminal session when you need one view of the heartbeat daemon, deployed runtime files, dor-state.plist, dor.pid, matching processes, aggressive-mode kill switch, and recent log entries. Use --watch 5 while testing or immediately after a kickstart:
rdnn="us.snelson"

zsh Resources/monitorRemoteSession.zsh --rdnn "${rdnn}" --watch 5
  1. In a second, elevated Terminal window, kickstart the DDM OS Reminder heartbeat LaunchDaemon when you want to force an immediate starter evaluation. Since version 4.0.0, kickstart may exit quietly if NextScheduledReminder is FALSE or future-dated (beginning with 5.0.0, Update Tonight suppression future-dates it to the first post-midnight slot and records UpdateTonightSuppressionUntil in dor-state.plist); check monitorRemoteSession.zsh before treating a missing dialog as a failure:
rdnn="us.snelson"

launchctl kickstart -kp system/"${rdnn}".dor
  1. Use Resources/reminderDialogPreferenceTest.zsh when you want to validate dialog copy, localization, branding, support contact details, button visibility, and infobox rendering from deployed preferences. Use monitorRemoteSession.zsh for runtime heartbeat, scheduler, PID, process, kill-switch, and log troubleshooting:
rdnn="us.snelson"

zsh Resources/reminderDialogPreferenceTest.zsh --rdnn "${rdnn}"
Use reminderDialogPreferenceTest.zsh when you want to validate dialog copy, localization, branding, support contact details, button visibility, and infobox rendering from deployed preferences without waiting for a real DDM deadline.
3. Upgrading
5.0.0 Upgrade Notes

Review the following before rolling out version 5.0.0; no preference keys, defaults, or precedence rules changed.

  • Self-extracting wrapper now forwards MDM script parameters: Wrappers generated by Resources/createSelfExtracting.zsh (2.4.0) pass Parameters 4–6 to the deployer. Values previously ignored on wrapper-based policies (reset mode, Uninstall, DDM Emergency Fallback) now take effect, so audit those policy parameters before rollout.
  • swiftDialog path: The runtime and deployer invoke /Library/Application Support/Dialog/Dialog.app/Contents/MacOS/dialogcli; the runtime, deployer, dor-starter.zsh, and LaunchDaemon no longer include /usr/local or /usr/local/bin in PATH.
  • Aggressive-mode kill switch survives redeployment: All and Script keep /Library/Management/<rdnn>/dor-aggressive-kill; only Uninstall removes it.
  • Support assistance text (from 4.2.1): InfoButtonText=hide no longer hides {supportAssistanceMessage}; set HideSupportAssistanceMessage=true if you relied on the old behavior. Version 5.0.0 is the first stable release with this change.
2.2.0 (and later)

Version 3.0.0 (and later) 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
root@XDT8675309 DDM-OS-Reminder-main # zsh assemble.zsh /Users/dan/Downloads/DDM-OS-Reminder-2.2.0/Artifacts/org.churchofjesuschrist.dorm-2026-01-06-073608.plist

===============================================================
🧩 Assemble DDM OS Reminder (5.0.0)
===============================================================

📍 Full Paths:

        Reminder Dialog: /Users/dan/Downloads/DDM-OS-Reminder-main/reminderDialog.zsh
LaunchDaemon Management: /Users/dan/Downloads/DDM-OS-Reminder-main/launchDaemonManagement.zsh
      Working Directory: /Users/dan/Downloads/DDM-OS-Reminder-main
    Resources Directory: /Users/dan/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/org.churchofjesuschrist.dorm-2026-01-06-073608.plist'

ℹ️  Importing supported values from: /Users/dan/Downloads/DDM-OS-Reminder-2.2.0/Artifacts/org.churchofjesuschrist.dorm-2026-01-06-073608.plist
🔎 Inferred RDNN from prior plist: 'org.churchofjesuschrist'

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
🏷️  Using 'org.churchofjesuschrist' as the Reverse Domain Name Notation
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━


ℹ️  Prior plist supplied; skipping IT support, branding and restart policy prompts.


━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
🛠️  Interactive Configuration
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
🌐 Localization Artifact Mode
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Localization Output (Full / [M]inimal / [S]elected languages) [Full] (or ‘X’ to exit): m

ℹ️  Localization mode set to: minimal localization surface (base keys + English localized keys)

🌐 Artifact Localization: minimal localization surface (base keys + English localized keys)


━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
🚦 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-10-07-051723]
   → Artifacts/ddm-os-reminder-assembled-2026-10-07-051723.zsh

🔁 Updating reverseDomainNameNotation to 'org.churchofjesuschrist' in assembled script …

🔍 Performing syntax check on 'Artifacts/ddm-os-reminder-assembled-2026-10-07-051723.zsh' …
    ✅ Syntax check passed.

🗂  Generating LaunchDaemon plist …
    🗂  Creating org.churchofjesuschrist.dorm plist from /Users/dan/Downloads/DDM-OS-Reminder-main/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/org.churchofjesuschrist.log
    🌐 Removed 249 localized key(s) from generated plist
   → Artifacts/org.churchofjesuschrist.dorm-2026-10-07-051723-prod.plist

🧩 Generating Configuration Profile (.mobileconfig) …
   → Artifacts/org.churchofjesuschrist.dorm-2026-10-07-051723-prod-unsigned.mobileconfig

🔍 Performing syntax check on 'Artifacts/org.churchofjesuschrist.dorm-2026-10-07-051723-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-org.churchofjesuschrist-2026-10-07-051723-prod.zsh
    Organizational Plist: Artifacts/org.churchofjesuschrist.dorm-2026-10-07-051723-prod.plist
   Configuration Profile: Artifacts/org.churchofjesuschrist.dorm-2026-10-07-051723-prod-unsigned.mobileconfig
  Deployed Runtime Assets: /Library/Management/<RDNN>/dor-starter.zsh, dor-state.plist, dor.pid

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
⚠️  Important Next Steps:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  Production Artifacts Generated:
    - All placeholder text removed (clean output)
    - Localization artifact mode: minimal localization surface (base keys + English localized keys)
    - Supported configuration values imported from prior plist
    - Prior plist: /Users/dan/Downloads/DDM-OS-Reminder-2.2.0/Artifacts/org.churchofjesuschrist.dorm-2026-01-06-073608.plist
    - ScriptLog resolved to '/var/log/org.churchofjesuschrist.log'

  Recommended review items:
    - Support team name, phone, email, website
    - DailyReminderTimes baseline schedule
    - AggressiveModePastDeadlineHours and AggressiveModeFrequencyMinutes
    - Localization key set matches intended artifact mode
    - Imported ScriptLog path and any carried-forward KB/help visibility
    - Organization overlay icon URLs
    - Button labels and dialog messages

  Files to review:
    - Artifacts/org.churchofjesuschrist.dorm-2026-10-07-051723-prod.plist
    - Artifacts/org.churchofjesuschrist.dorm-2026-10-07-051723-prod-unsigned.mobileconfig

===============================================================

As always, carefully review 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 since version 2.1.0 (and earlier) that it’s best to just start-from-scratch — first uninstalling earlier versions — rather than attempting to upgrade to the latest version.

4. Resources
4.1 AI-generated Documentation

AI-generated documentation is available in the GitHub repository

Diagrams: Comprehensive, AI-generated visual diagrams to augment this documentation

  1. Executive Overview: High-level lifecycle view for Mac Admins who need the big picture first
  2. System Architecture: Complete ecosystem overview from development through runtime execution
  3. Runtime Decision Tree: Complete decision logic executed each time the LaunchDaemon triggers
  4. Deadline Timeline: Visual representation of how user experience changes as deadline approaches
  5. Deployment Workflow: Step-by-step guide for administrators deploying DDM OS Reminder
  6. Configuration Hierarchy: 3-tier preference system showing precedence rules
  7. Configuration Reference: Complete reference for all configurable preferences
4.2 Scripts

A number of Mac Admin quality-of-life scripts are available in the GitHub repository

Scripts

  1. The assemble.zsh script creates combined, deployable artifacts of your customized scripts
  2. Create Self-extracting encodes the most recently assembled script for easier deployment with some MDMs; version 2.4.0 extracts into a private mktemp -d directory and forwards MDM Script Parameters 4–6 (real-world testing and feedback welcome)
  3. Create .plist creates .plist and .mobileconfig, based on your customizations to reminderDialog.zsh (real-world testing and feedback welcome)
  4. Extension Attributes were created for and tested on Jamf Pro and can most likely be adapted for other MDMs
  5. Jamf-getDDMstatusFromCSV.zsh (version 1.4.0 keeps API credentials and bearer tokens out of process arguments); See: DDM Status from .CSV (1.3.0)
4.3 Configuration Profile

Special thanks to Max Sundell for his write-up

Create and deploy a macOS configuration profile (.mobileconfig)

4.4 Testing Tips

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
zsh -c 'PS4=" → "; zsh -x "$1"' -- /Library/Management/org.churchofjesuschrist/dor.zsh

Force-display

The following commands can be used to manually execute the deployed reminder runtime and force-display the reminder dialog on Macs with a pending update and valid DDM enforcement state.

###
# Force-display Reminder Dialog
#
# Note: Works only on Macs with pending updates
# AND a valid DDM enforcement state
###

rdnn="org.churchofjesuschrist"

rm -v "/var/log/${rdnn}.log"

zsh "/Library/Management/${rdnn}/dor.zsh" demo

Note: The above bypasses the so-called “heartbeat” scheduler; use monitorRemoteSession.zsh when validating dor-starter.zsh, dor-state.plist, NextScheduledReminder, or LaunchDaemon behavior.

rdnn="org.churchofjesuschrist"

zsh Resources/monitorRemoteSession.zsh --rdnn "${rdnn}" --watch 5

launchctl kickstart -kp system/"${rdnn}".dor
4.5 Troubleshooting

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

  1. Define a rdnn variable to aid in completing the following steps:
rdnn="org.churchofjesuschrist"
  1. Start monitorRemoteSession.zsh for one live view of the heartbeat LaunchDaemon, runtime files, scheduler state, PID, matching processes, aggressive-mode kill switch, and recent logs:
zsh Resources/monitorRemoteSession.zsh --rdnn "${rdnn}" --watch 5
  1. Review the client-side LaunchDaemon for any obvious issues:
plutil -p /Library/LaunchDaemons/"${rdnn}.dor.plist"

launchctl print system/"${rdnn}.dor" 2>/dev/null | grep -E 'state =|last exit code =|program =|path ='
  1. Confirm the deployed runtime assets exist and review scheduler state (for example, NextScheduledReminder and, when Update Tonight suppression is active, UpdateTonightSuppressionUntil):
ls -l /Library/Management/"${rdnn}"/dor.zsh
ls -l /Library/Management/"${rdnn}"/dor-starter.zsh
ls -l /Library/Management/"${rdnn}"/dor-state.plist
ls -l /Library/Management/"${rdnn}"/dor.pid

plutil -p /Library/Management/"${rdnn}"/dor-state.plist
  1. Review the client-side managed preferences .plist for any obvious issues:
plutil -lint /Library/Managed\ Preferences/"${rdnn}".dorm.plist

plutil -p /Library/Managed\ Preferences/"${rdnn}".dorm.plist
  1. Review the script’s log for warnings and errors:
scriptLog=$(/usr/libexec/PlistBuddy -c 'Print :ScriptLog' "/Library/Managed Preferences/${rdnn}.dorm.plist" 2>/dev/null || /usr/libexec/PlistBuddy -c 'Print :ScriptLog' "/Library/Preferences/${rdnn}.dorm.plist" 2>/dev/null || echo "/var/log/${rdnn}.log")

grep -nE '^(dor|dorm) \([^)]*\): .* \[(WARNING|ERROR|FATAL ERROR)\]' "${scriptLog}"
  1. 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|No updates found for DDM to enforce|Found product with requested PMV|Armed DDM activity scheduler|SUOSUInstallTonightManager|SUOSUScheduler' | less -S
  1. Beginning with version 5.0.0, confirm the active softwareupdated DDM declarations used to corroborate install.log candidates, then check the script’s log for corroboration warnings:
plutil -p /var/db/softwareupdate/SoftwareUpdateDDMStatePersistence.plist | grep -E 'TargetOSVersion|TargetLocalDateTime'

grep -nE 'softwareupdate DDM state|absent from softwareupdate DDM state' "${scriptLog}"
  1. To more easily validate the 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 (both apply the same softwareupdated corroboration as the runtime):
root@XDT8675309 DDM-OS-Reminder-main # zsh Resources/JamfEA-Pending_OS_Update_Date.zsh
<result>2026-07-14 18:00:00</result>

root@XDT8675309 DDM-OS-Reminder-main # zsh Resources/JamfEA-Pending_OS_Update_Version.zsh
<result>26.5.2</result>

When troubleshooting user-interface issues, leverage swiftDialog’s dialogcli binary and your client-side managed preferences .plist values.

2. User-interface Issues

  1. Confirm dialogcli is installed and working as expected (beginning with version 5.0.0, DDM OS Reminder invokes swiftDialog from its app bundle, not /usr/local/bin/dialog):
"/Library/Application Support/Dialog/Dialog.app/Contents/MacOS/dialogcli" --title "Dialog Test" --message "swiftDialog is installed and can render a basic dialog." --icon /System/Library/CoreServices/Finder.app --infotext
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
  1. Define a rdnn variable to aid in completing the following steps:
rdnn="org.churchofjesuschrist"
  1. Use Resources/reminderDialogPreferenceTest.zsh when you want to validate deployed preferences:
zsh Resources/reminderDialogPreferenceTest.zsh --rdnn "${rdnn}"
See: Preference Preview and download reminderDialogPreferenceTest.zsh

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