Menu Close

DDM OS Reminder (2.4.0)

Yet another maintenance release of Mac Admins’ new favorite, MDM-agnostic, “set-it-and-forget-it” end-user reminder for Apple’s Declarative Device Management-enforced macOS update deadlines, with a new allowlist for more robust meeting detectiondark‑mode overlay icon support, and a significantly improved, interactive pre-deployment assembly script

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:

Apple’s built-in Notification is often too subtle for most Mac Admins

DDM OS Reminder evaluates the most recent EnforcedInstallDate and setPastDuePaddedEnforcementDate entries in /var/log/install.log, and then leverages a swiftDialog-enabled script plus a LaunchDaemon to deliver a more prominent end-user dialog that reminds users to update their Mac 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 reminder dialog

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

  • 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.

Version 3.0.0, currently in its first alpha release, will support multiple languages

Implementation

1. Test Deployment

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.)

  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.com 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
  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 dan # cd /Users/dan/Downloads/DDM-OS-Reminder-main 
root@XDT8675309 DDM-OS-Reminder-main # zsh reminderDialog.zsh demo
dorm (2.4.0): 2026-02-07 17:38:28 - [PRE-FLIGHT]      Created specified scriptLog: /var/log/org.churchofjesuschrist.log
dorm (2.4.0): 2026-02-07 17:38:28 - [PRE-FLIGHT]      

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

dorm (2.4.0): 2026-02-07 17:38:28 - [PRE-FLIGHT]      Initiating …
dorm (2.4.0): 2026-02-07 17:38:28 - [PRE-FLIGHT]      Check for Logged-in System Accounts …
dorm (2.4.0): 2026-02-07 17:38:28 - [PRE-FLIGHT]      Current Logged-in User: dan
dorm (2.4.0): 2026-02-07 17:38:28 - [PRE-FLIGHT]      Current Logged-in User First Name (ID): Dan (502)
dorm (2.4.0): 2026-02-07 17:38:28 - [PRE-FLIGHT]      No client-side preferences found; using script-defined defaults
dorm (2.4.0): 2026-02-07 17:38:28 - [PRE-FLIGHT]      Complete
dorm (2.4.0): 2026-02-07 17:38:28 - [NOTICE]          Demo mode enabled
dorm (2.4.0): 2026-02-07 17:38:28 - [NOTICE]          Check dan’s Display Sleep Assertions
dorm (2.4.0): 2026-02-07 17:38:28 - [INFO]            Acceptable assertion application names (allowlist): MSTeams zoom.us Webex
dorm (2.4.0): 2026-02-07 17:38:28 - [INFO]            dan’s Display Sleep Assertion has ended after 0 minute(s).
dorm (2.4.0): 2026-02-07 17:38:28 - [NOTICE]          No active Display Sleep Assertions detected; proceeding …
dorm (2.4.0): 2026-02-07 17:38:28 - [NOTICE]          Light mode detected; using standard overlay icon
dorm (2.4.0): 2026-02-07 17:38:28 - [NOTICE]          Processing overlay icon from 'https://use2.ics.services.jamfcloud.com/icon/hash_2d64ce7f0042ad68234a2515211adb067ad6714703dd8ebd6f33c1ab30354b1d'
dorm (2.4.0): 2026-02-07 17:38:28 - [INFO]            Overlay icon appears to be a remote URL; downloading with curl
dorm (2.4.0): 2026-02-07 17:38:29 - [INFO]            Successfully downloaded overlay icon
dorm (2.4.0): 2026-02-07 17:38:30 - [NOTICE]          Display Reminder Dialog to dan with additional options: --ontop
dorm (2.4.0): 2026-02-07 17:38:44 - [INFO]            Return Code: 0
dorm (2.4.0): 2026-02-07 17:38:44 - [NOTICE]          dan clicked Open Software Update
dorm (2.4.0): 2026-02-07 17:38:44 - [NOTICE]          Checking if System Settings is open …
dorm (2.4.0): 2026-02-07 17:38:44 - [INFO]            System Settings is open; Telling System Settings to make a guest appearance …
dorm (2.4.0): 2026-02-07 17:38:45 - [QUIT]            Exiting …
dorm (2.4.0): 2026-02-07 17:38:45 - [QUIT]            Gambling only pays when you’re winning!
  1. Simulate the installation of a .plist client-side 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:
2. Basic Deployment

A basic deployment of DDM OS Reminder starts by specifying your organization’s Reverse Domain Name Notation value when prompted by the assemble.zsh script

  1. Generate customized deployment artifacts for your organization by using the assemble.zsh script, specifying your organization’s Reverse Domain Name Notation (i.e., com.company) when prompted:
zsh assemble.zsh --help
root@XDT8675309 DDM-OS-Reminder-main # zsh assemble.zsh --help

