Menu Close

DDM OS Reminder (2.0.0)

Updated version now available: DDM OS Reminder (2.1.0)

A major update to Mac Admins’ new favorite, MDM-agnostic, “set-it-and-forget-it” end-user reminder for Apple’s Declarative Device Management-enforced macOS update deadlines — now with Configuration Profile support and a demo mode for easy reminder dialog testing

Overview

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

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, then leverages a swiftDialog-enabled script and LaunchDaemon pair to dynamically deliver a more prominent end-user dialog reminding 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 editing the included .plist and distributing as part of 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, so you can quickly roll out DDM OS Reminder across your entire organization.
  • Set-it-and-forget-it: Once configured and installed, a LaunchDaemon displays your customized reminder dialog — which automatically checks the installed version of macOS against the DDM-required macOS version — to remind your users if an update is required.
  • Deadline Awareness: Each time a DDM-enforced macOS version or its deadline is updated via your MDM solution, the reminder dialog dynamically updates the countdown to the deadline and required macOS version, creating a sense of urgency for end users to update their Macs.
  • Intelligently Intrusive: The reminder dialog is designed to be informative without being overly disruptive — first checking if the user is in an online meeting — so users can continue their work while still being reminded of the need 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 easily test the reminder dialog’s appearance and functionality.
zsh reminderDialog.zsh demo

Implementation

1. Demonstration

Jumpstart your DDM OS Reminder implementation by first downloading the main branch of the GitHub repository and interacting with a demonstration on a non-production Mac.