===============================================================
🧩 Assemble DDM OS Reminder (2.4.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



Usage:
  zsh assemble.zsh [RDNN] [--lane dev|test|prod] [--interactive] [--help]

Options:
  --lane <dev|test|prod>       Select deployment mode
  --interactive                Prompt for IT support and branding values
  --help, -h                    Show this help
  • A new --interactive option helps to generate a fully customized .plist and .mobileconfig when assemblying the deployable script
zsh assemble.zsh us.snelson --interactive
root@XDT8675309 DDM-OS-Reminder-main # zsh assemble.zsh us.snelson --interactive

===============================================================
🧩 Assemble DDM OS Reminder (2.4.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'

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Using 'us.snelson' as the Reverse Domain Name Notation
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━


━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
IT Support & Branding (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 Team Website [https://support.snelson.us] (or ‘X’ to exit): 
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): https://usw2.ics.services.jamfcloud.com/icon/hash_4804203ac36cbd7c83607487f4719bd4707f2e283500f54428153af17da082e2
Overlay Icon URL (Dark) [https://use2.ics.services.jamfcloud.com/icon/hash_d3a3bc5e06d2db5f9697f9b4fa095bfecb2dc0d22c71aadea525eb38ff981d39] (or ‘X’ to exit): https://usw2.ics.services.jamfcloud.com/icon/hash_4804203ac36cbd7c83607487f4719bd4707f2e283500f54428153af17da082e2
Swap Overlay and Logo (YES/NO) [NO] (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-02-08-180243]
   → Artifacts/ddm-os-reminder-assembled-2026-02-08-180243.zsh

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

🔍 Performing syntax check on 'Artifacts/ddm-os-reminder-assembled-2026-02-08-180243.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 …
    🧪 Testing mode: replacing placeholder text → 'TEST'
    🔧 Applying IT support and branding values …
   → Artifacts/us.snelson.dorm-2026-02-08-180243-test.plist

🧩 Generating Configuration Profile (.mobileconfig) …
   → Artifacts/us.snelson.dorm-2026-02-08-180243-test-unsigned.mobileconfig

🔍 Performing syntax check on 'Artifacts/us.snelson.dorm-2026-02-08-180243-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-02-08-180243-test.zsh
    Organizational Plist: Artifacts/us.snelson.dorm-2026-02-08-180243-test.plist
   Configuration Profile: Artifacts/us.snelson.dorm-2026-02-08-180243-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

===============================================================
  1. Carefully review each deployment artifact and distribute the appropriate files to a single test Mac via your MDM.
  1. 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 
  1. Kickstart the DDM OS Reminder LaunchDaemon, using the following as an example (substitute your organization’s Reverse Domain Name Notation):
launchctl kickstart -kp system/org.churchofjesuschrist.dor
  1. 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
      • MinimumDiskFreePercentage
      • MeetingDelay
    • Branding
      • OrganizationOverlayIconURL
      • 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)
  2. Distribute the updated .plist or .mobileconfig to your test Mac
  3. Kickstart the DDM OS Reminder LaunchDaemon, using the following as an example (substitute your organization’s Reverse Domain Name Notation):
launchctl kickstart -kp system/org.churchofjesuschrist.dor
  1. 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

  1. In your preferred code editor, modify the StartCalendarInterval in launchDaemonManagement.zsh to match your organization’s requirements; launchd.info is a great reference
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
  1. 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"
  1. Execute the assemble.zsh script to insert your customized reminderDialog.zsh script into your customized launchDaemonManagement.zsh script and to create the deployment artifacts
  2. Carefully review each deployment artifact and distribute the appropriate files to a single test Mac via your MDM
  3. Kickstart the DDM OS Reminder LaunchDaemon
  4. Monitor the client-side log file
4. Upgrading

Author’s Highly Opinionated Thought: There are enough changes in version 2.x 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.

  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
5. Resources

Diagrams

AI-generated visual diagrams to augment this documentation are available at Diagrams/README.md.

Scripts

  1. Assemble combined, deployable artifacts of your customized scripts
  2. Create Self-extracting encodes the most recently assembled script for easier deployment with some MDMs
  3. Create .plist creates .plist and .mobileconfig, based on your customizations to reminderDialog.zsh
  4. Extension Attributes were created for and tested on Jamf Pro and can most likely be adapted for other MDMs
  5. Jamf-getDDMstatusFromCSV.zsh; See: DDM Status from .CSV (1.0.0)

Configuration Profile

Special thanks to Max Sundell for his write-up: Create and deploy a macOS configuration profile (.mobileconfig)

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 force-display the reminder dialog:

###
# Force-display reminder dialog
###

rdnn="org.churchofjesuschrist"
launchctl kickstart -kp "system/${rdnn}.dor"
tail -f "/var/log/${rdnn}.log"



###
# Atomic Log Reset
###

lines=24

keep_lines=$(($(wc -l < "/var/log/${rdnn}.log") - lines))

sed -i '' "${keep_lines}q" "/var/log/${rdnn}.log"

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

tail -f "/var/log/${rdnn}.log"



###
# Nuclear Log Reset
###

truncate -s 0 "/var/log/${rdnn}.log"

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

tail -f "/var/log/${rdnn}.log"

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