(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.com
  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 (elevated privileges required):
zsh reminderDialog.zsh demo
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 DDM-OS-Reminder-main # zsh reminderDialog.zsh demo
dorm (2.0.0): 2025-12-06 12:00:31 - [PRE-FLIGHT]      

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

dorm (2.0.0): 2025-12-06 12:00:31 - [PRE-FLIGHT]      Initiating …
dorm (2.0.0): 2025-12-06 12:00:31 - [PRE-FLIGHT]      Check for Logged-in System Accounts …
dorm (2.0.0): 2025-12-06 12:00:31 - [PRE-FLIGHT]      Current Logged-in User: dan
dorm (2.0.0): 2025-12-06 12:00:31 - [PRE-FLIGHT]      Current Logged-in User First Name (ID): Dan (502)
dorm (2.0.0): 2025-12-06 12:00:31 - [PRE-FLIGHT]      Complete
dorm (2.0.0): 2025-12-06 12:00:31 - [NOTICE]          Demo mode enabled
dorm (2.0.0): 2025-12-06 12:00:34 - [NOTICE]          Display Reminder Dialog to dan with additional options: --ontop
dorm (2.0.0): 2025-12-06 12:00:53 - [INFO]            Return Code: 0
dorm (2.0.0): 2025-12-06 12:00:53 - [NOTICE]          dan clicked Open Software Update
dorm (2.0.0): 2025-12-06 12:00:54 - [NOTICE]          Checking if System Settings is open …
dorm (2.0.0): 2025-12-06 12:00:54 - [INFO]            System Settings is open; Telling System Settings to make a guest appearance …
dorm (2.0.0): 2025-12-06 12:00:54 - [QUIT]            Exiting …
dorm (2.0.0): 2025-12-06 12:00:54 - [QUIT]            Keep them movin' blades sharp!
1.1 Optional Demonstration Steps

While the following steps are optional, they should prove at least somewhat informative.

  1. Change demoDeadlineOffsetDays to a positive value to disable blurscreen and re-execute zsh reminderDialog.zsh demo
    • Note: Only demo-related variables aren’t superseded by the Configuration Profile
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Demo Mode (i.e., zsh ~/Downloads/reminderDialog.zsh demo)
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

if [[ "${1}" == "demo" ]]; then

    notice "Demo mode enabled"

    # Installed vs Required Version
    installedmacOSVersion=$( sw_vers -productVersion )
    demoMajorVersion="${installedmacOSVersion%%.*}"
    ddmVersionString="${demoMajorVersion}.99"

    # Days from today to simulate deadline (can be + or -)
    demoDeadlineOffsetDays=-3   # positive → future deadline; negative → past due
    if (( demoDeadlineOffsetDays < 0 )); then       # Normalize the offset so “-3” becomes "-3d" and “7” becomes "+7d"
        offsetString="${demoDeadlineOffsetDays}d"   # → "-3d"
        blurscreen="--blurscreen"
    else
        offsetString="+${demoDeadlineOffsetDays}d"  # → "+7d"
        blurscreen="--noblurscreen"
    fi
  1. Execute the sample assembled script — with elevated privileges — which installs the combination of the following on your test Mac:
    • Sample reminderDialog.zsh
    • Sample launchDaemonManagment.zsh
zsh Resources/ddm-os-reminder-assembled-*.zsh
root@XDT8765309 DDM-OS-Reminder-main # zsh Resources/ddm-os-reminder-assembled-*.zsh                 
dor  (2.0.0): 2025-12-06 12:46:08 - [PRE-FLIGHT]      

###
# DDM OS Reminder (2.0.0)
# http://snelson.us/ddm
#
# Reset Configuration: All
###

dor  (2.0.0): 2025-12-06 12:46:08 - [PRE-FLIGHT]      Initiating …
dor  (2.0.0): 2025-12-06 12:46:08 - [PRE-FLIGHT]      Complete!
dor  (2.0.0): 2025-12-06 12:46:08 - [PRE-FLIGHT]      swiftDialog version 3.0.0.4922 found; proceeding...
dor  (2.0.0): 2025-12-06 12:46:08 - [NOTICE]          Reset Configuration: All
dor  (2.0.0): 2025-12-06 12:46:09 - [INFO]            Reset All Configuration Files … 
dor  (2.0.0): 2025-12-06 12:46:09 - [INFO]            Reset LaunchDaemon … 
dor  (2.0.0): 2025-12-06 12:46:09 - [NOTICE]          LaunchDaemon Status
dor  (2.0.0): 2025-12-06 12:46:09 -                   org.churchofjesuschrist.dor is NOT loaded
dor  (2.0.0): 2025-12-06 12:46:09 -                   Removing '/Library/LaunchDaemons/org.churchofjesuschrist.dor.plist' … 
dor  (2.0.0): 2025-12-06 12:46:09 -                   Removed '/Library/LaunchDaemons/org.churchofjesuschrist.dor.plist'
dor  (2.0.0): 2025-12-06 12:46:09 - [INFO]            Reset Script … 
dor  (2.0.0): 2025-12-06 12:46:09 -                   Removing '/Library/Management/org.churchofjesuschrist/dor.zsh' … 
dor  (2.0.0): 2025-12-06 12:46:09 -                   Removed '/Library/Management/org.churchofjesuschrist/dor.zsh' 
dor  (2.0.0): 2025-12-06 12:46:09 - [NOTICE]          Validating Script
dor  (2.0.0): 2025-12-06 12:46:09 - [NOTICE]          Create 'DDM OS Reminder' script: /Library/Management/org.churchofjesuschrist/dor.zsh
dor  (2.0.0): 2025-12-06 12:46:09 -                   DDM OS Reminder script created
dor  (2.0.0): 2025-12-06 12:46:09 -                   Setting permissions …
dor  (2.0.0): 2025-12-06 12:46:09 - [NOTICE]          Validating LaunchDaemon
dor  (2.0.0): 2025-12-06 12:46:09 -                   Checking for LaunchDaemon '/Library/LaunchDaemons/org.churchofjesuschrist.dor.plist' …
dor  (2.0.0): 2025-12-06 12:46:09 - [NOTICE]          Create LaunchDaemon
dor  (2.0.0): 2025-12-06 12:46:09 -                   Creating '/Library/LaunchDaemons/org.churchofjesuschrist.dor.plist' …
dor  (2.0.0): 2025-12-06 12:46:09 -                   Setting permissions for '/Library/LaunchDaemons/org.churchofjesuschrist.dor.plist' …
dor  (2.0.0): 2025-12-06 12:46:09 -                   Loading 'org.churchofjesuschrist.dor' …
dor  (2.0.0): 2025-12-06 12:46:09 - [NOTICE]          Status Checks
dor  (2.0.0): 2025-12-06 12:46:09 -                   I/O pause …
dor  (2.0.0): 2025-12-06 12:46:11 - [NOTICE]          LaunchDaemon Status
dor  (2.0.0): 2025-12-06 12:46:11 -                   67641	0	org.churchofjesuschrist.dor
  1. Monitor the client-side log file (using Control-C to stop)
tail -f /var/log/org.churchofjesuschrist.log 
root@XDT8675309 DDM-OS-Reminder-main # tail -f /var/log/org.churchofjesuschrist.log 
dorm (2.0.0): 2025-12-06 12:46:09 - [INFO]            DDM-enforced OS Version: 26.17
dorm (2.0.0): 2025-12-06 12:46:09 - [INFO]            DDM-enforced OS Version Deadline: Fri, 12-Dec-2025, 6:00 p.m.
dorm (2.0.0): 2025-12-06 12:46:09 - [NOTICE]          Within 14-day reminder window; proceeding with reminder.
dorm (2.0.0): 2025-12-06 12:46:09 - [NOTICE]          Check dan's Display Sleep Assertions
dorm (2.0.0): 2025-12-06 12:46:10 - [INFO]            dan's Display Sleep Assertion has ended after 0 minute(s).
dorm (2.0.0): 2025-12-06 12:46:10 - [NOTICE]          No active Display Sleep Assertions detected; proceeding with reminder.
dorm (2.0.0): 2025-12-06 12:46:10 - [NOTICE]          Login Trigger Pause: Random 30 to 90 seconds
dorm (2.0.0): 2025-12-06 12:46:10 - [INFO]            Pausing for 1 minute(s), 10 second(s) …
dor  (2.0.0): 2025-12-06 12:46:11 - [NOTICE]          LaunchDaemon Status
dor  (2.0.0): 2025-12-06 12:46:11 -                   67641	0	org.churchofjesuschrist.dor
dorm (2.0.0): 2025-12-06 12:47:21 - [NOTICE]          Display Reminder Dialog to dan with additional options: --ontop
dorm (2.0.0): 2025-12-06 12:47:38 - [INFO]            Return Code: 2
dorm (2.0.0): 2025-12-06 12:47:38 - [NOTICE]          dan clicked Remind Me Later
dorm (2.0.0): 2025-12-06 12:47:38 - [QUIT]            Exiting …
dorm (2.0.0): 2025-12-06 12:47:38 - [QUIT]            Keep them movin' blades sharp!
^C
  1. Kickstart the DDM OS Reminder LaunchDaemon (elevated privileges required)
launchctl kickstart -kp system/org.churchofjesuschrist.dor
root@XDT87675309 DDM-OS-Reminder-main # launchctl kickstart -kp system/org.churchofjesuschrist.dor
service spawned with pid: 86753099
  1. Again Monitor the client-side log file (use Control-C to stop)
tail -f /var/log/org.churchofjesuschrist.log
  1. In your preferred code editor, open Resources/ddm-os-reminder-assembled-*.zsh and modify the value of resetConfiguration to be Uninstall
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # 
# MDM Script Parameters
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # 

# Parameter 4: Configuration Files to Reset (i.e., None (blank) | All | LaunchDaemon | Script | Uninstall )
resetConfiguration="${4:-"Uninstall"}"
  1. Re-execute the sample assembled script (with elevated privileges) to uninstall DDM OS Reminder
zsh Resources/ddm-os-reminder-assembled-*.zsh
root@XDT8675309 DDM-OS-Reminder-main # zsh Resources/ddm-os-reminder-assembled-*.zsh             
dor  (2.0.0): 2025-12-06 13:56:01 - [PRE-FLIGHT]      

###
# DDM OS Reminder (2.0.0)
# http://snelson.us/ddm
#
# Reset Configuration: Uninstall
###

dor  (2.0.0): 2025-12-06 13:56:01 - [PRE-FLIGHT]      Initiating …
dor  (2.0.0): 2025-12-06 13:56:01 - [PRE-FLIGHT]      Complete!
dor  (2.0.0): 2025-12-06 13:56:01 - [PRE-FLIGHT]      swiftDialog version 3.0.0.4922 found; proceeding...
dor  (2.0.0): 2025-12-06 13:56:01 - [NOTICE]          Reset Configuration: Uninstall
dor  (2.0.0): 2025-12-06 13:56:01 - [WARNING]         *** UNINSTALLING DDM OS Reminder ***
dor  (2.0.0): 2025-12-06 13:56:01 - [INFO]            Uninstall LaunchDaemon … 
dor  (2.0.0): 2025-12-06 13:56:01 - [NOTICE]          LaunchDaemon Status
dor  (2.0.0): 2025-12-06 13:56:01 -                   -	0	org.churchofjesuschrist.dor
dor  (2.0.0): 2025-12-06 13:56:01 -                   Unload '/Library/LaunchDaemons/org.churchofjesuschrist.dor.plist' … 
dor  (2.0.0): 2025-12-06 13:56:01 - [NOTICE]          LaunchDaemon Status
dor  (2.0.0): 2025-12-06 13:56:01 -                   org.churchofjesuschrist.dor is NOT loaded
dor  (2.0.0): 2025-12-06 13:56:01 -                   Removing '/Library/LaunchDaemons/org.churchofjesuschrist.dor.plist' … 
dor  (2.0.0): 2025-12-06 13:56:02 -                   Removed '/Library/LaunchDaemons/org.churchofjesuschrist.dor.plist'
dor  (2.0.0): 2025-12-06 13:56:02 - [INFO]            Uninstall Script … 
dor  (2.0.0): 2025-12-06 13:56:02 -                   Removing '/Library/Management/org.churchofjesuschrist/dor.zsh' … 
dor  (2.0.0): 2025-12-06 13:56:02 -                   Removed '/Library/Management/org.churchofjesuschrist/dor.zsh' 
dor  (2.0.0): 2025-12-06 13:56:02 -                   Organization directory not empty; other management files may still exist — leaving intact: /Library/Management/org.churchofjesuschrist
dor  (2.0.0): 2025-12-06 13:56:02 -                   Uninstalled all DDM OS Reminder configuration files
dor  (2.0.0): 2025-12-06 13:56:02 - [NOTICE]          Thanks for trying DDM OS Reminder!
2. Basic Deployment

A basic deployment of DDM OS Reminder starts by distributing the sample .plist as a Configuration Profile, then distributing the sample assembled script via your MDM solution

(The following instructions presume you have completed the steps outlined in 1. Demonstration.)

  1. Use your MDM to distribute the sample org.churchofjesuschrist.dorm.plistas-is — to your test Mac, being certain to use the following preference domain:
    org.churchofjesuschrist.dorm
    • NOTE: Some Mac Admins have reported better success by deploying a .mobileconfig file, which can be easily generated by first running Resources/createPlist.zsh (version 2.1.0b3 or later).
Sample org.churchofjesuschrist.dorm.plist

Latest version available on GitHub.

<?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>
	<!-- Logging -->
	<key>ScriptLog</key>
	<string>/var/log/org.churchofjesuschrist.log</string>

	<!-- Reminder timing -->
	<key>DaysBeforeDeadlineDisplayReminder</key>
	<integer>14</integer>
	<key>DaysBeforeDeadlineBlurscreen</key>
	<integer>3</integer>
	<key>MeetingDelay</key>
	<integer>75</integer>

	<!-- Branding -->
	<key>OrganizationOverlayIconURL</key>
	<string>https://usw2.ics.services.jamfcloud.com/icon/hash_4804203ac36cbd7c83607487f4719bd4707f2e283500f54428153af17da082e2</string>
	<key>SwapOverlayAndLogo</key>
	<false/>
	<key>DateFormatDeadlineHumanReadable</key>
	<string>+%a, %d-%b-%Y, %-l:%M %p</string>

	<!-- Support block -->
	<key>SupportTeamName</key>
	<string>IT Support</string>
	<key>SupportTeamPhone</key>
	<string>+1 (801) 555-1212</string>
	<key>SupportTeamEmail</key>
	<string>rescue@domain.org</string>
	<key>SupportTeamWebsite</key>
	<string>https://support.domain.org</string>
	<key>SupportKB</key>
	<string>KB8675309</string>
	<key>InfoButtonAction</key>
	<string>https://servicenow.domain.org/support?id=kb_article_view&amp;sysparm_article=KB8675309</string>
	<key>SupportKBURL</key>
	<string>[KB8675309](https://servicenow.domain.org/support?id=kb_article_view&amp;sysparm_article=KB8675309)</string>

	<!-- Dialog text -->
	<key>Title</key>
	<string>macOS {titleMessageUpdateOrUpgrade} required</string>
	<key>Button1Text</key>
	<string>Open Software Update</string>
	<key>Button2Text</key>
	<string>Remind Me Later</string>
	<key>InfoButtonText</key>
	<string>KB8675309</string>
	<key>Message</key>
	<string>**A required macOS {titleMessageUpdateOrUpgrade} is now available**&lt;br&gt;---&lt;br&gt;Happy {weekday}, {loggedInUserFirstname}!&lt;br&gt;&lt;br&gt;Please {titleMessageUpdateOrUpgrade} to macOS **{ddmVersionString}** to ensure your Mac remains secure and compliant with organizational policies.&lt;br&gt;&lt;br&gt;To perform the {titleMessageUpdateOrUpgrade} now, click **{button1text}**, review the on-screen instructions, then click **{softwareUpdateButtonText}**.&lt;br&gt;&lt;br&gt;If you are unable to perform this {titleMessageUpdateOrUpgrade} now, click **{button2text}** to be reminded again later.&lt;br&gt;&lt;br&gt;However, your device **will automatically restart and {titleMessageUpdateOrUpgrade}** on **{ddmEnforcedInstallDateHumanReadable}** if you have not {titleMessageUpdateOrUpgrade}d before the deadline.&lt;br&gt;&lt;br&gt;For assistance, please contact **{supportTeamName}** by clicking the (?) button in the bottom, right-hand corner.</string>

	<!-- Infobox -->
	<key>InfoBox</key>
	<string>**Current:** {installedmacOSVersion}&lt;br&gt;&lt;br&gt;**Required:** {ddmVersionString}&lt;br&gt;&lt;br&gt;**Deadline:** {ddmVersionStringDeadlineHumanReadable}&lt;br&gt;&lt;br&gt;**Day(s) Remaining:** {ddmVersionStringDaysRemaining}</string>

	<!-- Help section -->
	<key>HelpMessage</key>
	<string>For assistance, please contact: **{supportTeamName}**&lt;br&gt;- **Telephone:** {supportTeamPhone}&lt;br&gt;- **Email:** {supportTeamEmail}&lt;br&gt;- **Website:** {supportTeamWebsite}&lt;br&gt;- **Knowledge Base Article:** {supportKBURL}&lt;br&gt;&lt;br&gt;**User Information:**&lt;br&gt;- **Full Name:** {userfullname}&lt;br&gt;- **User Name:** {username}&lt;br&gt;&lt;br&gt;**Computer Information:**&lt;br&gt;- **Computer Name:** {computername}&lt;br&gt;- **Serial Number:** {serialnumber}&lt;br&gt;- **macOS:** {osversion}&lt;br&gt;&lt;br&gt;**Script Information:**&lt;br&gt;- **Dialog:** {dialogVersion}&lt;br&gt;- **Script:** {scriptVersion}&lt;br&gt;</string>
	<key>HelpImage</key>
	<string>qr={infobuttonaction}</string>
</dict>
</plist>
  1. Use your MDM to distribute the sample Resources/ddm-os-reminder-assembled-YYYY-MM-DD-HHMMSS.zsh script to your test Mac

    Optional: Use zsh Resources/createSelfExtracting.zsh to create a self-extracting script (which is easier to deploy with some MDMs)
Resources/ddm-os-reminder-assembled-YYYY-MM-DD-HHMMSS.zsh

Latest version available on GitHub in the Resources directory.

#!/bin/zsh --no-rcs 
# shellcheck shell=bash

####################################################################################################
#
# DDM OS Reminder
# https://snelson.us/ddm
#
# Mac Admins’ new favorite, MDM-agnostic, “set-it-and-forget-it” end-user messaging for Apple’s
# Declarative Device Management-enforced macOS update deadlines.
#
# While Apple's Declarative Device Management (DDM) provides Mac Admins a powerful method to enforce
# macOS updates, its built-in notification tends to be too subtle for most Mac Admins.
#
# DDM OS Reminder evaluates the most recent `EnforcedInstallDate` and `setPastDuePaddedEnforcementDate`
# entries in `/var/log/install.log`, then leverages a swiftDialog-enabled script and LaunchDaemon pair
# to dynamically deliver a more prominent end-user message of when the user’s Mac needs to be updated
# to comply with DDM-enforced macOS update deadlines.
#
####################################################################################################
#
# HISTORY
#
# Version 2.0.0, 06-Dec-2025, Dan K. Snelson (@dan-snelson)
#   - Reorganized script structure for (hopefully) improved clarity
#   - Defined `swiftDialogMinimumRequiredVersion` (Addresses #16; thanks for the heads-up, @deski-arnaud!)
#   - Refactored `displayReminderDialog` function's Exit Code `3` to re-display dialog after 61 seconds when infobutton (i.e., KB) is clicked (Inspired by Pull Request: #20; thanks, @TazNZ!)
#   - Refactored `daysBeforeDeadlineBlurscreen` logic to use seconds (instead of days) for more precise control (thanks for the suggestion, @Ancaeus!)
#   - Added a "demo" mode to the `reminderDialog.zsh` script for testing purposes (thanks for the suggestion, Max S!)
#   - Added ability to read variables from `.plist` (Pull Request #22; thanks, Obi-@maxsundellacne!)
#
####################################################################################################



####################################################################################################
#
# Global Variables
#
####################################################################################################

export PATH=/usr/bin:/bin:/usr/sbin:/sbin:/usr/local:/usr/local/bin

# Script Version
scriptVersion="2.0.0"

# Client-side Log
scriptLog="/var/log/org.churchofjesuschrist.log"

# Minimum Required Version of swiftDialog
swiftDialogMinimumRequiredVersion="2.5.6.4805"



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# MDM Script Parameters
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

# Parameter 4: Configuration Files to Reset (i.e., None (blank) | All | LaunchDaemon | Script | Uninstall )
resetConfiguration="${4:-"All"}"



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

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

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

# Organization's Script Name
organizationScriptName="dor"

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

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



####################################################################################################
#
# Functions
#
####################################################################################################

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Client-side Logging
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

function updateScriptLog() {
    echo "${organizationScriptName}  ($scriptVersion): $( date +%Y-%m-%d\ %H:%M:%S ) - ${1}" | tee -a "${scriptLog}"
}

function preFlight()    { updateScriptLog "[PRE-FLIGHT]      ${1}"; }
function logComment()   { updateScriptLog "                  ${1}"; }
function notice()       { updateScriptLog "[NOTICE]          ${1}"; }
function info()         { updateScriptLog "[INFO]            ${1}"; }
function errorOut()     { updateScriptLog "[ERROR]           ${1}"; }
function error()        { updateScriptLog "[ERROR]           ${1}"; let errorCount++; }
function warning()      { updateScriptLog "[WARNING]         ${1}"; let errorCount++; }
function fatal()        { updateScriptLog "[FATAL ERROR]     ${1}"; exit 1; }
function quitOut()      { updateScriptLog "[QUIT]            ${1}"; }



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Reset Configuration
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

function resetConfiguration() {

    notice "Reset Configuration: ${1}"

    # Ensure the directory exists
    mkdir -p "${organizationDirectory}"

    # Secure ownership
    chown -R root:wheel "${organizationDirectory}"

    # Secure directory permissions (no world-writable bits)
    chmod 755 "${organizationDirectory}"
    chmod 755 "${organizationDirectory}/${reverseDomainNameNotation}"

    case ${1} in

        "All" )

            info "Reset All Configuration Files … "

            # Reset LaunchDaemon
            info "Reset LaunchDaemon … "
            launchDaemonStatus
            if [[ -n "${launchDaemonStatus}" ]]; then
                logComment "Unload '${launchDaemonPath}' … "
                launchctl bootout system "${launchDaemonPath}"
                launchDaemonStatus
            fi
            logComment "Removing '${launchDaemonPath}' … "
            rm -f "${launchDaemonPath}" 2>&1
            logComment "Removed '${launchDaemonPath}'"

            # Reset Script
            info "Reset Script … "
            logComment "Removing '${organizationDirectory}/${organizationScriptName}.zsh' … "
            rm -f "${organizationDirectory}/${organizationScriptName}.zsh"
            logComment "Removed '${organizationDirectory}/${organizationScriptName}.zsh' "
            ;;

        "LaunchDaemon" )

            info "Reset LaunchDaemon … "
            launchDaemonStatus
            if [[ -n "${launchDaemonStatus}" ]]; then
                logComment "Unload '${launchDaemonPath}' … "
                launchctl bootout system "${launchDaemonPath}"
                launchDaemonStatus
            fi
            logComment "Removing '${launchDaemonPath}' … "
            rm -f "${launchDaemonPath}" 2>&1
            logComment "Removed '${launchDaemonPath}'"
            ;;

        "Script" )

            info "Reset Script … "
            logComment "Removing '${organizationDirectory}/${organizationScriptName}.zsh' … "
            rm -f "${organizationDirectory}/${organizationScriptName}.zsh"
            logComment "Removed '${organizationDirectory}/${organizationScriptName}.zsh' "
            ;;

        "Uninstall" )

            warning "*** UNINSTALLING ${humanReadableScriptName} ***"

            # Uninstall LaunchDaemon
            info "Uninstall LaunchDaemon … "
            launchDaemonStatus
            if [[ -n "${launchDaemonStatus}" ]]; then
                logComment "Unload '${launchDaemonPath}' … "
                launchctl bootout system "${launchDaemonPath}"
                launchDaemonStatus
            fi
            logComment "Removing '${launchDaemonPath}' … "
            rm -f "${launchDaemonPath}" 2>&1
            logComment "Removed '${launchDaemonPath}'"

            # Uninstall Script
            info "Uninstall Script … "
            logComment "Removing '${organizationDirectory}/${organizationScriptName}.zsh' … "
            rm -f "${organizationDirectory}/${organizationScriptName}.zsh"
            logComment "Removed '${organizationDirectory}/${organizationScriptName}.zsh' "

            # Remove legacy nested directory if it exists and is empty (pre-v1.3.0 cleanup)
            if [[ -d "${organizationDirectory}/${reverseDomainNameNotation}" ]]; then
                if [[ -z "$(ls -A "${organizationDirectory}/${reverseDomainNameNotation}")" ]]; then
                    logComment "Removing legacy nested directory: ${organizationDirectory}/${reverseDomainNameNotation}"
                    rmdir "${organizationDirectory}/${reverseDomainNameNotation}"
                    logComment "Removed legacy nested directory"
                else
                    logComment "Legacy nested directory not empty; leaving intact: ${organizationDirectory}/${reverseDomainNameNotation}"
                fi
            fi

            # Remove organization directory if empty
            if [[ -d "${organizationDirectory}" ]]; then
                if [[ -z "$(ls -A "${organizationDirectory}")" ]]; then
                    logComment "Removing empty organization directory: ${organizationDirectory}"
                    rmdir "${organizationDirectory}"
                    logComment "Removed empty organization directory"
                else
                    logComment "Organization directory not empty; other management files may still exist — leaving intact: ${organizationDirectory}"
                fi
            fi

            # Exit
            logComment "Uninstalled all ${humanReadableScriptName} configuration files"
            notice "Thanks for trying ${humanReadableScriptName}!"
            exit 0
            ;;
            
        * )

            warning "None of the expected reset options was entered; don't reset anything"
            ;;

    esac

}

function createDDMOSReminderScript() {

    notice "Create '${humanReadableScriptName}' script: ${organizationDirectory}/${organizationScriptName}.zsh"

(
cat <<'ENDOFSCRIPT'
#!/bin/zsh --no-rcs
# shellcheck shell=bash

####################################################################################################
#
# Declarative Device Management macOS Reminder: End-user Message
#
# http://snelson.us/ddm
#
####################################################################################################



####################################################################################################
#
# Global Variables
#
####################################################################################################

export PATH=/usr/bin:/bin:/usr/sbin:/sbin:/usr/local:/usr/local/bin

# Script Version
scriptVersion="2.0.0"

# Client-side Log
scriptLog="/var/log/org.churchofjesuschrist.log"

# Load is-at-least for version comparison
autoload -Uz is-at-least



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

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

# Organization's reverse domain (used for plist domains)
reverseDomainNameNotation="org.churchofjesuschrist"

# Organization's Script Name
organizationScriptName="dorm"

# Preference plist domains
preferenceDomain="${reverseDomainNameNotation}.${organizationScriptName}"
managedPreferencesPlist="/Library/Managed Preferences/${preferenceDomain}"
localPreferencesPlist="/Library/Preferences/${preferenceDomain}"

# Organization's number of days before deadline to starting displaying reminders
daysBeforeDeadlineDisplayReminder="14"

# Organization's number of days before deadline to enable swiftDialog's blurscreen
daysBeforeDeadlineBlurscreen="3"

# Organization's Meeting Delay (in minutes) 
meetingDelay="75"

# Date format for deadlines (used with date -jf)
dateFormatDeadlineHumanReadable="+%a, %d-%b-%Y, %-l:%M %p"

# Swap main icon and overlay icon (set to YES, true, or 1 to enable)
swapOverlayAndLogo="NO"



####################################################################################################
#
# Functions
#
####################################################################################################

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Client-side Logging
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

function updateScriptLog() {
    echo "${organizationScriptName} ($scriptVersion): $( date +%Y-%m-%d\ %H:%M:%S ) - ${1}" # | tee -a "${scriptLog}"
}

function preFlight()    { updateScriptLog "[PRE-FLIGHT]      ${1}"; }
function logComment()   { updateScriptLog "                  ${1}"; }
function notice()       { updateScriptLog "[NOTICE]          ${1}"; }
function info()         { updateScriptLog "[INFO]            ${1}"; }
function errorOut()     { updateScriptLog "[ERROR]           ${1}"; }
function error()        { updateScriptLog "[ERROR]           ${1}"; let errorCount++; }
function warning()      { updateScriptLog "[WARNING]         ${1}"; let errorCount++; }
function fatal()        { updateScriptLog "[FATAL ERROR]     ${1}"; exit 1; }
function quitOut()      { updateScriptLog "[QUIT]            ${1}"; }



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Preference Helpers
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

function setPreferenceValue() {

    local targetVariable="${1}"
    local managedValue="${2}"
    local localValue="${3}"
    local defaultValue="${4}"
    local chosenValue=""

    if [[ -n "${managedValue}" ]]; then
        chosenValue="${managedValue}"
    elif [[ -n "${localValue}" ]]; then
        chosenValue="${localValue}"
    else
        chosenValue="${defaultValue}"
    fi

    printf -v "${targetVariable}" '%s' "${chosenValue}"

}

function setNumericPreferenceValue() {

    local targetVariable="${1}"
    local managedValue="${2}"
    local localValue="${3}"
    local defaultValue="${4}"
    local candidate=""

    if [[ -n "${managedValue}" && "${managedValue}" == <-> ]]; then
        candidate="${managedValue}"
    elif [[ -n "${localValue}" && "${localValue}" == <-> ]]; then
        candidate="${localValue}"
    else
        candidate="${defaultValue}"
    fi

    printf -v "${targetVariable}" '%s' "${candidate}"

}

function replacePlaceholders() {

    local targetVariable="${1}"
    local value="${(P)targetVariable}"

    # Handle both {placeholder} from plist and \{placeholder\} from inline defaults
    value=${value//\{weekday\}/$( date +'%A' )}
    value=${value//'{weekday}'/$( date +'%A' )}
    value=${value//\{userfirstname\}/${loggedInUserFirstname}}
    value=${value//'{userfirstname}'/${loggedInUserFirstname}}
    value=${value//\{loggedInUserFirstname\}/${loggedInUserFirstname}}
    value=${value//'{loggedInUserFirstname}'/${loggedInUserFirstname}}
    value=${value//\{ddmVersionString\}/${ddmVersionString}}
    value=${value//'{ddmVersionString}'/${ddmVersionString}}
    value=${value//\{ddmEnforcedInstallDateHumanReadable\}/${ddmEnforcedInstallDateHumanReadable}}
    value=${value//'{ddmEnforcedInstallDateHumanReadable}'/${ddmEnforcedInstallDateHumanReadable}}
    value=${value//\{installedmacOSVersion\}/${installedmacOSVersion}}
    value=${value//'{installedmacOSVersion}'/${installedmacOSVersion}}
    value=${value//\{ddmVersionStringDeadlineHumanReadable\}/${ddmVersionStringDeadlineHumanReadable}}
    value=${value//'{ddmVersionStringDeadlineHumanReadable}'/${ddmVersionStringDeadlineHumanReadable}}
    value=${value//\{ddmVersionStringDaysRemaining\}/${ddmVersionStringDaysRemaining}}
    value=${value//'{ddmVersionStringDaysRemaining}'/${ddmVersionStringDaysRemaining}}
    value=${value//\{titleMessageUpdateOrUpgrade\}/${titleMessageUpdateOrUpgrade}}
    value=${value//'{titleMessageUpdateOrUpgrade}'/${titleMessageUpdateOrUpgrade}}
    value=${value//\{softwareUpdateButtonText\}/${softwareUpdateButtonText}}
    value=${value//'{softwareUpdateButtonText}'/${softwareUpdateButtonText}}
    value=${value//\{button1text\}/${button1text}}
    value=${value//'{button1text}'/${button1text}}
    value=${value//\{button2text\}/${button2text}}
    value=${value//'{button2text}'/${button2text}}
    value=${value//\{supportTeamName\}/${supportTeamName}}
    value=${value//'{supportTeamName}'/${supportTeamName}}
    value=${value//\{supportTeamPhone\}/${supportTeamPhone}}
    value=${value//'{supportTeamPhone}'/${supportTeamPhone}}
    value=${value//\{supportTeamEmail\}/${supportTeamEmail}}
    value=${value//'{supportTeamEmail}'/${supportTeamEmail}}
    value=${value//\{supportTeamWebsite\}/${supportTeamWebsite}}
    value=${value//'{supportTeamWebsite}'/${supportTeamWebsite}}
    value=${value//\{supportKBURL\}/${supportKBURL}}
    value=${value//'{supportKBURL}'/${supportKBURL}}
    value=${value//\{supportKB\}/${supportKB}}
    value=${value//'{supportKB}'/${supportKB}}
    value=${value//\{infobuttonaction\}/${infobuttonaction}}
    value=${value//'{infobuttonaction}'/${infobuttonaction}}
    value=${value//\{dialogVersion\}/$(/usr/local/bin/dialog -v 2>/dev/null)}
    value=${value//'{dialogVersion}'/$(/usr/local/bin/dialog -v 2>/dev/null)}
    value=${value//\{scriptVersion\}/${scriptVersion}}
    value=${value//'{scriptVersion}'/${scriptVersion}}

    printf -v "${targetVariable}" '%s' "${value}"

}

function applyHideRules() {

    # Hide info button explicitly
    if [[ "${infobuttontext}" == "hide" ]]; then
        infobuttontext=""
    fi

    # Hide help image (QR) if requested
    if [[ "${helpimage}" == "hide" ]]; then
        helpimage=""
    fi

}

function loadPreferenceOverrides() {

    if [[ -f ${managedPreferencesPlist}.plist ]]; then
        scriptLog_managed=$(defaults read "${managedPreferencesPlist}" ScriptLog 2> /dev/null)
        daysBeforeDeadlineDisplayReminder_managed=$(defaults read "${managedPreferencesPlist}" DaysBeforeDeadlineDisplayReminder 2> /dev/null)
        daysBeforeDeadlineBlurscreen_managed=$(defaults read "${managedPreferencesPlist}" DaysBeforeDeadlineBlurscreen 2> /dev/null)
        meetingDelay_managed=$(defaults read "${managedPreferencesPlist}" MeetingDelay 2> /dev/null)
        organizationOverlayiconURL_managed=$(defaults read "${managedPreferencesPlist}" OrganizationOverlayIconURL 2> /dev/null)
        swapOverlayAndLogo_managed=$(defaults read "${managedPreferencesPlist}" SwapOverlayAndLogo 2> /dev/null)
        dateFormatDeadlineHumanReadable_managed=$(defaults read "${managedPreferencesPlist}" DateFormatDeadlineHumanReadable 2> /dev/null)
        supportTeamName_managed=$(defaults read "${managedPreferencesPlist}" SupportTeamName 2> /dev/null)
        supportTeamPhone_managed=$(defaults read "${managedPreferencesPlist}" SupportTeamPhone 2> /dev/null)
        supportTeamEmail_managed=$(defaults read "${managedPreferencesPlist}" SupportTeamEmail 2> /dev/null)
        supportTeamWebsite_managed=$(defaults read "${managedPreferencesPlist}" SupportTeamWebsite 2> /dev/null)
        supportKB_managed=$(defaults read "${managedPreferencesPlist}" SupportKB 2> /dev/null)
        infobuttonaction_managed=$(defaults read "${managedPreferencesPlist}" InfoButtonAction 2> /dev/null)
        supportKBURL_managed=$(defaults read "${managedPreferencesPlist}" SupportKBURL 2> /dev/null)
        title_managed=$(defaults read "${managedPreferencesPlist}" Title 2> /dev/null)
        button1text_managed=$(defaults read "${managedPreferencesPlist}" Button1Text 2> /dev/null)
        button2text_managed=$(defaults read "${managedPreferencesPlist}" Button2Text 2> /dev/null)
        message_managed=$(defaults read "${managedPreferencesPlist}" Message 2> /dev/null)
        infobuttontext_managed=$(defaults read "${managedPreferencesPlist}" InfoButtonText 2> /dev/null)
        infobox_managed=$(defaults read "${managedPreferencesPlist}" InfoBox 2> /dev/null)
        helpmessage_managed=$(defaults read "${managedPreferencesPlist}" HelpMessage 2> /dev/null)
        helpimage_managed=$(defaults read "${managedPreferencesPlist}" HelpImage 2> /dev/null)
    fi

    if [[ -f ${localPreferencesPlist}.plist ]]; then
        scriptLog_local=$(defaults read "${localPreferencesPlist}" ScriptLog 2> /dev/null)
        daysBeforeDeadlineDisplayReminder_local=$(defaults read "${localPreferencesPlist}" DaysBeforeDeadlineDisplayReminder 2> /dev/null)
        daysBeforeDeadlineBlurscreen_local=$(defaults read "${localPreferencesPlist}" DaysBeforeDeadlineBlurscreen 2> /dev/null)
        meetingDelay_local=$(defaults read "${localPreferencesPlist}" MeetingDelay 2> /dev/null)
        organizationOverlayiconURL_local=$(defaults read "${localPreferencesPlist}" OrganizationOverlayIconURL 2> /dev/null)
        swapOverlayAndLogo_local=$(defaults read "${localPreferencesPlist}" SwapOverlayAndLogo 2> /dev/null)
        dateFormatDeadlineHumanReadable_local=$(defaults read "${localPreferencesPlist}" DateFormatDeadlineHumanReadable 2> /dev/null)
        supportTeamName_local=$(defaults read "${localPreferencesPlist}" SupportTeamName 2> /dev/null)
        supportTeamPhone_local=$(defaults read "${localPreferencesPlist}" SupportTeamPhone 2> /dev/null)
        supportTeamEmail_local=$(defaults read "${localPreferencesPlist}" SupportTeamEmail 2> /dev/null)
        supportTeamWebsite_local=$(defaults read "${localPreferencesPlist}" SupportTeamWebsite 2> /dev/null)
        supportKB_local=$(defaults read "${localPreferencesPlist}" SupportKB 2> /dev/null)
        infobuttonaction_local=$(defaults read "${localPreferencesPlist}" InfoButtonAction 2> /dev/null)
        supportKBURL_local=$(defaults read "${localPreferencesPlist}" SupportKBURL 2> /dev/null)
        title_local=$(defaults read "${localPreferencesPlist}" Title 2> /dev/null)
        button1text_local=$(defaults read "${localPreferencesPlist}" Button1Text 2> /dev/null)
        button2text_local=$(defaults read "${localPreferencesPlist}" Button2Text 2> /dev/null)
        message_local=$(defaults read "${localPreferencesPlist}" Message 2> /dev/null)
        infobuttontext_local=$(defaults read "${localPreferencesPlist}" InfoButtonText 2> /dev/null)
        infobox_local=$(defaults read "${localPreferencesPlist}" InfoBox 2> /dev/null)
        helpmessage_local=$(defaults read "${localPreferencesPlist}" HelpMessage 2> /dev/null)
        helpimage_local=$(defaults read "${localPreferencesPlist}" HelpImage 2> /dev/null)
    fi

    setPreferenceValue "scriptLog" "${scriptLog_managed}" "${scriptLog_local}" "${scriptLog}"
    setNumericPreferenceValue "daysBeforeDeadlineDisplayReminder" "${daysBeforeDeadlineDisplayReminder_managed}" "${daysBeforeDeadlineDisplayReminder_local}" "${daysBeforeDeadlineDisplayReminder}"
    setNumericPreferenceValue "daysBeforeDeadlineBlurscreen" "${daysBeforeDeadlineBlurscreen_managed}" "${daysBeforeDeadlineBlurscreen_local}" "${daysBeforeDeadlineBlurscreen}"
    setNumericPreferenceValue "meetingDelay" "${meetingDelay_managed}" "${meetingDelay_local}" "${meetingDelay}"
    setPreferenceValue "swapOverlayAndLogo" "${swapOverlayAndLogo_managed}" "${swapOverlayAndLogo_local}" "${swapOverlayAndLogo}"
    setPreferenceValue "dateFormatDeadlineHumanReadable" "${dateFormatDeadlineHumanReadable_managed}" "${dateFormatDeadlineHumanReadable_local}" "${dateFormatDeadlineHumanReadable}"
    # Ensure date format starts with '+' as required by `date`
    [[ "${dateFormatDeadlineHumanReadable}" != +* ]] && dateFormatDeadlineHumanReadable="+${dateFormatDeadlineHumanReadable}"

}



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Current Logged-in User
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

function currentLoggedInUser() {
    loggedInUser=$( echo "show State:/Users/ConsoleUser" | scutil | awk '/Name :/ { print $3 }' )
    preFlight "Current Logged-in User: ${loggedInUser}"
}



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Installed OS vs. DDM-enforced OS Comparison
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

installedOSvsDDMenforcedOS() {

    # Installed macOS Version
    installedmacOSVersion=$( sw_vers -productVersion )
    notice "Installed macOS Version: $installedmacOSVersion"

    # DDM-enforced macOS Version
    ddmLogEntry=$( grep "EnforcedInstallDate" /var/log/install.log | tail -n 1 )
    if [[ -z "$ddmLogEntry" ]]; then
        versionComparisonResult="No DDM enforcement log entry found; please confirm this Mac is in-scope for DDM-enforced updates."
        return
    fi

    # Parse enforced date and version
    ddmEnforcedInstallDate="${${ddmLogEntry##*|EnforcedInstallDate:}%%|*}"
    ddmVersionString="${${ddmLogEntry##*|VersionString:}%%|*}"

    # DDM-enforced Deadline
    ddmVersionStringDeadline="${ddmEnforcedInstallDate%%T*}"
    deadlineEpoch=$( date -jf "%Y-%m-%dT%H:%M:%S" "$ddmEnforcedInstallDate" "+%s" 2>/dev/null )
    ddmVersionStringDeadlineHumanReadable=$( date -jf "%Y-%m-%dT%H:%M:%S" "$ddmEnforcedInstallDate" "${dateFormatDeadlineHumanReadable}" 2>/dev/null )
    # Fallback to default if format fails
    if [[ -z "${ddmVersionStringDeadlineHumanReadable}" ]]; then
        ddmVersionStringDeadlineHumanReadable=$( date -jf "%Y-%m-%dT%H:%M:%S" "$ddmEnforcedInstallDate" "+%a, %d-%b-%Y, %-l:%M %p" 2>/dev/null )
    fi
    ddmVersionStringDeadlineHumanReadable=${ddmVersionStringDeadlineHumanReadable// AM/ a.m.}
    ddmVersionStringDeadlineHumanReadable=${ddmVersionStringDeadlineHumanReadable// PM/ p.m.}

    # DDM-enforced Install Date
    if (( deadlineEpoch <= $(date +%s) )); then

        # Enforcement deadline passed
        notice "DDM enforcement deadline has passed; evaluating post-deadline enforcement …"

        # Read Apple's internal padded enforcement date from install.log
        pastDueDeadline=$(grep "setPastDuePaddedEnforcementDate" /var/log/install.log | tail -n 1)
        if [[ -n "$pastDueDeadline" ]]; then
            paddedDateRaw="${pastDueDeadline#*setPastDuePaddedEnforcementDate is set: }"
            paddedEpoch=$( date -jf "%a %b %d %H:%M:%S %Y" "$paddedDateRaw" "+%s" 2>/dev/null )
            info "Found setPastDuePaddedEnforcementDate: ${paddedDateRaw:-Unparseable}"

            if [[ -n "$paddedEpoch" ]]; then
                ddmEnforcedInstallDateHumanReadable=$( date -jf "%s" "$paddedEpoch" "${dateFormatDeadlineHumanReadable}" 2>/dev/null )
                if [[ -z "${ddmEnforcedInstallDateHumanReadable}" ]]; then
                    ddmEnforcedInstallDateHumanReadable=$( date -jf "%s" "$paddedEpoch" "+%a, %d-%b-%Y, %-l:%M %p" 2>/dev/null )
                fi
                info "Using ${ddmEnforcedInstallDateHumanReadable} for enforced install date"
            else
                warning "Unable to parse padded enforcement date from install.log"
                ddmEnforcedInstallDateHumanReadable="Unavailable"
            fi
        else
            warning "No setPastDuePaddedEnforcementDate found in install.log"
            ddmEnforcedInstallDateHumanReadable="Unavailable"
        fi

        info "Effective enforcement source: setPastDuePaddedEnforcementDate"

    else

        # Deadline still in the future
        ddmEnforcedInstallDateHumanReadable="$ddmVersionStringDeadlineHumanReadable"

    fi

    # Normalize AM/PM formatting
    ddmEnforcedInstallDateHumanReadable=${ddmEnforcedInstallDateHumanReadable// AM/ a.m.}
    ddmEnforcedInstallDateHumanReadable=${ddmEnforcedInstallDateHumanReadable// PM/ p.m.}

    # Blurscreen logic (based on precise timestamp comparison)
    nowEpoch=$(date +%s)
    secondsUntilDeadline=$(( deadlineEpoch - nowEpoch ))
    blurThresholdSeconds=$(( daysBeforeDeadlineBlurscreen * 86400 ))
    ddmVersionStringDaysRemaining=$(( (secondsUntilDeadline + 43200) / 86400 )) # Round to nearest whole day
    if (( secondsUntilDeadline <= blurThresholdSeconds )); then
        blurscreen="--blurscreen"
    else
        blurscreen="--noblurscreen"
    fi

    # Version Comparison Result
    if is-at-least "$ddmVersionString" "$installedmacOSVersion"; then
        versionComparisonResult="Up-to-date"
        info "DDM-enforced OS Version: $ddmVersionString"
    else
        versionComparisonResult="Update Required"
        info "DDM-enforced OS Version: $ddmVersionString"
        info "DDM-enforced OS Version Deadline: $ddmVersionStringDeadlineHumanReadable"
        majorInstalled="${installedmacOSVersion%%.*}"
        majorDDM="${ddmVersionString%%.*}"
        if [[ "$majorInstalled" != "$majorDDM" ]]; then
            titleMessageUpdateOrUpgrade="upgrade"
            softwareUpdateButtonText="Upgrade Now"
        else
            titleMessageUpdateOrUpgrade="update"
            softwareUpdateButtonText="Restart Now"
        fi
    fi

}



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Check User's Display Sleep Assertions (thanks, @techtrekkie!)
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

function checkUserDisplaySleepAssertions() {

    notice "Check ${loggedInUser}'s Display Sleep Assertions"

    local intervalSeconds=300  # Default: 300 seconds (i.e., 5 minutes)
    local intervalMinutes=$(( intervalSeconds / 60 ))
    local maxChecks=$(( meetingDelay * 60 / intervalSeconds ))
    local checkCount=0

    while (( checkCount < maxChecks )); do
        local previousIFS="${IFS}"
        IFS=$'\n'

        local displayAssertionsArray
        displayAssertionsArray=( $(pmset -g assertions | awk '/NoDisplaySleepAssertion | PreventUserIdleDisplaySleep/ && match($0,/\(.+\)/) && ! /coreaudiod/ {gsub(/^\ +/,"",$0); print};') )

        if [[ -n "${displayAssertionsArray[*]}" ]]; then
            userDisplaySleepAssertions="TRUE"
            ((checkCount++))
            for displayAssertion in "${displayAssertionsArray[@]}"; do
                info "Found the following Display Sleep Assertion(s): $(echo "${displayAssertion}" | awk -F ':' '{print $1;}')"
            done
            info "Check ${checkCount} of ${maxChecks}: Display Sleep Assertion still active; pausing reminder. (Will check again in ${intervalMinutes} minute(s).)"
            IFS="${previousIFS}"
            sleep "${intervalSeconds}"
        else
            userDisplaySleepAssertions="FALSE"
            info "${loggedInUser}'s Display Sleep Assertion has ended after $(( checkCount * intervalMinutes )) minute(s)."
            IFS="${previousIFS}"
            return 0  # No active Display Sleep Assertions found
        fi
    done

    if [[ "${userDisplaySleepAssertions}" == "TRUE" ]]; then
        info "Presentation delay limit (${meetingDelay} min) reached after ${maxChecks} checks. Proceeding with reminder."
        return 1  # Presentation still active after full delay
    fi

}



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Update Required Variables
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

function updateRequiredVariables() {

    # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
    # Organization's Branding Variables
    # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

    # Organization's Overlayicon URL
    local defaultOverlayiconURL="${organizationOverlayiconURL:-"https://usw2.ics.services.jamfcloud.com/icon/hash_4804203ac36cbd7c83607487f4719bd4707f2e283500f54428153af17da082e2"}"
    setPreferenceValue "organizationOverlayiconURL" "${organizationOverlayiconURL_managed}" "${organizationOverlayiconURL_local}" "${defaultOverlayiconURL}"

    # Download the overlayicon from ${organizationOverlayiconURL}
    if [[ -n "${organizationOverlayiconURL}" ]]; then
        # notice "Downloading overlayicon from '${organizationOverlayiconURL}' …"
        curl -o "/var/tmp/overlayicon.png" "${organizationOverlayiconURL}" --silent --show-error --fail
        if [[ "$?" -ne 0 ]]; then
            echo "Error: Failed to download the overlayicon from '${organizationOverlayiconURL}'."
            overlayicon="/System/Library/CoreServices/Finder.app"
        else
            overlayicon="/var/tmp/overlayicon.png"
        fi
    else
        overlayicon="/System/Library/CoreServices/Finder.app"
    fi



    # macOS Installer Icon URL
    majorDDM="${ddmVersionString%%.*}"
    case ${majorDDM} in
        14)  macOSIconURL="https://ics.services.jamfcloud.com/icon/hash_eecee9688d1bc0426083d427d80c9ad48fa118b71d8d4962061d4de8d45747e7" ;;
        15)  macOSIconURL="https://ics.services.jamfcloud.com/icon/hash_0968afcd54ff99edd98ec6d9a418a5ab0c851576b687756dc3004ec52bac704e" ;;
        26)  macOSIconURL="https://ics.services.jamfcloud.com/icon/hash_7320c100c9ca155dc388e143dbc05620907e2d17d6bf74a8fb6d6278ece2c2b4" ;;
        *)   macOSIconURL="https://ics.services.jamfcloud.com/icon/hash_4555d9dc8fecb4e2678faffa8bdcf43cba110e81950e07a4ce3695ec2d5579ee" ;;
    esac

    # Download the icon from ${macOSIconURL}
    if [[ -n "${macOSIconURL}" ]]; then
        # notice "Downloading icon from '${macOSIconURL}' …"
        curl -o "/var/tmp/icon.png" "${macOSIconURL}" --silent --show-error --fail
        if [[ "$?" -ne 0 ]]; then
            error "Failed to download the icon from '${macOSIconURL}'."
            icon="/System/Library/CoreServices/Finder.app"
        else
            icon="/var/tmp/icon.png"
        fi
    fi

    if [[ "${swapOverlayAndLogo}" == "1" || "${swapOverlayAndLogo:l}" == "true" || "${swapOverlayAndLogo:l}" == "yes" ]]; then
        tmp="$icon"
        icon="$overlayicon"
        overlayicon="$tmp"
    fi

    # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
    # swiftDialog Variables
    # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

    # swiftDialog Binary Path
    dialogBinary="/usr/local/bin/dialog"



    # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
    # IT Support Variables
    # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

    local defaultSupportTeamName="${supportTeamName:-"IT Support"}"
    setPreferenceValue "supportTeamName" "${supportTeamName_managed}" "${supportTeamName_local}" "${defaultSupportTeamName}"

    local defaultSupportTeamPhone="${supportTeamPhone:-"+1 (801) 555-1212"}"
    setPreferenceValue "supportTeamPhone" "${supportTeamPhone_managed}" "${supportTeamPhone_local}" "${defaultSupportTeamPhone}"

    local defaultSupportTeamEmail="${supportTeamEmail:-"rescue@domain.org"}"
    setPreferenceValue "supportTeamEmail" "${supportTeamEmail_managed}" "${supportTeamEmail_local}" "${defaultSupportTeamEmail}"

    local defaultSupportTeamWebsite="${supportTeamWebsite:-"https://support.domain.org"}"
    setPreferenceValue "supportTeamWebsite" "${supportTeamWebsite_managed}" "${supportTeamWebsite_local}" "${defaultSupportTeamWebsite}"

    local defaultSupportKB="${supportKB:-"KB8675309"}"
    setPreferenceValue "supportKB" "${supportKB_managed}" "${supportKB_local}" "${defaultSupportKB}"

    local defaultInfobuttonaction="https://servicenow.domain.org/support?id=kb_article_view&sysparm_article=${supportKB}"
    setPreferenceValue "infobuttonaction" "${infobuttonaction_managed}" "${infobuttonaction_local}" "${defaultInfobuttonaction}"

    local defaultSupportKBURL="[${supportKB}](${infobuttonaction})"
    setPreferenceValue "supportKBURL" "${supportKBURL_managed}" "${supportKBURL_local}" "${defaultSupportKBURL}"



    # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
    # Title, Message and  Button Variables
    # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

    local defaultTitle="macOS ${titleMessageUpdateOrUpgrade} required"
    setPreferenceValue "title" "${title_managed}" "${title_local}" "${defaultTitle}"
    replacePlaceholders "title"

    local defaultButton1text="${button1text:-"Open Software Update"}"
    setPreferenceValue "button1text" "${button1text_managed}" "${button1text_local}" "${defaultButton1text}"

    local defaultButton2text="${button2text:-"Remind Me Later"}"
    setPreferenceValue "button2text" "${button2text_managed}" "${button2text_local}" "${defaultButton2text}"

    local defaultInfobuttontext="${infobuttontext:-${supportKB}}"
    setPreferenceValue "infobuttontext" "${infobuttontext_managed}" "${infobuttontext_local}" "${defaultInfobuttontext}"

    local defaultAction="${action:-"x-apple.systempreferences:com.apple.preferences.softwareupdate"}"
    printf -v "action" '%s' "${defaultAction}"

    local defaultMessage="**A required macOS ${titleMessageUpdateOrUpgrade:l} is now available**<br>---<br>Happy $( date +'%A' ), ${loggedInUserFirstname}!<br><br>Please ${titleMessageUpdateOrUpgrade:l} to macOS **${ddmVersionString}** to ensure your Mac remains secure and compliant with organizational policies.<br><br>To perform the ${titleMessageUpdateOrUpgrade:l} now, click **${button1text}**, review the on-screen instructions, then click **${softwareUpdateButtonText}**.<br><br>If you are unable to perform this ${titleMessageUpdateOrUpgrade:l} now, click **${button2text}** to be reminded again later.<br><br>However, your device **will automatically restart and ${titleMessageUpdateOrUpgrade:l}** on **${ddmEnforcedInstallDateHumanReadable}** if you have not ${titleMessageUpdateOrUpgrade:l}d before the deadline.<br><br>For assistance, please contact **${supportTeamName}** by clicking the (?) button in the bottom, right-hand corner."
    setPreferenceValue "message" "${message_managed}" "${message_local}" "${defaultMessage}"
    replacePlaceholders "message"



    # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
    # Infobox Variables
    # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

    local defaultInfobox="**Current:** ${installedmacOSVersion}<br><br>**Required:** ${ddmVersionString}<br><br>**Deadline:** ${ddmVersionStringDeadlineHumanReadable}<br><br>**Day(s) Remaining:** ${ddmVersionStringDaysRemaining}"
    setPreferenceValue "infobox" "${infobox_managed}" "${infobox_local}" "${defaultInfobox}"
    replacePlaceholders "infobox"



    # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
    # Help Message Variables
    # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

    local defaultHelpmessage="For assistance, please contact: **${supportTeamName}**<br>- **Telephone:** ${supportTeamPhone}<br>- **Email:** ${supportTeamEmail}<br>- **Website:** ${supportTeamWebsite}<br>- **Knowledge Base Article:** ${supportKBURL}<br><br>**User Information:**<br>- **Full Name:** {userfullname}<br>- **User Name:** {username}<br><br>**Computer Information:**<br>- **Computer Name:** {computername}<br>- **Serial Number:** {serialnumber}<br>- **macOS:** {osversion}<br><br>**Script Information:**<br>- **Dialog:** $(/usr/local/bin/dialog -v)<br>- **Script:** ${scriptVersion}<br>"
    setPreferenceValue "helpmessage" "${helpmessage_managed}" "${helpmessage_local}" "${defaultHelpmessage}"
    replacePlaceholders "helpmessage"
    local defaultHelpimage="qr=${infobuttonaction}"
    setPreferenceValue "helpimage" "${helpimage_managed}" "${helpimage_local}" "${defaultHelpimage}"
    replacePlaceholders "helpimage"

    applyHideRules

}



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Display Reminder Dialog
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

function displayReminderDialog() {

    additionalDialogOptions=("$@")

    notice "Display Reminder Dialog to ${loggedInUser} with additional options: ${additionalDialogOptions}"

    dialogArgs=(
        --title "${title}"
        --message "${message}"
        --icon "${icon}"
        --iconsize 250
        --overlayicon "${overlayicon}"
        --infobox "${infobox}"
        --button1text "${button1text}"
        --button2text "${button2text}"
        --messagefont "size=14"
        --width 800
        --height 600
        "${blurscreen}"
        "${additionalDialogOptions[@]}"
    )

    [[ -n "${infobuttontext}" ]] && dialogArgs+=(--infobuttontext "${infobuttontext}")
    [[ -n "${helpmessage}" ]] && dialogArgs+=(--helpmessage "${helpmessage}")
    [[ -n "${helpimage}" ]] && dialogArgs+=(--helpimage "${helpimage}")

    ${dialogBinary} "${dialogArgs[@]}"

    returncode=$?
    info "Return Code: ${returncode}"

    case ${returncode} in

    0)  ## Process exit code 0 scenario here
        notice "${loggedInUser} clicked ${button1text}"
        if [[ "${action}" == *"systempreferences"* ]]; then
            su - "$(stat -f%Su /dev/console)" -c "open '${action}'"
            notice "Checking if System Settings is open …"
            until osascript -e 'application "System Settings" is running' >/dev/null 2>&1; do
                info "Pending System Settings launch …"
                sleep 0.5
            done
            info "System Settings is open; Telling System Settings to make a guest appearance …"
            su - "$(stat -f%Su /dev/console)" -c '
            timeout=10
            while ((timeout > 0)); do
                if osascript -e "application \"System Settings\" is running" >/dev/null 2>&1; then
                    if osascript -e "tell application \"System Settings\" to activate" >/dev/null 2>&1; then
                        exit 0
                    fi
                fi
                sleep 0.5
                ((timeout--))
            done
            exit 1
            '
        else
            su - "$(stat -f%Su /dev/console)" -c "open '${action}'"
        fi
        quitScript "0"
        ;;

        2)  ## Process exit code 2 scenario here
            notice "${loggedInUser} clicked ${button2text}"
            quitScript "0"
            ;;

        3)  ## Process exit code 3 scenario here
            notice "${loggedInUser} clicked ${infobuttontext}"
            info "Disabling blurscreen, hiding dialog and opening KB article: ${infobuttontext}"
            echo "blurscreen: disable" >> /var/tmp/dialog.log
            echo "hide:" >> /var/tmp/dialog.log
            su \- "$(stat -f%Su /dev/console)" -c "open '${infobuttonaction}'"
            info "Waiting 61 seconds before re-showing dialog …"
            sleep 61
            displayReminderDialog --ontop --moveable
            ;;

        4)  ## Process exit code 4 scenario here
            notice "User allowed timer to expire"
            quitScript "0"
            ;;

        20) ## Process exit code 20 scenario here
            notice "User had Do Not Disturb enabled"
            quitScript "0"
            ;;

        *)  ## Catch all processing
            notice "Something else happened; Exit code: ${returncode}"
            quitScript "${returncode}"
            ;;

    esac

}



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Quit Script (thanks, @bartreadon!)
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

function quitScript() {

    quitOut "Exiting …"

    # Remove overlay icon
    if [[ -f "${icon}" ]] && [[ "${icon}" != "/System/Library/CoreServices/Finder.app" ]]; then
        rm -f "${icon}"
    fi

    # Remove default dialog.log
    rm -f /var/tmp/dialog.log

    quitOut "Keep them movin' blades sharp!"

    exit "${1}"

}



####################################################################################################
#
# Apply Preference Overrides
#
####################################################################################################

loadPreferenceOverrides



####################################################################################################
#
# Pre-flight Checks
#
####################################################################################################

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Pre-flight Check: Client-side Logging
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

if [[ ! -f "${scriptLog}" ]]; then
    touch "${scriptLog}"
    if [[ -f "${scriptLog}" ]]; then
        preFlight "Created specified scriptLog: ${scriptLog}"
    else
        fatal "Unable to create specified scriptLog '${scriptLog}'; exiting.\n\n(Is this script running as 'root' ?)"
    fi
else
    # preFlight "Specified scriptLog '${scriptLog}' exists; writing log entries to it"
fi



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Pre-flight Check: Logging Preamble
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

preFlight "\n\n###\n# $humanReadableScriptName (${scriptVersion})\n# http://snelson.us/ddm\n####\n"
preFlight "Initiating …"



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Pre-flight Check: Confirm script is running as root
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

if [[ $(id -u) -ne 0 ]]; then
    fatal "This script must be run as root; exiting."
fi



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Pre-flight Check: Validate Logged-in System Accounts
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

preFlight "Check for Logged-in System Accounts …"
currentLoggedInUser

counter="1"

until { [[ -n "${loggedInUser}" && "${loggedInUser}" != "loginwindow" ]] || [[ "${counter}" -gt "30" ]]; } ; do

    preFlight "Logged-in User Counter: ${counter}"
    currentLoggedInUser
    sleep 2
    ((counter++))

done

loggedInUserFullname=$( id -F "${loggedInUser}" )
loggedInUserFirstname=$( echo "$loggedInUserFullname" | sed -E 's/^.*, // ; s/([^ ]*).*/\1/' | sed 's/\(.\{25\}\).*/\1…/' | awk '{print ( $0 == toupper($0) ? toupper(substr($0,1,1))substr(tolower($0),2) : toupper(substr($0,1,1))substr($0,2) )}' )
loggedInUserID=$( id -u "${loggedInUser}" )
preFlight "Current Logged-in User First Name (ID): ${loggedInUserFirstname} (${loggedInUserID})"



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Pre-flight Check: Complete
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

preFlight "Complete"



####################################################################################################
#
# Program
#
####################################################################################################

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Installed OS vs. DDM-enforced OS Comparison
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

installedOSvsDDMenforcedOS



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# If Update Required, Display Dialog Window (respecting Display Reminder threshold)
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

if [[ "${versionComparisonResult}" == "Update Required" ]]; then

    # Skip notifications if we're outside the display reminder window (thanks for the suggestion, @kristian!)
    if (( ddmVersionStringDaysRemaining > daysBeforeDeadlineDisplayReminder )); then
        notice "Deadline still ${ddmVersionStringDaysRemaining} days away; skipping reminder until within ${daysBeforeDeadlineDisplayReminder}-day window."
        quitScript "0"
    else
        notice "Within ${daysBeforeDeadlineDisplayReminder}-day reminder window; proceeding with reminder."
    fi

    # Confirm the currently logged-in user is "available" to be reminded
    # If the deadline is more than 24 hours away, and the user has an active Display Assertion, exit the script
    if [[ "${ddmVersionStringDaysRemaining}" -gt 1 ]]; then
        if checkUserDisplaySleepAssertions; then
            notice "No active Display Sleep Assertions detected; proceeding with reminder."
        else
            notice "Presentation still active after ${meetingDelay} minutes; exiting quietly."
            quitScript "0"
        fi
    else
        info "Deadline is within 24 hours; ignoring ${loggedInUser}'s Display Sleep Assertions."
    fi

    # Randomly pause script during its launch hours of 8 a.m. and 4 p.m.; Login pause of 30 to 90 seconds
    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}"

    # Update Required Variables
    updateRequiredVariables

    # Display reminder dialog (with blurscreen, depending on proximity to deadline)
    displayReminderDialog --ontop

else

    info "Version Comparison Result: ${versionComparisonResult}"

fi



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Exit
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

exit 0

ENDOFSCRIPT
) > "${organizationDirectory}/${organizationScriptName}.zsh"

    logComment "${humanReadableScriptName} script created"

    logComment "Setting permissions …"
    chown root:wheel "${organizationDirectory}/${organizationScriptName}.zsh"
    chmod 755 "${organizationDirectory}/${organizationScriptName}.zsh"
    chmod +x "${organizationDirectory}/${organizationScriptName}.zsh"

}



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

function createLaunchDaemon() {

    notice "Create LaunchDaemon"

    logComment "Creating '${launchDaemonPath}' …"

(
cat <<ENDOFLAUNCHDAEMON
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>${launchDaemonLabel}</string>
    <key>UserName</key>
    <string>root</string>
    <key>ProgramArguments</key>
    <array>
        <string>/bin/zsh</string>
        <string>${organizationDirectory}/${organizationScriptName}.zsh</string>
    </array>
    <key>RunAtLoad</key>
    <true/>
    <key>EnvironmentVariables</key>
    <dict>
        <key>PATH</key>
        <string>/usr/bin:/bin:/usr/sbin:/sbin:/usr/local:/usr/local/bin</string>
    </dict>
    <key>StartCalendarInterval</key>
    <array>
        <dict>
            <key>Hour</key>
            <integer>8</integer>
            <key>Minute</key>
            <integer>0</integer>
        </dict>
        <dict>
            <key>Hour</key>
            <integer>16</integer>
            <key>Minute</key>
            <integer>0</integer>
        </dict>
    </array>
    <key>StandardErrorPath</key>
    <string>${scriptLog}</string>
    <key>StandardOutPath</key>
    <string>${scriptLog}</string>
</dict>
</plist>

ENDOFLAUNCHDAEMON
)  > "${launchDaemonPath}"

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

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

}



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# LaunchDaemon Status
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

function launchDaemonStatus() {

    notice "LaunchDaemon Status"
    
    launchDaemonStatus=$( launchctl list | grep "${launchDaemonLabel}" )

    if [[ -n "${launchDaemonStatus}" ]]; then
        logComment "${launchDaemonStatus}"
    else
        logComment "${launchDaemonLabel} is NOT loaded"
    fi

}



####################################################################################################
#
# Pre-flight Checks
#
####################################################################################################

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Pre-flight Check: Client-side Logging
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

if [[ ! -f "${scriptLog}" ]]; then
    touch "${scriptLog}"
    if [[ -f "${scriptLog}" ]]; then
        preFlight "Created specified scriptLog: ${scriptLog}"
    else
        fatal "Unable to create specified scriptLog '${scriptLog}'; exiting.

(Is this script running as 'root' ?)"
    fi
else
    # preFlight "Specified scriptLog '${scriptLog}' exists; writing log entries to it"
fi




# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Pre-flight Check: Logging Preamble
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

preFlight "

###
# $humanReadableScriptName (${scriptVersion})
# http://snelson.us/ddm
#
# Reset Configuration: ${resetConfiguration}
###
"
preFlight "Initiating …"



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Pre-flight Check: Confirm script is running as root
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

if [[ $(id -u) -ne 0 ]]; then
    fatal "This script must be run as root; exiting."
fi


# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Pre-flight Check: Validate / install swiftDialog (Thanks big bunches, @acodega!)
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

function dialogInstall() {

    # Get the URL of the latest PKG From the Dialog GitHub repo
    dialogURL=$(curl -L --silent --fail "https://api.github.com/repos/swiftDialog/swiftDialog/releases/latest" | awk -F '"' "/browser_download_url/ && /pkg\"/ { print \$4; exit }")

    # Expected Team ID of the downloaded PKG
    expectedDialogTeamID="PWA5E9TQ59"

    preFlight "Installing swiftDialog..."

    # Create temporary working directory
    workDirectory=$( basename "$0" )
    tempDirectory=$( mktemp -d "/private/tmp/$workDirectory.XXXXXX" )

    # Download the installer package
    curl --location --silent "$dialogURL" -o "$tempDirectory/Dialog.pkg"

    # Verify the download
    teamID=$(spctl -a -vv -t install "$tempDirectory/Dialog.pkg" 2>&1 | awk '/origin=/ {print $NF }' | tr -d '()')

    # Install the package if Team ID validates
    if [[ "$expectedDialogTeamID" == "$teamID" ]]; then

        installer -pkg "$tempDirectory/Dialog.pkg" -target /
        sleep 2
        dialogVersion=$( /usr/local/bin/dialog --version )
        preFlight "swiftDialog version ${dialogVersion} installed; proceeding..."

    else

        # Display a so-called "simple" dialog if Team ID fails to validate
        osascript -e 'display dialog "Please advise your Support Representative of the following error:

• Dialog Team ID verification failed

" with title "Mac Health Check Error" buttons {"Close"} with icon caution'
        completionActionOption="Quit"
        exitCode="1"
        quitScript

    fi

    # Remove the temporary working directory when done
    rm -Rf "$tempDirectory"

}



function dialogCheck() {

    # Check for Dialog and install if not found
    if [ ! -x "/Library/Application Support/Dialog/Dialog.app" ]; then

        preFlight "swiftDialog not found. Installing..."
        dialogInstall

    else

        dialogVersion=$(/usr/local/bin/dialog --version)
        if [[ "${dialogVersion}" < "${swiftDialogMinimumRequiredVersion}" ]]; then
            
            preFlight "swiftDialog version ${dialogVersion} found but swiftDialog ${swiftDialogMinimumRequiredVersion} or newer is required; updating..."
            dialogInstall
            
        else

            preFlight "swiftDialog version ${dialogVersion} found; proceeding..."

        fi
    
    fi

}



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Pre-flight Check: Complete
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

preFlight "Complete!"



####################################################################################################
#
# Program
#
####################################################################################################

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Validate / install swiftDialog
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

dialogCheck



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Reset Configuration
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

resetConfiguration "${resetConfiguration}"



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Script Validation / Creation
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

notice "Validating Script"

if [[ -f "${organizationDirectory}/${organizationScriptName}.zsh" ]]; then

    logComment "${humanReadableScriptName} script '"${organizationDirectory}/${organizationScriptName}.zsh"' exists"

else

    createDDMOSReminderScript

fi



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# LaunchDaemon Validation / Creation
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

notice "Validating LaunchDaemon"

logComment "Checking for LaunchDaemon '${launchDaemonPath}' …"

if [[ -f "${launchDaemonPath}" ]]; then

    logComment "LaunchDaemon '${launchDaemonPath}' exists"

    launchDaemonStatus

    if [[ -n "${launchDaemonStatus}" ]]; then

        logComment "${launchDaemonLabel} IS loaded"

    else

        logComment "Loading '${launchDaemonLabel}' …"
        launchctl asuser $(id -u) bootstrap gui/$(id -u) "${launchDaemonPath}"
        launchDaemonStatus

    fi

else

    createLaunchDaemon

fi



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Status Checks
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

notice "Status Checks"

logComment "I/O pause …"
sleep 1.3

launchDaemonStatus



# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Exit
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

exit 0
  1. Monitor the client-side log file (using Control-C to break)
tail -f /var/log/org.churchofjesuschrist.log 
  1. Kickstart the DDM OS Reminder LaunchDaemon (elevated privileges required)
launchctl kickstart -kp system/org.churchofjesuschrist.dor
  1. In your preferred code editor, modify the various string values in the sample org.churchofjesuschrist.dorm.plist to suit your organization, being careful to preserve the XML-escaped text:
    • Logging
      • ScriptLog
    • Branding
      • OrganizationOverlayIconURL
    • Support
      • SupportTeamName
      • SupportTeamPhone
      • SupportTeamEmail
      • SupportTeamWebsite
      • SupportKB
      • InfoButtonAction
      • SupportKBURL
    • Dialog
      • Title
      • Button1Text
      • Button2Text (set to hide to supress displaying)
      • InfoButtonText (set to hide to supress displaying)
      • Message
    • Infobox
      • InfoBox
    • Help
      • HelpMessage
      • HelpImage (set to hide to supress displaying)
  2. Distribute an updated Configuration Profile with your customized .plist to your test Mac
  1. Kickstart the DDM OS Reminder LaunchDaemon (elevated privileges required)
launchctl kickstart -kp system/org.churchofjesuschrist.dor
  1. Monitor the client-side log file (using Control-C to break)
tail -f /var/log/org.churchofjesuschrist.log 
  1. Execute the script under xtrace with a custom prompt; once the reminder dialog appears, the last several output blocks tend to be the most informative:
PS4='+%3l:%I → ' zsh -x /Library/Management/org.churchofjesuschrist/dor.zsh 2>&1
3. Advanced Deployment

An advanced deployment of DDM OS Reminder leverages a custom preference domain, which is identically referenced in all of your customized files

  1. Open Terminal and change to your Downloads directory
cd ~/Downloads
  1. Clone the DDM OS Reminder repository to your Downloads directory:
git clone https://github.com/dan-snelson/DDM-OS-Reminder.git
  1. Change to the DDM-OS-Reminder directory:
cd DDM-OS-Reminder
  1. Determine the current Reverse Domain Name Notation value (i.e., reverseDomainNameNotation)
git grep --no-recursive 'reverseDomainNameNotation='
  1. Modify the following command to search for the current Reverse Domain Name Notation value (i.e., currentRDNN) and replace all matches with your organization’s preferred value (i.e., newRDNN)
git grep -z -l 'currentRDNN' | xargs -0 perl -pi -e 's/currentRDNN/newRDNN/g'
  1. Confirm the changes:
git grep -n 'newRDNN'
  1. Rename a copy of org.churchofjesuschrist.dorm.plist to match your organization’s Reverse Domain Name Notation value (i.e., newRDNN)
  1. In your preferred code editor, modify the various string values for your organization, being careful to preserve the XML-escaped text:
    • Logging
      • ScriptLog
    • Branding
      • OrganizationOverlayIconURL
    • Support
      • SupportTeamName
      • SupportTeamPhone
      • SupportTeamEmail
      • SupportTeamWebsite
      • SupportKB
      • InfoButtonAction
      • SupportKBURL
    • Dialog
      • Title
      • Button1Text
      • Button2Text (set to hide to supress displaying)
      • InfoButtonText (set to hide to supress displaying)
      • Message
    • Infobox
      • InfoBox
    • Help
      • HelpMessage
      • HelpImage (set to hide to supress displaying)
  2. Distribute a Configuration Profile with your customized .plist to your test Mac
  3. Kickstart your DDM OS Reminder LaunchDaemon (elevated privileges required)
launchctl kickstart -kp system/com.company.dor
  1. Monitor your client-side log file
tail -f /var/log/com.company.log 
  1. Execute your script under xtrace with a custom prompt; once the reminder dialog appears, the last several output blocks tend to be the most informative:
PS4='+%3l:%I → ' zsh -x /Library/Management/com.company/dor.zsh 2>&1
3.1 Optional Advanced Deployment

The following optional steps will help you fine-tune your reminderDialog.zsh script using familiar Markdown, which is then easily converted to XML-escaped strings by the createPlist.zsh script.

Additionally, instructions are provided for customizing the LaunchDaemon to meet your organizational requirement.

  1. Edit reminderDialog.zsh as desired (which contains the logic and sample code to dynamically display the reminder dialog)
    • Search for the various local default variables and update for your organization (there are more than a dozen throughout various locations in the script)
…

    # Organization's Overlayicon URL
    local defaultOverlayiconURL="${organizationOverlayiconURL:-"https://usw2.ics.services.jamfcloud.com/icon/hash_4804203ac36cbd7c83607487f4719bd4707f2e283500f54428153af17da082e2"}"

…

    # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
    # IT Support Variables
    # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

    local defaultSupportTeamName="${supportTeamName:-"IT Support"}"
    setPreferenceValue "supportTeamName" "${supportTeamName_managed}" "${supportTeamName_local}" "${defaultSupportTeamName}"

    local defaultSupportTeamPhone="${supportTeamPhone:-"+1 (801) 555-1212"}"
    setPreferenceValue "supportTeamPhone" "${supportTeamPhone_managed}" "${supportTeamPhone_local}" "${defaultSupportTeamPhone}"

    local defaultSupportTeamEmail="${supportTeamEmail:-"rescue@domain.org"}"
    setPreferenceValue "supportTeamEmail" "${supportTeamEmail_managed}" "${supportTeamEmail_local}" "${defaultSupportTeamEmail}"

    local defaultSupportTeamWebsite="${supportTeamWebsite:-"https://support.domain.org"}"
    setPreferenceValue "supportTeamWebsite" "${supportTeamWebsite_managed}" "${supportTeamWebsite_local}" "${defaultSupportTeamWebsite}"

    local defaultSupportKB="${supportKB:-"KB8675309"}"
    setPreferenceValue "supportKB" "${supportKB_managed}" "${supportKB_local}" "${defaultSupportKB}"

    local defaultInfobuttonaction="https://servicenow.domain.org/support?id=kb_article_view&sysparm_article=${supportKB}"
    setPreferenceValue "infobuttonaction" "${infobuttonaction_managed}" "${infobuttonaction_local}" "${defaultInfobuttonaction}"

    local defaultSupportKBURL="[${supportKB}](${infobuttonaction})"
    setPreferenceValue "supportKBURL" "${supportKBURL_managed}" "${supportKBURL_local}" "${defaultSupportKBURL}"



    # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
    # Title, Message and  Button Variables
    # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

    local defaultTitle="macOS ${titleMessageUpdateOrUpgrade} required"
    setPreferenceValue "title" "${title_managed}" "${title_local}" "${defaultTitle}"
    replacePlaceholders "title"

    local defaultButton1text="${button1text:-"Open Software Update"}"
    setPreferenceValue "button1text" "${button1text_managed}" "${button1text_local}" "${defaultButton1text}"

    local defaultButton2text="${button2text:-"Remind Me Later"}"
    setPreferenceValue "button2text" "${button2text_managed}" "${button2text_local}" "${defaultButton2text}"

    local defaultInfobuttontext="${infobuttontext:-${supportKB}}"
    setPreferenceValue "infobuttontext" "${infobuttontext_managed}" "${infobuttontext_local}" "${defaultInfobuttontext}"

    local defaultAction="${action:-"x-apple.systempreferences:com.apple.preferences.softwareupdate"}"
    printf -v "action" '%s' "${defaultAction}"

    local defaultMessage="**A required macOS ${titleMessageUpdateOrUpgrade:l} is now available**<br>---<br>Happy $( date +'%A' ), ${loggedInUserFirstname}!<br><br>Please ${titleMessageUpdateOrUpgrade:l} to macOS **${ddmVersionString}** to ensure your Mac remains secure and compliant with organizational policies.<br><br>To perform the ${titleMessageUpdateOrUpgrade:l} now, click **${button1text}**, review the on-screen instructions, then click **${softwareUpdateButtonText}**.<br><br>If you are unable to perform this ${titleMessageUpdateOrUpgrade:l} now, click **${button2text}** to be reminded again later.<br><br>However, your device **will automatically restart and ${titleMessageUpdateOrUpgrade:l}** on **${ddmEnforcedInstallDateHumanReadable}** if you have not ${titleMessageUpdateOrUpgrade:l}d before the deadline.<br><br>For assistance, please contact **${supportTeamName}** by clicking the (?) button in the bottom, right-hand corner."
    setPreferenceValue "message" "${message_managed}" "${message_local}" "${defaultMessage}"
    replacePlaceholders "message"



    # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
    # Infobox Variables
    # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

    local defaultInfobox="**Current:** ${installedmacOSVersion}<br><br>**Required:** ${ddmVersionString}<br><br>**Deadline:** ${ddmVersionStringDeadlineHumanReadable}<br><br>**Day(s) Remaining:** ${ddmVersionStringDaysRemaining}"
    setPreferenceValue "infobox" "${infobox_managed}" "${infobox_local}" "${defaultInfobox}"
    replacePlaceholders "infobox"



    # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
    # Help Message Variables
    # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

    local defaultHelpmessage="For assistance, please contact: **${supportTeamName}**<br>- **Telephone:** ${supportTeamPhone}<br>- **Email:** ${supportTeamEmail}<br>- **Website:** ${supportTeamWebsite}<br>- **Knowledge Base Article:** ${supportKBURL}<br><br>**User Information:**<br>- **Full Name:** {userfullname}<br>- **User Name:** {username}<br><br>**Computer Information:**<br>- **Computer Name:** {computername}<br>- **Serial Number:** {serialnumber}<br>- **macOS:** {osversion}<br><br>**Script Information:**<br>- **Dialog:** $(/usr/local/bin/dialog -v)<br>- **Script:** ${scriptVersion}<br>"
    setPreferenceValue "helpmessage" "${helpmessage_managed}" "${helpmessage_local}" "${defaultHelpmessage}"
    replacePlaceholders "helpmessage"
    local defaultHelpimage="qr=${infobuttonaction}"
    setPreferenceValue "helpimage" "${helpimage_managed}" "${helpimage_local}" "${defaultHelpimage}"
    replacePlaceholders "helpimage"

…
  1. Deploy your customized reminderDialog.zsh script to a test Mac and confirm your edits work as intended
  2. Execute the zsh Resources/createPlist.zsh script to create a customized .plist and .mobileconfig based on your changes to reminderDialog.zsh
    • Carefully validate the file you’ll be deploying
  3. Deploy your carefully validated .plist or .mobileconfig to your test Mac
  4. Edit launchDaemonManagement.zsh as desired (which writes your customized reminderDialog.zsh client-side and creates your customized LaunchDaemon)
    • Modify StartCalendarInterval for your organization; 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. Execute zsh assemble.zsh to combine the two scripts into a MDM-deployable Resources/ddm-os-reminder-assembled-<timestamp>.zsh.
    • The  zsh Resources/createSelfExtracting.zsh script creates a self-extracting script (which is easier to deploy for some MDMs)
  2. Deploy the freshly generated Resources/ddm-os-reminder-assembled-<timestamp>.zsh to your test Mac
4. Upgrading

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

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