#!/bin/bash
# S.U.P.E.R.M.A.N.
# Software Update/Upgrade Policy Enforcement (with) Recursive Messaging And Notification
# https://github.com/Macjutsu/super
# by Kevin M. White
# The next line disables specific ShellCheck codes (https://github.com/koalaman/shellcheck) for the entire script.
# shellcheck disable=SC2012,SC2024,SC2207
SUPER_VERSION="5.0.0"
readonly SUPER_VERSION
SUPER_DATE="2024/10/17"
readonly SUPER_DATE
# MARK: *** Documentation ***
################################################################################
# Show usage documentation.
show_usage() {
echo "
S.U.P.E.R.M.A.N.
Software Update/Upgrade Policy Enforcement (with) Recursive
Messaging And Notification
Version ${SUPER_VERSION}
${SUPER_DATE}
https://github.com/Macjutsu/super
Usage:
sudo ./super
Installation Options:
[--install-macos-major-upgrades] [--install-macos-major-upgrades-off]
[--install-macos-major-version-target=number]
[--install-rapid-security-responses] [--install-rapid-security-responses-off]
[--install-non-system-updates-without-restarting]
[--install-non-system-updates-without-restarting-off]
[--install-jamf-policy-triggers=PolicyTrigger,PolicyTrigger,etc...]
[--install-jamf-policy-triggers-without-restarting]
[--install-jamf-policy-triggers-without-restarting-off]
Workflow Options:
[--workflow-install-now] [--workflow-install-now-off]
[--workflow-only-download] [--workflow-only-download-off]
[--workflow-restart-without-updates] [--workflow-restart-without-updates-off]
[--workflow-disable-update-check] [--workflow-disable-update-check-off]
[--workflow-disable-relaunch] [--workflow-disable-relaunch-off]
[--workflow-reset-super-after-completion]
Deferral Timer Options:
[--deferral-timer-default=minutes]
[--deferral-timer-menu=minutes,minutes,etc...]
[--deferral-timer-focus=minutes] [--deferral-timer-error=minutes]
[--deferral-timer-workflow-relaunch=minutes] [--deferral-timer-reset-all]
Scheduling Options:
[--schedule-workflow-active=DAY:hh:mm-hh:mm,DAY:hh:mm-hh:mm,etc...]
[--schedule-zero-date-release] [--schedule-zero-date-release-off]
[--schedule-zero-date-sofa-custom-url=URL]
[--schedule-zero-date-manual=YYYY-MM-DD:hh:mm]
[--scheduled-install-days=number] [--scheduled-install-date=YYYY-MM-DD:hh:mm]
[--scheduled-install-user-choice] [--scheduled-install-user-choice-off]
[--scheduled-install-reminder=minutes,minutes,etc...]
[--scheduled-install-delete-all]
Deadline COUNT Options:
[--deadline-count-focus=number] [--deadline-count-soft=number]
[--deadline-count-hard=number] [--deadline-count-restart-all]
[--deadline-count-delete-all]
Deadline DAYS Options:
[--deadline-days-focus=number] [--deadline-days-soft=number]
[--deadline-days-hard=number] [--deadline-days-restart-all]
[--deadline-days-delete-all]
Deadline DATE Options:
[--deadline-date-focus=YYYY-MM-DD:hh:mm]
[--deadline-date-soft=YYYY-MM-DD:hh:mm]
[--deadline-date-hard=YYYY-MM-DD:hh:mm] [--deadline-date-delete-all]
Display Behavior Options:
[--dialog-timeout-default=seconds] [--dialog-timeout-user-auth=seconds]
[--dialog-timeout-user-choice=seconds]
[--dialog-timeout-user-schedule=seconds]
[--dialog-timeout-soft-deadline=seconds]
[--dialog-timeout-insufficient-storage=seconds]
[--dialog-timeout-power-required=seconds] [--dialog-timeout-delete-all]
[--display-unmovable=ALWAYS,DIALOG,DEADLINE,SCHEDULED,INSTALLNOW,ERROR]
[--display-hide-background=ALWAYS,DIALOG,DEADLINE,SCHEDULED,INSTALLNOW,ERROR]
[--display-silently=ALWAYS,DIALOG,DEADLINE,SCHEDULED,INSTALLNOW,ERROR]
[--display-hide-progress-bar=ALWAYS,DEADLINE,SCHEDULED,INSTALLNOW,ERROR]
[--display-notifications-centered=ALWAYS,DEADLINE,SCHEDULED,INSTALLNOW,ERROR]
Display Interface Options:
[--display-icon-size=pixels] [--display-icon-file=/local/path or URL]
[--display-icon-light-file=/local/path or URL]
[--display-icon-dark-file=/local/path or URL]
[--display-accessory-type=TEXTBOX|HTMLBOX|HTML|IMAGE|VIDEO|VIDEOAUTO]
[--display-accessory-default-file=/local/path or URL]
[--display-accessory-macos-minor-update-file=/local/path or URL]
[--display-accessory-macos-major-upgrade-file=/local/path or URL]
[--display-accessory-non-system-updates-file=/local/path or URL]
[--display-accessory-jamf-policy-triggers-file=/local/path or URL]
[--display-accessory-restart-without-updates-file=/local/path or URL]
[--display-help-button-string=plain text or URL]
[--display-warning-button-string=plain text or URL]
Apple Silicon Authentication Options:
[--auth-ask-user-to-save-password] [--auth-ask-user-to-save-password-off]
[--auth-local-account=AccountName] [--auth-local-password=Password]
[--auth-service-add-via-admin-account=AccountName]
[--auth-service-add-via-admin-password=Password]
[--auth-service-account=AccountName] [--auth-service-password=Password]
[--auth-jamf-client=ClientID] [--auth-jamf-secret=ClientSecret]
[--auth-jamf-account=AccountName] [--auth-jamf-password=Password]
[--auth-jamf-custom-url=URL] [--auth-delete-all]
[--auth-credential-failover-to-user] [--auth-credential-failover-to-user-off]
[--auth-mdm-failover-to-user=ALWAYS,DIALOG,DEADLINE,SCHEDULED,INSTALLNOW,ERROR]
Test Mode Options:
[--test-mode] [--test-mode-off] [--test-mode-timeout=seconds]
[--test-storage-update=gigabytes] [--test-storage-upgrade=gigabytes]
[--test-battery-level=percentage]
Troubleshooting and Documentation Options:
[--open-logs] [--reset-super] [--verbose-mode] [--verbose-mode-off]
[--usage] [--help]
** Managed preferences override local options via domain: com.macjutsu.super
InstallMacOSMajorUpgrades |
InstallMacOSMajorVersionTarget version
InstallRapidSecurityResponses |
InstallNonSystemUpdatesWithoutRestarting |
InstallJamfPolicyTriggers
PolicyTrigger,PolicyTrigger,etc...
InstallJamfPolicyTriggersWithoutRestarting |
WorkflowInstallNow |
WorkflowOnlyDownload |
WorkflowRestartWithoutUpdates |
WorkflowDisableUpdateCheck |
WorkflowDisableRelaunch |
DeferralTimerDefault minutes
DeferralTimerMenu minutes,minutes,etc...
DeferralTimerFocus minutes
DeferralTimerError minutes
DeferralTimerWorkflowRelaunch minutes
ScheduleWorkflowActive
DAY:hh:mm-hh:mm,DAY:hh:mm-hh:mm,etc...
ScheduleZeroDateRelease |
ScheduleZeroDateSOFACustomURL URL
ScheduleZeroDateManual YYYY-MM-DD:hh:mm
ScheduledInstallDays number
ScheduledInstallDate YYYY-MM-DD:hh:mm
ScheduledInstallUserChoice |
ScheduledInstallReminder minutes,minutes,etc...
DeadlineCountFocus number
DeadlineCountSoft number
DeadlineCountHard number
DeadlineDaysFocus number
DeadlineDaysSoft number
DeadlineDaysHard number
DeadlineDateFocus YYYY-MM-DD:hh:mm
DeadlineDateSoft YYYY-MM-DD:hh:mm
DeadlineDateHard YYYY-MM-DD:hh:mm
DialogTimeoutDefault seconds
DialogTimeoutUserAuth seconds
DialogTimeoutUserChoice seconds
DialogTimeoutUserSchedule seconds
DialogTimeoutSoftDeadline seconds
DialogTimeoutInsufficientStorage seconds
DialogTimeoutPowerRequired seconds
DisplayUnmovable
ALWAYS,DIALOG,DEADLINE,SCHEDULED,INSTALLNOW,ERROR
DisplayHideBackground
ALWAYS,DIALOG,DEADLINE,SCHEDULED,INSTALLNOW,ERROR
DisplaySilently
ALWAYS,DIALOG,DEADLINE,SCHEDULED,INSTALLNOW,ERROR
DisplayHideProgressBar
ALWAYS,DEADLINE,SCHEDULED,INSTALLNOW,ERROR
DisplayNotificationsCentered
ALWAYS,DEADLINE,SCHEDULED,INSTALLNOW,ERROR
DisplayIconSize pixels
DisplayIconFile path
DisplayIconLightFile path
DisplayIconDarkFile path
DisplayAccessoryType
TEXTBOX|HTMLBOX|HTML|IMAGE|VIDEO|VIDEOAUTO
DisplayAccessoryDefaultFile path or URL
DisplayAccessoryMacOSMinorUpdateFile path or URL
DisplayAccessoryMacOSMajorUpgradeFile path or URL
DisplayAccessoryNonSystemUpdatesFile path or URL
DisplayAccessoryJamfPolicyTriggersFile path or URL
DisplayAccessoryRestartWithoutUpdatesFile path or URL
DisplayHelpButtonString plain text or URL
DisplayWarningButtonString plain text or URL
AuthAskUserToSavePassword |
AuthJamfCustomURL URL
AuthCredentialFailoverToUser |
AuthMDMFailoverToUser
ALWAYS,DIALOG,DEADLINE,SCHEDULED,INSTALLNOW,ERROR
TestMode |
TestModeTimeout seconds
TestStorageUpdate gigabytes
TestStorageUpgrade gigabytes
TestBatteryLevel percentage
VerboseMode |
** For detailed documentation visit: https://github.com/Macjutsu/super/wiki
** Or use --help to automatically open the S.U.P.E.R.M.A.N. Wiki.
"
# Error log any unrecognized options.
if [[ -n "${unrecognized_options_array[*]}" ]]; then
if [[ $(id -u) -eq 0 ]] && [[ -d "${SUPER_LOG_FOLDER}" ]]; then
log_super "Parameter Error: Unrecognized Options: ${unrecognized_options_array[*]%%=*}"
[[ "${parent_process_is_jamf}" == "TRUE" ]] && log_super "Warning: Note that each Jamf Pro Policy Parameter can only contain a single option."
log_status "Inactive Error: Unrecognized Options: ${unrecognized_options_array[*]%%=*}"
else # super is not running as root or not installed yet.
log_echo "Parameter Error: Unrecognized Options: ${unrecognized_options_array[*]%%=*}"
[[ "${parent_process_is_jamf}" == "TRUE" ]] && log_echo "Warning: Note that each Jamf Pro Policy Parameter can only contain a single option."
fi
fi
log_echo "**** S.U.P.E.R.M.A.N. ${SUPER_VERSION} - EXIT USAGE ****"
exit 0
}
# If there is a real current user then open the S.U.P.E.R.M.A.N. Wiki, otherwise run the show_usage() function.
show_help() {
check_current_user
if [[ "${current_user_account_name}" != "FALSE" ]]; then
log_echo "Status: Opening S.U.P.E.R.M.A.N. Wiki for user account: ${current_user_account_name}."
sudo -u "${current_user_account_name}" open "https://github.com/Macjutsu/super/wiki" &
log_echo "**** S.U.P.E.R.M.A.N. ${SUPER_VERSION} - EXIT HELP ****"
else # No current GUI user.
log_echo "Warning: Unable to open S.U.P.E.R.M.A.N. Wiki because there is no GUI user."
show_usage
fi
exit 0
}
# MARK: *** Parameters ***
################################################################################
# Set default parameters that are used throughout the script.
set_defaults() {
# Path to the super working folder:
SUPER_FOLDER="/Library/Management/super"
readonly SUPER_FOLDER
# Path to the super Symbolic link in default binary folder.
SUPER_LINK="/usr/local/bin/super"
readonly SUPER_LINK
# IMPORTANT DETAIL: Changing this path provides no benefit as this is the default location for most non-system command line tools.
# Path to the super PID file:
SUPER_PID_FILE="/var/run/super.pid"
readonly SUPER_PID_FILE
# IMPORTANT DETAIL: Changing this path provides no benefit as this is the default location for PID files.
# Label name for the super LaunchDaemon.
SUPER_LAUNCH_DAEMON_LABEL="com.macjutsu.super" # No trailing ".plist"
readonly SUPER_LAUNCH_DAEMON_LABEL
# Path to the local property list file:
SUPER_LOCAL_PLIST="${SUPER_FOLDER}/com.macjutsu.super" # No trailing ".plist"
readonly SUPER_LOCAL_PLIST
# Path to the managed property list file:
SUPER_MANAGED_PLIST="/Library/Managed Preferences/com.macjutsu.super" # No trailing ".plist"
readonly SUPER_MANAGED_PLIST
# IMPORTANT DETAIL: While you can customize the identifier "com.macjutsu.super", you must keep the "/Library/Managed Preferences/" folder path intact.
# Path to the super log folder:
SUPER_LOG_FOLDER="${SUPER_FOLDER}/logs"
readonly SUPER_LOG_FOLDER
# Path to the super log archive folder:
SUPER_LOG_ARCHIVE_FOLDER="${SUPER_FOLDER}/logs-archive"
readonly SUPER_LOG_ARCHIVE_FOLDER
# The maximum size (in KB) for any super log that triggers an archival of log files at startup:
SUPER_LOG_ARCHIVE_SIZE=1000
readonly SUPER_LOG_ARCHIVE_SIZE
# Path to the log for the main super workflow:
SUPER_LOG="${SUPER_LOG_FOLDER}/super.log"
readonly SUPER_LOG
# Path to the log for the current "mdmclient AvailableOSUpdates" command response:
MDMCLIENT_LIST_LOG="${SUPER_LOG_FOLDER}/mdmclient-list.log"
readonly MDMCLIENT_LIST_LOG
# Path to the log for the current "mist list" command response:
MACOS_INSTALLERS_LIST_LOG="${SUPER_LOG_FOLDER}/macos-installers-list.log"
readonly MACOS_INSTALLERS_LIST_LOG
# Path to the log for the current "softwareupdate --list" command response:
MSU_LIST_LOG="${SUPER_LOG_FOLDER}/msu-list.log"
readonly MSU_LIST_LOG
# URL to the default SOFA macOS machine readable json feed:
SOFA_MACOS_DEFAULT_URL="https://sofafeed.macadmins.io/v1/macos_data_feed.json"
readonly SOFA_MACOS_DEFAULT_URL
# Path to the local copy of the SOFA machine readable feed:
SOFA_MACOS_JSON_CACHE="${SUPER_LOG_FOLDER}/sofa-macos-data-feed.json"
readonly SOFA_MACOS_JSON_CACHE
# Path to the local copy of the SOFA etag cache:
SOFA_MACOS_JSON_ETAG_CACHE="${SUPER_LOG_FOLDER}/sofa-macos-data-feed-etag.txt"
readonly SOFA_MACOS_JSON_ETAG_CACHE
# Path to the log for all softwareupdate download/install workflows:
MSU_WORKFLOW_LOG="${SUPER_LOG_FOLDER}/msu-workflow.log"
readonly MSU_WORKFLOW_LOG
# Path to the log for all macOS installer application download/install workflows:
INSTALLER_WORKFLOW_LOG="${SUPER_LOG_FOLDER}/installer-workflow.log"
readonly INSTALLER_WORKFLOW_LOG
# Path to the log for filtered MDM client command progress:
MDM_COMMAND_LOG="${SUPER_LOG_FOLDER}/mdm-command.log"
readonly MDM_COMMAND_LOG
# Path to the log for debug MDM client command progress:
MDM_COMMAND_DEBUG_LOG="${SUPER_LOG_FOLDER}/mdm-command-debug.log"
readonly MDM_COMMAND_DEBUG_LOG
# Path to the log for filtered MDM update/upgrade workflow progress:
MDM_WORKFLOW_LOG="${SUPER_LOG_FOLDER}/mdm-workflow.log"
readonly MDM_WORKFLOW_LOG
# Path to the log for debug MDM update/upgrade workflow progress:
MDM_WORKFLOW_DEBUG_LOG="${SUPER_LOG_FOLDER}/mdm-workflow-debug.log"
readonly MDM_WORKFLOW_DEBUG_LOG
# Path to the jamf binary:
JAMF_PRO_BINARY="/usr/local/bin/jamf"
readonly JAMF_PRO_BINARY
# IMPORTANT DETAIL: Changing this path provides no benefit as this is the default location for the jamf binary.
# URL to the IBM Notifier.app download:
IBM_NOTIFIER_DOWNLOAD_URL="https://github.com/IBM/mac-ibm-notifications/releases/download/v-3.2.1-b-127/IBM.Notifier.zip"
readonly IBM_NOTIFIER_DOWNLOAD_URL
# Target version for IBM Notifier.app:
IBM_NOTIFIER_TARGET_VERSION="3.2.1"
readonly IBM_NOTIFIER_TARGET_VERSION
# Path to the local IBM Notifier.app:
IBM_NOTIFIER_APP="${SUPER_FOLDER}/IBM Notifier.app"
readonly IBM_NOTIFIER_APP
# IMPORTANT DETAIL: super does not move the IBM Notifier.app to another custom location.
# Changing this folder path to anything besides "${SUPER_FOLDER}/IBM Notifier.app" requires that you must also deploy the IBM Notifier.app to the custom location prior to using super.
# Path to the local IBM Notifier.app binary:
IBM_NOTIFIER_BINARY="${IBM_NOTIFIER_APP}/Contents/MacOS/IBM Notifier"
readonly IBM_NOTIFIER_BINARY
# URL to the mist-cli package installer:
MIST_CLI_DOWNLOAD_URL="https://github.com/ninxsoft/mist-cli/releases/download/v2.1.1/mist-cli.2.1.1.pkg"
readonly MIST_CLI_DOWNLOAD_URL
# Target version for mist-cli:
MIST_CLI_TARGET_VERSION="2.1.1"
readonly MIST_CLI_TARGET_VERSION
# Path to the local mist-cli binary:
MIST_CLI_BINARY="/usr/local/bin/mist"
readonly MIST_CLI_BINARY
# IMPORTANT DETAIL: super does not move the mist-cli binary to another custom location.
# Changing this folder path to anything besides "/usr/local/bin/mist" requires that you must also deploy the mist-cli binary to the custom location prior to using super.
# Path to the local preference file that would contain any software update settings:
MSU_LOCAL_PLIST="/Library/Preferences/com.apple.SoftwareUpdate" # No trailing ".plist"
readonly MSU_LOCAL_PLIST
# IMPORTANT DETAIL: Changing this path provides no benefit as this is the default location for the local software update settings property list file.
# Path to the managed preference file that would contain any automatic software update settings:
MSU_MANAGED_PLIST="/Library/Managed Preferences/com.apple.SoftwareUpdate" # No trailing ".plist"
readonly MSU_MANAGED_PLIST
# IMPORTANT DETAIL: Changing this path provides no benefit as this is the default location for the managed software update settings property list file.
# Path to the managed preference file that would contain any software update deferral restrictions:
APPLICATION_ACCESS_MANAGED_PLIST="/Library/Managed Preferences/com.apple.applicationaccess" # No trailing ".plist"
readonly APPLICATION_ACCESS_MANAGED_PLIST
# IMPORTANT DETAIL: Changing this path provides no benefit as this is the default location for the software update deferral restrictions property list file.
# The default number of minutes to defer the super workflow (except for the relaunch workflow deferral timer).
DEFERRAL_TIMER_DEFAULT_MINUTES=60
readonly DEFERRAL_TIMER_DEFAULT_MINUTES
# The default number of minutes to defer once the super workflow is complete (aka. automatically relaunch the super workflow).
DEFERRAL_TIMER_WORKFLOW_RELAUNCH_DEFAULT_MINUTES=360
readonly DEFERRAL_TIMER_WORKFLOW_RELAUNCH_DEFAULT_MINUTES
# The default number of minutes to defer the super restart validation workflow if there is a workflow error.
DEFERRAL_TIMER_RESTART_VALIDATION_ERROR_MINUTES=5
readonly DEFERRAL_TIMER_RESTART_VALIDATION_ERROR_MINUTES
# The the minimum number of minutes in the future a user can select a scheduled install.
DIALOG_USER_SCHEDULE_MINIMUM_SELECTION_MINUTES=2
readonly DIALOG_USER_SCHEDULE_MINIMUM_SELECTION_MINUTES
# Default icon size for dialogs and notifications.
DISPLAY_ICON_DEFAULT_SIZE=96
readonly DISPLAY_ICON_DEFAULT_SIZE
# Path to for the local cached light mode display icon (the file type must be set to .png):
DISPLAY_ICON_LIGHT_FILE_CACHE="${SUPER_FOLDER}/icon-light.png"
readonly DISPLAY_ICON_LIGHT_FILE_CACHE
# Path to for the local cached dark mode display icon (the file type must be set to .png):
DISPLAY_ICON_DARK_FILE_CACHE="${SUPER_FOLDER}/icon-dark.png"
readonly DISPLAY_ICON_DARK_FILE_CACHE
# The default icon in the if no ${display_icon_light_file_path} or ${display_icon_dark_file_path} is specified or found.
DISPLAY_ICON_DEFAULT_FILE="/System/Library/PrivateFrameworks/SoftwareUpdate.framework/Versions/Current/Resources/SoftwareUpdate.icns"
readonly DISPLAY_ICON_DEFAULT_FILE
# Deadline date display format.
DISPLAY_STRING_FORMAT_DATE="%a %b %d" # Formatting options can be found in the man page for the date command.
readonly DISPLAY_STRING_FORMAT_DATE
# Deadline date/time separator display string.
DISPLAY_STRING_FORMAT_DATE_TIME_SEPARATOR=" "
readonly DISPLAY_STRING_FORMAT_DATE_TIME_SEPARATOR
# Deadline time display format.
DISPLAY_STRING_FORMAT_TIME="%l:%M %p" # Formatting options can be found in the man page for the date command.
readonly DISPLAY_STRING_FORMAT_TIME
# The default minium storage space in gigabytes required for a macOS minor update.
STORAGE_REQUIRED_UPDATE_DEFAULT_GB=15
readonly STORAGE_REQUIRED_UPDATE_DEFAULT_GB
# The default minium storage space in gigabytes required for a macOS major upgrade.
STORAGE_REQUIRED_UPGRADE_DEFAULT_GB=25
readonly STORAGE_REQUIRED_UPGRADE_DEFAULT_GB
# The number of seconds between storage checks when displaying the insufficient storage notification via the dialog_insufficient_storage() function.
STORAGE_REQUIRED_RECHECK_SECONDS=5
readonly STORAGE_REQUIRED_RECHECK_SECONDS
# The default battery level percentage required for a macOS software update/upgrade on Mac computers with Apple Silicon.
POWER_REQUIRED_BATTERY_APPLE_SILICON_PERCENT=20
readonly POWER_REQUIRED_BATTERY_APPLE_SILICON_PERCENT
# The default battery level percentage required for a macOS software update/upgrade on Mac computers with Intel.
POWER_REQUIRED_BATTERY_INTEL_PERCENT=50
readonly POWER_REQUIRED_BATTERY_INTEL_PERCENT
# The number of seconds between AC power checks when displaying the insufficient battery notification via the dialog_insufficient_storage() function.
POWER_REQUIRED_RECHECK_SECONDS=1
readonly POWER_REQUIRED_RECHECK_SECONDS
# The number of seconds to timeout various workflow startup processes if no progress is reported.
TIMEOUT_START_SECONDS=120
readonly TIMEOUT_START_SECONDS
# The number of seconds to timeout the macOS 11+ softwareupdate download/prepare workflow if no progress is reported.
TIMEOUT_MSU_SYSTEM_SECONDS=1200
readonly TIMEOUT_MSU_SYSTEM_SECONDS
# The number of seconds to timeout the softwareupdate non-system update workflow if no progress is reported.
TIMEOUT_non_system_msu_SECONDS=600
readonly TIMEOUT_non_system_msu_SECONDS
# The number of seconds to timeout the macOS installer download workflow if no progress is reported.
TIMEOUT_INSTALLER_DOWNLOAD_SECONDS=300
readonly TIMEOUT_INSTALLER_DOWNLOAD_SECONDS
# The number of seconds to timeout the macOS installation workflow if no progress is reported.
TIMEOUT_INSTALLER_WORKFLOW_SECONDS=600
readonly TIMEOUT_INSTALLER_DOWNLOAD_SECONDS
# The number of seconds to timeout MDM commands if no response is reported.
TIMEOUT_MDM_COMMAND_SECONDS=300
readonly TIMEOUT_MDM_COMMAND_SECONDS
# The number of seconds to timeout the MDM download/prepare workflow if no progress is reported.
TIMEOUT_MDM_WORKFLOW_SECONDS=600
readonly TIMEOUT_MDM_WORKFLOW_SECONDS
# The default amount of time in seconds to leave test notifications and dialogs open before moving on with the test mode workflow.
TEST_MODE_DEFAULT_TIMEOUT=10
readonly TEST_MODE_DEFAULT_TIMEOUT
# Various regular expressions used for parameter validation.
REGEX_MACOS_MAJOR_VERSION="^([1][1-9])$"
readonly REGEX_MACOS_MAJOR_VERSION
REGEX_ANY_WHOLE_NUMBER="^[0-9]+$"
readonly REGEX_ANY_WHOLE_NUMBER
REGEX_CSV_WHOLE_NUMBERS="^[0-9*,]+$"
readonly REGEX_CSV_WHOLE_NUMBERS
REGEX_HOURS_MINUTES="^(2[0-3]|[01][0-9]):[0-5][0-9]$"
readonly REGEX_HOURS_MINUTES
REGEX_DATE_HOURS_MINUTES="^[0-9][0-9][0-9][0-9]-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1]):(2[0-3]|[01][0-9]):[0-5][0-9]$"
readonly REGEX_DATE_HOURS_MINUTES
REGEX_DATE_HOURS_MINUTES_SECONDS="^[0-9][0-9][0-9][0-9]-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1]):(2[0-3]|[01][0-9]):[0-5][0-9]:[0-5][0-9]$"
readonly REGEX_DATE_HOURS_MINUTES_SECONDS
REGEX_DATE="^[0-9][0-9][0-9][0-9]-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])$"
readonly REGEX_DATE
REGEX_WEEKDAY="^(MON|TUE|WED|THU|FRI|SAT|SUN)$"
readonly REGEX_WEEKDAY
REGEX_HOURS_MINUTES_RANGE="^(2[0-3]|[01][0-9]):[0-5][0-9]-(2[0-3]|[01][0-9]):[0-5][0-9]$"
readonly REGEX_HOURS_MINUTES_RANGE
REGEX_schedule_workflow_active_time_frame="^(MON|TUE|WED|THU|FRI|SAT|SUN):(2[0-3]|[01][0-9]):[0-5][0-9]-(2[0-3]|[01][0-9]):[0-5][0-9]$"
readonly REGEX_schedule_workflow_active_time_frame
REGEX_HTML_URL="^http:\/\/|^https:\/\/"
readonly REGEX_HTML_URL
REGEX_HTTPS="^https:\/\/.*"
readonly REGEX_HTTPS
REGEX_WORKFLOW_OPTIONS="^ALWAYS$|^DIALOG$|^DEADLINE$|^SCHEDULED$|^INSTALLNOW$|^ERROR$"
readonly REGEX_WORKFLOW_OPTIONS
}
# Collect input options and set associated parameters.
get_options() {
# If super is running via Jamf Pro Policy installation then the first 3 input parameters are skipped.
if [[ "$1" == "/" ]] || [[ $(ps -p "${PPID}" | grep -c -e 'bin/jamf' -e 'jamf/bin' -e '\sjamf\s') -gt 0 ]]; then
shift 3
parent_process_is_jamf="TRUE"
fi
# get_options debug mode.
# log_super "Debug Mode: Function ${FUNCNAME[0]}: @ is:\n$@"
# This is a standard while/case loop to collect all the input parameters.
while [[ -n "$1" ]]; do
case "$1" in
-u | -U | --usage)
show_usage
;;
-h | -H | --help)
show_help
;;
--install-macos-major-upgrades)
install_macos_major_upgrades="TRUE"
;;
--install-macos-major-upgrades-off)
install_macos_major_upgrades="FALSE"
;;
--install-macos-major-version-target=*)
install_macos_major_version_target_option="${1##*=}"
;;
--install-rapid-security-responses)
install_rapid_security_responses_option="TRUE"
;;
--install-rapid-security-responses-off)
install_rapid_security_responses_option="FALSE"
;;
--install-non-system-updates-without-restarting)
install_non_system_updates_without_restarting_option="TRUE"
;;
--install-non-system-updates-without-restarting-off)
install_non_system_updates_without_restarting_option="FALSE"
;;
--install-jamf-policy-triggers=*)
install_jamf_policy_triggers_option="${1##*=}"
;;
--install-jamf-policy-triggers-without-restarting)
install_jamf_policy_triggers_without_restarting_option="TRUE"
;;
--install-jamf-policy-triggers-without-restarting-off)
install_jamf_policy_triggers_without_restarting_option="FALSE"
;;
--workflow-install-now)
workflow_install_now_option="TRUE"
;;
--workflow-install-now-off)
workflow_install_now_option="FALSE"
;;
--workflow-only-download)
workflow_only_download_option="TRUE"
;;
--workflow-only-download-off)
workflow_only_download_option="FALSE"
;;
--workflow-restart-without-updates)
workflow_restart_without_updates_option="TRUE"
;;
--workflow-restart-without-updates-off)
workflow_restart_without_updates_option="FALSE"
;;
--workflow-disable-update-check)
workflow_disable_update_check_option="TRUE"
;;
--workflow-disable-update-check-off)
workflow_disable_update_check_option="FALSE"
;;
--workflow-disable-relaunch)
workflow_disable_relaunch_option="TRUE"
;;
--workflow-disable-relaunch-off)
workflow_disable_relaunch_option="FALSE"
;;
--workflow-reset-super-after-completion)
workflow_reset_super_after_completion_active="TRUE"
;;
--deferral-timer-default=*)
deferral_timer_default_option="${1##*=}"
;;
--deferral-timer-menu=*)
deferral_timer_menu_option="${1##*=}"
;;
--deferral-timer-focus=*)
deferral_timer_focus_option="${1##*=}"
;;
--deferral-timer-error=*)
deferral_timer_error_option="${1##*=}"
;;
--deferral-timer-workflow-relaunch=*)
deferral_timer_workflow_relaunch_option="${1##*=}"
;;
--deferral-timer-reset-all)
deferral_timer_reset_all_option="TRUE"
;;
--schedule-workflow-active=*)
schedule_workflow_active_option="${1##*=}"
;;
--schedule-zero-date-release)
schedule_zero_date_release_option="TRUE"
;;
--schedule-zero-date-release-off)
schedule_zero_date_release_option="FALSE"
;;
--schedule-zero-date-sofa-custom-url=*)
schedule_zero_date_sofa_custom_url_option="${1##*=}"
;;
--schedule-zero-date-manual=*)
schedule_zero_date_manual_option="${1##*=}"
;;
--scheduled-install-days=*)
scheduled_install_days_option="${1##*=}"
;;
--scheduled-install-date=*)
scheduled_install_date_option="${1##*=}"
;;
--scheduled-install-user-choice)
scheduled_install_user_choice_option="TRUE"
;;
--scheduled-install-user-choice-off)
scheduled_install_user_choice_option="FALSE"
;;
--scheduled-install-reminder=*)
scheduled_install_reminder_option="${1##*=}"
;;
--scheduled-install-delete-all)
scheduled_install_delete_all_option="TRUE"
;;
--deadline-count-focus=*)
deadline_count_focus_option="${1##*=}"
;;
--deadline-count-soft=*)
deadline_count_soft_option="${1##*=}"
;;
--deadline-count-hard=*)
deadline_count_hard_option="${1##*=}"
;;
--deadline-count-restart-all)
deadline_count_restart_all_option="TRUE"
;;
--deadline-count-delete-all)
deadline_count_delete_all_option="TRUE"
;;
--deadline-days-focus=*)
deadline_days_focus_option="${1##*=}"
;;
--deadline-days-soft=*)
deadline_days_soft_option="${1##*=}"
;;
--deadline-days-hard=*)
deadline_days_hard_option="${1##*=}"
;;
--deadline-days-restart-all)
deadline_days_restart_all_option="TRUE"
;;
--deadline-days-delete-all)
deadline_days_delete_all_option="TRUE"
;;
--deadline-date-focus=*)
deadline_date_focus_option="${1##*=}"
;;
--deadline-date-soft=*)
deadline_date_soft_option="${1##*=}"
;;
--deadline-date-hard=*)
deadline_date_hard_option="${1##*=}"
;;
--deadline-date-delete-all)
deadline_date_delete_all_option="TRUE"
;;
--dialog-timeout-default=*)
dialog_timeout_default_option="${1##*=}"
;;
--dialog-timeout-user-auth=*)
dialog_timeout_user_auth_option="${1##*=}"
;;
--dialog-timeout-user-choice=*)
dialog_timeout_user_choice_option="${1##*=}"
;;
--dialog-timeout-user-schedule=*)
dialog_timeout_user_schedule_option="${1##*=}"
;;
--dialog-timeout-soft-deadline=*)
dialog_timeout_soft_deadline_option="${1##*=}"
;;
--dialog-timeout-insufficient-storage=*)
dialog_timeout_insufficient_storage_option="${1##*=}"
;;
--dialog-timeout-power-required=*)
dialog_timeout_power_required_option="${1##*=}"
;;
--dialog-timeout-delete-all)
dialog_timeout_delete_all_option="TRUE"
;;
--display-unmovable=*)
display_unmovable_option="${1##*=}"
;;
--display-hide-background=*)
display_hide_background_option="${1##*=}"
;;
--display-silently=*)
display_silently_option="${1##*=}"
;;
--display-hide-progress-bar=*)
display_hide_progress_bar_option="${1##*=}"
;;
--display-notifications-centered=*)
display_notifications_centered_option="${1##*=}"
;;
--display-icon-size=*)
display_icon_size_option="${1##*=}"
;;
--display-icon-file=*)
display_icon_file_option="${1##*=}"
;;
--display-icon-light-file=*)
display_icon_light_file_option="${1##*=}"
;;
--display-icon-dark-file=*)
display_icon_dark_file_option="${1##*=}"
;;
--display-accessory-type=*)
display_accessory_type_option="${1##*=}"
;;
--display-accessory-default-file=*)
display_accessory_default_file_option="${1##*=}"
;;
--display-accessory-macos-minor-update-file=*)
display_accessory_macos_minor_update_file_option="${1##*=}"
;;
--display-accessory-macos-major-upgrade-file=*)
display_accessory_macos_major_upgrade_file_option="${1##*=}"
;;
--display-accessory-non-system-updates-file=*)
display_accessory_non_system_updates_file_option="${1##*=}"
;;
--display-accessory-jamf-policy-triggers-file=*)
display_accessory_jamf_policy_triggers_file_option="${1##*=}"
;;
--display-accessory-restart-without-updates-file=*)
display_accessory_restart_without_updates_file_option="${1##*=}"
;;
--display-help-button-string=*)
display_help_button_string_option="${1##*=}"
;;
--display-warning-button-string=*)
display_warning_button_string_option="${1##*=}"
;;
--auth-ask-user-to-save-password)
auth_ask_user_to_save_password="TRUE"
;;
--auth-ask-user-to-save-password-off)
auth_ask_user_to_save_password="FALSE"
;;
--auth-local-account=*)
auth_local_account_option="${1##*=}"
;;
--auth-local-password=*)
auth_local_password_option="${1##*=}"
;;
--auth-service-add-via-admin-account=*)
auth_service_add_via_admin_account_option="${1##*=}"
;;
--auth-service-add-via-admin-password=*)
auth_service_add_via_admin_password_option="${1##*=}"
;;
--auth-service-account=*)
auth_service_account_option="${1##*=}"
;;
--auth-service-password=*)
auth_service_password_option="${1##*=}"
;;
--auth-jamf-client=*)
auth_jamf_client_option="${1##*=}"
;;
--auth-jamf-secret=*)
auth_jamf_secret_option="${1##*=}"
;;
--auth-jamf-account=*)
auth_jamf_account_option="${1##*=}"
;;
--auth-jamf-password=*)
auth_jamf_password_option="${1##*=}"
;;
--auth-delete-all)
auth_delete_all_option="TRUE"
;;
--auth-jamf-custom-url=*)
auth_jamf_custom_url_option="${1##*=}"
;;
--auth-credential-failover-to-user)
auth_credential_failover_to_user_option="TRUE"
;;
--auth-credential-failover-to-user-off)
auth_credential_failover_to_user_option="FALSE"
;;
--auth-mdm-failover-to-user=*)
auth_mdm_failover_to_user_option="${1##*=}"
;;
-T | --test-mode)
test_mode_option="TRUE"
;;
-t | --test-mode-off)
test_mode_option="FALSE"
;;
--test-mode-timeout=*)
test_mode_timeout_option="${1##*=}"
;;
--test-storage-update=*)
test_storage_update_option="${1##*=}"
;;
--test-storage-upgrade=*)
test_storage_upgrade_option="${1##*=}"
;;
--test-battery-level=*)
test_battery_level_option="${1##*=}"
;;
-l | -L | --open-logs)
open_logs_option="TRUE"
;;
-x | -X | --reset-super)
reset_super_option="TRUE"
;;
-V | --verbose-mode)
verbose_mode_option="TRUE"
;;
-v | --verbose-mode-off)
verbose_mode_option="FALSE"
;;
*)
unrecognized_options_array+=("$1")
;;
esac
shift
done
# Error log any unrecognized options.
[[ -n "${unrecognized_options_array[*]}" ]] && show_usage
}
# Collect any parameters stored in ${SUPER_MANAGED_PLIST} and/or ${SUPER_LOCAL_PLIST}.
get_preferences() {
# First handle any preference deletion requests.
local workflow_reset_super_after_completion_now_local
workflow_reset_super_after_completion_now_local=$(defaults read "${SUPER_LOCAL_PLIST}" WorkflowResetSuperAfterCompletionNow 2>/dev/null)
if [[ "${reset_super_option}" == "TRUE" ]] || [[ "${workflow_reset_super_after_completion_now_local}" -eq 1 ]]; then
log_super "Status: Deleting all local (non-managed and non-authentication) preferences."
# Backup any preferences made before this function and saved authentication preferences first.
local auth_ask_user_to_save_password_backup
auth_ask_user_to_save_password_backup=$(defaults read "${SUPER_LOCAL_PLIST}" AuthAskUserToSavePassword 2>/dev/null)
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_ask_user_to_save_password_backup: ${auth_ask_user_to_save_password_backup}"
local auth_local_account_backup
auth_local_account_backup=$(defaults read "${SUPER_LOCAL_PLIST}" AuthLocalAccount 2>/dev/null)
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_local_account_backup: ${auth_local_account_backup}"
local auth_service_account_backup
auth_service_account_backup=$(defaults read "${SUPER_LOCAL_PLIST}" AuthServiceAccount 2>/dev/null)
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_service_account_backup: ${auth_service_account_backup}"
local auth_jamf_client_backup
auth_jamf_client_backup=$(defaults read "${SUPER_LOCAL_PLIST}" AuthJamfClient 2>/dev/null)
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_jamf_client_backup: ${auth_jamf_client_backup}"
local auth_jamf_account_backup
auth_jamf_account_backup=$(defaults read "${SUPER_LOCAL_PLIST}" AuthJamfAccount 2>/dev/null)
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_jamf_account_backup: ${auth_jamf_account_backup}"
local auth_legacy_local_account_backup
auth_legacy_local_account_backup=$(defaults read "${SUPER_LOCAL_PLIST}" LocalAccount 2>/dev/null)
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_legacy_local_account_backup: ${auth_legacy_local_account_backup}"
local auth_legacy_super_account_backup
auth_legacy_super_account_backup=$(defaults read "${SUPER_LOCAL_PLIST}" SuperAccount 2>/dev/null)
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_legacy_super_account_backup: ${auth_legacy_super_account_backup}"
local auth_legacy_jamf_account_backup
auth_legacy_jamf_account_backup=$(defaults read "${SUPER_LOCAL_PLIST}" JamfAccount 2>/dev/null)
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_legacy_jamf_account_backup: ${auth_legacy_jamf_account_backup}"
# Delete and/or reset locally saved items.
defaults delete "${SUPER_LOCAL_PLIST}"
rm -f "${SUPER_FOLDER}/.WorkflowInstallNow" 2>/dev/null # This is cleaning up a legacy item.
rm -f "${SUPER_FOLDER}/.WorkflowRestartValidate" 2>/dev/null # This is cleaning up a legacy item.
rm -r "${SUPER_FOLDER}/icon.png" 2>/dev/null # This is cleaning up a legacy item.
rm -r "${DISPLAY_ICON_LIGHT_FILE_CACHE}" 2>/dev/null
rm -r "${DISPLAY_ICON_DARK_FILE_CACHE}" 2>/dev/null
check_software_status_required="TRUE" # This will trigger the reset_software_update_status() function.
rm -f "${SOFA_MACOS_JSON_ETAG_CACHE}" 2>/dev/null
rm -f "${SOFA_MACOS_JSON_CACHE}" 2>/dev/null
defaults write "${SUPER_LOCAL_PLIST}" SuperVersion -string "${SUPER_VERSION}"
[[ "${verbose_mode_option}" == "TRUE" ]] && defaults write "${SUPER_LOCAL_PLIST}" VerboseMode -bool true
# Restore any saved preferences from backup.
[[ "${auth_ask_user_to_save_password_backup}" -eq 1 ]] && defaults write "${SUPER_LOCAL_PLIST}" AuthAskUserToSavePassword -bool true
[[ "${auth_local_account_backup}" -eq 1 ]] && defaults write "${SUPER_LOCAL_PLIST}" AuthLocalAccount -bool true
[[ "${auth_service_account_backup}" -eq 1 ]] && defaults write "${SUPER_LOCAL_PLIST}" AuthServiceAccount -bool true
[[ "${auth_jamf_client_backup}" -eq 1 ]] && defaults write "${SUPER_LOCAL_PLIST}" AuthJamfClient -bool true
[[ "${auth_jamf_account_backup}" -eq 1 ]] && defaults write "${SUPER_LOCAL_PLIST}" AuthJamfAccount -bool true
[[ -n "${auth_legacy_local_account_backup}" ]] && defaults write "${SUPER_LOCAL_PLIST}" LocalAccount -string "${auth_legacy_local_account_backup}"
[[ -n "${auth_legacy_super_account_backup}" ]] && defaults write "${SUPER_LOCAL_PLIST}" SuperAccount -string "${auth_legacy_super_account_backup}"
[[ -n "${auth_legacy_jamf_account_backup}" ]] && defaults write "${SUPER_LOCAL_PLIST}" JamfAccount -string "${auth_legacy_jamf_account_backup}"
else # Lesser delete/reset options.
if [[ "${deferral_timer_reset_all_option}" == "TRUE" ]]; then
log_super "Status: Resetting all local deferral timer preferences."
defaults delete "${SUPER_LOCAL_PLIST}" DeferralTimerDefault 2>/dev/null
defaults delete "${SUPER_LOCAL_PLIST}" DeferralTimerMenu 2>/dev/null
defaults delete "${SUPER_LOCAL_PLIST}" DeferralTimerFocus 2>/dev/null
defaults delete "${SUPER_LOCAL_PLIST}" DeferralTimerError 2>/dev/null
defaults delete "${SUPER_LOCAL_PLIST}" DeferralTimerWorkflowRelaunch 2>/dev/null
fi
if [[ "${scheduled_install_delete_all_option}" == "TRUE" ]]; then
log_super "Status: Deleting all scheduled installation preferences."
defaults delete "${SUPER_LOCAL_PLIST}" ScheduledInstallDays 2>/dev/null
defaults delete "${SUPER_LOCAL_PLIST}" ScheduledInstallDate 2>/dev/null
defaults delete "${SUPER_LOCAL_PLIST}" ScheduledInstallUserChoice 2>/dev/null
defaults delete "${SUPER_LOCAL_PLIST}" ScheduledInstallReminder 2>/dev/null
defaults delete "${SUPER_LOCAL_PLIST}" WorkflowScheduledInstall 2>/dev/null
fi
if [[ "${deadline_count_delete_all_option}" == "TRUE" ]]; then
log_super "Status: Deleting all local deadline count preferences."
defaults delete "${SUPER_LOCAL_PLIST}" DeadlineCountFocus 2>/dev/null
defaults delete "${SUPER_LOCAL_PLIST}" DeadlineCountSoft 2>/dev/null
defaults delete "${SUPER_LOCAL_PLIST}" DeadlineCountHard 2>/dev/null
defaults delete "${SUPER_LOCAL_PLIST}" DeadlineCounterFocus 2>/dev/null
defaults delete "${SUPER_LOCAL_PLIST}" DeadlineCounterSoft 2>/dev/null
defaults delete "${SUPER_LOCAL_PLIST}" DeadlineCounterHard 2>/dev/null
fi
if [[ "${deadline_days_delete_all_option}" == "TRUE" ]]; then
log_super "Status: Deleting all local deadline days preferences."
defaults delete "${SUPER_LOCAL_PLIST}" DeadlineDaysFocus 2>/dev/null
defaults delete "${SUPER_LOCAL_PLIST}" DeadlineDaysSoft 2>/dev/null
defaults delete "${SUPER_LOCAL_PLIST}" DeadlineDaysHard 2>/dev/null
fi
if [[ "${deadline_date_delete_all_option}" == "TRUE" ]]; then
log_super "Status: Deleting all local deadline date preferences."
defaults delete "${SUPER_LOCAL_PLIST}" DeadlineDateFocus 2>/dev/null
defaults delete "${SUPER_LOCAL_PLIST}" DeadlineDateSoft 2>/dev/null
defaults delete "${SUPER_LOCAL_PLIST}" DeadlineDateHard 2>/dev/null
fi
if [[ "${dialog_timeout_delete_all_option}" == "TRUE" ]]; then
log_super "Status: Deleting all local dialog timeout preferences."
defaults delete "${SUPER_LOCAL_PLIST}" DialogTimeoutDefault 2>/dev/null
defaults delete "${SUPER_LOCAL_PLIST}" DialogTimeoutUserChoice 2>/dev/null
defaults delete "${SUPER_LOCAL_PLIST}" DialogTimeoutSoftDeadline 2>/dev/null
defaults delete "${SUPER_LOCAL_PLIST}" DialogTimeoutUserAuth 2>/dev/null
defaults delete "${SUPER_LOCAL_PLIST}" DialogTimeoutInsufficientStorage 2>/dev/null
defaults delete "${SUPER_LOCAL_PLIST}" DialogTimeoutPowerRequired 2>/dev/null
fi
fi
# Collect any managed preferences from ${SUPER_MANAGED_PLIST}.
if [[ -f "${SUPER_MANAGED_PLIST}.plist" ]]; then
local install_macos_major_upgrades_managed
install_macos_major_upgrades_managed=$(defaults read "${SUPER_MANAGED_PLIST}" InstallMacOSMajorUpgrades 2>/dev/null)
local install_macos_major_version_target_managed
install_macos_major_version_target_managed=$(defaults read "${SUPER_MANAGED_PLIST}" InstallMacOSMajorVersionTarget 2>/dev/null)
local install_rapid_security_responses_managed
install_rapid_security_responses_managed=$(defaults read "${SUPER_MANAGED_PLIST}" InstallRapidSecurityResponses 2>/dev/null)
local install_non_system_updates_without_restarting_managed
install_non_system_updates_without_restarting_managed=$(defaults read "${SUPER_MANAGED_PLIST}" InstallNonSystemUpdatesWithoutRestarting 2>/dev/null)
local install_jamf_policy_triggers_managed
install_jamf_policy_triggers_managed=$(defaults read "${SUPER_MANAGED_PLIST}" InstallJamfPolicyTriggers 2>/dev/null)
local install_jamf_policy_triggers_without_restarting_managed
install_jamf_policy_triggers_without_restarting_managed=$(defaults read "${SUPER_MANAGED_PLIST}" InstallJamfPolicyTriggersWithoutRestarting 2>/dev/null)
local workflow_install_now_managed
workflow_install_now_managed=$(defaults read "${SUPER_MANAGED_PLIST}" WorkflowInstallNow 2>/dev/null)
local workflow_only_download_managed
workflow_only_download_managed=$(defaults read "${SUPER_MANAGED_PLIST}" WorkflowOnlyDownload 2>/dev/null)
local workflow_restart_without_updates_managed
workflow_restart_without_updates_managed=$(defaults read "${SUPER_MANAGED_PLIST}" WorkflowRestartWithoutUpdates 2>/dev/null)
local workflow_disable_update_check_managed
workflow_disable_update_check_managed=$(defaults read "${SUPER_MANAGED_PLIST}" WorkflowDisableUpdateCheck 2>/dev/null)
local workflow_disable_relaunch_managed
workflow_disable_relaunch_managed=$(defaults read "${SUPER_MANAGED_PLIST}" WorkflowDisableRelaunch 2>/dev/null)
local deferral_timer_default_managed
deferral_timer_default_managed=$(defaults read "${SUPER_MANAGED_PLIST}" DeferralTimerDefault 2>/dev/null)
local deferral_timer_menu_managed
deferral_timer_menu_managed=$(defaults read "${SUPER_MANAGED_PLIST}" DeferralTimerMenu 2>/dev/null)
local deferral_timer_focus_managed
deferral_timer_focus_managed=$(defaults read "${SUPER_MANAGED_PLIST}" DeferralTimerFocus 2>/dev/null)
local deferral_timer_error_managed
deferral_timer_error_managed=$(defaults read "${SUPER_MANAGED_PLIST}" DeferralTimerError 2>/dev/null)
local deferral_timer_workflow_relaunch_managed
deferral_timer_workflow_relaunch_managed=$(defaults read "${SUPER_MANAGED_PLIST}" DeferralTimerWorkflowRelaunch 2>/dev/null)
local schedule_workflow_active_managed
schedule_workflow_active_managed=$(defaults read "${SUPER_MANAGED_PLIST}" ScheduleWorkflowActive 2>/dev/null)
local schedule_zero_date_release_managed
schedule_zero_date_release_managed=$(defaults read "${SUPER_MANAGED_PLIST}" ScheduleZeroDateRelease 2>/dev/null)
local schedule_zero_date_sofa_custom_url_managed
schedule_zero_date_sofa_custom_url_managed=$(defaults read "${SUPER_MANAGED_PLIST}" ScheduleZeroDateSOFACustomURL 2>/dev/null)
local schedule_zero_date_manual_managed
schedule_zero_date_manual_managed=$(defaults read "${SUPER_MANAGED_PLIST}" ScheduleZeroDateManual 2>/dev/null)
local scheduled_install_days_managed
scheduled_install_days_managed=$(defaults read "${SUPER_MANAGED_PLIST}" ScheduledInstallDays 2>/dev/null)
local scheduled_install_date_managed
scheduled_install_date_managed=$(defaults read "${SUPER_MANAGED_PLIST}" ScheduledInstallDate 2>/dev/null)
local scheduled_install_user_choice_managed
scheduled_install_user_choice_managed=$(defaults read "${SUPER_MANAGED_PLIST}" ScheduledInstallUserChoice 2>/dev/null)
local scheduled_install_reminder_managed
scheduled_install_reminder_managed=$(defaults read "${SUPER_MANAGED_PLIST}" ScheduledInstallReminder 2>/dev/null)
local deadline_count_focus_managed
deadline_count_focus_managed=$(defaults read "${SUPER_MANAGED_PLIST}" DeadlineCountFocus 2>/dev/null)
local deadline_count_soft_managed
deadline_count_soft_managed=$(defaults read "${SUPER_MANAGED_PLIST}" DeadlineCountSoft 2>/dev/null)
local deadline_count_hard_managed
deadline_count_hard_managed=$(defaults read "${SUPER_MANAGED_PLIST}" DeadlineCountHard 2>/dev/null)
local deadline_days_focus_managed
deadline_days_focus_managed=$(defaults read "${SUPER_MANAGED_PLIST}" DeadlineDaysFocus 2>/dev/null)
local deadline_days_soft_managed
deadline_days_soft_managed=$(defaults read "${SUPER_MANAGED_PLIST}" DeadlineDaysSoft 2>/dev/null)
local deadline_days_hard_managed
deadline_days_hard_managed=$(defaults read "${SUPER_MANAGED_PLIST}" DeadlineDaysHard 2>/dev/null)
local deadline_date_focus_managed
deadline_date_focus_managed=$(defaults read "${SUPER_MANAGED_PLIST}" DeadlineDateFocus 2>/dev/null)
local deadline_date_soft_managed
deadline_date_soft_managed=$(defaults read "${SUPER_MANAGED_PLIST}" DeadlineDateSoft 2>/dev/null)
local deadline_date_hard_managed
deadline_date_hard_managed=$(defaults read "${SUPER_MANAGED_PLIST}" DeadlineDateHard 2>/dev/null)
local dialog_timeout_default_managed
dialog_timeout_default_managed=$(defaults read "${SUPER_MANAGED_PLIST}" DialogTimeoutDefault 2>/dev/null)
local dialog_timeout_user_auth_managed
dialog_timeout_user_auth_managed=$(defaults read "${SUPER_MANAGED_PLIST}" DialogTimeoutUserAuth 2>/dev/null)
local dialog_timeout_user_choice_managed
dialog_timeout_user_choice_managed=$(defaults read "${SUPER_MANAGED_PLIST}" DialogTimeoutUserChoice 2>/dev/null)
local dialog_timeout_user_schedule_managed
dialog_timeout_user_schedule_managed=$(defaults read "${SUPER_MANAGED_PLIST}" DialogTimeoutUserSchedule 2>/dev/null)
local dialog_timeout_soft_deadline_managed
dialog_timeout_soft_deadline_managed=$(defaults read "${SUPER_MANAGED_PLIST}" DialogTimeoutSoftDeadline 2>/dev/null)
local dialog_timeout_insufficient_storage_managed
dialog_timeout_insufficient_storage_managed=$(defaults read "${SUPER_MANAGED_PLIST}" DialogTimeoutInsufficientStorage 2>/dev/null)
local dialog_timeout_power_required_managed
dialog_timeout_power_required_managed=$(defaults read "${SUPER_MANAGED_PLIST}" DialogTimeoutPowerRequired 2>/dev/null)
local display_unmovable_managed
display_unmovable_managed=$(defaults read "${SUPER_MANAGED_PLIST}" DisplayUnmovable 2>/dev/null)
local display_hide_background_managed
display_hide_background_managed=$(defaults read "${SUPER_MANAGED_PLIST}" DisplayHideBackground 2>/dev/null)
local display_silently_managed
display_silently_managed=$(defaults read "${SUPER_MANAGED_PLIST}" DisplaySilently 2>/dev/null)
local display_hide_progress_bar_managed
display_hide_progress_bar_managed=$(defaults read "${SUPER_MANAGED_PLIST}" DisplayHideProgressBar 2>/dev/null)
local display_notifications_centered_managed
display_notifications_centered_managed=$(defaults read "${SUPER_MANAGED_PLIST}" DisplayNotificationsCentered 2>/dev/null)
local display_icon_size_managed
display_icon_size_managed=$(defaults read "${SUPER_MANAGED_PLIST}" DisplayIconSize 2>/dev/null)
local display_icon_file_managed
display_icon_file_managed=$(defaults read "${SUPER_MANAGED_PLIST}" DisplayIconFile 2>/dev/null)
local display_icon_light_file_managed
display_icon_light_file_managed=$(defaults read "${SUPER_MANAGED_PLIST}" DisplayIconLightFile 2>/dev/null)
local display_icon_dark_file_managed
display_icon_dark_file_managed=$(defaults read "${SUPER_MANAGED_PLIST}" DisplayIconDarkFile 2>/dev/null)
local display_accessory_type_managed
display_accessory_type_managed=$(defaults read "${SUPER_MANAGED_PLIST}" DisplayAccessoryType 2>/dev/null)
local display_accessory_default_file_managed
display_accessory_default_file_managed=$(defaults read "${SUPER_MANAGED_PLIST}" DisplayAccessoryDefaultFile 2>/dev/null)
local display_accessory_macos_minor_update_file_managed
display_accessory_macos_minor_update_file_managed=$(defaults read "${SUPER_MANAGED_PLIST}" DisplayAccessoryMacOSMinorUpdateFile 2>/dev/null)
local display_accessory_macos_minor_update_file_managed
display_accessory_macos_major_upgrade_file_managed=$(defaults read "${SUPER_MANAGED_PLIST}" DisplayAccessoryMacOSMajorUpgradeFile 2>/dev/null)
local display_accessory_non_system_updates_file_managed
display_accessory_non_system_updates_file_managed=$(defaults read "${SUPER_MANAGED_PLIST}" DisplayAccessoryNonSystemUpdatesFile 2>/dev/null)
local display_accessory_jamf_policy_triggers_file_managed
display_accessory_jamf_policy_triggers_file_managed=$(defaults read "${SUPER_MANAGED_PLIST}" DisplayAccessoryJamfPolicyTriggersFile 2>/dev/null)
local display_accessory_restart_without_updates_file_managed
display_accessory_restart_without_updates_file_managed=$(defaults read "${SUPER_MANAGED_PLIST}" DisplayAccessoryRestartWithoutUpdatesFile 2>/dev/null)
local display_help_button_string_managed
display_help_button_string_managed=$(defaults read "${SUPER_MANAGED_PLIST}" DisplayHelpButtonString 2>/dev/null)
local display_warning_button_string_managed
display_warning_button_string_managed=$(defaults read "${SUPER_MANAGED_PLIST}" DisplayWarningButtonString 2>/dev/null)
local auth_user_save_password_managed
auth_user_save_password_managed=$(defaults read "${SUPER_MANAGED_PLIST}" AuthAskUserToSavePassword 2>/dev/null)
local auth_jamf_computer_id_managed
auth_jamf_computer_id_managed=$(defaults read "${SUPER_MANAGED_PLIST}" AuthJamfComputerID 2>/dev/null)
local auth_jamf_custom_url_managed
auth_jamf_custom_url_managed=$(defaults read "${SUPER_MANAGED_PLIST}" AuthJamfCustomURL 2>/dev/null)
local auth_credential_failover_to_user_managed
auth_credential_failover_to_user_managed=$(defaults read "${SUPER_MANAGED_PLIST}" AuthCredentialFailoverToUser 2>/dev/null)
local auth_mdm_failover_to_user_managed
auth_mdm_failover_to_user_managed=$(defaults read "${SUPER_MANAGED_PLIST}" AuthMDMFailoverToUser 2>/dev/null)
local test_mode_managed
test_mode_managed=$(defaults read "${SUPER_MANAGED_PLIST}" TestMode 2>/dev/null)
local test_mode_timeout_managed
test_mode_timeout_managed=$(defaults read "${SUPER_MANAGED_PLIST}" TestModeTimeout 2>/dev/null)
local test_storage_update_managed
test_storage_update_managed=$(defaults read "${SUPER_MANAGED_PLIST}" TestStorageUpdate 2>/dev/null)
local test_storage_upgrade_managed
test_storage_upgrade_managed=$(defaults read "${SUPER_MANAGED_PLIST}" TestStorageUpgrade 2>/dev/null)
local test_battery_level_managed
test_battery_level_managed=$(defaults read "${SUPER_MANAGED_PLIST}" TestBatteryLevel 2>/dev/null)
fi
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: Managed preference file: ${SUPER_MANAGED_PLIST}:\n$(defaults read "${SUPER_MANAGED_PLIST}" 2>/dev/null)"
# Collect any local preferences from ${SUPER_LOCAL_PLIST}.
if [[ -f "${SUPER_LOCAL_PLIST}.plist" ]]; then
local install_macos_major_upgrades_local
install_macos_major_upgrades_local=$(defaults read "${SUPER_LOCAL_PLIST}" InstallMacOSMajorUpgrades 2>/dev/null)
local install_macos_major_version_target_local
install_macos_major_version_target_local=$(defaults read "${SUPER_LOCAL_PLIST}" InstallMacOSMajorVersionTarget 2>/dev/null)
local install_rapid_security_responses_local
install_rapid_security_responses_local=$(defaults read "${SUPER_LOCAL_PLIST}" InstallRapidSecurityResponses 2>/dev/null)
local install_non_system_updates_without_restarting_local
install_non_system_updates_without_restarting_local=$(defaults read "${SUPER_LOCAL_PLIST}" InstallNonSystemUpdatesWithoutRestarting 2>/dev/null)
local install_jamf_policy_triggers_local
install_jamf_policy_triggers_local=$(defaults read "${SUPER_LOCAL_PLIST}" InstallJamfPolicyTriggers 2>/dev/null)
local install_jamf_policy_triggers_without_restarting_local
install_jamf_policy_triggers_without_restarting_local=$(defaults read "${SUPER_LOCAL_PLIST}" InstallJamfPolicyTriggersWithoutRestarting 2>/dev/null)
local workflow_install_now_local
workflow_install_now_local=$(defaults read "${SUPER_LOCAL_PLIST}" WorkflowInstallNow 2>/dev/null)
local workflow_only_download_local
workflow_only_download_local=$(defaults read "${SUPER_LOCAL_PLIST}" WorkflowOnlyDownload 2>/dev/null)
local workflow_restart_without_updates_local
workflow_restart_without_updates_local=$(defaults read "${SUPER_LOCAL_PLIST}" WorkflowRestartWithoutUpdates 2>/dev/null)
local workflow_disable_update_check_local
workflow_disable_update_check_local=$(defaults read "${SUPER_LOCAL_PLIST}" WorkflowDisableUpdateCheck 2>/dev/null)
local workflow_disable_relaunch_local
workflow_disable_relaunch_local=$(defaults read "${SUPER_LOCAL_PLIST}" WorkflowDisableRelaunch 2>/dev/null)
local deferral_timer_default_local
deferral_timer_default_local=$(defaults read "${SUPER_LOCAL_PLIST}" DeferralTimerDefault 2>/dev/null)
local deferral_timer_menu_local
deferral_timer_menu_local=$(defaults read "${SUPER_LOCAL_PLIST}" DeferralTimerMenu 2>/dev/null)
local deferral_timer_focus_local
deferral_timer_focus_local=$(defaults read "${SUPER_LOCAL_PLIST}" DeferralTimerFocus 2>/dev/null)
local deferral_timer_error_local
deferral_timer_error_local=$(defaults read "${SUPER_LOCAL_PLIST}" DeferralTimerError 2>/dev/null)
local deferral_timer_workflow_relaunch_local
deferral_timer_workflow_relaunch_local=$(defaults read "${SUPER_LOCAL_PLIST}" DeferralTimerWorkflowRelaunch 2>/dev/null)
local schedule_workflow_active_local
schedule_workflow_active_local=$(defaults read "${SUPER_LOCAL_PLIST}" ScheduleWorkflowActive 2>/dev/null)
local schedule_zero_date_release_local
schedule_zero_date_release_local=$(defaults read "${SUPER_LOCAL_PLIST}" ScheduleZeroDateRelease 2>/dev/null)
local schedule_zero_date_sofa_custom_url_local
schedule_zero_date_sofa_custom_url_local=$(defaults read "${SUPER_LOCAL_PLIST}" ScheduleZeroDateSOFACustomURL 2>/dev/null)
local schedule_zero_date_manual_local
schedule_zero_date_manual_local=$(defaults read "${SUPER_LOCAL_PLIST}" ScheduleZeroDateManual 2>/dev/null)
local scheduled_install_days_local
scheduled_install_days_local=$(defaults read "${SUPER_LOCAL_PLIST}" ScheduledInstallDays 2>/dev/null)
local scheduled_install_date_local
scheduled_install_date_local=$(defaults read "${SUPER_LOCAL_PLIST}" ScheduledInstallDate 2>/dev/null)
local scheduled_install_user_choice_local
scheduled_install_user_choice_local=$(defaults read "${SUPER_LOCAL_PLIST}" ScheduledInstallUserChoice 2>/dev/null)
local scheduled_install_reminder_local
scheduled_install_reminder_local=$(defaults read "${SUPER_LOCAL_PLIST}" ScheduledInstallReminder 2>/dev/null)
local deadline_count_focus_local
deadline_count_focus_local=$(defaults read "${SUPER_LOCAL_PLIST}" DeadlineCountFocus 2>/dev/null)
local deadline_count_soft_local
deadline_count_soft_local=$(defaults read "${SUPER_LOCAL_PLIST}" DeadlineCountSoft 2>/dev/null)
local deadline_count_hard_local
deadline_count_hard_local=$(defaults read "${SUPER_LOCAL_PLIST}" DeadlineCountHard 2>/dev/null)
local deadline_days_focus_local
deadline_days_focus_local=$(defaults read "${SUPER_LOCAL_PLIST}" DeadlineDaysFocus 2>/dev/null)
local deadline_days_soft_local
deadline_days_soft_local=$(defaults read "${SUPER_LOCAL_PLIST}" DeadlineDaysSoft 2>/dev/null)
local deadline_days_hard_local
deadline_days_hard_local=$(defaults read "${SUPER_LOCAL_PLIST}" DeadlineDaysHard 2>/dev/null)
local deadline_date_focus_local
deadline_date_focus_local=$(defaults read "${SUPER_LOCAL_PLIST}" DeadlineDateFocus 2>/dev/null)
local deadline_date_soft_local
deadline_date_soft_local=$(defaults read "${SUPER_LOCAL_PLIST}" DeadlineDateSoft 2>/dev/null)
local deadline_date_hard_local
deadline_date_hard_local=$(defaults read "${SUPER_LOCAL_PLIST}" DeadlineDateHard 2>/dev/null)
local dialog_timeout_default_local
dialog_timeout_default_local=$(defaults read "${SUPER_LOCAL_PLIST}" DialogTimeoutDefault 2>/dev/null)
local dialog_timeout_user_auth_local
dialog_timeout_user_auth_local=$(defaults read "${SUPER_LOCAL_PLIST}" DialogTimeoutUserAuth 2>/dev/null)
local dialog_timeout_user_choice_local
dialog_timeout_user_choice_local=$(defaults read "${SUPER_LOCAL_PLIST}" DialogTimeoutUserChoice 2>/dev/null)
local dialog_timeout_user_schedule_local
dialog_timeout_user_schedule_local=$(defaults read "${SUPER_LOCAL_PLIST}" DialogTimeoutUserSchedule 2>/dev/null)
local dialog_timeout_soft_deadline_local
dialog_timeout_soft_deadline_local=$(defaults read "${SUPER_LOCAL_PLIST}" DialogTimeoutSoftDeadline 2>/dev/null)
local dialog_timeout_insufficient_storage_local
dialog_timeout_insufficient_storage_local=$(defaults read "${SUPER_LOCAL_PLIST}" DialogTimeoutInsufficientStorage 2>/dev/null)
local dialog_timeout_power_required_local
dialog_timeout_power_required_local=$(defaults read "${SUPER_LOCAL_PLIST}" DialogTimeoutPowerRequired 2>/dev/null)
local display_unmovable_local
display_unmovable_local=$(defaults read "${SUPER_LOCAL_PLIST}" DisplayUnmovable 2>/dev/null)
local display_hide_background_local
display_hide_background_local=$(defaults read "${SUPER_LOCAL_PLIST}" DisplayHideBackground 2>/dev/null)
local display_silently_local
display_silently_local=$(defaults read "${SUPER_LOCAL_PLIST}" DisplaySilently 2>/dev/null)
local display_hide_progress_bar_local
display_hide_progress_bar_local=$(defaults read "${SUPER_LOCAL_PLIST}" DisplayHideProgressBar 2>/dev/null)
local display_notifications_centered_local
display_notifications_centered_local=$(defaults read "${SUPER_LOCAL_PLIST}" DisplayNotificationsCentered 2>/dev/null)
local display_icon_size_local
display_icon_size_local=$(defaults read "${SUPER_LOCAL_PLIST}" DisplayIconSize 2>/dev/null)
local display_icon_file_local
display_icon_file_local=$(defaults read "${SUPER_LOCAL_PLIST}" DisplayIconFile 2>/dev/null)
local display_icon_light_file_local
display_icon_light_file_local=$(defaults read "${SUPER_LOCAL_PLIST}" DisplayIconLightFile 2>/dev/null)
local display_icon_dark_file_local
display_icon_dark_file_local=$(defaults read "${SUPER_LOCAL_PLIST}" DisplayIconDarkFile 2>/dev/null)
local display_accessory_type_local
display_accessory_type_local=$(defaults read "${SUPER_LOCAL_PLIST}" DisplayAccessoryType 2>/dev/null)
local display_accessory_default_file_local
display_accessory_default_file_local=$(defaults read "${SUPER_LOCAL_PLIST}" DisplayAccessoryDefaultFile 2>/dev/null)
local display_accessory_macos_minor_update_file_local
display_accessory_macos_minor_update_file_local=$(defaults read "${SUPER_LOCAL_PLIST}" DisplayAccessoryMacOSMinorUpdateFile 2>/dev/null)
local display_accessory_macos_minor_update_file_local
display_accessory_macos_major_upgrade_file_local=$(defaults read "${SUPER_LOCAL_PLIST}" DisplayAccessoryMacOSMajorUpgradeFile 2>/dev/null)
local display_accessory_non_system_updates_file_local
display_accessory_non_system_updates_file_local=$(defaults read "${SUPER_LOCAL_PLIST}" DisplayAccessoryNonSystemUpdatesFile 2>/dev/null)
local display_accessory_jamf_policy_triggers_file_local
display_accessory_jamf_policy_triggers_file_local=$(defaults read "${SUPER_LOCAL_PLIST}" DisplayAccessoryJamfPolicyTriggersFile 2>/dev/null)
local display_accessory_restart_without_updates_file_local
display_accessory_restart_without_updates_file_local=$(defaults read "${SUPER_LOCAL_PLIST}" DisplayAccessoryRestartWithoutUpdatesFile 2>/dev/null)
local display_help_button_string_local
display_help_button_string_local=$(defaults read "${SUPER_LOCAL_PLIST}" DisplayHelpButtonString 2>/dev/null)
local display_warning_button_string_local
display_warning_button_string_local=$(defaults read "${SUPER_LOCAL_PLIST}" DisplayWarningButtonString 2>/dev/null)
local auth_user_save_password_local
auth_user_save_password_local=$(defaults read "${SUPER_LOCAL_PLIST}" AuthAskUserToSavePassword 2>/dev/null)
local auth_jamf_computer_id_local
auth_jamf_computer_id_local=$(defaults read "${SUPER_LOCAL_PLIST}" AuthJamfComputerID 2>/dev/null)
local auth_jamf_custom_url_local
auth_jamf_custom_url_local=$(defaults read "${SUPER_LOCAL_PLIST}" AuthJamfCustomURL 2>/dev/null)
local auth_credential_failover_to_user_local
auth_credential_failover_to_user_local=$(defaults read "${SUPER_LOCAL_PLIST}" AuthCredentialFailoverToUser 2>/dev/null)
local auth_mdm_failover_to_user_local
auth_mdm_failover_to_user_local=$(defaults read "${SUPER_LOCAL_PLIST}" AuthMDMFailoverToUser 2>/dev/null)
local test_mode_local
test_mode_local=$(defaults read "${SUPER_LOCAL_PLIST}" TestMode 2>/dev/null)
local test_mode_timeout_local
test_mode_timeout_local=$(defaults read "${SUPER_LOCAL_PLIST}" TestModeTimeout 2>/dev/null)
local test_storage_update_local
test_storage_update_local=$(defaults read "${SUPER_LOCAL_PLIST}" TestStorageUpdate 2>/dev/null)
local test_storage_upgrade_local
test_storage_upgrade_local=$(defaults read "${SUPER_LOCAL_PLIST}" TestStorageUpgrade 2>/dev/null)
local test_battery_level_local
test_battery_level_local=$(defaults read "${SUPER_LOCAL_PLIST}" TestBatteryLevel 2>/dev/null)
fi
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: Local preference file before startup validation: ${SUPER_LOCAL_PLIST}:\n$(defaults read "${SUPER_LOCAL_PLIST}" 2>/dev/null)"
# This logic ensures the priority order of managed preference overrides the new input option which overrides the saved local preference.
[[ -n "${install_macos_major_upgrades_managed}" ]] && install_macos_major_upgrades="${install_macos_major_upgrades_managed}"
{ [[ -z "${install_macos_major_upgrades_managed}" ]] && [[ -z "${install_macos_major_upgrades}" ]] && [[ -n "${install_macos_major_upgrades_local}" ]]; } && install_macos_major_upgrades="${install_macos_major_upgrades_local}"
[[ -n "${install_macos_major_version_target_managed}" ]] && install_macos_major_version_target_option="${install_macos_major_version_target_managed}"
{ [[ -z "${install_macos_major_version_target_managed}" ]] && [[ -z "${install_macos_major_version_target_option}" ]] && [[ -n "${install_macos_major_version_target_local}" ]]; } && install_macos_major_version_target_option="${install_macos_major_version_target_local}"
[[ -n "${install_rapid_security_responses_managed}" ]] && install_rapid_security_responses_option="${install_rapid_security_responses_managed}"
{ [[ -z "${install_rapid_security_responses_managed}" ]] && [[ -z "${install_rapid_security_responses_option}" ]] && [[ -n "${install_rapid_security_responses_local}" ]]; } && install_rapid_security_responses_option="${install_rapid_security_responses_local}"
[[ -n "${install_non_system_updates_without_restarting_managed}" ]] && install_non_system_updates_without_restarting_option="${install_non_system_updates_without_restarting_managed}"
{ [[ -z "${install_non_system_updates_without_restarting_managed}" ]] && [[ -z "${install_non_system_updates_without_restarting_option}" ]] && [[ -n "${install_non_system_updates_without_restarting_local}" ]]; } && install_non_system_updates_without_restarting_option="${install_non_system_updates_without_restarting_local}"
[[ -n "${install_jamf_policy_triggers_managed}" ]] && install_jamf_policy_triggers_option="${install_jamf_policy_triggers_managed}"
{ [[ -z "${install_jamf_policy_triggers_managed}" ]] && [[ -z "${install_jamf_policy_triggers_option}" ]] && [[ -n "${install_jamf_policy_triggers_local}" ]]; } && install_jamf_policy_triggers_option="${install_jamf_policy_triggers_local}"
[[ -n "${install_jamf_policy_triggers_without_restarting_managed}" ]] && install_jamf_policy_triggers_without_restarting_option="${install_jamf_policy_triggers_without_restarting_managed}"
{ [[ -z "${install_jamf_policy_triggers_without_restarting_managed}" ]] && [[ -z "${install_jamf_policy_triggers_without_restarting_option}" ]] && [[ -n "${install_jamf_policy_triggers_without_restarting_local}" ]]; } && install_jamf_policy_triggers_without_restarting_option="${install_jamf_policy_triggers_without_restarting_local}"
[[ -n "${workflow_install_now_managed}" ]] && workflow_install_now_option="${workflow_install_now_managed}"
{ [[ -z "${workflow_install_now_managed}" ]] && [[ -z "${workflow_install_now_option}" ]] && [[ -n "${workflow_install_now_local}" ]]; } && workflow_install_now_option="${workflow_install_now_local}"
[[ -n "${workflow_only_download_managed}" ]] && workflow_only_download_option="${workflow_only_download_managed}"
{ [[ -z "${workflow_only_download_managed}" ]] && [[ -z "${workflow_only_download_option}" ]] && [[ -n "${workflow_only_download_local}" ]]; } && workflow_only_download_option="${workflow_only_download_local}"
[[ -n "${workflow_restart_without_updates_managed}" ]] && workflow_restart_without_updates_option="${workflow_restart_without_updates_managed}"
{ [[ -z "${workflow_restart_without_updates_managed}" ]] && [[ -z "${workflow_restart_without_updates_option}" ]] && [[ -n "${workflow_restart_without_updates_local}" ]]; } && workflow_restart_without_updates_option="${workflow_restart_without_updates_local}"
[[ -n "${workflow_disable_update_check_managed}" ]] && workflow_disable_update_check_option="${workflow_disable_update_check_managed}"
{ [[ -z "${workflow_disable_update_check_managed}" ]] && [[ -z "${workflow_disable_update_check_option}" ]] && [[ -n "${workflow_disable_update_check_local}" ]]; } && workflow_disable_update_check_option="${workflow_disable_update_check_local}"
[[ -n "${workflow_disable_relaunch_managed}" ]] && workflow_disable_relaunch_option="${workflow_disable_relaunch_managed}"
{ [[ -z "${workflow_disable_relaunch_managed}" ]] && [[ -z "${workflow_disable_relaunch_option}" ]] && [[ -n "${workflow_disable_relaunch_local}" ]]; } && workflow_disable_relaunch_option="${workflow_disable_relaunch_local}"
[[ -n "${deferral_timer_default_managed}" ]] && deferral_timer_default_option="${deferral_timer_default_managed}"
{ [[ -z "${deferral_timer_default_managed}" ]] && [[ -z "${deferral_timer_default_option}" ]] && [[ -n "${deferral_timer_default_local}" ]]; } && deferral_timer_default_option="${deferral_timer_default_local}"
[[ -n "${deferral_timer_menu_managed}" ]] && deferral_timer_menu_option="${deferral_timer_menu_managed}"
{ [[ -z "${deferral_timer_menu_managed}" ]] && [[ -z "${deferral_timer_menu_option}" ]] && [[ -n "${deferral_timer_menu_local}" ]]; } && deferral_timer_menu_option="${deferral_timer_menu_local}"
[[ -n "${deferral_timer_focus_managed}" ]] && deferral_timer_focus_option="${deferral_timer_focus_managed}"
{ [[ -z "${deferral_timer_focus_managed}" ]] && [[ -z "${deferral_timer_focus_option}" ]] && [[ -n "${deferral_timer_focus_local}" ]]; } && deferral_timer_focus_option="${deferral_timer_focus_local}"
[[ -n "${deferral_timer_error_managed}" ]] && deferral_timer_error_option="${deferral_timer_error_managed}"
{ [[ -z "${deferral_timer_error_managed}" ]] && [[ -z "${deferral_timer_error_option}" ]] && [[ -n "${deferral_timer_error_local}" ]]; } && deferral_timer_error_option="${deferral_timer_error_local}"
[[ -n "${deferral_timer_workflow_relaunch_managed}" ]] && deferral_timer_workflow_relaunch_option="${deferral_timer_workflow_relaunch_managed}"
{ [[ -z "${deferral_timer_workflow_relaunch_managed}" ]] && [[ -z "${deferral_timer_workflow_relaunch_option}" ]] && [[ -n "${deferral_timer_workflow_relaunch_local}" ]]; } && deferral_timer_workflow_relaunch_option="${deferral_timer_workflow_relaunch_local}"
[[ -n "${schedule_workflow_active_managed}" ]] && schedule_workflow_active_option="${schedule_workflow_active_managed}"
{ [[ -z "${schedule_workflow_active_managed}" ]] && [[ -z "${schedule_workflow_active_option}" ]] && [[ -n "${schedule_workflow_active_local}" ]]; } && schedule_workflow_active_option="${schedule_workflow_active_local}"
[[ -n "${schedule_zero_date_release_managed}" ]] && schedule_zero_date_release_option="${schedule_zero_date_release_managed}"
{ [[ -z "${schedule_zero_date_release_managed}" ]] && [[ -z "${schedule_zero_date_release_option}" ]] && [[ -n "${schedule_zero_date_release_local}" ]]; } && schedule_zero_date_release_option="${schedule_zero_date_release_local}"
[[ -n "${schedule_zero_date_sofa_custom_url_managed}" ]] && schedule_zero_date_sofa_custom_url_option="${schedule_zero_date_sofa_custom_url_managed}"
{ [[ -z "${schedule_zero_date_sofa_custom_url_managed}" ]] && [[ -z "${schedule_zero_date_sofa_custom_url_option}" ]] && [[ -n "${schedule_zero_date_sofa_custom_url_local}" ]]; } && schedule_zero_date_sofa_custom_url_option="${schedule_zero_date_sofa_custom_url_local}"
[[ -n "${schedule_zero_date_manual_managed}" ]] && schedule_zero_date_manual_option="${schedule_zero_date_manual_managed}"
{ [[ -z "${schedule_zero_date_manual_managed}" ]] && [[ -z "${schedule_zero_date_manual_option}" ]] && [[ -n "${schedule_zero_date_manual_local}" ]]; } && schedule_zero_date_manual_option="${schedule_zero_date_manual_local}"
[[ -n "${scheduled_install_days_managed}" ]] && scheduled_install_days_option="${scheduled_install_days_managed}"
{ [[ -z "${scheduled_install_days_managed}" ]] && [[ -z "${scheduled_install_days_option}" ]] && [[ -n "${scheduled_install_days_local}" ]]; } && scheduled_install_days_option="${scheduled_install_days_local}"
[[ -n "${scheduled_install_date_managed}" ]] && scheduled_install_date_option="${scheduled_install_date_managed}"
{ [[ -z "${scheduled_install_date_managed}" ]] && [[ -z "${scheduled_install_date_option}" ]] && [[ -n "${scheduled_install_date_local}" ]]; } && scheduled_install_date_option="${scheduled_install_date_local}"
[[ -n "${scheduled_install_user_choice_managed}" ]] && scheduled_install_user_choice_option="${scheduled_install_user_choice_managed}"
{ [[ -z "${scheduled_install_user_choice_managed}" ]] && [[ -z "${scheduled_install_user_choice_option}" ]] && [[ -n "${scheduled_install_user_choice_local}" ]]; } && scheduled_install_user_choice_option="${scheduled_install_user_choice_local}"
[[ -n "${scheduled_install_reminder_managed}" ]] && scheduled_install_reminder_option="${scheduled_install_reminder_managed}"
{ [[ -z "${scheduled_install_reminder_managed}" ]] && [[ -z "${scheduled_install_reminder_option}" ]] && [[ -n "${scheduled_install_reminder_local}" ]]; } && scheduled_install_reminder_option="${scheduled_install_reminder_local}"
[[ -n "${deadline_count_focus_managed}" ]] && deadline_count_focus_option="${deadline_count_focus_managed}"
{ [[ -z "${deadline_count_focus_managed}" ]] && [[ -z "${deadline_count_focus_option}" ]] && [[ -n "${deadline_count_focus_local}" ]]; } && deadline_count_focus_option="${deadline_count_focus_local}"
[[ -n "${deadline_count_soft_managed}" ]] && deadline_count_soft_option="${deadline_count_soft_managed}"
{ [[ -z "${deadline_count_soft_managed}" ]] && [[ -z "${deadline_count_soft_option}" ]] && [[ -n "${deadline_count_soft_local}" ]]; } && deadline_count_soft_option="${deadline_count_soft_local}"
[[ -n "${deadline_count_hard_managed}" ]] && deadline_count_hard_option="${deadline_count_hard_managed}"
{ [[ -z "${deadline_count_hard_managed}" ]] && [[ -z "${deadline_count_hard_option}" ]] && [[ -n "${deadline_count_hard_local}" ]]; } && deadline_count_hard_option="${deadline_count_hard_local}"
[[ -n "${deadline_days_focus_managed}" ]] && deadline_days_focus_option="${deadline_days_focus_managed}"
{ [[ -z "${deadline_days_focus_managed}" ]] && [[ -z "${deadline_days_focus_option}" ]] && [[ -n "${deadline_days_focus_local}" ]]; } && deadline_days_focus_option="${deadline_days_focus_local}"
[[ -n "${deadline_days_soft_managed}" ]] && deadline_days_soft_option="${deadline_days_soft_managed}"
{ [[ -z "${deadline_days_soft_managed}" ]] && [[ -z "${deadline_days_soft_option}" ]] && [[ -n "${deadline_days_soft_local}" ]]; } && deadline_days_soft_option="${deadline_days_soft_local}"
[[ -n "${deadline_days_hard_managed}" ]] && deadline_days_hard_option="${deadline_days_hard_managed}"
{ [[ -z "${deadline_days_hard_managed}" ]] && [[ -z "${deadline_days_hard_option}" ]] && [[ -n "${deadline_days_hard_local}" ]]; } && deadline_days_hard_option="${deadline_days_hard_local}"
[[ -n "${deadline_date_focus_managed}" ]] && deadline_date_focus_option="${deadline_date_focus_managed}"
{ [[ -z "${deadline_date_focus_managed}" ]] && [[ -z "${deadline_date_focus_option}" ]] && [[ -n "${deadline_date_focus_local}" ]]; } && deadline_date_focus_option="${deadline_date_focus_local}"
[[ -n "${deadline_date_soft_managed}" ]] && deadline_date_soft_option="${deadline_date_soft_managed}"
{ [[ -z "${deadline_date_soft_managed}" ]] && [[ -z "${deadline_date_soft_option}" ]] && [[ -n "${deadline_date_soft_local}" ]]; } && deadline_date_soft_option="${deadline_date_soft_local}"
[[ -n "${deadline_date_hard_managed}" ]] && deadline_date_hard_option="${deadline_date_hard_managed}"
{ [[ -z "${deadline_date_hard_managed}" ]] && [[ -z "${deadline_date_hard_option}" ]] && [[ -n "${deadline_date_hard_local}" ]]; } && deadline_date_hard_option="${deadline_date_hard_local}"
[[ -n "${dialog_timeout_default_managed}" ]] && dialog_timeout_default_option="${dialog_timeout_default_managed}"
{ [[ -z "${dialog_timeout_default_managed}" ]] && [[ -z "${dialog_timeout_default_option}" ]] && [[ -n "${dialog_timeout_default_local}" ]]; } && dialog_timeout_default_option="${dialog_timeout_default_local}"
[[ -n "${dialog_timeout_user_auth_managed}" ]] && dialog_timeout_user_auth_option="${dialog_timeout_user_auth_managed}"
{ [[ -z "${dialog_timeout_user_auth_managed}" ]] && [[ -z "${dialog_timeout_user_auth_option}" ]] && [[ -n "${dialog_timeout_user_auth_local}" ]]; } && dialog_timeout_user_auth_option="${dialog_timeout_user_auth_local}"
[[ -n "${dialog_timeout_user_choice_managed}" ]] && dialog_timeout_user_choice_option="${dialog_timeout_user_choice_managed}"
{ [[ -z "${dialog_timeout_user_choice_managed}" ]] && [[ -z "${dialog_timeout_user_choice_option}" ]] && [[ -n "${dialog_timeout_user_choice_local}" ]]; } && dialog_timeout_user_choice_option="${dialog_timeout_user_choice_local}"
[[ -n "${dialog_timeout_user_schedule_managed}" ]] && dialog_timeout_user_schedule_option="${dialog_timeout_user_schedule_managed}"
{ [[ -z "${dialog_timeout_user_schedule_managed}" ]] && [[ -z "${dialog_timeout_user_schedule_option}" ]] && [[ -n "${dialog_timeout_user_schedule_local}" ]]; } && dialog_timeout_user_schedule_option="${dialog_timeout_user_schedule_local}"
[[ -n "${dialog_timeout_soft_deadline_managed}" ]] && dialog_timeout_soft_deadline_option="${dialog_timeout_soft_deadline_managed}"
{ [[ -z "${dialog_timeout_soft_deadline_managed}" ]] && [[ -z "${dialog_timeout_soft_deadline_option}" ]] && [[ -n "${dialog_timeout_soft_deadline_local}" ]]; } && dialog_timeout_soft_deadline_option="${dialog_timeout_soft_deadline_local}"
[[ -n "${dialog_timeout_insufficient_storage_managed}" ]] && dialog_timeout_insufficient_storage_option="${dialog_timeout_insufficient_storage_managed}"
{ [[ -z "${dialog_timeout_insufficient_storage_managed}" ]] && [[ -z "${dialog_timeout_insufficient_storage_option}" ]] && [[ -n "${dialog_timeout_insufficient_storage_local}" ]]; } && dialog_timeout_insufficient_storage_option="${dialog_timeout_insufficient_storage_local}"
[[ -n "${dialog_timeout_power_required_managed}" ]] && dialog_timeout_power_required_option="${dialog_timeout_power_required_managed}"
{ [[ -z "${dialog_timeout_power_required_managed}" ]] && [[ -z "${dialog_timeout_power_required_option}" ]] && [[ -n "${dialog_timeout_power_required_local}" ]]; } && dialog_timeout_power_required_option="${dialog_timeout_power_required_local}"
[[ -n "${display_unmovable_managed}" ]] && display_unmovable_option="${display_unmovable_managed}"
{ [[ -z "${display_unmovable_managed}" ]] && [[ -z "${display_unmovable_option}" ]] && [[ -n "${display_unmovable_local}" ]]; } && display_unmovable_option="${display_unmovable_local}"
[[ -n "${display_hide_background_managed}" ]] && display_hide_background_option="${display_hide_background_managed}"
{ [[ -z "${display_hide_background_managed}" ]] && [[ -z "${display_hide_background_option}" ]] && [[ -n "${display_hide_background_local}" ]]; } && display_hide_background_option="${display_hide_background_local}"
[[ -n "${display_silently_managed}" ]] && display_silently_option="${display_silently_managed}"
{ [[ -z "${display_silently_managed}" ]] && [[ -z "${display_silently_option}" ]] && [[ -n "${display_silently_local}" ]]; } && display_silently_option="${display_silently_local}"
[[ -n "${display_hide_progress_bar_managed}" ]] && display_hide_progress_bar_option="${display_hide_progress_bar_managed}"
{ [[ -z "${display_hide_progress_bar_managed}" ]] && [[ -z "${display_hide_progress_bar_option}" ]] && [[ -n "${display_hide_progress_bar_local}" ]]; } && display_hide_progress_bar_option="${display_hide_progress_bar_local}"
[[ -n "${display_notifications_centered_managed}" ]] && display_notifications_centered_option="${display_notifications_centered_managed}"
{ [[ -z "${display_notifications_centered_managed}" ]] && [[ -z "${display_notifications_centered_option}" ]] && [[ -n "${display_notifications_centered_local}" ]]; } && display_notifications_centered_option="${display_notifications_centered_local}"
[[ -n "${display_icon_size_managed}" ]] && display_icon_size_option="${display_icon_size_managed}"
{ [[ -z "${display_icon_size_managed}" ]] && [[ -z "${display_icon_size_option}" ]] && [[ -n "${display_icon_size_local}" ]]; } && display_icon_size_option="${display_icon_size_local}"
[[ -n "${display_icon_file_managed}" ]] && display_icon_file_option="${display_icon_file_managed}"
{ [[ -z "${display_icon_file_managed}" ]] && [[ -z "${display_icon_file_option}" ]] && [[ -n "${display_icon_file_local}" ]]; } && display_icon_file_option="${display_icon_file_local}"
[[ -n "${display_icon_light_file_managed}" ]] && display_icon_light_file_option="${display_icon_light_file_managed}"
{ [[ -z "${display_icon_light_file_managed}" ]] && [[ -z "${display_icon_light_file_option}" ]] && [[ -n "${display_icon_light_file_local}" ]]; } && display_icon_light_file_option="${display_icon_light_file_local}"
[[ -n "${display_icon_dark_file_managed}" ]] && display_icon_dark_file_option="${display_icon_dark_file_managed}"
{ [[ -z "${display_icon_dark_file_managed}" ]] && [[ -z "${display_icon_dark_file_option}" ]] && [[ -n "${display_icon_dark_file_local}" ]]; } && display_icon_dark_file_option="${display_icon_dark_file_local}"
[[ -n "${display_accessory_type_managed}" ]] && display_accessory_type_option="${display_accessory_type_managed}"
{ [[ -z "${display_accessory_type_managed}" ]] && [[ -z "${display_accessory_type_option}" ]] && [[ -n "${display_accessory_type_local}" ]]; } && display_accessory_type_option="${display_accessory_type_local}"
[[ -n "${display_accessory_default_file_managed}" ]] && display_accessory_default_file_option="${display_accessory_default_file_managed}"
{ [[ -z "${display_accessory_default_file_managed}" ]] && [[ -z "${display_accessory_default_file_option}" ]] && [[ -n "${display_accessory_default_file_local}" ]]; } && display_accessory_default_file_option="${display_accessory_default_file_local}"
[[ -n "${display_accessory_macos_minor_update_file_managed}" ]] && display_accessory_macos_minor_update_file_option="${display_accessory_macos_minor_update_file_managed}"
{ [[ -z "${display_accessory_macos_minor_update_file_managed}" ]] && [[ -z "${display_accessory_macos_minor_update_file_option}" ]] && [[ -n "${display_accessory_macos_minor_update_file_local}" ]]; } && display_accessory_macos_minor_update_file_option="${display_accessory_macos_minor_update_file_local}"
[[ -n "${display_accessory_macos_major_upgrade_file_managed}" ]] && display_accessory_macos_major_upgrade_file_option="${display_accessory_macos_major_upgrade_file_managed}"
{ [[ -z "${display_accessory_macos_major_upgrade_file_managed}" ]] && [[ -z "${display_accessory_macos_major_upgrade_file_option}" ]] && [[ -n "${display_accessory_macos_major_upgrade_file_local}" ]]; } && display_accessory_macos_major_upgrade_file_option="${display_accessory_macos_major_upgrade_file_local}"
[[ -n "${display_accessory_non_system_updates_file_managed}" ]] && display_accessory_non_system_updates_file_option="${display_accessory_non_system_updates_file_managed}"
{ [[ -z "${display_accessory_non_system_updates_file_managed}" ]] && [[ -z "${display_accessory_non_system_updates_file_option}" ]] && [[ -n "${display_accessory_non_system_updates_file_local}" ]]; } && display_accessory_non_system_updates_file_option="${display_accessory_non_system_updates_file_local}"
[[ -n "${display_accessory_jamf_policy_triggers_file_managed}" ]] && display_accessory_jamf_policy_triggers_file_option="${display_accessory_jamf_policy_triggers_file_managed}"
{ [[ -z "${display_accessory_jamf_policy_triggers_file_managed}" ]] && [[ -z "${display_accessory_jamf_policy_triggers_file_option}" ]] && [[ -n "${display_accessory_jamf_policy_triggers_file_local}" ]]; } && display_accessory_jamf_policy_triggers_file_option="${display_accessory_jamf_policy_triggers_file_local}"
[[ -n "${display_accessory_restart_without_updates_file_managed}" ]] && display_accessory_restart_without_updates_file_option="${display_accessory_restart_without_updates_file_managed}"
{ [[ -z "${display_accessory_restart_without_updates_file_managed}" ]] && [[ -z "${display_accessory_restart_without_updates_file_option}" ]] && [[ -n "${display_accessory_restart_without_updates_file_local}" ]]; } && display_accessory_restart_without_updates_file_option="${display_accessory_restart_without_updates_file_local}"
[[ -n "${display_help_button_string_managed}" ]] && display_help_button_string_option="${display_help_button_string_managed}"
{ [[ -z "${display_help_button_string_managed}" ]] && [[ -z "${display_help_button_string_option}" ]] && [[ -n "${display_help_button_string_local}" ]]; } && display_help_button_string_option="${display_help_button_string_local}"
[[ -n "${display_warning_button_string_managed}" ]] && display_warning_button_string_option="${display_warning_button_string_managed}"
{ [[ -z "${display_warning_button_string_managed}" ]] && [[ -z "${display_warning_button_string_option}" ]] && [[ -n "${display_warning_button_string_local}" ]]; } && display_warning_button_string_option="${display_warning_button_string_local}"
[[ -n "${auth_user_save_password_managed}" ]] && auth_ask_user_to_save_password="${auth_user_save_password_managed}"
{ [[ -z "${auth_user_save_password_managed}" ]] && [[ -z "${auth_ask_user_to_save_password}" ]] && [[ -n "${auth_user_save_password_local}" ]]; } && auth_ask_user_to_save_password="${auth_user_save_password_local}"
[[ -n "${auth_jamf_computer_id_managed}" ]] && auth_jamf_computer_id_option="${auth_jamf_computer_id_managed}"
{ [[ -z "${auth_jamf_computer_id_managed}" ]] && [[ -z "${auth_jamf_computer_id_option}" ]] && [[ -n "${auth_jamf_computer_id_local}" ]]; } && auth_jamf_computer_id_option="${auth_jamf_computer_id_local}"
[[ -n "${auth_jamf_custom_url_managed}" ]] && auth_jamf_custom_url_option="${auth_jamf_custom_url_managed}"
{ [[ -z "${auth_jamf_custom_url_managed}" ]] && [[ -z "${auth_jamf_custom_url_option}" ]] && [[ -n "${auth_jamf_custom_url_local}" ]]; } && auth_jamf_custom_url_option="${auth_jamf_custom_url_local}"
[[ -n "${auth_credential_failover_to_user_managed}" ]] && auth_credential_failover_to_user_option="${auth_credential_failover_to_user_managed}"
{ [[ -z "${auth_credential_failover_to_user_managed}" ]] && [[ -z "${auth_credential_failover_to_user_option}" ]] && [[ -n "${auth_credential_failover_to_user_local}" ]]; } && auth_credential_failover_to_user_option="${auth_credential_failover_to_user_local}"
[[ -n "${auth_mdm_failover_to_user_managed}" ]] && auth_mdm_failover_to_user_option="${auth_mdm_failover_to_user_managed}"
{ [[ -z "${auth_mdm_failover_to_user_managed}" ]] && [[ -z "${auth_mdm_failover_to_user_option}" ]] && [[ -n "${auth_mdm_failover_to_user_local}" ]]; } && auth_mdm_failover_to_user_option="${auth_mdm_failover_to_user_local}"
[[ -n "${test_mode_managed}" ]] && test_mode_option="${test_mode_managed}"
{ [[ -z "${test_mode_managed}" ]] && [[ -z "${test_mode_option}" ]] && [[ -n "${test_mode_local}" ]]; } && test_mode_option="${test_mode_local}"
[[ -n "${test_mode_timeout_managed}" ]] && test_mode_timeout_option="${test_mode_timeout_managed}"
{ [[ -z "${test_mode_timeout_managed}" ]] && [[ -z "${test_mode_timeout_option}" ]] && [[ -n "${test_mode_timeout_local}" ]]; } && test_mode_timeout_option="${test_mode_timeout_local}"
[[ -n "${test_storage_update_managed}" ]] && test_storage_update_option="${test_storage_update_managed}"
{ [[ -z "${test_storage_update_managed}" ]] && [[ -z "${test_storage_update_option}" ]] && [[ -n "${test_storage_update_local}" ]]; } && test_storage_update_option="${test_storage_update_local}"
[[ -n "${test_storage_upgrade_managed}" ]] && test_storage_upgrade_option="${test_storage_upgrade_managed}"
{ [[ -z "${test_storage_upgrade_managed}" ]] && [[ -z "${test_storage_upgrade_option}" ]] && [[ -n "${test_storage_upgrade_local}" ]]; } && test_storage_upgrade_option="${test_storage_upgrade_local}"
[[ -n "${test_battery_level_managed}" ]] && test_battery_level_option="${test_battery_level_managed}"
{ [[ -z "${test_battery_level_managed}" ]] && [[ -z "${test_battery_level_option}" ]] && [[ -n "${test_battery_level_local}" ]]; } && test_battery_level_option="${test_battery_level_local}"
}
# Validate non-authentication parameters and manage ${SUPER_LOCAL_PLIST}. Any errors set ${option_error}.
manage_parameter_options() {
option_error="FALSE"
# Manage ${install_macos_major_upgrades} and save to ${SUPER_LOCAL_PLIST}.
if [[ "${install_macos_major_upgrades}" -eq 1 ]] || [[ "${install_macos_major_upgrades}" == "TRUE" ]]; then
install_macos_major_upgrades="TRUE"
defaults write "${SUPER_LOCAL_PLIST}" InstallMacOSMajorUpgrades -bool true
else
install_macos_major_upgrades="FALSE"
defaults delete "${SUPER_LOCAL_PLIST}" InstallMacOSMajorUpgrades 2>/dev/null
fi
# Validate ${install_macos_major_version_target_option} and if a valid set ${install_macos_major_upgrades_target} and save to ${SUPER_LOCAL_PLIST}.
if [[ "${install_macos_major_version_target_option}" == "X" ]]; then
log_super "Status: Deleting local preference for the --install-macos-major-version-target option, defaulting to the newest compatible major macOS version."
defaults delete "${SUPER_LOCAL_PLIST}" InstallMacOSMajorVersionTarget 2>/dev/null
unset install_macos_major_version_target_option
elif [[ -n "${install_macos_major_version_target_option}" ]] && ! [[ "${install_macos_major_version_target_option}" =~ ${REGEX_MACOS_MAJOR_VERSION} ]]; then
log_super "Parameter Error: The --install-macos-major-version-target=number value must be a contemporary macOS major version number (12,13,etc.)."
option_error="TRUE"
elif [[ -n "${install_macos_major_version_target_option}" ]] && [[ "${install_macos_major_version_target_option}" =~ ${REGEX_MACOS_MAJOR_VERSION} ]]; then
if [[ "${install_macos_major_upgrades}" == "TRUE" ]]; then
install_macos_major_upgrades_target="${install_macos_major_version_target_option}"
defaults write "${SUPER_LOCAL_PLIST}" InstallMacOSMajorVersionTarget -string "${install_macos_major_upgrades_target}"
else
log_super "Parameter Error: To use the --install-macos-major-version-target option you must also use the --install-macos-major-upgrades option."
option_error="TRUE"
defaults delete "${SUPER_LOCAL_PLIST}" InstallMacOSMajorVersionTarget 2>/dev/null
fi
fi
# Manage the ${install_macos_major_upgrades} and ${install_macos_major_upgrades_target} if it's less than or the same as the current macOS ${macos_version_major}.
if [[ "${install_macos_major_upgrades}" == "TRUE" ]] && [[ -n "${install_macos_major_upgrades_target}" ]] && [[ "${install_macos_major_upgrades_target}" -le "${macos_version_major}" ]]; then
[[ "${install_macos_major_upgrades_target}" -lt "${macos_version_major}" ]] && log_super "Parameter Warning: The --install-macos-major-version-target=${install_macos_major_upgrades_target} option is less than current macOS ${macos_version_major}. Disabling macOS major upgrade workflow."
[[ "${install_macos_major_upgrades_target}" -eq "${macos_version_major}" ]] && log_super "Status: The --install-macos-major-version-target=${install_macos_major_upgrades_target} option is the same as current macOS ${macos_version_major}. Disabling macOS major upgrade workflow."
install_macos_major_upgrades="FALSE"
unset install_macos_major_upgrades_target
fi
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: install_macos_major_upgrades is: ${install_macos_major_upgrades}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: install_macos_major_upgrades_target is: ${install_macos_major_upgrades_target}"
# Manage ${install_rapid_security_responses_option} and save to ${SUPER_LOCAL_PLIST}.
if [[ "${install_rapid_security_responses_option}" -eq 1 ]] || [[ "${install_rapid_security_responses_option}" == "TRUE" ]]; then
install_rapid_security_responses_option="TRUE"
defaults write "${SUPER_LOCAL_PLIST}" InstallRapidSecurityResponses -bool true
else
install_rapid_security_responses_option="FALSE"
defaults delete "${SUPER_LOCAL_PLIST}" InstallRapidSecurityResponses 2>/dev/null
fi
{ [[ "${verbose_mode_option}" == "TRUE" ]] && [[ -n "${install_rapid_security_responses_option}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: install_rapid_security_responses_option is: ${install_rapid_security_responses_option}"
# Manage ${install_non_system_updates_without_restarting_option} and save to ${SUPER_LOCAL_PLIST}.
if [[ "${install_non_system_updates_without_restarting_option}" -eq 1 ]] || [[ "${install_non_system_updates_without_restarting_option}" == "TRUE" ]]; then
install_non_system_updates_without_restarting_option="TRUE"
defaults write "${SUPER_LOCAL_PLIST}" InstallNonSystemUpdatesWithoutRestarting -bool true
else
install_non_system_updates_without_restarting_option="FALSE"
defaults delete "${SUPER_LOCAL_PLIST}" InstallNonSystemUpdatesWithoutRestarting 2>/dev/null
fi
{ [[ "${verbose_mode_option}" == "TRUE" ]] && [[ -n "${install_non_system_updates_without_restarting_option}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: install_non_system_updates_without_restarting_option is: ${install_non_system_updates_without_restarting_option}"
# Validate ${install_jamf_policy_triggers_option} input and save to ${SUPER_LOCAL_PLIST}.
if [[ "${install_jamf_policy_triggers_option}" == "X" ]]; then
log_super "Status: Deleting local preference for the --install-jamf-policy-triggers option."
defaults delete "${SUPER_LOCAL_PLIST}" InstallJamfPolicyTriggers 2>/dev/null
unset install_jamf_policy_triggers_option
elif [[ -n "${install_jamf_policy_triggers_option}" ]]; then
defaults write "${SUPER_LOCAL_PLIST}" InstallJamfPolicyTriggers -string "${install_jamf_policy_triggers_option}"
fi
if [[ ! -f "${JAMF_PRO_BINARY}" ]] && [[ -n "${install_jamf_policy_triggers_option}" ]]; then
log_super "Parameter Error: Unable to use the --install-jamf-policy-triggers option due to missing Jamf binary."
option_error="TRUE"
fi
{ [[ "${verbose_mode_option}" == "TRUE" ]] && [[ -n "${install_jamf_policy_triggers_option}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: install_jamf_policy_triggers_option is: ${install_jamf_policy_triggers_option}"
# Manage ${install_jamf_policy_triggers_without_restarting_option} and save to ${SUPER_LOCAL_PLIST}.
if [[ "${install_jamf_policy_triggers_without_restarting_option}" -eq 1 ]] || [[ "${install_jamf_policy_triggers_without_restarting_option}" == "TRUE" ]]; then
install_jamf_policy_triggers_without_restarting_option="TRUE"
defaults write "${SUPER_LOCAL_PLIST}" InstallJamfPolicyTriggersWithoutRestarting -bool true
else
install_jamf_policy_triggers_without_restarting_option="FALSE"
defaults delete "${SUPER_LOCAL_PLIST}" InstallJamfPolicyTriggersWithoutRestarting 2>/dev/null
fi
if [[ "${install_jamf_policy_triggers_without_restarting_option}" == "TRUE" ]] && [[ -z "${install_jamf_policy_triggers_option}" ]]; then
log_super "Parameter Parameter Warning: The --install-jamf-policy-triggers-without-restarting only works if you also set the --install-jamf-policy-triggers option."
install_jamf_policy_triggers_without_restarting_option="FALSE"
fi
{ [[ "${verbose_mode_option}" == "TRUE" ]] && [[ -n "${install_jamf_policy_triggers_without_restarting_option}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: install_jamf_policy_triggers_without_restarting_option is: ${install_jamf_policy_triggers_without_restarting_option}"
# Manage ${workflow_install_now_option} and save to ${SUPER_LOCAL_PLIST}.
if [[ "${workflow_install_now_option}" -eq 1 ]] || [[ "${workflow_install_now_option}" == "TRUE" ]]; then
workflow_install_now_option="TRUE"
defaults write "${SUPER_LOCAL_PLIST}" WorkflowInstallNow -bool true
else
workflow_install_now_option="FALSE"
defaults delete "${SUPER_LOCAL_PLIST}" WorkflowInstallNow 2>/dev/null
fi
{ [[ "${verbose_mode_option}" == "TRUE" ]] && [[ -n "${workflow_install_now_option}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: workflow_install_now_option is: ${workflow_install_now_option}"
# Manage ${workflow_only_download_option} and save to ${SUPER_LOCAL_PLIST}.
if [[ "${workflow_only_download_option}" -eq 1 ]] || [[ "${workflow_only_download_option}" == "TRUE" ]]; then
workflow_only_download_option="TRUE"
defaults write "${SUPER_LOCAL_PLIST}" WorkflowOnlyDownload -bool true
else
workflow_only_download_option="FALSE"
defaults delete "${SUPER_LOCAL_PLIST}" WorkflowOnlyDownload 2>/dev/null
fi
if [[ "${workflow_install_now_option}" == "TRUE" ]] && [[ "${workflow_only_download_option}" == "TRUE" ]]; then
log_super "Parameter Warning: When both the --workflow-install-now and the --workflow-only-download options are enabled the --workflow-install-now option takes priority."
workflow_only_download_option="FALSE"
fi
{ [[ "${verbose_mode_option}" == "TRUE" ]] && [[ -n "${workflow_only_download_option}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: workflow_only_download_option is: ${workflow_only_download_option}"
# Manage ${workflow_restart_without_updates_option} and save to ${SUPER_LOCAL_PLIST}.
if [[ "${workflow_restart_without_updates_option}" -eq 1 ]] || [[ "${workflow_restart_without_updates_option}" == "TRUE" ]]; then
workflow_restart_without_updates_option="TRUE"
defaults write "${SUPER_LOCAL_PLIST}" WorkflowRestartWithoutUpdates -bool true
else
workflow_restart_without_updates_option="FALSE"
defaults delete "${SUPER_LOCAL_PLIST}" WorkflowRestartWithoutUpdates 2>/dev/null
fi
if [[ "${workflow_only_download_option}" == "TRUE" ]] && [[ "${workflow_restart_without_updates_option}" == "TRUE" ]]; then
log_super "Parameter Error: The --workflow-restart-without-updates option can not be used with the --only-download option."
option_error="TRUE"
fi
{ [[ "${verbose_mode_option}" == "TRUE" ]] && [[ -n "${workflow_restart_without_updates_option}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: workflow_restart_without_updates_option is: ${workflow_restart_without_updates_option}"
# Manage ${workflow_disable_update_check_option} and save to ${SUPER_LOCAL_PLIST}.
if [[ "${workflow_disable_update_check_option}" -eq 1 ]] || [[ "${workflow_disable_update_check_option}" == "TRUE" ]]; then
workflow_disable_update_check_option="TRUE"
defaults write "${SUPER_LOCAL_PLIST}" WorkflowDisableUpdateCheck -bool true
else
workflow_disable_update_check_option="FALSE"
defaults delete "${SUPER_LOCAL_PLIST}" WorkflowDisableUpdateCheck 2>/dev/null
fi
if [[ "${workflow_only_download_option}" == "TRUE" ]] && [[ "${workflow_disable_update_check_option}" == "TRUE" ]]; then
log_super "Parameter Error: The --workflow-disable-update-check option can not be used with the --only-download option."
option_error="TRUE"
fi
{ [[ "${verbose_mode_option}" == "TRUE" ]] && [[ -n "${workflow_disable_update_check_option}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: workflow_disable_update_check_option is: ${workflow_disable_update_check_option}"
# Manage ${workflow_disable_relaunch_option} and save to ${SUPER_LOCAL_PLIST}.
if [[ "${workflow_disable_relaunch_option}" -eq 1 ]] || [[ "${workflow_disable_relaunch_option}" == "TRUE" ]]; then
workflow_disable_relaunch_option="TRUE"
defaults write "${SUPER_LOCAL_PLIST}" WorkflowDisableRelaunch -bool true
else
workflow_disable_relaunch_option="FALSE"
defaults delete "${SUPER_LOCAL_PLIST}" WorkflowDisableRelaunch 2>/dev/null
fi
{ [[ "${verbose_mode_option}" == "TRUE" ]] && [[ -n "${workflow_disable_relaunch_option}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: workflow_disable_relaunch_option is: ${workflow_disable_relaunch_option}"
# Validate ${deferral_timer_default_option} input and if valid set ${deferral_timer_minutes} and save to ${SUPER_LOCAL_PLIST}. If there is no ${deferral_timer_minutes} then set it to ${DEFERRAL_TIMER_DEFAULT_MINUTES}.
if [[ "${deferral_timer_default_option}" == "X" ]]; then
log_super "Status: Deleting local preference for the --deferral-timer-default option, defaulting to ${DEFERRAL_TIMER_DEFAULT_MINUTES} minutes."
defaults delete "${SUPER_LOCAL_PLIST}" DeferralTimerDefault 2>/dev/null
unset deferral_timer_default_option
elif [[ -n "${deferral_timer_default_option}" ]] && [[ "${deferral_timer_default_option}" =~ ${REGEX_ANY_WHOLE_NUMBER} ]]; then
if [[ "${deferral_timer_default_option}" -lt 2 ]]; then
log_super "Parameter Warning: Specified --deferral-timer-default=minutes value of ${deferral_timer_default_option} is too low, rounding up to 2 minutes."
deferral_timer_minutes=2
elif [[ "${deferral_timer_default_option}" -gt 10080 ]]; then
log_super "Parameter Warning: Specified --deferral-timer-default=minutes value of ${deferral_timer_default_option} is too high, rounding down to 10080 minutes (1 week)."
deferral_timer_minutes=10080
else
deferral_timer_minutes="${deferral_timer_default_option}"
fi
defaults write "${SUPER_LOCAL_PLIST}" DeferralTimerDefault -string "${deferral_timer_minutes}"
elif [[ -n "${deferral_timer_default_option}" ]] && ! [[ "${deferral_timer_default_option}" =~ ${REGEX_ANY_WHOLE_NUMBER} ]]; then
log_super "Parameter Error: The --deferral-timer-default=minutes value must only be a number."
option_error="TRUE"
fi
[[ -z "${deferral_timer_minutes}" ]] && deferral_timer_minutes="${DEFERRAL_TIMER_DEFAULT_MINUTES}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: deferral_timer_minutes is: ${deferral_timer_minutes}"
# Validate ${deferral_timer_menu_option} input and if valid set ${deferral_timer_menu_minutes} and save to ${SUPER_LOCAL_PLIST}.
local previous_ifs
if [[ "${deferral_timer_menu_option}" == "X" ]]; then
log_super "Status: Deleting local preference for the --deferral-timer-menu option, defaulting to ${deferral_timer_minutes} minutes."
defaults delete "${SUPER_LOCAL_PLIST}" DeferralTimerMenu 2>/dev/null
unset deferral_timer_menu_option
elif [[ -n "${deferral_timer_menu_option}" ]] && [[ "${deferral_timer_menu_option}" =~ ${REGEX_CSV_WHOLE_NUMBERS} ]]; then
previous_ifs="${IFS}"
IFS=','
local deferral_timer_menu_option_array
read -r -a deferral_timer_menu_option_array <<<"${deferral_timer_menu_option}"
for array_index in "${!deferral_timer_menu_option_array[@]}"; do
if [[ "${deferral_timer_menu_option_array[array_index]}" -lt 2 ]]; then
log_super "Parameter Warning: Specified --deferral-timer-menu=minutes value of ${deferral_timer_menu_option_array[array_index]} minutes is too low, rounding up to 2 minutes."
deferral_timer_menu_option_array[array_index]=2
elif [[ "${deferral_timer_menu_option_array[array_index]}" -gt 10080 ]]; then
log_super "Parameter Warning: Specified --deferral-timer-menu=minutes value of ${deferral_timer_menu_option_array[array_index]} minutes is too high, rounding down to 10080 minutes (1 week)."
deferral_timer_menu_option_array[array_index]=10080
fi
done
deferral_timer_menu_minutes="${deferral_timer_menu_option_array[*]}"
defaults write "${SUPER_LOCAL_PLIST}" DeferralTimerMenu -string "${deferral_timer_menu_minutes}"
IFS="${previous_ifs}"
elif [[ -n "${deferral_timer_menu_option}" ]] && ! [[ "${deferral_timer_menu_option}" =~ ${REGEX_CSV_WHOLE_NUMBERS} ]]; then
log_super "Parameter Error: The --deferral-timer-menu=minutes,minutes,etc... value must only contain numbers and commas (no spaces)."
option_error="TRUE"
fi
{ [[ "${verbose_mode_option}" == "TRUE" ]] && [[ -n "${deferral_timer_menu_minutes}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: deferral_timer_menu_minutes is: ${deferral_timer_menu_minutes}"
# Validate ${deferral_timer_focus_option} input and if valid set ${deferral_timer_focus_minutes} and save to ${SUPER_LOCAL_PLIST}. If there is no ${deferral_timer_focus_minutes} then set it to ${deferral_timer_minutes}.
if [[ "${deferral_timer_focus_option}" == "X" ]]; then
log_super "Status: Deleting local preference for the --deferral-timer-focus option, defaulting to ${deferral_timer_minutes} minutes."
defaults delete "${SUPER_LOCAL_PLIST}" DeferralTimerFocus 2>/dev/null
unset deferral_timer_focus_option
elif [[ -n "${deferral_timer_focus_option}" ]] && [[ "${deferral_timer_focus_option}" =~ ${REGEX_ANY_WHOLE_NUMBER} ]]; then
if [[ "${deferral_timer_focus_option}" -lt 2 ]]; then
log_super "Parameter Warning: Specified --deferral-timer-focus=minutes value of ${deferral_timer_focus_option} minutes is too low, rounding up to 2 minutes."
deferral_timer_focus_minutes=2
elif [[ "${deferral_timer_focus_option}" -gt 10080 ]]; then
log_super "Parameter Warning: Specified --deferral-timer-focus=minutes value of ${deferral_timer_focus_option} minutes is too high, rounding down to 1440 minutes (1 week)."
deferral_timer_focus_minutes=10080
else
deferral_timer_focus_minutes="${deferral_timer_focus_option}"
fi
defaults write "${SUPER_LOCAL_PLIST}" DeferralTimerFocus -string "${deferral_timer_focus_minutes}"
elif [[ -n "${deferral_timer_focus_option}" ]] && ! [[ "${deferral_timer_focus_option}" =~ ${REGEX_ANY_WHOLE_NUMBER} ]]; then
log_super "Parameter Error: The --deferral-timer-focus=minutes value must only be a number."
option_error="TRUE"
fi
[[ -z "${deferral_timer_focus_minutes}" ]] && deferral_timer_focus_minutes="${deferral_timer_minutes}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: deferral_timer_focus_minutes is: ${deferral_timer_focus_minutes}"
# Validate ${deferral_timer_error_option} input and if valid set ${deferral_timer_error_minutes} and save to ${SUPER_LOCAL_PLIST}. If there is no ${deferral_timer_error_minutes} then set it to ${deferral_timer_minutes}.
if [[ "${deferral_timer_error_option}" == "X" ]]; then
log_super "Status: Deleting local preference for the --deferral-timer-error option, defaulting to ${deferral_timer_minutes} minutes."
defaults delete "${SUPER_LOCAL_PLIST}" DeferralTimerError 2>/dev/null
unset deferral_timer_error_option
elif [[ -n "${deferral_timer_error_option}" ]] && [[ "${deferral_timer_error_option}" =~ ${REGEX_ANY_WHOLE_NUMBER} ]]; then
if [[ "${deferral_timer_error_option}" -lt 2 ]]; then
log_super "Parameter Warning: Specified --deferral-timer-error=minutes value of ${deferral_timer_error_option} minutes is too low, rounding up to 2 minutes."
deferral_timer_error_minutes=2
elif [[ "${deferral_timer_error_option}" -gt 10080 ]]; then
log_super "Parameter Warning: Specified --deferral-timer-error=minutes value of ${deferral_timer_error_option} minutes is too high, rounding down to 1440 minutes (1 week)."
deferral_timer_error_minutes=10080
else
deferral_timer_error_minutes="${deferral_timer_error_option}"
fi
defaults write "${SUPER_LOCAL_PLIST}" DeferralTimerError -string "${deferral_timer_error_minutes}"
elif [[ -n "${deferral_timer_error_option}" ]] && ! [[ "${deferral_timer_error_option}" =~ ${REGEX_ANY_WHOLE_NUMBER} ]]; then
log_super "Parameter Error: The --deferral-timer-error=minutes value must only be a number."
option_error="TRUE"
fi
[[ -z "${deferral_timer_error_minutes}" ]] && deferral_timer_error_minutes="${deferral_timer_minutes}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: deferral_timer_error_minutes is: ${deferral_timer_error_minutes}"
# Validate ${deferral_timer_workflow_relaunch_option} input and if valid set ${deferral_timer_workflow_relaunch_minutes} and save to ${SUPER_LOCAL_PLIST}. If there is no ${deferral_timer_workflow_relaunch_minutes} then set it to ${DEFERRAL_TIMER_WORKFLOW_RELAUNCH_DEFAULT_MINUTES}.
if [[ "${deferral_timer_workflow_relaunch_option}" == "X" ]]; then
log_super "Status: Deleting local preference for the --deferral-timer-workflow-relaunch option, defaulting to ${DEFERRAL_TIMER_WORKFLOW_RELAUNCH_DEFAULT_MINUTES} minutes."
defaults delete "${SUPER_LOCAL_PLIST}" DeferralTimerWorkflowRelaunch 2>/dev/null
unset deferral_timer_workflow_relaunch_option
elif [[ -n "${deferral_timer_workflow_relaunch_option}" ]] && [[ "${deferral_timer_workflow_relaunch_option}" =~ ${REGEX_ANY_WHOLE_NUMBER} ]]; then
if [[ "${deferral_timer_workflow_relaunch_option}" -lt 2 ]]; then
log_super "Parameter Warning: Specified --deferral-timer-workflow-relaunch=minutes value of ${deferral_timer_workflow_relaunch_option} minutes is too low, rounding up to 2 minutes."
deferral_timer_workflow_relaunch_minutes=2
elif [[ "${deferral_timer_workflow_relaunch_option}" -gt 43200 ]]; then
log_super "Parameter Warning: Specified --deferral-timer-workflow-relaunch=minutes value of ${deferral_timer_workflow_relaunch_option} minutes is too high, rounding down to 43200 minutes (30 days)."
deferral_timer_workflow_relaunch_minutes=43200
else
deferral_timer_workflow_relaunch_minutes="${deferral_timer_workflow_relaunch_option}"
fi
defaults write "${SUPER_LOCAL_PLIST}" DeferralTimerWorkflowRelaunch -string "${deferral_timer_workflow_relaunch_minutes}"
elif [[ -n "${deferral_timer_workflow_relaunch_option}" ]] && ! [[ "${deferral_timer_workflow_relaunch_option}" =~ ${REGEX_ANY_WHOLE_NUMBER} ]]; then
log_super "Parameter Error: The --deferral-timer-workflow-relaunch=minutes value must only be a number."
option_error="TRUE"
fi
[[ -z "${deferral_timer_workflow_relaunch_minutes}" ]] && deferral_timer_workflow_relaunch_minutes="${DEFERRAL_TIMER_WORKFLOW_RELAUNCH_DEFAULT_MINUTES}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: deferral_timer_workflow_relaunch_minutes is: ${deferral_timer_workflow_relaunch_minutes}"
# Manage ${schedule_workflow_active_option} and save to ${SUPER_LOCAL_PLIST}.
local extract_weekday
local extract_time
if [[ "${schedule_workflow_active_option}" == "X" ]]; then
log_super "Status: Deleting local preference for the --schedule-workflow-active option, defaulting to workflow is always active."
defaults delete "${SUPER_LOCAL_PLIST}" ScheduleWorkflowActive 2>/dev/null
unset schedule_workflow_active_option
elif [[ -n "${schedule_workflow_active_option}" ]]; then
previous_ifs="${IFS}"
IFS=','
read -r -a schedule_workflow_active_array <<<"${schedule_workflow_active_option}"
for schedule_workflow_active_time_frame in "${schedule_workflow_active_array[@]}"; do
extract_weekday="${schedule_workflow_active_time_frame:0:3}"
if ! [[ "${extract_weekday}" =~ ${REGEX_WEEKDAY} ]]; then
log_super "Parameter Error: Unrecognized --schedule-workflow-active weekday of ${extract_weekday}. The weekdays must be specified as MON|TUE|WED|THU|FRI|SAT|SUN."
option_error="TRUE"
fi
extract_time="${schedule_workflow_active_time_frame:4:11}"
if ! [[ "${extract_time}" =~ ${REGEX_HOURS_MINUTES_RANGE} ]]; then
log_super "Parameter Error: Each --schedule-workflow-active time frame value must be formated in 24-hour time as hh:mm-hh:mm."
option_error="TRUE"
fi
if ! [[ "${schedule_workflow_active_time_frame}" =~ ${REGEX_schedule_workflow_active_time_frame} ]]; then
log_super "Parameter Error: Each --schedule-workflow-active weekday and time frame must be formated as DAY:hh:mm-hh:mm."
option_error="TRUE"
fi
done
IFS="${previous_ifs}"
if [[ "${option_error}" != "TRUE" ]]; then
defaults write "${SUPER_LOCAL_PLIST}" ScheduleWorkflowActive -string "${schedule_workflow_active_option}"
else
unset schedule_workflow_active_option
fi
fi
{ [[ "${verbose_mode_option}" == "TRUE" ]] && [[ -n "${schedule_workflow_active_option}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: schedule_workflow_active_option is: ${schedule_workflow_active_option}"
# Manage ${schedule_zero_date_release_option} and save to ${SUPER_LOCAL_PLIST}.
if [[ "${schedule_zero_date_release_option}" -eq 1 ]] || [[ "${schedule_zero_date_release_option}" == "TRUE" ]]; then
schedule_zero_date_release_option="TRUE"
defaults write "${SUPER_LOCAL_PLIST}" ScheduleZeroDateRelease -bool true
else
schedule_zero_date_release_option="FALSE"
defaults delete "${SUPER_LOCAL_PLIST}" ScheduleZeroDateRelease 2>/dev/null
fi
{ [[ "${verbose_mode_option}" == "TRUE" ]] && [[ -n "${schedule_zero_date_release_option}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: schedule_zero_date_release_option is: ${schedule_zero_date_release_option}"
# Validate ${schedule_zero_date_sofa_custom_url_option} input and save to ${SUPER_LOCAL_PLIST}. This also sets ${sofa_macos_url}.
if [[ "${schedule_zero_date_sofa_custom_url_option}" == "X" ]]; then
log_super "Status: Deleting local preference for the --schedule-zero-date-sofa-custom-url option, now usign the default SOFA macOS releases feed at ${SOFA_MACOS_DEFAULT_URL}."
defaults delete "${SUPER_LOCAL_PLIST}" ScheduleZeroDateSOFACustomURL 2>/dev/null
unset schedule_zero_date_sofa_custom_url_option
elif [[ -n "${schedule_zero_date_sofa_custom_url_option}" ]] && ! [[ "${schedule_zero_date_sofa_custom_url_option}" =~ ${REGEX_HTTPS} ]]; then
log_super "Parameter Error: Invalid ---schedule-zero-date-sofa-custom-url VALUE must start with 'https://': ${schedule_zero_date_sofa_custom_url_option}"
option_error="TRUE"
elif [[ -n "${schedule_zero_date_sofa_custom_url_option}" ]] && [[ "${schedule_zero_date_sofa_custom_url_option}" =~ ${REGEX_HTTPS} ]]; then
if [[ "${schedule_zero_date_release_option}" == "TRUE" ]]; then
defaults write "${SUPER_LOCAL_PLIST}" ScheduleZeroDateSOFACustomURL -string "${schedule_zero_date_sofa_custom_url_option}"
sofa_macos_url="${schedule_zero_date_sofa_custom_url_option}"
else
log_super "Parameter Error: To use the --schedule-zero-date-sofa-custom-url option you must also use the --schedule-zero-date-release option."
option_error="TRUE"
defaults delete "${SUPER_LOCAL_PLIST}" ScheduleZeroDateSOFACustomURL 2>/dev/null
fi
fi
[[ -z "${sofa_macos_url}" ]] && sofa_macos_url="${SOFA_MACOS_DEFAULT_URL}"
{ [[ "${verbose_mode_option}" == "TRUE" ]] && [[ -n "${sofa_macos_url}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: sofa_macos_url is: ${sofa_macos_url}"
# Validate ${schedule_zero_date_manual_option} and if valid set ${schedule_zero_date_manual} and save to ${SUPER_LOCAL_PLIST}.
local extract_date
local sanitized_date
local extract_hours
local extract_minutes
if [[ "${schedule_zero_date_manual_option}" == "X" ]]; then
log_super "Status: Deleting local preference for the --schedule-zero-date-manual option, defaulting to an automatic zero date."
defaults delete "${SUPER_LOCAL_PLIST}" ScheduleZeroDateManual 2>/dev/null
unset schedule_zero_date_manual_option
elif [[ -n "${schedule_zero_date_manual_option}" ]]; then
extract_date="${schedule_zero_date_manual_option:0:10}"
if [[ "${extract_date}" =~ ${REGEX_DATE} ]]; then
extract_time="${schedule_zero_date_manual_option:11:5}"
if [[ -n "${extract_time}" ]]; then
extract_hours="${extract_time:0:2}"
[[ -z "${extract_hours}" ]] && extract_hours="00"
extract_minutes="${extract_time:3:2}"
[[ -z "${extract_minutes}" ]] && extract_minutes="00"
extract_time="${extract_hours}:${extract_minutes}"
else
extract_time="00:00"
fi
if [[ "${extract_time}" =~ ${REGEX_HOURS_MINUTES} ]]; then
sanitized_date="${extract_date}:${extract_time}"
else
log_super "Parameter Error: The --schedule-zero-date-manual value for time must be formated in 24-hour time as hh:mm."
option_error="TRUE"
fi
else
log_super "Parameter Error: The --schedule-zero-date-manual value for date must be formated as YYYY-MM-DD."
option_error="TRUE"
fi
if [[ "${sanitized_date}" =~ ${REGEX_DATE_HOURS_MINUTES} ]]; then
schedule_zero_date_manual="${sanitized_date}"
defaults write "${SUPER_LOCAL_PLIST}" ScheduleZeroDateManual -string "${schedule_zero_date_manual}"
else
log_super "Parameter Error: The --schedule-zero-date-manual value must be formatted as YYYY-MM-DD:hh:mm."
option_error="TRUE"
fi
fi
if [[ -n "${schedule_zero_date_manual}" ]] && [[ "${schedule_zero_date_release_option}" == "TRUE" ]]; then
log_super "Parameter Warning: When both the --schedule-zero-date-manual and the --schedule-zero-date-release options are enabled the --schedule-zero-date-manual option takes priority."
schedule_zero_date_release_option="FALSE"
fi
{ [[ "${verbose_mode_option}" == "TRUE" ]] && [[ -n "${schedule_zero_date_manual}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: schedule_zero_date_manual is: ${schedule_zero_date_manual}"
# Validate ${scheduled_install_days_option} input and if valid set ${scheduled_install_days}.
if [[ "${scheduled_install_days_option}" == "X" ]]; then
log_super "Status: Deleting local preference for the --scheduled-install-days option."
defaults delete "${SUPER_LOCAL_PLIST}" ScheduledInstallDays 2>/dev/null
unset scheduled_install_days_option
elif [[ -n "${scheduled_install_days_option}" ]] && [[ "${scheduled_install_days_option}" =~ ${REGEX_ANY_WHOLE_NUMBER} ]]; then
scheduled_install_days="${scheduled_install_days_option}"
elif [[ -n "${scheduled_install_days_option}" ]] && ! [[ "${scheduled_install_days_option}" =~ ${REGEX_ANY_WHOLE_NUMBER} ]]; then
log_super "Parameter Error: The --scheduled-install-days=number value must only be a number."
option_error="TRUE"
fi
# Validate ${scheduled_install_date_option}, if valid set ${scheduled_install_date} and ${scheduled_install_date_epoch}.
if [[ "${scheduled_install_date_option}" == "X" ]]; then
log_super "Status: Deleting local preference for the --scheduled-install-date option."
defaults delete "${SUPER_LOCAL_PLIST}" ScheduledInstallDate 2>/dev/null
unset scheduled_install_date_option
elif [[ -n "${scheduled_install_date_option}" ]]; then
extract_date="${scheduled_install_date_option:0:10}"
if [[ "${extract_date}" =~ ${REGEX_DATE} ]]; then
extract_time="${scheduled_install_date_option:11:5}"
if [[ -n "${extract_time}" ]]; then
extract_hours="${extract_time:0:2}"
[[ -z "${extract_hours}" ]] && extract_hours="00"
extract_minutes="${extract_time:3:2}"
[[ -z "${extract_minutes}" ]] && extract_minutes="00"
extract_time="${extract_hours}:${extract_minutes}"
else
extract_time="00:00"
fi
if [[ "${extract_time}" =~ ${REGEX_HOURS_MINUTES} ]]; then
sanitized_date="${extract_date}:${extract_time}"
else
log_super "Parameter Error: The --scheduled-install-date value for time must be formated in 24-hour time as hh:mm."
option_error="TRUE"
fi
else
log_super "Parameter Error: The --scheduled-install-date value for date must be formated as YYYY-MM-DD."
option_error="TRUE"
fi
if [[ "${sanitized_date}" =~ ${REGEX_DATE_HOURS_MINUTES} ]]; then
scheduled_install_date="${sanitized_date}"
else
log_super "Parameter Error: The --scheduled-install-date value must be formatted as YYYY-MM-DD:hh:mm."
option_error="TRUE"
fi
fi
# Manage ${scheduled_install_user_choice_option}.
if [[ "${scheduled_install_user_choice_option}" -eq 1 ]] || [[ "${scheduled_install_user_choice_option}" == "TRUE" ]]; then
scheduled_install_user_choice_option="TRUE"
else
scheduled_install_user_choice_option="FALSE"
defaults delete "${SUPER_LOCAL_PLIST}" ScheduledInstallUserChoice 2>/dev/null
fi
# Validated that ${scheduled_install_days}, ${scheduled_install_date}, and ${scheduled_install_user_choice_option} are not simultaneously active, if not then save the active scheduled restart to ${SUPER_LOCAL_PLIST}.
local scheduled_install_option_counter
scheduled_install_option_counter=0
[[ -n "${scheduled_install_days}" ]] && ((scheduled_install_option_counter++))
[[ -n "${scheduled_install_date}" ]] && ((scheduled_install_option_counter++))
[[ "${scheduled_install_user_choice_option}" == "TRUE" ]] && ((scheduled_install_option_counter++))
if [[ "${scheduled_install_option_counter}" -eq 1 ]]; then
[[ -n "${scheduled_install_days}" ]] && defaults write "${SUPER_LOCAL_PLIST}" ScheduledInstallDays -string "${scheduled_install_days}"
[[ -n "${scheduled_install_date}" ]] && defaults write "${SUPER_LOCAL_PLIST}" ScheduledInstallDate -string "${scheduled_install_date}"
[[ "${scheduled_install_user_choice_option}" == "TRUE" ]] && defaults write "${SUPER_LOCAL_PLIST}" ScheduledInstallUserChoice -bool true
elif [[ "${scheduled_install_option_counter}" -gt 1 ]]; then
log_super "Parameter Error: You can only use one of the following scheduled restart options at a time: --scheduled-install-days, --scheduled-install-date, or --scheduled-install-user-choice."
option_error="TRUE"
fi
{ [[ "${verbose_mode_option}" == "TRUE" ]] && [[ -n "${scheduled_install_option_counter}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: scheduled_install_option_counter is: ${scheduled_install_option_counter}"
{ [[ "${verbose_mode_option}" == "TRUE" ]] && [[ -n "${scheduled_install_days}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: scheduled_install_days is: ${scheduled_install_days}"
{ [[ "${verbose_mode_option}" == "TRUE" ]] && [[ -n "${scheduled_install_date}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: scheduled_install_date is: ${scheduled_install_date}"
{ [[ "${verbose_mode_option}" == "TRUE" ]] && [[ -n "${scheduled_install_user_choice_option}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: scheduled_install_user_choice_option is: ${scheduled_install_user_choice_option}"
# Validate ${scheduled_install_reminder_option} input and if valid set ${scheduled_install_reminder_minutes} and save to ${SUPER_LOCAL_PLIST}.
if [[ "${scheduled_install_reminder_option}" == "X" ]]; then
log_super "Status: Deleting local preference for the --scheduled-install-reminder option, defaulting to no warning prior to a scheduled restart."
defaults delete "${SUPER_LOCAL_PLIST}" ScheduledInstallReminder 2>/dev/null
unset scheduled_install_reminder_option
elif [[ -n "${scheduled_install_reminder_option}" ]] && [[ "${scheduled_install_reminder_option}" =~ ${REGEX_CSV_WHOLE_NUMBERS} ]]; then
if [[ "${scheduled_install_option_counter}" -eq 0 ]]; then
log_super "Parameter Error: The --scheduled-install-reminder=minutes option requires that you also set one of the following options: --scheduled-install-days, --scheduled-install-date, or --scheduled-install-user-choice."
option_error="TRUE"
else
previous_ifs="${IFS}"
IFS=','
local scheduled_install_reminder_array
read -r -a scheduled_install_reminder_array <<<"${scheduled_install_reminder_option}"
for array_index in "${!scheduled_install_reminder_array[@]}"; do
if [[ "${scheduled_install_reminder_array[array_index]}" -lt 2 ]]; then
log_super "Parameter Warning: Specified --scheduled-install-reminder=minutes value of ${scheduled_install_reminder_array[array_index]} minutes is too low, rounding up to 2 minutes."
scheduled_install_reminder_array[array_index]=2
elif [[ "${scheduled_install_reminder_array[array_index]}" -gt 10080 ]]; then
log_super "Parameter Warning: Specified --scheduled-install-reminder=minutes value of ${scheduled_install_reminder_array[array_index]} minutes is too high, rounding down to 10080 minutes (1 week)."
scheduled_install_reminder_array[array_index]=10080
fi
done
scheduled_install_reminder_minutes=$(echo "${scheduled_install_reminder_array[*]}" | sed -e $'s/,/\\\n/g' | sort -n -r | tr '\n' ',' | sed 's/.$//')
defaults write "${SUPER_LOCAL_PLIST}" ScheduledInstallReminder -string "${scheduled_install_reminder_minutes}"
IFS="${previous_ifs}"
fi
elif [[ -n "${scheduled_install_reminder_option}" ]] && ! [[ "${scheduled_install_reminder_option}" =~ ${REGEX_CSV_WHOLE_NUMBERS} ]]; then
log_super "Parameter Error: The --scheduled-install-reminder=minutes,minutes,etc... value must only contain numbers and commas (no spaces)."
option_error="TRUE"
fi
{ [[ "${verbose_mode_option}" == "TRUE" ]] && [[ -n "${scheduled_install_reminder_minutes}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: scheduled_install_reminder_minutes is: ${scheduled_install_reminder_minutes}"
# Validate ${deadline_count_focus_option} input and if valid set ${deadline_count_focus} and save to ${SUPER_LOCAL_PLIST}.
if [[ "${deadline_count_focus_option}" == "X" ]]; then
log_super "Status: Deleting local preference for the --deadline-count-focus option."
defaults delete "${SUPER_LOCAL_PLIST}" DeadlineCountFocus 2>/dev/null
unset deadline_count_focus_option
elif [[ -n "${deadline_count_focus_option}" ]] && [[ "${deadline_count_focus_option}" =~ ${REGEX_ANY_WHOLE_NUMBER} ]]; then
deadline_count_focus="${deadline_count_focus_option}"
defaults write "${SUPER_LOCAL_PLIST}" DeadlineCountFocus -string "${deadline_count_focus}"
elif [[ -n "${deadline_count_focus_option}" ]] && ! [[ "${deadline_count_focus_option}" =~ ${REGEX_ANY_WHOLE_NUMBER} ]]; then
log_super "Parameter Error: The --deadline-count-focus=number value must only be a number."
option_error="TRUE"
fi
# Validate ${deadline_count_soft_option} input and if valid set ${deadline_count_soft}.
if [[ "${deadline_count_soft_option}" == "X" ]]; then
log_super "Status: Deleting local preference for the --deadline-count-soft option."
defaults delete "${SUPER_LOCAL_PLIST}" DeadlineCountSoft 2>/dev/null
unset deadline_count_soft_option
elif [[ -n "${deadline_count_soft_option}" ]] && [[ "${deadline_count_soft_option}" =~ ${REGEX_ANY_WHOLE_NUMBER} ]]; then
deadline_count_soft="${deadline_count_soft_option}"
elif [[ -n "${deadline_count_soft_option}" ]] && ! [[ "${deadline_count_soft_option}" =~ ${REGEX_ANY_WHOLE_NUMBER} ]]; then
log_super "Parameter Error: The --deadline-count-soft=number value must only be a number."
option_error="TRUE"
fi
# Validate ${deadline_count_hard_option} input and if valid set ${deadline_count_hard}.
if [[ "${deadline_count_hard_option}" == "X" ]]; then
log_super "Status: Deleting local preference for the --deadline-count-hard option."
defaults delete "${SUPER_LOCAL_PLIST}" DeadlineCountHard 2>/dev/null
unset deadline_count_hard_option
elif [[ -n "${deadline_count_hard_option}" ]] && [[ "${deadline_count_hard_option}" =~ ${REGEX_ANY_WHOLE_NUMBER} ]]; then
deadline_count_hard="${deadline_count_hard_option}"
elif [[ -n "${deadline_count_hard_option}" ]] && ! [[ "${deadline_count_hard_option}" =~ ${REGEX_ANY_WHOLE_NUMBER} ]]; then
log_super "Parameter Error: The --deadline-count-hard=number value must only be a number."
option_error="TRUE"
fi
# Validated that ${deadline_count_soft} and ${deadline_count_hard} are not both active, if not then save ${deadline_count_soft} or ${deadline_count_hard} to ${SUPER_LOCAL_PLIST}.
if [[ -n "${deadline_count_soft}" ]] && [[ -n "${deadline_count_hard}" ]]; then
log_super "Parameter Error: You can not use both the --deadline-count-soft and --deadline-count-hard options at the same time. You must pick one deadline count behavior."
option_error="TRUE"
else
[[ -n "${deadline_count_soft}" ]] && defaults write "${SUPER_LOCAL_PLIST}" DeadlineCountSoft -string "${deadline_count_soft}"
[[ -n "${deadline_count_hard}" ]] && defaults write "${SUPER_LOCAL_PLIST}" DeadlineCountHard -string "${deadline_count_hard}"
fi
{ [[ "${verbose_mode_option}" == "TRUE" ]] && [[ -n "${deadline_count_focus}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: deadline_count_focus is: ${deadline_count_focus}"
{ [[ "${verbose_mode_option}" == "TRUE" ]] && [[ -n "${deadline_count_soft}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: deadline_count_soft is: ${deadline_count_soft}"
{ [[ "${verbose_mode_option}" == "TRUE" ]] && [[ -n "${deadline_count_hard}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: deadline_count_hard is: ${deadline_count_hard}"
# Validate ${deadline_days_focus_option} input and if valid set ${deadline_days_focus}.
if [[ "${deadline_days_focus_option}" == "X" ]]; then
log_super "Status: Deleting local preference for the --deadline-days-focus option."
defaults delete "${SUPER_LOCAL_PLIST}" DeadlineDaysFocus 2>/dev/null
unset deadline_days_focus_option
elif [[ -n "${deadline_days_focus_option}" ]] && [[ "${deadline_days_focus_option}" =~ ${REGEX_ANY_WHOLE_NUMBER} ]]; then
deadline_days_focus="${deadline_days_focus_option}"
elif [[ -n "${deadline_days_focus_option}" ]] && ! [[ "${deadline_days_focus_option}" =~ ${REGEX_ANY_WHOLE_NUMBER} ]]; then
log_super "Parameter Error: The --deadline-days-focus=number value must only be a number."
option_error="TRUE"
fi
# Validate ${deadline_days_soft_option} input and if valid set ${deadline_days_soft}.
if [[ "${deadline_days_soft_option}" == "X" ]]; then
log_super "Status: Deleting local preference for the --deadline-days-soft option."
defaults delete "${SUPER_LOCAL_PLIST}" DeadlineDaysSoft 2>/dev/null
unset deadline_days_soft_option
elif [[ -n "${deadline_days_soft_option}" ]] && [[ "${deadline_days_soft_option}" =~ ${REGEX_ANY_WHOLE_NUMBER} ]]; then
deadline_days_soft="${deadline_days_soft_option}"
elif [[ -n "${deadline_days_soft_option}" ]] && ! [[ "${deadline_days_soft_option}" =~ ${REGEX_ANY_WHOLE_NUMBER} ]]; then
log_super "Parameter Error: The --deadline-days-soft=number value must only be a number."
option_error="TRUE"
fi
# Validate ${deadline_days_hard_option} input and if valid set ${deadline_days_hard}.
if [[ "${deadline_days_hard_option}" == "X" ]]; then
log_super "Status: Deleting local preference for the --deadline-days-hard option."
defaults delete "${SUPER_LOCAL_PLIST}" DeadlineDaysHard 2>/dev/null
unset deadline_days_hard_option
elif [[ -n "${deadline_days_hard_option}" ]] && [[ "${deadline_days_hard_option}" =~ ${REGEX_ANY_WHOLE_NUMBER} ]]; then
deadline_days_hard="${deadline_days_hard_option}"
elif [[ -n "${deadline_days_hard_option}" ]] && ! [[ "${deadline_days_hard_option}" =~ ${REGEX_ANY_WHOLE_NUMBER} ]]; then
log_super "Parameter Error: The --deadline-days-hard=number value must only be a number."
option_error="TRUE"
fi
# Validate ${deadline_days_focus}, ${deadline_days_soft}, and ${deadline_days_hard} in relation to each other, and if valid save to ${SUPER_LOCAL_PLIST}.
if [[ -n "${deadline_days_hard}" ]] && [[ -n "${deadline_days_soft}" ]] && [[ "${deadline_days_hard}" -le "${deadline_days_soft}" ]]; then
log_super "Parameter Error: The --deadline-days-hard=number value of ${deadline_days_hard} day(s) must be more than the --deadline-days-soft=number value of ${deadline_days_soft} day(s)."
option_error="TRUE"
fi
if [[ -n "${deadline_days_hard}" ]] && [[ -n "${deadline_days_focus}" ]] && [[ "${deadline_days_hard}" -le "${deadline_days_focus}" ]]; then
log_super "Parameter Error: The --deadline-days-hard=number value of ${deadline_days_hard} day(s) must be more than the --deadline-days-focus=number value of ${deadline_days_focus} day(s)."
option_error="TRUE"
fi
if [[ -n "${deadline_days_soft}" ]] && [[ -n "${deadline_days_focus}" ]] && [[ "${deadline_days_soft}" -le "${deadline_days_focus}" ]]; then
log_super "Parameter Error: The --deadline-days-soft=number value of ${deadline_days_soft} day(s) must be more than the --deadline-days-focus=number value of ${deadline_days_focus} day(s)."
option_error="TRUE"
fi
if [[ "${option_error}" != "TRUE" ]]; then
[[ -n "${deadline_days_focus}" ]] && defaults write "${SUPER_LOCAL_PLIST}" DeadlineDaysFocus -string "${deadline_days_focus}"
[[ -n "${deadline_days_soft}" ]] && defaults write "${SUPER_LOCAL_PLIST}" DeadlineDaysSoft -string "${deadline_days_soft}"
[[ -n "${deadline_days_hard}" ]] && defaults write "${SUPER_LOCAL_PLIST}" DeadlineDaysHard -string "${deadline_days_hard}"
fi
{ [[ "${verbose_mode_option}" == "TRUE" ]] && [[ -n "${deadline_days_focus}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: deadline_days_focus is: ${deadline_days_focus}"
{ [[ "${verbose_mode_option}" == "TRUE" ]] && [[ -n "${deadline_days_soft}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: deadline_days_soft is: ${deadline_days_soft}"
{ [[ "${verbose_mode_option}" == "TRUE" ]] && [[ -n "${deadline_days_hard}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: deadline_days_hard is: ${deadline_days_hard}"
# Validate ${deadline_date_focus_option}, if valid set ${deadline_date_focus} and ${deadline_date_focus_epoch}.
if [[ "${deadline_date_focus_option}" == "X" ]]; then
log_super "Status: Deleting local preference for the --deadline-date-focus option."
defaults delete "${SUPER_LOCAL_PLIST}" DeadlineDateFocus 2>/dev/null
unset deadline_date_focus_option
elif [[ -n "${deadline_date_focus_option}" ]]; then
extract_date="${deadline_date_focus_option:0:10}"
if [[ "${extract_date}" =~ ${REGEX_DATE} ]]; then
extract_time="${deadline_date_focus_option:11:5}"
if [[ -n "${extract_time}" ]]; then
extract_hours="${extract_time:0:2}"
[[ -z "${extract_hours}" ]] && extract_hours="00"
extract_minutes="${extract_time:3:2}"
[[ -z "${extract_minutes}" ]] && extract_minutes="00"
extract_time="${extract_hours}:${extract_minutes}"
else
extract_time="00:00"
fi
if [[ "${extract_time}" =~ ${REGEX_HOURS_MINUTES} ]]; then
sanitized_date="${extract_date}:${extract_time}"
else
log_super "Parameter Error: The --deadline-date-focus value for time must be formated in 24-hour time as hh:mm."
option_error="TRUE"
fi
else
log_super "Parameter Error: The --deadline-date-focus value for date must be formated as YYYY-MM-DD."
option_error="TRUE"
fi
if [[ "${sanitized_date}" =~ ${REGEX_DATE_HOURS_MINUTES} ]]; then
deadline_date_focus="${sanitized_date}"
deadline_date_focus_epoch=$(date -j -f %Y-%m-%d:%H:%M:%S "${sanitized_date}:00" +%s)
else
log_super "Parameter Error: The --deadline-date-focus value must be formatted as YYYY-MM-DD:hh:mm."
option_error="TRUE"
fi
fi
# Validate ${deadline_date_soft_option}, if valid set ${deadline_date_soft} and ${deadline_date_soft_epoch}.
if [[ "${deadline_date_soft_option}" == "X" ]]; then
log_super "Status: Deleting local preference for the --deadline-date-soft option."
defaults delete "${SUPER_LOCAL_PLIST}" DeadlineDateSoft 2>/dev/null
unset deadline_date_soft_option
elif [[ -n "${deadline_date_soft_option}" ]]; then
extract_date="${deadline_date_soft_option:0:10}"
if [[ "${extract_date}" =~ ${REGEX_DATE} ]]; then
extract_time="${deadline_date_soft_option:11:5}"
if [[ -n "${extract_time}" ]]; then
extract_hours="${extract_time:0:2}"
[[ -z "${extract_hours}" ]] && extract_hours="00"
extract_minutes="${extract_time:3:2}"
[[ -z "${extract_minutes}" ]] && extract_minutes="00"
extract_time="${extract_hours}:${extract_minutes}"
else
extract_time="00:00"
fi
if [[ "${extract_time}" =~ ${REGEX_HOURS_MINUTES} ]]; then
sanitized_date="${extract_date}:${extract_time}"
else
log_super "Parameter Error: The --deadline-date-soft value for time must be formated in 24-hour time as hh:mm."
option_error="TRUE"
fi
else
log_super "Parameter Error: The --deadline-date-soft value for date must be formated as YYYY-MM-DD."
option_error="TRUE"
fi
if [[ "${sanitized_date}" =~ ${REGEX_DATE_HOURS_MINUTES} ]]; then
deadline_date_soft="${sanitized_date}"
deadline_date_soft_epoch=$(date -j -f %Y-%m-%d:%H:%M:%S "${sanitized_date}:00" +%s)
else
log_super "Parameter Error: The --deadline-date-soft value must be formatted as YYYY-MM-DD:hh:mm."
option_error="TRUE"
fi
fi
# Validate ${deadline_date_hard_option}, if valid set ${deadline_date_hard} and ${deadline_date_hard_epoch}.
if [[ "${deadline_date_hard_option}" == "X" ]]; then
log_super "Status: Deleting local preference for the --deadline-date-hard option."
defaults delete "${SUPER_LOCAL_PLIST}" DeadlineDateHard 2>/dev/null
unset deadline_date_hard_option
elif [[ -n "${deadline_date_hard_option}" ]]; then
extract_date="${deadline_date_hard_option:0:10}"
if [[ "${extract_date}" =~ ${REGEX_DATE} ]]; then
extract_time="${deadline_date_hard_option:11:5}"
if [[ -n "${extract_time}" ]]; then
extract_hours="${extract_time:0:2}"
[[ -z "${extract_hours}" ]] && extract_hours="00"
extract_minutes="${extract_time:3:2}"
[[ -z "${extract_minutes}" ]] && extract_minutes="00"
extract_time="${extract_hours}:${extract_minutes}"
else
extract_time="00:00"
fi
if [[ "${extract_time}" =~ ${REGEX_HOURS_MINUTES} ]]; then
sanitized_date="${extract_date}:${extract_time}"
else
log_super "Parameter Error: The --deadline-date-hard value for time must be formated in 24-hour time as hh:mm."
option_error="TRUE"
fi
else
log_super "Parameter Error: The --deadline-date-hard value for date must be formated as YYYY-MM-DD."
option_error="TRUE"
fi
if [[ "${sanitized_date}" =~ ${REGEX_DATE_HOURS_MINUTES} ]]; then
deadline_date_hard="${sanitized_date}"
deadline_date_hard_epoch=$(date -j -f %Y-%m-%d:%H:%M:%S "${sanitized_date}:00" +%s)
else
log_super "Parameter Error: The --deadline-date-hard value must be formatted as YYYY-MM-DD:hh:mm."
option_error="TRUE"
fi
fi
# Validate ${deadline_date_focus_epoch}, ${deadline_date_soft_epoch}, and ${deadline_date_hard_epoch} in relation to each other. If valid then save date deadlines to ${SUPER_LOCAL_PLIST}.
if [[ -n "${deadline_date_hard_epoch}" ]] && [[ -n "${deadline_date_soft_epoch}" ]] && [[ "${deadline_date_hard_epoch}" -le "${deadline_date_soft_epoch}" ]]; then
log_super "Parameter Error: The --deadline-date-hard value of ${deadline_date_hard} must be later than the --deadline-date-soft value of ${deadline_date_soft}."
option_error="TRUE"
fi
if [[ -n "${deadline_date_hard_epoch}" ]] && [[ -n "${deadline_date_focus_epoch}" ]] && [[ "${deadline_date_hard_epoch}" -le "${deadline_date_focus_epoch}" ]]; then
log_super "Parameter Error: The --deadline-date-hard value of ${deadline_date_hard} must be later than --deadline-date-focus value of ${deadline_date_focus}."
option_error="TRUE"
fi
if [[ -n "${deadline_date_soft_epoch}" ]] && [[ -n "${deadline_date_focus_epoch}" ]] && [[ "${deadline_date_soft_epoch}" -le "${deadline_date_focus_epoch}" ]]; then
log_super "Parameter Error: The --deadline-date-soft value of ${deadline_date_soft} must be later than than --deadline-date-focus value of ${deadline_date_focus}."
option_error="TRUE"
fi
if [[ "${option_error}" != "TRUE" ]]; then
[[ -n "${deadline_date_focus}" ]] && defaults write "${SUPER_LOCAL_PLIST}" DeadlineDateFocus -string "${deadline_date_focus}"
[[ -n "${deadline_date_soft}" ]] && defaults write "${SUPER_LOCAL_PLIST}" DeadlineDateSoft -string "${deadline_date_soft}"
[[ -n "${deadline_date_hard}" ]] && defaults write "${SUPER_LOCAL_PLIST}" DeadlineDateHard -string "${deadline_date_hard}"
fi
{ [[ "${verbose_mode_option}" == "TRUE" ]] && [[ -n "${deadline_date_focus}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: deadline_date_focus is: ${deadline_date_focus}"
{ [[ "${verbose_mode_option}" == "TRUE" ]] && [[ -n "${deadline_date_soft}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: deadline_date_soft is: ${deadline_date_soft}"
{ [[ "${verbose_mode_option}" == "TRUE" ]] && [[ -n "${deadline_date_hard}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: deadline_date_hard is: ${deadline_date_hard}"
# Some validation and logging for the focus deferral timer option.
if [[ -n "${deferral_timer_focus_option}" ]] && { [[ -z "${deadline_count_focus}" ]] && [[ -z "${deadline_days_focus}" ]] && [[ -z "${deadline_date_focus}" ]]; }; then
log_super "Parameter Error: The --deferral-timer-focus option requires that you also specify at least one focus deadline option."
option_error="TRUE"
fi
# Validate ${dialog_timeout_default_option} and if valid set ${dialog_timeout_default_seconds} and save to ${SUPER_LOCAL_PLIST}.
if [[ "${dialog_timeout_default_option}" == "X" ]]; then
log_super "Status: Deleting local preference for the --dialog-timeout-default option."
defaults delete "${SUPER_LOCAL_PLIST}" DialogTimeoutDefault 2>/dev/null
unset dialog_timeout_default_option
elif [[ -n "${dialog_timeout_default_option}" ]] && [[ "${dialog_timeout_default_option}" =~ ${REGEX_ANY_WHOLE_NUMBER} ]]; then
if [[ "${dialog_timeout_default_option}" -lt 60 ]]; then
log_super "Parameter Warning: Specified --dialog-timeout-default=seconds value of ${dialog_timeout_default_option} seconds is too low, rounding up to 60 seconds."
dialog_timeout_default_seconds=60
elif [[ "${dialog_timeout_default_option}" -gt 86400 ]]; then
log_super "Parameter Warning: Specified --dialog-timeout-default=seconds value of ${dialog_timeout_default_option} seconds is too high, rounding down to 86400 seconds (1 day)."
dialog_timeout_default_seconds=86400
else
dialog_timeout_default_seconds="${dialog_timeout_default_option}"
fi
defaults write "${SUPER_LOCAL_PLIST}" DialogTimeoutDefault -string "${dialog_timeout_default_seconds}"
elif [[ -n "${dialog_timeout_default_option}" ]] && ! [[ "${dialog_timeout_default_option}" =~ ${REGEX_ANY_WHOLE_NUMBER} ]]; then
log_super "Parameter Error: The --dialog-timeout-default=seconds value must only be a number."
option_error="TRUE"
fi
{ [[ "${verbose_mode_option}" == "TRUE" ]] && [[ -n "${dialog_timeout_default_seconds}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: dialog_timeout_default_seconds is: ${dialog_timeout_default_seconds}"
# Validate ${dialog_timeout_user_auth_option} and if valid set ${dialog_timeout_user_auth_seconds} and save to ${SUPER_LOCAL_PLIST}.
if [[ "${dialog_timeout_user_auth_option}" == "X" ]]; then
log_super "Status: Deleting local preference for the --dialog-timeout-user-auth option."
defaults delete "${SUPER_LOCAL_PLIST}" DialogTimeoutUserAuth 2>/dev/null
unset dialog_timeout_user_auth_option
elif [[ -n "${dialog_timeout_user_auth_option}" ]] && [[ "${dialog_timeout_user_auth_option}" =~ ${REGEX_ANY_WHOLE_NUMBER} ]]; then
if [[ "${dialog_timeout_user_auth_option}" -lt 60 ]]; then
log_super "Parameter Warning: Specified --dialog-timeout-user-auth=seconds value of ${dialog_timeout_user_auth_option} seconds is too low, rounding up to 60 seconds."
dialog_timeout_user_auth_seconds=60
elif [[ "${dialog_timeout_user_auth_option}" -gt 86400 ]]; then
log_super "Parameter Warning: Specified --dialog-timeout-user-auth=seconds value of ${dialog_timeout_user_auth_option} seconds is too high, rounding down to 86400 seconds (1 day)."
dialog_timeout_user_auth_seconds=86400
else
dialog_timeout_user_auth_seconds="${dialog_timeout_user_auth_option}"
fi
defaults write "${SUPER_LOCAL_PLIST}" DialogTimeoutUserAuth -string "${dialog_timeout_user_auth_seconds}"
elif [[ -n "${dialog_timeout_user_auth_option}" ]] && ! [[ "${dialog_timeout_user_auth_option}" =~ ${REGEX_ANY_WHOLE_NUMBER} ]]; then
log_super "Parameter Error: The --dialog-timeout-user-auth=seconds value must only be a number."
option_error="TRUE"
fi
{ [[ "${verbose_mode_option}" == "TRUE" ]] && [[ -n "${dialog_timeout_user_auth_seconds}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: dialog_timeout_user_auth_seconds is: ${dialog_timeout_user_auth_seconds}"
# Validate ${dialog_timeout_user_choice_option} and if valid set ${dialog_timeout_user_choice_seconds} and save to ${SUPER_LOCAL_PLIST}.
if [[ "${dialog_timeout_user_choice_option}" == "X" ]]; then
log_super "Status: Deleting local preference for the --dialog-timeout-user-choice option."
defaults delete "${SUPER_LOCAL_PLIST}" DialogTimeoutUserChoice 2>/dev/null
unset dialog_timeout_user_choice_option
elif [[ -n "${dialog_timeout_user_choice_option}" ]] && [[ "${dialog_timeout_user_choice_option}" =~ ${REGEX_ANY_WHOLE_NUMBER} ]]; then
if [[ "${dialog_timeout_user_choice_option}" -lt 60 ]]; then
log_super "Parameter Warning: Specified --dialog-timeout-user-choice=seconds value of ${dialog_timeout_user_choice_option} seconds is too low, rounding up to 60 seconds."
dialog_timeout_user_choice_seconds=60
elif [[ "${dialog_timeout_user_choice_option}" -gt 86400 ]]; then
log_super "Parameter Warning: Specified --dialog-timeout-user-choice=seconds value of ${dialog_timeout_user_choice_option} seconds is too high, rounding down to 86400 seconds (1 day)."
dialog_timeout_user_choice_seconds=86400
else
dialog_timeout_user_choice_seconds="${dialog_timeout_user_choice_option}"
fi
defaults write "${SUPER_LOCAL_PLIST}" DialogTimeoutUserChoice -string "${dialog_timeout_user_choice_seconds}"
elif [[ -n "${dialog_timeout_user_choice_option}" ]] && ! [[ "${dialog_timeout_user_choice_option}" =~ ${REGEX_ANY_WHOLE_NUMBER} ]]; then
log_super "Parameter Error: The --dialog-timeout-user-choice=seconds value must only be a number."
option_error="TRUE"
fi
{ [[ "${verbose_mode_option}" == "TRUE" ]] && [[ -n "${dialog_timeout_user_choice_seconds}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: dialog_timeout_user_choice_seconds is: ${dialog_timeout_user_choice_seconds}"
# Validate ${dialog_timeout_user_schedule_option} and if valid set ${dialog_timeout_user_schedule_seconds} and save to ${SUPER_LOCAL_PLIST}.
if [[ "${dialog_timeout_user_schedule_option}" == "X" ]]; then
log_super "Status: Deleting local preference for the --dialog-timeout-user-schedule option."
defaults delete "${SUPER_LOCAL_PLIST}" DialogTimeoutUserSchedule 2>/dev/null
unset dialog_timeout_user_schedule_option
elif [[ -n "${dialog_timeout_user_schedule_option}" ]] && [[ "${dialog_timeout_user_schedule_option}" =~ ${REGEX_ANY_WHOLE_NUMBER} ]]; then
if [[ "${dialog_timeout_user_schedule_option}" -lt 60 ]]; then
log_super "Parameter Warning: Specified --dialog-timeout-user-schedule=seconds value of ${dialog_timeout_user_schedule_option} seconds is too low, rounding up to 60 seconds."
dialog_timeout_user_schedule_seconds=60
elif [[ "${dialog_timeout_user_schedule_option}" -gt 86400 ]]; then
log_super "Parameter Warning: Specified --dialog-timeout-user-schedule=seconds value of ${dialog_timeout_user_schedule_option} seconds is too high, rounding down to 86400 seconds (1 day)."
dialog_timeout_user_schedule_seconds=86400
else
dialog_timeout_user_schedule_seconds="${dialog_timeout_user_schedule_option}"
fi
defaults write "${SUPER_LOCAL_PLIST}" DialogTimeoutUserSchedule -string "${dialog_timeout_user_schedule_seconds}"
elif [[ -n "${dialog_timeout_user_schedule_option}" ]] && ! [[ "${dialog_timeout_user_schedule_option}" =~ ${REGEX_ANY_WHOLE_NUMBER} ]]; then
log_super "Parameter Error: The --dialog-timeout-user-schedule=seconds value must only be a number."
option_error="TRUE"
fi
{ [[ "${verbose_mode_option}" == "TRUE" ]] && [[ -n "${dialog_timeout_user_schedule_seconds}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: dialog_timeout_user_schedule_seconds is: ${dialog_timeout_user_schedule_seconds}"
# Validate ${dialog_timeout_soft_deadline_option} and if valid set ${dialog_timeout_soft_deadline_seconds} and save to ${SUPER_LOCAL_PLIST}.
if [[ "${dialog_timeout_soft_deadline_option}" == "X" ]]; then
log_super "Status: Deleting local preference for the --dialog-timeout-soft-deadline option."
defaults delete "${SUPER_LOCAL_PLIST}" DialogTimeoutSoftDeadline 2>/dev/null
unset dialog_timeout_soft_deadline_option
elif [[ -n "${dialog_timeout_soft_deadline_option}" ]] && [[ "${dialog_timeout_soft_deadline_option}" =~ ${REGEX_ANY_WHOLE_NUMBER} ]]; then
if [[ "${dialog_timeout_soft_deadline_option}" -lt 60 ]]; then
log_super "Parameter Warning: Specified --dialog-timeout-soft-deadline=seconds value of ${dialog_timeout_soft_deadline_option} seconds is too low, rounding up to 60 seconds."
dialog_timeout_soft_deadline_seconds=60
elif [[ "${dialog_timeout_soft_deadline_option}" -gt 86400 ]]; then
log_super "Parameter Warning: Specified --dialog-timeout-soft-deadline=seconds value of ${dialog_timeout_soft_deadline_option} seconds is too high, rounding down to 86400 seconds (1 day)."
dialog_timeout_soft_deadline_seconds=86400
else
dialog_timeout_soft_deadline_seconds="${dialog_timeout_soft_deadline_option}"
fi
defaults write "${SUPER_LOCAL_PLIST}" DialogTimeoutSoftDeadline -string "${dialog_timeout_soft_deadline_seconds}"
elif [[ -n "${dialog_timeout_soft_deadline_option}" ]] && ! [[ "${dialog_timeout_soft_deadline_option}" =~ ${REGEX_ANY_WHOLE_NUMBER} ]]; then
log_super "Parameter Error: The --dialog-timeout-soft-deadline=seconds value must only be a number."
option_error="TRUE"
fi
{ [[ "${verbose_mode_option}" == "TRUE" ]] && [[ -n "${dialog_timeout_soft_deadline_seconds}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: dialog_timeout_soft_deadline_seconds is: ${dialog_timeout_soft_deadline_seconds}"
# Validate ${dialog_timeout_insufficient_storage_option} and if valid set ${dialog_timeout_insufficient_storage_seconds} and save to ${SUPER_LOCAL_PLIST}.
if [[ "${dialog_timeout_insufficient_storage_option}" == "X" ]]; then
log_super "Status: Deleting local preference for the --dialog-timeout-insufficient-storage option."
defaults delete "${SUPER_LOCAL_PLIST}" DialogTimeoutInsufficientStorage 2>/dev/null
unset dialog_timeout_insufficient_storage_option
elif [[ -n "${dialog_timeout_insufficient_storage_option}" ]] && [[ "${dialog_timeout_insufficient_storage_option}" =~ ${REGEX_ANY_WHOLE_NUMBER} ]]; then
if [[ "${dialog_timeout_insufficient_storage_option}" -lt 60 ]]; then
log_super "Parameter Warning: Specified --dialog-timeout-insufficient-storage=seconds value of ${dialog_timeout_insufficient_storage_option} seconds is too low, rounding up to 60 seconds."
dialog_timeout_insufficient_storage_seconds=60
elif [[ "${dialog_timeout_insufficient_storage_option}" -gt 86400 ]]; then
log_super "Parameter Warning: Specified --dialog-timeout-insufficient-storage=seconds value of ${dialog_timeout_insufficient_storage_option} seconds is too high, rounding down to 86400 seconds (1 day)."
dialog_timeout_insufficient_storage_seconds=86400
else
dialog_timeout_insufficient_storage_seconds="${dialog_timeout_insufficient_storage_option}"
fi
defaults write "${SUPER_LOCAL_PLIST}" DialogTimeoutInsufficientStorage -string "${dialog_timeout_insufficient_storage_seconds}"
elif [[ -n "${dialog_timeout_insufficient_storage_option}" ]] && ! [[ "${dialog_timeout_insufficient_storage_option}" =~ ${REGEX_ANY_WHOLE_NUMBER} ]]; then
log_super "Parameter Error: The --dialog-timeout-insufficient-storage=seconds value must only be a number."
option_error="TRUE"
fi
{ [[ "${verbose_mode_option}" == "TRUE" ]] && [[ -n "${dialog_timeout_insufficient_storage_seconds}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: dialog_timeout_insufficient_storage_seconds is: ${dialog_timeout_insufficient_storage_seconds}"
# Validate ${dialog_timeout_power_required_option} and if valid set ${dialog_timeout_power_required_seconds} and save to ${SUPER_LOCAL_PLIST}.
if [[ "${dialog_timeout_power_required_option}" == "X" ]]; then
log_super "Status: Deleting local preference for the --dialog-timeout-power-required option."
defaults delete "${SUPER_LOCAL_PLIST}" DialogTimeoutPowerRequired 2>/dev/null
unset dialog_timeout_insufficient_storage_option
elif [[ -n "${dialog_timeout_power_required_option}" ]] && [[ "${dialog_timeout_power_required_option}" =~ ${REGEX_ANY_WHOLE_NUMBER} ]]; then
if [[ "${dialog_timeout_power_required_option}" -lt 60 ]]; then
log_super "Parameter Warning: Specified --dialog-timeout-power-required=seconds value of ${dialog_timeout_power_required_option} seconds is too low, rounding up to 60 seconds."
dialog_timeout_power_required_seconds=60
elif [[ "${dialog_timeout_power_required_option}" -gt 86400 ]]; then
log_super "Parameter Warning: Specified --dialog-timeout-power-required=seconds value of ${dialog_timeout_power_required_option} seconds is too high, rounding down to 86400 seconds (1 day)."
dialog_timeout_power_required_seconds=86400
else
dialog_timeout_power_required_seconds="${dialog_timeout_power_required_option}"
fi
defaults write "${SUPER_LOCAL_PLIST}" DialogTimeoutPowerRequired -string "${dialog_timeout_power_required_seconds}"
elif [[ -n "${dialog_timeout_power_required_option}" ]] && ! [[ "${dialog_timeout_power_required_option}" =~ ${REGEX_ANY_WHOLE_NUMBER} ]]; then
log_super "Parameter Error: The --dialog-timeout-power-required=seconds value must only be a number."
option_error="TRUE"
fi
{ [[ "${verbose_mode_option}" == "TRUE" ]] && [[ -n "${dialog_timeout_power_required_seconds}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: dialog_timeout_power_required_seconds is: ${dialog_timeout_power_required_seconds}"
# Validate ${display_unmovable_option} and if valid save to ${SUPER_LOCAL_PLIST}.
if [[ "${display_unmovable_option}" == "X" ]]; then
log_super "Status: Deleting local preference for the --display-unmovable option, defaulting to movable dialogs and notifications."
defaults delete "${SUPER_LOCAL_PLIST}" DisplayUnmovable 2>/dev/null
unset display_unmovable_option
elif [[ -n "${display_unmovable_option}" ]]; then
previous_ifs="${IFS}"
IFS=','
local display_unmovable_array
read -r -a display_unmovable_array <<<"${display_unmovable_option}"
for option_type in "${display_unmovable_array[@]}"; do
if ! [[ "${option_type}" =~ ${REGEX_WORKFLOW_OPTIONS} ]]; then
log_super "Parameter Error: Unrecognized --display-unmovable type of ${option_type}. You can only specify the following types separated by commas (no spaces): ALWAYS,DIALOG,DEADLINE,SCHEDULED,INSTALLNOW,ERROR"
option_error="TRUE"
fi
done
IFS="${previous_ifs}"
[[ "${option_error}" != "TRUE" ]] && defaults write "${SUPER_LOCAL_PLIST}" DisplayUnmovable -string "${display_unmovable_option}"
[[ $(echo "${display_unmovable_option}" | grep -c 'ALWAYS') -gt 0 ]] && display_unmovable_status="TRUE"
fi
{ [[ "${verbose_mode_option}" == "TRUE" ]] && [[ -n "${display_unmovable_option}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: display_unmovable_option is: ${display_unmovable_option}"
{ [[ "${verbose_mode_option}" == "TRUE" ]] && [[ -n "${display_unmovable_status}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: display_unmovable_status is: ${display_unmovable_status}"
# Validate ${display_hide_background_option} and if valid save to ${SUPER_LOCAL_PLIST}.
if [[ "${display_hide_background_option}" == "X" ]]; then
log_super "Status: Deleting local preference for the --display-hide-background option, defaulting to visible background behind dialogs and notifications."
defaults delete "${SUPER_LOCAL_PLIST}" DisplayHideBackground 2>/dev/null
unset display_hide_background_option
elif [[ -n "${display_hide_background_option}" ]]; then
previous_ifs="${IFS}"
IFS=','
local display_hide_background_array
read -r -a display_hide_background_array <<<"${display_hide_background_option}"
for option_type in "${display_hide_background_array[@]}"; do
if ! [[ "${option_type}" =~ ${REGEX_WORKFLOW_OPTIONS} ]]; then
log_super "Parameter Error: Unrecognized --display-hide-background type of ${option_type}. You can only specify the following types separated by commas (no spaces): ALWAYS,DIALOG,DEADLINE,SCHEDULED,INSTALLNOW,ERROR"
option_error="TRUE"
fi
done
IFS="${previous_ifs}"
[[ "${option_error}" != "TRUE" ]] && defaults write "${SUPER_LOCAL_PLIST}" DisplayHideBackground -string "${display_hide_background_option}"
[[ $(echo "${display_hide_background_option}" | grep -c 'ALWAYS') -gt 0 ]] && display_hide_background_status="TRUE"
fi
{ [[ "${verbose_mode_option}" == "TRUE" ]] && [[ -n "${display_hide_background_option}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: display_hide_background_option is: ${display_hide_background_option}"
{ [[ "${verbose_mode_option}" == "TRUE" ]] && [[ -n "${display_hide_background_status}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: display_hide_background_status is: ${display_hide_background_status}"
# Validate ${display_silently_option} and if valid save to ${SUPER_LOCAL_PLIST}.
if [[ "${display_silently_option}" == "X" ]]; then
log_super "Status: Deleting local preference for the --display-silently option, defaulting to audible alert for dialogs and notifications."
defaults delete "${SUPER_LOCAL_PLIST}" DisplaySilently 2>/dev/null
unset display_silently_option
elif [[ -n "${display_silently_option}" ]]; then
previous_ifs="${IFS}"
IFS=','
local display_silently_array
read -r -a display_silently_array <<<"${display_silently_option}"
for option_type in "${display_silently_array[@]}"; do
if ! [[ "${option_type}" =~ ${REGEX_WORKFLOW_OPTIONS} ]]; then
log_super "Parameter Error: Unrecognized --display-silently type of ${option_type}. You can only specify the following types separated by commas (no spaces): ALWAYS,DIALOG,DEADLINE,SCHEDULED,INSTALLNOW,ERROR"
option_error="TRUE"
fi
done
IFS="${previous_ifs}"
[[ "${option_error}" != "TRUE" ]] && defaults write "${SUPER_LOCAL_PLIST}" DisplaySilently -string "${display_silently_option}"
[[ $(echo "${display_silently_option}" | grep -c 'ALWAYS') -gt 0 ]] && display_silently_status="TRUE"
fi
{ [[ "${verbose_mode_option}" == "TRUE" ]] && [[ -n "${display_silently_option}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: display_silently_option is: ${display_silently_option}"
{ [[ "${verbose_mode_option}" == "TRUE" ]] && [[ -n "${display_silently_status}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: display_silently_status is: ${display_silently_status}"
# Validate ${display_hide_progress_bar_option} and if valid save to ${SUPER_LOCAL_PLIST}.
if [[ "${display_hide_progress_bar_option}" == "X" ]]; then
log_super "Status: Deleting local preference for the --display-hide-progress-bar option, defaulting to all notifications are shown a progress bar."
defaults delete "${SUPER_LOCAL_PLIST}" DisplayHideProgressBar 2>/dev/null
unset display_hide_progress_bar_option
elif [[ -n "${display_hide_progress_bar_option}" ]]; then
previous_ifs="${IFS}"
IFS=','
local display_hide_progress_bar_array
read -r -a display_hide_progress_bar_array <<<"${display_hide_progress_bar_option}"
for option_type in "${display_hide_progress_bar_array[@]}"; do
if ! [[ "${option_type}" =~ ${REGEX_WORKFLOW_OPTIONS} ]]; then
log_super "Parameter Error: Unrecognized --display-hide-progress-bar type of ${option_type}. You can only specify the following types separated by commas (no spaces): ALWAYS,DIALOG,DEADLINE,SCHEDULED,INSTALLNOW,ERROR"
option_error="TRUE"
fi
[[ "${option_type}" == "DIALOG" ]] && log_super "Parameter Warning: The --display-hide-progress-bar type of DIALOG is ignored because interactive dialogs don't have progress bars'."
done
IFS="${previous_ifs}"
[[ "${option_error}" != "TRUE" ]] && defaults write "${SUPER_LOCAL_PLIST}" DisplayHideProgressBar -string "${display_hide_progress_bar_option}"
[[ $(echo "${display_hide_progress_bar_option}" | grep -c 'ALWAYS') -gt 0 ]] && display_hide_progress_bar_status="TRUE"
fi
{ [[ "${verbose_mode_option}" == "TRUE" ]] && [[ -n "${display_hide_progress_bar_option}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: display_hide_progress_bar_option is: ${display_hide_progress_bar_option}"
{ [[ "${verbose_mode_option}" == "TRUE" ]] && [[ -n "${display_hide_progress_bar_status}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: display_hide_progress_bar_status is: ${display_hide_progress_bar_status}"
# Validate ${display_notifications_centered_option} and if valid save to ${SUPER_LOCAL_PLIST}.
if [[ "${display_notifications_centered_option}" == "X" ]]; then
log_super "Status: Deleting local preference for the --display-notifications-centered option, defaulting to all notifications are shown in top right corner."
defaults delete "${SUPER_LOCAL_PLIST}" DisplayNotificationsCentered 2>/dev/null
elif [[ -n "${display_notifications_centered_option}" ]]; then
previous_ifs="${IFS}"
IFS=','
local display_notifications_centered_array
read -r -a display_notifications_centered_array <<<"${display_notifications_centered_option}"
for option_type in "${display_notifications_centered_array[@]}"; do
if ! [[ "${option_type}" =~ ${REGEX_WORKFLOW_OPTIONS} ]]; then
log_super "Parameter Error: Unrecognized --display-notifications-centered type of ${option_type}. You can only specify the following types separated by commas (no spaces): ALWAYS,DEADLINE,SCHEDULED,INSTALLNOW,ERROR"
option_error="TRUE"
fi
[[ "${option_type}" == "DIALOG" ]] && log_super "Parameter Warning: The --display-notifications-centered type of DIALOG is ignored because dialogs are not notifcations."
done
IFS="${previous_ifs}"
[[ "${option_error}" != "TRUE" ]] && defaults write "${SUPER_LOCAL_PLIST}" DisplayNotificationsCentered -string "${display_notifications_centered_option}"
[[ $(echo "${display_notifications_centered_option}" | grep -c 'ALWAYS') -gt 0 ]] && display_notifications_centered_status="TRUE"
fi
{ [[ "${verbose_mode_option}" == "TRUE" ]] && [[ -n "${display_notifications_centered_option}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: display_notifications_centered_option is: ${display_notifications_centered_option}"
{ [[ "${verbose_mode_option}" == "TRUE" ]] && [[ -n "${display_notifications_centered_status}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: display_notifications_centered_status is: ${display_notifications_centered_status}"
# Validate ${display_icon_size_option} input and if valid override default ${display_icon_size} parameter and save to ${SUPER_LOCAL_PLIST}. If there is no ${display_icon_size} then set it to ${DISPLAY_ICON_DEFAULT_SIZE}.
if [[ "${display_icon_size_option}" == "X" ]]; then
log_super "Status: Deleting local preference for the --display-icon-size option, defaulting to ${DISPLAY_ICON_DEFAULT_SIZE} pixels."
defaults delete "${SUPER_LOCAL_PLIST}" DisplayIconSize 2>/dev/null
unset display_icon_size_option
elif [[ -n "${display_icon_size_option}" ]] && [[ "${display_icon_size_option}" =~ ${REGEX_ANY_WHOLE_NUMBER} ]]; then
if [[ "${display_icon_size_option}" -lt 32 ]]; then
log_super "Parameter Warning: Specified --display-icon-size=pixels value of ${display_icon_size_option} pixels is too low, rounding up to 32 pixels."
display_icon_size=32
elif [[ "${display_icon_size_option}" -gt 150 ]]; then
log_super "Parameter Warning: Specified --display-icon-size=pixels value of ${display_icon_size_option} pixels is too high, rounding down to 150 pixels."
display_icon_size=150
else
display_icon_size="${display_icon_size_option}"
fi
defaults write "${SUPER_LOCAL_PLIST}" DisplayIconSize -string "${display_icon_size}"
elif [[ -n "${display_icon_size_option}" ]] && ! [[ "${display_icon_size_option}" =~ ${REGEX_ANY_WHOLE_NUMBER} ]]; then
log_super "Parameter Error: The --display-icon-size=pixels value must only be a number."
option_error="TRUE"
fi
[[ -z "${display_icon_size}" ]] && display_icon_size="${DISPLAY_ICON_DEFAULT_SIZE}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: display_icon_size is: ${display_icon_size}"
# Initial validation for the ${display_icon_file_option}, ${display_icon_light_file_option}, and ${display_icon_dark_file_option} to set ${display_icon_light_file_path} and ${display_icon_dark_file_path}. Handling of the actual display icon files themselves is in the manage_helpers() function.
display_icon_light_file_path="FALSE"
display_icon_dark_file_path="FALSE"
if [[ "${display_icon_light_file_option}" == "X" ]]; then
log_super "Status: Deleting local preference for the --display-icon-light-file option."
defaults delete "${SUPER_LOCAL_PLIST}" DisplayIconLightFile 2>/dev/null
defaults delete "${SUPER_LOCAL_PLIST}" DisplayIconLightFileCachedOrigin 2>/dev/null
unset display_icon_light_file_option
[[ -f "${DISPLAY_ICON_LIGHT_FILE_CACHE}" ]] && rm -f "${DISPLAY_ICON_LIGHT_FILE_CACHE}" 2>/dev/null
elif [[ -n "${display_icon_light_file_option}" ]]; then
defaults write "${SUPER_LOCAL_PLIST}" DisplayIconLightFile -string "${display_icon_light_file_option}"
display_icon_light_file_path="${display_icon_light_file_option}"
fi
if [[ "${display_icon_dark_file_option}" == "X" ]]; then
log_super "Status: Deleting local preference for the --display-icon-dark-file option."
defaults delete "${SUPER_LOCAL_PLIST}" DisplayIconDarkFile 2>/dev/null
defaults delete "${SUPER_LOCAL_PLIST}" DisplayIconDarkFileCachedOrigin 2>/dev/null
unset display_icon_dark_file_option
[[ -f "${DISPLAY_ICON_DARK_FILE_CACHE}" ]] && rm -f "${DISPLAY_ICON_DARK_FILE_CACHE}" 2>/dev/null
elif [[ -n "${display_icon_dark_file_option}" ]]; then
defaults write "${SUPER_LOCAL_PLIST}" DisplayIconDarkFile -string "${display_icon_dark_file_option}"
display_icon_dark_file_path="${display_icon_dark_file_option}"
fi
if [[ "${display_icon_file_option}" == "X" ]]; then
log_super "Status: Deleting local preference for the --display-icon-file option."
defaults delete "${SUPER_LOCAL_PLIST}" DisplayIconFile 2>/dev/null
defaults delete "${SUPER_LOCAL_PLIST}" DisplayIconFileCachedOrigin 2>/dev/null
unset display_icon_file_option
elif [[ -n "${display_icon_file_option}" ]]; then
defaults write "${SUPER_LOCAL_PLIST}" DisplayIconFile -string "${display_icon_file_option}"
[[ "${display_icon_light_file_path}" == "FALSE" ]] && display_icon_light_file_path="${display_icon_file_option}"
[[ "${display_icon_dark_file_path}" == "FALSE" ]] && display_icon_dark_file_path="${display_icon_file_option}"
fi
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: display_icon_light_file_path is: ${display_icon_light_file_path}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: display_icon_dark_file_path is: ${display_icon_dark_file_path}"
# Validate ${display_accessory_type_option}, ${display_accessory_default_file_option}, ${display_accessory_macos_minor_update_file_option}, ${display_accessory_macos_major_upgrade_file_option}, ${display_accessory_non_system_updates_file_option}, ${display_accessory_jamf_policy_triggers_file_option}, and ${display_accessory_restart_without_updates_file_option}.
if [[ "${display_accessory_type_option}" == "X" ]]; then
log_super "Status: Deleting local preference for the --display-accessory-type option."
defaults delete "${SUPER_LOCAL_PLIST}" DisplayAccessoryType 2>/dev/null
unset display_accessory_type_option
fi
if [[ "${display_accessory_default_file_option}" == "X" ]]; then
log_super "Status: Deleting local preference for the --display-accessory-default-file option."
defaults delete "${SUPER_LOCAL_PLIST}" DisplayAccessoryDefaultFile 2>/dev/null
unset display_accessory_default_file_option
fi
if [[ "${display_accessory_macos_minor_update_file_option}" == "X" ]]; then
log_super "Status: Deleting local preference for the --display-accessory-macos-minor-update-file option."
defaults delete "${SUPER_LOCAL_PLIST}" DisplayAccessoryMacOSMinorUpdateFile 2>/dev/null
unset display_accessory_macos_minor_update_file_option
fi
if [[ "${display_accessory_macos_major_upgrade_file_option}" == "X" ]]; then
log_super "Status: Deleting local preference for the --display-accessory-macos-major-upgrade-file option."
defaults delete "${SUPER_LOCAL_PLIST}" DisplayAccessoryMacOSMajorUpgradeFile 2>/dev/null
unset display_accessory_macos_major_upgrade_file_option
fi
if [[ "${display_accessory_non_system_updates_file_option}" == "X" ]]; then
log_super "Status: Deleting local preference for the --display-accessory-non-system-updates-file option."
defaults delete "${SUPER_LOCAL_PLIST}" DisplayAccessoryNonSystemUpdatesFile 2>/dev/null
unset display_accessory_non_system_updates_file_option
fi
if [[ "${display_accessory_jamf_policy_triggers_file_option}" == "X" ]]; then
log_super "Status: Deleting local preference for the --display-accessory-jamf-policy-triggers-file option."
defaults delete "${SUPER_LOCAL_PLIST}" DisplayAccessoryJamfPolicyTriggersFile 2>/dev/null
unset display_accessory_jamf_policy_triggers_file_option
fi
if [[ "${display_accessory_restart_without_updates_file_option}" == "X" ]]; then
log_super "Status: Deleting local preference for the --display-accessory-restart-without-updates-file option."
defaults delete "${SUPER_LOCAL_PLIST}" DisplayAccessoryRestartWithoutUpdatesFile 2>/dev/null
unset display_accessory_restart_without_updates_file_option
fi
if [[ -n "${display_accessory_type_option}" ]] && [[ -z "${display_accessory_default_file_option}" ]] && [[ -z "${display_accessory_macos_minor_update_file_option}" ]] && [[ -z "${display_accessory_macos_major_upgrade_file_option}" ]] && [[ -z "${display_accessory_non_system_updates_file_option}" ]] && [[ -z "${display_accessory_jamf_policy_triggers_file_option}" ]] && [[ -z "${display_accessory_restart_without_updates_file_option}" ]]; then
log_super "Parameter Error: To use the --display-accessory-type option you must also specify one of the display accessory file options."
option_error="TRUE"
fi
if [[ -z "${display_accessory_type_option}" ]] && { [[ -n "${display_accessory_default_file_option}" ]] || [[ -n "${display_accessory_macos_minor_update_file_option}" ]] || [[ -n "${display_accessory_macos_major_upgrade_file_option}" ]] || [[ -n "${display_accessory_non_system_updates_file_option}" ]] || [[ -n "${display_accessory_jamf_policy_triggers_file_option}" ]] || [[ -n "${display_accessory_restart_without_updates_file_option}" ]]; }; then
log_super "Parameter Error: To use any of the display accessory file options you must also specify the --display-accessory-type option."
option_error="TRUE"
fi
if [[ "${option_error}" != "TRUE" ]] && [[ -n "${display_accessory_type_option}" ]]; then
if [[ "${display_accessory_type_option}" =~ ^TEXTBOX$|^HTMLBOX$|^HTML$|^IMAGE$|^VIDEO$|^VIDEOAUTO$ ]]; then
display_accessory_type="${display_accessory_type_option}"
defaults write "${SUPER_LOCAL_PLIST}" DisplayAccessoryType -string "${display_accessory_type_option}"
else
log_super "Parameter Error: Unrecognized --display-accessory-type value of ${display_accessory_type_option}. You must specify one of the following; TEXTBOX, HTMLBOX, HTML, IMAGE, VIDEO, or VIDEOAUTO."
option_error="TRUE"
fi
fi
if [[ "${option_error}" != "TRUE" ]] && { [[ -n "${display_accessory_default_file_option}" ]] || [[ -n "${display_accessory_macos_minor_update_file_option}" ]] || [[ -n "${display_accessory_macos_major_upgrade_file_option}" ]] || [[ -n "${display_accessory_non_system_updates_file_option}" ]] || [[ -n "${display_accessory_jamf_policy_triggers_file_option}" ]] || [[ -n "${display_accessory_restart_without_updates_file_option}" ]]; }; then
if [[ -n "${display_accessory_default_file_option}" ]]; then
display_accessory_default="${display_accessory_default_file_option}"
defaults write "${SUPER_LOCAL_PLIST}" DisplayAccessoryDefaultFile -string "${display_accessory_default_file_option}"
fi
if [[ -n "${display_accessory_macos_minor_update_file_option}" ]]; then
display_accessory_macos_minor_update="${display_accessory_macos_minor_update_file_option}"
defaults write "${SUPER_LOCAL_PLIST}" DisplayAccessoryMacOSMinorUpdateFile -string "${display_accessory_macos_minor_update_file_option}"
fi
if [[ -n "${display_accessory_macos_major_upgrade_file_option}" ]]; then
display_accessory_macos_major_upgrade="${display_accessory_macos_major_upgrade_file_option}"
defaults write "${SUPER_LOCAL_PLIST}" DisplayAccessoryMacOSMajorUpgradeFile -string "${display_accessory_macos_major_upgrade_file_option}"
fi
if [[ -n "${display_accessory_non_system_updates_file_option}" ]]; then
display_accessory_non_system_updates="${display_accessory_non_system_updates_file_option}"
defaults write "${SUPER_LOCAL_PLIST}" DisplayAccessoryNonSystemUpdatesFile -string "${display_accessory_non_system_updates_file_option}"
fi
if [[ -n "${display_accessory_jamf_policy_triggers_file_option}" ]]; then
display_accessory_jamf_policy_triggers="${display_accessory_jamf_policy_triggers_file_option}"
defaults write "${SUPER_LOCAL_PLIST}" DisplayAccessoryJamfPolicyTriggersFile -string "${display_accessory_jamf_policy_triggers_file_option}"
fi
if [[ -n "${display_accessory_restart_without_updates_file_option}" ]]; then
display_accessory_restart_without_updates="${display_accessory_restart_without_updates_file_option}"
defaults write "${SUPER_LOCAL_PLIST}" DisplayAccessoryRestartWithoutUpdatesFile -string "${display_accessory_restart_without_updates_file_option}"
fi
fi
{ [[ "${verbose_mode_option}" == "TRUE" ]] && [[ -n "${display_accessory_type}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: display_accessory_type is: ${display_accessory_type}"
{ [[ "${verbose_mode_option}" == "TRUE" ]] && [[ -n "${display_accessory_default}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: display_accessory_default is: ${display_accessory_default}"
{ [[ "${verbose_mode_option}" == "TRUE" ]] && [[ -n "${display_accessory_macos_major_upgrade}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: display_accessory_macos_major_upgrade is: ${display_accessory_macos_major_upgrade}"
{ [[ "${verbose_mode_option}" == "TRUE" ]] && [[ -n "${display_accessory_macos_major_upgrade_file_option}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: display_accessory_macos_major_upgrade_file_option is: ${display_accessory_macos_major_upgrade_file_option}"
{ [[ "${verbose_mode_option}" == "TRUE" ]] && [[ -n "${display_accessory_non_system_updates}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: display_accessory_non_system_updates is: ${display_accessory_non_system_updates}"
{ [[ "${verbose_mode_option}" == "TRUE" ]] && [[ -n "${display_accessory_jamf_policy_triggers}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: display_accessory_jamf_policy_triggers is: ${display_accessory_jamf_policy_triggers}"
{ [[ "${verbose_mode_option}" == "TRUE" ]] && [[ -n "${display_accessory_restart_without_updates}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: display_accessory_restart_without_updates is: ${display_accessory_restart_without_updates}"
# Validate ${display_help_button_string_option} and set ${display_help_button_string} and save to ${SUPER_LOCAL_PLIST}.
if [[ "${display_help_button_string_option}" == "X" ]]; then
log_super "Status: Deleting local preference for the --display-help-button-string option."
defaults delete "${SUPER_LOCAL_PLIST}" DisplayHelpButtonString 2>/dev/null
unset display_help_button_string_option
elif [[ -n "${display_help_button_string_option}" ]]; then
display_help_button_string="${display_help_button_string_option}"
defaults write "${SUPER_LOCAL_PLIST}" DisplayHelpButtonString -string "${display_help_button_string_option}"
fi
{ [[ "${verbose_mode_option}" == "TRUE" ]] && [[ -n "${display_help_button_string}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: display_help_button_string is: ${display_help_button_string}"
# Validate ${display_warning_button_string_option} and set ${display_warning_button_string} and save to ${SUPER_LOCAL_PLIST}.
if [[ "${display_warning_button_string_option}" == "X" ]]; then
log_super "Status: Deleting local preference for the --display-warning-button-string option."
defaults delete "${SUPER_LOCAL_PLIST}" DisplayWarningButtonString 2>/dev/null
unset display_warning_button_string_option
elif [[ -n "${display_warning_button_string_option}" ]]; then
display_warning_button_string="${display_warning_button_string_option}"
defaults write "${SUPER_LOCAL_PLIST}" DisplayWarningButtonString -string "${display_warning_button_string_option}"
fi
{ [[ "${verbose_mode_option}" == "TRUE" ]] && [[ -n "${display_warning_button_string}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: display_warning_button_string is: ${display_warning_button_string}"
# This initial round of authentication option validation simply checks to make sure the user entered logically correct option combinations.
# Additional authentication validation is handled by the manage_authentication_options() function.
if [[ "${mac_cpu_architecture}" == "arm64" ]]; then
if [[ "${auth_ask_user_to_save_password}" -eq 1 ]] || [[ "${auth_ask_user_to_save_password}" == "TRUE" ]] && [[ "${auth_delete_all_option}" != "TRUE" ]]; then
auth_ask_user_to_save_password="TRUE"
defaults write "${SUPER_LOCAL_PLIST}" AuthAskUserToSavePassword -bool true
else
auth_ask_user_to_save_password="FALSE"
defaults delete "${SUPER_LOCAL_PLIST}" AuthAskUserToSavePassword 2>/dev/null
fi
if [[ -n "${auth_local_account_option}" ]] && [[ -z "${auth_local_password_option}" ]]; then
log_super "Auth Error: The --auth-local-account option requires that you also use the --auth-local-password option."
option_error="TRUE"
fi
if [[ -z "${auth_local_account_option}" ]] && [[ -n "${auth_local_password_option}" ]]; then
log_super "Auth Error: The --auth-local-password option requires that you also set the --auth-local-account option."
option_error="TRUE"
fi
if [[ -n "${auth_service_add_via_admin_account_option}" ]] && [[ -z "${auth_service_add_via_admin_password_option}" ]]; then
log_super "Auth Error: The --auth-service_add_via_admin-account option requires that you also use the --auth-service_add_via_admin-password option."
option_error="TRUE"
fi
if [[ -z "${auth_service_add_via_admin_account_option}" ]] && [[ -n "${auth_service_add_via_admin_password_option}" ]]; then
log_super "Auth Error: The --auth-service_add_via_admin-password option requires that you also set the --auth-service_add_via_admin-account option."
option_error="TRUE"
fi
if [[ -n "${auth_service_account_option}" ]] && { [[ -z "${auth_service_add_via_admin_account_option}" ]] || [[ -z "${auth_service_add_via_admin_password_option}" ]]; }; then
log_super "Auth Error: The --auth-service-account option requires that you also set the ---auth-service-add-via-admin-account and --auth-service-add-via-admin-password options."
option_error="TRUE"
fi
if [[ -n "${auth_service_password_option}" ]] && { [[ -z "${auth_service_add_via_admin_account_option}" ]] || [[ -z "${auth_service_add_via_admin_password_option}" ]]; }; then
log_super "Auth Error: The --auth-service-password option requires that you also set the ---auth-service-add-via-admin-account and --auth-service-add-via-admin-password options."
option_error="TRUE"
fi
if [[ -n "${auth_jamf_client_option}" ]] && [[ -z "${auth_jamf_secret_option}" ]]; then
log_super "Auth Error: The --auth-jamf-client option requires that you also set the --auth-jamf-secret option."
option_error="TRUE"
fi
if [[ -z "${auth_jamf_client_option}" ]] && [[ -n "${auth_jamf_secret_option}" ]]; then
log_super "Auth Error: The --auth-jamf-secret option requires that you also set the --auth-jamf-client."
option_error="TRUE"
fi
if [[ -n "${auth_jamf_client_option}" ]] && [[ "${jamf_version_number}" -lt 1049 ]]; then
log_super "Auth Error: The --auth-jamf-client option requires Jamf Pro version 10.49 or later, the currently installed version of Jamf Pro ${jamf_version_number_major}.${jamf_version_number_minor} does not support this option."
option_error="TRUE"
fi
if [[ -n "${auth_jamf_account_option}" ]] && [[ -z "${auth_jamf_password_option}" ]]; then
log_super "Auth Error: The --auth-jamf-account option requires that you also set the --auth-jamf-password option."
option_error="TRUE"
fi
if [[ -z "${auth_jamf_account_option}" ]] && [[ -n "${auth_jamf_password_option}" ]]; then
log_super "Auth Error: The --auth-jamf-password option requires that you also set the --auth-jamf-account."
option_error="TRUE"
fi
if [[ -n "${auth_jamf_account_option}" ]] && [[ "${jamf_version_number}" -ge 1049 ]]; then
log_super "Parameter Warning: The --auth-jamf-account option is not recommended for Jamf Pro version 10.49 or later. The recommended implementation is the more secure --auth-jamf-client option."
fi
# Validate ${auth_jamf_custom_url_option} input and save to ${SUPER_LOCAL_PLIST}.
if [[ "${auth_jamf_custom_url_option}" == "X" ]]; then
log_super "Status: Deleting local preference for the --auth-jamf-custom-url option, defaulting to Jamf Pro management URL."
defaults delete "${SUPER_LOCAL_PLIST}" AuthJamfCustomURL 2>/dev/null
unset auth_jamf_custom_url_option
elif [[ -n "${auth_jamf_custom_url_option}" ]] && [[ "${auth_jamf_custom_url_option}" =~ ${REGEX_HTTPS} ]]; then
if ! [[ "${auth_jamf_custom_url_option}" =~ .*\/$ ]]; then
auth_jamf_custom_url_option="${auth_jamf_custom_url_option}/"
log_super "Parameter Warning: Adding a trailing slash to the --auth-jamf-custom-url value: ${auth_jamf_custom_url_option}"
fi
defaults write "${SUPER_LOCAL_PLIST}" AuthJamfCustomURL -string "${auth_jamf_custom_url_option}"
auth_jamf_custom_url="${auth_jamf_custom_url_option}"
elif [[ -n "${auth_jamf_custom_url_option}" ]] && ! [[ "${auth_jamf_custom_url_option}" =~ ${REGEX_HTTPS} ]]; then
log_super "Parameter Error: Invalid --auth-jamf-custom-url=URL VALUE must start with 'https://': ${auth_jamf_custom_url_option}"
option_error="TRUE"
fi
{ [[ "${verbose_mode_option}" == "TRUE" ]] && [[ -n "${auth_jamf_custom_url}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_jamf_custom_url is: ${auth_jamf_custom_url}"
# Manage ${auth_credential_failover_to_user_option} and save to ${SUPER_LOCAL_PLIST}.
if [[ "${auth_credential_failover_to_user_option}" -eq 1 ]] || [[ "${auth_credential_failover_to_user_option}" == "TRUE" ]]; then
auth_credential_failover_to_user_option="TRUE"
defaults write "${SUPER_LOCAL_PLIST}" AuthCredentialFailoverToUser -bool true
else
auth_credential_failover_to_user_option="FALSE"
defaults delete "${SUPER_LOCAL_PLIST}" AuthCredentialFailoverToUser 2>/dev/null
fi
{ [[ "${verbose_mode_option}" == "TRUE" ]] && [[ -n "${auth_credential_failover_to_user_option}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_credential_failover_to_user_option is: ${auth_credential_failover_to_user_option}"
# Validate ${auth_mdm_failover_to_user_option} and set initial various ${auth_mdm_failover_*} parameters.
if [[ "${auth_mdm_failover_to_user_option}" == "X" ]]; then
log_super "Status: Deleting local preference for the --auth-mdm-failover-to-user option, defaulting to error deferral for any MDM failure."
defaults delete "${SUPER_LOCAL_PLIST}" AuthMDMFailoverToUser 2>/dev/null
unset auth_mdm_failover_to_user_option
elif [[ -n "${auth_mdm_failover_to_user_option}" ]]; then
previous_ifs="${IFS}"
IFS=','
local auth_mdm_failover_to_user_array
read -r -a auth_mdm_failover_to_user_array <<<"${auth_mdm_failover_to_user_option}"
for option_type in "${auth_mdm_failover_to_user_array[@]}"; do
if ! [[ "${option_type}" =~ ${REGEX_WORKFLOW_OPTIONS} ]]; then
log_super "Parameter Error: Unrecognized --auth-mdm-failover-to-user type of ${option_type}. You can only specify the following types separated by commas (no spaces): ALWAYS,DIALOG,DEADLINE,SCHEDULED,INSTALLNOW,ERROR"
option_error="TRUE"
fi
done
IFS="${previous_ifs}"
[[ "${option_error}" != "TRUE" ]] && defaults write "${SUPER_LOCAL_PLIST}" AuthMDMFailoverToUser -string "${auth_mdm_failover_to_user_option}"
[[ $(echo "${auth_mdm_failover_to_user_option}" | grep -c 'ALWAYS') -gt 0 ]] && auth_mdm_failover_to_user_status="TRUE"
fi
{ [[ "${verbose_mode_option}" == "TRUE" ]] && [[ -n "${auth_mdm_failover_to_user_option}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_mdm_failover_to_user_option is: ${auth_mdm_failover_to_user_option}"
{ [[ "${verbose_mode_option}" == "TRUE" ]] && [[ -n "${auth_mdm_failover_to_user_status}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_mdm_failover_to_user_status is: ${auth_mdm_failover_to_user_status}"
else # Mac computer with Intel.
[[ -n "${auth_ask_user_to_save_password}" ]] && log_super "Parameter Warning: The --auth-ask-user-to-save-password option is ignored on Mac computers with Intel."
[[ -n "${auth_local_account_option}" ]] && log_super "Parameter Warning: The --auth-local-account option is ignored on Mac computers with Intel."
[[ -n "${auth_service_add_via_admin_account_option}" ]] && log_super "Parameter Warning: The --auth-service-add-via-admin-account option is ignored on Mac computers with Intel."
[[ -n "${auth_service_account_option}" ]] && log_super "Parameter Warning: The --auth-service-account option is ignored on Mac computers with Intel."
[[ -n "${auth_jamf_client_option}" ]] && log_super "Parameter Warning: The --auth-jamf-client option is ignored on Mac computers with Intel."
[[ -n "${auth_jamf_account_option}" ]] && log_super "Parameter Warning: The --auth-jamf-account option is ignored on Mac computers with Intel."
[[ -n "${auth_jamf_custom_url_option}" ]] && log_super "Parameter Warning: The --auth-jamf-custom-url option is ignored on Mac computers with Intel."
[[ -n "${auth_credential_failover_to_user_option}" ]] && log_super "Parameter Warning: The --auth-credential-failover-to-user option is ignored on Mac computers with Intel."
[[ -n "${auth_mdm_failover_to_user_option}" ]] && log_super "Parameter Warning: The --auth-mdm-failover-to-user option is ignored on Mac computers with Intel."
fi
# Manage ${test_mode_option} and save to ${SUPER_LOCAL_PLIST}.
if [[ ${test_mode_option} -eq 1 ]] || [[ "${test_mode_option}" == "TRUE" ]]; then
test_mode_option="TRUE"
defaults write "${SUPER_LOCAL_PLIST}" TestMode -bool true
else
test_mode_option="FALSE"
defaults delete "${SUPER_LOCAL_PLIST}" TestMode 2>/dev/null
fi
if [[ "${test_mode_option}" == "TRUE" ]] && [[ "${current_user_account_name}" == "FALSE" ]]; then
log_super "Parameter Error: Test mode requires that a valid user is logged in."
option_error="TRUE"
fi
if [[ "${test_mode_option}" == "TRUE" ]] && [[ "${workflow_only_download_option}" == "TRUE" ]]; then
log_super "Parameter Error: The --test-mode option can not be used with the --workflow-only-download."
option_error="TRUE"
fi
{ [[ "${verbose_mode_option}" == "TRUE" ]] && [[ -n "${test_mode_option}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: test_mode_option is: ${test_mode_option}"
# Validate ${test_mode_timeout_option} input and if valid set "${test_mode_timeout_seconds}" and save to ${SUPER_LOCAL_PLIST}. If there is no "${test_mode_timeout_seconds}" then set it to ${TEST_MODE_DEFAULT_TIMEOUT}.
if [[ "${test_mode_timeout_option}" == "X" ]]; then
log_super "Status: Deleting local preference for the --test-mode-timeout option, defaulting to ${TEST_MODE_DEFAULT_TIMEOUT} seconds."
defaults delete "${SUPER_LOCAL_PLIST}" TestModeTimeout 2>/dev/null
unset test_mode_timeout_option
elif [[ -n "${test_mode_timeout_option}" ]] && [[ "${test_mode_timeout_option}" =~ ${REGEX_ANY_WHOLE_NUMBER} ]]; then
if [[ "${test_mode_timeout_option}" -lt 10 ]]; then
log_super "Parameter Warning: Specified --test-mode-timeout=seconds value of ${test_mode_timeout_option} seconds is too low, rounding up to 10 seconds."
test_mode_timeout_seconds=10
elif [[ "${test_mode_timeout_option}" -gt 120 ]]; then
log_super "Parameter Warning: Specified --test-mode-timeout=seconds value of ${test_mode_timeout_option} seconds is too high, rounding down to 120 seconds (2 minutes)."
test_mode_timeout_seconds=120
else
test_mode_timeout_seconds="${test_mode_timeout_option}"
fi
defaults write "${SUPER_LOCAL_PLIST}" TestModeTimeout -string "${test_mode_timeout_seconds}"
elif [[ -n "${test_mode_timeout_option}" ]] && ! [[ "${test_mode_timeout_option}" =~ ${REGEX_ANY_WHOLE_NUMBER} ]]; then
log_super "Parameter Error: The --test-mode-timeout=seconds value must only be a number."
option_error="TRUE"
fi
[[ -z "${test_mode_timeout_seconds}" ]] && test_mode_timeout_seconds="${TEST_MODE_DEFAULT_TIMEOUT}"
if [[ "${test_mode_option}" == "TRUE" ]]; then
if [[ -n "${dialog_timeout_default_seconds}" ]] && [[ "${dialog_timeout_default_seconds}" -gt "${test_mode_timeout_seconds}" ]]; then
log_super "Parameter Warning: Test mode requires temporary adjustment of the --dialog-timeout-default=seconds value from ${dialog_timeout_default_seconds} seconds to ${test_mode_timeout_seconds} seconds. This adjustment is not saved."
dialog_timeout_default_seconds="${test_mode_timeout_seconds}"
fi
if [[ -n "${dialog_timeout_user_choice_seconds}" ]] && [[ "${dialog_timeout_user_choice_seconds}" -gt "${test_mode_timeout_seconds}" ]]; then
log_super "Parameter Warning: Test mode requires temporary adjustment of the --dialog-timeout-user-choice=seconds value from ${dialog_timeout_user_choice_seconds} seconds to ${test_mode_timeout_seconds} seconds. This adjustment is not saved."
dialog_timeout_user_choice_seconds="${test_mode_timeout_seconds}"
fi
if [[ -n "${dialog_timeout_user_schedule_seconds}" ]] && [[ "${dialog_timeout_user_schedule_seconds}" -gt "${test_mode_timeout_seconds}" ]]; then
log_super "Parameter Warning: Test mode requires temporary adjustment of the --dialog-timeout-user-schedule=seconds value from ${dialog_timeout_user_schedule_seconds} seconds to ${test_mode_timeout_seconds} seconds. This adjustment is not saved."
dialog_timeout_user_schedule_seconds="${test_mode_timeout_seconds}"
fi
if [[ -n "${dialog_timeout_soft_deadline_seconds}" ]] && [[ "${dialog_timeout_soft_deadline_seconds}" -gt "${test_mode_timeout_seconds}" ]]; then
log_super "Parameter Warning: Test mode requires temporary adjustment of the --dialog-timeout-soft-deadline=seconds value from ${dialog_timeout_soft_deadline_seconds} seconds to ${test_mode_timeout_seconds} seconds. This adjustment is not saved."
dialog_timeout_soft_deadline_seconds="${test_mode_timeout_seconds}"
fi
if [[ -n "${dialog_timeout_user_auth_seconds}" ]] && [[ "${dialog_timeout_user_auth_seconds}" -gt "${test_mode_timeout_seconds}" ]]; then
log_super "Parameter Warning: Test mode requires temporary adjustment of the --dialog-timeout-user-auth=seconds value from ${dialog_timeout_user_auth_seconds} seconds to ${test_mode_timeout_seconds} seconds. This adjustment is not saved."
dialog_timeout_user_auth_seconds="${test_mode_timeout_seconds}"
fi
if [[ -n "${dialog_timeout_insufficient_storage_seconds}" ]] && [[ "${dialog_timeout_insufficient_storage_seconds}" -gt "${test_mode_timeout_seconds}" ]]; then
log_super "Parameter Warning: Test mode requires temporary adjustment of the --dialog-timeout-insufficient-storage=seconds value from ${dialog_timeout_insufficient_storage_seconds} seconds to ${test_mode_timeout_seconds} seconds. This adjustment is not saved."
dialog_timeout_insufficient_storage_seconds="${test_mode_timeout_seconds}"
fi
if [[ -n "${dialog_timeout_power_required_seconds}" ]] && [[ "${dialog_timeout_power_required_seconds}" -gt "${test_mode_timeout_seconds}" ]]; then
log_super "Parameter Warning: Test mode requires temporary adjustment of the --dialog-timeout-power-required=seconds value from ${dialog_timeout_power_required_seconds} seconds to ${test_mode_timeout_seconds} seconds. This adjustment is not saved."
dialog_timeout_power_required_seconds="${test_mode_timeout_seconds}"
fi
fi
{ [[ "${verbose_mode_option}" == "TRUE" ]] && [[ -n "${test_mode_timeout_option}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: test_mode_timeout_option is: ${test_mode_timeout_option}"
# Validate ${test_storage_update_option} input and if valid set ${storage_required_update_gb} and save to ${SUPER_LOCAL_PLIST}. If there is no ${storage_required_update_gb} then set it to ${STORAGE_REQUIRED_UPDATE_DEFAULT_GB}.
if [[ "${test_storage_update_option}" == "X" ]]; then
log_super "Status: Deleting local preference for the --test-storage-update option, defaulting to ${STORAGE_REQUIRED_UPDATE_DEFAULT_GB} GBs."
defaults delete "${SUPER_LOCAL_PLIST}" TestStorageUpdate 2>/dev/null
unset test_storage_update_option
elif [[ -n "${test_storage_update_option}" ]] && [[ "${test_storage_update_option}" =~ ${REGEX_ANY_WHOLE_NUMBER} ]]; then
[[ "${test_storage_update_option}" -ne "${storage_required_update_gb}" ]] && log_super "Parameter Warning: Specifying the --test-storage-update option should only be used for testing purposes."
storage_required_update_gb="${test_storage_update_option}"
defaults write "${SUPER_LOCAL_PLIST}" TestStorageUpdate -string "${storage_required_update_gb}"
elif [[ -n "${test_storage_update_option}" ]] && ! [[ "${test_storage_update_option}" =~ ${REGEX_ANY_WHOLE_NUMBER} ]]; then
log_super "Parameter Error: The --test-storage-update=gigabytes value must only be a number."
option_error="TRUE"
fi
[[ -z "${storage_required_update_gb}" ]] && storage_required_update_gb="${STORAGE_REQUIRED_UPDATE_DEFAULT_GB}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: storage_required_update_gb is: ${storage_required_update_gb}"
# Validate ${test_storage_upgrade_option} input and if valid set ${storage_required_upgrade_gb} and save to ${SUPER_LOCAL_PLIST}. If there is no ${storage_required_upgrade_gb} then set it to ${STORAGE_REQUIRED_UPGRADE_DEFAULT_GB}.
if [[ "${test_storage_upgrade_option}" == "X" ]]; then
log_super "Status: Deleting local preference for the --test-storage-upgrade option, defaulting to ${STORAGE_REQUIRED_UPGRADE_DEFAULT_GB} GBs."
defaults delete "${SUPER_LOCAL_PLIST}" TestStorageUpdate 2>/dev/null
unset test_storage_upgrade_option
elif [[ -n "${test_storage_upgrade_option}" ]] && [[ "${test_storage_upgrade_option}" =~ ${REGEX_ANY_WHOLE_NUMBER} ]]; then
[[ "${test_storage_upgrade_option}" -ne "${storage_required_upgrade_gb}" ]] && log_super "Parameter Warning: Specifying the --test-storage-upgrade option should only be used for testing purposes."
storage_required_upgrade_gb="${test_storage_upgrade_option}"
defaults write "${SUPER_LOCAL_PLIST}" TestStorageUpdate -string "${storage_required_upgrade_gb}"
elif [[ -n "${test_storage_upgrade_option}" ]] && ! [[ "${test_storage_upgrade_option}" =~ ${REGEX_ANY_WHOLE_NUMBER} ]]; then
log_super "Parameter Error: The --test-storage-upgrade=gigabytes value must only be a number."
option_error="TRUE"
fi
[[ -z "${storage_required_upgrade_gb}" ]] && storage_required_upgrade_gb="${STORAGE_REQUIRED_UPGRADE_DEFAULT_GB}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: storage_required_upgrade_gb is: ${storage_required_upgrade_gb}"
# Validate ${test_battery_level_option} input and if valid set ${power_required_battery_percent} and save to ${SUPER_LOCAL_PLIST}. If there is no ${power_required_battery_percent} then set it to ${POWER_REQUIRED_BATTERY_APPLE_SILICON_PERCENT} or ${POWER_REQUIRED_BATTERY_INTEL_PERCENT}.
if [[ "${test_battery_level_option}" == "X" ]]; then
log_super "Status: Deleting local preference for the --test-battery-level option, defaulting to ${power_required_battery_percent}%."
defaults delete "${SUPER_LOCAL_PLIST}" TestBatteryLevel 2>/dev/null
unset test_battery_level_option
elif [[ -n "${test_battery_level_option}" ]] && [[ "${test_battery_level_option}" =~ ${REGEX_ANY_WHOLE_NUMBER} ]]; then
[[ "${test_battery_level_option}" -ne "${power_required_battery_percent}" ]] && log_super "Parameter Warning: Specifying the --test-battery-level option should only be used for testing purposes."
power_required_battery_percent="${test_battery_level_option}"
defaults write "${SUPER_LOCAL_PLIST}" TestBatteryLevel -string "${power_required_battery_percent}"
elif [[ -n "${test_battery_level_option}" ]] && ! [[ "${test_battery_level_option}" =~ ${REGEX_ANY_WHOLE_NUMBER} ]]; then
log_super "Parameter Error: The --test-battery-level=percentage value must only be a number."
option_error="TRUE"
fi
if [[ -z "${power_required_battery_percent}" ]]; then
if [[ "${mac_cpu_architecture}" == "arm64" ]]; then
power_required_battery_percent="${POWER_REQUIRED_BATTERY_APPLE_SILICON_PERCENT}"
else # Intel.
power_required_battery_percent="${POWER_REQUIRED_BATTERY_INTEL_PERCENT}"
fi
fi
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: power_required_battery_percent is: ${power_required_battery_percent}"
}
# For Apple Silicon computers this function manages update/upgrade credentials given the various --auth_* options. Any error will set ${auth_error_new}.
manage_authentication_options() {
auth_error_new="FALSE"
# Collect any previously saved credential states (including legacy methods) from ${SUPER_LOCAL_PLIST}.
auth_user_account_saved="FALSE"
if [[ "${auth_ask_user_to_save_password}" == "TRUE" ]]; then
if [[ "${current_user_account_name}" != "FALSE" ]]; then
auth_user_password_keychain=$(launchctl asuser "${current_user_id}" sudo -u "${current_user_account_name}" security find-generic-password -w -a "super_auth_user_password" "/Users/${current_user_account_name}/Library/Keychains/login.keychain" 2>&1)
[[ "${verbose_mode_option}" == "TRUE" ]] && log_echo "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_user_password_keychain: ${auth_user_password_keychain}"
if [[ $(echo "${auth_user_password_keychain}" | grep -c 'The specified item could not be found in the keychain.') -eq 0 ]]; then
auth_user_account_saved="TRUE"
else
unset auth_user_password_keychain
fi
else # No ${current_user_account_name}
log_super "Auth Warning: No currently logged in user, unable to the check status of the --auth-ask-user-to-save-password option."
fi
fi
auth_local_account_saved=$(defaults read "${SUPER_LOCAL_PLIST}" AuthLocalAccount 2>/dev/null)
[[ "${auth_local_account_saved}" -eq 1 ]] && auth_local_account_saved="TRUE"
auth_service_account_saved=$(defaults read "${SUPER_LOCAL_PLIST}" AuthServiceAccount 2>/dev/null)
[[ "${auth_service_account_saved}" -eq 1 ]] && auth_service_account_saved="TRUE"
auth_jamf_client_saved=$(defaults read "${SUPER_LOCAL_PLIST}" AuthJamfClient 2>/dev/null)
[[ "${auth_jamf_client_saved}" -eq 1 ]] && auth_jamf_client_saved="TRUE"
auth_jamf_account_saved=$(defaults read "${SUPER_LOCAL_PLIST}" AuthJamfAccount 2>/dev/null)
[[ "${auth_jamf_account_saved}" -eq 1 ]] && auth_jamf_account_saved="TRUE"
local auth_legacy_local_account_saved
auth_legacy_local_account_saved=$(defaults read "${SUPER_LOCAL_PLIST}" LocalAccount 2>/dev/null)
local auth_legacy_super_account_saved
auth_legacy_super_account_saved=$(defaults read "${SUPER_LOCAL_PLIST}" SuperAccount 2>/dev/null)
local auth_legacy_jamf_account_saved
auth_legacy_jamf_account_saved=$(defaults read "${SUPER_LOCAL_PLIST}" JamfAccount 2>/dev/null)
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_ask_user_to_save_password: ${auth_ask_user_to_save_password}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_user_account_saved: ${auth_user_account_saved}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_local_account_saved: ${auth_local_account_saved}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_service_account_saved: ${auth_service_account_saved}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_jamf_client_saved: ${auth_jamf_client_saved}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_jamf_account_saved: ${auth_jamf_account_saved}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_legacy_local_account_saved: ${auth_legacy_local_account_saved}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_legacy_super_account_saved: ${auth_legacy_super_account_saved}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_legacy_jamf_account_saved: ${auth_legacy_jamf_account_saved}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_local_account_option: ${auth_local_account_option}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_service_add_via_admin_account_option: ${auth_service_add_via_admin_account_option}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_jamf_client_option: ${auth_jamf_client_option}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_jamf_account_option: ${auth_jamf_account_option}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_delete_all_option: ${auth_delete_all_option}"
# Logging to indicate if there are no saved accounts when a delete is requested.
{ [[ "${auth_ask_user_to_save_password}" == "FALSE" ]] && [[ "${auth_user_account_saved}" == "FALSE" ]] && [[ -z "${auth_local_account_saved}" ]] && [[ -z "${auth_service_account_saved}" ]] && [[ -z "${auth_jamf_client_saved}" ]] && [[ -z "${auth_jamf_account_saved}" ]] && [[ -z "${auth_legacy_local_account_saved}" ]] && [[ -z "${auth_legacy_super_account_saved}" ]] && [[ -z "${auth_legacy_jamf_account_saved}" ]] && [[ -n "${auth_delete_all_option}" ]]; } && log_super "Status: No saved authentication credentials to delete."
# If the user specified any new Apple silicon authentication option or the ${auth_delete_all_option} then delete any previously saved credentials.
if [[ "${auth_user_account_saved}" == "TRUE" ]] && { [[ "${auth_ask_user_to_save_password}" == "FALSE" ]] || [[ -n "${auth_local_account_option}" ]] || [[ -n "${auth_service_add_via_admin_account_option}" ]] || [[ -n "${auth_jamf_client_option}" ]] || [[ -n "${auth_jamf_account_option}" ]] || [[ "${auth_delete_all_option}" == "TRUE" ]]; }; then
log_super "Status: Deleting saved credentials for the --auth-ask-user-to-save-password option."
launchctl asuser "${current_user_id}" sudo -u "${current_user_account_name}" security delete-generic-password -a "super_auth_user_password" "/Users/${current_user_account_name}/Library/Keychains/login.keychain" >/dev/null 2>&1
defaults delete "${SUPER_LOCAL_PLIST}" AuthAskUserToSavePassword >/dev/null 2>&1
auth_ask_user_to_save_password="FALSE"
auth_user_account_saved="FALSE"
fi
if [[ "${auth_ask_user_to_save_password}" == "TRUE" ]] || [[ -n "${auth_local_account_option}" ]] || [[ -n "${auth_service_add_via_admin_account_option}" ]] || [[ -n "${auth_jamf_client_option}" ]] || [[ -n "${auth_jamf_account_option}" ]] || [[ "${auth_delete_all_option}" == "TRUE" ]]; then
if [[ "${auth_local_account_saved}" == "TRUE" ]]; then
log_super "Status: Deleting saved credentials for the --auth-local-account option."
security delete-generic-password -a "super_auth_local_account" "/Library/Keychains/System.keychain" >/dev/null 2>&1
security delete-generic-password -a "super_auth_local_password" "/Library/Keychains/System.keychain" >/dev/null 2>&1
defaults delete "${SUPER_LOCAL_PLIST}" AuthLocalAccount >/dev/null 2>&1
auth_local_account_saved="FALSE"
fi
if [[ "${auth_service_account_saved}" == "TRUE" ]]; then
log_super "Status: Deleting the super service account and saved credentials."
auth_service_account_keychain=$(security find-generic-password -w -a "super_auth_service_account" "/Library/Keychains/System.keychain" 2>/dev/null | base64 --decode)
sysadminctl -deleteUser "${auth_service_account_keychain}" >/dev/null 2>&1
security delete-generic-password -a "super_auth_service_account" "/Library/Keychains/System.keychain" >/dev/null 2>&1
security delete-generic-password -a "super_auth_service_password" "/Library/Keychains/System.keychain" >/dev/null 2>&1
defaults delete "${SUPER_LOCAL_PLIST}" AuthServiceAccount >/dev/null 2>&1
auth_service_account_saved="FALSE"
fi
if [[ "${auth_jamf_client_saved}" == "TRUE" ]]; then
log_super "Status: Deleting saved credentials for the --auth-jamf-client option."
security delete-generic-password -a "super_auth_jamf_client" "/Library/Keychains/System.keychain" >/dev/null 2>&1
security delete-generic-password -a "super_auth_jamf_secret" "/Library/Keychains/System.keychain" >/dev/null 2>&1
defaults delete "${SUPER_LOCAL_PLIST}" AuthJamfClient >/dev/null 2>&1
auth_jamf_client_saved="FALSE"
fi
if [[ "${auth_jamf_account_saved}" == "TRUE" ]]; then
log_super "Status: Deleting saved credentials for the --auth-jamf-account option."
security delete-generic-password -a "super_auth_jamf_account" "/Library/Keychains/System.keychain" >/dev/null 2>&1
security delete-generic-password -a "super_auth_jamf_password" "/Library/Keychains/System.keychain" >/dev/null 2>&1
defaults delete "${SUPER_LOCAL_PLIST}" AuthJamfAccount >/dev/null 2>&1
auth_jamf_account_saved="FALSE"
fi
if [[ -n "${auth_legacy_local_account_saved}" ]]; then
log_super "Status: Deleting saved credentials for legacy local account."
defaults delete "${SUPER_LOCAL_PLIST}" LocalAccount >/dev/null 2>&1
security delete-generic-password -a "${auth_legacy_local_account_saved}" -s "Super Local Account" /Library/Keychains/System.keychain >/dev/null 2>&1
unset auth_legacy_local_account_saved
fi
if [[ -n "${auth_legacy_super_account_saved}" ]]; then
log_super "Status: Deleting local account and saved credentials for legacy super service account."
sysadminctl -deleteUser "${auth_legacy_super_account_saved}" >/dev/null 2>&1
defaults delete "${SUPER_LOCAL_PLIST}" SuperAccount >/dev/null 2>&1
security delete-generic-password -a "${auth_legacy_super_account_saved}" -s "Super Service Account" /Library/Keychains/System.keychain >/dev/null 2>&1
unset auth_legacy_super_account_saved
fi
if [[ -n "${auth_legacy_jamf_account_saved}" ]]; then
log_super "Status: Deleting saved credentials for legacy Jamf Pro API account."
defaults delete "${SUPER_LOCAL_PLIST}" JamfAccount >/dev/null 2>&1
security delete-generic-password -a "${auth_legacy_jamf_account_saved}" -s "Super MDM Account" /Library/Keychains/System.keychain >/dev/null 2>&1
unset auth_legacy_jamf_account_saved
fi
fi
[[ "${auth_delete_all_option}" == "TRUE" ]] && return 0
# Start migration of any legacy authentication items new keychain storage method.
if [[ -n "${auth_legacy_local_account_saved}" ]]; then
log_super "Status: Migrating saved legacy local account credentials..."
local auth_legacy_local_password_saved
auth_legacy_local_password_saved=$(security find-generic-password -w -a "${auth_legacy_local_account_saved}" -s "Super Local Account" /Library/Keychains/System.keychain 2>/dev/null)
if [[ -n "${auth_legacy_local_password_saved}" ]]; then
auth_local_account_option="${auth_legacy_local_account_saved}"
auth_local_password_option="${auth_legacy_local_password_saved}"
local auth_legacy_local_migrate
auth_legacy_local_migrate="TRUE"
else
log_super "Auth Error: Unable to retrieve legacy local account password."
auth_error_new="TRUE"
fi
fi
if [[ -n "${auth_legacy_super_account_saved}" ]]; then
log_super "Status: Migrating saved legacy super service account credentials..."
local auth_legacy_super_password_saved
auth_legacy_super_password_saved=$(security find-generic-password -w -a "${auth_legacy_super_account_saved}" -s "Super Service Account" /Library/Keychains/System.keychain 2>/dev/null)
if [[ -n "${auth_legacy_super_password_saved}" ]]; then
auth_service_account="${auth_legacy_super_account_saved}"
auth_service_password="${auth_legacy_super_password_saved}"
local auth_legacy_service_migrate
auth_legacy_service_migrate="TRUE"
else
log_super "Auth Error: Unable to retrieve legacy super service account password."
auth_error_new="TRUE"
fi
fi
if [[ -n "${auth_legacy_jamf_account_saved}" ]]; then
log_super "Status: Migrating saved legacy Jamf Pro API account credentials..."
local auth_legacy_jamf_password_saved
auth_legacy_jamf_password_saved=$(security find-generic-password -w -a "${auth_legacy_jamf_account_saved}" -s "Super MDM Account" /Library/Keychains/System.keychain 2>/dev/null)
if [[ -n "${auth_legacy_jamf_password_saved}" ]]; then
auth_jamf_account_option="${auth_legacy_jamf_account_saved}"
auth_jamf_password_option="${auth_legacy_jamf_password_saved}"
local auth_legacy_jamf_migrate
auth_legacy_jamf_migrate="TRUE"
else
log_super "Auth Error: Unable to retrieve legacy Jamf Pro API account password."
auth_error_new="TRUE"
fi
fi
# This validates and saves to the keychain a single (non-end-user) Apple silicon authentication option. If multiple options are specified, only one is saved via the following priority order:
# ${auth_ask_user_to_save_password} > ${auth_local_account_option} > ${auth_service_add_via_admin_account_option} > ${auth_jamf_client_option} > ${auth_jamf_account_option}
if [[ "${auth_ask_user_to_save_password}" -eq 1 ]] || [[ "${auth_ask_user_to_save_password}" == "TRUE" ]]; then
{ [[ -n "${auth_local_account_option}" ]] || [[ -n "${auth_service_add_via_admin_account_option}" ]] || [[ -n "${auth_jamf_client_option}" ]] || [[ -n "${auth_jamf_account_option}" ]]; } && log_super "Auth Warning: The --auth-ask-user-to-save-password option overrides all other Apple silicon authentication methods."
[[ -n "${auth_local_account_option}" ]] && log_super "Auth Warning: Ignoring the --auth-local-account option."
[[ -n "${auth_service_add_via_admin_account_option}" ]] && log_super "Auth Warning: Ignoring the --auth-service-add-via-admin-account option."
[[ -n "${auth_jamf_client_option}" ]] && log_super "Auth Warning: Ignoring the --auth-jamf-client option."
[[ -n "${auth_jamf_account_option}" ]] && log_super "Auth Warning: Ignoring the --auth-jamf-account option."
[[ "${current_user_account_name}" != "FALSE" ]] && [[ "${auth_user_account_saved}" == "FALSE" ]] && log_super "Status: A new automatic authentication password will be saved the next time a valid user succesfully authenticates."
# The ${auth_ask_user_to_save_password} option is saved later via the dialog_user_auth() function.
elif [[ -n "${auth_local_account_option}" ]]; then
{ [[ -n "${auth_service_add_via_admin_account_option}" ]] || [[ -n "${auth_jamf_client_option}" ]] || [[ -n "${auth_jamf_account_option}" ]]; } && log_super "Auth Warning: The --auth-local-account option overrides the --auth-service-add-via-admin-account option and any other Apple silicon MDM authentication methods."
[[ -n "${auth_service_add_via_admin_account_option}" ]] && log_super "Auth Warning: Ignoring the --auth-service-add-via-admin-account option."
[[ -n "${auth_jamf_client_option}" ]] && log_super "Auth Warning: Ignoring the --auth-jamf-client option."
[[ -n "${auth_jamf_account_option}" ]] && log_super "Auth Warning: Ignoring the --auth-jamf-account option."
[[ "${auth_legacy_local_migrate}" != "TRUE" ]] && log_super "Status: Validating new --auth-local-account credentials..."
[[ "${auth_legacy_local_migrate}" == "TRUE" ]] && log_super "Status: Validating migrated --auth-local-account credentials..."
# Validate that ${auth_local_account_option} exists, is a volume owner, and that ${auth_local_password_option} is correct.
auth_local_account="${auth_local_account_option}"
auth_local_password="${auth_local_password_option}"
check_auth_local_account
unset auth_local_account
unset auth_local_password
[[ "${auth_error_local}" == "TRUE" ]] && auth_error_new="TRUE"
# If the ${auth_local_account_option} and ${auth_local_password_option} are valid then save credentials to keychain and then validate retrieval by setting ${auth_local_account_keychain} and ${auth_local_password_keychain}.
if [[ "${auth_error_new}" != "TRUE" ]]; then
security add-generic-password -a "super_auth_local_account" -s "Super Update Service" -w "$(echo "${auth_local_account_option}" | base64)" -T "/usr/bin/security" "/Library/Keychains/System.keychain" >/dev/null 2>&1
auth_local_account_keychain=$(security find-generic-password -w -a "super_auth_local_account" "/Library/Keychains/System.keychain" 2>/dev/null | base64 --decode)
if [[ "${auth_local_account_option}" != "${auth_local_account_keychain}" ]]; then
[[ "${verbose_mode_option}" == "TRUE" ]] && log_echo "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_local_account_option is: ${auth_local_account_option}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_echo "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_local_account_keychain is: ${auth_local_account_keychain}"
log_super "Auth Error: Unable to validate keychain item for --auth-local-account, deleting keychain item."
auth_error_new="TRUE"
security delete-generic-password -a "super_auth_local_account" "/Library/Keychains/System.keychain" >/dev/null 2>&1
fi
security add-generic-password -a "super_auth_local_password" -s "Super Update Service" -w "$(echo "${auth_local_password_option}" | base64)" -T "/usr/bin/security" "/Library/Keychains/System.keychain" >/dev/null 2>&1
auth_local_password_keychain=$(security find-generic-password -w -a "super_auth_local_password" "/Library/Keychains/System.keychain" 2>/dev/null | base64 --decode)
if [[ "${auth_local_password_option}" != "${auth_local_password_keychain}" ]]; then
[[ "${verbose_mode_option}" == "TRUE" ]] && log_echo "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_local_password_option is: ${auth_local_password_option}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_echo "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_local_password_keychain is: ${auth_local_password_keychain}"
log_super "Auth Error: Unable to validate keychain item for --auth-local-password, deleting keychain item."
auth_error_new="TRUE"
security delete-generic-password -a "super_auth_local_password" "/Library/Keychains/System.keychain" >/dev/null 2>&1
fi
fi
# If the saved credentials are valid then update ${SUPER_LOCAL_PLIST}.
if [[ "${auth_error_new}" != "TRUE" ]]; then
[[ "${auth_legacy_local_migrate}" != "TRUE" ]] && log_super "Status: Saved new credentials for the --auth-local-account option."
[[ "${auth_legacy_local_migrate}" == "TRUE" ]] && log_super "Status: Saved migrated credentials for the --auth-local-account option."
defaults write "${SUPER_LOCAL_PLIST}" AuthLocalAccount -bool true
auth_local_account_saved="TRUE"
else
[[ "${auth_legacy_local_migrate}" != "TRUE" ]] && log_super "Auth Error: The new --auth-local-account credentials will not be saved due to validation errors."
[[ "${auth_legacy_local_migrate}" == "TRUE" ]] && log_super "Auth Error: The migrated --auth-local-account and credentials will not be saved due to validation errors."
auth_local_account_saved="FALSE"
unset auth_local_account_keychain
unset auth_local_password_keychain
fi
elif [[ -n "${auth_service_add_via_admin_account_option}" ]] || [[ "${auth_legacy_service_migrate}" == "TRUE" ]]; then
{ [[ -n "${auth_jamf_client_option}" ]] || [[ -n "${auth_jamf_account_option}" ]]; } && log_super "Auth Warning: The --auth-service-add-via-admin-account option overrides any other Apple silicon MDM authentication methods."
[[ -n "${auth_jamf_client_option}" ]] && log_super "Auth Warning: Ignoring the --auth-jamf-client option."
[[ -n "${auth_jamf_account_option}" ]] && log_super "Auth Warning: Ignoring the --auth-jamf-account option."
[[ "${auth_legacy_service_migrate}" != "TRUE" ]] && log_super "Status: Creating and validating new super service account..."
[[ "${auth_legacy_service_migrate}" == "TRUE" ]] && log_super "Status: Validating migratred super service account..."
# Validate that ${auth_service_add_via_admin_account_option} exists, is a volume owner, a local admin, and that ${auth_service_add_via_admin_password_option} is correct.
if [[ "${auth_legacy_service_migrate}" != "TRUE" ]]; then # If migrating a legacy sevice account, then the ${auth_service_add_via_admin_account_option} doesn't have to be validated.
if [[ $(groups "${auth_service_add_via_admin_account_option}" 2>/dev/null | grep -c 'admin') -eq 0 ]]; then
log_super "Auth Error: The account \"${auth_service_add_via_admin_account_option}\" is not a local administrator."
auth_error_new="TRUE"
fi
if [[ "${auth_error_new}" != "TRUE" ]]; then
auth_local_account="${auth_service_add_via_admin_account_option}"
auth_local_password="${auth_service_add_via_admin_password_option}"
check_auth_local_account
unset auth_local_account
unset auth_local_password
[[ "${auth_error_local}" == "TRUE" ]] && auth_error_new="TRUE"
fi
fi
# Set the ${auth_service_account}, ${auth_service_real_name}, and ${auth_service_password} in preparation to create the super service account.
if [[ "${auth_error_new}" != "TRUE" ]]; then
if [[ "${auth_legacy_service_migrate}" != "TRUE" ]]; then # If migrating a legacy sevice account, then a new service account doesn't have to be created.
local auth_service_account
local auth_service_real_name
if [[ -n "${auth_service_account_option}" ]]; then
auth_service_account="${auth_service_account_option}"
auth_service_real_name="${auth_service_account_option}"
else
auth_service_account="super"
auth_service_real_name="Super Update Service"
fi
local auth_service_password
if [[ -n "${auth_service_password_option}" ]]; then
auth_service_password="${auth_service_password_option}"
else
auth_service_password=$(uuidgen)
fi
fi
# Save ${auth_service_account} and ${auth_service_password} credentials to keychain and then validate retrieval by setting ${auth_service_account_keychain} and ${auth_service_password_keychain}.
security add-generic-password -a "super_auth_service_account" -s "Super Update Service" -w "$(echo "${auth_service_account}" | base64)" -T "/usr/bin/security" "/Library/Keychains/System.keychain" >/dev/null 2>&1
auth_service_account_keychain=$(security find-generic-password -w -a "super_auth_service_account" "/Library/Keychains/System.keychain" 2>/dev/null | base64 --decode)
if [[ "${auth_service_account}" != "${auth_service_account_keychain}" ]]; then
[[ "${verbose_mode_option}" == "TRUE" ]] && log_echo "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_service_account is: ${auth_service_account}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_echo "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_service_account_keychain is: ${auth_service_account_keychain}"
log_super "Auth Error: Unable to validate keychain item for the super service account, deleting keychain item."
auth_error_new="TRUE"
security delete-generic-password -a "super_auth_service_account" "/Library/Keychains/System.keychain" >/dev/null 2>&1
fi
security add-generic-password -a "super_auth_service_password" -s "Super Update Service" -w "$(echo "${auth_service_password}" | base64)" -T "/usr/bin/security" "/Library/Keychains/System.keychain" >/dev/null 2>&1
auth_service_password_keychain=$(security find-generic-password -w -a "super_auth_service_password" "/Library/Keychains/System.keychain" 2>/dev/null | base64 --decode)
if [[ "${auth_service_password}" != "${auth_service_password_keychain}" ]]; then
[[ "${verbose_mode_option}" == "TRUE" ]] && log_echo "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_service_password is: ${auth_service_password}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_echo "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_service_password_keychain is: ${auth_service_password_keychain}"
log_super "Auth Error: Unable to validate keychain item for the super service password, deleting keychain item."
auth_error_new="TRUE"
security delete-generic-password -a "super_auth_service_password" "/Library/Keychains/System.keychain" >/dev/null 2>&1
fi
fi
# If the saved credentials are valid then create the new super service account.
if [[ "${auth_error_new}" != "TRUE" ]] && [[ "${auth_legacy_service_migrate}" != "TRUE" ]]; then # If migrating a legacy sevice account, then a new service account doesn't have to be created.
local auth_service_uid
auth_service_uid=501
while [[ $(id "${auth_service_uid}" 2>&1 | grep -c 'no such user') -eq 0 ]]; do
auth_service_uid=$((auth_service_uid + 1))
done
local sysadminctl_response
sysadminctl_response=$(sysadminctl -addUser "${auth_service_account}" -fullName "${auth_service_real_name}" -password "${auth_service_password}" -UID "${auth_service_uid}" -GID 20 -shell "/dev/null" -home "/dev/null" -picture "${display_icon_light}" -adminUser "${auth_service_add_via_admin_account_option}" -adminPassword "${auth_service_add_via_admin_password_option}" 2>&1)
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: sysadminctl_response is:\n${sysadminctl_response}"
dscl . create /Users/"${auth_service_account}" IsHidden 1
fi
# Validate the super service account locally.
auth_local_account="${auth_service_account}"
auth_local_password="${auth_service_password}"
check_auth_local_account
unset auth_local_account
unset auth_local_password
[[ "${auth_error_local}" == "TRUE" ]] && auth_error_new="TRUE"
# If the super service account is valid then update ${SUPER_LOCAL_PLIST}.
if [[ "${auth_error_new}" != "TRUE" ]]; then
[[ "${auth_legacy_service_migrate}" != "TRUE" ]] && log_super "Status: Created new super service account."
[[ "${auth_legacy_service_migrate}" == "TRUE" ]] && log_super "Status: Validated migrated super service account."
defaults write "${SUPER_LOCAL_PLIST}" AuthServiceAccount -bool true
auth_service_account_saved="TRUE"
else
[[ "${auth_legacy_service_migrate}" != "TRUE" ]] && log_super "Auth Error: Unable to validate newly created super service account, deleting account"
auth_error_new="TRUE"
[[ "${auth_legacy_service_migrate}" == "TRUE" ]] && log_super "Auth Error: Unable to validate migrated super service account, deleting account."
auth_error_new="TRUE"
sysadminctl -deleteUser "${auth_service_account}" >/dev/null 2>&1
security delete-generic-password -a "super_auth_service_account" "/Library/Keychains/System.keychain" >/dev/null 2>&1
security delete-generic-password -a "super_auth_service_password" "/Library/Keychains/System.keychain" >/dev/null 2>&1
auth_service_account_saved="FALSE"
unset auth_service_account_keychain
unset auth_service_password_keychain
fi
elif [[ -n "${auth_jamf_client_option}" ]]; then
[[ -n "${auth_jamf_account_option}" ]] && log_super "Auth Warning: The --auth-jamf-client option overrides the --auth-jamf-account option."
[[ -n "${auth_jamf_account_option}" ]] && log_super "Auth Warning: Ignoring the --auth-jamf-account option."
log_super "Status: Validating new --auth-jamf-client credentials..."
# Validate that the client ${auth_jamf_client_option} and ${auth_jamf_secret_option} are valid.
auth_jamf_client="${auth_jamf_client_option}"
auth_jamf_secret="${auth_jamf_secret_option}"
check_jamf_api_credentials
delete_jamf_api_access_token
unset auth_jamf_client
unset auth_jamf_secret
[[ "${auth_error_jamf}" == "TRUE" ]] && auth_error_new="TRUE"
# If the ${auth_jamf_client_option} and ${auth_jamf_secret_option} are valid then save credentials to keychain and then validate retrieval by setting ${auth_jamf_client_keychain} and ${auth_jamf_secret_keychain}.
if [[ "${auth_error_new}" != "TRUE" ]]; then
security add-generic-password -a "super_auth_jamf_client" -s "Super Update Service" -w "$(echo "${auth_jamf_client_option}" | base64)" -T "/usr/bin/security" "/Library/Keychains/System.keychain" >/dev/null 2>&1
auth_jamf_client_keychain=$(security find-generic-password -w -a "super_auth_jamf_client" "/Library/Keychains/System.keychain" 2>/dev/null | base64 --decode)
if [[ "${auth_jamf_client_option}" != "${auth_jamf_client_keychain}" ]]; then
[[ "${verbose_mode_option}" == "TRUE" ]] && log_echo "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_jamf_client_option is: ${auth_jamf_client_option}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_echo "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_jamf_client_keychain is: ${auth_jamf_client_keychain}"
log_super "Auth Error: Unable to validate keychain item for --auth-jamf-client, deleting keychain item."
auth_error_new="TRUE"
security delete-generic-password -a "super_auth_jamf_client" "/Library/Keychains/System.keychain" >/dev/null 2>&1
fi
security add-generic-password -a "super_auth_jamf_secret" -s "Super Update Service" -w "$(echo "${auth_jamf_secret_option}" | base64)" -T "/usr/bin/security" "/Library/Keychains/System.keychain" >/dev/null 2>&1
auth_jamf_secret_keychain=$(security find-generic-password -w -a "super_auth_jamf_secret" "/Library/Keychains/System.keychain" 2>/dev/null | base64 --decode)
if [[ "${auth_jamf_secret_option}" != "${auth_jamf_secret_keychain}" ]]; then
[[ "${verbose_mode_option}" == "TRUE" ]] && log_echo "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_jamf_secret_option is: ${auth_jamf_secret_option}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_echo "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_jamf_secret_keychain is: ${auth_jamf_secret_keychain}"
log_super "Auth Error: Unable to validate keychain item for --auth-jamf-secret, deleting keychain item."
auth_error_new="TRUE"
security delete-generic-password -a "super_auth_jamf_secret" "/Library/Keychains/System.keychain" >/dev/null 2>&1
fi
fi
# If the saved credentials are valid then update ${SUPER_LOCAL_PLIST}.
if [[ "${auth_error_new}" != "TRUE" ]]; then
log_super "Status: Saved new credentials for the --auth-jamf-client option."
defaults write "${SUPER_LOCAL_PLIST}" AuthJamfClient -bool true
auth_jamf_client_saved="TRUE"
else
log_super "Auth Error: The --auth-jamf-client credentials will not be saved due to validation errors."
auth_jamf_client_saved="FALSE"
unset auth_jamf_client_keychain
unset auth_jamf_secret_keychain
fi
elif [[ -n "${auth_jamf_account_option}" ]]; then
[[ "${auth_legacy_jamf_migrate}" != "TRUE" ]] && log_super "Status: Validating new --auth-jamf-account credentials..."
[[ "${auth_legacy_jamf_migrate}" == "TRUE" ]] && log_super "Status: Validating migrated --auth-jamf-account credentials..."
# Validate that the account ${auth_jamf_account_option} and ${auth_jamf_password_option} are valid.
auth_jamf_account="${auth_jamf_account_option}"
auth_jamf_password="${auth_jamf_password_option}"
check_jamf_api_credentials
delete_jamf_api_access_token
unset auth_jamf_account
unset auth_jamf_password
[[ "${auth_error_jamf}" == "TRUE" ]] && auth_error_new="TRUE"
# If the ${super_auth_jamf_account} and ${super_auth_jamf_password} are valid then save credentials to keychain and then validate retrieval by setting ${auth_jamf_account_keychain} and ${auth_jamf_password_keychain}.
if [[ "${auth_error_new}" != "TRUE" ]]; then
security add-generic-password -a "super_auth_jamf_account" -s "Super Update Service" -w "$(echo "${auth_jamf_account_option}" | base64)" -T "/usr/bin/security" "/Library/Keychains/System.keychain" >/dev/null 2>&1
auth_jamf_account_keychain=$(security find-generic-password -w -a "super_auth_jamf_account" "/Library/Keychains/System.keychain" 2>/dev/null | base64 --decode)
if [[ "${auth_jamf_account_option}" != "${auth_jamf_account_keychain}" ]]; then
[[ "${verbose_mode_option}" == "TRUE" ]] && log_echo "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_jamf_account_option is: ${auth_jamf_account_option}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_echo "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_jamf_account_keychain is: ${auth_jamf_account_keychain}"
log_super "Auth Error: Unable to validate keychain item for --auth-jamf-account, deleting keychain item."
auth_error_new="TRUE"
security delete-generic-password -a "super_auth_jamf_account" "/Library/Keychains/System.keychain" >/dev/null 2>&1
fi
security add-generic-password -a "super_auth_jamf_password" -s "Super Update Service" -w "$(echo "${auth_jamf_password_option}" | base64)" -T "/usr/bin/security" "/Library/Keychains/System.keychain" >/dev/null 2>&1
auth_jamf_password_keychain=$(security find-generic-password -w -a "super_auth_jamf_password" "/Library/Keychains/System.keychain" 2>/dev/null | base64 --decode)
if [[ "${auth_jamf_password_option}" != "${auth_jamf_password_keychain}" ]]; then
[[ "${verbose_mode_option}" == "TRUE" ]] && log_echo "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_jamf_password_option is: ${auth_jamf_password_option}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_echo "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_jamf_password_keychain is: ${auth_jamf_password_keychain}"
log_super "Auth Error: Unable to validate keychain item for --auth-jamf-password, deleting keychain item."
auth_error_new="TRUE"
security delete-generic-password -a "super_auth_jamf_password" "/Library/Keychains/System.keychain" >/dev/null 2>&1
fi
fi
# If the saved credentials are valid then update ${SUPER_LOCAL_PLIST}.
if [[ "${auth_error_new}" != "TRUE" ]]; then
[[ "${auth_legacy_jamf_migrate}" != "TRUE" ]] && log_super "Status: Saved new credentials for the --auth-jamf-account option."
[[ "${auth_legacy_jamf_migrate}" == "TRUE" ]] && log_super "Status: Saved migrated credentials for the --auth-jamf-account option."
defaults write "${SUPER_LOCAL_PLIST}" AuthJamfAccount -bool true
auth_jamf_account_saved="TRUE"
else
[[ "${auth_legacy_jamf_migrate}" != "TRUE" ]] && log_super "Auth Error: The new --auth-jamf-account credentials will not be saved due to validation errors."
[[ "${auth_legacy_jamf_migrate}" == "TRUE" ]] && log_super "Auth Error: The migrated --auth-jamf-account credentials will not be saved due to validation errors."
auth_jamf_account_saved="FALSE"
unset auth_jamf_account_keychain
unset auth_jamf_password_keychain
fi
fi
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_user_account_saved: ${auth_user_account_saved}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_echo "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_user_password_keychain: ${auth_user_password_keychain}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_local_account_saved: ${auth_local_account_saved}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_service_account_saved: ${auth_service_account_saved}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_jamf_client_saved: ${auth_jamf_client_saved}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_jamf_account_saved: ${auth_jamf_account_saved}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_error_new: ${auth_error_new}"
# Delete any migrated legacy credentials.
if [[ "${auth_error_new}" == "FALSE" ]]; then
if [[ "${auth_legacy_local_migrate}" == "TRUE" ]]; then
log_super "Status: Deleting saved credentials for legacy local account."
defaults delete "${SUPER_LOCAL_PLIST}" LocalAccount >/dev/null 2>&1
security delete-generic-password -a "${auth_legacy_local_account_saved}" -s "Super Local Account" /Library/Keychains/System.keychain >/dev/null 2>&1
fi
if [[ "${auth_legacy_service_migrate}" == "TRUE" ]]; then
log_super "Status: Deleting saved credentials for legacy super service account."
defaults delete "${SUPER_LOCAL_PLIST}" SuperAccount >/dev/null 2>&1
security delete-generic-password -a "${auth_legacy_super_account_saved}" -s "Super Service Account" /Library/Keychains/System.keychain >/dev/null 2>&1
fi
if [[ "${auth_legacy_jamf_migrate}" == "TRUE" ]]; then
log_super "Status: Deleting saved credentials for legacy Jamf Pro API account."
defaults delete "${SUPER_LOCAL_PLIST}" JamfAccount >/dev/null 2>&1
security delete-generic-password -a "${auth_legacy_jamf_account_saved}" -s "Super MDM Account" /Library/Keychains/System.keychain >/dev/null 2>&1
fi
fi
}
# This function determines what ${workflow_macos_auth} workflows are possible given the architecture and authentication options during startup.
manage_workflow_options() {
workflow_auth_error="FALSE"
# If the ${workflow_disable_update_check_option} is enabled then there is no reason to continue this function.
[[ "${workflow_disable_update_check_option}" == "TRUE" ]] && return 0
# Logic to determine update/upgrade workflow authentication method, assuming no ${auth_error_new}.
if [[ "${auth_error_new}" != "TRUE" ]]; then
if [[ "${mac_cpu_architecture}" == "i386" ]] || [[ "${workflow_only_download_active}" == "TRUE" ]]; then # All Intel workflows and the only download workflow do not need authentication.
[[ "${workflow_only_download_active}" != "TRUE" ]] && log_super "Status: macOS update/upgrade workflows automatically authenticated via system account (root)."
workflow_macos_auth="LOCAL"
else # Standard workflow and computers with Apple silicon.
if [[ "${auth_user_account_saved}" == "TRUE" ]] || [[ "${auth_local_account_saved}" == "TRUE" ]] || [[ "${auth_service_account_saved}" == "TRUE" ]]; then
[[ "${auth_user_account_saved}" == "TRUE" ]] && log_super "Status: macOS update/upgrade workflows automatically authenticated via saved password for current user: ${current_user_account_name}"
[[ "${auth_local_account_saved}" == "TRUE" ]] && log_super "Status: macOS update/upgrade workflows automatically authenticated via saved local account."
[[ "${auth_service_account_saved}" == "TRUE" ]] && log_super "Status: macOS update/upgrade workflows automatically authenticated via super service account."
workflow_macos_auth="LOCAL"
elif [[ "${auth_jamf_client_saved}" == "TRUE" ]] || [[ "${auth_jamf_account_saved}" == "TRUE" ]]; then
if [[ "${macos_version_number}" -ge 1103 ]]; then
[[ -n "${auth_mdm_failover_to_user_option}" ]] && log_super "Status: macOS update/upgrade workflows automatically authenticated via Jamf Pro API with --auth-mdm-failover-to-user=${auth_mdm_failover_to_user_option}."
[[ -z "${auth_mdm_failover_to_user_option}" ]] && log_super "Status: macOS update/upgrade workflows automatically authenticated via Jamf Pro API with no --auth-mdm-failover-to-user options."
workflow_macos_auth="JAMF"
else # Systems older than macOS 11.3.
log_super "Warning: Automatic macOS update/upgrade enforcement via MDM is only available on macOS 11.3 or newer."
if [[ "${current_user_account_name}" != "FALSE" ]] && [[ "${current_user_has_secure_token}" == "TRUE" ]] && [[ "${current_user_is_volume_owner}" == "TRUE" ]]; then
log_super "Status: User authentication is required to perform a macOS update/upgrade."
workflow_macos_auth="USER"
else # No valid current user to authenticate workflow.
workflow_auth_error="TRUE"
fi
fi
else # No Apple silicon authentication options were provided.
log_super "Warning: Automatic macOS update/upgrade enforcement on Apple Silicon computers requires authentication credentials."
if [[ "${current_user_account_name}" != "FALSE" ]] && [[ "${current_user_has_secure_token}" == "TRUE" ]] && [[ "${current_user_is_volume_owner}" == "TRUE" ]]; then
log_super "Status: User authentication is required to perform a macOS update/upgrade."
workflow_macos_auth="USER"
else # No valid current user to authenticate workflow.
workflow_auth_error="TRUE"
fi
fi
fi
else # New authentication validation errors.
if [[ "${auth_credential_failover_to_user_option}" == "TRUE" ]]; then
if [[ "${current_user_account_name}" != "FALSE" ]] && [[ "${current_user_has_secure_token}" == "TRUE" ]] && [[ "${current_user_is_volume_owner}" == "TRUE" ]]; then
log_super "Warning: Apple silicon authentication options could not be validated, failing over to user authenticated workflow."
workflow_macos_auth="FAILOVER"
else # No valid current user to authenticate workflow.
log_super "Auth Error: Apple silicon authentication options could not be validated and user authentication failover is not currently possible."
workflow_auth_error="TRUE"
fi
elif { [[ "${auth_jamf_client_saved}" -eq 1 ]] || [[ "${auth_jamf_account_saved}" -eq 1 ]]; } && { [[ "${auth_mdm_failover_to_user_status}" == "TRUE" ]] || [[ $(echo "${auth_mdm_failover_to_user_option}" | grep -c 'ERROR') -gt 0 ]] || { [[ "${workflow_install_now_active}" == "TRUE" ]] && [[ $(echo "${auth_mdm_failover_to_user_option}" | grep -c 'INSTALLNOW') -gt 0 ]]; }; }; then
if [[ "${current_user_account_name}" != "FALSE" ]] && [[ "${current_user_has_secure_token}" == "TRUE" ]] && [[ "${current_user_is_volume_owner}" == "TRUE" ]]; then
log_super "Warning: Apple silicon MDM credentials could not be validated, failing over to user authenticated workflow."
workflow_macos_auth="FAILOVER"
else # No valid current user to authenticate workflow.
log_super "Auth Error: Apple silicon MDM credentials could not be validated and user authentication failover is not currently possible."
workflow_auth_error="TRUE"
fi
else # No authentication failover options.
log_super "Exit: Apple silicon authentication options could not be validated and no failover option was specified, the workflow cannot continue."
log_status "Inactive Error: Apple silicon authentication options could not be validated and no failover option was specified, the workflow cannot continue."
{ [[ "${workflow_install_now_active}" == "TRUE" ]] && [[ "${current_user_account_name}" != "FALSE" ]]; } && notification_install_now_failed
exit_error
fi
fi
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: workflow_auth_error is: ${workflow_auth_error}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: workflow_macos_auth is: ${workflow_macos_auth}"
# Provide logging for ${workflow_auth_error} conditions, which at this point is a workflow-stopper.
if [[ "${workflow_auth_error}" == "TRUE" ]]; then
if [[ "${current_user_account_name}" == "FALSE" ]]; then
log_super "Auth Error: No current active user to provide local authentication."
else
[[ "${current_user_has_secure_token}" == "FALSE" ]] && log_super "Auth Error: The user \"${current_user_account_name}\" does not have a secure token."
[[ "${current_user_is_volume_owner}" == "FALSE" ]] && log_super "Auth Error: The user \"${current_user_account_name}\" is not a system volume owner."
fi
fi
# Provide logging if there is a scheduled installation option that will fail without saved authentication, which at this point is a workflow-stopper.
if { [[ -n "${scheduled_install_days}" ]] || [[ -n "${scheduled_install_date}" ]]; } && { [[ "${workflow_auth_error}" == "TRUE" ]] || [[ "${workflow_macos_auth}" == "USER" ]]; }; then
log_super "Auth Error: The --scheduled-install-date and --scheduled-install-days options require valid saved authentication."
workflow_auth_error="TRUE"
fi
}
# For Apple Silicon computers this function validates previously saved update/upgrade credentials given the various --auth_* options. Any error will set ${auth_error_saved}.
get_saved_authentication() {
auth_error_saved="FALSE"
auth_user_saved_password_valid="FALSE"
auth_local_account_valid="FALSE"
auth_service_account_valid="FALSE"
auth_jamf_client_valid="FALSE"
auth_jamf_account_valid="FALSE"
# If there is a previously saved user account then validate the credentials and set ${auth_local_account} and ${auth_local_password}.
if [[ "${auth_ask_user_to_save_password}" -eq 1 ]] || [[ "${auth_ask_user_to_save_password}" == "TRUE" ]]; then
[[ -z "${auth_user_password_keychain}" ]] && auth_user_password_keychain=$(launchctl asuser "${current_user_id}" sudo -u "${current_user_account_name}" security find-generic-password -w -a "super_auth_user_password" "/Users/${current_user_account_name}/Library/Keychains/login.keychain" 2>&1)
[[ "${verbose_mode_option}" == "TRUE" ]] && log_echo "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_user_password_keychain: ${auth_user_password_keychain}"
if [[ $(echo "${auth_user_password_keychain}" | grep -c 'The specified item could not be found in the keychain.') -ge 1 ]]; then
log_super "Status: The --auth-ask-user-to-save-password option is enabled but a user password is not currently saved."
log_super "Status: A new automatic authentication password will be saved the next time a valid user succesfully authenticates."
auth_error_saved="TRUE"
auth_user_account_saved="FALSE"
elif [[ $(echo "${auth_user_password_keychain}" | grep -c 'Failed to get user context') -ge 1 ]]; then
log_super "Warning: The user's keychain is not currently available, unable to validate saved credentials for user: ${current_user_account_name}."
auth_error_saved="TRUE"
auth_user_account_saved="FALSE"
else
auth_local_account="${current_user_account_name}"
auth_local_password="${auth_user_password_keychain}"
check_auth_local_account
if [[ "${auth_error_local}" != "TRUE" ]]; then
log_super "Status: Validated saved credentials for the current user: ${current_user_account_name}"
auth_user_saved_password_valid="TRUE"
else
log_super "Warning: Unable to validate previously saved credentials for the current user: ${current_user_account_name}"
unset auth_local_account
unset auth_local_password
launchctl asuser "${current_user_id}" sudo -u "${current_user_account_name}" security delete-generic-password -a "super_auth_user_password" "/Users/${current_user_account_name}/Library/Keychains/login.keychain" >/dev/null 2>&1
log_super "Status: A new automatic authentication password will be saved the next time a valid user succesfully authenticates."
auth_error_saved="TRUE"
auth_user_account_saved="FALSE"
fi
fi
unset auth_user_password_keychain
fi
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_ask_user_to_save_password: ${auth_ask_user_to_save_password}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_user_saved_password_valid: ${auth_user_saved_password_valid}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_user_account_saved: ${auth_user_account_saved}"
# If there is a previously saved local account then validate the credentials and set ${auth_local_account} and ${auth_local_password}.
if [[ "${auth_local_account_saved}" -eq 1 ]] || [[ "${auth_local_account_saved}" == "TRUE" ]]; then
[[ -z "${auth_local_account_keychain}" ]] && auth_local_account_keychain=$(security find-generic-password -w -a "super_auth_local_account" "/Library/Keychains/System.keychain" 2>/dev/null | base64 --decode)
[[ -z "${auth_local_password_keychain}" ]] && auth_local_password_keychain=$(security find-generic-password -w -a "super_auth_local_password" "/Library/Keychains/System.keychain" 2>/dev/null | base64 --decode)
[[ "${verbose_mode_option}" == "TRUE" ]] && log_echo "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_local_account_keychain: ${auth_local_account_keychain}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_echo "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_local_password_keychain: ${auth_local_password_keychain}"
if [[ -n "${auth_local_account_keychain}" ]] && [[ -n "${auth_local_password_keychain}" ]]; then
auth_local_account="${auth_local_account_keychain}"
auth_local_password="${auth_local_password_keychain}"
check_auth_local_account
if [[ "${auth_error_local}" != "TRUE" ]]; then
log_super "Status: Validated saved credentials for the --auth-local-account option."
auth_local_account_valid="TRUE"
else
log_super "Auth Error: Unable to validate the saved --auth-local-account credentials."
auth_error_saved="TRUE"
unset auth_local_account
unset auth_local_password
fi
else
log_super "Auth Error: Unable to retrieve keychain items for the saved --auth-local-account credentials."
auth_error_saved="TRUE"
fi
unset auth_local_account_keychain
unset auth_local_password_keychain
else
auth_local_account_saved="FALSE"
fi
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_local_account_saved: ${auth_local_account_saved}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_local_account_valid: ${auth_local_account_valid}"
# If there is a previously saved super service account then validate the credentials and set ${auth_local_account} and ${auth_local_password}.
if [[ "${auth_service_account_saved}" -eq 1 ]] || [[ "${auth_service_account_saved}" == "TRUE" ]]; then
[[ -z "${auth_service_account_keychain}" ]] && auth_service_account_keychain=$(security find-generic-password -w -a "super_auth_service_account" "/Library/Keychains/System.keychain" 2>/dev/null | base64 --decode)
[[ -z "${auth_service_password_keychain}" ]] && auth_service_password_keychain=$(security find-generic-password -w -a "super_auth_service_password" "/Library/Keychains/System.keychain" 2>/dev/null | base64 --decode)
[[ "${verbose_mode_option}" == "TRUE" ]] && log_echo "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_service_account_keychain: ${auth_service_account_keychain}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_echo "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_service_password_keychain: ${auth_service_password_keychain}"
if [[ -n "${auth_service_account_keychain}" ]] && [[ -n "${auth_service_password_keychain}" ]]; then
auth_local_account="${auth_service_account_keychain}"
auth_local_password="${auth_service_password_keychain}"
check_auth_local_account
if [[ "${auth_error_local}" != "TRUE" ]]; then
log_super "Status: Validated saved credentials for the super service account."
auth_service_account_valid="TRUE"
else
log_super "Auth Error: Unable to validate the saved super service account credentials."
auth_error_saved="TRUE"
unset auth_local_account
unset auth_local_password
fi
else
log_super "Auth Error: Unable to retrieve keychain items for the saved super service account credentials."
auth_error_saved="TRUE"
fi
unset auth_service_account_keychain
unset auth_service_password_keychain
else
auth_service_account_saved="FALSE"
fi
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_service_account_saved: ${auth_service_account_saved}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_service_account_valid: ${auth_service_account_valid}"
# If there is a previously saved Jamf Pro API client then validate the credentials and set ${auth_jamf_client} and ${auth_jamf_secret}.
if [[ "${auth_jamf_client_saved}" -eq 1 ]] || [[ "${auth_jamf_client_saved}" == "TRUE" ]]; then
[[ -z "${auth_jamf_client_keychain}" ]] && auth_jamf_client_keychain=$(security find-generic-password -w -a "super_auth_jamf_client" "/Library/Keychains/System.keychain" 2>/dev/null | base64 --decode)
[[ -z "${auth_jamf_secret_keychain}" ]] && auth_jamf_secret_keychain=$(security find-generic-password -w -a "super_auth_jamf_secret" "/Library/Keychains/System.keychain" 2>/dev/null | base64 --decode)
[[ "${verbose_mode_option}" == "TRUE" ]] && log_echo "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_jamf_client_keychain: ${auth_jamf_client_keychain}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_echo "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_jamf_secret_keychain: ${auth_jamf_secret_keychain}"
if [[ -n "${auth_jamf_client_keychain}" ]] && [[ -n "${auth_jamf_secret_keychain}" ]]; then
auth_jamf_client="${auth_jamf_client_keychain}"
auth_jamf_secret="${auth_jamf_secret_keychain}"
check_jamf_api_credentials
if [[ "${auth_error_jamf}" != "TRUE" ]]; then
log_super "Status: Validated saved credentials for the --auth-jamf-client option."
auth_jamf_client_valid="TRUE"
else
log_super "Auth Error: Unable to validate the saved --auth-jamf-client credentials."
auth_error_saved="TRUE"
unset auth_jamf_client
unset auth_jamf_secret
fi
else
log_super "Auth Error: Unable to retrieve keychain items for the saved --auth-jamf-client credentials."
auth_error_saved="TRUE"
fi
unset auth_jamf_client_keychain
unset auth_jamf_secret_keychain
else
auth_jamf_client_saved="FALSE"
fi
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_jamf_client_saved: ${auth_jamf_client_saved}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_jamf_client_valid: ${auth_jamf_client_valid}"
# If there is a previously saved Jamf Pro API account then validate the credentials and set ${auth_jamf_account} and ${auth_jamf_password}.
if [[ "${auth_jamf_account_saved}" -eq 1 ]] || [[ "${auth_jamf_account_saved}" == "TRUE" ]]; then
[[ -z "${auth_jamf_account_keychain}" ]] && auth_jamf_account_keychain=$(security find-generic-password -w -a "super_auth_jamf_account" "/Library/Keychains/System.keychain" 2>/dev/null | base64 --decode)
[[ -z "${auth_jamf_password_keychain}" ]] && auth_jamf_password_keychain=$(security find-generic-password -w -a "super_auth_jamf_password" "/Library/Keychains/System.keychain" 2>/dev/null | base64 --decode)
[[ "${verbose_mode_option}" == "TRUE" ]] && log_echo "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_jamf_account_keychain: ${auth_jamf_account_keychain}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_echo "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_jamf_password_keychain: ${auth_jamf_password_keychain}"
if [[ -n "${auth_jamf_account_keychain}" ]] && [[ -n "${auth_jamf_password_keychain}" ]]; then
auth_jamf_account="${auth_jamf_account_keychain}"
auth_jamf_password="${auth_jamf_password_keychain}"
check_jamf_api_credentials
if [[ "${auth_error_jamf}" != "TRUE" ]]; then
log_super "Status: Validated saved credentials for the --auth-jamf-account option."
auth_jamf_account_valid="TRUE"
else
log_super "Auth Error: Unable to validate the saved --auth-jamf-account credentials."
auth_error_saved="TRUE"
unset auth_jamf_account
unset auth_jamf_password
fi
else
log_super "Auth Error: Unable to retrieve keychain items for the saved --auth-jamf-account credentials."
auth_error_saved="TRUE"
fi
unset auth_jamf_account_keychain
unset auth_jamf_password_keychain
else
auth_jamf_account_saved="FALSE"
fi
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_jamf_account_saved: ${auth_jamf_account_saved}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_jamf_account_valid: ${auth_jamf_account_valid}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_error_saved: ${auth_error_saved}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_echo "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_local_account: ${auth_local_account}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_echo "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_local_password: ${auth_local_password}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_echo "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_jamf_client: ${auth_jamf_client}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_echo "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_jamf_secret: ${auth_jamf_secret}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_echo "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_jamf_account: ${auth_jamf_account}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_echo "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_jamf_password: ${auth_jamf_password}"
}
# MARK: *** Installation & Startup ***
################################################################################
# Install items required for super.
workflow_installation() {
[[ ! -d "${SUPER_FOLDER}" ]] && mkdir -p "${SUPER_FOLDER}"
[[ ! -d "${SUPER_LOG_FOLDER}" ]] && mkdir -p "${SUPER_LOG_FOLDER}"
[[ ! -d "${SUPER_LOG_ARCHIVE_FOLDER}" ]] && mkdir -p "${SUPER_LOG_ARCHIVE_FOLDER}"
log_super "**** S.U.P.E.R.M.A.N. ${SUPER_VERSION} - SUPER INSTALLATION ****"
log_status "Running: Installation workflow."
log_super "Installation: Copying super to: ${SUPER_FOLDER}/super"
cp "$0" "${SUPER_FOLDER}/super" >/dev/null 2>&1
if [[ ! -d "/usr/local/bin" ]]; then
log_super "Installation: Creating local search path folder: /usr/local/bin"
mkdir -p "/usr/local/bin"
chmod -R a+rx "/usr/local/bin"
fi
log_super "Installation: Creating super search path link: ${SUPER_LINK}"
ln -s "${SUPER_FOLDER}/super" "${SUPER_LINK}" >/dev/null 2>&1
log_super "Installation: Creating super LaunchDaemon helper: ${SUPER_FOLDER}/super-starter"
/bin/cat <"${SUPER_FOLDER}/super-starter"
#!/bin/bash
# S.U.P.E.R.M.A.N. STARTER
# Software Update/Upgrade Policy Enforcement (with) Recursive Messaging And Notification
# https://github.com/Macjutsu/super
# by Kevin M. White
# This script is ran by launchd every 60 seconds per the /Library/LaunchDaemons/com.macjutsu.super.plist.
# This script checks a variety of settings to ensure that the super workflow only restarts when necissary.
# Version ${SUPER_VERSION}
# ${SUPER_DATE}
# Exit this script if super is already running.
[[ "\$(pgrep -F "${SUPER_PID_FILE}" 2> /dev/null)" ]] && exit 0
# Exit this script if the super auto launch workflow is disabled.
next_auto_launch=\$(/usr/libexec/PlistBuddy -c "Print :NextAutoLaunch" "${SUPER_LOCAL_PLIST}.plist" 2> /dev/null)
if [[ "\${next_auto_launch}" == "FALSE" ]] || [[ "\${next_auto_launch}" == "false" ]]; then
exit 0
fi
# Exit this script if the super auto launch workflow is deferred until a system restart.
if [[ "\$(/usr/libexec/PlistBuddy -c "Print :WorkflowRestartValidate" "${SUPER_LOCAL_PLIST}.plist" 2> /dev/null)" == "true" ]]; then
mac_last_startup_saved_epoch=\$(date -j -f %Y-%m-%d:%H:%M:%S "\$(/usr/libexec/PlistBuddy -c "Print :MacLastStartup" "${SUPER_LOCAL_PLIST}.plist" 2> /dev/null)" +%s 2> /dev/null)
kernel_boot_epoch=\$(sysctl -n kern.boottime | awk -F 'sec = |, usec' '{print \$2}')
[[ -n "\${mac_last_startup_saved_epoch}" ]] && [[ -n "\${kernel_boot_epoch}" ]] && [[ "\${mac_last_startup_saved_epoch}" -ge "\${kernel_boot_epoch}" ]] && exit 0
fi
# Exit this script if the super auto launch workflow is deferred until a later date
if [[ \$(date +%s) -lt \$(date -j -f %Y-%m-%d:%H:%M:%S "\${next_auto_launch}" +%s 2> /dev/null) ]]; then
exit 0
fi
# If this script has not exited yet, then it's time to start the super workflow.
echo "\$(date +"%a %b %d %T") \$(hostname -s) \$(basename "\$0")[\$\$]: **** S.U.P.E.R.M.A.N. ${SUPER_VERSION} - LAUNCHDAEMON ****" | tee -a "${SUPER_LOG}"
"${SUPER_FOLDER}/super" &
disown -a
exit 0
EOSS
if [[ -f "/Library/LaunchDaemons/${SUPER_LAUNCH_DAEMON_LABEL}.plist" ]]; then
log_super "Installation: Removing previous super LaunchDaemon: /Library/LaunchDaemons/${SUPER_LAUNCH_DAEMON_LABEL}.plist"
launchctl bootout system "/Library/LaunchDaemons/${SUPER_LAUNCH_DAEMON_LABEL}.plist" 2>/dev/null
rm -f "/Library/LaunchDaemons/${SUPER_LAUNCH_DAEMON_LABEL}.plist" 2>/dev/null
fi
log_super "Installation: Creating super LaunchDaemon: /Library/LaunchDaemons/${SUPER_LAUNCH_DAEMON_LABEL}.plist."
/bin/cat <"/Library/LaunchDaemons/${SUPER_LAUNCH_DAEMON_LABEL}.plist"
Label
${SUPER_LAUNCH_DAEMON_LABEL}
ProgramArguments
${SUPER_FOLDER}/super-starter
UserName
root
AbandonProcessGroup
RunAtLoad
StartInterval
60
EOLDL
log_super "Installation: Setting permissions for installed super items."
chown -R root:wheel "${SUPER_FOLDER}"
chmod -R a+r "${SUPER_FOLDER}"
chmod -R go-w "${SUPER_FOLDER}"
chmod a+x "${SUPER_FOLDER}/super"
chmod a+x "${SUPER_FOLDER}/super-starter"
chown root:wheel "${SUPER_LINK}"
chmod a+rx "${SUPER_LINK}"
chmod go-w "${SUPER_LINK}"
chmod 644 "/Library/LaunchDaemons/${SUPER_LAUNCH_DAEMON_LABEL}.plist"
chown root:wheel "/Library/LaunchDaemons/${SUPER_LAUNCH_DAEMON_LABEL}.plist"
defaults write "${SUPER_LOCAL_PLIST}" SuperVersion -string "${SUPER_VERSION}"
}
# Download and install the IBM Notifier.app.
get_ibm_notifier() {
log_super "Status: Attempting to download and install IBM Notifier.app..."
local previous_umask
previous_umask=$(umask)
umask 077
local temp_file
temp_file="$(mktemp).zip"
local download_response
download_response=$(curl --location "${IBM_NOTIFIER_DOWNLOAD_URL}" --output "${temp_file}" 2>&1)
if [[ -f "${temp_file}" ]]; then
local unzip_response
unzip_response=$(unzip "${temp_file}" -d "${SUPER_FOLDER}/" 2>&1)
if [[ -d "${IBM_NOTIFIER_APP}" ]]; then
[[ -d "${SUPER_FOLDER}/__MACOSX" ]] && rm -Rf "${SUPER_FOLDER}/__MACOSX" >/dev/null 2>&1
chmod -R a+rx "${IBM_NOTIFIER_APP}"
else
log_super "Error: Unable to install IBM Notifier.app."
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: unzip_response is:\n${unzip_response}"
fi
else
log_super "Error: Unable to download IBM Notifier.app from: ${IBM_NOTIFIER_DOWNLOAD_URL}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: download_response is:\n${download_response}"
fi
rm -Rf "${temp_file}" >/dev/null 2>&1
umask "${previous_umask}"
}
# Check the IBM Notifier.app for validity.
check_ibm_notifier() {
ibm_notifier_valid="FALSE"
local codesign_response
codesign_response=$(codesign --verify --verbose "${IBM_NOTIFIER_APP}" 2>&1)
if [[ $(echo "${codesign_response}" | grep -c 'valid on disk') -gt 0 ]]; then
local version_response
version_response=$(defaults read "${IBM_NOTIFIER_APP}/Contents/Info.plist" CFBundleShortVersionString)
if [[ "${IBM_NOTIFIER_TARGET_VERSION}" == "${version_response}" ]]; then
ibm_notifier_valid="TRUE"
else
log_super "Warning: IBM Notifier at path: ${IBM_NOTIFIER_APP} is version ${version_response}, this does not match target version ${IBM_NOTIFIER_TARGET_VERSION}."
fi
else
log_super "Warning: unable validate signature for IBM Notifier at path: ${IBM_NOTIFIER_APP}."
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: codesign_response is:\n${codesign_response}"
fi
}
# Locate and verify ${get_display_icon_path} and, if valid save, to ${get_display_icon_temp_file}.
get_display_icon() {
get_display_icon_error="FALSE"
local previous_umask
previous_umask=$(umask)
umask 066
local get_response
get_display_icon_temp_file=$(mktemp)
if [[ "${get_display_icon_path}" =~ ${REGEX_HTML_URL} ]]; then
log_super "Status: Attempting to download new requested display icon from URL source..."
get_response=$(curl --location "${get_display_icon_path}" --output "${get_display_icon_temp_file}" 2>&1)
if [[ ! -f "${get_display_icon_temp_file}" ]]; then
log_super "Helper Error: Unable to download display icon file from: ${get_display_icon_path}"
get_display_icon_error="TRUE"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: get_response is:\n${get_response}"
fi
else # Local file copy.
log_super "Status: Attempting to copy new requested display icon from local path..."
get_response=$(cp "${get_display_icon_path}" "${get_display_icon_temp_file}" 2>&1)
if [[ ! -f "${get_display_icon_temp_file}" ]]; then
log_super "Helper Error: Unable to copy display icon file from: ${get_display_icon_path}"
get_display_icon_error="TRUE"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: get_response is:\n${get_response}"
fi
fi
if [[ "${get_display_icon_error}" == "FALSE" ]]; then
local sips_response
sips_response=$(sips --setProperty format png "${get_display_icon_temp_file}" --out "${get_display_icon_temp_file}.png" 2>&1)
if [[ ! -f "${get_display_icon_temp_file}.png" ]] || [[ $(echo "${sips_response}" | grep -c 'Warning') -gt 0 ]] || [[ $(echo "${sips_response}" | grep -c 'Error') -gt 0 ]]; then
log_super "Helper Error: Unable convert display icon file to PNG."
get_display_icon_error="TRUE"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: sips_response is:\n${sips_response}"
fi
fi
rm -Rf "${get_display_icon_temp_file}" >/dev/null 2>&1
umask "${previous_umask}"
unset get_display_icon_path
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: get_display_icon_error is: ${get_display_icon_error}"
}
# Download and install mist-cli.
get_mist_cli() {
log_super "Status: Attempting to download and install mist-cli..."
local previous_umask
previous_umask=$(umask)
umask 077
local temp_file
temp_file="$(mktemp).pkg"
local download_response
download_response=$(curl --location "${MIST_CLI_DOWNLOAD_URL}" --output "${temp_file}" 2>&1)
if [[ -f "${temp_file}" ]]; then
local install_response
install_response=$(installer -verboseR -pkg "${temp_file}" -target / 2>&1)
if ! { [[ $(echo "${install_response}" | grep -c 'The software was successfully installed.') -gt 0 ]] || [[ $(echo "${install_response}" | grep -c 'The install was successful.') -gt 0 ]]; }; then
log_super "Error: Unable to install mist-cli."
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: install_response is:\n${install_response}"
fi
else
log_super "Error: Unable to download mist-cli.pkg from: ${MIST_CLI_DOWNLOAD_URL}."
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: download_response is:\n${download_response}"
fi
rm -Rf "${temp_file}" >/dev/null 2>&1
umask "${previous_umask}"
}
# Check mist-cli for validity.
check_mist_cli() {
mist_cli_valid="FALSE"
local codesign_response
codesign_response=$(codesign --verify --verbose "${MIST_CLI_BINARY}" 2>&1)
if [[ $(echo "${codesign_response}" | grep -c 'valid on disk') -gt 0 ]]; then
local version_response
version_response=$("${MIST_CLI_BINARY}" --version | head -1 | awk '{print $1;}')
if [[ "${MIST_CLI_TARGET_VERSION}" == "${version_response}" ]]; then
mist_cli_valid="TRUE"
else
log_super "Warning: mist-cli at path: ${MIST_CLI_BINARY} is version ${version_response}, this does not match target version ${MIST_CLI_TARGET_VERSION}."
fi
else
log_super "Warning: unable validate signature for mist-cli at path: ${MIST_CLI_BINARY}."
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: codesign_response is:\n${codesign_response}"
fi
}
# Install and validate helper items that may be used by super.
manage_helpers() {
helper_error="FALSE"
# Validate the IBM Notifier.app, if missing or invalid then install and check again.
if [[ ! -d "${IBM_NOTIFIER_APP}" ]]; then
get_ibm_notifier
[[ -d "${IBM_NOTIFIER_APP}" ]] && check_ibm_notifier
[[ "${ibm_notifier_valid}" == "FALSE" ]] && log_super "Error: Unable to validate IBM Notifier.app after installation."
else # IBM Notifier.app is already installed.
check_ibm_notifier
if [[ "${ibm_notifier_valid}" == "FALSE" ]]; then
log_super "Status: Removing previously installed IBM Notifier.app."
rm -Rf "${IBM_NOTIFIER_APP}" >/dev/null 2>&1
[[ -d "${SUPER_FOLDER}/__MACOSX" ]] && rm -Rf "${SUPER_FOLDER}/__MACOSX" >/dev/null 2>&1
get_ibm_notifier
[[ -d "${IBM_NOTIFIER_APP}" ]] && check_ibm_notifier
fi
[[ "${ibm_notifier_valid}" == "FALSE" ]] && log_super "Error: Unable to validate IBM Notifier.app after re-installation."
fi
[[ "${ibm_notifier_valid}" == "FALSE" ]] && helper_error="TRUE"
# Set the ${display_icon_light} by validating the ${DISPLAY_ICON_LIGHT_FILE_CACHE}, if missing or changed, then get a new display icon light file or fail over to local ${DISPLAY_ICON_DEFAULT_FILE}.
local display_icon_light_file_cached_origin
display_icon_light_file_cached_origin=$(defaults read "${SUPER_LOCAL_PLIST}" DisplayIconLightFileCachedOrigin 2>/dev/null)
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: display_icon_light_file_cached_origin is: ${display_icon_light_file_cached_origin}"
if [[ -f "${DISPLAY_ICON_LIGHT_FILE_CACHE}" ]] && [[ "${display_icon_light_file_path}" == "${display_icon_light_file_cached_origin}" ]]; then
display_icon_light="${DISPLAY_ICON_LIGHT_FILE_CACHE}"
else # Need to get a new ${DISPLAY_ICON_LIGHT_FILE_CACHE} or fail over to local ${DISPLAY_ICON_DEFAULT_FILE}.
[[ -f "${DISPLAY_ICON_LIGHT_FILE_CACHE}" ]] && rm -f "${DISPLAY_ICON_LIGHT_FILE_CACHE}" 2>/dev/null
if [[ "${display_icon_light_file_path}" != "FALSE" ]]; then
defaults delete "${SUPER_LOCAL_PLIST}" DisplayIconLightFileCachedOrigin 2>/dev/null
get_display_icon_path="${display_icon_light_file_path}"
get_display_icon
if [[ "${get_display_icon_error}" == "FALSE" ]]; then
log_super "Status: Validated new diplay icon for light mode sourced from: ${display_icon_light_file_path}"
cp "${get_display_icon_temp_file}.png" "${DISPLAY_ICON_LIGHT_FILE_CACHE}" 2>/dev/null 2>&1
display_icon_light="${DISPLAY_ICON_LIGHT_FILE_CACHE}"
chmod a+r "${DISPLAY_ICON_LIGHT_FILE_CACHE}" 2>/dev/null 2>&1
rm -Rf "${get_display_icon_temp_file}.png" >/dev/null 2>&1
unset get_display_icon_temp_file
defaults write "${SUPER_LOCAL_PLIST}" DisplayIconLightFileCachedOrigin -string "${display_icon_light_file_path}"
else
log_super "Helper Warning: Unable to locate requested display icon for light mode, using default display icon: ${DISPLAY_ICON_DEFAULT_FILE}"
display_icon_light="${DISPLAY_ICON_DEFAULT_FILE}"
fi
else # Using failover ${DISPLAY_ICON_DEFAULT_FILE}.
if [[ "${display_icon_light_file_cached_origin}" == "DEFAULT" ]]; then
display_icon_light="${DISPLAY_ICON_DEFAULT_FILE}"
else
log_super "Status: No display icon for light mode specified, setting to default display icon: ${DISPLAY_ICON_DEFAULT_FILE}"
defaults write "${SUPER_LOCAL_PLIST}" DisplayIconLightFileCachedOrigin -string "DEFAULT"
display_icon_light="${DISPLAY_ICON_DEFAULT_FILE}"
fi
fi
fi
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: display_icon_light is: ${display_icon_light}"
# Set the ${display_icon_dark} by validating the ${DISPLAY_ICON_DARK_FILE_CACHE}, if missing or changed, then get a new display icon dark file or fail over to local ${DISPLAY_ICON_DEFAULT_FILE}.
local display_icon_dark_file_cached_origin
display_icon_dark_file_cached_origin=$(defaults read "${SUPER_LOCAL_PLIST}" DisplayIconDarkFileCachedOrigin 2>/dev/null)
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: display_icon_dark_file_cached_origin is: ${display_icon_dark_file_cached_origin}"
if [[ -f "${DISPLAY_ICON_DARK_FILE_CACHE}" ]] && [[ "${display_icon_dark_file_path}" == "${display_icon_dark_file_cached_origin}" ]]; then
display_icon_dark="${DISPLAY_ICON_DARK_FILE_CACHE}"
else # Need to get a new ${DISPLAY_ICON_DARK_FILE_CACHE} or fail over to local ${DISPLAY_ICON_DEFAULT_FILE}.
[[ -f "${DISPLAY_ICON_DARK_FILE_CACHE}" ]] && rm -f "${DISPLAY_ICON_DARK_FILE_CACHE}" 2>/dev/null
if [[ "${display_icon_dark_file_path}" != "FALSE" ]]; then
defaults delete "${SUPER_LOCAL_PLIST}" DisplayIconDarkFileCachedOrigin 2>/dev/null
get_display_icon_path="${display_icon_dark_file_path}"
get_display_icon
if [[ "${get_display_icon_error}" == "FALSE" ]]; then
log_super "Status: Validated new diplay icon for dark mode sourced from: ${display_icon_dark_file_path}"
cp "${get_display_icon_temp_file}.png" "${DISPLAY_ICON_DARK_FILE_CACHE}" 2>/dev/null 2>&1
display_icon_dark="${DISPLAY_ICON_DARK_FILE_CACHE}"
chmod a+r "${DISPLAY_ICON_DARK_FILE_CACHE}" 2>/dev/null 2>&1
rm -Rf "${get_display_icon_temp_file}.png" >/dev/null 2>&1
unset get_display_icon_temp_file
defaults write "${SUPER_LOCAL_PLIST}" DisplayIconDarkFileCachedOrigin -string "${display_icon_dark_file_path}"
else
log_super "Helper Warning: Unable to locate requested display icon for dark mode, using default display icon: ${DISPLAY_ICON_DEFAULT_FILE}"
display_icon_dark="${DISPLAY_ICON_DEFAULT_FILE}"
fi
else # Using failover ${DISPLAY_ICON_DEFAULT_FILE}.
if [[ "${display_icon_dark_file_cached_origin}" == "DEFAULT" ]]; then
display_icon_dark="${DISPLAY_ICON_DEFAULT_FILE}"
else
log_super "Status: No display icon for dark mode specified, setting to default display icon: ${DISPLAY_ICON_DEFAULT_FILE}"
defaults write "${SUPER_LOCAL_PLIST}" DisplayIconDarkFileCachedOrigin -string "DEFAULT"
display_icon_dark="${DISPLAY_ICON_DEFAULT_FILE}"
fi
fi
fi
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: display_icon_dark is: ${display_icon_dark}"
# If needed, validate mist-cli and if missing or invalid then install and check again.
if [[ "${install_macos_major_upgrades}" == "TRUE" ]] && { [[ "${macos_version_major}" -lt 13 ]] || [[ -n "${install_macos_major_upgrades_target}" ]]; }; then
if [[ ! -f "${MIST_CLI_BINARY}" ]]; then
get_mist_cli
[[ -f "${MIST_CLI_BINARY}" ]] && check_mist_cli
[[ "${mist_cli_valid}" == "FALSE" ]] && log_super "Error: Unable to validate mist-cli after installation."
else
check_mist_cli
if [[ "${mist_cli_valid}" == "FALSE" ]]; then
log_super "Status: Removing previously installed mist-cli."
rm -Rf "${MIST_CLI_BINARY}" >/dev/null 2>&1
get_mist_cli
[[ -f "${MIST_CLI_BINARY}" ]] && check_mist_cli
fi
[[ "${mist_cli_valid}" == "FALSE" ]] && log_super "Error: Unable to validate mist-cli after re-installation."
fi
[[ "${mist_cli_valid}" == "FALSE" ]] && helper_error="TRUE"
fi
}
# Prepare super by cleaning after previous super runs, record various maintenance modes, validate parameters, and if necessary restart via the super LaunchDaemon.
workflow_startup() {
# Make sure super is running as root.
if [[ $(id -u) -ne 0 ]]; then
log_echo "Exit: super must run with root privileges."
exit 1
fi
# Make sure macOS meets the minimum requirement of macOS 11.
macos_version_major=$(sw_vers -productVersion | cut -d'.' -f1) # Expected output: 10, 11, 12
if [[ "${macos_version_major}" -lt 11 ]]; then
if [[ -d "${SUPER_FOLDER}" ]]; then
log_super "Exit: This computer is running macOS ${macos_version_major} and super requires macOS 11 Big Sur or newer."
exit_error
else # super is not installed yet.
log_echo "Exit: This computer is running macOS ${macos_version_major} and super requires macOS 11 Big Sur or newer."
exit 1
fi
fi
# Check for any previous super processes and kill them.
killall -9 "softwareupdate" "mist" >/dev/null 2>&1
killall -9 "IBM Notifier" "IBM Notifier Popup" >/dev/null 2>&1
local super_previous_pid
super_previous_pid=$(pgrep -F "${SUPER_PID_FILE}" 2>/dev/null)
if [[ -n "${super_previous_pid}" ]]; then
[[ -d "${SUPER_LOG_FOLDER}" ]] && log_super "Status: Found previous super instance running with PID ${super_previous_pid}, killing processes..."
[[ ! -d "${SUPER_LOG_FOLDER}" ]] && log_echo "Status: Found previous super instance running with PID ${super_previous_pid}, killing processes..."
kill -9 "${super_previous_pid}" >/dev/null 2>&1
fi
# Create new ${SUPER_PID_FILE} for this instance of super.
echo $$ >"${SUPER_PID_FILE}"
# If super crashes or the system restarts unexpectedly before super exits, then automatically launch again.
/usr/libexec/PlistBuddy -c "Delete :NextAutoLaunch" "${SUPER_LOCAL_PLIST}.plist" 2> /dev/null
# Check for super installation.
local super_current_folder
super_current_folder=$(dirname "$0")
! { [[ "${super_current_folder}" == "${SUPER_FOLDER}" ]] || [[ "${super_current_folder}" == $(dirname "${SUPER_LINK}") ]]; } && workflow_installation
# Check for logs that need to be archived.
archive_logs
# After installation is verified, the startup workflow can begin.
log_super "**** S.U.P.E.R.M.A.N. ${SUPER_VERSION} - SUPER STARTUP ****"
log_status "Running: Startup workflow."
# Manage the ${verbose_mode_option} and if enabled start additional logging.
[[ "${reset_super_option}" == "TRUE" ]] && defaults delete "${SUPER_LOCAL_PLIST}" VerboseMode 2>/dev/null
if [[ -f "${SUPER_MANAGED_PLIST}.plist" ]]; then
local verbose_mode_managed
verbose_mode_managed=$(defaults read "${SUPER_MANAGED_PLIST}" VerboseMode 2>/dev/null)
fi
if [[ -f "${SUPER_LOCAL_PLIST}.plist" ]]; then
local verbose_mode_local
verbose_mode_local=$(defaults read "${SUPER_LOCAL_PLIST}" VerboseMode 2>/dev/null)
fi
[[ -n "${verbose_mode_managed}" ]] && verbose_mode_option="${verbose_mode_managed}"
{ [[ -z "${verbose_mode_managed}" ]] && [[ -z "${verbose_mode_option}" ]] && [[ -n "${verbose_mode_local}" ]]; } && verbose_mode_option="${verbose_mode_local}"
if [[ "${verbose_mode_option}" -eq 1 ]] || [[ "${verbose_mode_option}" == "TRUE" ]]; then
verbose_mode_option="TRUE"
defaults write "${SUPER_LOCAL_PLIST}" VerboseMode -bool true
else
verbose_mode_option="FALSE"
defaults delete "${SUPER_LOCAL_PLIST}" VerboseMode 2>/dev/null
fi
if [[ "${verbose_mode_option}" == "TRUE" ]]; then
log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: Verbose mode enabled."
log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: super_current_folder is: ${super_current_folder}"
log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: Uptime is: $(uptime)"
fi
# In case super is running at system startup, wait for the loginwindow process before continuing.
local startup_timeout
startup_timeout=0
while [[ ! $(pgrep "loginwindow") ]] && [[ "${startup_timeout}" -lt 600 ]]; do
log_super "Status: Waiting for macOS startup to complete..."
sleep 10
startup_timeout=$((startup_timeout + 10))
done
# Detailed system and user checks.
check_system
check_current_user
check_msu_settings
# Workflow for for ${open_logs_option}.
if [[ "${open_logs_option}" == "TRUE" ]]; then
if [[ "${current_user_account_name}" != "FALSE" ]]; then
log_super "Status: Opening logs for current user: ${current_user_account_name}."
if [[ "${mac_cpu_architecture}" == "arm64" ]]; then
touch "${MDM_WORKFLOW_LOG}" "${MDM_WORKFLOW_DEBUG_LOG}" "${MDM_COMMAND_LOG}" "${MDM_COMMAND_DEBUG_LOG}"
sudo -u "${current_user_account_name}" open "${MDM_WORKFLOW_LOG}"
sudo -u "${current_user_account_name}" open "${MDM_WORKFLOW_DEBUG_LOG}"
sudo -u "${current_user_account_name}" open "${MDM_COMMAND_LOG}"
sudo -u "${current_user_account_name}" open "${MDM_COMMAND_DEBUG_LOG}"
fi
touch "${INSTALLER_WORKFLOW_LOG}" "${MSU_WORKFLOW_LOG}" "${MACOS_INSTALLERS_LIST_LOG}" "${MDMCLIENT_LIST_LOG}" "${MSU_LIST_LOG}" "${SUPER_LOG}"
sudo -u "${current_user_account_name}" open "${INSTALLER_WORKFLOW_LOG}"
sudo -u "${current_user_account_name}" open "${MSU_WORKFLOW_LOG}"
sudo -u "${current_user_account_name}" open "${MACOS_INSTALLERS_LIST_LOG}"
sudo -u "${current_user_account_name}" open "${MDMCLIENT_LIST_LOG}"
sudo -u "${current_user_account_name}" open "${MSU_LIST_LOG}"
sudo -u "${current_user_account_name}" open "${SUPER_LOG}"
else # No current GUI user.
log_super "Warning: Can't open logs because there is currently no local user logged into the GUI."
fi
fi
# Initial Parameter and helper validation, if any of these fail then it's unsafe for the workflow to continue.
get_preferences
check_jamf_management_framework
manage_parameter_options
manage_helpers
if [[ "${check_error}" == "TRUE" ]] || [[ "${option_error}" == "TRUE" ]] || [[ "${helper_error}" == "TRUE" ]]; then
log_super "Exit: Initial startup validation failed."
log_status "Inactive Error: Initial startup validation failed."
exit_error
fi
# Initial preparation for various workflow modes. This enforces a hierarchy of workflows as follows: Restart Validate > Install Now > Scheduled Installation > Only Download > Default Workflow.
[[ "${test_mode_option}" == "TRUE" ]] && log_super "Status: Test mode active with ${test_mode_timeout_seconds} second timeout."
local workflow_restart_validate_local
workflow_restart_validate_local=$(defaults read "${SUPER_LOCAL_PLIST}" WorkflowRestartValidate 2>/dev/null)
local workflow_scheduled_install_local
workflow_scheduled_install_local=$(defaults read "${SUPER_LOCAL_PLIST}" WorkflowScheduledInstall 2>/dev/null)
local workflow_scheduled_install_local
workflow_reset_super_after_completion_local=$(defaults read "${SUPER_LOCAL_PLIST}" WorkflowResetSuperAfterCompletion 2>/dev/null)
if [[ "${workflow_restart_validate_local}" -eq 1 ]]; then
log_super "Status: Restart validation workflow active."
workflow_restart_validate_active="TRUE"
elif [[ "${workflow_install_now_option}" == "TRUE" ]]; then
log_super "Status: Install now workflow active."
workflow_install_now_active="TRUE"
[[ $(echo "${display_unmovable_option}" | grep -c 'INSTALLNOW') -gt 0 ]] && display_unmovable_status="TRUE"
[[ $(echo "${display_hide_background_option}" | grep -c 'INSTALLNOW') -gt 0 ]] && display_hide_background_status="TRUE"
[[ $(echo "${display_silently_option}" | grep -c 'INSTALLNOW') -gt 0 ]] && display_silently_status="TRUE"
[[ $(echo "${display_notifications_centered_option}" | grep -c 'INSTALLNOW') -gt 0 ]] && display_notifications_centered_status="TRUE"
[[ $(echo "${display_hide_progress_bar_option}" | grep -c 'INSTALLNOW') -gt 0 ]] && display_hide_progress_bar_status="TRUE"
if [[ "${current_user_account_name}" != "FALSE" ]]; then
notification_install_now_start
if [[ "${test_mode_option}" == "TRUE" ]]; then
log_super "Test Mode: Pausing ${test_mode_timeout_seconds} seconds for the install now start notification..."
sleep "${test_mode_timeout_seconds}"
fi
fi
if [[ -n "${workflow_scheduled_install_local}" ]]; then
log_super "Warning: Removing previously scheduled installation workflow due to current install now workflow."
defaults delete "${SUPER_LOCAL_PLIST}" WorkflowScheduledInstall 2>/dev/null
fi
elif [[ "${workflow_scheduled_install_local}" =~ ${REGEX_DATE_HOURS_MINUTES} ]]; then
workflow_scheduled_install="${workflow_scheduled_install_local}"
log_super "Status: Previously scheduled installation workflow active for: ${workflow_scheduled_install}"
workflow_scheduled_install_active="TRUE"
elif [[ "${workflow_only_download_option}" == "TRUE" ]]; then
log_super "Status: Only download workflow active."
workflow_only_download_active="TRUE"
fi
{ [[ "${workflow_only_download_active}" != "TRUE" ]] && [[ -n "${install_jamf_policy_triggers_option}" ]]; } && log_super "Status: Jamf Pro Policy triggers: ${install_jamf_policy_triggers_option}"
[[ "${install_jamf_policy_triggers_without_restarting_option}" == "TRUE" ]] && log_super "Warning: Install Jamf Pro Policy Triggers without restarting is enabled, this computer will run Jamf Pro Policies even if there is no macOS update or upgrade available."
[[ "${workflow_restart_without_updates_option}" == "TRUE" ]] && log_super "Warning: Restart without updates workflow is enabled, this computer will restart even if there is no macOS update or upgrade available."
if [[ "${workflow_reset_super_after_completion_active}" == "TRUE" ]] || [[ "${workflow_reset_super_after_completion_local}" -eq 1 ]]; then
log_super "Status: Workflow reset super after completion is active. All local (non-managed and non-authentication) preferences will be deleted after workflow completion."
workflow_reset_super_after_completion_active="TRUE"
defaults write "${SUPER_LOCAL_PLIST}" WorkflowResetSuperAfterCompletion -bool true
fi
[[ "${workflow_restart_validate_active}" != "TRUE" ]] && defaults write "${SUPER_LOCAL_PLIST}" MacLastStartup -string "${mac_last_startup}"
# Apple silicon authentication and workflow validations.
[[ "${mac_cpu_architecture}" == "arm64" ]] && manage_authentication_options
manage_workflow_options
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: Local preference file after startup validation: ${SUPER_LOCAL_PLIST}:\n$(defaults read "${SUPER_LOCAL_PLIST}" 2>/dev/null)"
# If super is running via Jamf, then restart via LaunchDaemon to release the jamf parent process.
if [[ "${parent_process_is_jamf}" == "TRUE" ]]; then
log_super "Status: Found that Jamf is installing or is the parent process, restarting via LaunchDaemon..."
restart_super_sleep_seconds=5
restart_super
fi
# If super is running from outside the ${SUPER_FOLDER}, then restart via LaunchDaemon to release any parent installer process.
if ! { [[ "${super_current_folder}" == "${SUPER_FOLDER}" ]] || [[ "${super_current_folder}" == $(dirname "${SUPER_LINK}") ]]; }; then
log_super "Status: Found that super is installing, restarting via LaunchDaemon..."
restart_super_sleep_seconds=5
restart_super
fi
# Handle the ${workflow_auth_error} condition, which is a workflow-stopper unless the restart validate or only download workflows are active.
if [[ "${workflow_auth_error}" == "TRUE" ]] && [[ "${workflow_restart_validate_active}" != "TRUE" ]] && [[ "${workflow_only_download_active}" != "TRUE" ]]; then
if [[ "${workflow_install_now_active}" == "TRUE" ]]; then
log_super "Error: Configured authentication workflow can not currently install macOS updates/upgrades, install now workflow can not continue."
log_status "Inactive Error: Configured authentication workflow can not currently install macOS updates/upgrades, install now workflow can not continue."
[[ "${current_user_account_name}" != "FALSE" ]] && notification_install_now_failed
else
deferral_timer_minutes="${deferral_timer_error_minutes}"
log_super "Workflow Error: Configured authentication workflow is not currently possible, trying again in ${deferral_timer_minutes} minutes."
log_status "Pending: Configured authentication workflow is not currently possible, trying again in ${deferral_timer_minutes} minutes."
set_auto_launch_deferral
fi
fi
# Wait for a valid network connection. If there is still no network after two minutes, an automatic deferral is started.
local network_timeout
network_timeout=0
while [[ $(ifconfig -a inet 2>/dev/null | sed -n -e '/127.0.0.1/d' -e '/0.0.0.0/d' -e '/inet/p' | wc -l) -le 0 ]] && [[ "${network_timeout}" -lt 120 ]]; do
log_super "Status: Waiting for network..."
sleep 5
network_timeout=$((network_timeout + 5))
done
if [[ $(ifconfig -a inet 2>/dev/null | sed -n -e '/127.0.0.1/d' -e '/0.0.0.0/d' -e '/inet/p' | wc -l) -le 0 ]]; then
if [[ "${workflow_install_now_active}" == "TRUE" ]]; then
log_super "Error: Network unavailable, install now workflow can not continue."
log_status "Inactive Error: Network unavailable, install now workflow can not continue."
[[ "${current_user_account_name}" != "FALSE" ]] && notification_install_now_failed
exit_error
else
deferral_timer_minutes="${deferral_timer_error_minutes}"
log_super "Error: Network unavailable, trying again in ${deferral_timer_minutes} minutes."
log_status "Pending: Network unavailable, trying again in ${deferral_timer_minutes} minutes."
set_auto_launch_deferral
fi
fi
}
# MARK: *** Process Management ***
################################################################################
# This function is only used for debugging from the command line to interrupt the workflow and wait for the user to press Enter to continue. Insert the following line wherever you want an interrupt to occur:
# [[ "${current_user_account_name}" != "FALSE" ]] && interactive_interrupt
interactive_interrupt() {
# shellcheck disable=SC2317
log_super "**** S.U.P.E.R.M.A.N. ${SUPER_VERSION} - INTERACTIVE INTERRUPT - PRESS ENTER TO CONTINUE ****"
# shellcheck disable=SC2317
read -n 1 -p -r >/dev/null 2>&1
}
# Restart super via the LaunchDaemon after waiting for ${restart_super_sleep_seconds} seconds.
restart_super() {
/usr/libexec/PlistBuddy -c "Delete :NextAutoLaunch" "${SUPER_LOCAL_PLIST}.plist" 2> /dev/null
{
sleep $restart_super_sleep_seconds
launchctl bootout system "/Library/LaunchDaemons/${SUPER_LAUNCH_DAEMON_LABEL}.plist" >/dev/null 2>&1
launchctl bootstrap system "/Library/LaunchDaemons/${SUPER_LAUNCH_DAEMON_LABEL}.plist" >/dev/null 2>&1
} &
disown -a
[[ -n "${jamf_access_token}" ]] && delete_jamf_api_access_token
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: Local preference file at restart exit: ${SUPER_LOCAL_PLIST}:\n$(defaults read "${SUPER_LOCAL_PLIST}" 2>/dev/null)"
log_super "**** S.U.P.E.R.M.A.N. ${SUPER_VERSION} - EXIT AND RESTART WORKFLOW ****"
rm -f "${SUPER_PID_FILE}" 2>/dev/null
exit 0
}
# Configure super to automatically launch ${deferral_timer_minutes} from now by setting the NextAutoLaunch attribute in the ${SUPER_LOCAL_PLIST}.
set_auto_launch_deferral() {
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: deferral_timer_minutes is: ${deferral_timer_minutes}"
local deferral_timer_epoch
deferral_timer_epoch=$(($(date +%s) + (deferral_timer_minutes * 60)))
local deferral_timer_year
deferral_timer_year=$(date -j -f %s "${deferral_timer_epoch}" +%Y | xargs)
local deferral_timer_month
deferral_timer_month=$(date -j -f %s "${deferral_timer_epoch}" +%m | xargs)
local deferral_timer_day
deferral_timer_day=$(date -j -f %s "${deferral_timer_epoch}" +%d | xargs)
local deferral_timer_hour
deferral_timer_hour=$(date -j -f %s "${deferral_timer_epoch}" +%H | xargs)
local deferral_timer_minute
deferral_timer_minute=$(date -j -f %s "${deferral_timer_epoch}" +%M | xargs)
local next_auto_launch
next_auto_launch="${deferral_timer_year}-${deferral_timer_month}-${deferral_timer_day}:${deferral_timer_hour}:${deferral_timer_minute}:00"
/usr/libexec/PlistBuddy -c "Add :NextAutoLaunch string ${next_auto_launch}" "${SUPER_LOCAL_PLIST}.plist" 2> /dev/null
log_super "Exit: super is scheduled to automatically relaunch at: ${next_auto_launch}"
exit_clean
}
# This function is used when the super workflow exits with no errors.
exit_clean() {
[[ -n "${jamf_access_token}" ]] && delete_jamf_api_access_token
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: Local preference file at clean exit: ${SUPER_LOCAL_PLIST}:\n$(defaults read "${SUPER_LOCAL_PLIST}" 2>/dev/null)"
log_super "**** S.U.P.E.R.M.A.N. ${SUPER_VERSION} - EXIT CLEAN ****"
rm -f "${SUPER_PID_FILE}" 2>/dev/null
exit 0
}
# This function is used when the super workflow must exit due to an unrecoverable error.
exit_error() {
[[ -n "${jamf_access_token}" ]] && delete_jamf_api_access_token
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: Local preference file at error exit: ${SUPER_LOCAL_PLIST}:\n$(defaults read "${SUPER_LOCAL_PLIST}" 2>/dev/null)"
log_super "**** S.U.P.E.R.M.A.N. ${SUPER_VERSION} - EXIT ERROR ****"
rm -f "${SUPER_PID_FILE}" 2>/dev/null
exit 1
}
# MARK: *** Logging ***
################################################################################
# Append input to the command line and log located at ${SUPER_LOG}.
log_super() {
echo -e "$(date +"%a %b %d %T") $(hostname -s) $(basename "$0")[$$]: $*" | tee -a "${SUPER_LOG}"
}
# Send input to the command line only, so as not to save secrets to the ${SUPER_LOG}.
log_echo() {
echo -e "$(date +"%a %b %d %T") $(hostname -s) $(basename "$0")[$$]: Not Logged: $*"
}
# Send input to the command line only replacing the current line, so as not to save save interactive progress updates to the ${SUPER_LOG}.
log_echo_replace_line() {
echo -ne "$(date +"%a %b %d %T") $(hostname -s) $(basename "$0")[$$]: Not Logged: $*\r"
}
# Append input to a log located at ${MSU_WORKFLOW_LOG}.
log_msu() {
echo -e "\n$(date +"%a %b %d %T") $(hostname -s) $(basename "$0")[$$]: $*" >>"${MSU_WORKFLOW_LOG}"
}
# Append input to a log located at ${INSTALLER_WORKFLOW_LOG}.
log_installer() {
echo -e "$(date +"%a %b %d %T") $(hostname -s) $(basename "$0")[$$]: $*" >>"${INSTALLER_WORKFLOW_LOG}"
}
# Append input to a log located at ${MDM_COMMAND_LOG}.
log_mdm_command() {
echo -e "$(date +"%a %b %d %T") $(hostname -s) $(basename "$0")[$$]: $*" >>"${MDM_COMMAND_LOG}"
}
# Append input to a log located at ${MDM_WORKFLOW_LOG}.
log_mdm_workflow() {
echo -e "$(date +"%a %b %d %T") $(hostname -s) $(basename "$0")[$$]: $*" >>"${MDM_WORKFLOW_LOG}"
}
# Update the SuperStatus key in the ${SUPER_LOCAL_PLIST}.
log_status() {
defaults write "${SUPER_LOCAL_PLIST}" SuperStatus -string "$(date +"%a %b %d %T"): $*"
}
# Archive all legacy log files to the ${SUPER_LOG_ARCHIVE_FOLDER}, and if needed, archive larger current logs.
archive_logs() {
# First, archive any legacy super logs.
if [[ -f "${SUPER_FOLDER}/super.log" ]]; then
local log_archive_legacy_name
log_archive_legacy_name=$(date +%Y-%m-%d.%H-%M-%S.legacy)
log_super "Status: Archiving legacy super logs to: ${SUPER_LOG_ARCHIVE_FOLDER}/${log_archive_legacy_name}.zip"
mkdir -p "${SUPER_LOG_ARCHIVE_FOLDER}/${log_archive_legacy_name}"
mv "${SUPER_FOLDER}/super.log" "${SUPER_LOG_ARCHIVE_FOLDER}/${log_archive_legacy_name}/super.log"
[[ -f "${SUPER_FOLDER}/asuList.log" ]] && rm -f "${SUPER_FOLDER}/asuList.log" 2>/dev/null
[[ -f "${SUPER_FOLDER}/installerList.log" ]] && rm -f "${SUPER_FOLDER}/installerList.log" 2>/dev/null
[[ -f "${SUPER_FOLDER}/asu.log" ]] && mv "${SUPER_FOLDER}/asu.log" "${SUPER_LOG_ARCHIVE_FOLDER}/${log_archive_legacy_name}/asu.log"
[[ -f "${SUPER_FOLDER}/installer.log" ]] && mv "${SUPER_FOLDER}/installer.log" "${SUPER_LOG_ARCHIVE_FOLDER}/${log_archive_legacy_name}/installer.log"
[[ -f "${SUPER_FOLDER}/mdmCommand.log" ]] && mv "${SUPER_FOLDER}/mdmCommand.log" "${SUPER_LOG_ARCHIVE_FOLDER}/${log_archive_legacy_name}/mdmCommand.log"
[[ -f "${SUPER_FOLDER}/mdmCommandDebug.log" ]] && mv "${SUPER_FOLDER}/mdmCommandDebug.log" "${SUPER_LOG_ARCHIVE_FOLDER}/${log_archive_legacy_name}/mdmCommandDebug.log"
[[ -f "${SUPER_FOLDER}/mdmWorkflow.log" ]] && mv "${SUPER_FOLDER}/mdmWorkflow.log" "${SUPER_LOG_ARCHIVE_FOLDER}/${log_archive_legacy_name}/mdmWorkflow.log"
[[ -f "${SUPER_FOLDER}/mdmWorkflowDebug.log" ]] && mv "${SUPER_FOLDER}/mdmWorkflowDebug.log" "${SUPER_LOG_ARCHIVE_FOLDER}/${log_archive_legacy_name}/mdmWorkflowDebug.log"
zip -r -j "${SUPER_LOG_ARCHIVE_FOLDER}/${log_archive_legacy_name}.zip" "${SUPER_LOG_ARCHIVE_FOLDER}/${log_archive_legacy_name}" >/dev/null 2>&1
rm -rf "${SUPER_LOG_ARCHIVE_FOLDER:?}/${log_archive_legacy_name}" 2>/dev/null
chown -R root:wheel "${SUPER_LOG_ARCHIVE_FOLDER}"
chmod -R a+r "${SUPER_LOG_ARCHIVE_FOLDER}"
fi
# Check to see if any log file is larger than $SUPER_LOG_ARCHIVE_SIZE.
local archive_logs_needed
archive_logs_needed="FALSE"
[[ $(ls -l "${SUPER_LOG}" 2>/dev/null | awk '{print int($5/1000)}') -gt $SUPER_LOG_ARCHIVE_SIZE ]] && archive_logs_needed="TRUE"
[[ $(ls -l "${MSU_WORKFLOW_LOG}" 2>/dev/null | awk '{print int($5/1000)}') -gt $SUPER_LOG_ARCHIVE_SIZE ]] && archive_logs_needed="TRUE"
[[ $(ls -l "${INSTALLER_WORKFLOW_LOG}" 2>/dev/null | awk '{print int($5/1000)}') -gt $SUPER_LOG_ARCHIVE_SIZE ]] && archive_logs_needed="TRUE"
[[ $(ls -l "${MDM_COMMAND_LOG}" 2>/dev/null | awk '{print int($5/1000)}') -gt $SUPER_LOG_ARCHIVE_SIZE ]] && archive_logs_needed="TRUE"
[[ $(ls -l "${MDM_COMMAND_DEBUG_LOG}" 2>/dev/null | awk '{print int($5/1000)}') -gt $SUPER_LOG_ARCHIVE_SIZE ]] && archive_logs_needed="TRUE"
[[ $(ls -l "${MDM_WORKFLOW_LOG}" 2>/dev/null | awk '{print int($5/1000)}') -gt $SUPER_LOG_ARCHIVE_SIZE ]] && archive_logs_needed="TRUE"
[[ $(ls -l "${MDM_WORKFLOW_DEBUG_LOG}" 2>/dev/null | awk '{print int($5/1000)}') -gt $SUPER_LOG_ARCHIVE_SIZE ]] && archive_logs_needed="TRUE"
# A super log has become to large, archival is required.
if [[ "${archive_logs_needed}" == "TRUE" ]]; then
local log_archive_name
log_archive_name=$(date +%Y-%m-%d.%H-%M-%S)
log_super "Status: A super log is larger than ${SUPER_LOG_ARCHIVE_SIZE} KB, archiving super logs to: ${SUPER_LOG_ARCHIVE_FOLDER}/${log_archive_name}.zip"
log_super "**** S.U.P.E.R.M.A.N. ${SUPER_VERSION} - LOGS ARCHIVAL ****"
mkdir -p "${SUPER_LOG_ARCHIVE_FOLDER}/${log_archive_name}"
mv "${SUPER_LOG}" "${SUPER_LOG_ARCHIVE_FOLDER}/${log_archive_name}/$(basename ${SUPER_LOG})"
log_super "**** S.U.P.E.R.M.A.N. ${SUPER_VERSION} - LOGS ARCHIVAL ****"
log_super "Status: A super log was larger than ${SUPER_LOG_ARCHIVE_SIZE} KB, previous super logs archived to: ${SUPER_LOG_ARCHIVE_FOLDER}/${log_archive_name}.zip"
[[ -f "${MSU_WORKFLOW_LOG}" ]] && mv "${MSU_WORKFLOW_LOG}" "${SUPER_LOG_ARCHIVE_FOLDER}/${log_archive_name}/$(basename ${MSU_WORKFLOW_LOG})"
[[ -f "${INSTALLER_WORKFLOW_LOG}" ]] && mv "${INSTALLER_WORKFLOW_LOG}" "${SUPER_LOG_ARCHIVE_FOLDER}/${log_archive_name}/$(basename ${INSTALLER_WORKFLOW_LOG})"
[[ -f "${MDM_COMMAND_LOG}" ]] && mv "${MDM_COMMAND_LOG}" "${SUPER_LOG_ARCHIVE_FOLDER}/${log_archive_name}/$(basename ${MDM_COMMAND_LOG})"
[[ -f "${MDM_COMMAND_DEBUG_LOG}" ]] && mv "${MDM_COMMAND_DEBUG_LOG}" "${SUPER_LOG_ARCHIVE_FOLDER}/${log_archive_name}/$(basename ${MDM_COMMAND_DEBUG_LOG})"
[[ -f "${MDM_WORKFLOW_LOG}" ]] && mv "${MDM_WORKFLOW_LOG}" "${SUPER_LOG_ARCHIVE_FOLDER}/${log_archive_name}/$(basename ${MDM_WORKFLOW_LOG})"
[[ -f "${MDM_WORKFLOW_DEBUG_LOG}" ]] && mv "${MDM_WORKFLOW_DEBUG_LOG}" "${SUPER_LOG_ARCHIVE_FOLDER}/${log_archive_name}/$(basename ${MDM_WORKFLOW_DEBUG_LOG})"
zip -r -j "${SUPER_LOG_ARCHIVE_FOLDER}/${log_archive_name}.zip" "${SUPER_LOG_ARCHIVE_FOLDER}/${log_archive_name}" >/dev/null 2>&1
rm -rf "${SUPER_LOG_ARCHIVE_FOLDER:?}/${log_archive_name}" 2>/dev/null
chown -R root:wheel "${SUPER_LOG_ARCHIVE_FOLDER}"
chmod -R a+r "${SUPER_LOG_ARCHIVE_FOLDER}"
fi
# This is a fail-safe to remove any excessively large files from the ${SUPER_LOG_ARCHIVE_FOLDER}.
for log_archive_file in "${SUPER_LOG_ARCHIVE_FOLDER}"/*; do
if [[ $(ls -l "${log_archive_file}" 2>/dev/null | awk '{print int($5/1000)}') -gt $((SUPER_LOG_ARCHIVE_SIZE * 10)) ]]; then
log_super "Warning: A file in the super log archive folder was larger than $((SUPER_LOG_ARCHIVE_SIZE * 10)) KB, deleting file to save space: ${log_archive_file}"
rm -rf "${log_archive_file:?}" 2>/dev/null
fi
done
}
# MARK: *** Local System Validation ***
################################################################################
# Set ${current_user_account_name} to the currently logged in GUI user or "FALSE" if there is none or a system account.
# If the current user is a normal account then this also sets ${current_user_id}, ${current_user_guid}, ${current_user_real_name}, ${current_user_is_admin}, ${current_user_has_secure_token}, ${current_user_is_volume_owner}, and ${current_user_appearance_mode}.
check_current_user() {
[[ -z "${current_user_account_name}" ]] && current_user_account_name="FALSE"
[[ -z "${current_user_id}" ]] && current_user_id="FALSE"
local current_user_account_name_response
current_user_account_name_response=$(scutil <<<"show State:/Users/ConsoleUser" | awk '/Name :/ {$1=$2="";print $0;}' | xargs)
local current_user_id_response
current_user_id_response=$(id -u "${current_user_account_name_response}" 2>/dev/null)
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: current_user_account_name is: ${current_user_account_name}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: current_user_id is: ${current_user_id}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: current_user_account_name_response is: ${current_user_account_name_response}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: current_user_id_response is: ${current_user_id_response}"
# If this function was already run earlier then check to see if ${current_user_account_name} and ${current_user_id} are the same as before, if so then it's not necessary to continue this function.
if [[ "${current_user_account_name}" != "FALSE" ]] && [[ "${current_user_id}" != "FALSE" ]] && [[ "${current_user_account_name}" == "${current_user_account_name_response}" ]] && [[ "${current_user_id}" == "${current_user_id_response}" ]]; then
return 0
fi
# Make sure we have a "normal" logged in user.
if [[ -z "${current_user_account_name_response}" ]]; then
{ [[ $(id -u) -eq 0 ]] && [[ -d "${SUPER_LOG_FOLDER}" ]]; } && log_super "Status: No GUI user currently logged in."
{ [[ $(id -u) -ne 0 ]] || [[ ! -d "${SUPER_LOG_FOLDER}" ]]; } && log_echo "Status: No GUI user currently logged in."
elif [[ "${current_user_account_name_response}" == "root" ]] || [[ "${current_user_account_name_response}" == "_mbsetupuser" ]] || [[ "${current_user_account_name_response}" == "loginwindow" ]]; then
{ [[ $(id -u) -eq 0 ]] && [[ -d "${SUPER_LOG_FOLDER}" ]]; } && log_super "Status: Current GUI user is system account: ${current_user_account_name_response}"
{ [[ $(id -u) -ne 0 ]] || [[ ! -d "${SUPER_LOG_FOLDER}" ]]; } && log_echo "Status: Current GUI user is system account: ${current_user_account_name_response}"
else # Normal locally logged in user.
current_user_account_name="${current_user_account_name_response}"
current_user_id=$(id -u "${current_user_account_name}" 2>/dev/null)
{ [[ $(id -u) -eq 0 ]] && [[ -d "${SUPER_LOG_FOLDER}" ]]; } && log_super "Status: Current active GUI user is: ${current_user_account_name} (${current_user_id})"
{ [[ $(id -u) -ne 0 ]] || [[ ! -d "${SUPER_LOG_FOLDER}" ]]; } && log_echo "Status: Current active GUI user is: ${current_user_account_name} (${current_user_id})"
fi
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: current_user_account_name is: ${current_user_account_name}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: current_user_id is: ${current_user_id}"
# Only collect user details if it's a "normal" GUI user.
if [[ "${current_user_account_name}" != "FALSE" ]] && [[ "${current_user_id}" != "FALSE" ]] && [[ -d "${SUPER_LOG_FOLDER}" ]]; then
current_user_guid=$(dscl . read "/Users/${current_user_account_name}" GeneratedUID 2>/dev/null | awk '{print $2;}')
current_user_real_name=$(dscl . read "/Users/${current_user_account_name}" RealName 2>/dev/null | tail -1 | sed -e 's/^RealName: //g' -e 's/^ //g')
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: current_user_guid is: ${current_user_guid}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: current_user_real_name is: ${current_user_real_name}"
current_user_is_admin="FALSE"
current_user_has_secure_token="FALSE"
current_user_is_volume_owner="FALSE"
current_user_password_policies="FALSE"
current_user_system_password_policies="FALSE"
if [[ -n "${current_user_id}" ]] && [[ -n "${current_user_guid}" ]] && [[ -n "${current_user_real_name}" ]]; then
[[ $(groups "${current_user_account_name}" 2>/dev/null | grep -c 'admin') -gt 0 ]] && current_user_is_admin="TRUE"
[[ $(dscl . read "/Users/${current_user_account_name}" AuthenticationAuthority 2>/dev/null | grep -c 'SecureToken') -gt 0 ]] && current_user_has_secure_token="TRUE"
[[ $(diskutil apfs listcryptousers / 2>/dev/null | grep -c "${current_user_guid}") -gt 0 ]] && current_user_is_volume_owner="TRUE"
local current_user_appearance_mode_response
current_user_appearance_mode_response=$(sudo -u "${current_user_account_name}" osascript -l 'JavaScript' -e "ObjC.import('AppKit'); $.NSAppearance.currentDrawingAppearance.bestMatchFromAppearancesWithNames(['NSAppearanceNameAqua', 'NSAppearanceNameDarkAqua']).js;" 2>/dev/null)
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: current_user_appearance_mode_response is: ${current_user_appearance_mode_response}"
[[ "${current_user_appearance_mode_response}" == "NSAppearanceNameAqua" ]] && current_user_appearance_mode="LIGHT"
[[ "${current_user_appearance_mode_response}" == "NSAppearanceNameDarkAqua" ]] && current_user_appearance_mode="DARK"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: current_user_is_admin is: ${current_user_is_admin}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: current_user_has_secure_token is: ${current_user_has_secure_token}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: current_user_is_volume_owner is: ${current_user_is_volume_owner}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: current_user_appearance_mode is: ${current_user_appearance_mode}"
else
log_super "Parameter Error: Unable to determine account details for current user: ${current_user_account_name}"
option_error="TRUE"
fi
fi
}
# Collect parameters for detailed system information setting a variety of parameters including ${macos_version_minor}, ${macos_version_patch}, ${macos_version_number}, ${macos_version_extra}, ${macos_build}, ${macos_title}, ${macos_version_full}, ${mac_cpu_architecture}, ${mac_model_name}, ${mac_is_portable}, and ${mac_last_startup}.
check_system() {
check_error="FALSE"
macos_version_minor=$(sw_vers -productVersion | cut -d'.' -f2) # Expected output: 6, 1
macos_version_patch=$(sw_vers -productVersion | cut -d'.' -f3) # Expected output: 6, 1
macos_version_number="${macos_version_major}$(printf "%02d" "${macos_version_minor}")" # Expected output: 1014, 1015, 1106, 1203
[[ "${macos_version_major}" -ge 13 ]] && macos_version_extra=$(sw_vers -productVersionExtra | cut -d'.' -f2) # Expected output: (a), (b), (c)
macos_build=$(sw_vers -buildVersion) # Expected output: 22D68
macos_title="macOS $(awk '/SOFTWARE LICENSE AGREEMENT FOR/' '/System/Library/CoreServices/Setup Assistant.app/Contents/Resources/en.lproj/OSXSoftwareLicense.rtf' | awk -F 'macOS ' '{print $NF}' | awk '{print substr($0, 0, length($0)-1)}')" # Expected output: macOS Ventura or "*PRE-RELEASE*"
[[ $(echo "${macos_title}" | grep -c 'PRE-RELEASE') -gt 0 ]] && macos_title="macOS Beta"
mac_cpu_architecture=$(arch) # Expected output: i386, arm64
mac_model_name=$(system_profiler SPHardwareDataType | grep 'Model Name' | awk -F ': ' '{print $2;}') # Expected output: MacBook Pro
[[ $(echo "${mac_model_name}" | grep -c 'Book') -gt 0 ]] && mac_is_portable="TRUE" # Expected output: TRUE
if [[ -n "${macos_version_patch}" ]]; then # macOS version has a patch number.
[[ -n "${macos_version_extra}" ]] && macos_version_full="${macos_title} ${macos_version_major}.${macos_version_minor}.${macos_version_patch}${macos_version_extra}-${macos_build}"
[[ -z "${macos_version_extra}" ]] && macos_version_full="${macos_title} ${macos_version_major}.${macos_version_minor}.${macos_version_patch}-${macos_build}"
else # macOS version does not have a patch number.
[[ -n "${macos_version_extra}" ]] && macos_version_full="${macos_title} ${macos_version_major}.${macos_version_minor}${macos_version_extra}-${macos_build}"
[[ -z "${macos_version_extra}" ]] && macos_version_full="${macos_title} ${macos_version_major}.${macos_version_minor}-${macos_build}"
fi
if [[ "${mac_cpu_architecture}" == "arm64" ]]; then # Mac computers with Apple Silicon.
log_super "Status: Mac computer with Apple silicon running: ${macos_version_full}"
else # Mac computers with Intel.
log_super "Status: Mac computer with Intel running: ${macos_version_full}"
fi
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_version_number is: ${macos_version_number}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: mac_model_name is: ${mac_model_name}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: mac_is_portable is: ${mac_is_portable}"
local kernel_boot_epoch
kernel_boot_epoch=$(sysctl -n kern.boottime | awk -F 'sec = |, usec' '{print $2}') # Expected outputs: seconds since epoch
if [[ "${kernel_boot_epoch}" =~ ${REGEX_ANY_WHOLE_NUMBER} ]]; then
mac_last_startup=$(date -r "${kernel_boot_epoch}" +%Y-%m-%d:%H:%M:%S) # Expected output: 2023-08-25:00:16:00
else
log_super "Error: Unrecognized kernel boot epoch time: ${kernel_boot_epoch}"
check_error="TRUE"
fi
if [[ "${mac_last_startup}" =~ ${REGEX_DATE_HOURS_MINUTES_SECONDS} ]]; then
log_super "Status: Last macOS startup was: ${mac_last_startup}"
else
log_super "Error: Unrecognized last startup date and time: ${mac_last_startup}"
check_error="TRUE"
fi
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: check_error is: ${check_error}"
}
# Check for software update settings to report non-ideal configurations, this also sets ${msu_automatic_check} and ${msu_automatic_security_updates} appropriately.
check_msu_settings() {
msu_automatic_check="TRUE"
msu_automatic_download="TRUE"
msu_automatic_macos_installation="TRUE"
msu_automatic_security_updates="TRUE"
# Start with automatic check.
local msu_automatic_check_managed
msu_automatic_check_managed=$(defaults read "${MSU_MANAGED_PLIST}" AutomaticCheckEnabled 2>/dev/null)
local msu_automatic_check_local
msu_automatic_check_local=$(defaults read "${MSU_LOCAL_PLIST}" AutomaticCheckEnabled 2>/dev/null)
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: msu_automatic_check_managed is: ${msu_automatic_check_managed}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: msu_automatic_check_local is: ${msu_automatic_check_local}"
[[ -n $msu_automatic_check_managed ]] && [[ $msu_automatic_check_managed -eq 0 ]] && msu_automatic_download="FALSE"
[[ -z $msu_automatic_check_managed ]] && [[ -n $msu_automatic_check_local ]] && [[ $msu_automatic_check_local -eq 0 ]] && msu_automatic_download="FALSE"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: msu_automatic_check is: ${msu_automatic_check}"
if [[ "${msu_automatic_check}" == "TRUE" ]]; then
# Automatic download check.
local msu_automatic_download_managed
msu_automatic_download_managed=$(defaults read "${MSU_MANAGED_PLIST}" AutomaticDownload 2>/dev/null)
local msu_automatic_download_local
msu_automatic_download_local=$(defaults read "${MSU_LOCAL_PLIST}" AutomaticDownload 2>/dev/null)
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: msu_automatic_download_managed is: ${msu_automatic_download_managed}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: msu_automatic_download_local is: ${msu_automatic_download_local}"
[[ -n $msu_automatic_download_managed ]] && [[ $msu_automatic_download_managed -eq 0 ]] && msu_automatic_download="FALSE"
[[ -z $msu_automatic_download_managed ]] && [[ -n $msu_automatic_download_local ]] && [[ $msu_automatic_download_local -eq 0 ]] && msu_automatic_download="FALSE"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: msu_automatic_download is: ${msu_automatic_download}"
# Automatic macOS installation check.
local msu_automatic_macos_installation_managed
msu_automatic_macos_installation_managed=$(defaults read "${MSU_MANAGED_PLIST}" AutomaticallyInstallMacOSUpdates 2>/dev/null)
local msu_automatic_macos_installation_local
msu_automatic_macos_installation_local=$(defaults read "${MSU_LOCAL_PLIST}" AutomaticallyInstallMacOSUpdates 2>/dev/null)
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: msu_automatic_macos_installation_managed is: ${msu_automatic_macos_installation_managed}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: msu_automatic_macos_installation_local is: ${msu_automatic_macos_installation_local}"
[[ -n $msu_automatic_macos_installation_managed ]] && [[ $msu_automatic_macos_installation_managed -eq 0 ]] && msu_automatic_macos_installation="FALSE"
[[ -z $msu_automatic_macos_installation_managed ]] && [[ -n $msu_automatic_macos_installation_local ]] && [[ $msu_automatic_macos_installation_local -eq 0 ]] && msu_automatic_macos_installation="FALSE"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: msu_automatic_macos_installation is: ${msu_automatic_macos_installation}"
# Automatic security update check.
local msu_automatic_config_data_managed
msu_automatic_config_data_managed=$(defaults read "${MSU_MANAGED_PLIST}" ConfigDataInstall 2>/dev/null)
local msu_automatic_config_data_local
msu_automatic_config_data_local=$(defaults read "${MSU_LOCAL_PLIST}" ConfigDataInstall 2>/dev/null)
local msu_automatic_critical_update_managed
msu_automatic_critical_update_managed=$(defaults read "${MSU_MANAGED_PLIST}" CriticalUpdateInstall 2>/dev/null)
local msu_automatic_critical_update_local
msu_automatic_critical_update_local=$(defaults read "${MSU_LOCAL_PLIST}" CriticalUpdateInstall 2>/dev/null)
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: msu_automatic_config_data_managed is: ${msu_automatic_config_data_managed}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: msu_automatic_config_data_local is: ${msu_automatic_config_data_local}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: msu_automatic_critical_update_managed is: ${msu_automatic_critical_update_managed}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: msu_automatic_critical_update_local is: ${msu_automatic_critical_update_local}"
{ { [[ -n $msu_automatic_config_data_managed ]] && [[ $msu_automatic_config_data_managed -eq 0 ]]; } || { [[ -n $msu_automatic_critical_update_managed ]] && [[ $msu_automatic_critical_update_managed -eq 0 ]]; }; } && msu_automatic_security_updates="FALSE"
{ { [[ -z $msu_automatic_config_data_managed ]] && [[ -n $msu_automatic_config_data_local ]] && [[ $msu_automatic_config_data_local -eq 0 ]]; } || { [[ -z $msu_automatic_critical_update_managed ]] && [[ -n $msu_automatic_critical_update_local ]] && [[ $msu_automatic_critical_update_local -eq 0 ]]; }; } && msu_automatic_security_updates="FALSE"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: msu_automatic_security_updates is: ${msu_automatic_security_updates}"
# Log any warnings.
if [[ "${msu_automatic_download}" == "TRUE" ]]; then
[[ "${msu_automatic_macos_installation}" == "FALSE" ]] && log_super "Warning: Automatic download of macOS updates is currently enabled, this can result in updates being downloaded outside of super workflows."
[[ "${msu_automatic_macos_installation}" == "TRUE" ]] && log_super "Warning: Automatic download and installation of macOS updates is currently enabled, this can result in updates being installed outside of super workflows."
fi
[[ "${msu_automatic_security_updates}" == "FALSE" ]] && log_super "Warning: Automatic installation of macOS security updates is disabled, this is a significant security risk."
else # msu_automatic_check="FALSE"
log_super "Warning: Automatic Apple software updated checking is currently disabled, this is not ideal for the super workflow and prevents macOS security updates from being installed which is a significant security risk."
msu_automatic_download="FALSE"
msu_automatic_macos_installation="FALSE"
msu_automatic_security_updates="FALSE"
fi
}
# Validate that the account ${auth_local_account} and ${auth_local_password} are valid credentials to a volume owner. If not set ${auth_error_local}.
check_auth_local_account() {
auth_error_local="FALSE"
local auth_local_guid
auth_local_guid=$(dscl . read "/Users/${auth_local_account}" GeneratedUID 2>/dev/null | awk '{print $2;}')
if [[ -n "${auth_local_guid}" ]]; then
if ! [[ $(diskutil apfs listcryptousers / | grep -c "${auth_local_guid}") -gt 0 ]]; then
log_super "Auth Error: The account \"${auth_local_account}\" is not a system volume owner."
auth_error_local="TRUE"
fi
if [[ $(dscl /Local/Default -authonly "${auth_local_account}" "${auth_local_password}" 2>&1 | grep -c 'eDSAuthFailed') -gt 0 ]]; then
log_super "Auth Error: The password for account \"${auth_local_account}\" is not valid."
auth_error_local="TRUE"
fi
else
log_super "Auth Error: Could not retrieve GUID for account \"${auth_local_account}\". Verify that account exists locally."
auth_error_local="TRUE"
fi
[[ "${verbose_mode_option}" == "TRUE" ]] && log_echo "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_local_account: ${auth_local_account}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_echo "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_local_password: ${auth_local_password}"
}
# Collect the available storage and set ${storage_ready} accordingly. This also sets ${storage_available_gigabytes} and ${storage_required_gigabytes}.
check_storage_available() {
check_storage_available_error="FALSE"
storage_ready="FALSE"
[[ -z "${current_user_account_name}" ]] && check_current_user
[[ "${current_user_account_name}" != "FALSE" ]] && storage_available_gigabytes=$(osascript -l 'JavaScript' -e "ObjC.import('Foundation'); var freeSpaceBytesRef=Ref(); $.NSURL.fileURLWithPath('/').getResourceValueForKeyError(freeSpaceBytesRef, 'NSURLVolumeAvailableCapacityForImportantUsageKey', null); Math.round(ObjC.unwrap(freeSpaceBytesRef[0]) / 1000000000)")
[[ "${current_user_account_name}" == "FALSE" ]] && storage_available_gigabytes=$(/usr/libexec/mdmclient QueryDeviceInformation 2>/dev/null | grep 'AvailableDeviceCapacity' | head -n 1 | awk '{print $3;}' | sed -e 's/;//g' -e 's/"//g' -e 's/\.[0-9]*//g')
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: storage_available_gigabytes: ${storage_available_gigabytes}"
if [[ -z "${storage_available_gigabytes}" ]] || [[ ! "${storage_available_gigabytes}" =~ ${REGEX_ANY_WHOLE_NUMBER} ]]; then
log_super "Error: Unable to determine available storage."
check_storage_available_error="TRUE"
elif [[ "${macos_installer_target}" != "FALSE" ]] || [[ "${macos_msu_major_upgrade_target}" != "FALSE" ]] || [[ "${macos_msu_minor_update_target}" != "FALSE" ]]; then
{ [[ -z "${test_storage_update_option}" ]] && [[ "${macos_msu_size}" -lt 5 ]]; } && storage_required_update_gb=$((macos_msu_size * 2))
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_installer_download_required: ${macos_installer_download_required}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_installer_size: ${macos_installer_size}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_msu_download_required: ${macos_msu_download_required}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_msu_size: ${macos_msu_size}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: storage_required_upgrade_gb: ${storage_required_upgrade_gb}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: storage_required_update_gb: ${storage_required_update_gb}"
if [[ "${macos_installer_target}" != "FALSE" ]]; then # Target is a macOS major upgrade via installer.
if [[ "${macos_installer_download_required}" == "TRUE" ]]; then
storage_required_gigabytes=$((storage_required_upgrade_gb + macos_installer_size))
else # Download calculation is not required.
storage_required_gigabytes="${storage_required_upgrade_gb}"
fi
fi
if [[ "${macos_msu_major_upgrade_target}" != "FALSE" ]]; then # Target is a macOS major upgrade via MSU.
if [[ "${macos_msu_download_required}" == "TRUE" ]]; then
storage_required_gigabytes=$((storage_required_upgrade_gb + macos_msu_size))
else # Download calculation is not required.
storage_required_gigabytes="${storage_required_upgrade_gb}"
fi
fi
if [[ "${macos_msu_minor_update_target}" != "FALSE" ]]; then # Target is a macOS minor update.
if [[ "${macos_msu_download_required}" == "TRUE" ]]; then
storage_required_gigabytes=$((storage_required_update_gb + macos_msu_size))
else # Download calculation is not required.
storage_required_gigabytes="${storage_required_update_gb}"
fi
fi
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: storage_required_gigabytes: ${storage_required_gigabytes}"
[[ "${storage_available_gigabytes}" -ge "${storage_required_gigabytes}" ]] && storage_ready="TRUE"
else # No macOS update/upgrade is available.
storage_ready="TRUE"
fi
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: check_storage_available_error: ${check_storage_available_error}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: storage_ready: ${storage_ready}"
}
# Validate if current system power is adequate for performing a macOS update/upgrade and set ${power_ready} accordingly. Desktops, obviously, always return that they are ready.
check_power_required() {
local power_required_charger_connected
power_required_charger_connected="FALSE"
check_power_required_error="FALSE"
power_ready="FALSE"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: mac_is_portable: ${mac_is_portable}"
if [[ "${mac_is_portable}" == "TRUE" ]]; then
[[ $(pmset -g ps | grep -ic 'AC Power') -ne 0 ]] && power_required_charger_connected="TRUE"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: power_required_charger_connected: ${power_required_charger_connected}"
if [[ "${power_required_charger_connected}" == "TRUE" ]]; then
power_ready="TRUE"
else # Not plugged into AC power.
power_battery_percent=$(pmset -g ps | grep '%' | awk '{print $3;}' | sed -e 's/%;//g')
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: power_battery_percent: ${power_battery_percent}"
if [[ -z "${power_battery_percent}" ]] || [[ ! "${power_battery_percent}" =~ ${REGEX_ANY_WHOLE_NUMBER} ]]; then
log_super "Error: Unable to determine battery power level."
check_power_required_error="TRUE"
else # Battery level is a real number.
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: power_required_battery_percent: ${power_required_battery_percent}"
[[ "${power_battery_percent}" -gt "${power_required_battery_percent}" ]] && power_ready="TRUE"
fi
fi
else # Mac desktop.
power_ready="TRUE"
fi
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: check_power_required_error: ${check_power_required_error}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: power_ready: ${power_ready}"
}
# Validate the computer's MDM service status and set ${mdm_enrolled}, ${mdm_automatically_enrolled}, ${mdm_service_url}, and ${auth_error_mdm}.
# Unlike other MDM validation functions, this function is MDM-vendor agnostic.
check_mdm_service() {
mdm_enrolled="FALSE"
mdm_automatically_enrolled="FALSE"
auth_error_mdm="FALSE"
local profiles_response
profiles_response=$(profiles status -type enrollment 2>&1)
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: profiles_response:\n${profiles_response}"
if [[ $(echo "${profiles_response}" | grep -c 'MDM server') -gt 0 ]]; then
mdm_enrolled="TRUE"
[[ $(echo "${profiles_response}" | grep 'Enrolled via DEP:' | grep -c 'Yes') -gt 0 ]] && mdm_automatically_enrolled="TRUE"
mdm_service_url="https://$(echo "${profiles_response}" | grep 'MDM server' | awk -F '/' '{print $3;}')"
local curl_response
curl_response=$(curl -Is "${mdm_service_url}" | head -n 1)
if [[ $(echo "${curl_response}" | grep -c 'HTTP') -gt 0 ]] && [[ $(echo "${curl_response}" | grep -c -e '400' -e '40[4-9]' -e '4[1-9][0-9]' -e '5[0-9][0-9]') -eq 0 ]]; then
log_super "Status: MDM service is currently available at: ${mdm_service_url}"
else
log_super "Warning: MDM service at ${mdm_service_url} is currently unavailable with status: ${curl_response}"
auth_error_mdm="TRUE"
fi
else
log_super "Warning: System is not enrolled with a MDM service."
auth_error_mdm="TRUE"
fi
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: mdm_enrolled: ${mdm_enrolled}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: mdm_automatically_enrolled: ${mdm_automatically_enrolled}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: mdm_service_url: ${mdm_service_url}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_error_mdm: ${auth_error_mdm}"
}
# Validate that the computer's bootstrap token is properly escrowed and set ${auth_error_bootstrap_token}.
check_bootstrap_token_escrow() {
auth_error_bootstrap_token="FALSE"
local profiles_response
profiles_response=$(profiles status -type bootstraptoken 2>&1)
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: profiles_response:\n${profiles_response}"
if [[ $(echo "${profiles_response}" | grep -c 'YES') -eq 2 ]]; then
if [[ "${macos_version_number}" -ge 1303 ]]; then
if [[ "${auth_error_mdm}" == "FALSE" ]]; then
local mdmclient_response
mdmclient_response=$(/usr/libexec/mdmclient QueryDeviceInformation 2>/dev/null | grep 'EACSPreflight' | sed -e 's/ EACSPreflight = //g' -e 's/"//g' -e 's/;//g')
if [[ $(echo "${mdmclient_response}" | grep -c 'success') -gt 0 ]] || [[ $(echo "${mdmclient_response}" | grep -c 'EFI password exists') -gt 0 ]]; then
log_super "Status: Bootstrap token escrowed and validated with MDM service."
else
log_super "Warning: Bootstrap token escrow validation failed with status: ${mdmclient_response}"
auth_error_bootstrap_token="TRUE"
fi
else
log_super "Warning: Bootstrap token was previously escrowed with MDM service but the service is currently unavailable so it can not be validated."
fi
else
log_super "Status: Bootstrap token escrowed with MDM service."
fi
else
log_super "Warning: Bootstrap token is not escrowed with MDM service."
auth_error_bootstrap_token="TRUE"
fi
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_error_bootstrap_token: ${auth_error_bootstrap_token}"
}
# MARK: *** Schedules, Deferrals, & Deadlines ***
################################################################################
# Delete the deadline counters in ${SUPER_LOCAL_PLIST} to reset the counters.
reset_deadline_counters() {
log_super "Status: Resetting any deadline counters."
defaults delete "${SUPER_LOCAL_PLIST}" DeadlineCounterFocus 2>/dev/null
defaults delete "${SUPER_LOCAL_PLIST}" DeadlineCounterSoft 2>/dev/null
defaults delete "${SUPER_LOCAL_PLIST}" DeadlineCounterHard 2>/dev/null
}
# Delete any automatic zero dates and associated cached dates in ${SUPER_LOCAL_PLIST}.
reset_schedule_zero_date() {
log_super "Status: Resetting any workflow dates."
defaults delete "${SUPER_LOCAL_PLIST}" ScheduleZeroDateAutomaticRelease 2>/dev/null
defaults delete "${SUPER_LOCAL_PLIST}" ScheduleZeroDateAutomaticStart 2>/dev/null
defaults delete "${SUPER_LOCAL_PLIST}" DeadlineDaysFocusDate 2>/dev/null
defaults delete "${SUPER_LOCAL_PLIST}" DeadlineDaysSoftDate 2>/dev/null
defaults delete "${SUPER_LOCAL_PLIST}" DeadlineDaysHardDate 2>/dev/null
}
# Evaluate ${schedule_workflow_active_option} and set ${workflow_time_weekday}, ${schedule_workflow_active_array}, and ${schedule_workflow_active_deferral} accordingly.
# If the workflow schedule is currently active then set ${schedule_workflow_active_current_end_epoch} and ${schedule_workflow_active_current_end}, but if the workflow schedule is currently inactive then set ${schedule_workflow_active_next_start_epoch} and ${schedule_workflow_active_next_start}.
check_schedule_workflow_active() {
schedule_workflow_active_deferral="TRUE"
workflow_time_weekday=$(date +%a | tr '[:lower:]' '[:upper:]')
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: schedule_workflow_active_option is: ${schedule_workflow_active_option}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: workflow_time_weekday is: ${workflow_time_weekday}"
# First check to see if workflow is allowed for current weekday and time.
local previous_ifs
previous_ifs="${IFS}"
IFS=','
[[ -z "${schedule_workflow_active_array[*]}" ]] && read -r -a schedule_workflow_active_array <<<"${schedule_workflow_active_option}"
local schedule_schedule_workflow_active_time_frame_start_epoch
local schedule_schedule_workflow_active_time_frame_end_epoch
local schedule_workflow_active_next_start_difference
local schedule_workflow_active_next_start_epoch_array
schedule_workflow_active_next_start_epoch_array=()
for schedule_workflow_active_time_frame in "${schedule_workflow_active_array[@]}"; do
# Convert the ${schedule_workflow_active_time_frame} into start and end epoch times relative to the current week.
schedule_schedule_workflow_active_time_frame_start_epoch=$(date -j -v"${schedule_workflow_active_time_frame:0:3}"d -v"${schedule_workflow_active_time_frame:4:2}"H -v"${schedule_workflow_active_time_frame:7:2}"M -v00S +%s)
schedule_schedule_workflow_active_time_frame_end_epoch=$(date -j -v"${schedule_workflow_active_time_frame:0:3}"d -v"${schedule_workflow_active_time_frame:10:2}"H -v"${schedule_workflow_active_time_frame:13:2}"M -v00S +%s)
if [[ $workflow_time_epoch -ge $schedule_schedule_workflow_active_time_frame_start_epoch ]] && [[ $workflow_time_epoch -lt $schedule_schedule_workflow_active_time_frame_end_epoch ]]; then # If the current time is between the start and end of the ${schedule_workflow_active_time_frame}.
schedule_workflow_active_deferral="FALSE"
schedule_workflow_active_current_end_epoch="${schedule_schedule_workflow_active_time_frame_end_epoch}"
schedule_workflow_active_current_end="$(date -j -f %s "${schedule_workflow_active_current_end_epoch}" +%a:%H:%M | tr '[:lower:]' '[:upper:]')"
break
elif [[ $workflow_time_epoch -lt $schedule_schedule_workflow_active_time_frame_start_epoch ]]; then # If the current time is less than the start time of the ${schedule_workflow_active_time_frame}.
schedule_workflow_active_next_start_difference=$((schedule_schedule_workflow_active_time_frame_start_epoch - workflow_time_epoch))
if [[ "${schedule_workflow_active_next_start_difference}" -le 180 ]]; then
log_super "Status: The next schedule workflow active time frame starts in only ${schedule_workflow_active_next_start_difference} seconds, waiting for time frame to start..."
sleep $((schedule_workflow_active_next_start_difference + 1 ))
schedule_workflow_active_deferral="FALSE"
schedule_workflow_active_current_end_epoch="${schedule_schedule_workflow_active_time_frame_end_epoch}"
schedule_workflow_active_current_end="$(date -j -f %s "${schedule_workflow_active_current_end_epoch}" +%a:%H:%M | tr '[:lower:]' '[:upper:]')"
break
else
schedule_workflow_active_next_start_epoch_array+=("${schedule_schedule_workflow_active_time_frame_start_epoch}")
fi
elif [[ $workflow_time_epoch -gt $schedule_schedule_workflow_active_time_frame_end_epoch ]] && [[ "${workflow_time_weekday}" != "${schedule_workflow_active_time_frame:0:3}" ]]; then # The current time is past the end time of the ${schedule_workflow_active_time_frame} and it's not today.
schedule_workflow_active_next_start_epoch_array+=("$(date -j -v+"${schedule_workflow_active_time_frame:0:3}"d -v"${schedule_workflow_active_time_frame:4:2}"H -v"${schedule_workflow_active_time_frame:7:2}"M -v00S +%s)")
fi
done
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: schedule_workflow_active_deferral is: ${schedule_workflow_active_deferral}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: schedule_workflow_active_current_end_epoch is: ${schedule_workflow_active_current_end_epoch}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: schedule_workflow_active_current_end is: ${schedule_workflow_active_current_end}"
# If ${schedule_workflow_active_deferral} is "TRUE" then set ${schedule_workflow_active_next_start_epoch}.
if [[ "${schedule_workflow_active_deferral}" == "TRUE" ]]; then
IFS=$'\n'
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: schedule_workflow_active_next_start_epoch_array is:\n${schedule_workflow_active_next_start_epoch_array[*]}"
schedule_workflow_active_next_start_epoch="$(echo "${schedule_workflow_active_next_start_epoch_array[*]}" | sort | head -1)"
schedule_workflow_active_next_start="$(date -j -f %s "${schedule_workflow_active_next_start_epoch}" +%a:%H:%M | tr '[:lower:]' '[:upper:]')"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: schedule_workflow_active_next_start_epoch is: ${schedule_workflow_active_next_start_epoch}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: schedule_workflow_active_next_start is: ${schedule_workflow_active_next_start}"
fi
IFS="${previous_ifs}"
}
# Adjust ${deferral_timer_minutes_schedule_workflow_active_check}, ${deadline_epoch_schedule_workflow_active_check}, or ${workflow_scheduled_install} to coordinate with the ${schedule_workflow_active_option}.
# This function can set ${deferral_timer_minutes_schedule_workflow_active_adjusted}, ${deadline_epoch_schedule_workflow_active_adjusted}, ${workflow_scheduled_install_adjusted}, ${schedule_workflow_active_matches_now}, and ${scheduled_install_user_choice_adjusted}.
set_schedule_workflow_active_adjustments() {
local previous_ifs
previous_ifs="${IFS}"
IFS=','
[[ -z "${schedule_workflow_active_array[*]}" ]] && read -r -a schedule_workflow_active_array <<<"${schedule_workflow_active_option}"
local schedule_workflow_active_match_start_epoch
local schedule_workflow_active_match_end_epoch
schedule_workflow_active_matches_now="FALSE"
local schedule_workflow_active_match_start_epochs_array
schedule_workflow_active_match_start_epochs_array=()
local schedule_workflow_active_match_weekday_start_epoch
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: workflow_time_epoch: ${workflow_time_epoch}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: workflow_time_weekday: ${workflow_time_weekday}"
# If there is a ${deferral_timer_minutes_schedule_workflow_active_check} or ${deferral_timer_menu_schedule_workflow_active_check} then adjust it to coordinate with the ${schedule_workflow_active_option} and set ${deferral_timer_minutes_schedule_workflow_active_adjusted} accordingly.
if [[ -n "${deferral_timer_minutes_schedule_workflow_active_check}" ]] || [[ -n "${deferral_timer_menu_schedule_workflow_active_check}" ]]; then
local deferral_timer_match
deferral_timer_match="FALSE"
local deferral_timer_epoch_temp
[[ -n "${deferral_timer_minutes_schedule_workflow_active_check}" ]] && deferral_timer_epoch_temp=$((workflow_time_epoch + (deferral_timer_minutes_schedule_workflow_active_check * 60)))
[[ -n "${deferral_timer_menu_schedule_workflow_active_check}" ]] && deferral_timer_epoch_temp=$((workflow_time_epoch + (deferral_timer_menu_schedule_workflow_active_check * 60)))
local deferral_timer_days_away
deferral_timer_days_away=$(((deferral_timer_epoch_temp - $(date -v+0d -v0H -v0M -v0S +%s)) / 86400))
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: deferral_timer_epoch_temp: ${deferral_timer_epoch_temp}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: deferral_timer_days_away: ${deferral_timer_days_away}"
# Loop through the weekdays (starting with today) and set ${schedule_workflow_active_match_start_epochs_array[*]} accordingly.
for ((counter=0; counter<7; ++counter)); do
local deferral_timer_weekday
deferral_timer_weekday="$(date -v+$((deferral_timer_days_away + counter))d +%a | tr '[:lower:]' '[:upper:]')"
for schedule_workflow_active_time_frame in "${schedule_workflow_active_array[@]}"; do
if [[ "${schedule_workflow_active_time_frame:0:3}" == "${deferral_timer_weekday}" ]]; then
schedule_workflow_active_match_start_epoch=$(date -j -v+$((deferral_timer_days_away + counter))d -v"${schedule_workflow_active_time_frame:4:2}"H -v"${schedule_workflow_active_time_frame:7:2}"M -v00S +%s)
schedule_workflow_active_match_end_epoch=$(date -j -v+$((deferral_timer_days_away + counter))d -v"${schedule_workflow_active_time_frame:10:2}"H -v"${schedule_workflow_active_time_frame:13:2}"M -v00S +%s)
if [[ $schedule_workflow_active_match_start_epoch -le $deferral_timer_epoch_temp ]] && [[ $deferral_timer_epoch_temp -le $schedule_workflow_active_match_end_epoch ]]; then
deferral_timer_match="TRUE"
{ [[ $schedule_workflow_active_match_start_epoch -le $workflow_time_epoch ]] && [[ $workflow_time_epoch -le $schedule_workflow_active_match_end_epoch ]]; } && schedule_workflow_active_matches_now="TRUE"
break
elif [[ $deferral_timer_epoch_temp -le $schedule_workflow_active_match_start_epoch ]]; then
schedule_workflow_active_match_start_epochs_array+=($(date -v+$((deferral_timer_days_away + counter))d -v"${schedule_workflow_active_time_frame:4:2}"H -v"${schedule_workflow_active_time_frame:7:2}"M -v00S +%s))
fi
fi
done
done
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: deferral_timer_match: ${deferral_timer_match}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: schedule_workflow_active_matches_now: ${schedule_workflow_active_matches_now}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: schedule_workflow_active_match_start_epochs_array is:\n${schedule_workflow_active_match_start_epochs_array[*]}"
# If at least one matching workflow active timeframe was identified then set ${deferral_timer_minutes_schedule_workflow_active_adjusted}.
if [[ "${deferral_timer_match}" == "TRUE" ]]; then
if [[ -n "${deferral_timer_minutes_schedule_workflow_active_check}" ]]; then
[[ "${schedule_workflow_active_matches_now}" == "TRUE" ]] && log_super "Status: The default deferral timer falls within the current schedule workflow active time frame."
[[ "${schedule_workflow_active_matches_now}" == "FALSE" ]] && log_super "Status: The default deferral timer falls within a future schedule workflow active time frame."
deferral_timer_minutes_schedule_workflow_active_adjusted="${deferral_timer_minutes_schedule_workflow_active_check}"
else # ${deferral_timer_menu_schedule_workflow_active_check}
deferral_timer_minutes_schedule_workflow_active_adjusted="${deferral_timer_menu_schedule_workflow_active_check}"
fi
elif [[ -n "${schedule_workflow_active_match_start_epochs_array[*]}" ]]; then
IFS=$'\n'
schedule_workflow_active_match_weekday_start_epoch=$(echo "${schedule_workflow_active_match_start_epochs_array[*]}" | sort | head -1)
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: schedule_workflow_active_match_weekday_start_epoch: ${schedule_workflow_active_match_weekday_start_epoch}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: deadline_epoch: ${deadline_epoch}"
if [[ -n "${deadline_epoch}" ]] && [[ $deadline_epoch -lt $schedule_workflow_active_match_weekday_start_epoch ]]; then
deferral_timer_minutes_schedule_workflow_active_adjusted=$(((deadline_epoch - workflow_time_epoch) / 60))
[[ -n "${deferral_timer_minutes_schedule_workflow_active_check}" ]] && log_super "Warning: Adjusting the default deferral timer to coordinate with the current deadline."
else
deferral_timer_minutes_schedule_workflow_active_adjusted=$(((schedule_workflow_active_match_weekday_start_epoch - workflow_time_epoch) / 60))
[[ -n "${deferral_timer_minutes_schedule_workflow_active_check}" ]] && log_super "Warning: Adjusting the default deferral timer to coordinate with a future schedule workflow active time frame that starts at $(date -j -f %s "${schedule_workflow_active_match_weekday_start_epoch}" +%a:%H:%M | tr '[:lower:]' '[:upper:]')."
fi
fi
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: deferral_timer_minutes_schedule_workflow_active_adjusted: ${deferral_timer_minutes_schedule_workflow_active_adjusted}"
IFS="${previous_ifs}"
return 0
fi
# If there is a ${deadline_epoch_schedule_workflow_active_check} then adjust it to coordinate with the ${schedule_workflow_active_option} and set ${deadline_epoch_schedule_workflow_active_adjusted} accordingly.
if [[ -n "${deadline_epoch_schedule_workflow_active_check}" ]]; then
local deadline_epoch_temp
deadline_epoch_temp=$(date -j -f %Y-%m-%d:%H:%M:%S "$(date -r "${deadline_epoch_schedule_workflow_active_check}" "+%Y-%m-%d:23:59:59")" +%s)
local deadline_days_away
deadline_days_away=$(((deadline_epoch_temp - $(date -v+0d -v0H -v0M -v0S +%s)) / 86400))
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: deadline_epoch_temp: ${deadline_epoch_temp}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: deadline_days_away: ${deadline_days_away}"
# If needed, look for ${schedule_workflow_active} time frames that match today and set ${schedule_workflow_active_match_start_epochs_array[*]} accordingly.
if [[ $deadline_days_away -eq 0 ]]; then
for schedule_workflow_active_time_frame in "${schedule_workflow_active_array[@]}"; do
if [[ "${schedule_workflow_active_time_frame:0:3}" == "${workflow_time_weekday}" ]]; then
schedule_workflow_active_match_start_epoch=$(date -j -f %H:%M:%S "${schedule_workflow_active_time_frame:4:5}:00" +%s)
schedule_workflow_active_match_end_epoch=$(date -j -f %H:%M:%S "${schedule_workflow_active_time_frame:10:5}:00" +%s)
if [[ $schedule_workflow_active_match_start_epoch -le $workflow_time_epoch ]] && [[ $workflow_time_epoch -le $schedule_workflow_active_match_end_epoch ]]; then
schedule_workflow_active_match_start_epochs_array=("${schedule_workflow_active_match_start_epoch}")
schedule_workflow_active_matches_now="TRUE"
break
elif [[ $workflow_time_epoch -le $schedule_workflow_active_match_start_epoch ]]; then
schedule_workflow_active_match_start_epochs_array+=("${schedule_workflow_active_match_start_epoch}")
fi
fi
done
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: schedule_workflow_active_matches_now: ${schedule_workflow_active_matches_now}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: schedule_workflow_active_match_start_epochs_array is:\n${schedule_workflow_active_match_start_epochs_array[*]}"
fi
# If needed, find future ${schedule_workflow_active} time frames by looping through 7 weekdays in reverse from the deadline day and set ${schedule_workflow_active_match_start_epochs_array[*]} accordingly.
if [[ -z "${schedule_workflow_active_match_start_epochs_array[*]}" ]]; then
local deadline_weekday
deadline_weekday="$(date -j -f %s "${deadline_epoch_temp}" +%a | tr '[:lower:]' '[:upper:]')"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: deadline_weekday: ${deadline_weekday}"
for ((counter=0; counter<6; ++counter)); do
if [[ $((deadline_days_away - counter)) -ge 1 ]]; then
for schedule_workflow_active_time_frame in "${schedule_workflow_active_array[@]}"; do
[[ "${schedule_workflow_active_time_frame:0:3}" == "${deadline_weekday}" ]] && schedule_workflow_active_match_start_epochs_array+=($(date -j -v+$((deadline_days_away - counter))d -v"${schedule_workflow_active_time_frame:4:2}"H -v"${schedule_workflow_active_time_frame:7:2}"M -v00S +%s))
done
else # No more weekdays to check.
break
fi
done
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: schedule_workflow_active_match_start_epochs_array is:\n${schedule_workflow_active_match_start_epochs_array[*]}"
fi
# If at least one matching workflow active timeframe was identified then set ${deadline_epoch_schedule_workflow_active_adjusted}.
if [[ -n "${schedule_workflow_active_match_start_epochs_array[*]}" ]]; then
IFS=$'\n'
[[ "${schedule_workflow_active_matches_now}" == "TRUE" ]] && deadline_epoch_schedule_workflow_active_adjusted=${schedule_workflow_active_match_start_epochs_array[0]}
[[ "${schedule_workflow_active_matches_now}" == "FALSE" ]] && deadline_epoch_schedule_workflow_active_adjusted=$(echo "${schedule_workflow_active_match_start_epochs_array[*]}" | sort -r | head -1)
log_super "Warning: Adjusting the dealine to coordinate with a schedule workflow active time frame that starts at $(date -j -f %s "${deadline_epoch_schedule_workflow_active_adjusted}" +%a:%H:%M | tr '[:lower:]' '[:upper:]')."
fi
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: deadline_epoch_schedule_workflow_active_adjusted: ${deadline_epoch_schedule_workflow_active_adjusted}"
IFS="${previous_ifs}"
return 0
fi
# If there is a ${workflow_scheduled_install} via the ${scheduled_install_days} and ${scheduled_install_date} options then compare to the ${schedule_workflow_active_option} for possible adjustments.
if [[ -n "${scheduled_install_days}" ]] || [[ -n "${scheduled_install_date}" ]]; then
local workflow_scheduled_install_weekday
[[ $(echo "${workflow_scheduled_install}" | grep -c ':00:00$') -gt 0 ]] && workflow_scheduled_install="${workflow_scheduled_install:0:10}:23:59"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: workflow_scheduled_install: ${workflow_scheduled_install}"
# If needed, look for ${schedule_workflow_active} time frames that match today and set ${schedule_workflow_active_match_start_epochs_array[*]} accordingly.
if { [[ -n "${scheduled_install_days}" ]] && [[ $scheduled_install_days -eq 0 ]]; } || [[ "${scheduled_install_past_deadline}" == "TRUE" ]]; then
for schedule_workflow_active_time_frame in "${schedule_workflow_active_array[@]}"; do
if [[ "${schedule_workflow_active_time_frame:0:3}" == "${workflow_time_weekday}" ]]; then
schedule_workflow_active_match_start_epoch=$(date -j -f %H:%M:%S "${schedule_workflow_active_time_frame:4:5}:00" +%s)
schedule_workflow_active_match_end_epoch=$(date -j -f %H:%M:%S "${schedule_workflow_active_time_frame:10:5}:00" +%s)
if [[ $schedule_workflow_active_match_start_epoch -le $workflow_time_epoch ]] && [[ $workflow_time_epoch -le $schedule_workflow_active_match_end_epoch ]]; then
schedule_workflow_active_match_start_epochs_array=("${schedule_workflow_active_match_start_epoch}")
schedule_workflow_active_matches_now="TRUE"
break
elif [[ $workflow_time_epoch -le $schedule_workflow_active_match_start_epoch ]]; then
schedule_workflow_active_match_start_epochs_array+=("${schedule_workflow_active_match_start_epoch}")
fi
fi
done
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: schedule_workflow_active_matches_now: ${schedule_workflow_active_matches_now}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: schedule_workflow_active_match_start_epochs_array is:\n${schedule_workflow_active_match_start_epochs_array[*]}"
fi
# At this point if a new ${scheduled_install_past_deadline} and there is no ${schedule_workflow_active_match_start_epochs_array[*]}, the install should be ASAP.
if [[ "${scheduled_install_past_deadline}" == "TRUE" ]] && [[ -z "${schedule_workflow_active_match_start_epochs_array[*]}" ]]; then
[[ $(echo "${workflow_scheduled_install}" | grep -c ':23:59$') -gt 0 ]] && workflow_scheduled_install="${workflow_scheduled_install:0:10}:00:00"
return 0
fi
# If ${scheduled_install_days} is zero and there are no matching time frames yet, then loop through the next 7 weekdays starting from tomorrow and set ${schedule_workflow_active_match_start_epochs_array[*]} accordingly.
if { [[ -n "${scheduled_install_days}" ]] && [[ $scheduled_install_days -eq 0 ]]; } && [[ -z "${schedule_workflow_active_match_start_epochs_array[*]}" ]]; then
for ((counter=1; counter<8; ++counter)); do
workflow_scheduled_install_weekday="$(date -v+${counter}d +%a | tr '[:lower:]' '[:upper:]')"
for schedule_workflow_active_time_frame in "${schedule_workflow_active_array[@]}"; do
if [[ "${schedule_workflow_active_time_frame:0:3}" == "${workflow_scheduled_install_weekday}" ]]; then
schedule_workflow_active_match_start_epochs_array+=($(date -v+"${counter}"d -v"${schedule_workflow_active_time_frame:4:2}"H -v"${schedule_workflow_active_time_frame:7:2}"M -v00S +%s))
fi
done
[[ -n "${schedule_workflow_active_match_start_epochs_array[*]}" ]] && break
done
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: schedule_workflow_active_match_start_epochs_array is:\n${schedule_workflow_active_match_start_epochs_array[*]}"
fi
# If needed, find future ${schedule_workflow_active} time frames by looping through 7 weekdays in reverse from the scheduled day and set ${schedule_workflow_active_match_start_epochs_array[*]} accordingly.
if [[ -z "${schedule_workflow_active_match_start_epochs_array[*]}" ]]; then
local workflow_scheduled_install_days_away
workflow_scheduled_install_days_away=$((($(date -j -f %Y-%m-%d:%H:%M:%S "${workflow_scheduled_install:0:10}:23:23:59" +%s) - $(date -v+0d -v0H -v0M -v0S +%s)) / 86400))
workflow_scheduled_install_weekday="$(date -j -f %Y-%m-%d:%H:%M:%S "${workflow_scheduled_install}:00" +%a | tr '[:lower:]' '[:upper:]')"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: workflow_scheduled_install_days_away: ${workflow_scheduled_install_days_away}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: workflow_scheduled_install_weekday: ${workflow_scheduled_install_weekday}"
for ((counter=0; counter<6; ++counter)); do
if [[ $((workflow_scheduled_install_days_away - counter)) -ge 1 ]]; then
for schedule_workflow_active_time_frame in "${schedule_workflow_active_array[@]}"; do
[[ "${schedule_workflow_active_time_frame:0:3}" == "${workflow_scheduled_install_weekday}" ]] && schedule_workflow_active_match_start_epochs_array+=($(date -j -v+$((workflow_scheduled_install_days_away - counter))d -v"${schedule_workflow_active_time_frame:4:2}"H -v"${schedule_workflow_active_time_frame:7:2}"M -v00S +%s))
done
else # No more weekdays to check.
break
fi
done
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: schedule_workflow_active_match_start_epochs_array is:\n${schedule_workflow_active_match_start_epochs_array[*]}"
fi
# If at least one matching workflow active timeframe was identified then set ${workflow_scheduled_install_adjusted}.
if [[ -n "${schedule_workflow_active_match_start_epochs_array[*]}" ]]; then
IFS=$'\n'
[[ "${schedule_workflow_active_matches_now}" == "TRUE" ]] && schedule_workflow_active_match_weekday_start_epoch=${schedule_workflow_active_match_start_epochs_array[0]}
[[ "${schedule_workflow_active_matches_now}" == "FALSE" ]] && schedule_workflow_active_match_weekday_start_epoch=$(echo "${schedule_workflow_active_match_start_epochs_array[*]}" | sort -r | head -1)
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: schedule_workflow_active_match_weekday_start_epoch: ${schedule_workflow_active_match_weekday_start_epoch}"
workflow_scheduled_install_adjusted="$(date -j -f %s "${schedule_workflow_active_match_weekday_start_epoch}" +%Y-%m-%d:%H:%M)"
[[ -n "${scheduled_install_days}" ]] && log_super "Warning: Adjusting the original scheduled installation of $(date -j -f %Y-%m-%d "${workflow_scheduled_install:0:10}" +%a | tr '[:lower:]' '[:upper:]') ${workflow_scheduled_install:0:10} (${scheduled_install_days} days after zero day) to coordinate with a schedule workflow active time frame."
[[ -n "${scheduled_install_date}" ]] && log_super "Warning: Adjusting the original manually scheduled installation of $(date -j -f %Y-%m-%d "${workflow_scheduled_install:0:10}" +%a | tr '[:lower:]' '[:upper:]') ${workflow_scheduled_install:0:10} to coordinate with a schedule workflow active time frame."
fi
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: workflow_scheduled_install_adjusted: ${workflow_scheduled_install_adjusted}"
{ [[ -z "${workflow_scheduled_install_adjusted}" ]] && [[ $(echo "${workflow_scheduled_install}" | grep -c ':23:59$') -gt 0 ]]; } && workflow_scheduled_install="${workflow_scheduled_install:0:10}:00:00"
fi
# If there is a ${workflow_scheduled_install} via the ${scheduled_install_user_choice_option} then compare it to the ${schedule_workflow_active_option} for possible adjustment.
if [[ "${scheduled_install_user_choice_option}" == "TRUE" ]]; then
local scheduled_install_user_choice_match
scheduled_install_user_choice_match="FALSE"
scheduled_install_user_choice_adjusted="FALSE"
local workflow_scheduled_install_epoch_temp
workflow_scheduled_install_epoch_temp=$(date -j -f %Y-%m-%d:%H:%M:%S "${workflow_scheduled_install}:00" +%s)
local workflow_scheduled_install_days_away
workflow_scheduled_install_days_away=$((($(date -j -f %Y-%m-%d:%H:%M:%S "${workflow_scheduled_install:0:10}:23:23:59" +%s) - $(date -v+0d -v0H -v0M -v0S +%s)) / 86400))
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: workflow_scheduled_install: ${workflow_scheduled_install}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: workflow_scheduled_install_epoch_temp: ${workflow_scheduled_install_epoch_temp}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: workflow_scheduled_install_days_away: ${workflow_scheduled_install_days_away}"
# If needed, look for a ${schedule_workflow_active} time frame that matches today to set ${scheduled_install_user_choice_match} and ${schedule_workflow_active_matches_now} accordingly.
if [[ $workflow_scheduled_install_days_away -eq 0 ]]; then
for schedule_workflow_active_time_frame in "${schedule_workflow_active_array[@]}"; do
if [[ "${schedule_workflow_active_time_frame:0:3}" == "${workflow_time_weekday}" ]]; then
schedule_workflow_active_match_start_epoch=$(date -j -f %H:%M:%S "${schedule_workflow_active_time_frame:4:5}:00" +%s)
schedule_workflow_active_match_end_epoch=$(date -j -f %H:%M:%S "${schedule_workflow_active_time_frame:10:5}:00" +%s)
if [[ $schedule_workflow_active_match_start_epoch -le $workflow_scheduled_install_epoch_temp ]] && [[ $workflow_scheduled_install_epoch_temp -le $schedule_workflow_active_match_end_epoch ]]; then
scheduled_install_user_choice_match="TRUE"
{ [[ $schedule_workflow_active_match_start_epoch -le $workflow_time_epoch ]] && [[ $workflow_time_epoch -le $schedule_workflow_active_match_end_epoch ]]; } && schedule_workflow_active_matches_now="TRUE"
break
fi
fi
done
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: scheduled_install_user_choice_match: ${scheduled_install_user_choice_match}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: schedule_workflow_active_matches_now: ${schedule_workflow_active_matches_now}"
fi
# If needed, find future ${schedule_workflow_active} time frames by looping through 7 weekdays in reverse from the scheduled day and set ${schedule_workflow_active_match_start_epochs_array[*]} accordingly.
if [[ "${scheduled_install_user_choice_match}" == "FALSE" ]]; then
local workflow_scheduled_install_weekday
workflow_scheduled_install_weekday="$(date -j -f %Y-%m-%d:%H:%M:%S "${workflow_scheduled_install}:00" +%a | tr '[:lower:]' '[:upper:]')"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: workflow_scheduled_install_days_away: ${workflow_scheduled_install_days_away}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: workflow_scheduled_install_weekday: ${workflow_scheduled_install_weekday}"
for ((counter=0; counter<6; ++counter)); do
if [[ $((workflow_scheduled_install_days_away - counter)) -ge 0 ]]; then
for schedule_workflow_active_time_frame in "${schedule_workflow_active_array[@]}"; do
if [[ "${schedule_workflow_active_time_frame:0:3}" == "${workflow_scheduled_install_weekday}" ]]; then
schedule_workflow_active_match_start_epoch=$(date -j -v+$((workflow_scheduled_install_days_away - counter))d -v"${schedule_workflow_active_time_frame:4:2}"H -v"${schedule_workflow_active_time_frame:7:2}"M -v00S +%s)
schedule_workflow_active_match_end_epoch=$(date -j -v+$((workflow_scheduled_install_days_away - counter))d -v"${schedule_workflow_active_time_frame:10:2}"H -v"${schedule_workflow_active_time_frame:13:2}"M -v00S +%s)
if [[ $schedule_workflow_active_match_start_epoch -le $workflow_scheduled_install_epoch_temp ]] && [[ $workflow_scheduled_install_epoch_temp -le $schedule_workflow_active_match_end_epoch ]]; then
scheduled_install_user_choice_match="TRUE"
break
elif [[ $workflow_scheduled_install_epoch_temp -le $schedule_workflow_active_match_start_epoch ]]; then
schedule_workflow_active_match_start_epochs_array+=($(date -j -v+$((workflow_scheduled_install_days_away - counter))d -v"${schedule_workflow_active_time_frame:4:2}"H -v"${schedule_workflow_active_time_frame:7:2}"M -v00S +%s))
fi
fi
done
else # No more weekdays to check.
break
fi
done
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: scheduled_install_user_choice_match: ${scheduled_install_user_choice_match}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: schedule_workflow_active_match_start_epochs_array is:\n${schedule_workflow_active_match_start_epochs_array[*]}"
fi
# If there is still no matching time frame, then loop through the following 7 weekdays starting from tomorrow and set ${schedule_workflow_active_match_start_epochs_array[*]} accordingly.
if [[ "${scheduled_install_user_choice_match}" == "FALSE" ]] && [[ -z "${schedule_workflow_active_match_start_epochs_array[*]}" ]]; then
for ((counter=1; counter<8; ++counter)); do
workflow_scheduled_install_weekday="$(date -v+$((workflow_scheduled_install_days_away + counter))d +%a | tr '[:lower:]' '[:upper:]')"
for schedule_workflow_active_time_frame in "${schedule_workflow_active_array[@]}"; do
if [[ "${schedule_workflow_active_time_frame:0:3}" == "${workflow_scheduled_install_weekday}" ]]; then
schedule_workflow_active_match_start_epochs_array+=($(date -v+$((workflow_scheduled_install_days_away + counter))d -v"${schedule_workflow_active_time_frame:4:2}"H -v"${schedule_workflow_active_time_frame:7:2}"M -v00S +%s))
fi
done
[[ -n "${schedule_workflow_active_match_start_epochs_array[*]}" ]] && break
done
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: schedule_workflow_active_match_start_epochs_array is:\n${schedule_workflow_active_match_start_epochs_array[*]}"
fi
# If at least one matching workflow active timeframe was identified so set ${workflow_scheduled_install_adjusted}.
if [[ "${scheduled_install_user_choice_match}" == "TRUE" ]]; then
if [[ $workflow_scheduled_install_days_away -eq 0 ]]; then
[[ "${schedule_workflow_active_matches_now}" == "TRUE" ]] && log_super "Status: The current user selected scheduled installation falls within the current schedule workflow active time frame."
[[ "${schedule_workflow_active_matches_now}" == "FALSE" ]] && log_super "Status: The current user selected scheduled installation falls within a future schedule workflow active time frame later today."
else
log_super "Status: The current user selected scheduled installation falls within a future schedule workflow active time frame on another day."
fi
workflow_scheduled_install_adjusted="${workflow_scheduled_install}"
elif [[ -n "${schedule_workflow_active_match_start_epochs_array[*]}" ]]; then
IFS=$'\n'
schedule_workflow_active_match_weekday_start_epoch=$(echo "${schedule_workflow_active_match_start_epochs_array[*]}" | sort -r | head -1)
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: schedule_workflow_active_match_weekday_start_epoch: ${schedule_workflow_active_match_weekday_start_epoch}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: deadline_epoch: ${deadline_epoch}"
if [[ -n "${deadline_epoch}" ]] && [[ $deadline_epoch -lt $schedule_workflow_active_match_weekday_start_epoch ]]; then
workflow_scheduled_install_adjusted="$(date -j -f %s "${deadline_epoch}" +%Y-%m-%d:%H:%M)"
log_super "Warning: Adjusting the original user selected scheduled installation of $(date -j -f %Y-%m-%d "${workflow_scheduled_install:0:10}" +%a | tr '[:lower:]' '[:upper:]') ${workflow_scheduled_install:0:10} to coordinate with the current deadline."
else
workflow_scheduled_install_adjusted="$(date -j -f %s "${schedule_workflow_active_match_weekday_start_epoch}" +%Y-%m-%d:%H:%M)"
log_super "Warning: Adjusting the original user selected scheduled installation of $(date -j -f %Y-%m-%d "${workflow_scheduled_install:0:10}" +%a | tr '[:lower:]' '[:upper:]') ${workflow_scheduled_install:0:10} to coordinate with a schedule workflow active time frame."
fi
scheduled_install_user_choice_adjusted="TRUE"
fi
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: scheduled_install_user_choice_adjusted: ${scheduled_install_user_choice_adjusted}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: workflow_scheduled_install_adjusted: ${workflow_scheduled_install_adjusted}"
fi
IFS="${previous_ifs}"
}
# Download the SOFA macOS releases json feed and set ${sofa_macos_json} and cache information.
get_sofa_macos_json() {
sofa_macos_cache="FALSE"
sofa_macos_json="FALSE"
# First check for an existing local SOFA macOS releases json feed cache.
local sofa_macos_json_checksum_cache
sofa_macos_json_checksum_cache=$(defaults read "${SUPER_LOCAL_PLIST}" SOFAMacOSJsonCacheChecksum 2>/dev/null)
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: sofa_macos_json_checksum_cache is: ${sofa_macos_json_checksum_cache}"
if [[ -n "${sofa_macos_json_checksum_cache}" ]]; then
if [[ -e "${SOFA_MACOS_JSON_ETAG_CACHE}" ]] && [[ "${sofa_macos_json_checksum_cache}" == "$(md5 -q "${SOFA_MACOS_JSON_CACHE}" 2>/dev/null)" ]]; then
sofa_macos_cache="TRUE"
else
log_super "Status: The SOFA macOS releases feed cache is not valid, a new feed must be downloaded."
defaults delete "${SUPER_LOCAL_PLIST}" SOFAMacOSJsonCacheChecksum 2>/dev/null
rm -f "${SOFA_MACOS_JSON_ETAG_CACHE}" 2>/dev/null
rm -f "${SOFA_MACOS_JSON_CACHE}" 2>/dev/null
fi
fi
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: sofa_macos_cache is: ${sofa_macos_cache}"
# Check SOFA macOS releases json feed cache via HTTP ETag or download a new feed.
local curl_response
if [[ "${sofa_macos_cache}" == "TRUE" ]]; then
log_super "Status: Validating SOFA macOS releases feed cache..."
curl_response=$(curl --write-out "%{http_code}" --max-time 5 --etag-compare "${SOFA_MACOS_JSON_ETAG_CACHE}" --location "${sofa_macos_url}" --etag-save "${SOFA_MACOS_JSON_ETAG_CACHE}" --output "${SOFA_MACOS_JSON_CACHE}" 2>&1)
[[ $(echo "${curl_response}" | grep -c '200') -gt 0 ]] && log_super "Status: An updated SOFA macOS releases feed was downloaded."
else
log_super "Status: Downloading new SOFA macOS releases feed..."
curl_response=$(curl --write-out "%{http_code}" --max-time 5 --location "${sofa_macos_url}" --etag-save "${SOFA_MACOS_JSON_ETAG_CACHE}" --output "${SOFA_MACOS_JSON_CACHE}" 2>&1)
fi
# Verify the SOFA releases json feed download completed and is valid.
if { [[ $(echo "${curl_response}" | grep -c '304') -gt 0 ]] || [[ $(echo "${curl_response}" | grep -c '200') -gt 0 ]]; } && [[ -e "${SOFA_MACOS_JSON_ETAG_CACHE}" ]] && [[ -e "${SOFA_MACOS_JSON_CACHE}" ]]; then
if [[ $(sqlite3 /dev/null "SELECT json_valid(readfile('${SOFA_MACOS_JSON_CACHE}'))") -eq 1 ]] && [[ -n $(sqlite3 /dev/null "SELECT json_extract(readfile('${SOFA_MACOS_JSON_CACHE}'), '$.UpdateHash')") ]]; then
sofa_macos_json="TRUE"
else
log_super "Error: The downloaded SOFA macOS releases feed ${SOFA_MACOS_JSON_CACHE} appears to be invalid."
fi
else
log_super "Error: SOFA macOS releases feed failed to download: ${curl_response}"
fi
# If the ${SOFA_MACOS_JSON_CACHE} is valid, then save cache information.
if [[ "${sofa_macos_json}" == "TRUE" ]]; then
defaults write "${SUPER_LOCAL_PLIST}" SOFAMacOSJsonCacheChecksum -string "$(md5 -q "${SOFA_MACOS_JSON_CACHE}" 2>/dev/null)"
else
defaults delete "${SUPER_LOCAL_PLIST}" SOFAMacOSJsonCacheChecksum 2>/dev/null
rm -f "${SOFA_MACOS_JSON_ETAG_CACHE}" 2>/dev/null
fi
}
# Evaluate ${schedule_zero_date_manual} and ${schedule_zero_date_release_option} to set ${schedule_zero_date}, ${schedule_zero_date_epoch}, and ${display_string_schedule_zero_date} accordingly.
check_schedule_zero_date() {
schedule_zero_date="FALSE"
local schedule_zero_date_automatic_release_previous
schedule_zero_date_automatic_release_previous=$(defaults read "${SUPER_LOCAL_PLIST}" ScheduleZeroDateAutomaticRelease 2>/dev/null)
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: schedule_zero_date_automatic_release_previous: ${schedule_zero_date_automatic_release_previous}"
local schedule_zero_date_automatic_release_failover_previous
schedule_zero_date_automatic_release_failover_previous=$(defaults read "${SUPER_LOCAL_PLIST}" ScheduleZeroDateAutomaticReleaseFailover 2>/dev/null)
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: schedule_zero_date_automatic_release_failover_previous: ${schedule_zero_date_automatic_release_failover_previous}"
local schedule_zero_date_automatic_start_previous
schedule_zero_date_automatic_start_previous=$(defaults read "${SUPER_LOCAL_PLIST}" ScheduleZeroDateAutomaticStart 2>/dev/null)
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: schedule_zero_date_automatic_start_previous: ${schedule_zero_date_automatic_start_previous}"
# If there is a ${schedule_zero_date_manual} then just use that.
if [[ -n "${schedule_zero_date_manual}" ]]; then
schedule_zero_date="${schedule_zero_date_manual}"
log_super "Warning: Using manually set zero date of ${schedule_zero_date}. This date does not change automatically."
[[ -n "${schedule_zero_date_automatic_start_previous}" ]] && defaults delete "${SUPER_LOCAL_PLIST}" ScheduleZeroDateAutomaticStart 2>/dev/null
[[ -n "${schedule_zero_date_automatic_start_previous}" ]] && unset schedule_zero_date_automatic_start_previous
[[ -n "${schedule_zero_date_automatic_release_failover_previous}" ]] && defaults delete "${SUPER_LOCAL_PLIST}" ScheduleZeroDateAutomaticReleaseFailover 2>/dev/null
[[ -n "${schedule_zero_date_automatic_release_failover_previous}" ]] && unset schedule_zero_date_automatic_release_failover_previous
[[ -n "${schedule_zero_date_automatic_release_previous}" ]] && defaults delete "${SUPER_LOCAL_PLIST}" ScheduleZeroDateAutomaticRelease 2>/dev/null
[[ -n "${schedule_zero_date_automatic_release_previous}" ]] && unset schedule_zero_date_automatic_release_previous
fi
# If no ${schedule_zero_date} yet then check for previously set automatic zero dates.
if [[ "${schedule_zero_date}" == "FALSE" ]]; then
# If the ${workflow_target} is not a macOS version then the automatic release date cannot be used.
if { [[ "${workflow_target}" == "Non-system Software Updates" ]] || [[ "${workflow_target}" == "Jamf Pro Policy Triggers Without Restarting" ]] || [[ "${workflow_target}" == "Jamf Pro Policy Triggers With Restart" ]] || [[ "${workflow_target}" == "Restart Without Updates" ]]; } && [[ "${schedule_zero_date_release_option}" == "TRUE" ]]; then
log_super "Warning: The --schedule-zero-date-release option can not be used with the current ${workflow_target} workflow target."
schedule_zero_date_release_option="FALSE"
fi
# If the current ${workflow_target} matches the previously set workflow target then collect any previously saved zero dates.
local workflow_target_previous
workflow_target_previous=$(defaults read "${SUPER_LOCAL_PLIST}" WorkflowTarget 2>/dev/null)
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: workflow_target: ${workflow_target}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: workflow_target_previous: ${workflow_target_previous}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: schedule_zero_date_release_option: ${schedule_zero_date_release_option}"
if [[ "${workflow_target}" == "${workflow_target_previous}" ]]; then
if [[ "${schedule_zero_date_release_option}" == "TRUE" ]] && [[ "${schedule_zero_date_automatic_release_failover_previous}" -ne 1 ]]; then
if [[ -n "${schedule_zero_date_automatic_release_previous}" ]]; then
schedule_zero_date="${schedule_zero_date_automatic_release_previous}"
log_super "Status: Previously set automatic zero date based on the ${workflow_target} release date of ${schedule_zero_date}."
fi
[[ -n "${schedule_zero_date_automatic_release_failover_previous}" ]] && defaults delete "${SUPER_LOCAL_PLIST}" ScheduleZeroDateAutomaticReleaseFailover 2>/dev/null
[[ -n "${schedule_zero_date_automatic_release_failover_previous}" ]] && unset schedule_zero_date_automatic_release_failover_previous
[[ -n "${schedule_zero_date_automatic_start_previous}" ]] && defaults delete "${SUPER_LOCAL_PLIST}" ScheduleZeroDateAutomaticStart 2>/dev/null
[[ -n "${schedule_zero_date_automatic_start_previous}" ]] && unset schedule_zero_date_automatic_start_previous
else # Automatic workflow start date or automatic release failover.
if [[ -n "${schedule_zero_date_automatic_start_previous}" ]]; then
schedule_zero_date="${schedule_zero_date_automatic_start_previous}"
log_super "Status: Previously set automatic zero date based on the ${workflow_target} workflow start date of ${schedule_zero_date}."
fi
[[ -n "${schedule_zero_date_automatic_release_previous}" ]] && defaults delete "${SUPER_LOCAL_PLIST}" ScheduleZeroDateAutomaticRelease 2>/dev/null
[[ -n "${schedule_zero_date_automatic_release_previous}" ]] && unset schedule_zero_date_automatic_release_previous
fi
else # If the current ${workflow_target} does not match the previously saved target.
{ [[ -n "${workflow_target_previous}" ]] && [[ "${workflow_target_previous}" != "FALSE" ]]; } && log_super "Warning: The previously saved ${workflow_target_previous} workflow target does not match the current ${workflow_target} workflow target."
fi
fi
# If no ${schedule_zero_date} yet and the ${schedule_zero_date_release_option} is enabled then download ${SOFA_MACOS_JSON_CACHE} if it hasn't already been cached.
if [[ "${schedule_zero_date}" == "FALSE" ]] && [[ "${schedule_zero_date_release_option}" == "TRUE" ]]; then
sofa_macos_json="FALSE"
local schedule_zero_date_release_failover
schedule_zero_date_release_failover="FALSE"
local schedule_zero_date_version_target
schedule_zero_date_version_target="FALSE"
[[ "${macos_installer_target}" != "FALSE" ]] && schedule_zero_date_version_target="${macos_installer_version}"
{ [[ "${macos_msu_major_upgrade_target}" != "FALSE" ]] || [[ "${macos_msu_minor_update_target}" != "FALSE" ]]; } && schedule_zero_date_version_target="${macos_msu_version}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: schedule_zero_date_version_target: ${schedule_zero_date_version_target}"
if [[ $(echo "${schedule_zero_date_version_target}" | awk -F '.' '{print $1}') -ge 13 ]]; then
# If required collecte a new ${sofa_macos_json}.
if [[ "${check_software_status_required}" == "TRUE" ]] || [[ "${sofa_macos_json}" == "FALSE" ]]; then
get_sofa_macos_json
# Error handling if curl is misbehaving.
if [[ "${sofa_macos_json}" == "FALSE" ]]; then
log_super "Warning: Re-starting downloading SOFA macOS releases feed..."
get_sofa_macos_json
fi
if [[ "${sofa_macos_json}" == "FALSE" ]]; then
log_super "Warning: Downloading SOFA macOS releases feed did not complete after multiple attempts, failing over to automatic zero date based on the workflow start date."
schedule_zero_date_release_failover="TRUE"
fi
fi
else # The ${schedule_zero_date_version_target} is too old.
log_super "Warning: The workflow target of macOS $(echo "${schedule_zero_date_version_target}" | awk -F '.' '{print $1}') is no longer updated by Apple therefore all release dates are well past being useful, failing over to automatic zero date based on the workflow start date."
sofa_macos_json="FALSE"
schedule_zero_date_release_failover="TRUE"
fi
fi
# If no ${schedule_zero_date} yet and the ${schedule_zero_date_release_option} is enabled and there is a ${schedule_zero_date_version_target} and there appears to be a valid ${SOFA_MACOS_JSON_CACHE}, then attempt to parse and find the release date associated with ${schedule_zero_date_version_target}
if [[ "${schedule_zero_date}" == "FALSE" ]] && [[ "${schedule_zero_date_version_target}" != "FALSE" ]] && [[ "${schedule_zero_date_release_option}" == "TRUE" ]] && [[ "${sofa_macos_json}" == "TRUE" ]]; then
local sofa_osversions_array_index
for array_index in $(seq 0 "$(sqlite3 /dev/null "SELECT json_array_length(readfile('${SOFA_MACOS_JSON_CACHE}'), '$.OSVersions')"-1 2>/dev/null)"); do
if [[ $(sqlite3 /dev/null "SELECT json_extract(readfile('${SOFA_MACOS_JSON_CACHE}'), '$.OSVersions[$array_index].OSVersion')" 2>/dev/null | awk '{print $2;}') -eq $(echo "${schedule_zero_date_version_target}" | awk -F '.' '{print $1}') ]]; then
sofa_osversions_array_index=$array_index
break
fi
done
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: sofa_osversions_array_index: ${sofa_osversions_array_index}"
for array_index in $(seq 0 "$(sqlite3 /dev/null "SELECT json_array_length(readfile('${SOFA_MACOS_JSON_CACHE}'), '$.OSVersions[$sofa_osversions_array_index].SecurityReleases')"-1 2>/dev/null)"); do
if [[ $(sqlite3 /dev/null "SELECT json_extract(readfile('${SOFA_MACOS_JSON_CACHE}'), '$.OSVersions[$sofa_osversions_array_index].SecurityReleases[$array_index].ProductVersion')" 2>/dev/null) == "${schedule_zero_date_version_target}" ]]; then
schedule_zero_date=$(sqlite3 /dev/null "SELECT json_extract(readfile('${SOFA_MACOS_JSON_CACHE}'), '$.OSVersions[$sofa_osversions_array_index].SecurityReleases[$array_index].ReleaseDate')" 2>/dev/null | sed -e 's/T/:/g' -e 's/:00Z//g')
log_super "Status: Setting new automatic zero date based on the ${workflow_target} release date of ${schedule_zero_date}."
defaults write "${SUPER_LOCAL_PLIST}" ScheduleZeroDateAutomaticRelease -string "${schedule_zero_date}"
[[ -n "${schedule_zero_date_automatic_start_previous}" ]] && defaults delete "${SUPER_LOCAL_PLIST}" ScheduleZeroDateAutomaticStart 2>/dev/null
[[ -n "${schedule_zero_date_automatic_start_previous}" ]] && unset schedule_zero_date_automatic_start_previous
break
fi
done
if [[ "${schedule_zero_date}" == "FALSE" ]]; then
log_super "Warning: Unable to find release date for target macOS ${schedule_zero_date_version_target}, failing over to automatic zero date based on the workflow start date."
schedule_zero_date_release_failover="TRUE"
fi
fi
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: schedule_zero_date_release_failover: ${schedule_zero_date_release_failover}"
# If there is no ${schedule_zero_date} at this point then set one based on workflow start date/time of now.
if [[ "${schedule_zero_date}" == "FALSE" ]]; then
schedule_zero_date=$(date +%Y-%m-%d:%H:%M)
log_super "Status: Setting new automatic zero date based on the ${workflow_target} workflow start date of ${schedule_zero_date}."
defaults write "${SUPER_LOCAL_PLIST}" ScheduleZeroDateAutomaticStart -string "${schedule_zero_date}"
defaults delete "${SUPER_LOCAL_PLIST}" ScheduleZeroDateAutomaticRelease 2>/dev/null
[[ -n "${schedule_zero_date_automatic_release_previous}" ]] && defaults delete "${SUPER_LOCAL_PLIST}" ScheduleZeroDateAutomaticRelease 2>/dev/null
[[ -n "${schedule_zero_date_automatic_release_previous}" ]] && unset schedule_zero_date_automatic_release_previous
if [[ "${schedule_zero_date_release_failover}" == "TRUE" ]]; then
defaults write "${SUPER_LOCAL_PLIST}" ScheduleZeroDateAutomaticReleaseFailover -bool true
else
[[ -n "${schedule_zero_date_automatic_release_failover_previous}" ]] && defaults delete "${SUPER_LOCAL_PLIST}" ScheduleZeroDateAutomaticReleaseFailover 2>/dev/null
[[ -n "${schedule_zero_date_automatic_release_failover_previous}" ]] && unset schedule_zero_date_automatic_release_failover_previous
fi
fi
# Set remaining zero date parameters.
schedule_zero_date_epoch=$(date -j -f %Y-%m-%d:%H:%M:%S "${schedule_zero_date}:00" +%s)
local display_string_schedule_zero_date_only_date
display_string_schedule_zero_date_only_date=$(date -r "${schedule_zero_date_epoch}" "+${DISPLAY_STRING_FORMAT_DATE}")
local display_string_schedule_zero_date_only_time
display_string_schedule_zero_date_only_time=$(date -r "${schedule_zero_date_epoch}" "+${DISPLAY_STRING_FORMAT_TIME}" | sed 's/^ *//g')
if [[ $(date -r "${schedule_zero_date_epoch}" +%H:%M) == "00:00" ]]; then
display_string_schedule_zero_date="${display_string_schedule_zero_date_only_date}"
else
display_string_schedule_zero_date="${display_string_schedule_zero_date_only_date}${DISPLAY_STRING_FORMAT_DATE_TIME_SEPARATOR}${display_string_schedule_zero_date_only_time}"
fi
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: schedule_zero_date_epoch: ${schedule_zero_date_epoch}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: display_string_schedule_zero_date_only_date: ${display_string_schedule_zero_date_only_date}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: display_string_schedule_zero_date_only_time: ${display_string_schedule_zero_date_only_time}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: display_string_schedule_zero_date: ${display_string_schedule_zero_date}"
}
# Evaluate any previously set ${workflow_scheduled_install_active}, ${scheduled_install_days}, and ${scheduled_install_date} options.
# If needed will set ${workflow_scheduled_install}, ${workflow_scheduled_install_epoch}, ${workflow_scheduled_install_active}, ${workflow_scheduled_install_now}, ${scheduled_install_past_deadline}, ${scheduled_install_suppress_reminder}, ${scheduled_install_final_reminder}, and ${scheduled_install_user_choice_active}.
check_scheduled_install() {
workflow_scheduled_install_now="FALSE"
scheduled_install_past_deadline="FALSE"
scheduled_install_suppress_reminder="FALSE"
scheduled_install_final_reminder="FALSE"
scheduled_install_user_choice_active="FALSE"
# If there is no ${workflow_scheduled_install} yet then, if needed, set it per the ${scheduled_install_days} or ${scheduled_install_date} options.
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: scheduled_install_days: ${scheduled_install_days}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: scheduled_install_date: ${scheduled_install_date}"
if [[ -z "${workflow_scheduled_install}" ]] && { [[ -n "${scheduled_install_days}" ]] || [[ -n "${scheduled_install_date}" ]]; }; then
local workflow_scheduled_install_epoch_temp
if [[ -n "${scheduled_install_days}" ]]; then
if [[ $scheduled_install_days -eq 0 ]]; then
log_super "Warning: The --scheduled-install-days option is set to 0. Adjusting scheduled installation date to today."
workflow_scheduled_install="$(date -r "$((workflow_time_epoch - 1))" +%Y-%m-%d):00:00"
else
workflow_scheduled_install_epoch_temp=$((schedule_zero_date_epoch + (scheduled_install_days * 86400)))
workflow_scheduled_install="$(date -r "${workflow_scheduled_install_epoch_temp}" +%Y-%m-%d):00:00"
if [[ $workflow_scheduled_install_epoch_temp -le $workflow_time_epoch ]]; then
log_super "Warning: The --scheduled-install-days option of ${scheduled_install_days} days is in the past given the current zero day of ${display_string_schedule_zero_date}."
scheduled_install_past_deadline="TRUE"
fi
fi
scheduled_install_suppress_reminder="TRUE"
fi
if [[ -n "${scheduled_install_date}" ]]; then
[[ -n "${scheduled_install_date}" ]] && log_super "Warning: The date and time specified in the --scheduled-install-date option does not change automatically when newer updates or workflows are made available."
workflow_scheduled_install="${scheduled_install_date}"
if [[ $(date -j -f %Y-%m-%d:%H:%M:%S "${workflow_scheduled_install}:00" +%s) -le $workflow_time_epoch ]]; then
log_super "Warning: The date and time specified in the --scheduled-install-date option of ${scheduled_install_date} is in the past."
scheduled_install_past_deadline="TRUE"
fi
scheduled_install_suppress_reminder="TRUE"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: scheduled_install_past_deadline: ${scheduled_install_past_deadline}"
fi
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: workflow_scheduled_install: ${workflow_scheduled_install}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: scheduled_install_suppress_reminder: ${scheduled_install_suppress_reminder}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: workflow_scheduled_install: ${workflow_scheduled_install}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: scheduled_install_past_deadline: ${scheduled_install_past_deadline}"
# If needed adjust for the ${schedule_workflow_active_option} and also write to log.
if [[ -n "${schedule_workflow_active_option}" ]] && { [[ -n "${scheduled_install_days}" ]] || [[ $(echo "${scheduled_install_date}" | grep -c ':00:00$') -gt 0 ]]; }; then
set_schedule_workflow_active_adjustments
if [[ -n "${workflow_scheduled_install_adjusted}" ]]; then
workflow_scheduled_install="${workflow_scheduled_install_adjusted}"
if [[ -n "${scheduled_install_days}" ]] && [[ $scheduled_install_days -eq 0 ]]; then
log_super "Status: Setting new scheduled installation for $(date -j -f %Y-%m-%d "${workflow_scheduled_install:0:10}" +%a | tr '[:lower:]' '[:upper:]') ${workflow_scheduled_install} which is the start of the soonest workflow active time frame after zero day."
else
[[ "${schedule_workflow_active_matches_now}" == "TRUE" ]] && log_super "Status: Setting new scheduled installation for $(date -j -f %Y-%m-%d "${workflow_scheduled_install:0:10}" +%a | tr '[:lower:]' '[:upper:]') ${workflow_scheduled_install} which is the start of the current schedule workflow active time frame."
[[ "${schedule_workflow_active_matches_now}" == "FALSE" ]] && log_super "Status: Setting new scheduled installation for $(date -j -f %Y-%m-%d "${workflow_scheduled_install:0:10}" +%a | tr '[:lower:]' '[:upper:]') ${workflow_scheduled_install} which is the start of the latest workflow active time frame on the original scheduled installation date."
fi
else
[[ -n "${scheduled_install_days}" ]] && log_super "Warning: The current scheduled installation for $(date -j -f %Y-%m-%d "${workflow_scheduled_install:0:10}" +%a | tr '[:lower:]' '[:upper:]') ${workflow_scheduled_install} (${scheduled_install_days} days after zero day) is overriding the schedule workflow active option because no coordinating time frame could be resolved."
[[ -n "${scheduled_install_date}" ]] && log_super "Warning: The current scheduled installation for $(date -j -f %Y-%m-%d "${workflow_scheduled_install:0:10}" +%a | tr '[:lower:]' '[:upper:]') ${workflow_scheduled_install} is overriding the schedule workflow active option because no coordinating time frame could be resolved."
fi
elif [[ -n "${schedule_workflow_active_option}" ]]; then
log_super "Warning: The manually scheduled installation date option that also includes a specific time of ${workflow_scheduled_install} is overriding the schedule workflow active option."
else # Default super workflow.
[[ -n "${scheduled_install_days}" ]] && log_super "Status: Setting new scheduled installation for ${workflow_scheduled_install} which is ${scheduled_install_days} days after zero date."
[[ -n "${scheduled_install_date}" ]] && log_super "Status: Using manually scheduled installation date and time of ${workflow_scheduled_install}."
fi
defaults write "${SUPER_LOCAL_PLIST}" WorkflowScheduledInstall -string "${workflow_scheduled_install}"
fi
# If there is a ${workflow_scheduled_install} then set both ${workflow_scheduled_install_epoch} and ${workflow_scheduled_install_active}.
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: workflow_scheduled_install: ${workflow_scheduled_install}"
if [[ -n "${workflow_scheduled_install}" ]]; then
workflow_scheduled_install_epoch=$(date -j -f %Y-%m-%d:%H:%M:%S "${workflow_scheduled_install}:00" +%s)
workflow_scheduled_install_active="TRUE"
fi
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: workflow_scheduled_install_epoch: ${workflow_scheduled_install_epoch}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: workflow_scheduled_install_active: ${workflow_scheduled_install_active}"
# If a scheduled install is active then set display strings and evaluate ${workflow_scheduled_install_now}.
if [[ "${workflow_scheduled_install_active}" == "TRUE" ]]; then
# Set various display options for scheduled installation dialogs.
[[ $(echo "${display_unmovable_option}" | grep -c 'SCHEDULED') -gt 0 ]] && display_unmovable_status="TRUE"
[[ $(echo "${display_hide_background_option}" | grep -c 'SCHEDULED') -gt 0 ]] && display_hide_background_status="TRUE"
[[ $(echo "${display_silently_option}" | grep -c 'SCHEDULED') -gt 0 ]] && display_silently_status="TRUE"
[[ $(echo "${display_notifications_centered_option}" | grep -c 'SCHEDULED') -gt 0 ]] && display_notifications_centered_status="TRUE"
[[ $(echo "${display_hide_progress_bar_option}" | grep -c 'SCHEDULED') -gt 0 ]] && display_hide_progress_bar_status="TRUE"
local display_string_scheduled_install_only_date
display_string_scheduled_install_only_date=$(date -r "${workflow_scheduled_install_epoch}" "+${DISPLAY_STRING_FORMAT_DATE}")
local display_string_scheduled_install_only_time
display_string_scheduled_install_only_time=$(date -r "${workflow_scheduled_install_epoch}" "+${DISPLAY_STRING_FORMAT_TIME}" | sed 's/^ *//g')
if [[ $(date -r "${workflow_scheduled_install_epoch}" +%H:%M) == "00:00" ]]; then
display_string_scheduled_install="${display_string_scheduled_install_only_date}"
else
display_string_scheduled_install="${display_string_scheduled_install_only_date}${DISPLAY_STRING_FORMAT_DATE_TIME_SEPARATOR}${display_string_scheduled_install_only_time}"
fi
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: display_string_scheduled_install_only_date: ${display_string_scheduled_install_only_date}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: display_string_scheduled_install_only_time: ${display_string_scheduled_install_only_time}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: display_string_scheduled_install: ${display_string_scheduled_install}"
# Evaluate if the scheduled installation should take place now or later.
if [[ "${schedule_workflow_active_matches_now}" == "TRUE" ]] || [[ $workflow_scheduled_install_epoch -lt $workflow_time_epoch ]]; then
log_super "Status: Scheduled installation date of ${workflow_scheduled_install} has passed."
workflow_scheduled_install_now="TRUE"
else
workflow_scheduled_install_difference=$((workflow_scheduled_install_epoch - workflow_time_epoch))
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: workflow_scheduled_install_difference is: ${workflow_scheduled_install_difference}"
{ [[ "${scheduled_install_user_choice_option}" == "TRUE" ]] && [[ "${workflow_scheduled_install_difference}" -gt "${deferral_timer_minutes}" ]]; } && scheduled_install_user_choice_active="TRUE"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: scheduled_install_user_choice_active: ${scheduled_install_user_choice_active}"
if [[ "${workflow_scheduled_install_difference}" -le 180 ]]; then
[[ "${scheduled_install_user_choice_active}" == "FALSE" ]] && log_super "Status: Scheduled installation date of ${workflow_scheduled_install} is only ${workflow_scheduled_install_difference} seconds away, waiting for time to pass..."
[[ "${scheduled_install_user_choice_active}" == "TRUE" ]] && log_super "Status: Scheduled installation date of ${workflow_scheduled_install} is only ${workflow_scheduled_install_difference} seconds away but the user can still reschedule, waiting for time to pass..."
if [[ "${current_user_account_name}" != "FALSE" ]]; then
scheduled_install_final_reminder="TRUE"
dialog_schedule_reminder
else
sleep $((workflow_scheduled_install_difference + 1))
fi
log_super "Status: Scheduled installation date of ${workflow_scheduled_install} has passed."
workflow_scheduled_install_now="TRUE"
else
[[ "${scheduled_install_user_choice_active}" == "FALSE" ]] && log_super "Status: Scheduled installation date of ${workflow_scheduled_install} has not passed."
[[ "${scheduled_install_user_choice_active}" == "TRUE" ]] && log_super "Status: Scheduled installation date of ${workflow_scheduled_install} has not passed and the user can reschedule."
fi
fi
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: workflow_scheduled_install_now: ${workflow_scheduled_install_now}"
fi
}
# Evaluate ${workflow_scheduled_install} and ${scheduled_install_reminder_minutes} to set a new ${deferral_timer_minutes}.
set_scheduled_install_deferral() {
[[ -z "${workflow_scheduled_install_epoch}" ]] && workflow_scheduled_install_epoch=$(date -j -f %Y-%m-%d:%H:%M:%S "${workflow_scheduled_install}:00" +%s)
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: workflow_scheduled_install_epoch: ${workflow_scheduled_install_epoch}"
local deferral_timer_scheduled_install_minutes
deferral_timer_scheduled_install_minutes=$(((workflow_scheduled_install_epoch - workflow_time_epoch) / 60 ))
if [[ -n "${scheduled_install_reminder_minutes}" ]]; then
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: scheduled_install_reminder_minutes is: ${scheduled_install_reminder_minutes}"
local previous_ifs
previous_ifs="${IFS}"
IFS=','
local scheduled_install_reminder_array
read -r -a scheduled_install_reminder_array <<<"${scheduled_install_reminder_minutes}"
for scheduled_install_reminder_item in "${scheduled_install_reminder_array[@]}"; do
if [[ $scheduled_install_reminder_item -ge $deferral_timer_scheduled_install_minutes ]]; then
log_super "Status: Scheduled installation reminder of ${scheduled_install_reminder_item} minutes prior to installation has passed."
scheduled_install_suppress_reminder="FALSE"
elif [[ $scheduled_install_reminder_item -lt $deferral_timer_scheduled_install_minutes ]]; then
deferral_timer_minutes=$((deferral_timer_scheduled_install_minutes - scheduled_install_reminder_item))
[[ $deferral_timer_minutes -lt 2 ]] && deferral_timer_scheduled_install_minutes=2
log_super "Status: Scheduled installation on $(date -j -f %Y-%m-%d "${workflow_scheduled_install:0:10}" +%a | tr '[:lower:]' '[:upper:]') ${workflow_scheduled_install}, with a warning notification ${scheduled_install_reminder_item} minutes prior, deferring for ${deferral_timer_minutes} minutes from now."
log_status "Pending: Scheduled installation on $(date -j -f %Y-%m-%d "${workflow_scheduled_install:0:10}" +%a | tr '[:lower:]' '[:upper:]') ${workflow_scheduled_install}, with a warning notification ${scheduled_install_reminder_item} minutes prior, deferring for ${deferral_timer_minutes} minutes from now."
break
fi
done
IFS="${previous_ifs}"
else # Default scheduled installation deferral.
deferral_timer_minutes="${deferral_timer_scheduled_install_minutes}"
log_super "Status: Scheduled installation on $(date -j -f %Y-%m-%d "${workflow_scheduled_install:0:10}" +%a | tr '[:lower:]' '[:upper:]') ${workflow_scheduled_install}, deferring for ${deferral_timer_minutes} minutes from now."
log_status "Pending: Scheduled installation on $(date -j -f %Y-%m-%d "${workflow_scheduled_install:0:10}" +%a | tr '[:lower:]' '[:upper:]') ${workflow_scheduled_install}, deferring for ${deferral_timer_minutes} minutes from now."
fi
}
# Evaluate ${deadline_days_focus}, ${deadline_days_soft}, and ${deadline_days_hard}, then set ${deadline_days_status}, ${deadline_days_epoch}, ${deadline_date_epoch}, ${deadline_epoch}, and ${display_string_deadline} accordingly.
check_deadlines_days_date() {
deadline_days_status="FALSE" # Deadline status modes: FALSE, SOFT, or HARD
deadline_date_status="FALSE" # Deadline status modes: FALSE, SOFT, or HARD
# Evaluate days deadlines and set ${deadline_days_status} and ${deadline_days_epoch}.
if [[ -n "${deadline_days_focus}" ]]; then
local deadline_days_focus_date
deadline_days_focus_date=$(date -r $((schedule_zero_date_epoch + (deadline_days_focus * 86400))) +%Y-%m-%d)
defaults write "${SUPER_LOCAL_PLIST}" DeadlineDaysFocusDate -string "${deadline_days_focus_date}"
local deadline_days_focus_epoch
deadline_days_focus_epoch=$(date -j -f %Y-%m-%d:%H:%M:%S "${deadline_days_focus_date}:00:00:00" +%s)
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: deadline_days_focus_epoch: ${deadline_days_focus_epoch}"
if [[ $deadline_days_focus_epoch -lt $workflow_time_epoch ]]; then
log_super "Status: Focus days deadline of ${deadline_days_focus_date} (${deadline_days_focus} day(s) after ${schedule_zero_date}) has passed."
deadline_days_status="FOCUS"
else
local deadline_days_focus_difference
deadline_days_focus_difference=$((deadline_days_focus_epoch - workflow_time_epoch))
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: deadline_days_focus_difference is: ${deadline_days_focus_difference}"
if [[ "${deadline_days_focus_difference}" -le 180 ]]; then
log_super "Status: Focus days deadline of ${deadline_days_focus_date} (${deadline_days_focus} day(s) after ${schedule_zero_date}) is only ${deadline_days_focus_difference} seconds away, waiting for deadline to pass..."
sleep $((deadline_days_focus_difference + 1))
log_super "Status: Focus days deadline of ${deadline_days_focus_date} (${deadline_days_focus} day(s) after ${schedule_zero_date}) has passed."
deadline_days_status="FOCUS"
else
log_super "Status: Focus days deadline of ${deadline_days_focus_date} (${deadline_days_focus} day(s) after ${schedule_zero_date}) not passed."
fi
fi
fi
if [[ -n "${deadline_days_soft}" ]]; then
local deadline_days_soft_date
deadline_days_soft_date=$(date -r $((schedule_zero_date_epoch + (deadline_days_soft * 86400))) +%Y-%m-%d)
defaults write "${SUPER_LOCAL_PLIST}" DeadlineDaysSoftDate -string "${deadline_days_soft_date}"
local deadline_days_soft_epoch
deadline_days_soft_epoch=$(date -j -f %Y-%m-%d:%H:%M:%S "${deadline_days_soft_date}:00:00:00" +%s)
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: deadline_days_soft_epoch: ${deadline_days_soft_epoch}"
if [[ $deadline_days_soft_epoch -lt $workflow_time_epoch ]]; then
log_super "Status: Soft days deadline of ${deadline_days_soft_date} (${deadline_days_soft} day(s) after ${schedule_zero_date}) has passed."
deadline_days_status="SOFT"
else
local deadline_days_soft_difference
deadline_days_soft_difference=$((deadline_days_soft_epoch - workflow_time_epoch))
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: deadline_days_soft_difference is: ${deadline_days_soft_difference}"
if [[ "${deadline_days_soft_difference}" -le 180 ]]; then
log_super "Status: Soft days deadline of ${deadline_days_soft_date} (${deadline_days_soft} day(s) after ${schedule_zero_date}) is only ${deadline_days_soft_difference} seconds away, waiting for deadline to pass..."
sleep $((deadline_days_soft_difference + 1))
log_super "Status: Soft days deadline of ${deadline_days_soft_date} (${deadline_days_soft} day(s) after ${schedule_zero_date}) has passed."
deadline_days_status="SOFT"
else
log_super "Status: Soft days deadline of ${deadline_days_soft_date} (${deadline_days_soft} day(s) after ${schedule_zero_date}) not passed."
fi
fi
fi
if [[ -n "${deadline_days_hard}" ]]; then
local deadline_days_hard_date
deadline_days_hard_date=$(date -r $((schedule_zero_date_epoch + (deadline_days_hard * 86400))) +%Y-%m-%d)
defaults write "${SUPER_LOCAL_PLIST}" DeadlineDaysHardDate -string "${deadline_days_hard_date}"
local deadline_days_hard_epoch
deadline_days_hard_epoch=$(date -j -f %Y-%m-%d:%H:%M:%S "${deadline_days_hard_date}:00:00:00" +%s)
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: deadline_days_hard_epoch: ${deadline_days_hard_epoch}"
if [[ $deadline_days_hard_epoch -lt $workflow_time_epoch ]]; then
log_super "Status: Hard days deadline of ${deadline_days_hard_date} (${deadline_days_hard} day(s) after ${schedule_zero_date}) has passed."
deadline_days_status="HARD"
else
local deadline_days_hard_difference
deadline_days_hard_difference=$((deadline_days_hard_epoch - workflow_time_epoch))
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: deadline_days_hard_difference is: ${deadline_days_hard_difference}"
if [[ "${deadline_days_hard_difference}" -le 180 ]]; then
log_super "Status: Hard days deadline of ${deadline_days_hard_date} (${deadline_days_hard} day(s) after ${schedule_zero_date}) is only ${deadline_days_hard_difference} seconds away, waiting for deadline to pass..."
sleep $((deadline_days_hard_difference + 1))
log_super "Status: Hard days deadline of ${deadline_days_hard_date} (${deadline_days_hard} day(s) after ${schedule_zero_date}) has passed."
deadline_days_status="HARD"
else
log_super "Status: Hard days deadline of ${deadline_days_hard_date} (${deadline_days_hard} day(s) after ${schedule_zero_date}) not passed."
fi
fi
fi
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: deadline_days_status is: ${deadline_days_status}"
[[ -n ${deadline_days_hard} ]] && deadline_days_epoch="${deadline_days_hard_epoch}"
[[ -n ${deadline_days_soft} ]] && deadline_days_epoch="${deadline_days_soft_epoch}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: deadline_days_epoch is: ${deadline_days_epoch}"
# Evaluate date deadlines and set ${deadline_date_status} and ${deadline_date_epoch}.
if [[ -n "${deadline_date_focus}" ]]; then
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: deadline_date_focus_epoch is: ${deadline_date_focus_epoch}"
if [[ $deadline_date_focus_epoch -lt $workflow_time_epoch ]]; then
log_super "Status: Focus deadline date of ${deadline_date_focus} has passed."
deadline_date_status="FOCUS"
else
local deadline_date_focus_difference
deadline_date_focus_difference=$((deadline_date_focus_epoch - workflow_time_epoch))
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: deadline_date_focus_difference is: ${deadline_date_focus_difference}"
if [[ "${deadline_date_focus_difference}" -le 180 ]]; then
log_super "Status: Focus deadline date of ${deadline_date_focus} is only ${deadline_date_focus_difference} seconds away, waiting for deadline to pass..."
sleep $((deadline_date_focus_difference + 1))
log_super "Status: Focus deadline date of ${deadline_date_focus} has passed."
deadline_date_status="FOCUS"
else
log_super "Status: Focus deadline date of ${deadline_date_focus} not passed."
fi
fi
fi
if [[ -n "${deadline_date_soft}" ]]; then
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: deadline_date_soft_epoch is: ${deadline_date_soft_epoch}"
if [[ $deadline_date_soft_epoch -lt $workflow_time_epoch ]]; then
log_super "Status: Soft deadline date of ${deadline_date_soft} has passed."
deadline_date_status="SOFT"
else
local deadline_date_soft_difference
deadline_date_soft_difference=$((deadline_date_soft_epoch - workflow_time_epoch))
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: deadline_date_soft_difference is: ${deadline_date_soft_difference}"
if [[ "${deadline_date_soft_difference}" -le 180 ]]; then
log_super "Status: Soft deadline date of ${deadline_date_soft} is only ${deadline_date_soft_difference} seconds away, waiting for deadline to pass..."
sleep $((deadline_date_soft_difference + 1))
log_super "Status: Soft deadline date of ${deadline_date_soft} has passed."
deadline_date_status="SOFT"
else
log_super "Status: Soft deadline date of ${deadline_date_soft} not passed."
fi
fi
fi
if [[ -n "${deadline_date_hard}" ]]; then
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: deadline_date_hard_epoch is: ${deadline_date_hard_epoch}"
if [[ $deadline_date_hard_epoch -lt $workflow_time_epoch ]]; then
log_super "Status: Hard deadline date of ${deadline_date_hard} has passed."
deadline_date_status="HARD"
else
local deadline_date_hard_difference
deadline_date_hard_difference=$((deadline_date_hard_epoch - workflow_time_epoch))
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: deadline_date_hard_difference is: ${deadline_date_hard_difference}"
if [[ "${deadline_date_hard_difference}" -le 180 ]]; then
log_super "Status: Hard deadline date of ${deadline_date_soft} is only ${deadline_date_hard_difference} seconds away, waiting for deadline to pass..."
sleep $((deadline_date_hard_difference + 1))
log_super "Status: Hard deadline date of ${deadline_date_soft} has passed."
deadline_date_status="HARD"
else
log_super "Status: Hard deadline date of ${deadline_date_hard} not passed."
fi
fi
fi
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: deadline_date_status is: ${deadline_date_status}"
[[ -n "${deadline_date_hard}" ]] && deadline_date_epoch="${deadline_date_hard_epoch}"
[[ -n "${deadline_date_soft}" ]] && deadline_date_epoch="${deadline_date_soft_epoch}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: deadline_date_epoch is: ${deadline_date_epoch}"
# Set ${deadline_epoch} to the soonest of either days or date deadlines.
{ [[ -n "${deadline_days_epoch}" ]] && [[ -z "${deadline_date_epoch}" ]]; } && deadline_epoch="${deadline_days_epoch}"
{ [[ -z "${deadline_days_epoch}" ]] && [[ -n "${deadline_date_epoch}" ]]; } && deadline_epoch="${deadline_date_epoch}"
if [[ -n "${deadline_days_epoch}" ]] && [[ -n "${deadline_date_epoch}" ]]; then
if [[ "${deadline_days_epoch}" -le "${deadline_date_epoch}" ]]; then
deadline_epoch="${deadline_days_epoch}"
unset deadline_date_epoch
else
deadline_epoch="${deadline_date_epoch}"
unset deadline_days_epoch
fi
fi
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: deadline_epoch is: ${deadline_epoch}"
# If needed adjust the ${deadline_epoch} for the ${schedule_workflow_active_option} and also write to log.
if [[ -n "${schedule_workflow_active_option}" ]] && [[ -n "${deadline_epoch}" ]]; then
if [[ "${deadline_date_status}" != "FALSE" ]] && [[ "${deadline_date_status}" != "FALSE" ]]; then
log_super "Warning: The past due deadline is overriding the schedule workflow active option."
elif [[ -n "${deadline_date_epoch}" ]] && [[ $(date -r "${deadline_epoch}" +%H:%M) != "00:00" ]]; then
log_super "Warning: The manually set deadline date option that also includes a specific time is overriding the schedule workflow active option."
else
deadline_epoch_schedule_workflow_active_check="${deadline_epoch}"
set_schedule_workflow_active_adjustments
if [[ -n "${deadline_epoch_schedule_workflow_active_adjusted}" ]]; then
deadline_epoch="${deadline_epoch_schedule_workflow_active_adjusted}"
else
log_super "Warning: The deadline is overriding the schedule workflow active option because no coordinating time frame could be resolved."
fi
unset deadline_epoch_schedule_workflow_active_check
unset deadline_epoch_schedule_workflow_active_adjusted
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: deadline_epoch is: ${deadline_epoch}"
fi
fi
# If there is a ${deadline_epoch} then set ${display_string_deadline}.
if [[ -n "${deadline_epoch}" ]]; then
local display_string_deadline_only_date
display_string_deadline_only_date=$(date -r "${deadline_epoch}" "+${DISPLAY_STRING_FORMAT_DATE}")
local display_string_deadline_only_time
display_string_deadline_only_time=$(date -r "${deadline_epoch}" "+${DISPLAY_STRING_FORMAT_TIME}" | sed 's/^ *//g')
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: display_string_deadline_only_date is: ${display_string_deadline_only_date}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: display_string_deadline_only_time is: ${display_string_deadline_only_time}"
if [[ $(date -r "${deadline_epoch}" +%H:%M) == "00:00" ]]; then
display_string_deadline="${display_string_deadline_only_date}"
else
display_string_deadline="${display_string_deadline_only_date}${DISPLAY_STRING_FORMAT_DATE_TIME_SEPARATOR}${display_string_deadline_only_time}"
fi
fi
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: display_string_deadline is: ${display_string_deadline}"
# If there is a ${deadline_epoch}, then make sure no user deferral timer or display timeout exceeds the deadline.
if [[ -n "${deadline_epoch}" ]]; then
local deferral_timer_deadline_minutes
deferral_timer_deadline_minutes=$(((deadline_epoch - workflow_time_epoch) / 60))
local deferral_timer_deadline_active
deferral_timer_deadline_active="FALSE"
[[ $deferral_timer_deadline_minutes -lt 2 ]] && deferral_timer_deadline_minutes=2
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: deferral_timer_deadline_minutes is: ${deferral_timer_deadline_minutes}"
if [[ -n "${deferral_timer_menu_minutes}" ]]; then
local previous_ifs
previous_ifs="${IFS}"
IFS=','
local deferral_timer_menu_minutes_array
read -r -a deferral_timer_menu_minutes_array <<<"${deferral_timer_menu_minutes}"
local deferral_timer_menu_reduced_array
deferral_timer_menu_reduced_array=()
local deferral_timer_menu_reduced
deferral_timer_menu_reduced="FALSE"
for deferral_timer_menu_item in "${deferral_timer_menu_minutes_array[@]}"; do
if [[ $deferral_timer_deadline_minutes -le $deferral_timer_menu_item ]]; then
if [[ "${deferral_timer_menu_reduced}" == "FALSE" ]]; then
deferral_timer_menu_reduced_array+=("${deferral_timer_deadline_minutes}")
deferral_timer_menu_reduced="TRUE"
deferral_timer_deadline_active="TRUE"
fi
else
deferral_timer_menu_reduced_array+=("${deferral_timer_menu_item}")
fi
done
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: deferral_timer_menu_reduced is: ${deferral_timer_menu_reduced}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: deferral_timer_menu_reduced_array is: ${deferral_timer_menu_reduced_array[*]}"
if [[ "${deferral_timer_menu_reduced}" == "TRUE" ]]; then
if [[ ${#deferral_timer_menu_reduced_array[@]} -gt 1 ]]; then
deferral_timer_menu_minutes="${deferral_timer_menu_reduced_array[*]}"
log_super "Warning: The deferral timer menu list has been reduced to ${deferral_timer_menu_minutes} minutes given the deadline of ${display_string_deadline}."
else
unset deferral_timer_menu_minutes
log_super "Warning: Not showing the deferral timer menu given the deadline of ${display_string_deadline}."
fi
fi
IFS="${previous_ifs}"
fi
if [[ -z "${deferral_timer_menu_minutes}" ]]; then
if [[ $deferral_timer_deadline_minutes -lt $deferral_timer_minutes ]]; then
log_super "Warning: Reducing user deferral timers to ${deferral_timer_deadline_minutes} minutes given the deadline of ${display_string_deadline}."
deferral_timer_minutes="${deferral_timer_deadline_minutes}"
[[ -n "${deferral_timer_focus_minutes}" ]] && deferral_timer_focus_minutes="${deferral_timer_deadline_minutes}"
deferral_timer_deadline_active="TRUE"
fi
fi
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: deferral_timer_deadline_active is: ${deferral_timer_deadline_active}"
if [[ "${deferral_timer_deadline_active}" == "TRUE" ]]; then
if [[ -n "${dialog_timeout_default_seconds}" ]] && [[ $dialog_timeout_default_seconds -gt 120 ]]; then
dialog_timeout_default_seconds=120
log_super "Warning: Reducing the --dialog-timeout-default option to ${dialog_timeout_default_seconds} seconds given the approaching deadline."
fi
if [[ -n "${dialog_timeout_user_choice_seconds}" ]] && [[ $dialog_timeout_user_choice_seconds -gt 120 ]]; then
dialog_timeout_user_choice_seconds=120
log_super "Warning: Reducing the --dialog-timeout-user-choice option to ${dialog_timeout_user_choice_seconds} seconds given the approaching deadline."
fi
if [[ -n "${dialog_timeout_soft_deadline_seconds}" ]] && [[ $dialog_timeout_soft_deadline_seconds -gt 120 ]]; then
dialog_timeout_soft_deadline_seconds=120
log_super "Warning: Reducing the --dialog-timeout-soft-deadline option to ${dialog_timeout_soft_deadline_seconds} seconds given the approaching deadline."
fi
if [[ -n "${dialog_timeout_user_auth_seconds}" ]] && [[ $dialog_timeout_user_auth_seconds -gt 120 ]]; then
dialog_timeout_user_auth_seconds=120
log_super "Warning: Reducing the --dialog-timeout-user-auth option to ${dialog_timeout_user_auth_seconds} seconds given the approaching deadline."
fi
if [[ -n "${dialog_timeout_insufficient_storage_seconds}" ]] && [[ $dialog_timeout_insufficient_storage_seconds -gt 120 ]]; then
dialog_timeout_insufficient_storage_seconds=120
log_super "Warning: Reducing the --dialog-timeout-insufficient-storage option to ${dialog_timeout_insufficient_storage_seconds} seconds given the approaching deadline."
fi
if [[ -n "${dialog_timeout_power_required_seconds}" ]] && [[ $dialog_timeout_power_required_seconds -gt 120 ]]; then
dialog_timeout_power_required_seconds=120
log_super "Warning: Reducing the --dialog-timeout-power-required option to ${dialog_timeout_power_required_seconds} seconds given the approaching deadline."
fi
fi
fi
}
# Evaluate if a process has told the display to not sleep or the user has enabled Focus or Do Not Disturb, and set ${user_focus_active} accordingly.
check_user_focus() {
user_focus_active="FALSE"
if [[ -n "${deadline_count_focus}" ]] || [[ -n "${deadline_days_focus}" ]] || [[ -n "${deadline_date_focus}" ]]; then
local focus_response
focus_response=$(plutil -extract data.0.storeAssertionRecords.0.assertionDetails.assertionDetailsModeIdentifier raw -o - "/Users/${current_user_account_name}/Library/DoNotDisturb/DB/Assertions.json" | grep -ic 'com.apple.')
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: focus_response is: ${focus_response}"
if [[ "${focus_response}" -gt 0 ]]; then
log_super "Status: Focus or Do Not Disturb enabled for current user: ${current_user_account_name}."
user_focus_active="TRUE"
fi
local previous_ifs
previous_ifs="${IFS}"
IFS=$'\n'
local display_assertions_array
display_assertions_array=($(pmset -g assertions | awk '/NoDisplaySleepAssertion | PreventUserIdleDisplaySleep/ && match($0,/\(.+\)/) && ! /coreaudiod/ {gsub(/^\ +/,"",$0); print};'))
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: display_assertions_array is:\n${display_assertions_array[*]}"
if [[ -n "${display_assertions_array[*]}" ]]; then
for display_assertion in "${display_assertions_array[@]}"; do
log_super "Status: The following Display Sleep Assertion was found: $(echo "${display_assertion}" | awk -F ':' '{print $1;}')"
done
user_focus_active="TRUE"
fi
IFS="${previous_ifs}"
fi
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: user_focus_active is: ${user_focus_active}"
}
# Evaluate ${deadline_count_focus}, ${deadline_count_soft}, and ${deadline_count_hard}, then set ${user_focus_active}, ${deadline_count_status}, ${display_string_deadline_count}, and ${display_string_deadline_count_maximum} accordingly.
check_deadlines_count() {
deadline_count_status="FALSE" # Deadline status modes: FALSE, SOFT, or HARD
if [[ "${user_focus_active}" == "TRUE" ]]; then
if [[ -n "${deadline_count_focus}" ]]; then
local deadline_counter_focus_previous
local deadline_counter_focus_current
deadline_counter_focus_previous=$(defaults read "${SUPER_LOCAL_PLIST}" DeadlineCounterFocus 2>/dev/null)
if [[ -z "${deadline_counter_focus_previous}" ]]; then
deadline_counter_focus_current=0
defaults write "${SUPER_LOCAL_PLIST}" DeadlineCounterFocus -int "${deadline_counter_focus_current}"
else
deadline_counter_focus_current=$((deadline_counter_focus_previous + 1))
defaults write "${SUPER_LOCAL_PLIST}" DeadlineCounterFocus -int "${deadline_counter_focus_current}"
fi
if [[ "${deadline_counter_focus_current}" -ge "${deadline_count_focus}" ]]; then
log_super "Status: Focus maximum deferral count of ${deadline_count_focus} has passed."
deadline_count_status="FOCUS"
user_focus_active="FALSE"
else
display_string_deadline_count_focus=$((deadline_count_focus - deadline_counter_focus_current))
log_super "Status: Focus maximum deferral count of ${deadline_count_focus} not passed with ${display_string_deadline_count_focus} remaining."
fi
else
log_super "Status: Focus or Do Not Disturb active, and no maximum focus deferral, so not incrementing deferral counters."
fi
fi
if [[ "${user_focus_active}" == "FALSE" ]]; then
if [[ -n "${deadline_count_soft}" ]]; then
local deadline_counter_soft_previous
local deadline_counter_soft_current
deadline_counter_soft_previous=$(defaults read "${SUPER_LOCAL_PLIST}" DeadlineCounterSoft 2>/dev/null)
if [[ -z "${deadline_counter_soft_previous}" ]]; then
deadline_counter_soft_current=0
defaults write "${SUPER_LOCAL_PLIST}" DeadlineCounterSoft -int "${deadline_counter_soft_current}"
else
deadline_counter_soft_current=$((deadline_counter_soft_previous + 1))
defaults write "${SUPER_LOCAL_PLIST}" DeadlineCounterSoft -int "${deadline_counter_soft_current}"
fi
if [[ "${deadline_counter_soft_current}" -ge "${deadline_count_soft}" ]]; then
log_super "Status: Soft maximum deferral count of ${deadline_count_soft} has passed."
deadline_count_status="SOFT"
else
display_string_deadline_count_soft=$((deadline_count_soft - deadline_counter_soft_current))
log_super "Status: Soft maximum deferral count of ${deadline_count_soft} not passed with ${display_string_deadline_count_soft} remaining."
fi
display_string_deadline_count="${display_string_deadline_count_soft}"
display_string_deadline_count_maximum="${deadline_count_soft}"
fi
if [[ -n "${deadline_count_hard}" ]]; then
local deadline_counter_hard_previous
local deadline_counter_hard_current
deadline_counter_hard_previous=$(defaults read "${SUPER_LOCAL_PLIST}" DeadlineCounterHard 2>/dev/null)
if [[ -z "${deadline_counter_hard_previous}" ]]; then
deadline_counter_hard_current=0
defaults write "${SUPER_LOCAL_PLIST}" DeadlineCounterHard -int "${deadline_counter_hard_current}"
else
deadline_counter_hard_current=$((deadline_counter_hard_previous + 1))
defaults write "${SUPER_LOCAL_PLIST}" DeadlineCounterHard -int "${deadline_counter_hard_current}"
fi
if [[ "${deadline_counter_hard_current}" -ge "${deadline_count_hard}" ]]; then
log_super "Status: Hard maximum deferral count of ${deadline_count_hard} has passed."
deadline_count_status="HARD"
else
display_string_deadline_count_hard=$((deadline_count_hard - deadline_counter_hard_current))
log_super "Status: Hard maximum deferral count of ${deadline_count_hard} not passed with ${display_string_deadline_count_hard} remaining."
fi
display_string_deadline_count="${display_string_deadline_count_hard}"
display_string_deadline_count_maximum="${deadline_count_hard}"
fi
fi
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: deadline_count_status is: ${deadline_count_status}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: user_focus_active is: ${user_focus_active}"
}
# MARK: *** Software Update Status ***
################################################################################
# This function checks the validity of software updates/upgrade caches.
check_software_update_status_cached() {
check_software_status_required="FALSE"
macos_beta_program="FALSE"
mdmclient_available="FALSE"
mdmclient_list="FALSE"
macos_installers_list="FALSE"
msu_list="FALSE"
# Check how long ago the last successful macOS software check update was.
local msu_last_sccessful_date
msu_last_sccessful_date=$(defaults read "${MSU_LOCAL_PLIST}" LastSuccessfulDate 2>/dev/null)
local msu_last_sccessful_epoch
[[ -n "${msu_last_sccessful_date}" ]] && msu_last_sccessful_epoch=$(date -j -u -f "%Y-%m-%d %H:%M:%S %z" "${msu_last_sccessful_date}" +%s)
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: msu_last_sccessful_date is: ${msu_last_sccessful_date}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: msu_last_sccessful_epoch is: ${msu_last_sccessful_epoch}"
if [[ "${msu_last_sccessful_epoch}" -lt $(($(date +%s) - 21600)) ]]; then
log_super "Status: Last macOS software update check was more than 6 hours ago, full software status check required."
check_software_status_required="TRUE"
return 0
fi
# Check macOS deferral restrictions cache.
local restrictions_deferral_checksum_cache
restrictions_deferral_checksum_cache=$(defaults read "${SUPER_LOCAL_PLIST}" DeferralRestrictionsChecksum 2>/dev/null)
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: restrictions_deferral_checksum_cache is: ${restrictions_deferral_checksum_cache}"
if { [[ "${restrictions_deferral_checksum_cache}" == "0" ]] && [[ ! -f "${APPLICATION_ACCESS_MANAGED_PLIST}.plist" ]]; } || [[ "${restrictions_deferral_checksum_cache}" == "$(md5 -q "${APPLICATION_ACCESS_MANAGED_PLIST}.plist" 2>/dev/null)" ]]; then
local restrictions_deferral_cache
restrictions_deferral_cache=$(defaults read "${SUPER_LOCAL_PLIST}" DeferralRestrictionsCache 2>/dev/null)
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: restrictions_deferral_cache is: ${restrictions_deferral_cache}"
if [[ -n "${restrictions_deferral_cache}" ]]; then
check_restrictions_deferral
else
log_super "Status: No deferral restrictions cache, full software status check required."
check_software_status_required="TRUE"
return 0
fi
else
log_super "Status: Deferral restrictions have changed since last super workflow run, full software status check required."
check_software_status_required="TRUE"
return 0
fi
# Check macOS beta program status cache.
local macos_beta_program_cache
macos_beta_program_cache=$(defaults read "${SUPER_LOCAL_PLIST}" MacOSBetaProgramCache 2>/dev/null)
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_beta_program_cache is: ${macos_beta_program_cache}"
if [[ -n "${macos_beta_program_cache}" ]]; then
if [[ "${macos_beta_program_cache}" == "1" ]]; then
log_super "Status: This system is currently configured to receive macOS beta updates/upgrades."
macos_beta_program="TRUE"
fi
else
log_super "Status: No macOS beta program status cache, full software status check required."
check_software_status_required="TRUE"
return 0
fi
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_beta_program is: ${macos_beta_program}"
# Check the mdmclient list cache.
local mdmclient_list_checksum_cache
mdmclient_list_checksum_cache=$(defaults read "${SUPER_LOCAL_PLIST}" MDMClientListChecksum 2>/dev/null)
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: mdmclient_list_checksum_cache is: ${mdmclient_list_checksum_cache}"
if [[ "${mdmclient_list_checksum_cache}" == "$(md5 -q "${MDMCLIENT_LIST_LOG}" 2>/dev/null)" ]]; then
local mdmclient_available_cache
mdmclient_available_cache=$(defaults read "${SUPER_LOCAL_PLIST}" MDMClientAvailableCache 2>/dev/null)
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: mdmclient_available_cache is: ${mdmclient_available_cache}"
if [[ -n "${mdmclient_available_cache}" ]]; then
if [[ "${mdmclient_available_cache}" == "1" ]]; then
mdmclient_available="TRUE"
mdmclient_list=$(sed -e '1,/^Available updates/d' -e '/^)$/d' < "${MDMCLIENT_LIST_LOG}")
fi
else
log_super "Status: No mdmclient available status cache, full software status check required."
check_software_status_required="TRUE"
return 0
fi
else
log_super "Status: The ${MDMCLIENT_LIST_LOG} has changed since last super workflow run, full software status check required."
check_software_status_required="TRUE"
return 0
fi
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: mdmclient_list is:\n${mdmclient_list}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: mdmclient_available is: ${mdmclient_available}"
# Check the macOS installers list cache.
local macos_installers_list_checksum_cache
macos_installers_list_checksum_cache=$(defaults read "${SUPER_LOCAL_PLIST}" MacOSInstallersListChecksum 2>/dev/null)
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_installers_list_checksum_cache is: ${macos_installers_list_checksum_cache}"
if [[ -n "${macos_installers_list_checksum_cache}" ]]; then
if [[ "${macos_installers_list_checksum_cache}" == "$(md5 -q "${MACOS_INSTALLERS_LIST_LOG}" 2>/dev/null)" ]]; then
macos_installers_list=$(sed -e '1,/^Identifier/d' -e '/^$/d' < "${MACOS_INSTALLERS_LIST_LOG}")
else
log_super "Status: The ${MACOS_INSTALLERS_LIST_LOG} has changed since last super workflow run, full software status check required."
check_software_status_required="TRUE"
return 0
fi
fi
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_installers_list is:\n${macos_installers_list}"
# Check the macOS software update list cache.
local msu_list_checksum_cache
msu_list_checksum_cache=$(defaults read "${SUPER_LOCAL_PLIST}" MSUListChecksum 2>/dev/null)
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: msu_list_checksum_cache is: ${msu_list_checksum_cache}"
if [[ -n "${msu_list_checksum_cache}" ]]; then
if [[ "${msu_list_checksum_cache}" == "$(md5 -q "${MSU_LIST_LOG}" 2>/dev/null)" ]]; then
msu_list=$(<"${MSU_LIST_LOG}")
else
log_super "Status: The ${MSU_LIST_LOG} has changed since last super workflow run, full software status check required."
check_software_status_required="TRUE"
return 0
fi
fi
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: msu_list is:\n${msu_list}"
}
# This function clears any cached update/upgrade information except for the SOFA cache (because SOFA has it's own cache mechanism).
reset_software_update_status() {
# Non-local get_*_list() parameters.
restrictions_deferral_macOS_minor_updates="FALSE"
restrictions_deferral_macOS_major_upgrades="FALSE"
restrictions_deferral_non_system_updates="FALSE"
macos_beta_program="FALSE"
mdmclient_available="FALSE"
mdmclient_list="FALSE"
macos_installers_list="FALSE"
msu_list="FALSE"
# Non-local workflow_check_software_status() parameters.
unset macos_installer_title
unset macos_installer_version
unset macos_installer_build
unset macos_installer_size
unset macos_msu_label
unset macos_msu_title
unset macos_msu_version
unset macos_msu_build
unset macos_msu_size
unset non_system_msu_labels_array
unset non_system_msu_titles_array
# Externally stored items.
defaults delete "${SUPER_LOCAL_PLIST}" DeferralRestrictionsChecksum 2>/dev/null
defaults delete "${SUPER_LOCAL_PLIST}" DeferralRestrictionsCache 2>/dev/null
defaults delete "${SUPER_LOCAL_PLIST}" MacOSBetaProgramCache 2>/dev/null
defaults delete "${SUPER_LOCAL_PLIST}" MDMClientListChecksum 2>/dev/null
defaults delete "${SUPER_LOCAL_PLIST}" MDMClientAvailableCache 2>/dev/null
defaults delete "${SUPER_LOCAL_PLIST}" MacOSInstallerDownloaded 2>/dev/null
defaults delete "${SUPER_LOCAL_PLIST}" MacOSMSULabelDownloaded 2>/dev/null
defaults delete "${SUPER_LOCAL_PLIST}" MacOSMSULastStartupDownloaded 2>/dev/null
rm -f "${MDMCLIENT_LIST_LOG}" 2>/dev/null
defaults delete "${SUPER_LOCAL_PLIST}" MacOSInstallersListChecksum 2>/dev/null
rm -f "${MACOS_INSTALLERS_LIST_LOG}" 2>/dev/null
defaults delete "${SUPER_LOCAL_PLIST}" MSUListChecksum 2>/dev/null
rm -f "${MSU_LIST_LOG}" 2>/dev/null
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: Local preference file after reset_software_update_status: ${SUPER_LOCAL_PLIST}:\n$(defaults read "${SUPER_LOCAL_PLIST}" 2>/dev/null)"
}
# Check for restrictions configuration profile deferrals and set ${restrictions_deferral_macOS_minor_updates}, ${restrictions_deferral_macOS_major_upgrades}, and ${restrictions_deferral_non_system_updates} accordingly.
check_restrictions_deferral() {
restrictions_deferral_macOS_major_upgrades="FALSE"
restrictions_deferral_macOS_minor_updates="FALSE"
restrictions_deferral_non_system_updates="FALSE"
if [[ -f "${APPLICATION_ACCESS_MANAGED_PLIST}.plist" ]]; then
defaults write "${SUPER_LOCAL_PLIST}" DeferralRestrictionsChecksum -string "$(md5 -q "${APPLICATION_ACCESS_MANAGED_PLIST}.plist" 2>/dev/null)"
local restrictions_deferral_response
restrictions_deferral_response=$(defaults read "${APPLICATION_ACCESS_MANAGED_PLIST}" 2>&1 | grep -E 'enforcedSoftware|forceDelayed' | tr -d ';')
fi
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: restrictions_deferral_response is:\n${restrictions_deferral_response}"
if [[ -n "${restrictions_deferral_response}" ]]; then
defaults write "${SUPER_LOCAL_PLIST}" DeferralRestrictionsCache -bool true
if [[ $(echo "${restrictions_deferral_response}" | awk '/forceDelayedMajorSoftwareUpdates/ {print $3}') -gt 0 ]]; then
restrictions_deferral_macOS_major_upgrades=30
[[ $(echo "${restrictions_deferral_response}" | awk '/enforcedSoftwareUpdateMajorOSDeferredInstallDelay/ {print $3}') -gt 0 ]] && restrictions_deferral_macOS_major_upgrades=$(echo "${restrictions_deferral_response}" | awk '/enforcedSoftwareUpdateMajorOSDeferredInstallDelay/ {print $3}')
log_super "Status: Restrictions configuration profile is deferring macOS major upgrades for ${restrictions_deferral_macOS_major_upgrades} days."
fi
if [[ $(echo "${restrictions_deferral_response}" | awk '/forceDelayedSoftwareUpdates/ {print $3}') -gt 0 ]]; then
restrictions_deferral_macOS_minor_updates=30
[[ $(echo "${restrictions_deferral_response}" | awk '/enforcedSoftwareUpdateDelay/ {print $3}') -gt 0 ]] && restrictions_deferral_macOS_minor_updates=$(echo "${restrictions_deferral_response}" | awk '/enforcedSoftwareUpdateDelay/ {print $3}')
[[ $(echo "${restrictions_deferral_response}" | awk '/enforcedSoftwareUpdateMinorOSDeferredInstallDelay/ {print $3}') -gt 0 ]] && restrictions_deferral_macOS_minor_updates=$(echo "${restrictions_deferral_response}" | awk '/enforcedSoftwareUpdateMinorOSDeferredInstallDelay/ {print $3}')
log_super "Status: Restrictions configuration profile is deferring macOS minor updates for ${restrictions_deferral_macOS_minor_updates} days."
fi
if [[ $(echo "${restrictions_deferral_response}" | awk '/forceDelayedAppSoftwareUpdates/ {print $3}') -gt 0 ]]; then
restrictions_deferral_non_system_updates=30
[[ $(echo "${restrictions_deferral_response}" | awk '/enforcedSoftwareUpdateDelay/ {print $3}') -gt 0 ]] && restrictions_deferral_non_system_updates=$(echo "${restrictions_deferral_response}" | awk '/enforcedSoftwareUpdateDelay/ {print $3}')
[[ $(echo "${restrictions_deferral_response}" | awk '/enforcedSoftwareUpdateNonOSDeferredInstallDelay/ {print $3}') -gt 0 ]] && restrictions_deferral_non_system_updates=$(echo "${restrictions_deferral_response}" | awk '/enforcedSoftwareUpdateNonOSDeferredInstallDelay/ {print $3}')
log_super "Status: Restrictions configuration profile is deferring non-system updates for ${restrictions_deferral_non_system_updates} days."
fi
else
defaults write "${SUPER_LOCAL_PLIST}" DeferralRestrictionsChecksum -int 0
defaults write "${SUPER_LOCAL_PLIST}" DeferralRestrictionsCache -bool false
fi
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: restrictions_deferral_macOS_major_upgrades is: ${restrictions_deferral_macOS_major_upgrades}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: restrictions_deferral_macOS_minor_updates is: ${restrictions_deferral_macOS_minor_updates}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: restrictions_deferral_non_system_updates is: ${restrictions_deferral_non_system_updates}"
}
# Check for macOS beta program enrollment and set ${macos_beta_program} accordingly.
check_macos_beta_program() {
macos_beta_program="FALSE"
if [[ "${macos_version_number}" -ge 1304 ]]; then
local mdmclient_response
mdmclient_response=$(/usr/libexec/mdmclient QueryDeviceInformation 2>/dev/null | grep 'IsDefaultCatalog')
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: mdmclient_response is:\n${mdmclient_response}"
if [[ $(echo "${mdmclient_response}" | grep -c '1') -eq 0 ]]; then
log_super "Status: This system is currently configured to receive macOS beta updates/upgrades."
macos_beta_program="TRUE"
fi
else # macOS versions prior to 13.4.
local seedutil_response
seedutil_response=$(/System/Library/PrivateFrameworks/Seeding.framework/Versions/A/Resources/seedutil current)
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: seedutil_response is:\n${seedutil_response}"
if [[ $(echo "${seedutil_response}" | grep -c 'Is Enrolled: YES') -gt 0 ]]; then
log_super "Status: This system is currently configured to receive macOS beta updates/upgrades."
macos_beta_program="TRUE"
fi
fi
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_beta_program is: ${macos_beta_program}"
if [[ "${macos_beta_program}" == "FALSE" ]]; then
defaults write "${SUPER_LOCAL_PLIST}" MacOSBetaProgramCache -bool false
else # "${macos_beta_program}" == "TRUE"
defaults write "${SUPER_LOCAL_PLIST}" MacOSBetaProgramCache -bool true
fi
}
# This restarts various softwareupdate daemon processes for systems older than macOS 14.4.
kick_softwareupdated() {
if [[ "${macos_version_number}" -ge 1404 ]]; then
log_super "Warning: Apple no longer allows for restarting of software update services on macOS 14.4 or newer. The system may need to be restarted for software update to function properly."
defaults delete /Library/Preferences/com.apple.Softwareupdate.plist >/dev/null 2>&1
return 0
fi
log_super "Status: Restarting various softwareupdate daemon processes..."
defaults delete /Library/Preferences/com.apple.Softwareupdate.plist >/dev/null 2>&1
if ! launchctl kickstart -k "system/com.apple.mobile.softwareupdated"; then
log_super "Warning: Restarting mobile softwareupdate daemon didn't respond, trying again in 10 seconds..."
sleep 10
launchctl kickstart -k "system/com.apple.mobile.softwareupdated"
fi
if ! launchctl kickstart -k "system/com.apple.softwareupdated"; then
log_super "Warning: Restarting system softwareupdate daemon didn't respond, trying again in 10 seconds..."
sleep 10
launchctl kickstart -k "system/com.apple.softwareupdated"
fi
# If a user is logged in then also restart the Software Update Notification Manager daemon.
if [[ "${current_user_account_name}" != "FALSE" ]]; then
if ! launchctl kickstart -k "gui/${current_user_id}/com.apple.SoftwareUpdateNotificationManager"; then
log_super "Warning: Restarting Software Update Notification Manager didn't respond, trying again in 10 seconds..."
sleep 10
launchctl kickstart -k "gui/${current_user_id}/com.apple.SoftwareUpdateNotificationManager"
fi
fi
}
# Check for updates via the the mdmclient command to create the ${MDMCLIENT_LIST_LOG} and set the ${mdmclient_list} and ${mdmclient_available} parameters. This is in a separate function to facilitate list caching and multiple run workflows.
# This also sets ${get_mdmclient_list_error} and ${get_mdmclient_list_timeout}.
get_mdmclient_list() {
mdmclient_available="FALSE"
mdmclient_list="FALSE"
log_super "mdmclient: Waiting for available updates listing..."
# Background the mdmclient list process and send to ${MDMCLIENT_LIST_LOG}.
/usr/libexec/mdmclient AvailableOSUpdates > "${MDMCLIENT_LIST_LOG}" 2>&1 &
local get_mdmclient_list_pid
get_mdmclient_list_pid="$!"
# Watch ${MDMCLIENT_LIST_LOG} while waiting for the mdmclient list process to complete. Note this while read loop has a timeout based on ${TIMEOUT_START_SECONDS}.
get_mdmclient_list_error="TRUE"
get_mdmclient_list_timeout="TRUE"
local get_mdmclient_list_timeout_seconds
get_mdmclient_list_timeout_seconds="${TIMEOUT_START_SECONDS}"
while read -t "${get_mdmclient_list_timeout_seconds}" -r log_line; do
# log_super "Debug Mode: Function ${FUNCNAME[0]}: log_line is:\n${log_line}"
if [[ $(echo "${log_line}" | grep -c 'Available updates') -gt 0 ]]; then
get_mdmclient_list_error="FALSE"
get_mdmclient_list_timeout="FALSE"
wait "${get_mdmclient_list_pid}"
break
fi
done < <(tail -n1 -F "${MDMCLIENT_LIST_LOG}")
# If the mdmclient list completed, then collect information.
if [[ "${get_mdmclient_list_error}" == "FALSE" ]] && [[ "${get_mdmclient_list_timeout}" == "FALSE" ]]; then
mdmclient_list=$(sed -e '1,/^Available updates/d' -e '/^)$/d' < "${MDMCLIENT_LIST_LOG}")
defaults write "${SUPER_LOCAL_PLIST}" MDMClientListChecksum -string "$(md5 -q "${MDMCLIENT_LIST_LOG}" 2>/dev/null)"
if [[ $(echo "${mdmclient_list}" | grep -c 'HumanReadableName') -gt 0 ]]; then
mdmclient_available="TRUE"
defaults write "${SUPER_LOCAL_PLIST}" MDMClientAvailableCache -bool true
else
defaults write "${SUPER_LOCAL_PLIST}" MDMClientAvailableCache -bool false
fi
else # mdmclient list failures.
[[ "${get_mdmclient_list_error}" == "TRUE" ]] && log_super "mdmclient Error: Apple update listing failed, check ${MDMCLIENT_LIST_LOG} for more detail."
[[ "${get_mdmclient_list_timeout}" == "TRUE" ]] && log_super "mdmclient Error: Apple update listing failed to complete, as indicated by no progress after waiting for ${get_mdmclient_list_timeout_seconds} seconds."
kill -9 "${get_mdmclient_list_pid}" >/dev/null 2>&1
fi
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: get_mdmclient_list_error is: ${get_mdmclient_list_error}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: get_mdmclient_list_timeout is: ${get_mdmclient_list_timeout}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: mdmclient_available is: ${mdmclient_available}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: mdmclient_list is:\n${mdmclient_list}"
}
# Check for full macOS installers via the mist-cli command to create the ${MACOS_INSTALLERS_LIST_LOG} and set the ${macos_installers_list} parameter. This is in a separate function to facilitate list caching and multiple run workflows.
# This also sets ${get_macos_installers_list_error} and ${get_macos_installers_list_timeout}.
get_macos_installers_list() {
macos_installers_list="FALSE"
# Background the mist-cli list process and send to ${MACOS_INSTALLERS_LIST_LOG}.
local get_macos_installers_list_pid
if [[ "${macos_beta_program}" == "FALSE" ]]; then
"${MIST_CLI_BINARY}" list installer --output-type csv --no-ansi --compatible >"${MACOS_INSTALLERS_LIST_LOG}" 2>&1 &
get_macos_installers_list_pid="$!"
else # macOS beta workflow.
"${MIST_CLI_BINARY}" list installer --output-type csv --no-ansi --compatible --include-betas >"${MACOS_INSTALLERS_LIST_LOG}" 2>&1 &
get_macos_installers_list_pid="$!"
fi
# Watch ${MACOS_INSTALLERS_LIST_LOG} while waiting for the mist-cli list process to complete. Note this while read loop has a timeout based on ${TIMEOUT_START_SECONDS}.
get_macos_installers_list_error="TRUE"
get_macos_installers_list_timeout="TRUE"
local get_macos_installers_list_timeout_seconds
get_macos_installers_list_timeout_seconds="${TIMEOUT_START_SECONDS}"
while read -t "${get_macos_installers_list_timeout_seconds}" -r log_line; do
# log_super "Debug Mode: Function ${FUNCNAME[0]}: log_line is:\n${log_line}"
if [[ $(echo "${log_line}" | grep -c 'SEARCH') -gt 0 ]]; then
log_super "mist-cli: Waiting for macOS installers listing..."
elif [[ $(echo "${log_line}" | grep -c 'Found 0') -gt 0 ]]; then
get_macos_installers_list_timeout="FALSE"
wait "${get_macos_installers_list_pid}"
break
elif [[ $(echo "${log_line}" | grep -c 'Found') -gt 0 ]]; then
get_macos_installers_list_error="FALSE"
get_macos_installers_list_timeout="FALSE"
wait "${get_macos_installers_list_pid}"
break
fi
done < <(tail -n1 -F "${MACOS_INSTALLERS_LIST_LOG}")
# If the mist-cli list completed, then collect information.
if [[ "${get_macos_installers_list_error}" == "FALSE" ]] && [[ "${get_macos_installers_list_timeout}" == "FALSE" ]]; then
macos_installers_list=$(sed -e '1,/^Identifier/d' -e '/^$/d' < "${MACOS_INSTALLERS_LIST_LOG}")
defaults write "${SUPER_LOCAL_PLIST}" MacOSInstallersListChecksum -string "$(md5 -q "${MACOS_INSTALLERS_LIST_LOG}" 2>/dev/null)"
else
[[ "${get_macos_installers_list_error}" == "TRUE" ]] && log_super "mist-cli Error: macOS installers listing failed, check ${MACOS_INSTALLERS_LIST_LOG} for more detail."
[[ "${get_macos_installers_list_timeout}" == "TRUE" ]] && log_super "mist-cli Error: macOS installers listing failed to complete, as indicated by no progress after waiting for ${get_macos_installers_list_timeout_seconds} seconds."
kill -9 "${get_macos_installers_list_pid}" >/dev/null 2>&1
fi
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: get_macos_installers_list_error is: ${get_macos_installers_list_error}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: get_macos_installers_list_timeout is: ${get_macos_installers_list_timeout}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_installers_list is:\n${macos_installers_list}"
}
# Check for updates via the softwareupdate command to create the ${MSU_LIST_LOG} and set the ${msu_list} parameter. This is in a separate function to facilitate list caching and multiple run workflows.
# This also sets ${get_msu_list_error} and ${get_msu_list_timeout}.
get_msu_list() {
msu_list="FALSE"
log_super "softwareupdate: Waiting for available updates listing..."
# Background the softwareupdate checking process and send to ${MSU_LIST_LOG}.
sudo -u root softwareupdate --list > "${MSU_LIST_LOG}" 2>&1 &
local get_msu_list_pid
get_msu_list_pid="$!"
# Watch ${MSU_LIST_LOG} while waiting for the softwareupdate list process to complete. Note this while read loop has a timeout based on ${TIMEOUT_START_SECONDS}.
get_msu_list_error="TRUE"
get_msu_list_timeout="TRUE"
local get_msu_list_timeout_seconds
get_msu_list_timeout_seconds="${TIMEOUT_START_SECONDS}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: get_msu_list_timeout is: ${get_msu_list_timeout}"
while read -t "${get_msu_list_timeout_seconds}" -r log_line; do
# log_super "Debug Mode: Function ${FUNCNAME[0]}: log_line is:\n${log_line}"
if [[ $(echo "${log_line}" | grep -c "Can’t connect") -gt 0 ]] || [[ $(echo "${log_line}" | grep -c "Couldn't communicate") -gt 0 ]]; then
break
elif [[ $(echo "${log_line}" | grep -c 'Software Update found') -gt 0 ]]; then
get_msu_list_error="FALSE"
get_msu_list_timeout="FALSE"
wait "${get_msu_list_pid}"
break
elif [[ $(echo "${log_line}" | grep -c 'No new software available.') -gt 0 ]]; then
get_msu_list_error="FALSE"
get_msu_list_timeout="FALSE"
wait "${get_msu_list_pid}"
break
fi
done < <(tail -n1 -F "${MSU_LIST_LOG}")
# If the softwareupdate list completed, then collect information.
if [[ "${get_msu_list_error}" == "FALSE" ]] && [[ "${get_msu_list_timeout}" == "FALSE" ]]; then
msu_list=$(<"${MSU_LIST_LOG}")
defaults write "${SUPER_LOCAL_PLIST}" MSUListChecksum -string "$(md5 -q "${MSU_LIST_LOG}" 2>/dev/null)"
else # softwareupdate list failures.
[[ "${get_msu_list_error}" == "TRUE" ]] && log_super "softwareupdate Error: macOS software update listing failed, check ${MSU_LIST_LOG} for more detail."
[[ "${get_msu_list_timeout}" == "TRUE" ]] && log_super "softwareupdate Error: macOS software update listing failed to complete, as indicated by no progress after waiting for ${get_msu_list_timeout_seconds} seconds."
kill -9 "${get_msu_list_pid}" >/dev/null 2>&1
fi
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: get_msu_list_error is: ${get_msu_list_error}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: get_msu_list_timeout is: ${get_msu_list_timeout}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: msu_list is:\n${msu_list}"
}
# This is the main workflow for checking all available macOS software including macOS major upgrades, macOS minor updates, and non-system software.
# This function sets ${macos_installer_target}, ${macos_msu_major_upgrade_target}, ${macos_msu_minor_update_target}, and ${non_system_msu_targets}.
# If there is a ${macos_installer_target} then this also sets ${macos_installer_title}, ${macos_installer_version}, ${macos_installer_build}, and ${macos_installer_size}.
# If there is a ${macos_msu_major_upgrade_target} or a ${macos_msu_minor_update_target} then this also sets ${macos_msu_label}, ${macos_msu_title}, ${macos_msu_version}, ${macos_msu_build}, and ${macos_msu_size}.
# If there is ${non_system_msu_targets} then this also sets ${non_system_msu_labels_array[]} and ${non_system_msu_titles_array[]}.
workflow_check_software_status() {
log_status "Running: Check for software update status workflow."
local mdmclient_macos_installer_target
mdmclient_macos_installer_target="FALSE"
local mdmclient_macos_msu_major_upgrade_target
mdmclient_macos_msu_major_upgrade_target="FALSE"
local mdmclient_macos_msu_minor_update_target
mdmclient_macos_msu_minor_update_target="FALSE"
macos_installer_target="FALSE"
macos_msu_major_upgrade_target="FALSE"
macos_major_upgrade_latest="FALSE"
macos_msu_minor_update_target="FALSE"
macos_minor_update_latest="FALSE"
non_system_msu_targets="FALSE"
local workflow_check_software_status_error
workflow_check_software_status_error="FALSE"
# First check caches to see if a full check can be avoided.
[[ "${check_software_status_required}" != "TRUE" ]] && check_software_update_status_cached
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: check_software_status_required is: ${check_software_status_required}"
# When a full software status check is required start with reseting caches and then check for beta program and mdmclient listing.
if [[ "${check_software_status_required}" == "TRUE" ]]; then
reset_software_update_status
check_restrictions_deferral
check_macos_beta_program
fi
# If (no errors) a full software status check is required then start with mdmclient listing.
if [[ "${workflow_check_software_status_error}" == "FALSE" ]] && [[ "${check_software_status_required}" == "TRUE" ]]; then
get_mdmclient_list
# Error handling if mdmclient is misbehaving.
if [[ "${get_mdmclient_list_error}" == "TRUE" ]] || [[ "${get_mdmclient_list_timeout}" == "TRUE" ]]; then
log_super "Warning: Re-starting mdmclient available updates listing..."
kick_softwareupdated
sleep 10
get_mdmclient_list
elif [[ "${macos_version_number}" -lt 1303 ]] && [[ "${mdmclient_available}" == "FALSE" ]]; then
log_super "Status: macOS 11.x - macOS 13.2, double-checking mdmclient available updates listing..."
sleep 10
get_mdmclient_list
fi
if [[ "${get_mdmclient_list_error}" == "TRUE" ]] || [[ "${get_mdmclient_list_timeout}" == "TRUE" ]]; then
log_super "Error: Checking for mdmclient available updates listing did not complete after multiple attempts."
workflow_check_software_status_error="TRUE"
fi
fi
# If (no errors) there are no macOS software updates available, then exit this function.
if [[ "${workflow_check_software_status_error}" == "FALSE" ]] && [[ "${mdmclient_available}" == "FALSE" ]]; then
log_super "Status: No available macOS software updates."
return 0
fi
# If (no errors) then parse ${mdmclient_list} for all update items.
local previous_ifs
previous_ifs="${IFS}"
IFS=$'\n'
if [[ "${workflow_check_software_status_error}" == "FALSE" ]]; then
# First clean up the structured data section of the ${mdmclient_list}.
local mdmclient_sanitized_list
mdmclient_sanitized_list=$(echo "${mdmclient_list}" | grep -e '^\s*Build ' -e '^\s*DeferredUntil ' -e '^\s*HumanReadableName ' -e '^\s*Version ' -e '},' | tr -d '\n' | sed -e 's/},/\n/g' -e 's/"//g' -e 's/ //g' -e 's/ = /:/g' -e 's/ +0000//g' -e 's/;/,/g' -e 's/HumanReadableName:/Title:/g')
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: mdmclient_sanitized_list is:\n${mdmclient_sanitized_list}"
# Create arrays for available macOS and non-system updates.
local mdmclient_macos_installers_array
mdmclient_macos_installers_array=($(echo "${mdmclient_sanitized_list}" | grep -E 'macOS' | grep -Ev 'Build|Deferred|Installer' | awk -F ',' '{print $1 "," $2}' | uniq | sort -t ':' -k3 -V -r))
local mdmclient_macos_msu_array
mdmclient_macos_msu_array=($(echo "${mdmclient_sanitized_list}" | grep -E 'Build.*macOS' | grep -Ev 'Deferred' | awk -F ',' '{print $2 "," $1 "," $3}' | uniq | sort -t ':' -k4 -V -r))
local mdmclient_non_system_array
mdmclient_non_system_array=($(echo "${mdmclient_sanitized_list}" | grep -Ev 'macOS|XProtect|MRT|Gatekeeper|DeferredUntil' | awk -F ',' '{print $1 "," $2}' | uniq))
# Create arrays for macOS and non-system updates reporting as deferred.
local mdmclient_macos_installers_deferred_array
mdmclient_macos_installers_deferred_array=($(echo "${mdmclient_sanitized_list}" | grep -E 'Deferred.*macOS' | grep -Ev 'Build' | awk -F ',' '{print $1 "," $2 "," $3}' | uniq | sort -t ':' -k6 -V -r))
local mdmclient_macos_msu_deferred_array
mdmclient_macos_msu_deferred_array=($(echo "${mdmclient_sanitized_list}" | grep -E 'Build.*Deferred.*macOS' | awk -F ',' '{print $2 "," $3 "," $1 "," $4}'| uniq | sort -t ':' -k7 -V -r))
local mdmclient_non_system_deferred_array
mdmclient_non_system_deferred_array=($(echo "${mdmclient_sanitized_list}" | grep -Ev 'macOS|XProtect|MRT|Gatekeeper' | grep -E 'Deferred' | awk -F ',' '{print $1 "," $2 "," $3}' | uniq))
# Create an array for security updates that can not be installed programmatically.
local mdmclient_security_array
mdmclient_security_array=($(echo "${mdmclient_sanitized_list}" | grep -E 'XProtect|MRT|Gatekeeper' | awk -F ',' '{print $1 "," $2}' | uniq))
# Evaluate the numer of available updates and make sure there is at least one update available.
if [[ ${#mdmclient_macos_installers_array[@]} -eq 0 ]] && [[ ${#mdmclient_macos_msu_array[@]} -eq 0 ]] && [[ ${#mdmclient_non_system_array[@]} -eq 0 ]] && [[ ${#mdmclient_macos_installers_deferred_array[@]} -eq 0 ]] && [[ ${#mdmclient_macos_msu_deferred_array[@]} -eq 0 ]] && [[ ${#mdmclient_non_system_deferred_array[@]} -eq 0 ]] && [[ ${#mdmclient_security_array[@]} -eq 0 ]]; then
log_super "Error: Parsing mdmclient available updates listing did not find any available or deferred updates."
workflow_check_software_status_error="TRUE"
fi
fi
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: mdmclient_macos_installers_array is:\n${mdmclient_macos_installers_array[*]}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: mdmclient_macos_msu_array is:\n${mdmclient_macos_msu_array[*]}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: mdmclient_non_system_array is:\n${mdmclient_non_system_array[*]}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: mdmclient_macos_installers_deferred_array is:\n${mdmclient_macos_installers_deferred_array[*]}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: mdmclient_macos_msu_deferred_array is:\n${mdmclient_macos_msu_deferred_array[*]}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: mdmclient_non_system_deferred_array is:\n${mdmclient_non_system_deferred_array[*]}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: mdmclient_security_array is:\n${mdmclient_security_array[*]}"
# If (no errors) split ${mdmclient_macos_msu_array[]} and into separate major upgrade and minor update arrays.
if [[ "${workflow_check_software_status_error}" == "FALSE" ]] && [[ ${#mdmclient_macos_msu_array[@]} -gt 0 ]]; then
local mdmclient_macos_msu_major_upgrade_array
mdmclient_macos_msu_major_upgrade_array=()
local mdmclient_macos_msu_minor_update_array
mdmclient_macos_msu_minor_update_array=()
for mdmclient_macos_msu_item in "${mdmclient_macos_msu_array[@]}"; do
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: mdmclient_macos_msu_item is:\n${mdmclient_macos_msu_item}"
[[ $(echo "${mdmclient_macos_msu_item}" | awk -F ',' '{print $3}' | sed -e 's/.*://g' -e 's/\..*//g') -gt "${macos_version_major}" ]] && mdmclient_macos_msu_major_upgrade_array+=("${mdmclient_macos_msu_item}")
[[ $(echo "${mdmclient_macos_msu_item}" | awk -F ',' '{print $3}' | sed -e 's/.*://g' -e 's/\..*//g') -eq "${macos_version_major}" ]] && mdmclient_macos_msu_minor_update_array+=("${mdmclient_macos_msu_item}")
done
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: mdmclient_macos_msu_major_upgrade_array is:\n${mdmclient_macos_msu_major_upgrade_array[*]}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: mdmclient_macos_msu_minor_update_array is:\n${mdmclient_macos_msu_minor_update_array[*]}"
fi
# If (no errors) split ${mdmclient_macos_msu_deferred_array[]} and into separate major upgrade and minor update arrays.
if [[ "${workflow_check_software_status_error}" == "FALSE" ]] && [[ ${#mdmclient_macos_msu_deferred_array[@]} -gt 0 ]]; then
local mdmclient_macos_msu_major_upgrade_deferred_array
mdmclient_macos_msu_major_upgrade_deferred_array=()
local mdmclient_macos_msu_minor_update_deferred_array
mdmclient_macos_msu_minor_update_deferred_array=()
for mdmclient_macos_msu_item in "${mdmclient_macos_msu_deferred_array[@]}"; do
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: mdmclient_macos_msu_item is:\n${mdmclient_macos_msu_item}"
[[ $(echo "${mdmclient_macos_msu_item}" | awk -F ',' '{print $4}' | sed -e 's/.*://g' -e 's/\..*//g') -gt "${macos_version_major}" ]] && mdmclient_macos_msu_major_upgrade_deferred_array+=("${mdmclient_macos_msu_item}")
[[ $(echo "${mdmclient_macos_msu_item}" | awk -F ',' '{print $4}' | sed -e 's/.*://g' -e 's/\..*//g') -eq "${macos_version_major}" ]] && mdmclient_macos_msu_minor_update_deferred_array+=("${mdmclient_macos_msu_item}")
done
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: mdmclient_macos_msu_major_upgrade_deferred_array is:\n${mdmclient_macos_msu_major_upgrade_deferred_array[*]}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: mdmclient_macos_msu_minor_update_deferred_array is:\n${mdmclient_macos_msu_minor_update_deferred_array[*]}"
fi
# If (no errors) there are deferred items then evaluate and report to super.log. Further, if restrictions deferrals are not present then erroneously deferred items are moved back to the appropriate available items array.
if [[ "${workflow_check_software_status_error}" == "FALSE" ]] && { [[ ${#mdmclient_macos_installers_deferred_array[@]} -gt 0 ]] || [[ ${#mdmclient_macos_msu_major_upgrade_deferred_array[@]} -gt 0 ]] || [[ ${#mdmclient_macos_msu_minor_update_deferred_array[@]} -gt 0 ]] || [[ ${#mdmclient_non_system_deferred_array[@]} -gt 0 ]]; }; then
# Iterate trough the deferred arrays for logging and erroneous deferrals.
{ [[ "${restrictions_deferral_macOS_major_upgrades}" != "FALSE" ]] || [[ "${restrictions_deferral_macOS_minor_updates}" != "FALSE" ]] || [[ "${restrictions_deferral_non_system_updates}" != "FALSE" ]]; } && log_super "Status: Some updates are deferred due to a restrictions deferral configuration profile."
{ [[ "${restrictions_deferral_macOS_major_upgrades}" == "FALSE" ]] || [[ "${restrictions_deferral_macOS_minor_updates}" == "FALSE" ]] || [[ "${restrictions_deferral_non_system_updates}" == "FALSE" ]]; } && log_super "Warning: Some updates are inaccurately reporting as deferred even though restrictions deferral configuration is not enabled. As such, these updates will still be considered for installation."
if [[ ${#mdmclient_macos_installers_deferred_array[@]} -gt 0 ]]; then
for array_index in "${!mdmclient_macos_installers_deferred_array[@]}"; do
[[ "${restrictions_deferral_macOS_major_upgrades}" != "FALSE" ]] && log_super "Restrictions Deferral: macOS major upgrade installer $((array_index + 1)) of ${#mdmclient_macos_installers_deferred_array[@]} is: ${mdmclient_macos_installers_deferred_array[array_index]}"
if [[ "${restrictions_deferral_macOS_major_upgrades}" == "FALSE" ]]; then
log_super "Warning: Inaccurate deferral found for macOS major upgrade installer $((array_index + 1)) of ${#mdmclient_macos_installers_deferred_array[@]} is: ${mdmclient_macos_installers_deferred_array[array_index]}"
# shellcheck disable=SC2001
mdmclient_macos_installers_array+=($(echo "${mdmclient_macos_installers_deferred_array[array_index]}" | sed -e 's/^Deferred.*,T/T/g'))
fi
done
[[ "${restrictions_deferral_macOS_major_upgrades}" == "FALSE" ]] && mdmclient_macos_installers_array=($(sort -t ':' -k4 -V -r <<<"${mdmclient_macos_installers_array[*]}"))
fi
if [[ ${#mdmclient_macos_msu_major_upgrade_deferred_array[@]} -gt 0 ]]; then
for array_index in "${!mdmclient_macos_msu_major_upgrade_deferred_array[@]}"; do
[[ "${restrictions_deferral_macOS_major_upgrades}" != "FALSE" ]] && log_super "Restrictions Deferral: macOS major upgrade $((array_index + 1)) of ${#mdmclient_macos_msu_major_upgrade_deferred_array[@]} is: ${mdmclient_macos_msu_major_upgrade_deferred_array[array_index]}"
if [[ "${restrictions_deferral_macOS_major_upgrades}" == "FALSE" ]]; then
log_super "Warning: Inaccurate deferral found for macOS major upgrade $((array_index + 1)) of ${#mdmclient_macos_msu_major_upgrade_deferred_array[@]} is: ${mdmclient_macos_msu_major_upgrade_deferred_array[array_index]}"
# shellcheck disable=SC2001
mdmclient_macos_msu_major_upgrade_array+=($(echo "${mdmclient_macos_msu_major_upgrade_deferred_array[array_index]}" | sed -e 's/^Deferred.*,T/T/g'))
fi
done
[[ "${restrictions_deferral_macOS_major_upgrades}" == "FALSE" ]] && mdmclient_macos_msu_major_upgrade_array=($(sort -t ':' -k3 -V -r <<<"${mdmclient_macos_msu_major_upgrade_array[*]}"))
fi
if [[ ${#mdmclient_macos_msu_minor_update_deferred_array[@]} -gt 0 ]]; then
for array_index in "${!mdmclient_macos_msu_minor_update_deferred_array[@]}"; do
[[ "${restrictions_deferral_macOS_minor_updates}" != "FALSE" ]] && log_super "Restrictions Deferral: macOS minor update $((array_index + 1)) of ${#mdmclient_macos_msu_minor_update_deferred_array[@]} is: ${mdmclient_macos_msu_minor_update_deferred_array[array_index]}"
if [[ "${restrictions_deferral_macOS_minor_updates}" == "FALSE" ]]; then
log_super "Warning: Inaccurate deferral found for macOS minor update $((array_index + 1)) of ${#mdmclient_macos_msu_minor_update_deferred_array[@]} is: ${mdmclient_macos_msu_minor_update_deferred_array[array_index]}"
# shellcheck disable=SC2001
mdmclient_macos_msu_minor_update_array+=($(echo "${mdmclient_macos_msu_minor_update_deferred_array[array_index]}" | sed -e 's/^Deferred.*,T/T/g'))
fi
done
[[ "${restrictions_deferral_macOS_minor_updates}" == "FALSE" ]] && mdmclient_macos_msu_minor_update_array=($(sort -t ':' -k3 -V -r <<<"${mdmclient_macos_msu_minor_update_array[*]}"))
fi
if [[ ${#mdmclient_non_system_deferred_array[@]} -gt 0 ]]; then
for array_index in "${!mdmclient_non_system_deferred_array[@]}"; do
[[ "${restrictions_deferral_non_system_updates}" != "FALSE" ]] && log_super "Restrictions Deferral: Non-system update $((array_index + 1)) of ${#mdmclient_non_system_deferred_array[@]} is: ${mdmclient_non_system_deferred_array[array_index]}"
if [[ "${restrictions_deferral_non_system_updates}" == "FALSE" ]]; then
log_super "Warning: Inaccurate deferral found for Non-system update $((array_index + 1)) of ${#mdmclient_non_system_deferred_array[@]} is: ${mdmclient_non_system_deferred_array[array_index]}"
# shellcheck disable=SC2001
mdmclient_non_system_array+=($(echo "${mdmclient_non_system_deferred_array[array_index]}" | sed -e 's/^Deferred.*,T/T/g'))
fi
done
fi
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: mdmclient_macos_installers_array is:\n${mdmclient_macos_installers_array[*]}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: mdmclient_macos_msu_major_upgrade_array is:\n${mdmclient_macos_msu_major_upgrade_array[*]}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: mdmclient_macos_msu_minor_update_array is:\n${mdmclient_macos_msu_minor_update_array[*]}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: mdmclient_non_system_array is:\n${mdmclient_non_system_array[*]}"
fi
# If (no errors) no installable updates then report any macOS security updates (that can't be installed programmatically) and then exit this function.
if [[ "${workflow_check_software_status_error}" == "FALSE" ]] && [[ ${#mdmclient_macos_installers_array[@]} -eq 0 ]] && [[ ${#mdmclient_macos_msu_major_upgrade_array[@]} -eq 0 ]] && [[ ${#mdmclient_macos_msu_minor_update_array[@]} -eq 0 ]] && [[ ${#mdmclient_non_system_array[@]} -eq 0 ]]; then
{ [[ ${#mdmclient_macos_installers_deferred_array[@]} -gt 0 ]] || [[ ${#mdmclient_macos_msu_major_upgrade_deferred_array[@]} -gt 0 ]] || [[ ${#mdmclient_macos_msu_minor_update_deferred_array[@]} -gt 0 ]] || [[ ${#mdmclient_non_system_deferred_array[@]} -gt 0 ]]; } && log_super "Status: No currently available macOS software updates due to software update deferral restrictions configuration profile."
if [[ ${#mdmclient_security_array[@]} -gt 0 ]]; then
[[ "${msu_automatic_security_updates}" == "TRUE" ]] && log_super "Status: The following security updates are currently available and they should be installed soon via automatic macOS software udpate:"
[[ "${msu_automatic_security_updates}" == "FALSE" ]] && log_super "Warning: The following security updates are available but due to macOS software update settings are not allowed for installation:"
for array_index in "${!mdmclient_security_array[@]}"; do
[[ "${msu_automatic_security_updates}" == "TRUE" ]] && log_super "Status: Security update $((array_index + 1)) of ${#mdmclient_security_array[@]} is: ${mdmclient_security_array[array_index]}"
[[ "${msu_automatic_security_updates}" == "FALSE" ]] && log_super "Warning: Security update $((array_index + 1)) of ${#mdmclient_security_array[@]} is: ${mdmclient_security_array[array_index]}"
done
fi
IFS="${previous_ifs}"
return 0
fi
# If (no errors) macOS major upgrades are available then determine if any of them are allowed.
if [[ "${workflow_check_software_status_error}" == "FALSE" ]] && { [[ ${#mdmclient_macos_installers_array[@]} -gt 0 ]] || [[ ${#mdmclient_macos_msu_major_upgrade_array[@]} -gt 0 ]]; }; then
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: install_macos_major_upgrades is: ${install_macos_major_upgrades}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: install_macos_major_upgrades_target is: ${install_macos_major_upgrades_target}"
# If there is no ${install_macos_major_upgrades_target}, then the newest macOS major upgrade versions should be the targets.
if [[ "${install_macos_major_upgrades}" == "TRUE" ]] && [[ -z "${install_macos_major_upgrades_target}" ]]; then
[[ ${#mdmclient_macos_installers_array[@]} -gt 0 ]] && mdmclient_macos_installer_target="${mdmclient_macos_installers_array[0]}"
[[ ${#mdmclient_macos_msu_major_upgrade_array[@]} -gt 0 ]] && mdmclient_macos_msu_major_upgrade_target="${mdmclient_macos_msu_major_upgrade_array[0]}"
[[ "${restrictions_deferral_macOS_major_upgrades}" == "FALSE" ]] && macos_major_upgrade_latest="TRUE"
fi
# If there is a ${install_macos_major_upgrades_target} then evaluate versions in ${mdmclient_macos_installers_array[]} for a possible target.
if [[ "${install_macos_major_upgrades}" == "TRUE" ]] && [[ -n "${install_macos_major_upgrades_target}" ]] && [[ ${#mdmclient_macos_installers_array[@]} -gt 0 ]]; then
local mdmclient_macos_installers_disallowed_array
mdmclient_macos_installers_disallowed_array=()
for mdmclient_macos_installer_item in "${mdmclient_macos_installers_array[@]}"; do
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: mdmclient_macos_installer_item is:\n${mdmclient_macos_installer_item}"
if [[ $(echo "${mdmclient_macos_installer_item}" | awk -F ',' '{print $2}' | sed -e 's/.*://g' -e 's/\..*//g') -eq ${install_macos_major_upgrades_target} ]]; then
mdmclient_macos_installer_target="${mdmclient_macos_installer_item}"
break
fi
[[ "${mdmclient_macos_installer_target}" == "FALSE" ]] && mdmclient_macos_installers_disallowed_array+=("${mdmclient_macos_installer_item}")
done
fi
# If there is a ${install_macos_major_upgrades_target}then evaluate versions in ${mdmclient_macos_msu_major_upgrade_array[]} for a possible target.
if [[ "${install_macos_major_upgrades}" == "TRUE" ]] && [[ -n "${install_macos_major_upgrades_target}" ]] && [[ ${#mdmclient_macos_msu_major_upgrade_array[@]} -gt 0 ]]; then
local mdmclient_macos_msu_major_upgrades_disallowed_array
mdmclient_macos_msu_major_upgrades_disallowed_array=()
for mdmclient_macos_msu_item in "${mdmclient_macos_msu_major_upgrade_array[@]}"; do
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: mdmclient_macos_msu_item is:\n${mdmclient_macos_msu_item}"
if [[ $(echo "${mdmclient_macos_msu_item}" | awk -F ',' '{print $3}' | sed -e 's/.*://g' -e 's/\..*//g') -eq ${install_macos_major_upgrades_target} ]]; then
mdmclient_macos_msu_major_upgrade_target="${mdmclient_macos_msu_item}"
break
fi
[[ "${mdmclient_macos_msu_major_upgrade_target}" == "FALSE" ]] && mdmclient_macos_msu_major_upgrades_disallowed_array+=("${mdmclient_macos_msu_item}")
done
fi
# If using an older version of macOS and the MDM workflow then major upgrades via MSU are not possible.
if [[ "${install_macos_major_upgrades}" == "TRUE" ]] && [[ "${mdmclient_macos_msu_major_upgrade_target}" != "FALSE" ]] && [[ "${macos_version_major}" -lt 13 ]] && [[ "${workflow_macos_auth}" == "JAMF" ]]; then
log_super "Warning: The MDM upgrade command on macOS 12 only supports full macOS installers, thus the following \"over-the-air\" macOS major upgrade is not currently possible: ${mdmclient_macos_msu_major_upgrade_target}"
mdmclient_macos_msu_major_upgrade_target="FALSE"
fi
# At this point if there is both a target installer and a target MSU major upgrade, then the MSU major upgrade takes priority.
{ [[ "${mdmclient_macos_installer_target}" != "FALSE" ]] && [[ "${mdmclient_macos_msu_major_upgrade_target}" != "FALSE" ]]; } && mdmclient_macos_installer_target="FALSE"
# If there are macOS major upgrades but they aren't possible or allowed then report to super.log.
if [[ "${install_macos_major_upgrades}" == "FALSE" ]] && [[ ${#mdmclient_macos_installers_array[@]} -gt 0 ]]; then
log_super "Disallowed: The following macOS major upgrade installers are available but not allowed:"
for array_index in "${!mdmclient_macos_installers_array[@]}"; do
log_super "Disallowed: macOS major upgrade installer $((array_index + 1)) of ${#mdmclient_macos_installers_array[@]} is: ${mdmclient_macos_installers_array[array_index]}"
done
fi
if [[ "${install_macos_major_upgrades}" == "FALSE" ]] && [[ ${#mdmclient_macos_msu_major_upgrade_array[@]} -gt 0 ]]; then
log_super "Disallowed: The following \"over-the-air\" macOS major upgrades are available but not allowed:"
for array_index in "${!mdmclient_macos_msu_major_upgrade_array[@]}"; do
log_super "Disallowed: macOS major upgrade $((array_index + 1)) of ${#mdmclient_macos_msu_major_upgrade_array[@]} is: ${mdmclient_macos_msu_major_upgrade_array[array_index]}"
done
fi
if [[ "${install_macos_major_upgrades}" == "TRUE" ]] && [[ ${#mdmclient_macos_installers_disallowed_array[@]} -gt 0 ]]; then
log_super "Disallowed: The following macOS major upgrade installers are available but not allowed:"
for array_index in "${!mdmclient_macos_installers_disallowed_array[@]}"; do
log_super "Disallowed: macOS major upgrade installer $((array_index + 1)) of ${#mdmclient_macos_installers_disallowed_array[@]} is: ${mdmclient_macos_installers_disallowed_array[array_index]}"
done
fi
if [[ "${install_macos_major_upgrades}" == "TRUE" ]] && [[ ${#mdmclient_macos_msu_major_upgrades_disallowed_array[@]} -gt 0 ]]; then
log_super "Disallowed: The following \"over-the-air\" macOS major upgrades are available but not allowed:"
for array_index in "${!mdmclient_macos_msu_major_upgrades_disallowed_array[@]}"; do
log_super "Disallowed: macOS major upgrade $((array_index + 1)) of ${#mdmclient_macos_msu_major_upgrades_disallowed_array[@]} is: ${mdmclient_macos_msu_major_upgrades_disallowed_array[array_index]}"
done
{ [[ "${mdmclient_macos_msu_major_upgrade_target}" == "FALSE" ]] && [[ "${mdmclient_macos_installer_target}" != "FALSE" ]]; } && log_super "Warning: The --install-macos-major-version-target=${install_macos_major_upgrades_target} option is forcing the workflow to target an older macOS installer (as opposed to a more efficient \"over-the-air\" macOS major upgrade)."
fi
fi
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: mdmclient_macos_installer_target is: ${mdmclient_macos_installer_target}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: mdmclient_macos_msu_major_upgrade_target is: ${mdmclient_macos_msu_major_upgrade_target}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_major_upgrade_latest is: ${macos_major_upgrade_latest}"
# If (no errors) check for previously targeted macOS major upgrade betas plus Jamf API beta limitation.
if [[ "${workflow_check_software_status_error}" == "FALSE" ]] && [[ -n "${install_macos_major_upgrades_target}" ]] && [[ "${macos_beta_program}" == "TRUE" ]] && [[ "${workflow_macos_auth}" == "JAMF" ]]; then
local macos_older_beta_target
{ [[ "${mdmclient_macos_installer_target}" != "FALSE" ]] && [[ $(echo "${mdmclient_macos_installers_array[0]}" | awk -F ',' '{print $2}' | sed -e 's/.*://g' -e 's/\..*//g') -gt ${install_macos_major_upgrades_target} ]]; } && macos_older_beta_target="${mdmclient_macos_installer_target}"
{ [[ "${mdmclient_macos_msu_major_upgrade_target}" != "FALSE" ]] && [[ $(echo "${mdmclient_macos_msu_major_upgrade_array[0]}" | awk -F ',' '{print $3}' | sed -e 's/.*://g' -e 's/\..*//g') -gt ${install_macos_major_upgrades_target} ]]; } && macos_older_beta_target="${mdmclient_macos_msu_major_upgrade_target}"
if [[ -n "${macos_older_beta_target}" ]]; then
check_mdm_service
[[ "${auth_error_mdm}" == "FALSE" ]] && get_saved_authentication
[[ "${auth_error_saved}" == "FALSE" ]] && check_jamf_api_access_token
[[ "${auth_error_jamf}" == "FALSE" ]] && check_jamf_api_update_workflow
if [[ "${auth_error_jamf}" == "FALSE" ]]; then
if [[ "${jamf_api_update_workflow}" == "NEW" ]]; then
log_super "Error: Unable to target older macOS beta major upgrade: ${macos_older_beta_target}"
log_super "Error: The Jamf Pro new Managed Software Updates API does not support targeting older macOS beta major upgrades. To target older macOS beta major upgrades you must disable the new Managed Software Updates feature in Jamf Pro or use a local authentication option."
workflow_check_software_status_error="TRUE"
fi
fi
if [[ "${auth_error_mdm}" == "TRUE" ]] || [[ "${auth_error_saved}" == "TRUE" ]] || [[ "${auth_error_jamf}" == "TRUE" ]]; then
log_super "Error: Failed to validate Jamf Pro API configuration. Verify Jamf Pro API configuration: https://github.com/Macjutsu/super/wiki/Apple-Silicon-Jamf-Pro-API-Credentials"
workflow_check_software_status_error="TRUE"
unset auth_jamf_client
unset auth_jamf_secret
fi
fi
fi
# If (no errors) no macOS major upgrade is targeted and a macOS minor update is available then select the latest for the ${mdmclient_macos_msu_minor_update_target}.
if [[ "${workflow_check_software_status_error}" == "FALSE" ]] && [[ "${mdmclient_macos_installer_target}" == "FALSE" ]] && [[ "${mdmclient_macos_msu_major_upgrade_target}" == "FALSE" ]] && [[ ${#mdmclient_macos_msu_minor_update_array[@]} -gt 0 ]]; then
mdmclient_macos_msu_minor_update_target="${mdmclient_macos_msu_minor_update_array[0]}"
[[ "${restrictions_deferral_macOS_minor_updates}" == "FALSE" ]] && macos_minor_update_latest="TRUE"
fi
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: mdmclient_macos_msu_minor_update_target is: ${mdmclient_macos_msu_minor_update_target}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_minor_update_latest is: ${macos_minor_update_latest}"
# If (no errors) there is a macOS installer targeted then collect/verify the ${macos_installers_list}.
if [[ "${workflow_check_software_status_error}" == "FALSE" ]] && [[ "${mdmclient_macos_installer_target}" != "FALSE" ]]; then
# If required collecte a new ${macos_installers_list}.
if [[ "${check_software_status_required}" == "TRUE" ]] || [[ "${macos_installers_list}" == "FALSE" ]] || [[ -z "${macos_installers_list}" ]]; then
get_macos_installers_list
# Error handling if mist-list is misbehaving.
if [[ "${get_macos_installers_list_error}" == "TRUE" ]] || [[ "${get_macos_installers_list_timeout}" == "TRUE" ]]; then
log_super "Warning: Re-starting check for macOS installers..."
get_macos_installers_list
fi
if [[ "${get_mdmclient_list_error}" == "TRUE" ]] || [[ "${get_mdmclient_list_timeout}" == "TRUE" ]]; then
log_super "Error: Checking for macOS installers listing did not complete after multiple attempts."
workflow_check_software_status_error="TRUE"
fi
fi
# Make sure we have a ${macos_installers_list}, this checks both new and cached lists.
if [[ "${macos_installers_list}" == "FALSE" ]]; then
log_super "Error: Checking for macOS installers listing did not return any available installers."
workflow_check_software_status_error="TRUE"
fi
fi
# If (no errors) there is a ${mdmclient_macos_installer_target} then evaluate the ${macos_installers_list} to select the appropriate ${macos_installer_target}.
# If a ${macos_installer_target} is selected then this function will also set ${macos_installer_title}, ${macos_installer_version}, ${macos_installer_build}, ${macos_installer_size}, and then exit this function.
if [[ "${workflow_check_software_status_error}" == "FALSE" ]] && [[ "${mdmclient_macos_installer_target}" != "FALSE" ]] && [[ "${macos_installers_list}" != "FALSE" ]]; then
# Clean up the ${macos_installers_list} and create an array for searching.
local macos_installers_sanitized_list
macos_installers_sanitized_list=$(echo "${macos_installers_list}" | sed -e 's/"//g' -e 's/=//g')
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_installers_sanitized_list is:\n${macos_installers_sanitized_list}"
local macos_installers_array
macos_installers_array=($(echo "${macos_installers_sanitized_list}" | awk -F ',' '{print "Title:" $2 ",Version:" $3 ",Build:" $4 ",Size:" $5}'))
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_installers_array is:\n${macos_installers_array[*]}"
# Pick the appropriate macOS installer from the ${macos_installers_array} based on the ${mdmclient_macos_installer_target}.
for macos_installer_item in "${macos_installers_array[@]}"; do
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_installer_item is:\n${macos_installer_item}"
if [[ "$(echo "${macos_installer_item}" | awk -F ',' '{print $2}' | sed -e 's/.*://g')" == "$(echo "${mdmclient_macos_installer_target}" | awk -F ',' '{print $2}' | sed -e 's/.*://g')" ]]; then
macos_installer_target="${macos_installer_item}"
break
fi
done
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_installer_target is: ${macos_installer_target}"
# If there is a ${macos_installer_target} then set the remaining individual parameters.
if [[ "${macos_installer_target}" != "FALSE" ]]; then
macos_installer_title=$(echo "${macos_installer_target}" | awk -F ',' '{print $1}' | sed -e 's/.*://g')
macos_installer_version=$(echo "${macos_installer_target}" | awk -F ',' '{print $2}' | sed -e 's/.*://g')
macos_installer_build=$(echo "${macos_installer_target}" | awk -F ',' '{print $3}' | sed -e 's/.*://g')
macos_installer_size=$(echo "${macos_installer_target}" | awk -F ',' '{print $4}' | sed -e 's/.*://g' | awk '{print $1"/1000000000 +1"}' | bc)
log_super "Target: macOS major upgrade installer ${macos_installer_title} ${macos_installer_version}-${macos_installer_build} which is a ${macos_installer_size}GB download."
IFS="${previous_ifs}"
return 0
else
log_super "Error: The mist-cli listing did not contain a macOS installer that matches targeted version: ${macos_installer_target}"
workflow_check_software_status_error="TRUE"
fi
fi
# If (no errors) the function is still running at this point then it's time to collect/verify the ${msu_list}.
if [[ "${workflow_check_software_status_error}" == "FALSE" ]]; then
# If required collecte a new ${msu_list}.
if [[ "${check_software_status_required}" == "TRUE" ]] || [[ "${msu_list}" == "FALSE" ]] || [[ -z "${msu_list}" ]]; then
get_msu_list
# Error handling if softwareupdate is misbehaving.
if [[ "${get_msu_list_error}" == "TRUE" ]] || [[ "${get_msu_list_timeout}" == "TRUE" ]]; then
log_super "Warning: Re-starting softwareupdate available updates listing..."
kick_softwareupdated
sleep 10
get_msu_list
elif [[ "${macos_version_number}" -lt 1303 ]] && [[ $(echo "${msu_list}" | grep -c 'macOS') -eq 0 ]]; then
log_super "Status: macOS 11.x - macOS 13.2, double-checking softwareupdate available updates listing..."
sleep 10
get_msu_list
fi
if [[ "${get_msu_list_error}" == "TRUE" ]] || [[ "${get_msu_list_timeout}" == "TRUE" ]]; then
log_super "Error: Checking for softwareupdate available updates listing did not complete after multiple attempts."
workflow_check_software_status_error="TRUE"
fi
fi
# Make sure we have a ${msu_list}, this checks both new and cached lists.
if [[ "${msu_list}" == "FALSE" ]]; then
log_super "Error: Checking for macOS software update listing did not return any available updates."
workflow_check_software_status_error="TRUE"
fi
fi
# If (no errors) and there is a ${msu_list} then parse and split the updates in to individual arrays.
if [[ "${workflow_check_software_status_error}" == "FALSE" ]] && [[ "${msu_list}" != "FALSE" ]]; then
local msu_sanitized_list
msu_sanitized_list=$(echo "${msu_list}" | grep -e 'Label' -e 'Title' | tr -d '\n' | sed -e 's/^* //' -e 's/* /\n/g' -e 's/\tTitle/,Title/g' -e 's/, /,/g' -e 's/: /:/g')
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: msu_sanitized_list is:\n${msu_sanitized_list}"
local macos_msu_array
macos_msu_array=($(echo "${msu_sanitized_list}" | grep 'macOS' | awk -F ',' '{print $1 "," $2 "," $4 "," $3}' | sort -t ':' -k5 -V -r))
non_system_msu_array=($(echo "${msu_sanitized_list}" | grep -v 'macOS' | awk -F ',' '{print $1 "," $2 "," $4 "," $3}'))
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_msu_array is:\n${macos_msu_array[*]}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: non_system_msu_array is:\n${non_system_msu_array[*]}"
fi
# If (no errors) there is a ${mdmclient_macos_msu_major_upgrade_target} then evaluate the ${macos_msu_array[]} to select the appropriate macOS major upgrade for the ${macos_msu_major_upgrade_target}.
# If a ${macos_msu_major_upgrade_target} is selected then this function will also set ${macos_msu_label}, ${macos_msu_title}, ${macos_msu_version}, ${macos_msu_build}, ${macos_msu_size } and then exit this function.
if [[ "${workflow_check_software_status_error}" == "FALSE" ]] && [[ "${mdmclient_macos_msu_major_upgrade_target}" != "FALSE" ]] && [[ ${#macos_msu_array[@]} -gt 0 ]]; then
# Pick the appropriate macOS major upgrade from the ${macos_msu_array} based on the ${mdmclient_macos_msu_major_upgrade_target}.
for macos_msu_item in "${macos_msu_array[@]}"; do
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_msu_item is:\n${macos_msu_item}"
if [[ "$(echo "${macos_msu_item}" | awk -F ',' '{print $4}' | sed -e 's/.*://g')" == "$(echo "${mdmclient_macos_msu_major_upgrade_target}" | awk -F ',' '{print $3}' | sed -e 's/.*://g')" ]]; then
macos_msu_major_upgrade_target="${macos_msu_item}"
break
fi
done
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_msu_major_upgrade_target is: ${macos_msu_major_upgrade_target}"
# If there is a ${macos_msu_major_upgrade_target} then set the remaining individual parameters.
if [[ "${macos_msu_major_upgrade_target}" != "FALSE" ]]; then
macos_msu_label=$(echo "${macos_msu_major_upgrade_target}" | awk -F ',' '{print $1}' | sed -e 's/.*://g')
[[ $(echo "${macos_msu_major_upgrade_target}" | grep -c 'Beta') -eq 0 ]] && macos_msu_title=$(echo "${macos_msu_major_upgrade_target}" | awk -F ',' '{print $2}' | sed -e 's/.*://g' -e 's/ [1-9].*//g')
[[ $(echo "${macos_msu_major_upgrade_target}" | grep -c 'Beta') -gt 0 ]] && macos_msu_title=$(echo "${macos_msu_major_upgrade_target}" | awk -F ',' '{print $2}' | sed -e 's/.*://g' -e 's/ [1-9].*/ Beta/g')
macos_msu_version=$(echo "${macos_msu_major_upgrade_target}" | awk -F ',' '{print $4}' | sed -e 's/.*://g')
macos_msu_build=$(echo "${macos_msu_major_upgrade_target}" | awk -F ',' '{print $1}' | sed -e 's/.*://g' -e 's/^.*-//g')
macos_msu_size=$(echo "${macos_msu_major_upgrade_target}" | awk -F ',' '{print $3}' | sed -e 's/.*://g' -e 's/[^0-9]//g' | awk '{print $1"/1000000 +1"}' | bc)
log_super "Target: \"Over-the-air\" macOS major upgrade ${macos_msu_title} ${macos_msu_version}-${macos_msu_build} which is a ${macos_msu_size}GB download."
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_msu_label is: ${macos_msu_label}"
IFS="${previous_ifs}"
return 0
else
log_super "Error: The softwareupdate listing did not contain a macOS major upgrade that matches targeted version: ${mdmclient_macos_msu_major_upgrade_target}"
workflow_check_software_status_error="TRUE"
fi
fi
# If (no errors) there is a ${mdmclient_macos_msu_minor_update_target} then evaluate the ${macos_msu_array[]} to select the appropriate macOS minor update for the ${macos_msu_minor_update_target}.
# If a ${macos_msu_minor_update_target} is selected then this function will also set ${macos_msu_label}, ${macos_msu_title}, ${macos_msu_version}, ${macos_msu_build}, ${macos_msu_size } and then exit this function.
if [[ "${workflow_check_software_status_error}" == "FALSE" ]] && [[ "${mdmclient_macos_msu_minor_update_target}" != "FALSE" ]] && [[ ${#macos_msu_array[@]} -gt 0 ]]; then
# Pick the appropriate macOS minor update from the ${macos_msu_array} based on the ${mdmclient_macos_msu_minor_update_target}.
for macos_msu_item in "${macos_msu_array[@]}"; do
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_msu_item is:\n${macos_msu_item}"
if [[ "$(echo "${macos_msu_item}" | awk -F ',' '{print $4}' | sed -e 's/.*://g')" == "$(echo "${mdmclient_macos_msu_minor_update_target}" | awk -F ',' '{print $3}' | sed -e 's/.*://g')" ]]; then
macos_msu_minor_update_target="${macos_msu_item}"
break
fi
done
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_msu_minor_update_target is: ${macos_msu_minor_update_target}"
# If there is a ${macos_msu_minor_update_target} then set the remaining individual parameters.
if [[ "${macos_msu_minor_update_target}" != "FALSE" ]]; then
macos_msu_label=$(echo "${macos_msu_minor_update_target}" | awk -F ',' '{print $1}' | sed -e 's/.*://g')
[[ $(echo "${macos_msu_minor_update_target}" | grep -c 'Beta') -eq 0 ]] && macos_msu_title=$(echo "${macos_msu_minor_update_target}" | awk -F ',' '{print $2}' | sed -e 's/.*://g' -e 's/ [1-9].*//g')
[[ $(echo "${macos_msu_minor_update_target}" | grep -c 'Beta') -gt 0 ]] && macos_msu_title=$(echo "${macos_msu_minor_update_target}" | awk -F ',' '{print $2}' | sed -e 's/.*://g' -e 's/ [1-9].*/ Beta/g')
macos_msu_version=$(echo "${macos_msu_minor_update_target}" | awk -F ',' '{print $4}' | sed -e 's/.*://g')
macos_msu_build=$(echo "${macos_msu_minor_update_target}" | awk -F ',' '{print $1}' | sed -e 's/.*://g' -e 's/^.*-//g')
macos_msu_size=$(echo "${macos_msu_minor_update_target}" | awk -F ',' '{print $3}' | sed -e 's/.*://g' -e 's/[^0-9]//g' | awk '{print $1"/1000000 +1"}' | bc)
log_super "Target: macOS minor update ${macos_msu_title} ${macos_msu_version}-${macos_msu_build} which is a ${macos_msu_size}GB download."
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_msu_label is: ${macos_msu_label}"
IFS="${previous_ifs}"
return 0
else
log_super "Error: The softwareupdate listing did not contain a macOS minor update that matches targeted version: ${mdmclient_macos_msu_minor_update_target}"
workflow_check_software_status_error="TRUE"
fi
fi
# If (no errors) the only updates are non-system updates then evaluate the ${non_system_msu_array[]} and set ${non_system_msu_targets}.
# If there are ${non_system_msu_targets} then this function will also set ${non_system_msu_labels_array[]} and ${non_system_msu_titles_array[]}.
if [[ "${workflow_check_software_status_error}" == "FALSE" ]] && [[ ${#mdmclient_non_system_array[@]} -gt 0 ]] && [[ ${#non_system_msu_array[@]} -gt 0 ]]; then
# Make sure the number of non-system updates collected by mdmclient and msu match.
if [[ ${#non_system_msu_array[@]} -eq ${#mdmclient_non_system_array[@]} ]];then
non_system_msu_targets="TRUE"
non_system_msu_labels_array=()
non_system_msu_titles_array=()
for array_index in "${!non_system_msu_array[@]}"; do
non_system_msu_labels_array+=($(echo "${non_system_msu_array[array_index]}" | awk -F ',' '{print $1}' | sed -e 's/.*://g'))
non_system_msu_titles_array+=($(echo "${non_system_msu_array[array_index]}" | awk -F ',' '{print $2}' | sed -e 's/.*://g'))
log_super "Target: Non-system update $((array_index + 1)) of ${#non_system_msu_array[@]} is: ${non_system_msu_titles_array[array_index]} $(echo "${non_system_msu_array[array_index]}" | awk -F ',' '{print $4}' | sed -e 's/.*://g')"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: non_system_msu_labels_array[${array_index}] is: ${non_system_msu_labels_array[array_index]}"
done
else
log_super "Error: The softwareupdate listing did not contain the expeted number of non-system updates."
workflow_check_software_status_error="TRUE"
fi
fi
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: non_system_msu_targets is: ${non_system_msu_targets}"
IFS="${previous_ifs}"
if [[ "${workflow_check_software_status_error}" == "TRUE" ]]; then
if [[ "${workflow_install_now_active}" == "TRUE" ]]; then # Install now workflow mode.
log_super "Error: Checking for macOS software status workflow failed, install now workflow can not continue."
log_status "Inactive Error: Checking for macOS software status workflow failed, install now workflow can not continue."
[[ "${current_user_account_name}" != "FALSE" ]] && notification_install_now_failed
exit_error
else # Default super workflow.
if [[ "${workflow_restart_validate_active}" == "TRUE" ]]; then
deferral_timer_minutes="${DEFERRAL_TIMER_RESTART_VALIDATION_ERROR_MINUTES}"
log_super "Error: Checking for macOS software status workflow failed, trying restart validation workflow again in ${deferral_timer_minutes} minutes."
log_status "Pending: Checking for macOS software status workflow failed, trying restart validation workflow again in ${deferral_timer_minutes} minutes."
else # Default super workflow.
deferral_timer_minutes="${deferral_timer_error_minutes}"
log_super "Error: Checking for macOS software status workflow failed, trying again in ${deferral_timer_minutes} minutes."
log_status "Pending: Checking for macOS software status workflow failed, trying again in ${deferral_timer_minutes} minutes."
fi
set_auto_launch_deferral
fi
fi
}
# MARK: *** Downloads ***
################################################################################
# This function determines which macOS updates, upgrades, or installers should be downloaded and validates any previously downloaded macOS updates, upgrades, or installers and sets ${macos_installer_download_required}, ${macos_msu_download_required}, ${macos_msu_label}, and ${macos_msu_title} accordingly.
check_macos_downloads() {
macos_installer_download_required="FALSE"
macos_msu_download_required="FALSE"
# If a macOS installer is targeted then evaluate any local installers and set ${macos_installer_download_required}.
if [[ "${macos_installer_target}" != "FALSE" ]]; then
local macos_installer_download
macos_installer_download=$(defaults read "${SUPER_LOCAL_PLIST}" MacOSInstallerDownloaded 2>/dev/null)
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_installer_download is: ${macos_installer_download}"
if [[ -d "/Applications/Install ${macos_installer_title}.app" ]]; then
check_macos_installer
if [[ "${check_macos_installer_error}" == "TRUE" ]]; then
[[ -n "${macos_installer_download}" ]] && log_super "Warning: Previously downloaded macOS installer failed local validations, removing installer: /Applications/Install ${macos_installer_title}.app."
[[ -z "${macos_installer_download}" ]] && log_super "Warning: Existing macOS installer failed local validations, removing installer: /Applications/Install ${macos_installer_title}.app."
macos_installer_download_required="TRUE"
defaults delete "${SUPER_LOCAL_PLIST}" MacOSInstallerDownloaded 2>/dev/null
rm -Rf "/Applications/Install ${macos_installer_title}.app" >/dev/null 2>&1
fi
defaults write "${SUPER_LOCAL_PLIST}" MacOSInstallerDownloaded -string "${macos_installer_title} ${macos_installer_version}-${macos_installer_build}"
else # No local macOS installer.
[[ -n "${macos_installer_download}" ]] && log_super "Warning: Previously downloaded macOS installer could not be found."
macos_installer_download_required="TRUE"
defaults delete "${SUPER_LOCAL_PLIST}" MacOSInstallerDownloaded 2>/dev/null
fi
fi
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_installer_download_required is: ${macos_installer_download_required}"
# If a softwareupdate is targeted then evaluate cached macOS update/upgrade.
if [[ "${macos_msu_major_upgrade_target}" != "FALSE" ]] || [[ "${macos_msu_minor_update_target}" != "FALSE" ]]; then
macos_msu_label_downloaded=$(defaults read "${SUPER_LOCAL_PLIST}" MacOSMSULabelDownloaded 2>/dev/null)
local macos_msu_last_startup_downloaded
macos_msu_last_startup_downloaded=$(defaults read "${SUPER_LOCAL_PLIST}" MacOSMSULastStartupDownloaded 2>/dev/null)
[[ $(defaults read "${SUPER_LOCAL_PLIST}" WorkflowDownloadMacOSAuthRequired 2>/dev/null) -eq 1 ]] && workflow_download_macos_auth_required="TRUE"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_msu_label_downloaded is: ${macos_msu_label_downloaded}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_msu_last_startup_downloaded is: ${macos_msu_last_startup_downloaded}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: workflow_download_macos_auth_required is: ${workflow_download_macos_auth_required}"
# Only validate if we know the list of previous downloads and last startup time.
if [[ -n "${macos_msu_label_downloaded}" ]] && [[ -n "${macos_msu_last_startup_downloaded}" ]]; then
local macos_msu_downloaded_error
macos_msu_downloaded_error="FALSE"
if [[ "${macos_msu_label_downloaded}" != "${macos_msu_label}" ]]; then
log_super "Warning: Previously downloaded macOS update/upgrade \"${macos_msu_label_downloaded}\" does not match the expected macOS update/upgrade \"${macos_msu_label}\", download workflow needs to run again."
macos_msu_downloaded_error="TRUE"
fi
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: mac_last_startup is: ${mac_last_startup}"
if [[ "${macos_msu_last_startup_downloaded}" != "${mac_last_startup}" ]]; then
log_super "Warning: The system has been restarted without applying the previously downloaded macOS update/upgrade, download workflow needs to run again."
macos_msu_downloaded_error="TRUE"
fi
if [[ "${macos_msu_downloaded_error}" == "FALSE" ]]; then
local update_asset_attributes
update_asset_attributes=$(defaults read /System/Volumes/Update/Update update-asset-attributes 2>/dev/null)
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: update_asset_attributes is:\n${update_asset_attributes}"
local macos_msu_version_prepared
macos_msu_version_prepared=$(echo "${update_asset_attributes}" | grep -w 'OSVersion' | awk -F '"' '{print $2;}')
if [[ ${macos_version_major} -ge 13 ]]; then
local macos_msu_prepared_version_extra
macos_msu_prepared_version_extra=$(echo "${update_asset_attributes}" | grep -w 'ProductVersionExtra' | awk -F '"' '{print $2;}')
[[ -n "${macos_msu_prepared_version_extra}" ]] && macos_msu_version_prepared="${macos_msu_version_prepared} ${macos_msu_prepared_version_extra}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_msu_prepared_version_extra is: ${macos_msu_prepared_version_extra}"
fi
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_msu_version_prepared is: ${macos_msu_version_prepared}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_msu_version is: ${macos_msu_version}"
if [[ -z "${macos_msu_version_prepared}" ]]; then
log_super "Warning: Previously downloaded macOS update/upgrade is no longer valid, download workflow needs to run again."
macos_msu_downloaded_error="TRUE"
else
if [[ "${macos_msu_version_prepared}" != "${macos_msu_version}" ]]; then
macos_msu_downloaded_error="TRUE"
[[ "${macos_msu_major_upgrade_target}" != "FALSE" ]] && log_super "Warning: Previously downloaded macOS major upgrade version ${macos_msu_version_prepared} doesn't match expected version ${macos_msu_version}, download workflow needs to run again."
[[ "${macos_msu_minor_update_target}" != "FALSE" ]] && log_super "Warning: Previously downloaded macOS minor update version ${macos_msu_version_prepared} doesn't match expected version ${macos_msu_version}, download workflow needs to run again."
fi
fi
fi
if [[ "${macos_msu_downloaded_error}" == "TRUE" ]]; then
macos_msu_download_required="TRUE"
defaults delete "${SUPER_LOCAL_PLIST}" MacOSMSULabelDownloaded 2>/dev/null
defaults delete "${SUPER_LOCAL_PLIST}" MacOSMSULastStartupDownloaded 2>/dev/null
fi
else
macos_msu_download_required="TRUE"
fi
fi
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_msu_download_required is: ${macos_msu_download_required}"
}
# This function checks the macOS installer to be used for upgrades matches the ${macos_installer_build} and passes Gatekeeper validation.
check_macos_installer() {
check_macos_installer_error="FALSE"
log_super "Status: Gatekeeper and version validation of /Applications/Install ${macos_installer_title}.app..."
# First check to see if the downloaded macOS installer build version matches the ${macos_installer_build}.
[[ -d "/Volumes/Shared Support" ]] && diskutil unmount force "/Volumes/Shared Support" >/dev/null 2>&1
sleep 1
if [[ -f "/Applications/Install ${macos_installer_title}.app/Contents/SharedSupport/SharedSupport.dmg" ]]; then
local hidiutil_response
hidiutil_response=$(hdiutil attach -quiet -noverify -nobrowse "/Applications/Install ${macos_installer_title}.app/Contents/SharedSupport/SharedSupport.dmg" 2>&1)
sleep 1
if [[ -d "/Volumes/Shared Support" ]]; then
if [[ -f "/Volumes/Shared Support/com_apple_MobileAsset_MacSoftwareUpdate/com_apple_MobileAsset_MacSoftwareUpdate.xml" ]]; then
local macos_installer_build_downloaded
macos_installer_build_downloaded=$(/usr/libexec/PlistBuddy -c "Print :Assets:0:Build" "/Volumes/Shared Support/com_apple_MobileAsset_MacSoftwareUpdate/com_apple_MobileAsset_MacSoftwareUpdate.xml")
sleep 1
diskutil unmount force "/Volumes/Shared Support" >/dev/null 2>&1
if [[ -n "${macos_installer_build_downloaded}" ]]; then
if [[ "${macos_installer_build_downloaded}" != "${macos_installer_build}" ]]; then
log_super "Status: Currently downloaded macOS installer build number ${macos_installer_build_downloaded} does not match target build number ${macos_installer_build}."
check_macos_installer_error="TRUE"
fi
else
log_super "Status: Unable to resolve the macOS installer build version."
check_macos_installer_error="TRUE"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_installer_build_downloaded is: ${macos_installer_build_downloaded}"
fi
else
log_super "Status: Unable to locate macOS installer com_apple_MobileAsset_MacSoftwareUpdate.xml file for validation."
check_macos_installer_error="TRUE"
fi
else
log_super "Status: Unable to mount macOS installer SharedSupport.dmg for validation."
check_macos_installer_error="TRUE"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: hidiutil_response is:\n${hidiutil_response}"
fi
fi
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: check_macos_installer_error is: ${check_macos_installer_error}"
# If there are no errors, then move on to gatekeeper validation.
if [[ "${check_macos_installer_error}" == "FALSE" ]]; then
local startosinstall_response
startosinstall_response=$("/Applications/Install ${macos_installer_title}.app/Contents/Resources/startosinstall" --usage 2>&1)
if [[ $(echo "${startosinstall_response}" | grep -c 'Usage: startosinstall') -eq 0 ]]; then
log_super "Status: Currently downloaded macOS installer failed Gatekeeper validation."
check_macos_installer_error="TRUE"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: startosinstall_response is:\n${startosinstall_response}"
fi
fi
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: check_macos_installer_error is: ${check_macos_installer_error}"
}
# Delete any unneeded macOS installers based on the value of ${macos_installer_target} in order to save space.
delete_unneeded_macos_installers() {
local previous_ifs
previous_ifs="${IFS}"
IFS=$'\n'
local mdfind_macos_installers_array
mdfind_macos_installers_array=($(mdfind kind:app -name "Install macOS" 2>/dev/null))
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: mdfind_macos_installers_array is:\n${mdfind_macos_installers_array[*]}"
if [[ -n "${mdfind_macos_installers_array[*]}" ]]; then
for macos_installer_path in "${mdfind_macos_installers_array[@]}"; do
if [[ $(echo "${macos_installer_path}" | grep -c '/Volumes/') -gt 0 ]] || { [[ $(echo "${macos_installer_path}" | grep -c '/Users/') -gt 0 ]] && [[ $(echo "${macos_installer_path}" | grep -c '/Users/.*/Applications/') -eq 0 ]] && [[ $(echo "${macos_installer_path}" | grep -c '/Users/.*/Desktop/') -eq 0 ]] && [[ $(echo "${macos_installer_path}" | grep -c '/Users/.*/Downloads/') -eq 0 ]]; }; then
log_super "Status: Skipping deletion of assumed archived macOS installer at: ${macos_installer_path}"
else
if [[ "${macos_installer_target}" == "FALSE" ]]; then
if [[ "${test_mode_option}" == "TRUE" ]]; then # Test mode.
log_super "Test Mode: macOS upgrades are not allowed, found unnecessary macOS installer at: ${macos_installer_path}"
else # Normal workflow.
log_super "Warning: macOS upgrades are not allowed, removing unnecessary macOS installer at: ${macos_installer_path}"
rm -Rf "${macos_installer_path}" >/dev/null 2>&1
fi
elif [[ "${macos_installer_path}" != "/Applications/Install ${macos_installer_title}.app" ]]; then
if [[ "${test_mode_option}" == "TRUE" ]]; then # Test mode.
log_super "Test Mode: Found unnecessary macOS installer at: ${macos_installer_path}"
else # Normal workflow.
log_super "Warning: Removing unnecessary macOS installer at: ${macos_installer_path}"
rm -Rf "${macos_installer_path}" >/dev/null 2>&1
fi
fi
fi
done
fi
IFS="${previous_ifs}"
}
# Download macOS update or upgrade via softwareupdate command, and also save responses to ${SUPER_LOG}, ${MSU_WORKFLOW_LOG}, and ${SUPER_LOCAL_PLIST}.
download_macos_msu() {
# If ${test_mode_option} then it's not necessary to continue this function.
if [[ "${test_mode_option}" == "TRUE" ]]; then
log_super "Test Mode: Skipping the download macOS update/upgrade via MSU workflow."
if [[ "${workflow_install_now_active}" == "TRUE" ]]; then
log_super "Test Mode: Pausing ${test_mode_timeout_seconds} seconds for install now download notification..."
sleep "${test_mode_timeout_seconds}"
fi
download_maocs_msu_error="FALSE"
return 0
fi
# Start with log and status updates.
if [[ "${macos_version_major}" -ge 14 ]] && [[ "${workflow_download_macos_auth_required}" == "TRUE" ]]; then
log_super "softwareupdate: Starting ${macos_msu_title} authenticated download workflow, check ${MSU_WORKFLOW_LOG} for more detail."
log_status "Running: softwareupdate: Starting ${macos_msu_title} authenticated download workflow."
log_msu "**** S.U.P.E.R.M.A.N. ${SUPER_VERSION} - DOWNLOAD ${macos_msu_title} VIA AUTHENTICATED SOFTWAREUPDATE START ****"
else
log_super "softwareupdate: Starting ${macos_msu_title} download workflow, check ${MSU_WORKFLOW_LOG} for more detail."
log_status "Running: softwareupdate: Starting ${macos_msu_title} download workflow."
log_msu "**** S.U.P.E.R.M.A.N. ${SUPER_VERSION} - DOWNLOAD ${macos_msu_title} VIA SOFTWAREUPDATE START ****"
fi
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_msu_label is: ${macos_msu_label}"
# The softwareupdate download process is backgrounded and is watched via a while loop later on. Also note the difference between macOS versions.
local download_macos_msu_pid
if [[ "${macos_version_major}" -ge 14 ]] && [[ "${workflow_download_macos_auth_required}" == "TRUE" ]]; then # macOS 14+ on Apple Silicon systems that require authenticated downloads.
echo "${auth_local_password}" | launchctl asuser "${current_user_id}" sudo -u root softwareupdate --download "${macos_msu_label}" --agree-to-license --user "${auth_local_account}" --stdinpass >>"${MSU_WORKFLOW_LOG}" 2>&1 &
download_macos_msu_pid="$!"
elif [[ "${macos_version_major}" -ge 13 ]]; then # macOS 13+
if [[ "${mac_cpu_architecture}" == "arm64" ]]; then # Apple Silicon.
echo ' ' | launchctl asuser "${current_user_id}" sudo -u "${current_user_account_name}" softwareupdate --download "${macos_msu_label}" --agree-to-license --user "${current_user_account_name}" --stdinpass >>"${MSU_WORKFLOW_LOG}" 2>&1 &
download_macos_msu_pid="$!"
else # Intel.
launchctl asuser "${current_user_id}" sudo -u "${current_user_account_name}" softwareupdate --download "${macos_msu_label}" --agree-to-license >>"${MSU_WORKFLOW_LOG}" 2>&1 &
download_macos_msu_pid="$!"
fi
elif [[ "${macos_version_major}" -ge 12 ]]; then # macOS 12
if [[ "${mac_cpu_architecture}" == "arm64" ]]; then # Apple Silicon.
launchctl asuser "${current_user_id}" sudo -u root softwareupdate --download "${macos_msu_label}" --agree-to-license --user "root" --stdinpass "" >>"${MSU_WORKFLOW_LOG}" 2>&1 &
download_macos_msu_pid="$!"
else # Intel.
launchctl asuser "${current_user_id}" sudo -u root softwareupdate --download "${macos_msu_label}" --agree-to-license >>"${MSU_WORKFLOW_LOG}" 2>&1 &
download_macos_msu_pid="$!"
fi
else # macOS 11
if [[ "${mac_cpu_architecture}" == "arm64" ]]; then # Apple Silicon.
echo ' ' | softwareupdate --download "${macos_msu_label}" --agree-to-license >>"${MSU_WORKFLOW_LOG}" 2>&1 &
else # Intel.
softwareupdate --download "${macos_msu_label}" --agree-to-license >>"${MSU_WORKFLOW_LOG}" 2>&1 &
download_macos_msu_pid="$!"
fi
fi
# Watch ${MSU_WORKFLOW_LOG} while waiting for the softwareupdate download workflow to complete.
# Note this while read loop has a timeout based on ${TIMEOUT_START_SECONDS} then changes to ${TIMEOUT_MSU_SYSTEM_SECONDS}.
local download_macos_msu_start_error
download_macos_msu_start_error="TRUE"
local download_macos_msu_start_timeout
download_macos_msu_start_timeout="TRUE"
local download_macos_msu_timeout_error
download_macos_msu_timeout_error="TRUE"
local download_macos_msu_timeout_seconds
download_macos_msu_timeout_seconds="${TIMEOUT_START_SECONDS}"
local download_macos_msu_phase
download_macos_msu_phase="START"
local download_macos_msu_complete_perecent
download_macos_msu_complete_perecent=0
local download_macos_msu_complete_perecent_previous
download_macos_msu_complete_perecent_previous=0
local download_macos_msu_complete_display
unset macos_msu_title_downloaded
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: download_macos_msu_timeout_seconds is: ${download_macos_msu_timeout_seconds}"
while read -t "${download_macos_msu_timeout_seconds}" -r log_line; do
# log_super "Debug Mode: Function ${FUNCNAME[0]}: log_line is:\n${log_line}"
if [[ $(echo "${log_line}" | grep -c "Can’t connect") -gt 0 ]] || [[ $(echo "${log_line}" | grep -c "Couldn't communicate") -gt 0 ]]; then
download_macos_msu_start_error="CONNECT"
break
elif [[ $(echo "${log_line}" | grep -c 'No such update') -gt 0 ]]; then
download_macos_msu_start_error="NOUPDATE"
break
elif [[ $(echo "${log_line}" | grep -c 'Not enough free disk space') -gt 0 ]]; then
download_macos_msu_start_error="SPACE"
break
elif [[ $(echo "${log_line}" | grep -c 'Failed to authenticate') -gt 0 ]]; then
download_macos_msu_start_error="AUTH"
break
elif [[ $(echo "${log_line}" | grep -c 'Failed to download') -gt 0 ]]; then
download_macos_msu_start_error="FAILED"
break
elif [[ $(echo "${log_line}" | grep -c 'Downloading') -gt 0 ]] && [[ "${download_macos_msu_phase}" == "START" ]]; then
macos_msu_title_downloaded="${log_line/Downloading /}"
log_super "softwareupdate: ${macos_msu_title_downloaded} is downloading..."
log_msu "**** TIMESTAMP ****"
download_macos_msu_phase="DOWNLOADING"
download_macos_msu_timeout_seconds="${TIMEOUT_MSU_SYSTEM_SECONDS}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: download_macos_msu_timeout_seconds is: ${download_macos_msu_timeout_seconds}"
download_macos_msu_start_error="FALSE"
download_macos_msu_start_timeout="FALSE"
[[ $(echo "${macos_msu_title_downloaded}" | grep -c 'macOS') -gt 0 ]] && download_macos_msu_phase="DOWNLOADING"
elif [[ $(echo "${log_line}" | grep -c 'Downloading') -gt 0 ]] && [[ "${download_macos_msu_phase}" == "DOWNLOADING" ]]; then
download_macos_msu_complete_perecent=$(echo "${log_line}" | sed -e 's/Downloading: //' -e 's/\.[0-9][0-9]//' | tr -d '\n' | tr -d '\r')
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: download_macos_msu_complete_perecent is: ${download_macos_msu_complete_perecent}"
if [[ "${download_macos_msu_complete_perecent}" -ge 60 ]]; then
log_echo_replace_line "${macos_msu_title_downloaded} download progress: 100%\n"
log_super "softwareupdate: ${macos_msu_title_downloaded} download complete, now preparing..."
log_msu "**** TIMESTAMP ****"
download_macos_msu_phase="PREPARING"
elif [[ "${download_macos_msu_complete_perecent}" -gt "${download_macos_msu_complete_perecent_previous}" ]]; then
download_macos_msu_complete_display=$( (echo "${download_macos_msu_complete_perecent} * 1.69" | bc) | cut -d '.' -f1)
log_echo_replace_line "${macos_msu_title_downloaded} download progress: ${download_macos_msu_complete_display}%"
download_macos_msu_complete_perecent_previous=${download_macos_msu_complete_perecent}
fi
elif [[ $(echo "${log_line}" | grep -c 'Downloading') -gt 0 ]] && [[ "${download_macos_msu_phase}" == "PREPARING" ]]; then
download_macos_msu_complete_perecent=$(echo "${log_line}" | sed -e 's/Downloading: //' -e 's/\.[0-9][0-9]//' | tr -d '\n' | tr -d '\r')
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: download_macos_msu_complete_perecent is: ${download_macos_msu_complete_perecent}"
if [[ ${download_macos_msu_complete_perecent} -ge 100 ]]; then
log_echo_replace_line "${macos_msu_title_downloaded} preparing progress: 100%\n"
log_msu "**** TIMESTAMP ****"
download_macos_msu_phase="DONE"
elif [[ "${download_macos_msu_complete_perecent}" -gt "${download_macos_msu_complete_perecent_previous}" ]]; then
download_macos_msu_complete_display=$(((download_macos_msu_complete_perecent - 60) * 2))
log_echo_replace_line "${macos_msu_title_downloaded} preparing progress: ${download_macos_msu_complete_display}%"
download_macos_msu_complete_perecent_previous=${download_macos_msu_complete_perecent}
fi
elif [[ $(echo "${log_line}" | grep -c 'Downloaded') -gt 0 ]]; then
macos_msu_title_downloaded=$(echo "${log_line}" | sed -e 's/://' -e 's/Downloaded //')
log_super "softwareupdate: ${macos_msu_title_downloaded} download and preparation complete."
download_macos_msu_start_error="FALSE"
download_macos_msu_start_timeout="FALSE"
download_macos_msu_timeout_error="FALSE"
break
fi
done < <(tail -n1 -F "${MSU_WORKFLOW_LOG}" | tr -u '%' '\n')
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: download_macos_msu_start_timeout is: ${download_macos_msu_start_timeout}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: download_macos_msu_start_error is: ${download_macos_msu_start_error}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: download_macos_msu_timeout_error is: ${download_macos_msu_timeout_error}"
# If the softwareupdate download workflow completed, then validate the prepared macOS update/upgrade.
if [[ "${download_macos_msu_start_error}" == "FALSE" ]] && [[ "${download_macos_msu_start_timeout}" == "FALSE" ]] && [[ "${download_macos_msu_timeout_error}" == "FALSE" ]]; then
local download_macos_msu_title_error
download_macos_msu_title_error="TRUE"
local download_macos_msu_validation_error
download_macos_msu_validation_error="TRUE"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_msu_title is: ${macos_msu_title}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_msu_title_downloaded is: ${macos_msu_title_downloaded}"
if [[ "${macos_msu_title}" == "${macos_msu_title_downloaded}" ]]; then
download_macos_msu_title_error="FALSE"
local update_asset_attributes
update_asset_attributes=$(defaults read /System/Volumes/Update/Update update-asset-attributes 2>/dev/null)
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: update_asset_attributes is:\n${update_asset_attributes}"
local macos_msu_version_prepared
macos_msu_version_prepared=$(echo "${update_asset_attributes}" | grep -w 'OSVersion' | awk -F '"' '{print $2;}')
if [[ "${macos_version_major}" -ge 13 ]]; then
local macos_msu_prepared_version_extra
macos_msu_prepared_version_extra=$(echo "${update_asset_attributes}" | grep -w 'ProductVersionExtra' | awk -F '"' '{print $2;}')
[[ -n "${macos_msu_prepared_version_extra}" ]] && macos_msu_version_prepared="${macos_msu_version_prepared} ${macos_msu_prepared_version_extra}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_msu_prepared_version_extra is: ${macos_msu_prepared_version_extra}"
fi
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_msu_version_prepared is: ${macos_msu_version_prepared}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_msu_version is: ${macos_msu_version}"
[[ "${macos_msu_version_prepared}" == "${macos_msu_version}" ]] && download_macos_msu_validation_error="FALSE"
fi
fi
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: download_macos_msu_title_error is: ${download_macos_msu_title_error}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: download_macos_msu_validation_error is: ${download_macos_msu_validation_error}"
# If the macOS update/upgrade is downloaded and prepared, then collect information.
if [[ "${download_macos_msu_start_error}" == "FALSE" ]] || [[ "${download_macos_msu_start_timeout}" == "FALSE" ]] || [[ "${download_macos_msu_timeout_error}" == "FALSE" ]] || [[ "${download_macos_msu_title_error}" == "FALSE" ]] || [[ "${download_macos_msu_validation_error}" == "FALSE" ]]; then
log_msu "**** S.U.P.E.R.M.A.N. ${SUPER_VERSION} - DOWNLOAD MACOS VIA SOFTWAREUPDATE COMPLETED ****"
download_maocs_msu_error="FALSE"
macos_msu_download_required="FALSE"
defaults write "${SUPER_LOCAL_PLIST}" MacOSMSULabelDownloaded -string "${macos_msu_label}"
defaults write "${SUPER_LOCAL_PLIST}" MacOSMSULastStartupDownloaded -string "${mac_last_startup}"
else # Some part of the softwareupdate download workflow failed.
if [[ "${download_macos_msu_start_error}" == "CONNECT" ]]; then
log_msu "Error: Unable to reach macOS software update servers."
log_super "Error: Unable to reach macOS software update servers."
elif [[ "${download_macos_msu_start_error}" == "NOUPDATE" ]]; then
log_msu "Error: Unable to find requested macOS update/upgrade via softwareupdate."
log_super "Error: Unable to find requested macOS update/upgrade via softwareupdate."
elif [[ "${download_macos_msu_start_error}" == "SPACE" ]]; then
log_msu "Error: Not enough available storage to download macOS update/upgrade."
log_super "Error: Not enough available storage to download macOS update/upgrade."
elif [[ "${download_macos_msu_start_error}" == "AUTH" ]]; then
log_msu "Error: Download of macOS update/upgrade via softwareupdate failed to authenticate."
log_super "Error: Download of macOS update/upgrade via softwareupdate failed to authenticate."
[[ "${mac_cpu_architecture}" == "arm64" ]] && workflow_download_macos_auth_required="TRUE"
[[ "${mac_cpu_architecture}" == "arm64" ]] && defaults write "${SUPER_LOCAL_PLIST}" WorkflowDownloadMacOSAuthRequired -bool true
elif [[ "${download_macos_msu_start_error}" == "FAILED" ]]; then
log_msu "Error: Download of macOS update/upgrade via softwareupdate failed to start."
log_super "Error: Download of macOS update/upgrade via softwareupdate failed to start."
elif [[ "${download_macos_msu_start_timeout}" == "TRUE" ]]; then
log_msu "Error: Download of macOS update/upgrade via softwareupdate failed to start after waiting for ${download_macos_msu_timeout_seconds} seconds."
log_super "Error: Download of macOS update/upgrade via softwareupdate failed to start after waiting for ${download_macos_msu_timeout_seconds} seconds."
elif [[ "${download_macos_msu_timeout_error}" == "TRUE" ]]; then
log_msu "Error: Download of macOS update/upgrade via softwareupdate failed to complete, as indicated by no progress after waiting for ${download_macos_msu_timeout_seconds} seconds."
log_super "Error: Download of macOS update/upgrade via softwareupdate failed to complete, as indicated by no progress after waiting for ${download_macos_msu_timeout_seconds} seconds."
elif [[ "${download_macos_msu_title_error}" == "TRUE" ]]; then
log_msu "Error: Download of ${macos_msu_title} did not complete or match requested download title."
log_super "Error: Download of ${macos_msu_title} ddid not complete or match requested download title."
else # "${download_macos_msu_validation_error}" == "TRUE"
if [[ "${macos_msu_major_upgrade_target}" != "FALSE" ]]; then
log_msu "Error: Downloaded macOS major upgrade version of ${macos_msu_version_prepared} doesn't match expected version ${macos_msu_version}."
log_super "Error: Downloaded macOS major upgrade version of ${macos_msu_version_prepared} doesn't match expected version ${macos_msu_version}."
else # "${macos_msu_minor_update_target}" != "FALSE"
log_msu "Error: Downloaded macOS minor update version of ${macos_msu_version_prepared} doesn't match expected version ${macos_msu_version}."
log_super "Error: Downloaded macOS mintor update version of ${macos_msu_version_prepared} doesn't match expected version ${macos_msu_version}."
fi
fi
log_msu "**** S.U.P.E.R.M.A.N. ${SUPER_VERSION} - DOWNLOAD MACOS VIA SOFTWAREUPDATE FAILED ****"
download_maocs_msu_error="TRUE"
macos_msu_download_required="TRUE"
defaults delete "${SUPER_LOCAL_PLIST}" MacOSMSULabelDownloaded 2>/dev/null
defaults delete "${SUPER_LOCAL_PLIST}" MacOSMSULastStartupDownloaded 2>/dev/null
kill -9 "${download_macos_msu_pid}" >/dev/null 2>&1
kick_softwareupdated
fi
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: download_maocs_msu_error is: ${download_maocs_msu_error}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_msu_download_required is: ${macos_msu_download_required}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: workflow_download_macos_auth_required is: ${workflow_download_macos_auth_required}"
}
# Download macOS installer via ${MIST_CLI_BINARY}, and also save responses to ${SUPER_LOG}, ${INSTALLER_WORKFLOW_LOG}, and ${SUPER_LOCAL_PLIST}.
download_macos_installer() {
# If ${test_mode_option} then it's not necessary to continue this function.
if [[ "${test_mode_option}" == "TRUE" ]]; then
log_super "Test Mode: Skipping the download macOS update/upgrade via installer workflow."
if [[ "${workflow_install_now_active}" == "TRUE" ]]; then
log_super "Test Mode: Pausing ${test_mode_timeout_seconds} seconds for install now download notification..."
sleep "${test_mode_timeout_seconds}"
fi
download_macos_installer_error="FALSE"
return 0
fi
# Start with log and status updates.
log_super "mist_cli: Starting ${macos_installer_title} ${macos_installer_version}-${macos_installer_build} download installer workflow, check ${INSTALLER_WORKFLOW_LOG} for more detail."
log_status "Running: mist_cli: Starting ${macos_installer_title} ${macos_installer_version}-${macos_installer_build} download installer workflow."
log_installer "**** S.U.P.E.R.M.A.N. ${SUPER_VERSION} - DOWNLOAD ${macos_installer_title} ${macos_installer_version}-${macos_installer_build} INSTALLER APP START ****"
# Background the ${MIST_CLI_BINARY} download process and send to ${INSTALLER_WORKFLOW_LOG}.
local download_macos_installer_mist_pid
if [[ "${macos_beta_program}" == "FALSE" ]]; then
"${MIST_CLI_BINARY}" download installer --force --no-ansi --output-directory "/Applications" --compatible "${macos_installer_build}" application --application-name "Install %NAME%.app" >>"${INSTALLER_WORKFLOW_LOG}" 2>&1 &
download_macos_installer_mist_pid="$!"
else # macOS beta workflow.
"${MIST_CLI_BINARY}" download installer --force --no-ansi --output-directory "/Applications" --compatible --include-betas "${macos_installer_build}" application --application-name "Install %NAME%.app" >>"${INSTALLER_WORKFLOW_LOG}" 2>&1 &
download_macos_installer_mist_pid="$!"
fi
# Watch ${INSTALLER_WORKFLOW_LOG} while waiting for the mist-cli download process to complete.
# Note this while read loop has a timeout based on ${TIMEOUT_START_SECONDS} then changes to ${TIMEOUT_INSTALLER_DOWNLOAD_SECONDS}.
local download_macos_installer_start_error
download_macos_installer_start_error="TRUE"
local download_macos_installer_start_timeout
download_macos_installer_start_timeout="TRUE"
local download_macos_installer_timeout_error
download_macos_installer_timeout_error="TRUE"
local download_macos_installer_timeout_seconds
download_macos_installer_timeout_seconds="${TIMEOUT_START_SECONDS}"
local download_macos_installer_phase
download_macos_installer_phase="START"
local download_macos_installer_complete_percent
download_macos_installer_complete_percent=0
local download_macos_installer_complete_percent_previous
download_macos_installer_complete_percent_previous=0
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: download_macos_installer_timeout_seconds is: ${download_macos_installer_timeout_seconds}"
while read -t "${download_macos_installer_timeout_seconds}" -r log_line; do
# log_super "Debug Mode: Function ${FUNCNAME[0]}: log_line is:\n${log_line}"
if [[ $(echo "${log_line}" | grep -c 'No macOS Installer found') -gt 0 ]]; then
break
elif [[ $(echo "${log_line}" | grep -c 'DOWNLOAD') -gt 0 ]] && [[ "${download_macos_installer_phase}" != "DOWNLOADING" ]]; then
log_super "mist_cli: Install ${macos_installer_title}.app is downloading..."
log_installer "**** TIMESTAMP ****"
download_macos_installer_timeout_seconds="${TIMEOUT_INSTALLER_DOWNLOAD_SECONDS}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: download_macos_installer_timeout_seconds is: ${download_macos_installer_timeout_seconds}"
download_macos_installer_phase="DOWNLOADING"
download_macos_installer_start_error="FALSE"
download_macos_installer_start_timeout="FALSE"
elif [[ "${download_macos_installer_phase}" == "DOWNLOADING" ]] && [[ $(echo "${log_line}" | grep -c 'InstallAssistant.pkg') -gt 0 ]]; then
download_macos_installer_complete_percent=$(echo "${log_line}" | awk -F '(' '{print $2;}' | awk -F '.' '{print $1;}' | tr -d -c 0-9)
download_macos_installer_complete_percent=${download_macos_installer_complete_percent#0}
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: download_macos_installer_complete_percent is: ${download_macos_installer_complete_percent}"
if [[ $download_macos_installer_complete_percent -ge 100 ]]; then
log_echo_replace_line "Install ${macos_installer_title}.app download progress: 100%\n"
log_super "mist_cli: Install ${macos_installer_title}.app downloaded, now verifying..."
log_installer "**** TIMESTAMP ****"
download_macos_installer_phase="DONE"
elif [[ $download_macos_installer_complete_percent -gt $download_macos_installer_complete_percent_previous ]]; then
log_echo_replace_line "Install ${macos_installer_title}.app download progress: ${download_macos_installer_complete_percent}%"
download_macos_installer_complete_percent_previous=$download_macos_installer_complete_percent
fi
elif [[ $(echo "${log_line}" | grep -c 'INSTALL') -gt 0 ]]; then
log_super "mist_cli: Install ${macos_installer_title}.app verified, now preparing..."
log_installer "**** TIMESTAMP ****"
elif [[ $(echo "${log_line}" | grep -c 'APPLICATION') -gt 0 ]]; then
log_super "mist_cli: Install ${macos_installer_title}.app prepared, now moving to /Applications..."
log_installer "**** TIMESTAMP ****"
download_macos_installer_phase="APPLICATION"
elif [[ $(echo "${log_line}" | grep -c 'TEARDOWN') -gt 0 ]]; then
log_installer "**** TIMESTAMP ****"
download_macos_installer_start_error="FALSE"
download_macos_installer_start_timeout="FALSE"
download_macos_installer_timeout_error="FALSE"
break
fi
done < <(tail -n1 -F "${INSTALLER_WORKFLOW_LOG}")
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: download_macos_installer_start_error is: ${download_macos_installer_start_error}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: download_macos_installer_start_timeout is: ${download_macos_installer_start_timeout}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: download_macos_installer_timeout_error is: ${download_macos_installer_timeout_error}"
# If the mist-cli download workflow completed, then validate the macOS installer application.
if [[ "${download_macos_installer_start_error}" == "FALSE" ]] && [[ "${download_macos_installer_start_timeout}" == "FALSE" ]] && [[ "${download_macos_installer_timeout_error}" == "FALSE" ]]; then
local download_macos_installer_app_error
download_macos_installer_app_error="TRUE"
local download_macos_installer_validation_error
download_macos_installer_validation_error="TRUE"
if [[ -d "/Applications/Install ${macos_installer_title}.app" ]]; then
download_macos_installer_app_error="FALSE"
check_macos_installer
[[ "${check_macos_installer_error}" == "FALSE" ]] && download_macos_installer_validation_error="FALSE"
fi
fi
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: download_macos_installer_app_error is: ${download_macos_installer_app_error}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: download_macos_installer_validation_error is: ${download_macos_installer_validation_error}"
# If the macOS installer application is downloaded and valid, then collect information.
if [[ "${download_macos_installer_start_error}" == "FALSE" ]] || [[ "${download_macos_installer_start_timeout}" == "FALSE" ]] || [[ "${download_macos_installer_timeout_error}" == "FALSE" ]] || [[ "${download_macos_installer_app_error}" == "FALSE" ]] || [[ "${download_macos_installer_validation_error}" == "FALSE" ]]; then
log_installer "Status: A macOS installer is now available at: /Applications/Install ${macos_installer_title}.app"
log_installer "**** S.U.P.E.R.M.A.N. ${SUPER_VERSION} - DOWNLOAD MACOS INSTALLER APP COMPLETE ****"
log_super "Status: A macOS installer is now available at: /Applications/Install ${macos_installer_title}.app"
download_macos_installer_error="FALSE"
macos_installer_download_required="FALSE"
defaults write "${SUPER_LOCAL_PLIST}" MacOSInstallerDownloaded -string "${macos_installer_title} ${macos_installer_version}-${macos_installer_build}"
else # Some part of the macOS installer download workflow failed.
if [[ "${download_macos_installer_start_error}" == "TRUE" ]]; then
log_installer "Error: macOS installer download failed start or the requested installer could not be found."
log_super "Error: macOS installer download failed start or the requested installer could not be found."
elif [[ "${download_macos_installer_start_timeout}" == "TRUE" ]]; then
log_installer "Error: macOS installer download failed to start after waiting for ${download_macos_installer_timeout_seconds} seconds."
log_super "Error: macOS installer download failed to start after waiting for ${download_macos_installer_timeout_seconds} seconds."
elif [[ "${download_macos_installer_timeout_error}" == "TRUE" ]]; then
log_installer "Error: macOS installer download failed to complete, as indicated by no progress after waiting for ${download_macos_installer_timeout_seconds} seconds."
log_super "Error: macOS installer download failed to complete, as indicated by no progress after waiting for ${download_macos_installer_timeout_seconds} seconds."
elif [[ "${download_macos_installer_app_error}" == "TRUE" ]]; then
log_installer "Error: The target macOS installer could not be found in the /Applications folder."
log_super "Error: The target macOS installer could not be found in the /Applications folder."
else # "${download_macos_installer_validation_error}" == "TRUE"
log_installer "Error: macOS installer failed local validations, removing installer: /Applications/Install ${macos_installer_title}.app."
log_super "Error: macOS installer failed local validations, removing installer: /Applications/Install ${macos_installer_title}.app."
rm -Rf "/Applications/Install ${macos_installer_title}.app" >/dev/null 2>&1
fi
log_installer "**** S.U.P.E.R.M.A.N. ${SUPER_VERSION} - DOWNLOAD MACOS INSTALLER APP FAILED ****"
kill -9 "${download_macos_installer_mist_pid}" >/dev/null 2>&1
download_macos_installer_error="TRUE"
defaults delete "${SUPER_LOCAL_PLIST}" MacOSInstallerDownloaded 2>/dev/null
fi
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: download_macos_installer_error is: ${download_macos_installer_error}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_installer_download_required is: ${macos_installer_download_required}"
}
# This function contains logic to determine the correct download behavior based on system condition and specified options.
# This function only runs if there is an active user.
workflow_download_macos() {
workflow_download_macos_error="FALSE"
workflow_download_macos_check_user="FALSE"
download_macos_installer_error="FALSE"
download_maocs_msu_error="FALSE"
push_macos_mdm_download_error="FALSE"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_installer_download_required is: ${macos_installer_download_required}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_msu_download_required is: ${macos_msu_download_required}"
# If downloads are required then check available storage.
if [[ "${macos_installer_download_required}" == "TRUE" ]] || [[ "${macos_msu_download_required}" == "TRUE" ]]; then
check_storage_available
if [[ "${check_storage_available_error}" == "FALSE" ]]; then
if [[ "${storage_ready}" == "FALSE" ]]; then
if [[ "${current_user_account_name}" == "FALSE" ]]; then
log_super "Error: Current available storage is at ${storage_available_gigabytes} GBs which is below the ${storage_required_gigabytes} GBs that is required for download. And no active user is logged in to attempt remediation."
workflow_download_macos_error="TRUE"
else # A normal user is currently logged in.
dialog_insufficient_storage
[[ "${dialog_insufficient_storage_error}" == "TRUE" ]] && workflow_download_macos_error="TRUE"
fi
fi
else # "${check_storage_available_error}" == "TRUE"
workflow_download_macos_error="TRUE"
fi
else # Downloads not required so log that download is not needed.
[[ "${macos_installer_target}" != "FALSE" ]] && log_super "Status: Previously downloaded ${macos_installer_title} ${macos_installer_version}-${macos_installer_build} installer is available at: /Applications/Install ${macos_installer_title}.app"
[[ "${macos_msu_major_upgrade_target}" != "FALSE" ]] && log_super "Status: Previously downloaded macOS major upgrade is prepared: ${macos_msu_label_downloaded}"
[[ "${macos_msu_minor_update_target}" != "FALSE" ]] && log_super "Status: Previously downloaded macOS minor update is prepared: ${macos_msu_label_downloaded}"
fi
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: workflow_download_macos_error is: ${workflow_download_macos_error}"
# If (no errors) and ${test_mode_option} then simulate dialogs and notifications for potential user authenticated download dialog.
if [[ "${workflow_download_macos_error}" == "FALSE" ]] && [[ "${test_mode_option}" == "TRUE" ]] && [[ "${macos_version_major}" -ge 14 ]] && [[ "${mac_cpu_architecture}" == "arm64" ]] && [[ "${workflow_macos_auth}" == "USER" ]]; then
local current_user_password_policies
local current_user_system_password_policies
[[ $(dscl . read "/Users/${current_user_account_name}" accountPolicyData 2>/dev/null | grep -c 'policies') -gt 0 ]] && current_user_password_policies="TRUE"
[[ $(system_profiler SPConfigurationProfileDataType | grep -c 'passwordpolicy') -gt 0 ]] && current_user_system_password_policies="TRUE"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: current_user_password_policies is: ${current_user_password_policies}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: current_user_system_password_policies is: ${current_user_system_password_policies}"
if [[ "${current_user_password_policies}" == "TRUE" ]] || [[ "${current_user_system_password_policies}" == "TRUE" ]]; then
workflow_download_macos_auth_required="TRUE"
defaults write "${SUPER_LOCAL_PLIST}" WorkflowDownloadMacOSAuthRequired -bool true
log_super "Test Mode: Simluating authenticated download requirement because user password policies were found. Under normal curcumstances this dialog would only appear if an earlier download attempt failed because it required authentication."
fi
fi
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: workflow_download_macos_auth_required is: ${workflow_download_macos_auth_required}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: workflow_install_now_active is: ${workflow_install_now_active}"
# If (no errors) and macOS installer download is needed.
if [[ "${workflow_download_macos_error}" == "FALSE" ]] && [[ "${macos_installer_download_required}" == "TRUE" ]]; then
[[ "${workflow_install_now_active}" == "TRUE" ]] && notification_install_now_download
download_macos_installer
[[ "${download_macos_installer_error}" == "TRUE" ]] && workflow_download_macos_error="TRUE"
[[ "${download_macos_installer_error}" == "FALSE" ]] && workflow_download_macos_check_user="TRUE"
fi
# If (no errors) and macOS update/upgrade via MSU download is needed, attempt an unauthenticated download.
if [[ "${workflow_download_macos_error}" == "FALSE" ]] && [[ "${macos_msu_download_required}" == "TRUE" ]] && [[ "${workflow_download_macos_auth_required}" != "TRUE" ]]; then
[[ "${workflow_install_now_active}" == "TRUE" ]] && notification_install_now_download
download_macos_msu
if [[ "${download_maocs_msu_error}" == "TRUE" ]]; then
if [[ "${workflow_download_macos_auth_required}" == "TRUE" ]]; then
log_super "Warning: Initial attempt of unauthenticated download of macOS update/upgrade via MSU failed, failing over to authenticated download workflow."
else
workflow_download_macos_error="TRUE"
fi
else
workflow_download_macos_check_user="TRUE"
fi
fi
# If (no errors) and macOS update/upgrade via MSU authenticated download is needed.
if [[ "${workflow_download_macos_error}" == "FALSE" ]] && [[ "${macos_msu_download_required}" == "TRUE" ]] && [[ "${workflow_download_macos_auth_required}" == "TRUE" ]]; then
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: workflow_macos_auth is: ${workflow_macos_auth}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_mdm_failover_to_user_status is: ${auth_mdm_failover_to_user_status}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_credential_failover_to_user_option is: ${auth_credential_failover_to_user_option}"
# If an MDM workflow is expected, first check for MDM service, bootstrap token, and possibly failover to user authentication workflow.
if [[ "${workflow_macos_auth}" == "JAMF" ]]; then
check_mdm_service
if [[ "${auth_error_mdm}" == "TRUE" ]]; then
if [[ "${auth_mdm_failover_to_user_status}" == "TRUE" ]] || [[ $(echo "${auth_mdm_failover_to_user_option}" | grep -c 'ERROR') -gt 0 ]] || { [[ "${workflow_install_now_active}" == "TRUE" ]] && [[ $(echo "${auth_mdm_failover_to_user_option}" | grep -c 'INSTALLNOW') -gt 0 ]]; }; then
log_super "Warning: MDM service is not available, failing over to local (and possibly user authenticated) download workflow."
workflow_macos_auth="FAILOVER"
else
log_super "Error: Can not use MDM workflow because the MDM service is not available."
workflow_download_macos_error="TRUE"
fi
else # MDM service is available.
check_bootstrap_token_escrow
if [[ "${auth_error_bootstrap_token}" == "TRUE" ]]; then
if [[ "${auth_mdm_failover_to_user_status}" == "TRUE" ]] || [[ $(echo "${auth_mdm_failover_to_user_option}" | grep -c 'ERROR') -gt 0 ]] || { [[ "${workflow_install_now_active}" == "TRUE" ]] && [[ $(echo "${auth_mdm_failover_to_user_option}" | grep -c 'INSTALLNOW') -gt 0 ]]; }; then
log_super "Warning: Missing or invalid bootstrap token escrow, failing over to local (and possibly user authenticated) download workflow."
workflow_macos_auth="FAILOVER"
else
log_super "Error: Can not use MDM workflow because this computer's bootstrap token is not escrowed."
workflow_download_macos_error="TRUE"
fi
fi
fi
fi
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: workflow_macos_auth is: ${workflow_macos_auth}"
# If (no errors) then get authentication.
if [[ "${workflow_download_macos_error}" == "FALSE" ]] && [[ "${workflow_macos_auth}" != "FALSE" ]]; then
if [[ "${workflow_macos_auth}" == "LOCAL" ]] || [[ "${workflow_macos_auth}" == "JAMF" ]]; then
get_saved_authentication
if [[ "${auth_error_saved}" == "TRUE" ]]; then
if [[ "${auth_credential_failover_to_user_option}" == "TRUE" ]] || [[ "${auth_ask_user_to_save_password}" == "TRUE" ]]; then
log_super "Warning: Saved authentication error, failing over to user authenticated download workflow."
workflow_macos_auth="FAILOVER"
if [[ "${dialog_user_auth_valid}" != "TRUE" ]]; then
dialog_user_auth_type="DOWNLOAD"
dialog_user_auth
unset dialog_user_auth_type
[[ "${dialog_user_auth_error}" == "TRUE" ]] && workflow_download_macos_error="TRUE"
fi
else
log_super "Error: Unable to use saved authentication for download and the --auth-credential-failover-to-user option is not enabled."
workflow_download_macos_error="TRUE"
fi
fi
else # [[ "${workflow_macos_auth}" == "USER" ]] || [[ "${workflow_macos_auth}" == "FAILOVER" ]]
if [[ "${dialog_user_auth_valid}" != "TRUE" ]]; then
dialog_user_auth_type="DOWNLOAD"
dialog_user_auth
unset dialog_user_auth_type
[[ "${dialog_user_auth_error}" == "TRUE" ]] && workflow_download_macos_error="TRUE"
fi
fi
fi
# If no errors, then start the appropriate authenticated download workflow.
if [[ "${workflow_download_macos_error}" == "FALSE" ]]; then
[[ "${workflow_install_now_active}" == "TRUE" ]] && notification_install_now_download
if [[ "${workflow_macos_auth}" == "LOCAL" ]] || [[ "${workflow_macos_auth}" == "USER" ]] || [[ "${workflow_macos_auth}" == "FAILOVER" ]]; then
download_macos_msu
[[ "${download_maocs_msu_error}" == "TRUE" ]] && workflow_download_macos_error="TRUE"
[[ "${download_maocs_msu_error}" == "FALSE" ]] && workflow_download_macos_check_user="TRUE"
else # [[ "${workflow_macos_auth}" == "JAMF" ]]
push_macos_mdm_workflow="DOWNLOAD"
push_macos_mdm
unset push_macos_mdm_workflow
[[ "${push_macos_mdm_download_error}" == "TRUE" ]] && workflow_download_macos_error="TRUE"
[[ "${push_macos_mdm_download_error}" == "FALSE" ]] && workflow_download_macos_check_user="TRUE"
fi
fi
fi
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: workflow_download_macos_error is: ${workflow_download_macos_error}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: workflow_download_macos_check_user is: ${workflow_download_macos_check_user}"
# Handle download workflow failures.
if [[ "${workflow_download_macos_error}" == "TRUE" ]]; then
if [[ "${workflow_install_now_active}" == "TRUE" ]]; then # Install now workflow mode.
log_super "Error: Download macOS update/upgrade workflow failed, install now workflow can not continue."
log_status "Inactive Error: Download macOS update/upgrade workflow failed, install now workflow can not continue."
notification_install_now_failed
exit_error
else # Default super workflow.
deferral_timer_minutes="${deferral_timer_error_minutes}"
log_super "Error: Download macOS update/upgrade workflow failed, trying again in ${deferral_timer_minutes} minutes."
log_status "Pending: Download macOS update/upgrade workflow failed, trying again in ${deferral_timer_minutes} minutes."
set_auto_launch_deferral
fi
fi
}
# MARK: *** Installation & Restart ***
################################################################################
# Install only non-system macOS software updates via the softwareupdate command, and also save responses to ${SUPER_LOG} and ${MSU_WORKFLOW_LOG}.
install_non_system_msu() {
install_non_system_msu_error="TRUE"
log_super "softwareupdate: Starting non-system macOS software updates installation workflow, check ${MSU_WORKFLOW_LOG} for more detail."
log_status "Running: softwareupdate: Starting non-system macOS software updates installation workflow."
log_msu "**** S.U.P.E.R.M.A.N. ${SUPER_VERSION} - INSTALL NON-SYSTEM UPDATES VIA SOFTWAREUPDATE START ****"
local previous_ifs
previous_ifs="${IFS}"
IFS=$' '
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: non_system_msu_labels_array is:\n${non_system_msu_labels_array[*]}"
# The update process is backgrounded and is watched via a while loop later on. Also note the different requirements between macOS versions.
local download_macos_msu_pid
if [[ ${macos_version_major} -ge 12 ]]; then
if [[ "${current_user_account_name}" == "FALSE" ]]; then
sudo -i softwareupdate --install "${non_system_msu_labels_array[@]}" --agree-to-license >>"${MSU_WORKFLOW_LOG}" 2>&1 &
download_macos_msu_pid="$!"
else # Local user is logged in.
launchctl asuser "${current_user_id}" sudo -u "${current_user_account_name}" softwareupdate --install "${non_system_msu_labels_array[@]}" --agree-to-license >>"${MSU_WORKFLOW_LOG}" 2>&1 &
download_macos_msu_pid="$!"
fi
else # macOS 11
softwareupdate --install "${non_system_msu_labels_array[@]}" --agree-to-license >>"${MSU_WORKFLOW_LOG}" 2>&1 &
download_macos_msu_pid="$!"
fi
# Watch ${MSU_WORKFLOW_LOG} while waiting for the softwareupdate installation workflow to complete.
# Note this while read loop has a timeout based on ${TIMEOUT_START_SECONDS} then changes to ${TIMEOUT_non_system_msu_SECONDS}.
local install_non_system_msu_start_timeout
install_non_system_msu_start_timeout="TRUE"
local install_non_system_msu_start_error
install_non_system_msu_start_error="TRUE"
local install_non_system_msu_timeout_error
install_non_system_msu_timeout_error="TRUE"
local install_non_system_msu_timeout_seconds
install_non_system_msu_timeout_seconds="${TIMEOUT_START_SECONDS}"
local install_non_system_msu_installed_title
local install_non_system_msu_installed_titles_array
install_non_system_msu_installed_titles_array=()
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: install_non_system_msu_timeout_seconds is: ${install_non_system_msu_timeout_seconds}"
while read -t "${install_non_system_msu_timeout_seconds}" -r log_line; do
# log_super "Debug Mode: Function ${FUNCNAME[0]}: log_line is:\n${log_line}"
if [[ $(echo "${log_line}" | grep -c "Can’t connect") -gt 0 ]] || [[ $(echo "${log_line}" | grep -c "Couldn't communicate") -gt 0 ]] || [[ $(echo "${log_line}" | grep -c 'No such update') -gt 0 ]]; then
install_non_system_msu_start_timeout="FALSE"
break
elif [[ $(echo "${log_line}" | grep -c 'Downloading') -gt 0 ]]; then
macos_msu_title_downloaded=$(echo "${log_line}" | sed -e 's/://' | awk -F 'Downloading ' '{print $2;}')
log_super "softwareupdate: ${macos_msu_title_downloaded} is downloading..."
log_msu "**** TIMESTAMP ****"
install_non_system_msu_timeout_seconds="${TIMEOUT_non_system_msu_SECONDS}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: install_non_system_msu_timeout_seconds is: ${install_non_system_msu_timeout_seconds}"
install_non_system_msu_start_timeout="FALSE"
install_non_system_msu_start_error="FALSE"
elif [[ $(echo "${log_line}" | grep -c 'Downloaded') -gt 0 ]]; then
macos_msu_title_downloaded=$(echo "${log_line}" | sed -e 's/://' | awk -F 'Downloaded ' '{print $2;}')
log_super "softwareupdate: ${macos_msu_title_downloaded} download complete."
install_non_system_msu_timeout_seconds="${TIMEOUT_non_system_msu_SECONDS}"
log_msu "**** TIMESTAMP ****"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: install_non_system_msu_timeout_seconds is: ${install_non_system_msu_timeout_seconds}"
install_non_system_msu_start_timeout="FALSE"
install_non_system_msu_start_error="FALSE"
elif [[ $(echo "${log_line}" | grep -c 'Done with') -gt 0 ]]; then
install_non_system_msu_installed_title=$(echo "${log_line}" | sed -e 's/://' | awk -F 'Done with ' '{print $2;}')
log_super "softwareupdate: ${install_non_system_msu_installed_title} installed."
install_non_system_msu_installed_titles_array+=("${install_non_system_msu_installed_title}")
log_msu "**** TIMESTAMP ****"
install_non_system_msu_start_timeout="FALSE"
install_non_system_msu_start_error="FALSE"
elif [[ $(echo "${log_line}" | grep -c 'Done.') -gt 0 ]]; then
log_msu "**** TIMESTAMP ****"
install_non_system_msu_timeout_error="FALSE"
break
fi
done < <(tail -n1 -F "${MSU_WORKFLOW_LOG}")
# If the softwareupdate installation workflow completed, then validate and collect information.
if [[ "${install_non_system_msu_start_timeout}" == "FALSE" ]] && [[ "${install_non_system_msu_start_error}" == "FALSE" ]] && [[ "${install_non_system_msu_timeout_error}" == "FALSE" ]]; then
local previous_ifs
previous_ifs="${IFS}"
IFS=$'\n'
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: non_system_msu_titles_array is:\n${non_system_msu_titles_array[*]}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: install_non_system_msu_installed_titles_array is:\n${install_non_system_msu_installed_titles_array[*]}"
if [[ ! $(echo -e "${non_system_msu_titles_array[*]}\n${install_non_system_msu_installed_titles_array[*]}" | sort | uniq -u) ]]; then
log_msu "**** S.U.P.E.R.M.A.N. ${SUPER_VERSION} - INSTALL NON-SYSTEM UPDATES VIA SOFTWAREUPDATE COMPLETED ****"
install_non_system_msu_error="FALSE"
else # The expected ${non_system_msu_titles_array} did not match the ${install_non_system_msu_installed_titles_array}.
log_msu "**** S.U.P.E.R.M.A.N. ${SUPER_VERSION} - INSTALL NON-SYSTEM UPDATES VIA SOFTWAREUPDATE INCOMPLETE ****"
log_msu "Error: Installation of non-system macOS software updates did not complete."
log_super "Error: Installation of non-system macOS software updates did not complete."
fi
else # The softwareupdate installation workflow failed.
log_msu "**** S.U.P.E.R.M.A.N. ${SUPER_VERSION} - INSTALL NON-SYSTEM UPDATES VIA SOFTWAREUPDATE FAILED ****"
if [[ "${install_non_system_msu_start_timeout}" == "TRUE" ]]; then
log_msu "Error: Installation of non-system macOS software updates failed to start after waiting for ${install_non_system_msu_timeout_seconds} seconds."
log_super "Error: Installation of non-system macOS software updates failed to start after waiting for ${install_non_system_msu_timeout_seconds} seconds."
elif [[ "${install_non_system_msu_start_error}" == "TRUE" ]]; then
log_msu "Error: Unable to reach macOS software update server."
log_super "Error: Unable to reach macOS software update server."
elif [[ "${install_non_system_msu_timeout_error}" == "TRUE" ]]; then
log_msu "Error: Installation of non-system macOS software updates failed, as indicated by no progress after waiting for ${install_non_system_msu_timeout_seconds} seconds."
log_super "Error: Installation of non-system macOS software updates failed, as indicated by no progress after waiting for ${install_non_system_msu_timeout_seconds} seconds."
fi
kill -9 "${download_macos_msu_pid}" >/dev/null 2>&1
kick_softwareupdated
fi
IFS="${previous_ifs}"
}
# Install macOS updates via the softwareupdate command, and also save responses to ${SUPER_LOG}, ${MSU_WORKFLOW_LOG}, and ${SUPER_LOCAL_PLIST}.
install_macos_msu() {
[[ "${current_user_account_name}" != "FALSE" ]] && notification_restart
# If ${test_mode_option} then it's not necessary to continue this function.
if [[ "${test_mode_option}" == "TRUE" ]]; then
[[ "${workflow_scheduled_install_active}" == "TRUE" ]] && defaults delete "${SUPER_LOCAL_PLIST}" WorkflowScheduledInstall 2>/dev/null
log_super "Test Mode: Skipping the macOS update/upgrade via softwareupdate workflow."
if [[ "${current_user_account_name}" != "FALSE" ]]; then
log_super "Test Mode: Pausing ${test_mode_timeout_seconds} seconds for the restart notification..."
sleep "${test_mode_timeout_seconds}"
killall -9 "IBM Notifier" "IBM Notifier Popup" >/dev/null 2>&1
fi
# Reset various items after test macOS update is complete.
defaults delete "${SUPER_LOCAL_PLIST}" WorkflowDownloadMacOSAuthRequired 2>/dev/null
reset_schedule_zero_date
reset_deadline_counters
return 0
fi
# Start with log and status updates.
if [[ "${macos_msu_major_upgrade_target}" != "FALSE" ]]; then # macOS major upgrade via MSU.
if [[ "${macos_msu_download_required}" == "TRUE" ]]; then # If no ${current_user_account_name} then the sytem update was not pre-downloaded.
log_super "softwareupdate: Starting ${macos_msu_label} download and upgrade workflow, check ${MSU_WORKFLOW_LOG} for more detail."
log_status "Running: softwareupdate: Starting ${macos_msu_label} download and upgrade workflow."
log_msu "**** S.U.P.E.R.M.A.N. ${SUPER_VERSION} - DOWNLOAD AND UPGRADE ${macos_msu_label} VIA SOFTWAREUPDATE START ****"
else
log_super "softwareupdate: Starting ${macos_msu_label} upgrade workflow, check ${MSU_WORKFLOW_LOG} for more detail."
log_status "Running: softwareupdate: Starting ${macos_msu_label} upgrade workflow."
log_msu "**** S.U.P.E.R.M.A.N. ${SUPER_VERSION} - UPGRADE ${macos_msu_label} VIA SOFTWAREUPDATE START ****"
fi
else # macOS minor update via MSU.
if [[ "${macos_msu_download_required}" == "TRUE" ]]; then # If no ${current_user_account_name} then the sytem update was not pre-downloaded.
log_super "softwareupdate: Starting ${macos_msu_label} download and update workflow, check ${MSU_WORKFLOW_LOG} for more detail."
log_status "Running: softwareupdate: Starting ${macos_msu_label} download and update workflow."
log_msu "**** S.U.P.E.R.M.A.N. ${SUPER_VERSION} - DOWNLOAD AND UPDATE ${macos_msu_label} VIA SOFTWAREUPDATE START ****"
else
log_super "softwareupdate: Starting ${macos_msu_label} update workflow, check ${MSU_WORKFLOW_LOG} for more detail."
log_status "Running: softwareupdate: Starting ${macos_msu_label} update workflow."
log_msu "**** S.U.P.E.R.M.A.N. ${SUPER_VERSION} - UPDATE ${macos_msu_label} VIA SOFTWAREUPDATE START ****"
fi
fi
# The update/upgrade process is backgrounded and is watched via while loops later on. Also note the different requirements between macOS versions.
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_msu_label is: ${macos_msu_label}"
local install_macos_msu_pid
if [[ "${macos_version_major}" -ge 13 ]]; then # macOS 13+
if [[ "${current_user_account_name}" == "FALSE" ]]; then # Local user not is logged in.
if [[ "${mac_cpu_architecture}" == "arm64" ]]; then # Apple Silicon.
echo "${auth_local_password}" | sudo -u root softwareupdate --install "${macos_msu_label}" --restart --force --no-scan --agree-to-license --user "${auth_local_account}" --stdinpass >>"${MSU_WORKFLOW_LOG}" 2>&1 &
install_macos_msu_pid="$!"
else # Intel.
sudo -u root softwareupdate --install "${macos_msu_label}" --restart --force --no-scan --agree-to-license >>"${MSU_WORKFLOW_LOG}" 2>&1 &
install_macos_msu_pid="$!"
fi
else # Local user is logged in.
if [[ "${mac_cpu_architecture}" == "arm64" ]]; then # Apple Silicon.
echo "${auth_local_password}" | launchctl asuser "${current_user_id}" sudo -u root softwareupdate --install "${macos_msu_label}" --restart --force --no-scan --agree-to-license --user "${auth_local_account}" --stdinpass >>"${MSU_WORKFLOW_LOG}" 2>&1 &
install_macos_msu_pid="$!"
else # Intel.
launchctl asuser "${current_user_id}" sudo -u root softwareupdate --install "${macos_msu_label}" --restart --force --no-scan --agree-to-license >>"${MSU_WORKFLOW_LOG}" 2>&1 &
install_macos_msu_pid="$!"
fi
fi
elif [[ "${macos_version_major}" -ge 12 ]]; then # macOS 12
if [[ "${current_user_account_name}" == "FALSE" ]]; then # Local user not is logged in.
if [[ "${mac_cpu_architecture}" == "arm64" ]]; then # Apple Silicon.
sudo -u root softwareupdate --install "${macos_msu_label}" --restart --force --no-scan --agree-to-license --user "${auth_local_account}" --stdinpass "${auth_local_password}" >>"${MSU_WORKFLOW_LOG}" 2>&1 &
install_macos_msu_pid="$!"
else # Intel.
sudo -u root softwareupdate --install "${macos_msu_label}" --restart --force --no-scan --agree-to-license >>"${MSU_WORKFLOW_LOG}" 2>&1 &
install_macos_msu_pid="$!"
fi
else # Local user is logged in.
if [[ "${mac_cpu_architecture}" == "arm64" ]]; then # Apple Silicon.
launchctl asuser "${current_user_id}" sudo -u root softwareupdate --install "${macos_msu_label}" --restart --force --no-scan --agree-to-license --user "${auth_local_account}" --stdinpass "${auth_local_password}" >>"${MSU_WORKFLOW_LOG}" 2>&1 &
install_macos_msu_pid="$!"
else # Intel.
launchctl asuser "${current_user_id}" sudo -u root softwareupdate --install "${macos_msu_label}" --restart --force --no-scan --agree-to-license >>"${MSU_WORKFLOW_LOG}" 2>&1 &
install_macos_msu_pid="$!"
fi
fi
else # macOS 11
if [[ "${mac_cpu_architecture}" == "arm64" ]]; then # Apple Silicon.
echo ' ' | softwareupdate --install "${macos_msu_label}" --restart --force --no-scan --agree-to-license >>"${MSU_WORKFLOW_LOG}" 2>&1 &
install_macos_msu_pid="$!"
else # Intel.
softwareupdate --install "${macos_msu_label}" --restart --force --no-scan --agree-to-license >>"${MSU_WORKFLOW_LOG}" 2>&1 &
install_macos_msu_pid="$!"
fi
fi
disown -a
# Watch ${MSU_WORKFLOW_LOG} while waiting for the softwareupdate installation workflow to complete.
# Note this while read loop has a timeout based on ${TIMEOUT_START_SECONDS} then changes to ${TIMEOUT_MSU_SYSTEM_SECONDS}.
local install_macos_msu_start_error
install_macos_msu_start_error="TRUE"
local install_macos_msu_start_timeout
install_macos_msu_start_timeout="TRUE"
local install_macos_msu_timeout_error
install_macos_msu_timeout_error="TRUE"
local install_macos_msu_timeout_seconds
install_macos_msu_timeout_seconds="${TIMEOUT_START_SECONDS}"
local install_macos_msu_phase
install_macos_msu_phase="START"
local install_macos_msu_complete_percent
install_macos_msu_complete_percent=0
local install_macos_msu_complete_percent_previous
install_macos_msu_complete_percent_previous=0
local install_macos_msu_complete_percent_display
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: install_macos_msu_timeout_seconds is: ${install_macos_msu_timeout_seconds}"
while read -t "${install_macos_msu_timeout_seconds}" -r log_line; do
# log_super "Debug Mode: Function ${FUNCNAME[0]}: log_line is:\n${log_line}"
if [[ $(echo "${log_line}" | grep -c "Can’t connect") -gt 0 ]] || [[ $(echo "${log_line}" | grep -c "Couldn't communicate") -gt 0 ]] || [[ $(echo "${log_line}" | grep -c 'No such update') -gt 0 ]] || [[ $(echo "${log_line}" | grep -c 'Failed to download') -gt 0 ]]; then
break
elif [[ $(echo "${log_line}" | grep -c 'Downloading') -gt 0 ]] && [[ $(echo "${log_line}" | grep -c 'Downloading:') -eq 0 ]]; then
macos_msu_title_downloaded="${log_line/Downloading /}"
log_super "softwareupdate: ${macos_msu_title_downloaded} is downloading..."
log_msu "**** TIMESTAMP ****"
install_macos_msu_timeout_seconds="${TIMEOUT_MSU_SYSTEM_SECONDS}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: install_macos_msu_timeout_seconds is: ${install_macos_msu_timeout_seconds}"
install_macos_msu_start_error="FALSE"
[[ $(echo "${macos_msu_title_downloaded}" | grep -c 'macOS') -gt 0 ]] && install_macos_msu_phase="DOWNLOADING"
elif [[ $(echo "${log_line}" | grep -c 'Downloading:') -gt 0 ]] && [[ "${install_macos_msu_phase}" == "DOWNLOADING" ]]; then
install_macos_msu_complete_percent=$(echo "${log_line}" | sed -e 's/Downloading: //' -e 's/\.[0-9][0-9]//' | tr -d '\n' | tr -d '\r')
install_macos_msu_complete_percent=${install_macos_msu_complete_percent#0}
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: install_macos_msu_complete_percent is: ${install_macos_msu_complete_percent}"
if [[ $install_macos_msu_complete_percent -ge 60 ]]; then
log_echo_replace_line "${macos_msu_title_downloaded} download progress: 100%\n"
log_super "softwareupdate: ${macos_msu_title_downloaded} download complete, now preparing..."
log_msu "**** TIMESTAMP ****"
install_macos_msu_phase="PREPARING"
elif [[ $install_macos_msu_complete_percent -gt $install_macos_msu_complete_percent_previous ]]; then
install_macos_msu_complete_percent_display=$( (echo "$install_macos_msu_complete_percent * 1.69" | bc) | cut -d '.' -f1)
log_echo_replace_line "${macos_msu_title_downloaded} download progress: ${install_macos_msu_complete_percent_display}%"
install_macos_msu_complete_percent_previous=$install_macos_msu_complete_percent
fi
elif [[ $(echo "${log_line}" | grep -c 'Downloading:') -gt 0 ]] && [[ "${install_macos_msu_phase}" == "PREPARING" ]]; then
install_macos_msu_complete_percent=$(echo "${log_line}" | sed -e 's/Downloading: //' -e 's/\.[0-9][0-9]//' | tr -d '\n' | tr -d '\r')
install_macos_msu_complete_percent=${install_macos_msu_complete_percent#0}
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: install_macos_msu_complete_percent is: ${install_macos_msu_complete_percent}"
if [[ $install_macos_msu_complete_percent -ge 100 ]]; then
log_echo_replace_line "${macos_msu_title_downloaded} preparing progress: 100%\n"
log_msu "**** TIMESTAMP ****"
install_macos_msu_start_error="FALSE"
install_macos_msu_start_timeout="FALSE"
install_macos_msu_timeout_error="FALSE"
break
elif [[ $install_macos_msu_complete_percent -gt $install_macos_msu_complete_percent_previous ]]; then
install_macos_msu_complete_percent_display=$(((install_macos_msu_complete_percent - 60) * 2))
log_echo_replace_line "${macos_msu_title_downloaded} preparing progress: ${install_macos_msu_complete_percent_display}%"
install_macos_msu_complete_percent_previous=$install_macos_msu_complete_percent
fi
elif [[ $(echo "${log_line}" | grep -c 'Downloaded') -gt 0 ]]; then
macos_msu_title_downloaded=$(echo "${log_line}" | sed -e 's/://' -e 's/Downloaded //')
log_msu "**** TIMESTAMP ****"
install_macos_msu_start_error="FALSE"
install_macos_msu_start_timeout="FALSE"
install_macos_msu_timeout_error="FALSE"
break
fi
done < <(tail -n1 -F "${MSU_WORKFLOW_LOG}" | tr -u '%' '\n')
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: install_macos_msu_start_error is: ${install_macos_msu_start_error}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: install_macos_msu_start_timeout is: ${install_macos_msu_start_timeout}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: install_macos_msu_timeout_error is: ${install_macos_msu_timeout_error}"
# If the softwareupdate installation workflow completed, then prepare for restart.
if [[ "${install_macos_msu_start_error}" == "FALSE" ]] && [[ "${install_macos_msu_start_timeout}" == "FALSE" ]] && [[ "${install_macos_msu_timeout_error}" == "FALSE" ]]; then
/usr/libexec/PlistBuddy -c "Add :WorkflowRestartValidate bool true" "${SUPER_LOCAL_PLIST}.plist" 2> /dev/null
[[ "${workflow_scheduled_install_active}" == "TRUE" ]] && /usr/libexec/PlistBuddy -c "Delete :WorkflowScheduledInstall" "${SUPER_LOCAL_PLIST}.plist" 2> /dev/null
log_msu "**** S.U.P.E.R.M.A.N. ${SUPER_VERSION} - UPDATE/UPGRADE MACOS VIA SOFTWAREUPDATE COMPLETED ****"
log_super "softwareupdate: macOS update/upgrade is prepared and ready for restart!"
else # Some part of the softwareupdate installation workflow failed.
if [[ "${install_macos_msu_start_error}" == "TRUE" ]]; then
log_msu "Error: The softwareupdate process was unable to reach macOS software update servers or find the requested macOS update/upgrade."
log_super "Error: The softwareupdate process was unable to reach macOS software update servers or find the requested macOS update/upgrade."
elif [[ "${install_macos_msu_start_timeout}" == "TRUE" ]]; then
log_msu "Error: Installation of macOS update/upgrade via softwareupdate failed to start downloading/preparing after waiting for ${install_macos_msu_timeout_seconds} seconds."
log_super "Error: Installation of macOS update/upgrade via softwareupdate failed to start downloading/preparing after waiting for ${install_macos_msu_timeout_seconds} seconds."
else # ${install_macos_msu_timeout_error}" == "TRUE"
log_msu "Error: Installation of macOS update/upgrade via softwareupdate failed while downloading/preparing, as indicated by no progress after waiting for ${install_macos_msu_timeout_seconds} seconds."
log_super "Error: Installation of macOS update/upgrade via softwareupdate failed while downloading/preparing, as indicated by no progress after waiting for ${install_macos_msu_timeout_seconds} seconds."
fi
log_msu "**** S.U.P.E.R.M.A.N. ${SUPER_VERSION} - UPDATE/UPGRADE MACOS VIA SOFTWAREUPDATE FAILED ****"
kill -9 "${install_macos_msu_pid}" >/dev/null 2>&1
kick_softwareupdated
# Handle workflow failure options.
if [[ "${workflow_install_now_active}" == "TRUE" ]]; then # Install now workflow mode.
log_msu "Error: Installation of macOS update/upgrade via softwareupdate failed, install now workflow can not continue."
log_super "Error: Installation of macOS update/upgrade via softwareupdate failed, install now workflow can not continue."
log_status "Inactive Error: Installation of macOS update/upgrade via softwareupdate failed, install now workflow can not continue."
[[ "${current_user_account_name}" != "FALSE" ]] && notification_install_now_failed
exit_error
else # Default super workflow.
deferral_timer_minutes="${deferral_timer_error_minutes}"
log_msu "Error: Installation of macOS update/upgrade via softwareupdate failed, trying again in ${deferral_timer_minutes} minutes."
log_super "Error: Installation of macOS update/upgrade via softwareupdate failed, trying again in ${deferral_timer_minutes} minutes."
log_status "Pending: Installation of macOS update/upgrade via softwareupdate failed, trying again in ${deferral_timer_minutes} minutes."
[[ "${current_user_account_name}" != "FALSE" ]] && notification_failed
set_auto_launch_deferral
fi
fi
}
# Install macOS major upgrade via macOS installer application, and also save responses to ${SUPER_LOG}, ${INSTALLER_WORKFLOW_LOG}, and ${SUPER_LOCAL_PLIST}.
install_macos_app() {
# If ${test_mode_option} then it's not necessary to continue this function.
if [[ "${test_mode_option}" == "TRUE" ]]; then
[[ "${workflow_scheduled_install_active}" == "TRUE" ]] && defaults delete "${SUPER_LOCAL_PLIST}" WorkflowScheduledInstall 2>/dev/null
log_super "Test Mode: Skipping the macOS major upgrade via insaller workflow."
if [[ "${current_user_account_name}" != "FALSE" ]]; then
log_super "Test Mode: Pausing ${test_mode_timeout_seconds} seconds for the macOS major upgrade via insaller preparation notification..."
sleep "${test_mode_timeout_seconds}"
notification_restart
log_super "Test Mode: Pausing ${test_mode_timeout_seconds} seconds for the restart notification..."
sleep "${test_mode_timeout_seconds}"
killall -9 "IBM Notifier" "IBM Notifier Popup" >/dev/null 2>&1
fi
# Reset various items after test macOS update is complete.
defaults delete "${SUPER_LOCAL_PLIST}" WorkflowDownloadMacOSAuthRequired 2>/dev/null
reset_schedule_zero_date
reset_deadline_counters
return 0
fi
# Start with log and status updates.
log_super "startosinstall: Starting ${macos_installer_title} ${macos_installer_version}-${macos_installer_build} install upgrade workflow, check ${INSTALLER_WORKFLOW_LOG} for more detail."
log_status "Running: Starting ${macos_installer_title} ${macos_installer_version}-${macos_installer_build} install upgrade workflow."
log_installer "**** S.U.P.E.R.M.A.N. ${SUPER_VERSION} - INSTALL ${macos_installer_title} ${macos_installer_version}-${macos_installer_build} START ****"
# Background the startosinstall process and send to ${INSTALLER_WORKFLOW_LOG}.
if [[ "${mac_cpu_architecture}" == "arm64" ]]; then # Apple Silicon.
"/Applications/Install ${macos_installer_title}.app/Contents/Resources/startosinstall" --agreetolicense --forcequitapps --user "${auth_local_account}" --stdinpass <<<"${auth_local_password}" >>"${INSTALLER_WORKFLOW_LOG}" 2>&1 &
else # Intel.
"/Applications/Install ${macos_installer_title}.app/Contents/Resources/startosinstall" --agreetolicense --forcequitapps >>"${INSTALLER_WORKFLOW_LOG}" 2>&1 &
fi
local install_macos_app_pid
install_macos_app_pid="$!"
# Watch ${INSTALLER_WORKFLOW_LOG} while waiting for the startosinstall process to complete.
# Note this while read loop has a timeout based on ${TIMEOUT_START_SECONDS} then changes to ${TIMEOUT_INSTALLER_WORKFLOW_SECONDS}.
local install_macos_app_start_error
install_macos_app_start_error="TRUE"
local install_macos_app_start_timeout
install_macos_app_start_timeout="TRUE"
local install_macos_app_timeout
install_macos_app_timeout="TRUE"
local install_macos_app_timeout_seconds
install_macos_app_timeout_seconds="${TIMEOUT_START_SECONDS}"
local install_macos_app_phase
install_macos_app_phase="START"
local install_macos_app_complete_percent
install_macos_app_complete_percent=0
local install_macos_app_complete_percent_previous
install_macos_app_complete_percent_previous=0
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: install_macos_app_timeout_seconds is: ${install_macos_app_timeout_seconds}"
while read -t "${install_macos_app_timeout_seconds}" -r log_line; do
# log_super "Debug Mode: Function ${FUNCNAME[0]}: log_line is:\n${log_line}"
if [[ $(echo "${log_line}" | grep -c 'Preparing to run') -gt 0 ]]; then
log_super "startosinstall: ${macos_installer_title} ${macos_installer_version}-${macos_installer_build} preparing installation..."
log_installer "**** TIMESTAMP ****"
install_macos_app_phase="PREPARING"
install_macos_app_timeout_seconds="${TIMEOUT_INSTALLER_WORKFLOW_SECONDS}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: install_macos_app_timeout_seconds is: ${install_macos_app_timeout_seconds}"
install_macos_app_start_error="FALSE"
elif [[ $(echo "${log_line}" | grep -c 'Preparing:') -gt 0 ]] && [[ "${install_macos_app_phase}" == "PREPARING" ]]; then
install_macos_app_start_timeout="FALSE"
install_macos_app_complete_percent=$(echo "${log_line}" | sed -e 's/Preparing: //' -e 's/\.[0-9]//' | tr -d '\n' | tr -d '\r')
install_macos_app_complete_percent=${install_macos_app_complete_percent#0}
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: install_macos_app_complete_percent is: ${install_macos_app_complete_percent}"
if [[ $install_macos_app_complete_percent -ge 99 ]]; then
log_installer "**** TIMESTAMP ****"
log_echo_replace_line "${macos_installer_title} ${macos_installer_version}-${macos_installer_build} installation preparing progress: 100%\n"
install_macos_app_timeout="FALSE"
break
elif [[ $install_macos_app_complete_percent -gt $install_macos_app_complete_percent_previous ]]; then
log_echo_replace_line "${macos_installer_title} ${macos_installer_version}-${macos_installer_build} installation preparing progress: ${install_macos_app_complete_percent}%"
install_macos_app_complete_percent_previous=$install_macos_app_complete_percent
fi
elif [[ $(echo "${log_line}" | grep -c -e 'Preparing: 99' -e 'Preparing: 100' -e 'Restarting') -gt 0 ]]; then
log_installer "**** TIMESTAMP ****"
log_echo_replace_line "${macos_installer_title} ${macos_installer_version}-${macos_installer_build} installation preparing progress: 100%\n"
install_macos_app_start_error="FALSE"
install_macos_app_start_timeout="FALSE"
install_macos_app_timeout="FALSE"
break
fi
done < <(tail -n1 -F "${INSTALLER_WORKFLOW_LOG}" | tr -u '%' '\n')
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: install_macos_app_start_error is: ${install_macos_app_start_error}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: install_macos_app_start_timeout is: ${install_macos_app_start_timeout}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: install_macos_app_timeout is: ${install_macos_app_timeout}"
# If the startosinstall workflow completed, then prepare for restart.
if [[ "${install_macos_app_start_error}" == "FALSE" ]] && [[ "${install_macos_app_start_timeout}" == "FALSE" ]] && [[ "${install_macos_app_timeout}" == "FALSE" ]]; then
/usr/libexec/PlistBuddy -c "Add :WorkflowRestartValidate bool true" "${SUPER_LOCAL_PLIST}.plist" 2> /dev/null
[[ "${workflow_scheduled_install_active}" == "TRUE" ]] && /usr/libexec/PlistBuddy -c "Delete :WorkflowScheduledInstall" "${SUPER_LOCAL_PLIST}.plist" 2> /dev/null
log_installer "**** S.U.P.E.R.M.A.N. ${SUPER_VERSION} - INSTALL MACOS COMPLETED ****"
log_super "Status: ${macos_installer_title} ${macos_installer_version}-${macos_installer_build} is prepared and ready for restart!"
[[ "${current_user_account_name}" != "FALSE" ]] && notification_restart
else # Some part of the startosinstall workflow failed.
log_installer "**** S.U.P.E.R.M.A.N. ${SUPER_VERSION} - INSTALL MACOS FAILED ****"
kill -9 "${install_macos_app_pid}" >/dev/null 2>&1
if [[ "${install_macos_app_start_error}" == "TRUE" ]]; then
log_installer "Error: Installation of macOS major upgrade via installer application failed to start."
log_super "Error: Installation of macOS major upgrade via installer application failed to start."
elif [[ "${install_macos_app_start_timeout}" == "TRUE" ]]; then
log_installer "Error: Installation of macOS major upgrade via installer application failed to start preparing after waiting for ${install_macos_app_timeout_seconds} seconds."
log_super "Error: Installation of macOS major upgrade via installer application failed to start preparing after waiting for ${install_macos_app_timeout_seconds} seconds."
else # "${install_macos_app_timeout}" == "TRUE"
log_installer "Error: Installation of macOS major upgrade via installer application failed to prepare, as indicated by no progress after waiting for ${install_macos_app_timeout_seconds} seconds."
log_super "Error: Installation of macOS major upgrade via installer application failed to prepare, as indicated by no progress after waiting for ${install_macos_app_timeout_seconds} seconds."
fi
# Handle workflow failure options.
if [[ "${workflow_install_now_active}" == "TRUE" ]]; then # Install now workflow mode.
log_installer "Error: Installation of macOS major upgrade via installer application failed, install now workflow can not continue."
log_super "Error: Installation of macOS major upgrade via installer application failed, install now workflow can not continue."
log_status "Inactive Error: Installation of macOS major upgrade via installer application failed, install now workflow can not continue."
[[ "${current_user_account_name}" != "FALSE" ]] && notification_install_now_failed
exit_error
else # Default super workflow.
deferral_timer_minutes="${deferral_timer_error_minutes}"
log_installer "Error: Installation of macOS major upgrade via installer application failed, trying again in ${deferral_timer_minutes} minutes."
log_super "Error: Installation of macOS major upgrade via installer application failed, trying again in ${deferral_timer_minutes} minutes."
log_status "Pending: Installation of macOS major upgrade via installer application failed, trying again in ${deferral_timer_minutes} minutes."
[[ "${current_user_account_name}" != "FALSE" ]] && notification_failed
set_auto_launch_deferral
fi
fi
}
# Download and/or install macOS update/upgrade via MDM push command, and also save responses to ${SUPER_LOG}, ${MDM_COMMAND_LOG}, ${MDM_WORKFLOW_LOG}, and ${SUPER_LOCAL_PLIST}.
push_macos_mdm() {
# If ${test_mode_option} then it's not necessary to continue this function.
if [[ "${test_mode_option}" == "TRUE" ]] && [[ "${push_macos_mdm_workflow}" == "DOWNLOAD" ]]; then
log_super "Test Mode: Skipping the download macOS update/upgrade via MDM workflow."
if [[ "${workflow_install_now_active}" == "TRUE" ]]; then
log_super "Test Mode: Pausing ${test_mode_timeout_seconds} seconds for install now download notification..."
sleep "${test_mode_timeout_seconds}"
fi
push_macos_mdm_download_error="FALSE"
return 0
elif [[ "${test_mode_option}" == "TRUE" ]]; then # [[ "${push_macos_mdm_workflow}" == "INSTALL" ]]
[[ "${workflow_scheduled_install_active}" == "TRUE" ]] && defaults delete "${SUPER_LOCAL_PLIST}" WorkflowScheduledInstall 2>/dev/null
log_super "Test Mode: Skipping the install macOS update/upgrade via MDM workflow."
if [[ "${current_user_account_name}" != "FALSE" ]]; then
log_super "Test Mode: Pausing ${test_mode_timeout_seconds} seconds for the MDM preparation notification..."
sleep "${test_mode_timeout_seconds}"
notification_restart
log_super "Test Mode: Pausing ${test_mode_timeout_seconds} seconds for the restart notification..."
sleep "${test_mode_timeout_seconds}"
killall -9 "IBM Notifier" "IBM Notifier Popup" >/dev/null 2>&1
fi
# Reset various items after test macOS update is complete.
defaults delete "${SUPER_LOCAL_PLIST}" WorkflowDownloadMacOSAuthRequired 2>/dev/null
reset_schedule_zero_date
reset_deadline_counters
return 0
fi
# Start with log and status updates.
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: push_macos_mdm_workflow is: ${push_macos_mdm_workflow}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_msu_download_required is: ${macos_msu_download_required}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_mdm_failover_to_user_status is: ${auth_mdm_failover_to_user_status}"
local push_macos_mdm_target_type
local push_macos_mdm_target_version
if [[ "${macos_installer_target}" != "FALSE" ]]; then # Target is a macOS major upgrade via installer.
push_macos_mdm_target_type="INSTALLER"
push_macos_mdm_target_version="${macos_installer_version}"
if [[ "${auth_mdm_failover_to_user_status}" == "TRUE" ]]; then
log_super "MDM: Starting ${macos_installer_title} ${macos_installer_version}-${macos_installer_build} install workflow with user authenticated failover."
log_status "Running: MDM: Starting ${macos_installer_title} ${macos_installer_version}-${macos_installer_build} install workflow with user authenticated failover."
else
log_super "MDM: Starting ${macos_installer_title} ${macos_installer_version}-${macos_installer_build} install workflow."
log_status "Running: MDM: Starting ${macos_installer_title} ${macos_installer_version}-${macos_installer_build} install workflow."
fi
log_mdm_command "**** S.U.P.E.R.M.A.N. ${SUPER_VERSION} - INSTALL ${macos_installer_title} ${macos_installer_version}-${macos_installer_build} VIA MDM START ****"
log_mdm_workflow "**** S.U.P.E.R.M.A.N. ${SUPER_VERSION} - INSTALL ${macos_installer_title} ${macos_installer_version}-${macos_installer_build} VIA MDM START ****"
fi
if [[ "${macos_msu_major_upgrade_target}" != "FALSE" ]] || [[ "${macos_msu_minor_update_target}" != "FALSE" ]]; then # Target is a macOS update/upgrade via MSU.
[[ "${macos_msu_major_upgrade_target}" != "FALSE" ]] && push_macos_mdm_target_type="UPGRADE"
[[ "${macos_msu_minor_update_target}" != "FALSE" ]] && push_macos_mdm_target_type="UPDATE"
push_macos_mdm_target_version="${macos_msu_version}"
if [[ "${push_macos_mdm_workflow}" == "DOWNLOAD" ]]; then
if [[ "${auth_mdm_failover_to_user_status}" == "TRUE" ]]; then
log_super "MDM: Starting macOS ${macos_msu_version} download workflow with user authenticated failover."
log_status "Running: MDM: Starting macOS ${macos_msu_version} download workflow with user authenticated failover."
else
log_super "MDM: Starting macOS ${macos_msu_version} download workflow."
log_status "Running: MDM: Starting macOS ${macos_msu_version} download workflow."
fi
log_mdm_command "**** S.U.P.E.R.M.A.N. ${SUPER_VERSION} - DOWNLOAD MACOS ${macos_msu_version} VIA MDM START ****"
log_mdm_workflow "**** S.U.P.E.R.M.A.N. ${SUPER_VERSION} - DOWNLOAD MACOS ${macos_msu_version} VIA MDM START ****"
elif [[ "${macos_msu_download_required}" == "TRUE" ]]; then
if [[ "${auth_mdm_failover_to_user_status}" == "TRUE" ]]; then
log_super "MDM: Starting macOS ${macos_msu_version} download and update/upgrade workflow with user authenticated failover."
log_status "Running: MDM: Starting macOS ${macos_msu_version} download and update/upgrade workflow with user authenticated failover."
else
log_super "MDM: Starting macOS ${macos_msu_version} download and update/upgrade workflow."
log_status "Running: MDM: Starting macOS ${macos_msu_version} download and update/upgrade workflow."
fi
log_mdm_command "**** S.U.P.E.R.M.A.N. ${SUPER_VERSION} - DOWNLOAD AND UPDATE/UPGRADE MACOS ${macos_msu_version} VIA MDM START ****"
log_mdm_workflow "**** S.U.P.E.R.M.A.N. ${SUPER_VERSION} - DOWNLOAD AND UPDATE/UPGRADE MACOS ${macos_msu_version} VIA MDM START ****"
else # Install workflow.
if [[ "${auth_mdm_failover_to_user_status}" == "TRUE" ]]; then
log_super "MDM: Starting macOS ${macos_msu_version} update/upgrade workflow with user authenticated failover."
log_status "Running: MDM: Starting macOS ${macos_msu_version} update/upgrade workflow with user authenticated failover."
else
log_super "MDM: Starting macOS ${macos_msu_version} update/upgrade workflow."
log_status "Running: MDM: Starting macOS ${macos_msu_version} update/upgrade workflow."
fi
log_mdm_command "**** S.U.P.E.R.M.A.N. ${SUPER_VERSION} - UPDATE/UPGRADE MACOS ${macos_msu_version} VIA MDM START ****"
log_mdm_workflow "**** S.U.P.E.R.M.A.N. ${SUPER_VERSION} - UPDATE/UPGRADE MACOS ${macos_msu_version} VIA MDM START ****"
fi
fi
log_super "MDM: check ${MDM_COMMAND_LOG} and ${MDM_WORKFLOW_LOG} for more detail."
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: push_macos_mdm_target_type is: ${push_macos_mdm_target_type}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: push_macos_mdm_target_version is: ${push_macos_mdm_target_version}"
# Validate Jamf Pro API token and update workflow.
check_jamf_api_access_token
check_jamf_api_update_workflow
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_error_jamf is: ${auth_error_jamf}"
# Only continue workflow if Jamf Pro API configuration is valid.
if [[ "${auth_error_jamf}" == "FALSE" ]]; then
# Start log streaming for MDM push commands and send to ${MDM_COMMAND_LOG}.
local log_stream_mdm_command_pid
log stream --style compact --predicate 'subsystem == "com.apple.ManagedClient" AND category == "HTTPUtil"' >> "${MDM_COMMAND_LOG}" &
log_stream_mdm_command_pid="$!"
if [[ "${verbose_mode_option}" == "TRUE" ]]; then
log_super "Verbose Mode: Starting debug log for MDM client command progress at: ${MDM_COMMAND_DEBUG_LOG}"
local log_stream_mdm_command_debug_pid
log stream --style compact --predicate 'subsystem == "com.apple.ManagedClient"' >>"${MDM_COMMAND_DEBUG_LOG}" &
log_stream_mdm_command_debug_pid="$!"
fi
# Start log streaming for MDM update/upgrade progress and send to ${MDM_WORKFLOW_LOG}.
local log_stream_mdm_workflow_pid
log stream --style compact --predicate 'process == "softwareupdated" AND composedMessage CONTAINS "Reported progress"' >>"${MDM_WORKFLOW_LOG}" &
log_stream_mdm_workflow_pid="$!"
if [[ "${verbose_mode_option}" == "TRUE" ]]; then
log_super "Verbose Mode: Starting debug log for MDM update/upgrade workflow progress at: ${MDM_WORKFLOW_DEBUG_LOG}"
local log_stream_mdm_workflow_debug_pid
log stream --style compact --predicate 'process == "softwareupdated"' >>"${MDM_WORKFLOW_DEBUG_LOG}" &
log_stream_mdm_workflow_debug_pid="$!"
fi
# Send the Jamf Pro API command to update/upgrade and restart via MDM.
local push_macos_mdm_api_error
push_macos_mdm_api_error="FALSE"
local jamf_api_update_url
local jamf_api_update_json
if [[ "${jamf_api_update_workflow}" == "NEW" ]]; then # Jamf Pro API new managed update workflow.
jamf_api_update_url="${jamf_api_url}api/v1/managed-software-updates/plans"
if { [[ "${push_macos_mdm_target_type}" == "INSTALLER" ]] || [[ "${push_macos_mdm_target_type}" == "UPGRADE" ]]; } && { [[ "${macos_beta_program}" == "TRUE" ]] || [[ "${macos_major_upgrade_latest}" == "TRUE" ]]; }; then
[[ "${push_macos_mdm_workflow}" == "DOWNLOAD" ]] && jamf_api_update_json='{ "devices": [ { "objectType": "COMPUTER", "deviceId": "'${jamf_computer_id}'" } ], "config": { "updateAction": "DOWNLOAD_ONLY", "versionType": "LATEST_MAJOR" } }'
[[ "${push_macos_mdm_workflow}" == "INSTALL" ]] && jamf_api_update_json='{ "devices": [ { "objectType": "COMPUTER", "deviceId": "'${jamf_computer_id}'" } ], "config": { "updateAction": "DOWNLOAD_INSTALL_RESTART", "versionType": "LATEST_MAJOR" } }'
elif [[ "${macos_beta_program}" == "TRUE" ]] || [[ "${macos_minor_update_latest}" == "TRUE" ]]; then
[[ "${push_macos_mdm_workflow}" == "DOWNLOAD" ]] && jamf_api_update_json='{ "devices": [ { "objectType": "COMPUTER", "deviceId": "'${jamf_computer_id}'" } ], "config": { "updateAction": "DOWNLOAD_ONLY", "versionType": "LATEST_MINOR" } }'
[[ "${push_macos_mdm_workflow}" == "INSTALL" ]] && jamf_api_update_json='{ "devices": [ { "objectType": "COMPUTER", "deviceId": "'${jamf_computer_id}'" } ], "config": { "updateAction": "DOWNLOAD_INSTALL_RESTART", "versionType": "LATEST_MINOR" } }'
else # Non-latest macOS version targets must be called by their specific version number.
log_super "Warning: Workflow target is not the latest macOS minor update or major upgrade. The Jamf Pro new Managed Software Updates API is unreliable when requesting specific older macOS versions."
[[ "${push_macos_mdm_workflow}" == "DOWNLOAD" ]] && jamf_api_update_json='{ "devices": [ { "objectType": "COMPUTER", "deviceId": "'${jamf_computer_id}'" } ], "config": { "updateAction": "DOWNLOAD_ONLY", "versionType": "SPECIFIC_VERSION", "specificVersion": "'${push_macos_mdm_target_version}'" } }'
[[ "${push_macos_mdm_workflow}" == "INSTALL" ]] && jamf_api_update_json='{ "devices": [ { "objectType": "COMPUTER", "deviceId": "'${jamf_computer_id}'" } ], "config": { "updateAction": "DOWNLOAD_INSTALL_RESTART", "versionType": "SPECIFIC_VERSION", "specificVersion": "'${push_macos_mdm_target_version}'" } }'
fi
else # Jamf Pro API legacy managed update workflow.
log_super "Warning: Workflow is using the Jamf Pro legacy macOS Managed Software Updates API. Although this API remains stable, it is now deprecated."
jamf_api_update_url="${jamf_api_url}api/v1/macos-managed-software-updates/send-updates"
if [[ "${push_macos_mdm_target_type}" == "INSTALLER" ]] || [[ "${push_macos_mdm_target_type}" == "UPGRADE" ]]; then # macOS major upgrade.
[[ "${push_macos_mdm_workflow}" == "DOWNLOAD" ]] && jamf_api_update_json='{ "deviceIds": ["'${jamf_computer_id}'"], "version": "'${push_macos_mdm_target_version}'", "skipVersionVerification": true, "updateAction": "DOWNLOAD_ONLY" }'
[[ "${push_macos_mdm_workflow}" == "INSTALL" ]] && jamf_api_update_json='{ "deviceIds": ["'${jamf_computer_id}'"], "version": "'${push_macos_mdm_target_version}'", "skipVersionVerification": true, "updateAction": "DOWNLOAD_AND_INSTALL" }'
else # macOS minor update.
[[ "${push_macos_mdm_workflow}" == "DOWNLOAD" ]] && jamf_api_update_json='{ "deviceIds": ["'${jamf_computer_id}'"], "version": "'${push_macos_mdm_target_version}'", "skipVersionVerification": true, "updateAction": "DOWNLOAD_ONLY" }'
[[ "${push_macos_mdm_workflow}" == "INSTALL" ]] && jamf_api_update_json='{ "deviceIds": ["'${jamf_computer_id}'"], "version": "'${push_macos_mdm_target_version}'", "skipVersionVerification": true, "updateAction": "DOWNLOAD_AND_INSTALL", "forceRestart": true }'
fi
fi
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: jamf_api_update_workflow is: ${jamf_api_update_workflow}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: jamf_api_update_url is: ${jamf_api_update_url}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: jamf_api_update_json is: ${jamf_api_update_json}"
local curl_response
curl_response=$(curl --silent --output /dev/null --write-out "%{http_code}" --location --request POST "${jamf_api_update_url}" --header "Authorization: Bearer ${jamf_access_token}" --header 'accept: application/json' --header 'content-type: application/json' --data "${jamf_api_update_json}")
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: curl_response is:\n${curl_response}"
# If the Jamf Pro API managed update was not successful then it's not necessary to continue this function.
if [[ $(echo "${curl_response}" | grep -c '200') -gt 0 ]] || [[ $(echo "${curl_response}" | grep -c '201') -gt 0 ]]; then
[[ "${push_macos_mdm_workflow}" == "DOWNLOAD" ]] && log_super "MDM: Successful download macOS update/upgrade command request."
[[ "${push_macos_mdm_workflow}" == "INSTALL" ]] && log_super "MDM: Successful install macOS update/upgrade command request."
send_jamf_api_blank_push
else
push_macos_mdm_api_error="TRUE"
fi
fi
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: push_macos_mdm_api_error is: ${push_macos_mdm_api_error}"
# Only continue workflow if Jamf Pro API managed update command was successful.
if [[ "${auth_error_jamf}" == "FALSE" ]] && [[ "${push_macos_mdm_api_error}" == "FALSE" ]]; then
# Some helpfull logging while waiting for Jamf Pro's mandatory 5 minute delay. Note this while read loop has a timeout based on ${TIMEOUT_MDM_COMMAND_SECONDS}.
local push_macos_mdm_start_error
push_macos_mdm_start_error="TRUE"
while read -t "${TIMEOUT_MDM_COMMAND_SECONDS}" -r log_line; do
# log_super "Debug Mode: Function ${FUNCNAME[0]}: log_line is:\n${log_line}"
if [[ $(echo "${log_line}" | grep -c 'Received HTTP response (200) \[Error') -gt 0 ]]; then
log_super "MDM: Workflow error detected."
log_mdm_command "**** TIMESTAMP ****"
break
elif [[ $(echo "${log_line}" | grep -c 'Received HTTP response (200) \[Acknowledged(ScheduleOSUpdateScan)') -gt 0 ]]; then
log_super "MDM: Received push command \"ScheduleOSUpdateScan\", checking back after Jamf Pro's mandatory 5 minute delay..."
log_mdm_command "**** TIMESTAMP ****"
push_macos_mdm_start_error="FALSE"
pkill -P $$ tail
break
fi
done < <(tail -n1 -F "${MDM_COMMAND_LOG}")
fi
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: push_macos_mdm_start_error is: ${push_macos_mdm_start_error}"
# Only continue workflow if the initial managed update commands were received.
if [[ "${auth_error_jamf}" == "FALSE" ]] && [[ "${push_macos_mdm_api_error}" == "FALSE" ]] && [[ "${push_macos_mdm_start_error}" == "FALSE" ]]; then
local push_macos_mdm_start_timeout
push_macos_mdm_start_timeout="TRUE"
local timer_end
timer_end=300
while [[ "${timer_end}" -ge 0 ]]; do
log_echo_replace_line "Waiting for Jamf Pro's mandatory 5 minute delay: -$(date -u -r ${timer_end} +%M:%S)"
timer_end=$((timer_end - 1))
sleep 1
done
log_echo_replace_line "Waiting for Jamf Pro's mandatory 5 minute delay: 00:00\n)"
log_super "MDM: Jamf Pro's mandatory 5 minute delay should be complete, sending Blank Push..."
send_jamf_api_blank_push
if [[ "${current_user_account_name}" != "FALSE" ]] && [[ "${push_macos_mdm_workflow}" == "INSTALL" ]]; then
if [[ "${push_macos_mdm_target_type}" == "INSTALLER" ]]; then
display_string_prepare_time_estimate="20-25"
notification_prepare
else
notification_restart
fi
fi
# Watch ${MDM_COMMAND_LOG} while waiting for the MDM workflow to complete. Note this while read loop has a timeout based on ${TIMEOUT_MDM_COMMAND_SECONDS}.
while read -t "${TIMEOUT_MDM_COMMAND_SECONDS}" -r log_line; do
# log_super "Debug Mode: Function ${FUNCNAME[0]}: log_line is:\n${log_line}"
if [[ $(echo "${log_line}" | grep -c 'Received HTTP response (200) \[Error') -gt 0 ]]; then
if [[ $(echo "${log_line}" | grep -c 'DeclarativeManagement') -gt 0 ]] && [[ "${macos_version_major}" -lt 14 ]]; then
log_super "MDM: DDM error detected but it should be ignored by older versions of macOS."
else
log_super "MDM: Command error detected."
log_mdm_command "**** TIMESTAMP ****"
push_macos_mdm_start_error="TRUE"
break
fi
elif [[ $(echo "${log_line}" | grep -c 'Received HTTP response (200) \[Idle\]') -gt 0 ]]; then
log_super "MDM: Received blank push."
log_mdm_command "**** TIMESTAMP ****"
elif [[ $(echo "${log_line}" | grep -c 'Received HTTP response (200) \[Acknowledged(AvailableOSUpdates)') -gt 0 ]]; then
log_super "MDM: Received push command \"AvailableOSUpdates\"."
log_mdm_command "**** TIMESTAMP ****"
elif [[ $(echo "${log_line}" | grep -c 'Received HTTP response (200) \[Acknowledged(ScheduleOSUpdate)') -gt 0 ]]; then
kill -9 "${log_stream_mdm_command_pid}" >/dev/null 2>&1
[[ "${verbose_mode_option}" == "TRUE" ]] && kill -9 "${log_stream_mdm_command_debug_pid}" >/dev/null 2>&1
[[ "${push_macos_mdm_workflow}" == "DOWNLOAD" ]] && log_mdm_command "**** S.U.P.E.R.M.A.N. ${SUPER_VERSION} - DOWNLOAD MACOS VIA MDM COMMAND COMPLETED ****"
[[ "${push_macos_mdm_workflow}" == "DOWNLOAD" ]] && log_super "MDM: Received push command \"ScheduleOSUpdate\", local download should start soon..."
[[ "${push_macos_mdm_workflow}" == "INSTALL" ]] && log_mdm_command "**** S.U.P.E.R.M.A.N. ${SUPER_VERSION} - INSTALL MACOS VIA MDM COMMAND COMPLETED ****"
[[ "${push_macos_mdm_workflow}" == "INSTALL" ]] && log_super "MDM: Received push command \"ScheduleOSUpdate\", local update/upgrade should start soon..."
push_macos_mdm_start_timeout="FALSE"
break
fi
done < <(tail -n1 -F "${MDM_COMMAND_LOG}")
fi
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: push_macos_mdm_start_error is: ${push_macos_mdm_start_error}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: push_macos_mdm_start_timeout is: ${push_macos_mdm_start_timeout}"
# Only continue workflow if the the final (required) 'ScheduleOSUpdate' MDM command was received.
if [[ "${auth_error_jamf}" == "FALSE" ]] && [[ "${push_macos_mdm_api_error}" == "FALSE" ]] && [[ "${push_macos_mdm_start_error}" == "FALSE" ]] && [[ "${push_macos_mdm_start_timeout}" == "FALSE" ]]; then
local push_macos_mdm_timeout_error
push_macos_mdm_timeout_error="TRUE"
local push_macos_mdm_timeout_seconds
push_macos_mdm_timeout_seconds="${TIMEOUT_MDM_COMMAND_SECONDS}"
local push_macos_mdm_phase
push_macos_mdm_phase="START"
local push_macos_mdm_complete_percent
push_macos_mdm_complete_percent=0
local push_macos_mdm_complete_percent_previous
push_macos_mdm_complete_percent_previous=0
local push_macos_mdm_complete_percent_display
# Watch ${MDM_WORKFLOW_LOG} while waiting for the update/upgrade workflow to complete.
# Note this while read loop has a timeout based on ${TIMEOUT_MDM_COMMAND_SECONDS} then may change to ${TIMEOUT_MDM_WORKFLOW_SECONDS}.
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: push_macos_mdm_target_type is: ${push_macos_mdm_target_type}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: push_macos_mdm_timeout_seconds is: ${push_macos_mdm_timeout_seconds}"
if [[ "${push_macos_mdm_target_type}" == "UPGRADE" ]] || [[ "${push_macos_mdm_target_type}" == "UPDATE" ]]; then
while read -t "${push_macos_mdm_timeout_seconds}" -r log_line; do
# log_super "Debug Mode: Function ${FUNCNAME[0]}: log_line is:\n${log_line}"
if [[ $(echo "${log_line}" | grep -c 'phase:PREFLIGHT') -gt 0 ]]; then
if [[ "${push_macos_mdm_phase}" != "PREFLIGHT" ]] && [[ "${push_macos_mdm_phase}" != "DOWNLOADING" ]] && [[ "${push_macos_mdm_phase}" != "PREPARING" ]]; then
log_super "MDM: ${macos_msu_label} preflight..."
log_mdm_workflow "**** TIMESTAMP ****"
push_macos_mdm_timeout_seconds="${TIMEOUT_MDM_WORKFLOW_SECONDS}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: push_macos_mdm_timeout_seconds is: ${push_macos_mdm_timeout_seconds}"
push_macos_mdm_phase="PREFLIGHT"
fi
elif [[ $(echo "${log_line}" | grep -c 'phase:DOWNLOADING_UPDATE') -gt 0 ]]; then
if [[ "${push_macos_mdm_phase}" != "DOWNLOADING" ]]; then
log_super "MDM: ${macos_msu_label} is downloading..."
log_mdm_workflow "**** TIMESTAMP ****"
push_macos_mdm_timeout_seconds="${TIMEOUT_MDM_WORKFLOW_SECONDS}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: push_macos_mdm_timeout_seconds is: ${push_macos_mdm_timeout_seconds}"
push_macos_mdm_phase="DOWNLOADING"
fi
push_macos_mdm_complete_percent=$(echo "${log_line}" | awk '{print $17;}' | sed -e 's/portionComplete:0.//' | cut -c 1-2)
push_macos_mdm_complete_percent=${push_macos_mdm_complete_percent#0}
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: push_macos_mdm_complete_percent is: ${push_macos_mdm_complete_percent}"
if [[ $push_macos_mdm_complete_percent -ge 60 ]]; then
log_echo_replace_line "${macos_msu_label} download progress: 100%\n"
elif [[ $push_macos_mdm_complete_percent -gt $push_macos_mdm_complete_percent_previous ]]; then
push_macos_mdm_complete_percent_display=$( (echo "$push_macos_mdm_complete_percent * 1.69" | bc) | cut -d '.' -f1)
log_echo_replace_line "${macos_msu_label} download progress: ${push_macos_mdm_complete_percent_display}%"
push_macos_mdm_complete_percent_previous=$push_macos_mdm_complete_percent
fi
elif [[ $(echo "${log_line}" | grep -c 'phase:PREPARING_UPDATE') -gt 0 ]]; then
if [[ "${push_macos_mdm_phase}" != "PREPARING" ]]; then
log_super "MDM: ${macos_msu_label} download complete, now preparing..."
log_mdm_workflow "**** TIMESTAMP ****"
push_macos_mdm_timeout_seconds=${TIMEOUT_MDM_WORKFLOW_SECONDS}
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: push_macos_mdm_timeout_seconds is: ${push_macos_mdm_timeout_seconds}"
push_macos_mdm_phase="PREPARING"
fi
push_macos_mdm_complete_percent=$(echo "${log_line}" | awk '{print $17;}' | sed -e 's/portionComplete:0.//' | cut -c 1-2)
push_macos_mdm_complete_percent=${push_macos_mdm_complete_percent#0}
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: push_macos_mdm_complete_percent is: ${push_macos_mdm_complete_percent}"
if [[ $push_macos_mdm_complete_percent -ge 98 ]]; then
log_echo_replace_line "${macos_msu_label} preparing progress: 100%\n"
[[ "${push_macos_mdm_workflow}" == "DOWNLOAD" ]] && log_super "MDM: ${macos_msu_label} is downloaded and prepared."
[[ "${push_macos_mdm_workflow}" == "INSTALL" ]] && log_super "MDM: ${macos_msu_label} is downloaded and prepared, system restart is soon..."
log_mdm_workflow "**** TIMESTAMP ****"
elif [[ $push_macos_mdm_complete_percent -gt $push_macos_mdm_complete_percent_previous ]]; then
push_macos_mdm_complete_percent_display=$(((push_macos_mdm_complete_percent - 60) * 2))
log_echo_replace_line "${macos_msu_label} preparing progress: ${push_macos_mdm_complete_percent_display}%"
push_macos_mdm_complete_percent_previous=$push_macos_mdm_complete_percent
fi
elif [[ $(echo "${log_line}" | grep -c 'phase:PREPARED_COMMITTING_STASH') -gt 0 ]]; then
log_mdm_workflow "**** TIMESTAMP ****"
push_macos_mdm_timeout_error="FALSE"
push_macos_mdm_phase="DONE"
break
fi
done < <(tail -n1 -F "${MDM_WORKFLOW_LOG}")
else # ${push_macos_mdm_target_type} == "INSTALLER"
# This while loop is broken into sections to allow for the notification_prepare function to update. Putting this function inside the while loop breaks the tail.
while read -t "${push_macos_mdm_timeout_seconds}" -r log_line; do
# log_super "Debug Mode: Function ${FUNCNAME[0]}: log_line is:\n${log_line}"
if [[ $(echo "${log_line}" | grep -c 'phase:PREFLIGHT') -gt 0 ]]; then
log_super "MDM: ${macos_installer_title} ${macos_installer_version}-${macos_installer_build} installer preflight..."
log_mdm_workflow "**** TIMESTAMP ****"
push_macos_mdm_timeout_seconds="${TIMEOUT_MDM_WORKFLOW_SECONDS}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: push_macos_mdm_timeout_seconds is: ${push_macos_mdm_timeout_seconds}"
push_macos_mdm_timeout_error="FALSE"
push_macos_mdm_phase="PREFLIGHT"
break
fi
done < <(tail -n1 -F "${MDM_WORKFLOW_LOG}")
if [[ "${push_macos_mdm_timeout_error}" == "FALSE" ]]; then
push_macos_mdm_timeout_error="TRUE"
while read -t "${push_macos_mdm_timeout_seconds}" -r log_line; do
# log_super "Debug Mode: Function ${FUNCNAME[0]}: log_line is:\n${log_line}"
if [[ $(echo "${log_line}" | grep -c 'phase:DOWNLOADING_SFR') -gt 0 ]]; then
log_super "MDM: ${macos_installer_title} ${macos_installer_version}-${macos_installer_build} downloading additional items..."
log_mdm_workflow "**** TIMESTAMP ****"
push_macos_mdm_phase="DOWNLOADING"
elif [[ $(echo "${log_line}" | grep -c 'phase:PREPARING_UPDATE') -gt 0 ]]; then
log_super "MDM: ${macos_installer_title} ${macos_installer_version}-${macos_installer_build} installer preparing..."
log_mdm_workflow "**** TIMESTAMP ****"
push_macos_mdm_timeout_error="FALSE"
push_macos_mdm_phase="PREPARING"
break
fi
done < <(tail -n1 -F "${MDM_WORKFLOW_LOG}")
fi
if [[ "${push_macos_mdm_timeout_error}" == "FALSE" ]] && [[ "${current_user_account_name}" != "FALSE" ]]; then
display_string_prepare_time_estimate="10-15"
notification_prepare
fi
if [[ "${push_macos_mdm_timeout_error}" == "FALSE" ]]; then
push_macos_mdm_timeout_error="TRUE"
while read -t "${push_macos_mdm_timeout_seconds}" -r log_line; do
# log_super "Debug Mode: Function ${FUNCNAME[0]}: log_line is:\n${log_line}"
if [[ $(echo "${log_line}" | grep -c 'phase:PREPARED') -gt 0 ]]; then
log_super "MDM: ${macos_installer_title} ${macos_installer_version}-${macos_installer_build} installer is prepared, system restart is soon..."
log_mdm_workflow "**** TIMESTAMP ****"
push_macos_mdm_timeout_error="FALSE"
push_macos_mdm_phase="DONE"
break
fi
done < <(tail -n1 -F "${MDM_WORKFLOW_LOG}")
fi
{ [[ "${push_macos_mdm_timeout_error}" == "FALSE" ]] && [[ "${current_user_account_name}" != "FALSE" ]]; } && notification_restart
if [[ "${push_macos_mdm_timeout_error}" == "FALSE" ]]; then
push_macos_mdm_timeout_error="TRUE"
while read -t "${push_macos_mdm_timeout_seconds}" -r log_line; do
# log_super "Debug Mode: Function ${FUNCNAME[0]}: log_line is:\n${log_line}"
if [[ $(echo "${log_line}" | grep -c 'phase:ACCEPTED') -gt 0 ]] || [[ $(echo "${log_line}" | grep -c 'phase:APPLYING') -gt 0 ]] || [[ $(echo "${log_line}" | grep -c 'phase:APPLYING') -gt 0 ]]; then
log_mdm_workflow "**** TIMESTAMP ****"
push_macos_mdm_timeout_error="FALSE"
push_macos_mdm_phase="DONE"
break
fi
done < <(tail -n1 -F "${MDM_WORKFLOW_LOG}")
fi
fi
fi
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: push_macos_mdm_timeout_error is: ${push_macos_mdm_timeout_error}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: push_macos_mdm_phase is: ${push_macos_mdm_phase}"
# If the macOS update/upgrade completed, then prepare for restart.
if [[ "${auth_error_jamf}" == "FALSE" ]] && [[ "${push_macos_mdm_api_error}" == "FALSE" ]] && [[ "${push_macos_mdm_start_error}" == "FALSE" ]] && [[ "${push_macos_mdm_start_timeout}" == "FALSE" ]] && [[ "${push_macos_mdm_timeout_error}" == "FALSE" ]]; then
kill -9 "${log_stream_mdm_workflow_pid}" >/dev/null 2>&1
[[ "${verbose_mode_option}" == "TRUE" ]] && kill -9 "${log_stream_mdm_workflow_debug_pid}" >/dev/null 2>&1
if [[ "${push_macos_mdm_workflow}" == "DOWNLOAD" ]]; then
local push_macos_mdm_download_validation_error
push_macos_mdm_download_validation_error="TRUE"
local update_asset_attributes
update_asset_attributes=$(defaults read /System/Volumes/Update/Update update-asset-attributes 2>/dev/null)
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: update_asset_attributes is:\n${update_asset_attributes}"
local macos_mdm_version_prepared
macos_mdm_version_prepared=$(echo "${update_asset_attributes}" | grep -w 'OSVersion' | awk -F '"' '{print $2;}')
if [[ "${macos_version_major}" -ge 13 ]]; then
local macos_mdm_prepared_version_extra
macos_mdm_prepared_version_extra=$(echo "${update_asset_attributes}" | grep -w 'ProductVersionExtra' | awk -F '"' '{print $2;}')
[[ -n "${macos_mdm_prepared_version_extra}" ]] && macos_mdm_version_prepared="${macos_mdm_version_prepared} ${macos_mdm_prepared_version_extra}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_mdm_prepared_version_extra is: ${macos_mdm_prepared_version_extra}"
fi
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_mdm_version_prepared is: ${macos_mdm_version_prepared}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_msu_version is: ${macos_msu_version}"
if [[ "${macos_mdm_version_prepared}" == "${macos_msu_version}" ]]; then
push_macos_mdm_download_validation_error="FALSE"
log_mdm_workflow "**** S.U.P.E.R.M.A.N. ${SUPER_VERSION} - DOWNLOAD UPDATE/UPGRADE MACOS VIA MDM COMPLETED ****"
[[ "${push_macos_mdm_target_type}" == "UPGRADE" ]] && log_super "MDM: ${macos_msu_label} upgrade is downloaded and prepared."
[[ "${push_macos_mdm_target_type}" == "UPDATE" ]] && log_super "MDM: ${macos_msu_label} upgrade is downloaded and prepared."
push_macos_mdm_download_error="FALSE"
macos_msu_download_required="FALSE"
defaults write "${SUPER_LOCAL_PLIST}" MacOSMSULabelDownloaded -string "${macos_msu_label}"
defaults write "${SUPER_LOCAL_PLIST}" MacOSMSULastStartupDownloaded -string "${mac_last_startup}"
fi
else # Install workflow at this point is a success.
/usr/libexec/PlistBuddy -c "Add :WorkflowRestartValidate bool true" "${SUPER_LOCAL_PLIST}.plist" 2> /dev/null
[[ "${workflow_scheduled_install_active}" == "TRUE" ]] && /usr/libexec/PlistBuddy -c "Delete :WorkflowScheduledInstall" "${SUPER_LOCAL_PLIST}.plist" 2> /dev/null
log_mdm_workflow "**** S.U.P.E.R.M.A.N. ${SUPER_VERSION} - INSTALL UPDATE/UPGRADE MACOS VIA MDM COMPLETED ****"
[[ "${push_macos_mdm_target_type}" == "UPGRADE" ]] && log_super "MDM: ${macos_msu_label} upgrade is prepared and ready for restart!"
[[ "${push_macos_mdm_target_type}" == "UPDATE" ]] && log_super "MDM: ${macos_msu_label} update is prepared and ready for restart!"
[[ "${push_macos_mdm_target_type}" == "INSTALLER" ]] && log_super "MDM: ${macos_installer_title} ${macos_installer_version}-${macos_installer_build} installer is prepared and ready for restart!"
if [[ "${push_macos_mdm_target_type}" == "INSTALLER" ]] && [[ "${current_user_account_name}" != "FALSE" ]]; then
log_super "MDM: Forcing logout for current user: ${current_user_account_name}."
launchctl bootout "user/${current_user_id}" &
disown %
fi
fi
fi
# Handle MDM workflow failures.
if [[ "${auth_error_jamf}" == "TRUE" ]] || [[ "${push_macos_mdm_api_error}" == "TRUE" ]] || [[ "${push_macos_mdm_start_error}" == "TRUE" ]] || [[ "${push_macos_mdm_start_timeout}" == "TRUE" ]] || [[ "${push_macos_mdm_timeout_error}" == "TRUE" ]] || [[ "${push_macos_mdm_download_validation_error}" == "TRUE" ]]; then
kill -9 "${log_stream_mdm_command_pid}" >/dev/null 2>&1
[[ "${verbose_mode_option}" == "TRUE" ]] && kill -9 "${log_stream_mdm_command_debug_pid}" >/dev/null 2>&1
kill -9 "${log_stream_mdm_workflow_pid}" >/dev/null 2>&1
[[ "${verbose_mode_option}" == "TRUE" ]] && kill -9 "${log_stream_mdm_workflow_debug_pid}" >/dev/null 2>&1
if [[ "${push_macos_mdm_workflow}" == "DOWNLOAD" ]]; then
log_mdm_command "**** S.U.P.E.R.M.A.N. ${SUPER_VERSION} - DOWNLOAD UPDATE/UPGRADE MACOS VIA MDM FAILED ****"
log_mdm_workflow "**** S.U.P.E.R.M.A.N. ${SUPER_VERSION} - DOWNLOAD UPDATE/UPGRADE MACOS VIA MDM FAILED ****"
push_macos_mdm_download_error="TRUE"
macos_msu_download_required="TRUE"
defaults delete "${SUPER_LOCAL_PLIST}" MacOSMSULabelDownloaded 2>/dev/null
defaults delete "${SUPER_LOCAL_PLIST}" MacOSMSULastStartupDownloaded 2>/dev/null
else # [[ "${push_macos_mdm_workflow}" == "INSTALL" ]]
log_mdm_command "**** S.U.P.E.R.M.A.N. ${SUPER_VERSION} - INSTALL UPDATE/UPGRADE MACOS VIA MDM FAILED ****"
log_mdm_workflow "**** S.U.P.E.R.M.A.N. ${SUPER_VERSION} - INSTALL UPDATE/UPGRADE MACOS VIA MDM FAILED ****"
fi
if [[ "${auth_error_jamf}" == "TRUE" ]]; then
log_mdm_command "Error: Failed to validate Jamf Pro API configuration. Verify Jamf Pro API configuration: https://github.com/Macjutsu/super/wiki/Apple-Silicon-Jamf-Pro-API-Credentials"
log_mdm_workflow "Error: Failed to validate Jamf Pro API configuration. Verify Jamf Pro API configuration: https://github.com/Macjutsu/super/wiki/Apple-Silicon-Jamf-Pro-API-Credentials"
log_super "Error: Failed to validate Jamf Pro API configuration. Verify Jamf Pro API configuration: https://github.com/Macjutsu/super/wiki/Apple-Silicon-Jamf-Pro-API-Credentials"
elif [[ "${push_macos_mdm_api_error}" == "TRUE" ]]; then
log_mdm_command "Error: Failed to send MDM download/install macOS request. Verify that this account has appropriate privileges: https://github.com/Macjutsu/super/wiki/Apple-Silicon-Jamf-Pro-API-Credentials#jamf-pro-api-account-privileges"
log_mdm_workflow "Error: Failed to send MDM download/install macOS request. Verify that this account has appropriate privileges: https://github.com/Macjutsu/super/wiki/Apple-Silicon-Jamf-Pro-API-Credentials#jamf-pro-api-account-privileges"
log_super "Error: Failed to send MDM download/install macOS request. Verify that this account has appropriate privileges: https://github.com/Macjutsu/super/wiki/Apple-Silicon-Jamf-Pro-API-Credentials#jamf-pro-api-account-privileges"
elif [[ "${push_macos_mdm_start_error}" == "TRUE" ]]; then
log_mdm_command "Error: Push workflow for download/install macOS via MDM failed."
log_mdm_workflow "Error: Push workflow for download/install macOS via MDM failed."
log_super "Error: Push workflow for download/install macOS via MDM failed."
elif [[ "${push_macos_mdm_start_timeout}" == "TRUE" ]]; then
log_mdm_command "Error: Push workflow for download/install macOS via MDM failed to complete, as indicated by no progress after waiting for ${TIMEOUT_MDM_COMMAND_SECONDS} seconds."
log_mdm_workflow "Error: Push workflow for download/install macOS via MDM failed to complete, as indicated by no progress after waiting for ${TIMEOUT_MDM_COMMAND_SECONDS} seconds."
log_super "Error: Push workflow for download/install macOS via MDM failed to complete, as indicated by no progress after waiting for ${TIMEOUT_MDM_COMMAND_SECONDS} seconds."
elif [[ "${push_macos_mdm_timeout_error}" == "TRUE" ]]; then
log_mdm_workflow "Error: Download/install of macOS via MDM failed to complete, as indicated by no progress after waiting for ${push_macos_mdm_timeout_seconds} seconds."
log_super "Error: Download/install of macOS via MDM failed to complete, as indicated by no progress after waiting for ${push_macos_mdm_timeout_seconds} seconds."
else # ${push_macos_mdm_download_validation_error}
if [[ "${macos_msu_major_upgrade_target}" != "FALSE" ]]; then
log_msu "Error: Downloaded macOS major upgrade version of ${macos_mdm_version_prepared} doesn't match expected version ${macos_msu_version}."
log_super "Error: Downloaded macOS major upgrade version of ${macos_mdm_version_prepared} doesn't match expected version ${macos_msu_version}."
else # "${macos_msu_minor_update_target}" != "FALSE"
log_msu "Error: Downloaded macOS minor update version of ${macos_mdm_version_prepared} doesn't match expected version ${macos_msu_version}."
log_super "Error: Downloaded macOS mintor update version of ${macos_mdm_version_prepared} doesn't match expected version ${macos_msu_version}."
fi
fi
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: push_macos_mdm_download_error is: ${push_macos_mdm_download_error}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_msu_download_required is: ${macos_msu_download_required}"
# Handle workflow failure options.
if [[ "${workflow_install_now_active}" == "TRUE" ]]; then # Install now workflow mode.
if [[ "${auth_mdm_failover_to_user_status}" == "TRUE" ]]; then # User authentication MDM failover option enabled.
log_super "Warning: Download/install of macOS via MDM failed, failing over to user authenticated workflow."
workflow_macos_auth="FAILOVER"
if [[ "${push_macos_mdm_workflow}" == "DOWNLOAD" ]]; then
workflow_download_macos
else # [[ "${push_macos_mdm_workflow}" == "INSTALL" ]]
unset install_jamf_policy_triggers_option
workflow_install_active_user
fi
return 0
else # No user authentication MDM failover option.
log_super "Error: Download/install of macOS via MDM failed, install now workflow can not continue."
log_status "Inactive Error: Download/install of macOS via MDM failed, install now workflow can not continue."
[[ "${current_user_account_name}" != "FALSE" ]] && notification_install_now_failed
exit_error
fi
else # Default super workflow.
if [[ "${current_user_account_name}" != "FALSE" ]] && [[ "${auth_mdm_failover_to_user_status}" == "TRUE" ]]; then
log_super "Warning: Download/install of macOS via MDM failed, failing over to user authenticated workflow."
workflow_macos_auth="FAILOVER"
if [[ "${push_macos_mdm_workflow}" == "DOWNLOAD" ]]; then
workflow_download_macos
else # [[ "${push_macos_mdm_workflow}" == "INSTALL" ]]
unset install_jamf_policy_triggers_option
workflow_install_active_user
fi
return 0
else # No current user, or no user authentication MDM failover option.
deferral_timer_minutes="${deferral_timer_error_minutes}"
log_super "Error: Download/install of macOS via MDM failed, trying again in ${deferral_timer_minutes} minutes."
log_status "Pending: Download/install of macOS via MDM failed, trying again in ${deferral_timer_minutes} minutes."
{ [[ "${push_macos_mdm_workflow}" == "INSTALL" ]] && [[ "${current_user_account_name}" != "FALSE" ]]; } && notification_failed
set_auto_launch_deferral
fi
fi
fi
}
# This is the install workflow for all (non-system) macOS software updates when enforced.
workflow_install_non_system_msu() {
[[ "${current_user_account_name}" != "FALSE" ]] && notification_non_system_updates
# If ${test_mode_option} then it's not necessary to continue this function.
if [[ "${test_mode_option}" == "TRUE" ]]; then
log_super "Test Mode: Skipping the install non-system macOS software updates workflow."
log_super "Test Mode: Pausing ${test_mode_timeout_seconds} seconds for the non-system macOS software updates notification..."
sleep "${test_mode_timeout_seconds}"
killall -9 "IBM Notifier" "IBM Notifier Popup" >/dev/null 2>&1
return 0
fi
# Install and check to make sure all updates are complete.
install_non_system_msu
check_software_status_required="TRUE"
workflow_check_software_status
# If we had failures or there are still more non-system updates, then try again.
if [[ "${install_non_system_msu_error}" == "TRUE" ]] && [[ "${non_system_msu_targets}" == "TRUE" ]]; then
log_super "Warning: Failed to install all non-system macOS software updates, re-trying installation workflow."
install_non_system_msu
check_software_status_required="TRUE"
workflow_check_software_status
fi
# Log status of non-system update completion.
[[ "${workflow_scheduled_install_active}" == "TRUE" ]] && defaults delete "${SUPER_LOCAL_PLIST}" WorkflowScheduledInstall 2>/dev/null
unset notification_non_system_updates_active
if [[ "${install_non_system_msu_error}" == "FALSE" ]] && [[ "${non_system_msu_targets}" == "FALSE" ]]; then
log_super "Status: Completed installation of all non-system macOS software updates!"
killall -9 "IBM Notifier" "IBM Notifier Popup" >/dev/null 2>&1
# For computers managed via Jamf Pro, submit inventory.
if [[ "${jamf_version_number}" != "FALSE" ]]; then
if [[ "${auth_error_jamf}" != "TRUE" ]]; then
log_super "Status: Submitting updated inventory to Jamf Pro. Use --verbose-mode or check /var/log/jamf.log for more detail..."
if [[ "${verbose_mode_option}" == "TRUE" ]]; then
local jamf_response
jamf_response=$("${JAMF_PRO_BINARY}" recon -verbose 2>&1)
log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: jamf_response is:\n${jamf_response}"
else
"${JAMF_PRO_BINARY}" recon >/dev/null 2>&1
fi
else # There was an earlier Jamf Pro validation error.
log_super "Warning: Unable to submit inventory to Jamf Pro."
fi
fi
else # Some software updates did not complete
if [[ "${workflow_install_now_active}" == "TRUE" ]]; then
log_super "Error: Some non-system macOS software updates did not complete, install now workflow can not continue."
log_status "Inactive Error: Some non-system macOS software updates did not complete, install now workflow can not continue."
[[ "${current_user_account_name}" != "FALSE" ]] && notification_install_now_failed
exit_error
else
deferral_timer_minutes="${deferral_timer_error_minutes}"
log_super "Warning: Some non-system macOS software updates did not complete, trying again in ${deferral_timer_minutes} minutes."
log_status "Pending: Some non-system macOS software updates did not complete, trying again in ${deferral_timer_minutes} minutes."
[[ "${current_user_account_name}" != "FALSE" ]] && notification_failed
set_auto_launch_deferral
fi
fi
}
# This is the install and restart workflow when a user is not logged in.
workflow_install_no_user() {
local workflow_install_macos_no_user_error
workflow_install_macos_no_user_error="FALSE"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_installer_target is: ${macos_installer_target}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_msu_major_upgrade_target is: ${macos_msu_major_upgrade_target}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_msu_minor_update_target is: ${macos_msu_minor_update_target}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: workflow_macos_auth is: ${workflow_macos_auth}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: workflow_install_now_active is: ${workflow_install_now_active}"
# A macOS update/upgrade is targeted.
if [[ "${macos_installer_target}" != "FALSE" ]] || [[ "${macos_msu_major_upgrade_target}" != "FALSE" ]] || [[ "${macos_msu_minor_update_target}" != "FALSE" ]]; then
# Check to make sure system has enough available storage space.
check_storage_available
if [[ "${check_storage_available_error}" == "TRUE" ]]; then
workflow_install_macos_no_user_error="TRUE"
elif [[ "${storage_ready}" == "FALSE" ]]; then
log_super "Status: Current available storage is at ${storage_available_gigabytes} GBs which is below the ${storage_required_gigabytes} GBs that is required for download."
workflow_install_macos_no_user_error="TRUE"
fi
# Check to make sure system is plugged into AC power or that the current battery level is above ${power_required_battery_percent}.
check_power_required
if [[ "${check_power_required_error}" == "TRUE" ]]; then
workflow_install_macos_no_user_error="TRUE"
elif [[ "${power_ready}" == "FALSE" ]]; then
log_super "Status: Current battery level is at ${power_battery_percent}% which is below the minimum required level of ${power_required_battery_percent}%."
workflow_install_macos_no_user_error="TRUE"
fi
# If (no errors) a target is a macOS major upgrade via installer and a download is required.
if [[ "${workflow_install_macos_no_user_error}" == "FALSE" ]] && [[ "${macos_installer_target}" != "FALSE" ]] && [[ "${macos_installer_download_required}" == "TRUE" ]]; then
download_macos_installer
[[ "${download_macos_installer_error}" == "TRUE" ]] && workflow_install_macos_no_user_error="TRUE"
fi
# If (no errors) the MDM workflow is expected, check for MDM service and bootstrap token.
if [[ "${workflow_install_macos_no_user_error}" == "FALSE" ]] && [[ "${workflow_macos_auth}" == "JAMF" ]]; then
check_mdm_service
if [[ "${auth_error_mdm}" == "FALSE" ]]; then
check_bootstrap_token_escrow
if [[ "${auth_error_bootstrap_token}" == "TRUE" ]]; then
log_super "Error: Can not use MDM workflow because this computer's bootstrap token escrow is not valid."
workflow_install_macos_no_user_error="TRUE"
fi
else # MDM service is unavailable.
log_super "Error: Can not use MDM workflow because the MDM service is not available."
workflow_install_macos_no_user_error="TRUE"
fi
fi
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: workflow_install_macos_no_user_error is: ${workflow_install_macos_no_user_error}"
# If (no errors) computers with Apple silicon, get saved authentication.
if [[ "${workflow_install_macos_no_user_error}" == "FALSE" ]] && [[ "${mac_cpu_architecture}" == "arm64" ]] && [[ "${workflow_macos_auth}" != "USER" ]] && [[ "${workflow_macos_auth}" != "FAILOVER" ]]; then
get_saved_authentication
[[ "${auth_error_saved}" == "TRUE" ]] && workflow_install_macos_no_user_error="TRUE"
fi
# If no errors, then start the appropriate macOS update/upgrade workflow.
if [[ "${workflow_install_macos_no_user_error}" == "FALSE" ]]; then
if [[ "${workflow_macos_auth}" == "LOCAL" ]]; then
[[ -n "${install_jamf_policy_triggers_option}" ]] && run_jamf_policy_triggers
if [[ "${macos_installer_target}" != "FALSE" ]]; then # Target is a macOS major upgrade via installer.
install_macos_app
else # Target is a macOS upgrade/update via MSU.
install_macos_msu
fi
elif [[ "${workflow_macos_auth}" == "JAMF" ]]; then
[[ -n "${install_jamf_policy_triggers_option}" ]] && run_jamf_policy_triggers
push_macos_mdm_workflow="INSTALL"
push_macos_mdm
unset push_macos_mdm_workflow
else # Apple Silicon with no saved update/upgrade credentials can not enforce macOS upgrade/update.
log_super "Error: No saved Apple silicon credentials and no active logged in user."
workflow_install_macos_no_user_error="TRUE"
fi
fi
elif [[ "${workflow_target}" == "Non-system Software Updates" ]]; then
workflow_install_non_system_msu
else # Workflows when there are no macOS updates/upgrades.
if [[ "${workflow_restart_without_updates_option}" == "TRUE" ]]; then # If requested, restart without updates.
[[ -n "${install_jamf_policy_triggers_option}" ]] && run_jamf_policy_triggers
defaults write "${SUPER_LOCAL_PLIST}" WorkflowRestartValidate -bool true
[[ "${workflow_scheduled_install_active}" == "TRUE" ]] && defaults delete "${SUPER_LOCAL_PLIST}" WorkflowScheduledInstall 2>/dev/null
log_super "Status: Restarting computer in one minute..."
shutdown -o -r +1 >/dev/null 2>&1 &
disown -a
else # Option to restart without updates is not enabled.
if [[ "${install_jamf_policy_triggers_without_restarting_option}" == "TRUE" ]] && [[ -n "${install_jamf_policy_triggers_option}" ]]; then
run_jamf_policy_triggers
[[ "${workflow_scheduled_install_active}" == "TRUE" ]] && defaults delete "${SUPER_LOCAL_PLIST}" WorkflowScheduledInstall 2>/dev/null
else
log_super "Warning: When no macOS update/upgrade is available you must also specify the --workflow-restart-without-updates option to restart automatically."
fi
fi
fi
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: workflow_install_macos_no_user_error is: ${workflow_install_macos_no_user_error}"
# Handle workflow failure options.
if [[ "${workflow_install_macos_no_user_error}" == "TRUE" ]]; then
if [[ "${workflow_install_now_active}" == "TRUE" ]]; then # Install now workflow mode.
log_super "Error: macOS update/upgrade workflow failed, install now workflow can not continue."
log_status "Inactive Error: macOS update/upgrade workflow failed, install now workflow can not continue."
exit_error
else # Default super workflow.
deferral_timer_minutes="${deferral_timer_error_minutes}"
log_super "Error: macOS update/upgrade workflow failed, trying again in ${deferral_timer_minutes} minutes."
log_status "Pending: macOS update/upgrade workflow failed, trying again in ${deferral_timer_minutes} minutes."
set_auto_launch_deferral
fi
fi
}
# This is the install and restart workflow when a user is logged in.
workflow_install_active_user() {
local workflow_install_macos_active_user_error
workflow_install_macos_active_user_error="FALSE"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_installer_target is: ${macos_installer_target}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_msu_major_upgrade_target is: ${macos_msu_major_upgrade_target}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_msu_minor_update_target is: ${macos_msu_minor_update_target}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: workflow_macos_auth is: ${workflow_macos_auth}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: workflow_install_now_active is: ${workflow_install_now_active}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_mdm_failover_to_user_status is: ${auth_mdm_failover_to_user_status}"
# A macOS update/upgrade is targeted.
if [[ "${macos_installer_target}" != "FALSE" ]] || [[ "${macos_msu_major_upgrade_target}" != "FALSE" ]] || [[ "${macos_msu_minor_update_target}" != "FALSE" ]]; then
# Check to make sure system has enough available storage space.
check_storage_available
if [[ "${check_storage_available_error}" == "FALSE" ]]; then
if [[ "${storage_ready}" == "FALSE" ]]; then
dialog_insufficient_storage
[[ "${dialog_insufficient_storage_error}" == "TRUE" ]] && workflow_install_macos_active_user_error="TRUE"
fi
else # "${check_storage_available_error}" == "TRUE"
workflow_install_macos_active_user_error="TRUE"
fi
# Check to make sure system is plugged into AC power or that the current battery level is above ${power_required_battery_percent}.
check_power_required
if [[ "${check_power_required_error}" == "FALSE" ]]; then
if [[ "${power_ready}" == "FALSE" ]]; then
dialog_power_required
[[ "${dialog_power_required_error}" == "TRUE" ]] && workflow_install_macos_active_user_error="TRUE"
fi
else # "${check_power_required_error}" == "TRUE"
workflow_install_macos_active_user_error="TRUE"
fi
# If (no errors) an MDM workflow is expected, first check for MDM service, bootstrap token, and possibly failover to user authentication workflow.
if [[ "${workflow_install_macos_active_user_error}" == "FALSE" ]] && [[ "${workflow_macos_auth}" == "JAMF" ]]; then
check_mdm_service
if [[ "${auth_error_mdm}" == "TRUE" ]]; then
if [[ "${auth_mdm_failover_to_user_status}" == "TRUE" ]] || [[ $(echo "${auth_mdm_failover_to_user_option}" | grep -c 'ERROR') -gt 0 ]] || { [[ "${workflow_install_now_active}" == "TRUE" ]] && [[ $(echo "${auth_mdm_failover_to_user_option}" | grep -c 'INSTALLNOW') -gt 0 ]]; }; then
log_super "Warning: MDM service is not available, failing over to user authenticated install workflow."
workflow_macos_auth="FAILOVER"
else
log_super "Error: Can not use MDM workflow because the MDM service is not available."
workflow_install_macos_active_user_error="TRUE"
fi
else # MDM service is available.
check_bootstrap_token_escrow
if [[ "${auth_error_bootstrap_token}" == "TRUE" ]]; then
if [[ "${auth_mdm_failover_to_user_status}" == "TRUE" ]] || [[ $(echo "${auth_mdm_failover_to_user_option}" | grep -c 'ERROR') -gt 0 ]] || { [[ "${workflow_install_now_active}" == "TRUE" ]] && [[ $(echo "${auth_mdm_failover_to_user_option}" | grep -c 'INSTALLNOW') -gt 0 ]]; }; then
log_super "Warning: Missing or invalid bootstrap token escrow, failing over to user authenticated install workflow."
workflow_macos_auth="FAILOVER"
else
log_super "Error: Can not use MDM workflow because this computer's bootstrap token is not escrowed."
workflow_install_macos_active_user_error="TRUE"
fi
fi
fi
fi
# If (no errors) computers with Apple silicon, get authentication.
if [[ "${workflow_install_macos_active_user_error}" == "FALSE" ]] && [[ "${mac_cpu_architecture}" == "arm64" ]] && [[ "${workflow_macos_auth}" != "FALSE" ]]; then
if [[ "${workflow_macos_auth}" == "LOCAL" ]] || [[ "${workflow_macos_auth}" == "JAMF" ]]; then
get_saved_authentication
if [[ "${auth_error_saved}" == "TRUE" ]]; then
if [[ "${auth_credential_failover_to_user_option}" == "TRUE" ]] || [[ "${auth_ask_user_to_save_password}" == "TRUE" ]]; then
log_super "Warning: Saved authentication error, failing over to user authenticated installation workflow."
workflow_macos_auth="FAILOVER"
[[ "${dialog_user_auth_valid}" != "TRUE" ]] && dialog_user_auth
[[ "${dialog_user_auth_error}" == "TRUE" ]] && workflow_install_macos_active_user_error="TRUE"
else
log_super "Error: Unable to use saved authentication for installation and the --auth-credential-failover-to-user option is not enabled."
workflow_install_macos_active_user_error="TRUE"
fi
fi
else # [[ "${workflow_macos_auth}" == "USER" ]] || [[ "${workflow_macos_auth}" == "FAILOVER" ]]
[[ "${dialog_user_auth_valid}" != "TRUE" ]] && dialog_user_auth
[[ "${dialog_user_auth_error}" == "TRUE" ]] && workflow_install_macos_active_user_error="TRUE"
fi
fi
# If no errors, then start the appropriate macOS update/upgrade workflow.
if [[ "${workflow_install_macos_active_user_error}" == "FALSE" ]]; then
if [[ "${workflow_macos_auth}" == "LOCAL" ]] || [[ "${workflow_macos_auth}" == "USER" ]] || [[ "${workflow_macos_auth}" == "FAILOVER" ]]; then
if [[ "${macos_installer_target}" != "FALSE" ]]; then # Target is a macOS major upgrade via installer.
display_string_prepare_time_estimate="15-25"
notification_prepare
[[ -n "${install_jamf_policy_triggers_option}" ]] && run_jamf_policy_triggers
install_macos_app
else # Target is a macOS upgrade/update via MSU.
[[ -n "${install_jamf_policy_triggers_option}" ]] && run_jamf_policy_triggers
install_macos_msu
fi
else # [[ "${workflow_macos_auth}" == "JAMF" ]]
if [[ "${macos_installer_target}" != "FALSE" ]]; then # Target is a macOS major upgrade via installer.
display_string_prepare_time_estimate="25-30"
else # Target is a macOS upgrade/update via MSU.
display_string_prepare_time_estimate="5"
fi
notification_prepare
[[ -n "${install_jamf_policy_triggers_option}" ]] && run_jamf_policy_triggers
push_macos_mdm_workflow="INSTALL"
push_macos_mdm
unset push_macos_mdm_workflow
fi
fi
elif [[ "${workflow_target}" == "Non-system Software Updates" ]]; then
workflow_install_non_system_msu
else # Workflows when there are no macOS updates/upgrades.
if [[ "${workflow_restart_without_updates_option}" == "TRUE" ]]; then # If requested, restart without updates.
[[ -n "${install_jamf_policy_triggers_option}" ]] && run_jamf_policy_triggers
notification_restart
if [[ "${test_mode_option}" != "TRUE" ]]; then
defaults write "${SUPER_LOCAL_PLIST}" WorkflowRestartValidate -bool true
[[ "${workflow_scheduled_install_active}" == "TRUE" ]] && defaults delete "${SUPER_LOCAL_PLIST}" WorkflowScheduledInstall 2>/dev/null
log_super "Status: Restarting computer in one minute..."
shutdown -o -r +1 >/dev/null 2>&1 &
disown -a
else # Test mode.
log_super "Test Mode: Pausing ${test_mode_timeout_seconds} seconds for the restart notification..."
sleep "${test_mode_timeout_seconds}"
killall -9 "IBM Notifier" "IBM Notifier Popup" >/dev/null 2>&1
fi
else # Option to restart without updates is not enabled.
if [[ "${install_jamf_policy_triggers_without_restarting_option}" == "TRUE" ]] && [[ -n "${install_jamf_policy_triggers_option}" ]]; then
run_jamf_policy_triggers
[[ "${workflow_scheduled_install_active}" == "TRUE" ]] && defaults delete "${SUPER_LOCAL_PLIST}" WorkflowScheduledInstall 2>/dev/null
else
log_super "Warning: When no macOS update/upgrade is available you must also specify the --workflow-restart-without-updates option to restart automatically."
fi
fi
fi
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: workflow_install_macos_active_user_error is: ${workflow_install_macos_active_user_error}"
# Handle workflow failure options.
if [[ "${workflow_install_macos_active_user_error}" == "TRUE" ]]; then
if [[ "${workflow_install_now_active}" == "TRUE" ]]; then # Install now workflow mode.
log_super "Error: macOS update/upgrade workflow failed, install now workflow can not continue."
log_status "Inactive Error: macOS update/upgrade workflow failed, install now workflow can not continue."
notification_install_now_failed
exit_error
else # Default super workflow.
deferral_timer_minutes="${deferral_timer_error_minutes}"
log_super "Error: macOS update/upgrade workflow failed, trying again in ${deferral_timer_minutes} minutes."
log_status "Pending: macOS update/upgrade workflow failed, trying again in ${deferral_timer_minutes} minutes."
notification_failed
set_auto_launch_deferral
fi
fi
}
# This function checks the macOS upgrade/update status after a previous super macOS upgrade/update restart.
workflow_restart_validate() {
log_status "Running: Restart validation workflow."
if [[ "${workflow_disable_update_check_option}" != "TRUE" ]]; then # Skip software updates/upgrade mode option.
check_software_status_required="TRUE"
workflow_check_software_status
fi
# Install any non-system macOS software updates.
if [[ "${non_system_msu_targets}" == "TRUE" ]]; then
install_non_system_msu
if [[ "${install_non_system_msu_error}" != "TRUE" ]]; then
log_super "Status: Completed installation of all non-system macOS software updates!"
else
log_super "Warning: Failed to install all non-system macOS software updates."
fi
check_software_status_required="TRUE"
workflow_check_software_status
fi
# Log status of updates/upgrade completion.
if [[ "${macos_installer_target}" == "FALSE" ]] && [[ "${macos_msu_major_upgrade_target}" == "FALSE" ]] && [[ "${macos_msu_minor_update_target}" == "FALSE" ]] && [[ "${non_system_msu_targets}" == "FALSE" ]]; then
log_super "Status: All available and enabled macOS software updates/upgrades completed!"
check_software_status_required="FALSE"
elif [[ "${macos_installer_target}" == "FALSE" ]] && [[ "${macos_msu_major_upgrade_target}" == "FALSE" ]] && [[ "${macos_msu_minor_update_target}" == "FALSE" ]] && [[ "${non_system_msu_targets}" == "TRUE" ]]; then
deferral_timer_minutes="${DEFERRAL_TIMER_RESTART_VALIDATION_ERROR_MINUTES}"
log_super "Error: Some non-system macOS software updates remain available for installation, trying restart validation workflow again in ${deferral_timer_minutes} minutes."
log_status "Pending: Some non-system macOS software updates remain available for installation, trying restart validation workflow again in ${deferral_timer_minutes} minutes."
set_auto_launch_deferral
else
log_super "Warning: Some macOS software updates/upgrades did not complete after last restart, continuing workflow."
fi
# For computers managed via Jamf Pro, submit inventory and check for policies.
if [[ "${jamf_version_number}" != "FALSE" ]]; then
if [[ "${auth_error_jamf}" != "TRUE" ]]; then
log_super "Status: Submitting updated inventory to Jamf Pro. Use --verbose-mode or check /var/log/jamf.log for more detail..."
local jamf_response
if [[ "${verbose_mode_option}" == "TRUE" ]]; then
jamf_response=$("${JAMF_PRO_BINARY}" recon -verbose 2>&1)
log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: jamf_response is:\n${jamf_response}"
else
"${JAMF_PRO_BINARY}" recon >/dev/null 2>&1
fi
sleep 5
log_super "Status: Running Jamf Pro check-in policies. Use --verbose-mode or check /var/log/jamf.log for more detail..."
if [[ "${verbose_mode_option}" == "TRUE" ]]; then
jamf_response=$("${JAMF_PRO_BINARY}" policy -verbose 2>&1)
log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: jamf_response is:\n${jamf_response}"
else
"${JAMF_PRO_BINARY}" policy >/dev/null 2>&1
fi
else # There was an earlier Jamf Pro validation error.
deferral_timer_minutes="${DEFERRAL_TIMER_RESTART_VALIDATION_ERROR_MINUTES}"
log_super "Error: Unable to submit inventory or perform check-in via Jamf Pro, trying restart validation workflow again in ${deferral_timer_minutes} minutes."
log_status "Pending: Unable to submit inventory or perform check-in via Jamf Pro, trying restart validation workflow again in ${deferral_timer_minutes} minutes."
set_auto_launch_deferral
fi
fi
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: Local preference file after workflow_restart_validate: ${SUPER_LOCAL_PLIST}:\n$(defaults read "${SUPER_LOCAL_PLIST}" 2>/dev/null)"
}
# MARK: *** Jamf Pro ***
################################################################################
# Validate connectivity to Jamf Pro service and set ${jamf_version_number}, ${jamf_management_url}, and ${jamf_api_url}.
check_jamf_management_framework() {
jamf_version_number="FALSE"
auth_error_jamf="FALSE"
if [[ -f "${JAMF_PRO_BINARY}" ]]; then
jamf_version_number_full=$("${JAMF_PRO_BINARY}" -version | sed -e 's/version=//' -e 's/-.*//')
jamf_version_number_major=$(echo "${jamf_version_number_full}" | cut -d'.' -f1) # Expected output: 10, 11
jamf_version_number_minor=$(echo "${jamf_version_number_full}" | cut -d'.' -f2) # Expected output: 0, 30, 31, 32, etc.
jamf_version_number="${jamf_version_number_major}$(printf "%02d" "${jamf_version_number_minor}")" # Expected output: 1048, 1100, etc.
if [[ "${mac_cpu_architecture}" == "arm64" ]] && [[ "${jamf_version_number}" -lt 1038 ]]; then
log_super "Error: super requires Jamf Pro version 10.38 or later, the currently installed version of Jamf Pro ${jamf_version_number_major}.${jamf_version_number_minor} is not supported."
auth_error_jamf="TRUE"
elif [[ "${mac_cpu_architecture}" != "arm64" ]] && [[ "${jamf_version_number}" -lt 1000 ]]; then
log_super "Error: super requires Jamf Pro version 10.00 or later, the currently installed version of Jamf Pro ${jamf_version_number_major}.${jamf_version_number_minor} is not supported."
auth_error_jamf="TRUE"
else # Jamf Pro version is supported.
local jamf_response
jamf_response=$("${JAMF_PRO_BINARY}" checkJSSConnection -retry 1 2>/dev/null)
if [[ $(echo "${jamf_response}" | grep -c 'available') -gt 0 ]]; then
jamf_management_url=$(defaults read /Library/Preferences/com.jamfsoftware.jamf.plist jss_url)
log_super "Status: Managed by Jamf Pro ${jamf_version_number_full} hosted at: ${jamf_management_url}"
jamf_api_url="${jamf_management_url}"
else
log_super "Error: Jamf Pro service is unavailable with response:\n${jamf_response}"
auth_error_jamf="TRUE"
fi
fi
fi
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: jamf_version_number is: ${jamf_version_number}"
# Check for ${auth_jamf_custom_url} and sets ${jamf_api_url} accordingly.
if [[ "${jamf_version_number}" != "FALSE" ]] && [[ "${auth_error_jamf}" != "TRUE" ]] && [[ -n "${auth_jamf_custom_url}" ]]; then
local curl_response
curl_response=$(curl -Is "${auth_jamf_custom_url}" | head -n 1)
if [[ $(echo "${curl_response}" | grep -c 'HTTP') -gt 0 ]] && [[ $(echo "${curl_response}" | grep -c -e '400' -e '40[4-9]' -e '4[1-9][0-9]' -e '5[0-9][0-9]') -eq 0 ]]; then
jamf_api_url="${auth_jamf_custom_url}"
log_super "Status: Using custom Jamf Pro API URL hosted at ${jamf_api_url}."
else
log_super "Error: Custom Jamf Pro API URL is unavailable: ${curl_response}"
auth_error_jamf="TRUE"
fi
fi
}
# Use preferences derived ${auth_jamf_computer_id_option} or a jamf recon to resolve the computer's ${jamf_computer_id}.
get_jamf_api_computer_id() {
unset jamf_computer_id
if [[ -n "${auth_jamf_computer_id_option}" ]]; then
jamf_computer_id="${auth_jamf_computer_id_option}"
else # Resolve the computer's Jamf Pro ID via jamf recon.
log_super "Warning: Running Jamf Pro inventory to resolve the Jamf Pro Computer ID. To avoid this step use a configuration profile as covered in the super Wiki: https://github.com/Macjutsu/super/wiki/"
local jamf_response
local jamf_return
jamf_response=$("${JAMF_PRO_BINARY}" recon -verbose)
jamf_return="$?"
if [[ "${jamf_return}" -eq 0 ]]; then
jamf_computer_id=$(echo "${jamf_response}" | grep '' | sed -e 's/[^0-9]*//g')
else
log_super "Error: Jamf Pro inventory collection failed."
auth_error_jamf="TRUE"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: jamf_response is:\n${jamf_response}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: jamf_return is: ${jamf_return}"
fi
fi
# A bit of error checking to make sure it's a regular number.
if [[ "${jamf_computer_id}" =~ ${REGEX_ANY_WHOLE_NUMBER} ]]; then
defaults write "${SUPER_LOCAL_PLIST}" AuthJamfComputerID -string "${jamf_computer_id}"
else
log_super "Error: Unable to resolve Jamf Pro Computer ID."
auth_error_jamf="TRUE"
fi
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: jamf_computer_id is: ${jamf_computer_id}"
}
# Attempt to acquire a ${jamf_access_token} via ${auth_jamf_client} and ${auth_jamf_secret} credentials or via ${auth_jamf_account} and ${auth_jamf_password} credentials.
get_jamf_api_access_token() {
local curl_response
if [[ -n "${auth_jamf_client}" ]]; then
curl_response=$(curl --silent --location --request POST "${jamf_api_url}api/oauth/token" --header "Content-Type: application/x-www-form-urlencoded" --data-urlencode "client_id=${auth_jamf_client}" --data-urlencode "grant_type=client_credentials" --data-urlencode "client_secret=${auth_jamf_secret}")
else # Legacy ${auth_jamf_account} authentication.
curl_response=$(curl --silent --location --request POST "${jamf_api_url}api/v1/auth/token" --user "${auth_jamf_account}:${auth_jamf_password}")
fi
if [[ $(echo "${curl_response}" | grep -c 'token') -gt 0 ]]; then
if [[ -n "${auth_jamf_client}" ]]; then
if [[ "${macos_version_major}" -ge 12 ]]; then
jamf_access_token=$(echo "${curl_response}" | plutil -extract access_token raw -)
else # macOS 11.
jamf_access_token=$(echo "${curl_response}" | awk -F '"' '{print $4;}' | xargs)
fi
else # Legacy ${auth_jamf_account} authentication.
if [[ "${macos_version_major}" -ge 12 ]]; then
jamf_access_token=$(echo "${curl_response}" | plutil -extract token raw -)
else # macOS 11.
jamf_access_token=$(echo "${curl_response}" | grep 'token' | awk -F '"' '{print $4;}' | xargs)
fi
fi
else # There was no access token.
if [[ -n "${auth_jamf_client}" ]]; then
log_super "Auth Error: Response from Jamf Pro API access token request did not contain a token. Verify the --auth-jamf-client=ClientID and --auth-jamf-secret=ClientSecret values."
auth_error_jamf="TRUE"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_echo "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_jamf_client is: ${auth_jamf_client}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_echo "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_jamf_secret is: ${auth_jamf_secret}"
else # Legacy ${auth_jamf_account} authentication.
log_super "Auth Error: Response from Jamf Pro API access token request did not contain a token. Verify the --auth-jamf-account=AccountName and --auth-jamf-password=Password values."
auth_error_jamf="TRUE"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_echo "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_jamf_account is: ${auth_jamf_account}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_echo "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_jamf_password is: ${auth_jamf_password}"
fi
auth_error_jamf="TRUE"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: jamf_api_url is: ${jamf_api_url}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: curl_response is:\n${curl_response}"
fi
}
# Use ${jamf_access_token} to attempt a Blank Push request to the Jamf Pro API at ${jamf_api_url}.
send_jamf_api_blank_push() {
jamf_blank_push_response=$(curl --silent --output /dev/null --write-out "%{http_code}" --location --request POST "${jamf_api_url}JSSResource/computercommands/command/BlankPush/id/${jamf_computer_id}" --header "Authorization: Bearer ${jamf_access_token}")
}
# Validate that the account ${auth_jamf_account} and ${auth_jamf_password} are valid credentials and has appropriate permissions to send MDM push commands. If not set ${auth_error_jamf}.
check_jamf_api_credentials() {
auth_error_jamf="FALSE"
[[ -z "${jamf_computer_id}" ]] && get_jamf_api_computer_id
[[ "${auth_error_jamf}" == "TRUE" ]] && return 0
get_jamf_api_access_token
[[ "${auth_error_jamf}" == "TRUE" ]] && return 0
send_jamf_api_blank_push
[[ "${auth_error_jamf}" == "TRUE" ]] && return 0
if [[ "${jamf_blank_push_response}" != 201 ]]; then
log_super "Auth Error: Unable to request Blank Push via the Jamf Pro API. Verify that the provided Jamf Pro credentials has appropriate privileges: https://github.com/Macjutsu/super/wiki/Apple-Silicon-Jamf-Pro-API-Credentials#jamf-pro-api-account-privileges"
auth_error_jamf="TRUE"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: jamf_blank_push_response is:\n${jamf_blank_push_response}"
fi
}
# Validate existing ${jamf_access_token} and if found invalid, a new token is requested and again validated.
check_jamf_api_access_token() {
auth_error_jamf="FALSE"
[[ -z "${jamf_computer_id}" ]] && get_jamf_api_computer_id
[[ "${auth_error_jamf}" == "TRUE" ]] && return 0
local curl_response
if [[ -n "${jamf_access_token}" ]]; then
curl_response=$(curl --silent --output /dev/null --write-out "%{http_code}" --location --request GET "${jamf_api_url}api/v1/auth" --header "Authorization: Bearer ${jamf_access_token}")
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: curl_response is:\n${curl_response}"
fi
if [[ "${curl_response}" -ne 200 ]]; then
get_jamf_api_access_token
[[ "${auth_error_jamf}" == "TRUE" ]] && return 0
curl_response=$(curl --silent --output /dev/null --write-out "%{http_code}" --location --request GET "${jamf_api_url}api/v1/auth" --header "Authorization: Bearer ${jamf_access_token}")
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: curl_response is:\n${curl_response}"
if [[ "${curl_response}" -ne 200 ]]; then
log_super "Auth Error: Unable to validate Jamf Pro API access token."
auth_error_jamf="TRUE"
fi
fi
}
# Validate if Jamf Pro 10.48 or later is using "the new software updates experience" and set ${jamf_api_update_workflow} accordingly.
check_jamf_api_update_workflow() {
jamf_api_update_workflow="LEGACY"
if [[ "${jamf_version_number}" -ge 1048 ]]; then
local curl_response
curl_response=$(curl --silent --write-out "%{http_code}" --location --request GET "${jamf_api_url}api/v1/managed-software-updates/plans/feature-toggle" --header "Authorization: Bearer ${jamf_access_token}" --header "Content-Type: application/json")
if [[ $(echo "${curl_response}" | grep -c '200') -gt 0 ]]; then
if [[ $(echo "${curl_response}" | grep -e 'toggle' | grep -c 'true') -gt 0 ]]; then
jamf_api_update_workflow="NEW"
fi
else
log_super "Error: Unable to validate Jamf Pro API software update workflow type. Verify that the provided Jamf Pro credentials has appropriate privileges: https://github.com/Macjutsu/super/wiki/Apple-Silicon-Jamf-Pro-API-Credentials#jamf-pro-api-account-privileges"
auth_error_jamf="TRUE"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: curl_response is: ${curl_response}"
fi
fi
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: jamf_api_update_workflow is: ${jamf_api_update_workflow}"
}
# Invalidate and remove from local memory the ${jamf_access_token}.
delete_jamf_api_access_token() {
local curl_response
curl_response=$(curl --silent --output /dev/null --write-out "%{http_code}" --location --request POST --url "${jamf_api_url}api/v1/auth/invalidate-token" --header "Authorization: Bearer ${jamf_access_token}")
if [[ "${curl_response}" -eq 204 ]]; then
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: Jamf Pro API access token successfully invalidated."
unset jamf_access_token
elif [[ "${curl_response}" -eq 401 ]]; then
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: Jamf Pro API access token already invalid."
unset jamf_access_token
else
log_super "Error: Invalidating Jamf Pro API access token."
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: curl_response is:\n${curl_response}"
fi
}
# Install any optional ${install_jamf_policy_triggers_option}.
run_jamf_policy_triggers() {
jamf_policy_error="FALSE"
log_super "Status: Installing Jamf Pro Policy triggers. Use --verbose-mode or check /var/log/jamf.log for more detail..."
log_status "Running: Installing Jamf Pro Policy triggers."
local previous_ifs
previous_ifs="${IFS}"
IFS=','
local jamf_policy_triggers_array
read -r -a jamf_policy_triggers_array <<<"${install_jamf_policy_triggers_option}"
for jamf_policy_trigger in "${jamf_policy_triggers_array[@]}"; do
[[ "${current_user_account_name}" != "FALSE" ]] && notification_jamf_pro_policy
if [[ "${test_mode_option}" != "TRUE" ]]; then
log_super "Status: Jamf Pro Policy with Trigger \"${jamf_policy_trigger}\" is starting..."
local jamf_response
local jamf_return
if [[ "${verbose_mode_option}" == "TRUE" ]]; then
jamf_response=$("${JAMF_PRO_BINARY}" policy -event "${jamf_policy_trigger}" -verbose 2>&1)
jamf_return="$?"
log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: jamf_response is:\n${jamf_response}"
log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: jamf_return is: ${jamf_return}"
else
"${JAMF_PRO_BINARY}" policy -event "${jamf_policy_trigger}" >/dev/null 2>&1
jamf_return="$?"
fi
if [[ "${jamf_return}" -ne 0 ]]; then
log_super "Error: Jamf Pro Policy with Trigger \"${jamf_policy_trigger}\" failed!"
jamf_policy_error="TRUE"
else
log_super "Status: Jamf Pro Policy with Trigger \"${jamf_policy_trigger}\" was successful."
fi
else
log_super "Test Mode: Skipping Jamf Pro Policy with Trigger: ${jamf_policy_trigger}."
if [[ "${current_user_account_name}" != "FALSE" ]]; then
log_super "Test Mode: Pausing ${test_mode_timeout_seconds} seconds for the Jamf Pro Policy notification..."
sleep "${test_mode_timeout_seconds}"
fi
fi
done
IFS="${previous_ifs}"
{ [[ "${workflow_target}" == "Jamf Pro Policy Triggers Without Restarting" ]] && [[ "${current_user_account_name}" != "FALSE" ]]; } && killall -9 "IBM Notifier" "IBM Notifier Popup" >/dev/null 2>&1
# Wrap-up Jamf Pro Policies workflow.
if [[ "${jamf_policy_error}" != "TRUE" ]]; then
log_super "Status: All Jamf Pro Policies completed."
else # Handle workflow failure options.
if [[ "${workflow_install_now_active}" == "TRUE" ]]; then # Install now workflow mode.
log_super "Error: Some Jamf Pro Policies failed, install now workflow can not continue."
log_status "Inactive Error: Some Jamf Pro Policies failed, install now workflow can not continue."
[[ "${current_user_account_name}" != "FALSE" ]] && notification_install_now_failed
exit_error
else # Default super workflow.
deferral_timer_minutes="${deferral_timer_error_minutes}"
log_super "Error: macOS update/upgrade workflow failed, trying again in ${deferral_timer_minutes} minutes."
log_status "Pending: macOS update/upgrade workflow failed, trying again in ${deferral_timer_minutes} minutes."
[[ "${current_user_account_name}" != "FALSE" ]] && notification_failed
set_auto_launch_deferral
fi
fi
}
# MARK: *** User Interface Management ***
################################################################################
# Set language strings for dialogs and notifications.
set_display_strings_language() {
#### Langauge for the restart button in dialogs.
display_string_restart_now_button="Restart Now"
#### Langauge for the install button in dialogs.
display_string_install_now_button="Install Now"
#### Langauge for the download button in dialogs.
display_string_download_now_button="Download Now"
#### Language for the defer button in dialogs when the deferral time is sometime today.
display_string_defer_today_button="Defer"
#### Language for the defer button in dialogs when the deferral time is after tomorrow.
display_string_defer_tomorrow_button="Defer Until Tomorrow"
#### Language for the defer button in dialogs when the deferral time is after tomorrow.
display_string_defer_future_button="Defer Until"
#### Language for the user schedule restart button in dialogs.
display_string_schedule_restart_button="Schedule Restart"
#### Language for the user schedule installation button in Jamf Pro Policy dialogs.
display_string_schedule_install_button="Schedule Installation"
#### Language for the user reschedule button in dialogs.
display_string_reschedule_button="Reschedule"
#### Language for the cancel button in certain dialogs and notifications.
display_string_cancel_button="Cancel"
#### Language for the OK button in certain dialogs and notifications.
display_string_ok_button="OK"
### Language for various deferral timer durations.
display_string_minutes="Minutes"
display_string_hour="Hour"
display_string_hours="Hours"
display_string_and="and"
#### This code generates the ${display_string_workflow_title} variable to include the appropriate upgrade workflow title .
if [[ "${workflow_target}" == "Non-system Software Updates" ]]; then
display_string_workflow_title="Apple Software Updates"
elif [[ "${workflow_target}" == "Jamf Pro Policy Triggers Without Restarting" ]] || [[ "${workflow_target}" == "Jamf Pro Policy Triggers With Restart" ]]; then
display_string_workflow_title="Jamf Pro Policies"
elif [[ "${workflow_target}" == "Restart Without Updates" ]]; then
display_string_workflow_title="Management"
else # Standard macOS update/upgrade workflows removes the build number to simplify the dialogs.
# shellcheck disable=SC2001
display_string_workflow_title=$(echo "${workflow_target}" | sed -e 's/-.*//')
fi
#### Useful display variables and info:
# ${display_string_workflow_title} is the workflow title that may include the target macOS version number.
# ${jamf_policy_trigger} is the trigger name of the currently running Jamf Pro Policy.
# ${storage_available_gigabytes} is the number of gigabytes of currently available storage.
# ${storage_required_gigabytes} is the number of gigabytes required for the current macOS update/upgrade workflow.
# ${display_string_deadline_count} is the current number of user soft/hard deferrals.
# ${display_string_deadline_count_maximum} is the maximum number of user soft/hard deferrals.
# ${deadline_days_soft} is the maximum number of deferral days before a soft deadline.
# ${deadline_days_hard} is the maximum number of deferral days before a hard deadline.
# ${display_string_schedule_zero_date} is the date:time of a zero date that is used for calculating deferral deadlines and scheduled installations.
# ${display_string_scheduled_install} is the date:time of a scheduled installation.
# ${display_string_deadline} is the soonest date or date and time based on evaluating both the maximum date and days deferral deadlines.
# ${display_string_prepare_time_estimate} is a estimated number of minutes that an update/upgrade process needs for preparation before a restart.
# ${current_user_real_name} is the current users full (display friendly) name.
# See ${DISPLAY_STRING_FORMAT_DATE} and ${DISPLAY_STRING_FORMAT_TIME} in the set_defaults() function to adjust how the date:time is shown.
# For body text note that IBM Notifier interprets "\n" as a return.
#### Language for notification_install_now_start(), a non-interactive notification informing the user that the install now workflow has started.
display_string_install_now_start_title="Software Update Workflow Starting"
display_string_install_now_start_body="The software update workflow has started, and you will be notified if this computer needs to restart.\n\nDuring this time you can continue to use the computer or lock the screen, but please do not restart or sleep the computer as it will prolong the macOS software update process."
#### Language for notification_install_now_download(), a non-interactive notification informing the user that the install now workflow is downloading the macOS update/upgrade.
display_string_install_now_download_title="Downloading ${display_string_workflow_title}"
display_string_install_now_download_body="The ${display_string_workflow_title} is downloading from Apple. This may take a while, but you will be notified before this computer automatically restarts.\n\nDuring this time you can continue to use the computer or lock the screen, but please do not restart or sleep the computer as it will prolong the update/upgrade process."
#### Language for notification_install_now_up_to_date(), a non-interactive notification informing the user that the install now workflow doesn't have anything else to do.
display_string_install_now_up_to_date_title="Update Workflow Complete"
display_string_install_now_up_to_date_body="All software is already up to date or is the latest version allowed by management."
#### Language for notification_install_now_failed(), a non-interactive notification informing the user that the install now workflow has failed.
# This is used for all update/upgrade workflows if they fail to start or timeout after a pending restart notification has been shown.
display_string_install_now_failed_title="${display_string_workflow_title} Failed"
display_string_install_now_failed_body="${display_string_workflow_title} installation failed to complete.\n\nYou can try again or consider contacting your technical support team if you're experiencing consistent failures."
#### Language for notification_jamf_pro_policy(), a non-interactive notification informing the user that a Jamf Pro Policy has started.
# This is used for both non-deadline and deadline workflows.
display_string_jamf_pro_policy_title="Running Jamf Pro Policy ${jamf_policy_trigger}"
display_string_jamf_pro_policy_default_body="During this time you can continue to use the computer or lock the screen, but please do not restart or sleep the computer as it will prolong the process."
display_string_jamf_pro_policy_deadline_schedule_body="A scheduled installation date of ${display_string_scheduled_install} has passed.\n\nDuring this time you can continue to use the computer or lock the screen, but please do not restart or sleep the computer as it will prolong the process."
display_string_jamf_pro_policy_deadline_count_body="You have deferred the maximum number of ${display_string_deadline_count_maximum} times.\n\nDuring this time you can continue to use the computer or lock the screen, but please do not restart or sleep the computer as it will prolong the process."
display_string_jamf_pro_policy_deadline_date_body="The deferment deadline of ${display_string_deadline} has passed.\n\nDuring this time you can continue to use the computer or lock the screen, but please do not restart or sleep the computer as it will prolong the process."
#### Language for notification_prepare(), a non-interactive notification informing the user that a update/upgrade preparation process has started.
# This is used for both non-deadline and deadline workflows.
display_string_prepare_title="${display_string_workflow_title} System Restart"
display_string_prepare_default_body="A required software update will automatically restart this computer in about ${display_string_prepare_time_estimate} minutes.\n\nDuring this time you can continue to use the computer or lock the screen, but please do not restart or sleep the computer as it will prolong the update process."
display_string_prepare_deadline_schedule_body="A scheduled installation date of ${display_string_scheduled_install} has passed.\n\nA required software update will automatically restart this computer in about ${display_string_prepare_time_estimate} minutes.\n\nDuring this time you can continue to use the computer or lock the screen, but please do not restart or sleep the computer as it will prolong the update process."
display_string_prepare_deadline_count_body="You have deferred the maximum number of ${display_string_deadline_count_maximum} times.\n\nA required software update will automatically restart this computer in about ${display_string_prepare_time_estimate} minutes.\n\nDuring this time you can continue to use the computer or lock the screen, but please do not restart or sleep the computer as it will prolong the update process."
display_string_prepare_deadline_date_body="The deferment deadline of ${display_string_deadline} has passed.\n\nA required software update will automatically restart this computer in about ${display_string_prepare_time_estimate} minutes.\n\nDuring this time you can continue to use the computer or lock the screen, but please do not restart or sleep the computer as it will prolong the update process."
#### Language for notification_restart(), a non-interactive notification informing the user that the computer is going to restart very soon.
# This is used for all softwareupdate workflows and near the end of the MDM workflow.
# This is used for both non-deadline and deadline workflows.
display_string_restart_title="${display_string_workflow_title} System Restart"
display_string_restart_default_body="This computer will automatically restart very soon.\n\nSave any open documents now."
display_string_restart_deadline_schedule_body="A scheduled installation date of ${display_string_scheduled_install} has passed.\n\nThis computer will automatically restart very soon.\n\nSave any open documents now."
display_string_restart_deadline_count_body="You have deferred the maximum number of ${display_string_deadline_count_maximum} times.\n\nThis computer will automatically restart very soon.\n\nSave any open documents now."
display_string_restart_deadline_date_body="The deferment deadline of ${display_string_deadline} has passed.\n\nThis computer will automatically restart very soon.\n\nSave any open documents now."
#### Language for notification_non_system_updates(), a non-interactive notification informing the user that the workflow is installing non-system macOS software updates.
display_string_non_system_updates_title="Installing ${display_string_workflow_title} (No Restart)"
display_string_non_system_updates_body="Required macOS software updates are now installing.\n\nThe computer will not restart but some Apple applications (like Safari) may automatically restart.\n\nThis should only take a few minutes, but please do not restart or sleep the computer as it will prolong the update process."
#### Language for notification_failed(), a non-interactive notification informing the user that the managed update/upgrade process has failed.
# This is used for all update/upgrade workflows if they fail to start or timeout after a pending restart notification has been shown.
display_string_failed_title="${display_string_workflow_title} Failed"
display_string_failed_body="${display_string_workflow_title} installation failed to complete.\n\nYou will be notified later when the installation is attempted again."
#### Language for dialog_insufficient_storage(), a dialog informing the user that there is insufficient storage for macOS update/upgrade.
# This is used for both non-deadline and deadline workflows.
display_string_insufficient_storage_title="Insufficient Storage For ${display_string_workflow_title}"
display_string_insufficient_storage_timeout="* Please remove unecessary items in"
display_string_insufficient_storage_default_body="A required macOS update needs ${storage_required_gigabytes} GBs of storage space and only ${storage_available_gigabytes} GBs is available.\n\nPlease use the storage settings (shown behind this notification) to remove unnecessary items."
display_string_insufficient_storage_deadline_schedule_body="A scheduled installation date of ${display_string_scheduled_install} has passed.\n\nA required macOS update needs ${storage_required_gigabytes} GBs of storage space and only ${storage_available_gigabytes} GBs is available.\n\nPlease use the storage settings (shown behind this notification) to remove unnecessary items."
display_string_insufficient_storage_deadline_count_body="You have deferred the maximum number of ${display_string_deadline_count_maximum} times.\n\nA required macOS update needs ${storage_required_gigabytes} GBs of storage space and only ${storage_available_gigabytes} GBs is available.\n\nPlease use the storage settings (shown behind this notification) to remove unnecessary items."
display_string_insufficient_storage_deadline_date_body="The deferment deadline of ${display_string_deadline} has passed.\n\nA required macOS update needs ${storage_required_gigabytes} GBs of storage space and only ${storage_available_gigabytes} GBs is available.\n\nPlease use the storage settings (shown behind this notification) to remove unnecessary items."
#### Language for dialog_power_required(), a dialog notification informing the user that they need to plug the computer into AC power.
# This is used for both non-deadline and deadline workflows.
display_string_power_required_title="${display_string_workflow_title} Requires Power Source"
display_string_power_required_timeout="* Please connect power supply in"
display_string_power_required_default_body="You must connect this computer to a power supply in order to install the required macOS update."
display_string_power_required_deadline_schedule_body="A scheduled installation date of ${display_string_scheduled_install} has passed.\n\nYou must connect this computer to a power supply in order to install the required macOS update."
display_string_power_required_deadline_count_body="You have deferred the maximum number of ${display_string_deadline_count_maximum} times.\n\nYou must connect this computer to a power supply in order to install the required macOS update."
display_string_power_required_deadline_date_body="The deferment deadline of ${display_string_deadline} has passed.\n\nYou must connect this computer to a power supply in order to install the required macOS update."
#### Language for dialog_user_choice(), an interactive dialog giving the user a choice to schedule, defer, or restart.
display_string_user_choice_restart_title="${display_string_workflow_title} Requires System Restart"
display_string_user_choice_install_title="${display_string_workflow_title} Requires Installation (No Restart)"
display_string_user_choice_timeout="* Please make selection in"
display_string_user_choice_menu_title="Defer software update for:"
display_string_user_choice_default_body="• No deadline date and unlimited deferrals.\n"
display_string_user_choice_date_body="• Deferral available until ${display_string_deadline}.\n"
display_string_user_choice_count_body="• ${display_string_deadline_count} out of ${display_string_deadline_count_maximum} deferrals remaining.\n"
display_string_user_choice_date_count_body="• Deferral available until ${display_string_deadline}.\n\n• ${display_string_deadline_count} out of ${display_string_deadline_count_maximum} deferrals remaining.\n"
#### Language for dialog_user_schedule(), an interactive dialog allowing the user to schedule an installation.
display_string_user_schedule_restart_title="${display_string_workflow_title} Scheduled System Restart"
display_string_user_schedule_install_title="${display_string_workflow_title} Scheduled Installation (No Restart)"
display_string_user_schedule_timeout="* Please make selection in"
display_string_user_schedule_calendar_title="Select schedule below:"
display_string_user_schedule_default_body="• Select a date and time to sechedule this event."
display_string_user_schedule_date_body="• Schedule available until ${display_string_deadline}.\n\n• Select a date and time to sechedule this event."
#### Language for dialog_schedule_reminder(), an interactive dialog that notifies the user of a pending scheduled installation and optionally allows for rescheduling.
display_string_schedule_reminder_restart_title="${display_string_workflow_title} Scheduled System Restart Reminder"
display_string_schedule_reminder_install_title="${display_string_workflow_title} Scheduled Installation (No Restart) Reminder"
display_string_schedule_reminder_restart_timeout="* Automatic restart in"
display_string_schedule_reminder_install_timeout="* Automatic installation in"
display_string_schedule_reminder_default_body="• This event is scheduled for ${display_string_scheduled_install}."
display_string_schedule_reminder_reschedule_date_body="• This event is scheduled for ${display_string_scheduled_install}\n\n• Reschedule available until ${display_string_deadline}."
display_string_schedule_reminder_adjusted_body="The date and time you selected was adjusted to coordinate with scheduling requirements.\n\n• This event is scheduled for ${display_string_scheduled_install}."
display_string_schedule_reminder_adjusted_reschedule_date_body="The date and time you selected was adjusted to coordinate with scheduling requirements.\n\n• This event is scheduled for ${display_string_scheduled_install}.\n\n• Reschedule available until ${display_string_deadline}."
#### Language for dialog_soft_deadline(), an interactive dialog when a soft deadline has passed, giving the user only one button to continue the workflow.
display_string_soft_deadline_restart_title="${display_string_workflow_title} Requires System Restart"
display_string_soft_deadline_install_title="${display_string_workflow_title} Requires Installation (No Restart)"
display_string_soft_deadline_restart_timeout="* Automatic restart in"
display_string_soft_deadline_install_timeout="* Automatic installation in"
display_string_soft_deadline_count_body="You have deferred the maximum number of ${display_string_deadline_count_maximum} times."
display_string_soft_deadline_date_body="The deferment deadline has passed:\n${display_string_deadline}."
#### Language for dialog_user_auth(), an interactive dialog to collect user credentials for macOS update/upgrade workflow
display_string_user_auth_title="${display_string_workflow_title} Requires Authentication"
display_string_user_auth_timeout="* Please enter password in"
display_string_user_auth_default_body="You must enter the password for user \"${current_user_real_name}\" to install ${display_string_workflow_title}.\n"
display_string_user_auth_download_body="You must enter the password for user \"${current_user_real_name}\" to download and prepare ${display_string_workflow_title}.\n"
display_string_user_auth_schedule_body="You must enter the password for user \"${current_user_real_name}\" to schedule the installation of ${display_string_workflow_title}.\n"
display_string_user_auth_deadline_count_body="You have deferred the maximum number of ${display_string_deadline_count_maximum} times.\n\nYou must enter the password for user \"${current_user_real_name}\" to install the ${display_string_workflow_title}.\n"
display_string_user_auth_deadline_date_body="The deferment deadline of ${display_string_deadline} has passed.\n\nYou must enter the password for user \"${current_user_real_name}\" to install the ${display_string_workflow_title}.\n"
display_string_user_auth_password_title="Enter Password Here:"
display_string_user_auth_password_placeholder="Password Required"
display_string_user_auth_retry_default_body="You must enter the password for user \"${current_user_real_name}\" to install ${display_string_workflow_title}.\n"
display_string_user_auth_retry_download_body="You must enter the password for user \"${current_user_real_name}\" to download and prepare ${display_string_workflow_title}.\n"
display_string_user_auth_retry_schedule_body="You must enter the password for user \"${current_user_real_name}\" to schedule the installation of ${display_string_workflow_title}.\n"
display_string_user_auth_retry_deadline_count_body="You have deferred the maximum number of ${display_string_deadline_count_maximum} times.\n\nYou must enter the password for user \"${current_user_real_name}\" to install the ${display_string_workflow_title}.\n"
display_string_user_auth_retry_deadline_date_body="The deferment deadline of ${display_string_deadline} has passed.\n\nYou must enter the password for user \"${current_user_real_name}\" to install the ${display_string_workflow_title}.\n"
display_string_user_auth_retry_password_title="Authentication Failed - Try Password Again:"
display_string_user_auth_retry_password_placeholder="Password Required"
#### The following code sets the appropriate ${display_icon} for macOS light or dark mode but it does not affect the display strings.
{ [[ "${current_user_appearance_mode}" == "LIGHT" ]] || [[ -z "${current_user_appearance_mode}" ]]; } && display_icon="${display_icon_light}"
[[ "${current_user_appearance_mode}" == "DARK" ]] && display_icon="${display_icon_dark}"
}
# Set the ${display_string_defer_button} based on the ${deferral_timer_minutes}.
# This also adjusts ${deferral_timer_minutes} given the ${schedule_workflow_active_option}.
set_deferral_button() {
# If needed adjust the ${deferral_timer_minutes} for the ${schedule_workflow_active_option} and also write to log.
if [[ -n "${schedule_workflow_active_option}" ]]; then
deferral_timer_minutes_schedule_workflow_active_check="${deferral_timer_minutes}"
set_schedule_workflow_active_adjustments
if [[ -n "${deferral_timer_minutes_schedule_workflow_active_adjusted}" ]]; then
deferral_timer_minutes="${deferral_timer_minutes_schedule_workflow_active_adjusted}"
else
log_super "Warning: The deferral timer is overriding the schedule workflow active option because no coordinating time frame could be resolved."
fi
unset deferral_timer_minutes_schedule_workflow_active_check
unset deferral_timer_minutes_schedule_workflow_active_adjusted
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: deferral_timer_minutes is: ${deferral_timer_minutes}"
fi
# Set ${display_string_defer_button} with text variations based on length.
local deferral_timer_epoch_temp
deferral_timer_epoch_temp=$((workflow_time_epoch + (deferral_timer_minutes * 60)))
local deferral_timer_days_away
deferral_timer_days_away=$(((deferral_timer_epoch_temp - $(date -v+0d -v0H -v0M -v0S +%s)) / 86400))
if [[ "${deferral_timer_minutes}" -lt 60 ]]; then
display_string_defer_button="${display_string_defer_today_button} ${deferral_timer_minutes} ${display_string_minutes}"
elif [[ "${deferral_timer_minutes}" -eq 60 ]]; then
display_string_defer_button="${display_string_defer_today_button} 1 ${display_string_hour}"
elif [[ "${deferral_timer_minutes}" -gt 60 ]] && [[ "${deferral_timer_minutes}" -lt 120 ]]; then
display_string_defer_button="${display_string_defer_today_button} 1 ${display_string_hour} ${display_string_and} $((deferral_timer_minutes - 60)) ${display_string_minutes}"
elif [[ "${deferral_timer_minutes}" -ge 120 ]] && [[ "${deferral_timer_minutes}" -lt 1440 ]] && [[ $deferral_timer_days_away -lt 1 ]]; then
[[ $((deferral_timer_minutes % 60)) -eq 0 ]] && display_string_defer_button="${display_string_defer_today_button} $((deferral_timer_minutes / 60)) ${display_string_hours}"
[[ $((deferral_timer_minutes % 60)) -ne 0 ]] && display_string_defer_button="${display_string_defer_today_button} $((deferral_timer_minutes / 60)) ${display_string_hours} ${display_string_and} $((deferral_timer_minutes % 60)) ${display_string_minutes}"
elif [[ $deferral_timer_days_away -eq 1 ]]; then
display_string_defer_button="${display_string_defer_tomorrow_button}"
else
display_string_defer_button="${display_string_defer_future_button} $(date -r "${deferral_timer_epoch_temp}" "+${DISPLAY_STRING_FORMAT_DATE}")"
fi
}
# Set the ${deferral_timer_menu_minutes_array[*]} and ${display_string_deferral_menu} based on the ${deferral_timer_menu_minutes}.
# This also adjusts ${deferral_timer_menu_minutes} given the ${schedule_workflow_active_option}.
set_deferral_menu() {
local previous_ifs
previous_ifs="${IFS}"
IFS=','
read -r -a deferral_timer_menu_minutes_array <<<"${deferral_timer_menu_minutes}"
# If needed adjust the ${deferral_timer_menu_minutes} for the ${schedule_workflow_active_option}.
if [[ -n "${schedule_workflow_active_option}" ]]; then
local deferral_time_menu_adjusted_array
deferral_time_menu_adjusted_array=()
local deferral_timer_menu_adjusted
deferral_timer_menu_adjusted="FALSE"
for deferral_timer_menu_item in "${deferral_timer_menu_minutes_array[@]}"; do
deferral_timer_menu_schedule_workflow_active_check="${deferral_timer_menu_item}"
set_schedule_workflow_active_adjustments
[[ -n "${deferral_timer_minutes_schedule_workflow_active_adjusted}" ]] && deferral_time_menu_adjusted_array+=("${deferral_timer_minutes_schedule_workflow_active_adjusted}")
deferral_timer_menu_adjusted="TRUE"
unset deferral_timer_menu_schedule_workflow_active_check
unset deferral_timer_minutes_schedule_workflow_active_adjusted
done
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: deferral_timer_menu_adjusted is: ${deferral_timer_menu_adjusted}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: deferral_time_menu_adjusted_array is: ${deferral_time_menu_adjusted_array[*]}"
if [[ "${deferral_timer_menu_adjusted}" == "TRUE" ]]; then
if [[ ${#deferral_time_menu_adjusted_array[@]} -gt 1 ]]; then
deferral_timer_menu_minutes=$(echo "${deferral_time_menu_adjusted_array[*]}" | tr ',' '\n' | uniq | tr '\n' ',' | sed -e 's/,$//')
log_super "Warning: The deferral timer menu list has been adjusted to ${deferral_timer_menu_minutes} minutes to coordinate with schedule workflow active time frames."
read -r -a deferral_timer_menu_minutes_array <<<"${deferral_timer_menu_minutes}"
else
unset deferral_timer_menu_minutes
unset deferral_timer_menu_minutes_array
log_super "Warning: Not showing the deferral timer menu because no coordinating time frames could be resolved."
fi
else
log_super "Warning: Deferral timer menu items are overriding the schedule workflow active option because no coordinating time frames could be resolved."
fi
fi
# If there is still a ${deferral_timer_menu_minutes} then set the ${display_string_deferral_menu} variations.
if [[ -n "${deferral_timer_menu_minutes}" ]]; then
local deferral_timer_menu_display_array
read -r -a deferral_timer_menu_display_array <<<"${deferral_timer_menu_minutes}"
local deferral_timer_epoch_temp
local deferral_timer_days_away
for array_index in "${!deferral_timer_menu_display_array[@]}"; do
deferral_timer_epoch_temp=$((workflow_time_epoch + (deferral_timer_menu_minutes_array[array_index] * 60)))
deferral_timer_days_away=$(((deferral_timer_epoch_temp - $(date -v+0d -v0H -v0M -v0S +%s)) / 86400))
if [[ "${deferral_timer_menu_minutes_array[array_index]}" -lt 60 ]]; then
deferral_timer_menu_display_array[array_index]="${display_string_defer_today_button} ${deferral_timer_menu_minutes_array[array_index]} ${display_string_minutes}"
elif [[ "${deferral_timer_menu_minutes_array[array_index]}" -eq 60 ]]; then
deferral_timer_menu_display_array[array_index]="${display_string_defer_today_button} 1 ${display_string_hour}"
elif [[ "${deferral_timer_menu_minutes_array[array_index]}" -gt 60 ]] && [[ "${deferral_timer_menu_minutes_array[array_index]}" -lt 120 ]]; then
deferral_timer_menu_display_array[array_index]="${display_string_defer_today_button} 1 ${display_string_hour} ${display_string_and} $((deferral_timer_menu_minutes_array[array_index] - 60)) ${display_string_minutes}"
elif [[ "${deferral_timer_menu_minutes_array[array_index]}" -ge 120 ]] && [[ "${deferral_timer_menu_minutes_array[array_index]}" -lt 1440 ]] && [[ $deferral_timer_days_away -lt 1 ]]; then
[[ $((deferral_timer_menu_minutes_array[array_index] % 60)) -eq 0 ]] && deferral_timer_menu_display_array[array_index]="${display_string_defer_today_button} $((deferral_timer_menu_minutes_array[array_index] / 60)) ${display_string_hours}"
[[ $((deferral_timer_menu_minutes_array[array_index] % 60)) -ne 0 ]] && deferral_timer_menu_display_array[array_index]="${display_string_defer_today_button} $((deferral_timer_menu_minutes_array[array_index] / 60)) ${display_string_hours} ${display_string_and} $((deferral_timer_menu_minutes_array[array_index] % 60)) ${display_string_minutes}"
elif [[ $deferral_timer_days_away -eq 1 ]]; then
deferral_timer_menu_display_array[array_index]="${display_string_defer_tomorrow_button}"
else
deferral_timer_menu_display_array[array_index]="${display_string_defer_future_button} $(date -r "${deferral_timer_epoch_temp}" "+${DISPLAY_STRING_FORMAT_DATE}")"
fi
done
IFS=$'\n'
display_string_deferral_menu="${deferral_timer_menu_display_array[*]}"
display_string_defer_button="${display_string_defer_today_button}"
fi
IFS="${previous_ifs}"
}
# Set the ${display_help_button_string} and ${display_warning_button_string} options for the ${ibm_notifier_array}.
set_display_strings_optional_buttons() {
local curl_response
if [[ -n "${display_help_button_string}" ]]; then
if [[ $(echo "${display_help_button_string}" | grep -c '^http://\|^https://\|^mailto:\|^jamfselfservice://') -gt 0 ]]; then
if [[ $(echo "${display_help_button_string_option}" | grep -c '^http://\|^https://') -gt 0 ]]; then
curl_response=$(curl -Is "${display_help_button_string_option}" | head -1)
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: curl_response is: ${curl_response}"
if [[ $(echo "${curl_response}" | grep -c '200') -gt 0 ]] || [[ $(echo "${curl_response}" | grep -c '302') -gt 0 ]]; then
ibm_notifier_array+=(-help_button_cta_type link -help_button_cta_payload "${display_help_button_string}")
else
log_super "Warning: Help button not shown because URL is unreachable: ${display_help_button_string}"
fi
else
ibm_notifier_array+=(-help_button_cta_type link -help_button_cta_payload "${display_help_button_string}")
fi
else
ibm_notifier_array+=(-help_button_cta_type infopopup -help_button_cta_payload "${display_help_button_string}")
fi
fi
if [[ -n "${display_warning_button_string}" ]]; then
if [[ $(echo "${display_warning_button_string}" | grep -c '^http://\|^https://\|^mailto:\|^jamfselfservice://') -gt 0 ]]; then
if [[ $(echo "${display_warning_button_string_option}" | grep -c '^http://\|^https://') -gt 0 ]]; then
curl_response=$(curl -Is "${display_warning_button_string_option}" | head -1)
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: curl_response is: ${curl_response}"
if [[ $(echo "${curl_response}" | grep -c '200') -gt 0 ]] || [[ $(echo "${curl_response}" | grep -c '302') -gt 0 ]]; then
ibm_notifier_array+=(-warning_button_cta_type link -warning_button_cta_payload "${display_warning_button_string}")
else
log_super "Warning: Warning button not shown because URL is unreachable: ${display_warning_button_string}"
fi
else
ibm_notifier_array+=(-warning_button_cta_type link -warning_button_cta_payload "${display_warning_button_string}")
fi
else
ibm_notifier_array+=(-warning_button_cta_type infopopup -warning_button_cta_payload "${display_warning_button_string}")
fi
fi
}
# Open ${IBM_NOTIFIER_BINARY} for non-interactive notifications using the ${ibm_notifier_array} options.
open_notification_ibm_notifier() {
# Append any additional display options to the ${ibm_notifier_array}.
set_display_strings_optional_buttons
{ [[ "${display_unmovable_status}" == "TRUE" ]] || [[ "${display_unmovable_status}" == "TEMP" ]]; } && ibm_notifier_array+=(-unmovable)
{ [[ "${display_hide_background_status}" == "TRUE" ]] || [[ "${display_hide_background_status}" == "TEMP" ]]; } && ibm_notifier_array+=(-background_panel translucent)
{ [[ "${display_silently_status}" == "TRUE" ]] || [[ "${display_silently_status}" == "TEMP" ]]; } && ibm_notifier_array+=(-silent)
if [[ "${display_notifications_centered_status}" == "TRUE" ]]; then
ibm_notifier_array+=(-position center -always_on_top -disable_quit)
else # The default notification location.
ibm_notifier_array+=(-position top_right -always_on_top -disable_quit)
fi
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: ibm_notifier_array is:\n${ibm_notifier_array[*]}"
# Kill any previous notifications so new ones can take its place.
killall -9 "IBM Notifier" "IBM Notifier Popup" >/dev/null 2>&1
# Start IBM Notifier and wait for for ${notification_response} and ${notification_return}.
unset notification_response
unset notification_return
notification_response=$("${IBM_NOTIFIER_BINARY}" "${ibm_notifier_array[@]}")
notification_return="$?"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: notification_response is: ${notification_response}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: notification_return is: ${notification_return}"
}
# Open ${IBM_NOTIFIER_BINARY} for interactive dialogs using the ${ibm_notifier_array} options, also handle any ${dialog_timeout_seconds}, ${display_accessory_content}, and ${display_accessory_payload} options, and set ${dialog_response} and ${dialog_return}.
open_dialog_ibm_notifier() {
# If needed acquire and validate the ${display_accessory_content} option.
local display_accessory_payload
local display_accessory_enabled
display_accessory_enabled="FALSE"
local curl_response
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: display_accessory_content is: ${display_accessory_content}"
if [[ -n "${display_accessory_content}" ]] && [[ "${dialog_type}" != "ALERT" ]] && [[ "${dialog_type}" != "SCHEDULE" ]]; then
if [[ $(echo "${display_accessory_content}" | grep -c '^http://\|^https://') -gt 0 ]]; then
if [[ "${display_accessory_type}" =~ ^TEXTBOX$|^HTMLBOX$|^HTML$ ]]; then
display_accessory_payload=$(curl -s -f "${display_accessory_content}")
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: display_accessory_payload is:\n${display_accessory_payload}"
if [[ -n "${display_accessory_payload}" ]]; then
display_accessory_enabled="TRUE"
else
log_super "Warning: Custom display accessory not shown because URL failed to download: ${display_accessory_content}"
fi
else # ${display_accessory_type} is IMAGE or VIDEO or VIDEOAUTO.
curl_response=$(curl -Is "${display_accessory_content}" | head -1)
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: curl_response is: ${curl_response}"
if [[ $(echo "${curl_response}" | grep -c '200') -gt 0 ]] || [[ $(echo "${curl_response}" | grep -c '302') -gt 0 ]]; then
display_accessory_payload="${display_accessory_content}"
display_accessory_enabled="TRUE"
else
log_super "Warning: Custom display accessory not shown because URL is unreachable: ${display_accessory_content}"
fi
fi
else # Assume it's a local path.
if [[ "${display_accessory_type}" =~ ^TEXTBOX$|^HTMLBOX$|^HTML$ ]]; then
display_accessory_payload=$(<"${display_accessory_content}")
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: display_accessory_payload is:\n${display_accessory_payload}"
if [[ -n "${display_accessory_payload}" ]]; then
display_accessory_enabled="TRUE"
else
log_super "Warning: Custom display accessory not shown because file path could not be read: ${display_accessory_content}"
fi
else # ${display_accessory_type} is IMAGE or VIDEO or VIDEOAUTO.
if [[ -f "${display_accessory_content}" ]]; then
display_accessory_payload="${display_accessory_content}"
display_accessory_enabled="TRUE"
else
log_super "Warning: Custom display accessory not shown because file path does not exist: ${display_accessory_content}"
fi
fi
fi
fi
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: display_accessory_enabled is: ${display_accessory_enabled}"
# Append any additional display accessory options to the ${ibm_notifier_array}.
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: dialog_type is: ${dialog_type}"
if [[ "${dialog_type}" == "ALERT" ]]; then
if [[ "${display_hide_progress_bar_status}" == "TRUE" ]] || [[ "${display_hide_progress_bar_status}" == "TEMP" ]]; then
ibm_notifier_array+=(-buttonless)
[[ -n "${dialog_timeout_seconds}" ]] && ibm_notifier_array+=(-accessory_view_type timer -accessory_view_payload "${display_string_dialog_timeout} %@" -timeout "${dialog_timeout_seconds}")
else # The default alert dialog progress bar.
[[ -z "${dialog_timeout_seconds}" ]] && ibm_notifier_array+=(-accessory_view_type progressbar -accessory_view_payload "/percent indeterminate")
[[ -n "${dialog_timeout_seconds}" ]] && ibm_notifier_array+=(-accessory_view_type timer -accessory_view_payload "${display_string_dialog_timeout} %@" -timeout "${dialog_timeout_seconds}" -secondary_accessory_view_type progressbar -secondary_accessory_view_payload "/percent indeterminate")
fi
elif [[ "${dialog_type}" == "SCHEDULE" ]]; then
ibm_notifier_array+=(-accessory_view_type datepicker -accessory_view_payload "${display_accessory_datepicker_payload}")
[[ -n "${dialog_timeout_seconds}" ]] && ibm_notifier_array+=(-secondary_accessory_view_type timer -secondary_accessory_view_payload "${display_string_dialog_timeout} %@" -timeout "${dialog_timeout_seconds}" )
else # Remaining ${dialog_type}s can take advantage of ${display_accessory_type}; CHOICE, REMINDER, SOFT, AUTH.
if [[ "${display_accessory_enabled}" == "TRUE" ]]; then # Custom display accessory enabled.
[[ "${display_accessory_type}" == "TEXTBOX" ]] && ibm_notifier_array+=(-accessory_view_type whitebox -accessory_view_payload "${display_accessory_payload}")
[[ "${display_accessory_type}" == "HTMLBOX" ]] && ibm_notifier_array+=(-accessory_view_type htmlwhitebox -accessory_view_payload "${display_accessory_payload}")
[[ "${display_accessory_type}" == "HTML" ]] && ibm_notifier_array+=(-accessory_view_type html -accessory_view_payload "${display_accessory_payload}")
[[ "${display_accessory_type}" == "IMAGE" ]] && ibm_notifier_array+=(-accessory_view_type image -accessory_view_payload "${display_accessory_payload}")
[[ "${display_accessory_type}" == "VIDEO" ]] && ibm_notifier_array+=(-accessory_view_type video -accessory_view_payload "/url ${display_accessory_payload}")
[[ "${display_accessory_type}" == "VIDEOAUTO" ]] && ibm_notifier_array+=(-accessory_view_type video -accessory_view_payload "/url ${display_accessory_payload} /autoplay")
if [[ "${dialog_type}" == "CHOICE" ]]; then
{ [[ -n "${deferral_timer_menu_minutes}" ]] && [[ -z "${dialog_timeout_seconds}" ]]; } && ibm_notifier_array+=(-secondary_accessory_view_type dropdown -secondary_accessory_view_payload "/title ${display_string_user_choice_menu_title} /list ${display_string_deferral_menu} /selected 0")
{ [[ -z "${deferral_timer_menu_minutes}" ]] && [[ -n "${dialog_timeout_seconds}" ]]; } && ibm_notifier_array+=(-secondary_accessory_view_type timer -secondary_accessory_view_payload "${display_string_dialog_timeout} %@" -timeout "${dialog_timeout_seconds}")
if [[ -n "${deferral_timer_menu_minutes}" ]] && [[ -n "${dialog_timeout_seconds}" ]]; then
log_super "Warning: Unable to show --dialog-timeout-* countdown due to the --display-accessory-* option. However, there is still a display timeout at ${dialog_timeout_seconds} seconds."
ibm_notifier_array+=(-secondary_accessory_view_type dropdown -secondary_accessory_view_payload "/title ${display_string_user_choice_menu_title} /list ${display_string_deferral_menu} /selected 0" -timeout "${dialog_timeout_seconds}")
fi
elif [[ "${dialog_type}" == "AUTH" ]]; then
[[ -z "${dialog_timeout_seconds}" ]] && ibm_notifier_array+=(-secondary_accessory_view_type secureinput -secondary_accessory_view_payload "${display_accessory_secure_payload}")
if [[ -n "${dialog_timeout_seconds}" ]]; then
log_super "Warning: Unable to show --dialog-timeout-* countdown due to the --display-accessory-* option. However, there is still a display timeout at ${dialog_timeout_seconds} seconds."
ibm_notifier_array+=(-secondary_accessory_view_type secureinput -secondary_accessory_view_payload "${display_accessory_secure_payload}" -timeout "${dialog_timeout_seconds}")
fi
else # Remaining ${dialog_type}s; REMINDER, SOFT.
[[ -n "${dialog_timeout_seconds}" ]] && ibm_notifier_array+=(-secondary_accessory_view_type timer -accessory_view_payload "${display_string_dialog_timeout} %@" -timeout "${dialog_timeout_seconds}")
fi
else # No custom display accessory.
if [[ "${dialog_type}" == "CHOICE" ]]; then
{ [[ -n "${deferral_timer_menu_minutes}" ]] && [[ -z "${dialog_timeout_seconds}" ]]; } && ibm_notifier_array+=(-accessory_view_type dropdown -accessory_view_payload "/title ${display_string_user_choice_menu_title} /list ${display_string_deferral_menu} /selected 0")
{ [[ -z "${deferral_timer_menu_minutes}" ]] && [[ -n "${dialog_timeout_seconds}" ]]; } && ibm_notifier_array+=(-accessory_view_type timer -accessory_view_payload "${display_string_dialog_timeout} %@" -timeout "${dialog_timeout_seconds}")
{ [[ -n "${dialog_timeout_seconds}" ]] && [[ -n "${deferral_timer_menu_minutes}" ]]; } && ibm_notifier_array+=(-accessory_view_type dropdown -accessory_view_payload "/title ${display_string_user_choice_menu_title} /list ${display_string_deferral_menu} /selected 0" -secondary_accessory_view_type timer -secondary_accessory_view_payload "${display_string_dialog_timeout} %@" -timeout "${dialog_timeout_seconds}")
elif [[ "${dialog_type}" == "AUTH" ]]; then
ibm_notifier_array+=(-accessory_view_type secureinput -accessory_view_payload "${display_accessory_secure_payload}")
[[ -n "${dialog_timeout_seconds}" ]] && ibm_notifier_array+=(-secondary_accessory_view_type timer -secondary_accessory_view_payload "${display_string_dialog_timeout} %@" -timeout "${dialog_timeout_seconds}" )
else # Remaining ${dialog_type}s; REMINDER, SOFT.
[[ -n "${dialog_timeout_seconds}" ]] && ibm_notifier_array+=(-accessory_view_type timer -accessory_view_payload "${display_string_dialog_timeout} %@" -timeout "${dialog_timeout_seconds}")
fi
fi
fi
set_display_strings_optional_buttons
{ [[ "${display_unmovable_status}" == "TRUE" ]] || [[ "${display_unmovable_status}" == "TEMP" ]]; } && ibm_notifier_array+=(-unmovable)
{ [[ "${display_hide_background_status}" == "TRUE" ]] || [[ "${display_hide_background_status}" == "TEMP" ]]; } && ibm_notifier_array+=(-background_panel translucent)
{ [[ "${display_silently_status}" == "TRUE" ]] || [[ "${display_silently_status}" == "TEMP" ]]; } && ibm_notifier_array+=(-silent)
ibm_notifier_array+=(-position center -always_on_top -disable_quit)
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: ibm_notifier_array is:\n${ibm_notifier_array[*]}"
# Kill any previous notifications so new ones can take its place.
killall -9 "IBM Notifier" "IBM Notifier Popup" >/dev/null 2>&1
# Start IBM Notifier and wait for ${dialog_response} and ${dialog_return}.
unset dialog_response
unset dialog_return
dialog_response=$("${IBM_NOTIFIER_BINARY}" "${ibm_notifier_array[@]}")
dialog_return="$?"
{ [[ "${verbose_mode_option}" == "TRUE" ]] && [[ "${dialog_type}" != "AUTH" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: dialog_response is: ${dialog_response}"
{ [[ "${verbose_mode_option}" == "TRUE" ]] && [[ "${dialog_type}" == "AUTH" ]]; } && log_echo "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: dialog_response is: ${dialog_response}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: dialog_return is: ${dialog_return}"
}
# MARK: *** Install Now Notifications ***
################################################################################
# Display a non-interactive notification informing the user that the install now workflow has started.
notification_install_now_start() {
set_display_strings_language
log_super "IBM Notifier: Install now start notification."
ibm_notifier_array=(-type popup -bar_title "${display_string_install_now_start_title}" -subtitle "${display_string_install_now_start_body}" -icon_path "${display_icon}" -icon_width "${display_icon_size}" -icon_height "${display_icon_size}")
# Handle the ${display_hide_progress_bar_status} option.
if [[ "${display_hide_progress_bar_status}" == "TRUE" ]] || [[ "${display_hide_progress_bar_status}" == "TEMP" ]]; then
ibm_notifier_array+=(-buttonless)
else # The default notification progress bar.
ibm_notifier_array+=(-accessory_view_type progressbar -accessory_view_payload "/percent indeterminate")
fi
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: ibm_notifier_array is:\n${ibm_notifier_array[*]}"
open_notification_ibm_notifier &
}
# Display a non-interactive notification informing the user that the install now workflow is downloading the macOS update/upgrade.
notification_install_now_download() {
set_display_strings_language
log_super "IBM Notifier: Install now downloading notification."
ibm_notifier_array=(-type popup -bar_title "${display_string_install_now_download_title}" -subtitle "${display_string_install_now_download_body}" -icon_path "${display_icon}" -icon_width "${display_icon_size}" -icon_height "${display_icon_size}")
# Handle the ${display_hide_progress_bar_status} option.
if [[ "${display_hide_progress_bar_status}" == "TRUE" ]] || [[ "${display_hide_progress_bar_status}" == "TEMP" ]]; then
ibm_notifier_array+=(-buttonless)
else # The default notification progress bar.
ibm_notifier_array+=(-accessory_view_type progressbar -accessory_view_payload "/percent indeterminate")
fi
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: ibm_notifier_array is:\n${ibm_notifier_array[*]}"
open_notification_ibm_notifier &
}
# Display a non-interactive notification informing the user that macOS is up to date.
notification_install_now_up_to_date() {
set_display_strings_language
unset display_hide_progress_bar_status
log_super "IBM Notifier: Install now macOS software is already up to date notification."
ibm_notifier_array=(-type popup -bar_title "${display_string_install_now_up_to_date_title}" -subtitle "${display_string_install_now_up_to_date_body}" -icon_path "${display_icon}" -icon_width "${display_icon_size}" -icon_height "${display_icon_size}" -main_button_label "${display_string_ok_button}")
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: ibm_notifier_array is:\n${ibm_notifier_array[*]}"
open_notification_ibm_notifier &
disown -a
}
# Display a non-interactive notification informing the user that the install now workflow has failed.
notification_install_now_failed() {
set_display_strings_language
unset display_hide_progress_bar_status
log_super "IBM Notifier: Install now failure notification."
ibm_notifier_array=(-type popup -bar_title "${display_string_install_now_failed_title}" -subtitle "${display_string_install_now_failed_body}" -icon_path "${display_icon}" -icon_width "${display_icon_size}" -icon_height "${display_icon_size}" -main_button_label "${display_string_ok_button}")
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: ibm_notifier_array is:\n${ibm_notifier_array[*]}"
open_notification_ibm_notifier &
disown -a
}
# MARK: *** Default Notifications ***
################################################################################
# Display a non-interactive notification informing the user that a Jamf Pro Policy has started.
notification_jamf_pro_policy() {
set_display_strings_language
# The initial ${ibm_notifier_array} settings for the preparing update notification.
ibm_notifier_array=(-type popup -bar_title "${display_string_jamf_pro_policy_title}" -icon_path "${display_icon}" -icon_width "${display_icon_size}" -icon_height "${display_icon_size}")
# Variations for the main body text of the preparing update notification.
if [[ "${workflow_scheduled_install_now}" == "TRUE" ]]; then
log_super "IBM Notifier: Running Jamf Pro Policy secheduled installation notification."
ibm_notifier_array+=(-subtitle "${display_string_jamf_pro_policy_deadline_schedule_body}")
elif [[ "${deadline_days_status}" == "SOFT" ]] || [[ "${deadline_days_status}" == "HARD" ]] || [[ "${deadline_date_status}" == "SOFT" ]] || [[ "${deadline_date_status}" == "HARD" ]]; then
log_super "IBM Notifier: Running Jamf Pro Policy deadline date notification."
ibm_notifier_array+=(-subtitle "${display_string_jamf_pro_policy_deadline_date_body}")
elif [[ "${deadline_count_status}" == "SOFT" ]] || [[ "${deadline_count_status}" == "HARD" ]]; then
log_super "IBM Notifier: Running Jamf Pro Policy deadline count notification."
ibm_notifier_array+=(-subtitle "${display_string_jamf_pro_policy_deadline_count_body}")
else # No deadlines, this is the default preparing update notification.
log_super "IBM Notifier: Running Jamf Pro Policy default notification."
ibm_notifier_array+=(-subtitle "${display_string_jamf_pro_policy_default_body}")
fi
# Handle the ${display_hide_progress_bar_status} option.
if [[ "${display_hide_progress_bar_status}" == "TRUE" ]] || [[ "${display_hide_progress_bar_status}" == "TEMP" ]]; then
ibm_notifier_array+=(-buttonless)
else # The default notification progress bar.
ibm_notifier_array+=(-accessory_view_type progressbar -accessory_view_payload "/percent indeterminate")
fi
# Open notification in the background allowing super to continue.
open_notification_ibm_notifier &
}
# Display a non-interactive notification informing the user that an update/upgrade that requires preparation has started.
notification_prepare() {
set_display_strings_language
# The initial ${ibm_notifier_array} settings for the preparing update notification.
ibm_notifier_array=(-type popup -bar_title "${display_string_prepare_title}" -icon_path "${display_icon}" -icon_width "${display_icon_size}" -icon_height "${display_icon_size}")
# Variations for the main body text of the preparing update notification.
if [[ "${workflow_scheduled_install_now}" == "TRUE" ]]; then
log_super "IBM Notifier: Preparing update/upgrade secheduled installation notification showing a ${display_string_prepare_time_estimate} minute estimate."
ibm_notifier_array+=(-subtitle "${display_string_prepare_deadline_schedule_body}")
elif [[ "${deadline_days_status}" == "SOFT" ]] || [[ "${deadline_days_status}" == "HARD" ]] || [[ "${deadline_date_status}" == "SOFT" ]] || [[ "${deadline_date_status}" == "HARD" ]]; then
log_super "IBM Notifier: Preparing update/upgrade deadline date notification showing a ${display_string_prepare_time_estimate} minute estimate."
ibm_notifier_array+=(-subtitle "${display_string_prepare_deadline_date_body}")
elif [[ "${deadline_count_status}" == "SOFT" ]] || [[ "${deadline_count_status}" == "HARD" ]]; then
log_super "IBM Notifier: Preparing update/upgrade deadline count notification showing a ${display_string_prepare_time_estimate} minute estimate."
ibm_notifier_array+=(-subtitle "${display_string_prepare_deadline_count_body}")
else # No deadlines, this is the default preparing update notification.
log_super "IBM Notifier: Preparing update/upgrade default notification showing a ${display_string_prepare_time_estimate} minute estimate."
ibm_notifier_array+=(-subtitle "${display_string_prepare_default_body}")
fi
# Handle the ${display_hide_progress_bar_status} option.
if [[ "${display_hide_progress_bar_status}" == "TRUE" ]] || [[ "${display_hide_progress_bar_status}" == "TEMP" ]]; then
ibm_notifier_array+=(-buttonless)
else # The default notification progress bar.
ibm_notifier_array+=(-accessory_view_type progressbar -accessory_view_payload "/percent indeterminate")
fi
# Open notification in the background allowing super to continue.
open_notification_ibm_notifier &
}
# Display a non-interactive notification informing the user that the computer going to restart soon.
notification_restart() {
set_display_strings_language
# The initial ${ibm_notifier_array} settings for the restart notification.
ibm_notifier_array=(-type popup -bar_title "${display_string_restart_title}" -icon_path "${display_icon}" -icon_width "${display_icon_size}" -icon_height "${display_icon_size}")
# Variations for the main body text of the restart notification.
if [[ "${workflow_scheduled_install_now}" == "TRUE" ]]; then
log_super "IBM Notifier: Restart secheduled installation notification."
ibm_notifier_array+=(-subtitle "${display_string_restart_deadline_schedule_body}")
elif [[ "${deadline_days_status}" == "SOFT" ]] || [[ "${deadline_days_status}" == "HARD" ]] || [[ "${deadline_date_status}" == "SOFT" ]] || [[ "${deadline_date_status}" == "HARD" ]]; then
log_super "IBM Notifier: Restart deadline date notification."
ibm_notifier_array+=(-subtitle "${display_string_restart_deadline_date_body}")
elif [[ "${deadline_count_status}" == "SOFT" ]] || [[ "${deadline_count_status}" == "HARD" ]]; then
log_super "IBM Notifier: Restart deadline count notification."
ibm_notifier_array+=(-subtitle "${display_string_restart_deadline_count_body}")
else # No deadlines, this is the default restart notification.
log_super "IBM Notifier: Restart default notification."
ibm_notifier_array+=(-subtitle "${display_string_restart_default_body}")
fi
# Handle the ${display_hide_progress_bar_status} option.
if [[ "${display_hide_progress_bar_status}" == "TRUE" ]] || [[ "${display_hide_progress_bar_status}" == "TEMP" ]]; then
ibm_notifier_array+=(-buttonless)
else # The default notification progress bar.
ibm_notifier_array+=(-accessory_view_type progressbar -accessory_view_payload "/percent indeterminate")
fi
# Open notification in the background allowing super to continue.
open_notification_ibm_notifier &
}
# Display a non-interactive notification informing the user that the install now workflow is installing non-system macOS software updates.
notification_non_system_updates() {
set_display_strings_language
log_super "IBM Notifier: Installing non-system macOS software updates notification."
ibm_notifier_array=(-type popup -bar_title "${display_string_non_system_updates_title}" -subtitle "${display_string_non_system_updates_body}" -icon_path "${display_icon}" -icon_width "${display_icon_size}" -icon_height "${display_icon_size}")
# Handle the ${display_hide_progress_bar_status} option.
if [[ "${display_hide_progress_bar_status}" == "TRUE" ]] || [[ "${display_hide_progress_bar_status}" == "TEMP" ]]; then
ibm_notifier_array+=(-buttonless)
else # The default notification progress bar.
ibm_notifier_array+=(-accessory_view_type progressbar -accessory_view_payload "/percent indeterminate")
fi
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: ibm_notifier_array is:\n${ibm_notifier_array[*]}"
open_notification_ibm_notifier &
}
# Display a non-interactive notification informing the user that update process has failed.
notification_failed() {
set_display_strings_language
set_deferral_button
unset display_hide_progress_bar_status
log_super "IBM Notifier: Failure notification."
ibm_notifier_array=(-type popup -bar_title "${display_string_failed_title}" -subtitle "${display_string_failed_body}" -icon_path "${display_icon}" -icon_width "${display_icon_size}" -icon_height "${display_icon_size}" -main_button_label "${display_string_defer_button}")
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: ibm_notifier_array is:\n${ibm_notifier_array[*]}"
open_notification_ibm_notifier &
disown -a
}
# MARK: *** Interactive Dialogs ***
################################################################################
# Display a dialog informing the user there is insufficient storage for a macOS update/upgrade.
dialog_insufficient_storage() {
dialog_insufficient_storage_error="FALSE"
log_super "Warning: Current available storage is at ${storage_available_gigabytes} GBs which is below the ${storage_required_gigabytes} GBs that is required for macOS update/upgrade workflow."
log_status "Running: Dialog insufficient storage."
set_display_strings_language
unset dialog_timeout_seconds
[[ -n "${dialog_timeout_insufficient_storage_seconds}" ]] && dialog_timeout_seconds="${dialog_timeout_insufficient_storage_seconds}"
{ [[ -z "${dialog_timeout_insufficient_storage_seconds}" ]] && [[ -n "${dialog_timeout_default_seconds}" ]]; } && dialog_timeout_seconds="${dialog_timeout_default_seconds}"
# The initial ${ibm_notifier_array} settings for the insufficient storage dialog.
ibm_notifier_array=(-type popup -bar_title "${display_string_insufficient_storage_title}" -icon_path "${display_icon}" -icon_width "${display_icon_size}" -icon_height "${display_icon_size}")
# Variations for the main body text of the insufficient storage dialog.
if [[ "${workflow_scheduled_install_now}" == "TRUE" ]]; then
[[ -n "${dialog_timeout_seconds}" ]] && log_super "IBM Notifier: Insufficient storage secheduled installation dialog with a ${dialog_timeout_seconds} second timeout."
[[ -z "${dialog_timeout_seconds}" ]] && log_super "IBM Notifier: Insufficient storage secheduled installation dialog with no timeout."
ibm_notifier_array+=(-subtitle "${display_string_insufficient_storage_deadline_schedule_body}")
elif [[ "${deadline_days_status}" == "SOFT" ]] || [[ "${deadline_days_status}" == "HARD" ]] || [[ "${deadline_date_status}" == "SOFT" ]] || [[ "${deadline_date_status}" == "HARD" ]]; then
[[ -n "${dialog_timeout_seconds}" ]] && log_super "IBM Notifier: Insufficient storage deadline date dialog with a ${dialog_timeout_seconds} second timeout."
[[ -z "${dialog_timeout_seconds}" ]] && log_super "IBM Notifier: Insufficient storage deadline date dialog with no timeout."
ibm_notifier_array+=(-subtitle "${display_string_insufficient_storage_deadline_date_body}")
elif [[ "${deadline_count_status}" == "SOFT" ]] || [[ "${deadline_count_status}" == "HARD" ]]; then
[[ -n "${dialog_timeout_seconds}" ]] && log_super "IBM Notifier: Insufficient storage deadline count dialog with a ${dialog_timeout_seconds} second timeout."
[[ -z "${dialog_timeout_seconds}" ]] && log_super "IBM Notifier: Insufficient storage deadline count dialog with no timeout."
ibm_notifier_array+=(-subtitle "${display_string_insufficient_storage_deadline_count_body}")
else # No deadlines, this is the default insufficient storage dialog.
[[ -n "${dialog_timeout_seconds}" ]] && log_super "IBM Notifier: Insufficient storage default dialog with a ${dialog_timeout_seconds} second timeout."
[[ -z "${dialog_timeout_seconds}" ]] && log_super "IBM Notifier: Insufficient storage default dialog with no timeout."
ibm_notifier_array+=(-subtitle "${display_string_insufficient_storage_default_body}")
fi
[[ -n "${dialog_timeout_seconds}" ]] && display_string_dialog_timeout="${display_string_insufficient_storage_timeout}"
# Open the appropriate storage assistant and the insufficient storage dialog.
if [[ "${macos_version_major}" -ge 13 ]]; then
log_super "Status: Opening the Storage pane of the System Settings.app."
sudo -u "${current_user_account_name}" open "x-apple.systempreferences:com.apple.settings.Storage" &
else
log_super "Status: Opening the Storage Management.app."
sudo -u "${current_user_account_name}" open "/System/Library/CoreServices/Applications/Storage Management.app" &
fi
# Manage ${display_unmovable_option}, ${display_hide_background_option}, ${display_silently_option}, and ${display_hide_progress_bar_option}.
{ [[ "${display_unmovable_status}" != "TRUE" ]] && [[ $(echo "${display_unmovable_option}" | grep -c 'ERROR') -gt 0 ]]; } && display_unmovable_status="TEMP"
[[ "${display_hide_background_status}" == "TRUE" ]] && display_hide_background_status="TEMPOFF" # Don't want to ever hide the background for this one so the user can try to use the storage optimizer.
{ [[ "${display_silently_status}" != "TRUE" ]] && [[ $(echo "${display_silently_option}" | grep -c 'ERROR') -gt 0 ]]; } && display_silently_status="TEMP"
{ [[ "${display_hide_progress_bar_status}" != "TRUE" ]] && [[ $(echo "${display_hide_progress_bar_option}" | grep -c 'ERROR') -gt 0 ]]; } && display_hide_progress_bar_status="TEMP"
# Open the initial insufficient storage dialog.
dialog_type="ALERT"
open_dialog_ibm_notifier &
unset dialog_type
# This handles waiting for available storage along with the ${dialog_timeout_seconds} option.
local dialog_insufficient_storage_check_timeout
[[ -n "${dialog_timeout_seconds}" ]] && dialog_insufficient_storage_check_timeout="${dialog_timeout_seconds}"
[[ -z "${dialog_timeout_seconds}" ]] && dialog_insufficient_storage_check_timeout=1
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: STORAGE_REQUIRED_RECHECK_SECONDS is: ${STORAGE_REQUIRED_RECHECK_SECONDS}"
while [[ "${dialog_insufficient_storage_check_timeout}" -ge 0 ]]; do
sleep $STORAGE_REQUIRED_RECHECK_SECONDS
check_storage_available
if [[ "${check_storage_available_error}" == "FALSE" ]]; then
if [[ "${storage_ready}" == "TRUE" ]]; then
log_super "Status: Current available storage is now at ${storage_available_gigabytes} GBs, the macOS update/upgrade workflow can continue."
killall -9 "IBM Notifier" "IBM Notifier Popup" >/dev/null 2>&1
break
fi
else # "${check_storage_available_error}" == "FALSE"
dialog_insufficient_storage_error="TRUE"
break
fi
[[ -n "${dialog_timeout_seconds}" ]] && dialog_insufficient_storage_check_timeout=$((dialog_insufficient_storage_check_timeout - STORAGE_REQUIRED_RECHECK_SECONDS))
done
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: dialog_insufficient_storage_error is: ${dialog_insufficient_storage_error}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: storage_ready is: ${storage_ready}"
# Reset temporary ${display_unmovable_option}, ${display_hide_background_option}, ${display_silently_option}, and ${display_hide_progress_bar_option}.
[[ "${display_unmovable_status}" == "TEMP" ]] && unset display_unmovable_status
[[ "${display_hide_background_status}" == "TEMPOFF" ]] && unset display_hide_background_status
[[ "${display_silently_status}" == "TEMP" ]] && unset display_silently_status
[[ "${display_hide_progress_bar_status}" == "TEMP" ]] && unset display_hide_progress_bar_status
# If there still is not sufficient storage, then error.
if [[ "${dialog_insufficient_storage_error}" == "FALSE" ]] && [[ "${storage_ready}" == "FALSE" ]]; then
[[ -n "${dialog_timeout_seconds}" ]] && log_super "Error: Waiting for user to make more storage available timed out after ${dialog_timeout_seconds} seconds."
dialog_insufficient_storage_error="TRUE"
fi
[[ "${display_hide_background_status}" == "TEMPOFF" ]] && display_hide_background_status="TRUE"
}
# Display a dialog informing the user they need to plug the computer into AC power.
dialog_power_required() {
local power_required_charger_connected
power_required_charger_connected="FALSE"
dialog_power_required_error="FALSE"
log_super "Warning: Current battery level is at ${power_battery_percent}% which is below the minimum required level of ${power_required_battery_percent}%."
log_status "Running: Dialog power required."
set_display_strings_language
unset dialog_timeout_seconds
[[ -n "${dialog_timeout_power_required_seconds}" ]] && dialog_timeout_seconds="${dialog_timeout_power_required_seconds}"
{ [[ -z "${dialog_timeout_power_required_seconds}" ]] && [[ -n "${dialog_timeout_default_seconds}" ]]; } && dialog_timeout_seconds="${dialog_timeout_default_seconds}"
# The initial ${ibm_notifier_array} settings for the power required dialog.
ibm_notifier_array=(-type popup -bar_title "${display_string_power_required_title}" -icon_path "${display_icon}" -icon_width "${display_icon_size}" -icon_height "${display_icon_size}")
# Variations for the main body text of the power required dialog.
if [[ "${workflow_scheduled_install_now}" == "TRUE" ]]; then
[[ -n "${dialog_timeout_seconds}" ]] && log_super "IBM Notifier: Power required secheduled installation dialog with a ${dialog_timeout_seconds} second timeout."
[[ -z "${dialog_timeout_seconds}" ]] && log_super "IBM Notifier: Power required secheduled installation dialog with no timeout."
ibm_notifier_array+=(-subtitle "${display_string_power_required_deadline_schedule_body}")
elif [[ "${deadline_days_status}" == "SOFT" ]] || [[ "${deadline_days_status}" == "HARD" ]] || [[ "${deadline_date_status}" == "SOFT" ]] || [[ "${deadline_date_status}" == "HARD" ]]; then
[[ -n "${dialog_timeout_seconds}" ]] && log_super "IBM Notifier: Power required deadline date dialog with a ${dialog_timeout_seconds} second timeout."
[[ -z "${dialog_timeout_seconds}" ]] && log_super "IBM Notifier: Power required deadline date dialog with no timeout."
ibm_notifier_array+=(-subtitle "${display_string_power_required_deadline_date_body}")
elif [[ "${deadline_count_status}" == "SOFT" ]] || [[ "${deadline_count_status}" == "HARD" ]]; then
[[ -n "${dialog_timeout_seconds}" ]] && log_super "IBM Notifier: Power required deadline count dialog with a ${dialog_timeout_seconds} second timeout."
[[ -z "${dialog_timeout_seconds}" ]] && log_super "IBM Notifier: Power required deadline count dialog with no timeout."
ibm_notifier_array+=(-subtitle "${display_string_power_required_deadline_count_body}")
else # No deadlines, this is the default power required dialog.
[[ -n "${dialog_timeout_seconds}" ]] && log_super "IBM Notifier: Power required default dialog with a ${dialog_timeout_seconds} second timeout."
[[ -z "${dialog_timeout_seconds}" ]] && log_super "IBM Notifier: Power required default dialog with no timeout."
ibm_notifier_array+=(-subtitle "${display_string_power_required_default_body}")
fi
[[ -n "${dialog_timeout_seconds}" ]] && display_string_dialog_timeout="${display_string_power_required_timeout}"
# Manage ${display_unmovable_option}, ${display_hide_background_option}, ${display_silently_option}, and ${display_hide_progress_bar_option}.
{ [[ "${display_unmovable_status}" != "TRUE" ]] && [[ $(echo "${display_unmovable_option}" | grep -c 'ERROR') -gt 0 ]]; } && display_unmovable_status="TEMP"
{ [[ "${display_hide_background_status}" != "TRUE" ]] && [[ $(echo "${display_hide_background_option}" | grep -c 'ERROR') -gt 0 ]]; } && display_hide_background_status="TEMP"
{ [[ "${display_silently_status}" != "TRUE" ]] && [[ $(echo "${display_silently_option}" | grep -c 'ERROR') -gt 0 ]]; } && display_silently_status="TEMP"
{ [[ "${display_hide_progress_bar_status}" != "TRUE" ]] && [[ $(echo "${display_hide_progress_bar_option}" | grep -c 'ERROR') -gt 0 ]]; } && display_hide_progress_bar_status="TEMP"
# Open the initial power required dialog.
dialog_type="ALERT"
open_dialog_ibm_notifier &
unset dialog_type
# This handles waiting for AC power along with the ${dialog_timeout_power_required_seconds} option.
local dialog_power_required_check_timeout
[[ -n "${dialog_timeout_seconds}" ]] && dialog_power_required_check_timeout="${dialog_timeout_seconds}"
[[ -z "${dialog_timeout_seconds}" ]] && dialog_power_required_check_timeout=1
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: POWER_REQUIRED_RECHECK_SECONDS is: ${POWER_REQUIRED_RECHECK_SECONDS}"
while [[ "${dialog_power_required_check_timeout}" -ge 0 ]]; do
sleep $POWER_REQUIRED_RECHECK_SECONDS
[[ $(pmset -g ps | grep -ic 'AC Power') -ne 0 ]] && power_required_charger_connected="TRUE"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: power_required_charger_connected: ${power_required_charger_connected}"
if [[ "${power_required_charger_connected}" == "TRUE" ]]; then
log_super "Status: AC power detected, the macOS update/upgrade workflow can continue."
killall -9 "IBM Notifier" "IBM Notifier Popup" >/dev/null 2>&1
break
fi
[[ -n "${dialog_timeout_seconds}" ]] && dialog_power_required_check_timeout=$((dialog_power_required_check_timeout - POWER_REQUIRED_RECHECK_SECONDS))
done
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: power_required_charger_connected is: ${power_required_charger_connected}"
# Reset temporary ${display_unmovable_option}, ${display_hide_background_option}, ${display_silently_option}, and ${display_hide_progress_bar_option}.
[[ "${display_unmovable_status}" == "TEMP" ]] && unset display_unmovable_status
[[ "${display_hide_background_status}" == "TEMP" ]] && unset display_hide_background_status
[[ "${display_silently_status}" == "TEMP" ]] && unset display_silently_status
[[ "${display_hide_progress_bar_status}" == "TEMP" ]] && unset display_hide_progress_bar_status
# If there still is no AC power, then exit.
if [[ "${power_required_charger_connected}" == "FALSE" ]]; then
[[ -n "${dialog_timeout_seconds}" ]] && log_super "Error: Waiting for user to connect AC power timed out after ${dialog_timeout_seconds} seconds."
dialog_power_required_error="TRUE"
fi
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: dialog_power_required_error is: ${dialog_power_required_error}"
}
# Display an interactive dialog with schedule, defer, and restart options. This sets ${dialog_user_choice_return} and if ${deferral_timer_menu_minutes} then also sets ${deferral_timer_minutes}.
dialog_user_choice() {
set_display_strings_language
log_status "Running: Dialog user choice."
unset dialog_timeout_seconds
[[ -n "${dialog_timeout_user_choice_seconds}" ]] && dialog_timeout_seconds="${dialog_timeout_user_choice_seconds}"
{ [[ -z "${dialog_timeout_user_choice_seconds}" ]] && [[ -n "${dialog_timeout_default_seconds}" ]]; } && dialog_timeout_seconds="${dialog_timeout_default_seconds}"
[[ -n "${dialog_timeout_seconds}" ]] && log_super "IBM Notifier: User choice dialog with a ${dialog_timeout_seconds} second timeout."
[[ -z "${dialog_timeout_seconds}" ]] && log_super "IBM Notifier: User choice dialog with no timeout."
# Set the user choice title and button display strings.
local display_string_user_choice_title
local dialog_user_choice_now_button
local dialog_user_choice_schedule_button
if [[ "${workflow_target}" == "Non-system Software Updates" ]] || [[ "${workflow_target}" == "Jamf Pro Policy Triggers Without Restarting" ]]; then
display_string_user_choice_title="${display_string_user_choice_install_title}"
dialog_user_choice_now_button="${display_string_install_now_button}"
dialog_user_choice_schedule_button="${display_string_schedule_install_button}"
else # Default restart workflows.
display_string_user_choice_title="${display_string_user_choice_restart_title}"
dialog_user_choice_now_button="${display_string_restart_now_button}"
dialog_user_choice_schedule_button="${display_string_schedule_restart_button}"
fi
# Create initial ${ibm_notifier_array} settings for the dialog.
ibm_notifier_array=(-type popup -bar_title "${display_string_user_choice_title}" -icon_path "${display_icon}" -icon_width "${display_icon_size}" -icon_height "${display_icon_size}")
# Body text variations based on deadline options.
if [[ -n "${display_string_deadline}" ]] && [[ -n "${display_string_deadline_count}" ]]; then # Show both date and maximum deferral count deadlines.
ibm_notifier_array+=(-subtitle "${display_string_user_choice_date_count_body}")
elif [[ -n "${display_string_deadline}" ]]; then # Show only date deadline.
ibm_notifier_array+=(-subtitle "${display_string_user_choice_date_body}")
elif [[ -n "${display_string_deadline_count}" ]]; then # Show only maximum deferral count deadline.
ibm_notifier_array+=(-subtitle "${display_string_user_choice_count_body}")
else # Show no deadlines.
ibm_notifier_array+=(-subtitle "${display_string_user_choice_default_body}")
fi
[[ -n "${dialog_timeout_seconds}" ]] && display_string_dialog_timeout="${display_string_user_choice_timeout}"
# Display either the deferral menu or just the standard deferral button.
[[ -n "${deferral_timer_menu_minutes}" ]] && set_deferral_menu
[[ -z "${display_string_deferral_menu}" ]] && set_deferral_button
# Provide logging for workflow conditions related to the ${scheduled_install_user_choice_option}.
if [[ "${scheduled_install_user_choice_option}" == "TRUE" ]] && { [[ "${workflow_target}" != "Non-system Software Updates" ]] && [[ "${workflow_target}" != "Jamf Pro Policy Triggers Without Restarting" ]] && [[ "${workflow_target}" != "Jamf Pro Policy Triggers With Restart" ]] && [[ "${workflow_target}" != "Restart Without Updates" ]]; } && { [[ "${workflow_auth_error}" == "TRUE" ]] || [[ "${workflow_macos_auth}" == "USER" ]]; } && { ! [[ "${auth_ask_user_to_save_password}" == "TRUE" ]] && [[ "${auth_user_account_saved}" == "FALSE" ]]; }; then
log_super "Warning: The --scheduled-install-user-choice option requires valid saved authenticion. This option will not be show in the user choice dialog."
scheduled_install_user_choice_option="FALSE"
fi
# Set the user choice buttons including handling of ${scheduled_install_user_choice_option}.
if [[ "${scheduled_install_user_choice_option}" == "TRUE" ]]; then
ibm_notifier_array+=(-main_button_label "${display_string_defer_button}" -secondary_button_label "${dialog_user_choice_now_button}" -tertiary_button_label "${dialog_user_choice_schedule_button}" -tertiary_button_cta_type exitlink -tertiary_button_cta_payload "")
else # Default user choice buttons.
ibm_notifier_array+=(-main_button_label "${display_string_defer_button}" -secondary_button_label "${dialog_user_choice_now_button}")
fi
# Manage ${display_unmovable_option}, ${display_hide_background_option}, and ${display_silently_option} options.
{ [[ "${display_unmovable_status}" != "TRUE" ]] && [[ $(echo "${display_unmovable_option}" | grep -c 'DIALOG') -gt 0 ]]; } && display_unmovable_status="TEMP"
{ [[ "${display_hide_background_status}" != "TRUE" ]] && [[ $(echo "${display_hide_background_option}" | grep -c 'DIALOG') -gt 0 ]]; } && display_hide_background_status="TEMP"
{ [[ "${display_silently_status}" != "TRUE" ]] && [[ $(echo "${display_silently_option}" | grep -c 'DIALOG') -gt 0 ]]; } && display_silently_status="TEMP"
# Start the dialog.
dialog_type="CHOICE"
open_dialog_ibm_notifier
unset dialog_type
# Reset temporary ${display_unmovable_option}, ${display_hide_background_option}, and ${display_silently_option} options.
[[ "${display_unmovable_status}" == "TEMP" ]] && unset display_unmovable_status
[[ "${display_hide_background_status}" == "TEMP" ]] && unset display_hide_background_status
[[ "${display_silently_status}" == "TEMP" ]] && unset display_silently_status
# The ${dialog_return} contains the IBM Notifier.app return code. If the user selected a scheduled deferral or the dialog timed out then set ${deferral_timer_minutes}.
case "${dialog_return}" in
0)
if [[ -n "${deferral_timer_menu_minutes}" ]]; then
dialog_response="$(echo "${dialog_response}" | sed -e 's/[^0-9]*//g' | tr -d '\n')"
deferral_timer_minutes="${deferral_timer_menu_minutes_array[${dialog_response}]}"
log_super "Status: User chose to defer for ${deferral_timer_minutes} minutes."
log_status "Pending: User chose to defer for ${deferral_timer_minutes} minutes."
else
log_super "Status: User chose to defer, setting a deferral of ${deferral_timer_minutes} minutes."
log_status "Pending: User chose to defer, setting a deferral of ${deferral_timer_minutes} minutes."
fi
set_auto_launch_deferral
;;
2)
log_super "Status: User chose to install now."
workflow_install_active_user # This function includes internal power, storage, and Apple silicon authentication checks. Sub-functions include test mode logic.
;;
3)
log_super "Status: User chose to schedule the installation."
dialog_user_schedule # This function includes all workflows to facilitate user schedule selection.
;;
4 | 255)
if [[ -n "${deferral_timer_menu_minutes}" ]]; then
dialog_response="$(echo "${dialog_response}" | sed -e 's/[^0-9]*//g' | tr -d '\n')"
deferral_timer_minutes="${deferral_timer_menu_minutes_array[${dialog_response}]}"
log_super "Status: Display timeout automatically chose to defer for ${deferral_timer_minutes} minutes."
log_status "Pending: Display timeout automatically chose to defer for ${deferral_timer_minutes} minutes."
else
log_super "Status: Display timeout automatically chose to defer, using the default deferral of ${deferral_timer_minutes} minutes."
log_status "Pending: Display timeout automatically chose to defer, using the default deferral of ${deferral_timer_minutes} minutes."
fi
set_auto_launch_deferral
;;
esac
}
# Display an interactive dialog allowing the user to schedule an installation.
dialog_user_schedule() {
# First check if the user's password needs to be saved.
if [[ "${mac_cpu_architecture}" == "arm64" ]] && [[ "${auth_ask_user_to_save_password}" == "TRUE" ]] && [[ "${auth_user_account_saved}" == "FALSE" ]] && [[ "${workflow_target}" != "Non-system Software Updates" ]] && [[ "${workflow_target}" != "Jamf Pro Policy Triggers Without Restarting" ]] && [[ "${workflow_target}" != "Jamf Pro Policy Triggers With Restart" ]] && [[ "${workflow_target}" != "Restart Without Updates" ]]; then
dialog_user_auth_type="SCHEDULE"
[[ "${dialog_user_auth_valid}" != "TRUE" ]] && dialog_user_auth
unset dialog_user_auth_type
if [[ "${dialog_user_auth_error}" == "TRUE" ]]; then
deferral_timer_minutes="${deferral_timer_error_minutes}"
log_super "Error: User authentication for scheduled restart failed, trying again in ${deferral_timer_minutes} minutes."
log_status "Pending: User authentication for scheduled restart failed, trying again in ${deferral_timer_minutes} minutes."
set_auto_launch_deferral
fi
fi
# Start preparing for use schedule dialog.
set_display_strings_language
log_status "Running: Dialog user scheduled installation."
unset dialog_timeout_seconds
[[ -n "${dialog_timeout_user_schedule_seconds}" ]] && dialog_timeout_seconds="${dialog_timeout_user_schedule_seconds}"
{ [[ -z "${dialog_timeout_user_schedule_seconds}" ]] && [[ -n "${dialog_timeout_default_seconds}" ]]; } && dialog_timeout_seconds="${dialog_timeout_default_seconds}"
[[ -n "${dialog_timeout_seconds}" ]] && log_super "IBM Notifier: User scheduled install selection dialog with a ${dialog_timeout_seconds} second timeout."
[[ -z "${dialog_timeout_seconds}" ]] && log_super "IBM Notifier: User scheduled install selection dialog with no timeout."
# Set the user schedule title and buttons display strings.
local display_string_user_schedule_title
local dialog_user_schedule_button
if [[ "${workflow_target}" == "Non-system Software Updates" ]] || [[ "${workflow_target}" == "Jamf Pro Policy Triggers Without Restarting" ]]; then
display_string_user_schedule_title="${display_string_user_schedule_install_title}"
dialog_user_schedule_button="${display_string_schedule_install_button}"
else # Default restart workflows.
display_string_user_schedule_title="${display_string_user_schedule_restart_title}"
dialog_user_schedule_button="${display_string_schedule_restart_button}"
fi
# Create initial ${ibm_notifier_array} settings for the dialog.
ibm_notifier_array=(-type popup -bar_title "${display_string_user_schedule_title}" -icon_path "${display_icon}" -icon_width "${display_icon_size}" -icon_height "${display_icon_size}" -main_button_label "${dialog_user_schedule_button}" -secondary_button_label "${display_string_cancel_button}")
# Body text variations based on deadline options, also set ${display_accessory_datepicker_payload}.
local display_accessory_datepicker_start_epoch
display_accessory_datepicker_start_epoch=$(($(date +%s) + (DIALOG_USER_SCHEDULE_MINIMUM_SELECTION_MINUTES * 60) + 1))
local display_accessory_datepicker_start_date
display_accessory_datepicker_start_date=$(date -j -f %s "${display_accessory_datepicker_start_epoch}" +"%Y/%m/%d %H:%M:%S" | xargs)
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: display_accessory_datepicker_start_date is: ${display_accessory_datepicker_start_date}"
if [[ -n "${deadline_epoch}" ]]; then # Show date deadline.
ibm_notifier_array+=(-subtitle "${display_string_user_schedule_date_body}")
local display_accessory_datepicker_end_epoch
display_accessory_datepicker_end_epoch=$((deadline_epoch + 1))
local display_accessory_datepicker_end_date
display_accessory_datepicker_end_date=$(date -j -f %s "${display_accessory_datepicker_end_epoch}" +"%Y/%m/%d %H:%M:%S" | xargs)
log_super "IBM Notifier: Scheduled install selection starts at ${display_accessory_datepicker_start_date} with a maximum end date of ${display_accessory_datepicker_end_date}."
display_accessory_datepicker_payload="/title ${display_string_user_schedule_calendar_title} /preselection ${display_accessory_datepicker_start_date} /start_date ${display_accessory_datepicker_start_date} /end_date ${display_accessory_datepicker_end_date} /style compact"
else # Show no deadlines.
ibm_notifier_array+=(-subtitle "${display_string_user_schedule_default_body}")
log_super "IBM Notifier: Scheduled install selection starts at ${display_accessory_datepicker_start_date} with no maximum end date."
display_accessory_datepicker_payload="/title ${display_string_user_schedule_calendar_title} /preselection ${display_accessory_datepicker_start_date} /start_date ${display_accessory_datepicker_start_date} /style compact"
fi
[[ -n "${dialog_timeout_seconds}" ]] && display_string_dialog_timeout="${display_string_user_schedule_timeout}"
# Manage ${display_unmovable_option}, ${display_hide_background_option}, and ${display_silently_option} options.
{ [[ "${display_unmovable_status}" != "TRUE" ]] && [[ $(echo "${display_unmovable_option}" | grep -c 'DIALOG') -gt 0 ]]; } && display_unmovable_status="TEMP"
{ [[ "${display_hide_background_status}" != "TRUE" ]] && [[ $(echo "${display_hide_background_option}" | grep -c 'DIALOG') -gt 0 ]]; } && display_hide_background_status="TEMP"
{ [[ "${display_silently_status}" != "TRUE" ]] && [[ $(echo "${display_silently_option}" | grep -c 'DIALOG') -gt 0 ]]; } && display_silently_status="TEMP"
# Start the dialog.
dialog_type="SCHEDULE"
open_dialog_ibm_notifier
unset dialog_type
# Reset temporary ${display_unmovable_option}, ${display_hide_background_option}, and ${display_silently_option} options.
[[ "${display_unmovable_status}" == "TEMP" ]] && unset display_unmovable_status
[[ "${display_hide_background_status}" == "TEMP" ]] && unset display_hide_background_status
[[ "${display_silently_status}" == "TEMP" ]] && unset display_silently_status
# The ${dialog_return} contains the IBM Notifier.app return code. If the user selected a scheduled restart then prepare and call the set_scheduled_install_deferral() function or if the dialog timed out then set ${deferral_timer_minutes}.
case "${dialog_return}" in
0)
local dialog_user_schedule_selection
local dialog_user_schedule_selection_epoch
if [[ $(echo "${dialog_response}" | grep -Ec 'am|pm') -gt 0 ]]; then
dialog_user_schedule_selection=$(echo "${dialog_response}" | tr '[:space:]' ':' | sed -e 's/[0-9][0-9]:am:$/00:am/' -e 's/[0-9][0-9]:pm:$/00:pm/')
dialog_user_schedule_selection_epoch=$(date -j -f %Y-%m-%d:%I:%M:%S:%p "${dialog_user_schedule_selection}" +%s)
else # Default 24h time.
dialog_user_schedule_selection=$(echo "${dialog_response}" | tr '[:space:]' ':' | sed -e 's/[0-9][0-9]:$/00/')
dialog_user_schedule_selection_epoch=$(date -j -f %Y-%m-%d:%H:%M:%S "${dialog_user_schedule_selection}" +%s)
fi
local dialog_user_schedule_minimum_selection_epoch
dialog_user_schedule_minimum_selection_epoch=$(($(date +%s) + 121))
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: dialog_user_schedule_selection is: ${dialog_user_schedule_selection}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: dialog_user_schedule_selection_epoch is: ${dialog_user_schedule_selection_epoch}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: dialog_user_schedule_minimum_selection_epoch is: ${dialog_user_schedule_minimum_selection_epoch}"
if [[ $dialog_user_schedule_selection_epoch -lt $dialog_user_schedule_minimum_selection_epoch ]];then
log_super "Warning: Adjusting user selected scheduled installation because it's in the past or less than two minutes from now."
workflow_scheduled_install=$(date -r "${dialog_user_schedule_minimum_selection_epoch}" +%Y-%m-%d:%H:%M)
fi
if [[ -n "${deadline_epoch}" ]] && [[ $dialog_user_schedule_selection_epoch -gt $display_accessory_datepicker_end_epoch ]];then
log_super "Warning: Adjusting user selected scheduled installation because it's past the current workflow deadline."
workflow_scheduled_install=$(date -r "${display_accessory_datepicker_end_epoch}" +%Y-%m-%d:%H:%M)
fi
[[ -z "${workflow_scheduled_install}" ]] && workflow_scheduled_install=$(date -r "${dialog_user_schedule_selection_epoch}" +%Y-%m-%d:%H:%M)
log_super "Status: User chose to schedule installation for $(date -j -f %Y-%m-%d "${workflow_scheduled_install:0:10}" +%a | tr '[:lower:]' '[:upper:]') ${workflow_scheduled_install}."
if [[ -n "${schedule_workflow_active_option}" ]]; then
set_schedule_workflow_active_adjustments
if [[ -n "${workflow_scheduled_install_adjusted}" ]]; then
workflow_scheduled_install="${workflow_scheduled_install_adjusted}"
[[ "${scheduled_install_user_choice_adjusted}" == "TRUE" ]] && log_super "Status: Setting new user selected scheduled installation for $(date -j -f %Y-%m-%d "${workflow_scheduled_install:0:10}" +%a | tr '[:lower:]' '[:upper:]') ${workflow_scheduled_install} which is the start of a workflow active time frame that closest matches the user's choice."
else
log_super "Warning: The current user selected scheduled installation for $(date -j -f %Y-%m-%d "${workflow_scheduled_install:0:10}" +%a | tr '[:lower:]' '[:upper:]') ${workflow_scheduled_install} is overriding the schedule workflow active option because no coordinating time frame could be resolved."
fi
fi
defaults write "${SUPER_LOCAL_PLIST}" WorkflowScheduledInstall -string "${workflow_scheduled_install}"
workflow_scheduled_install_epoch=$(date -j -f %Y-%m-%d:%H:%M:%S "${workflow_scheduled_install}:00" +%s)
local display_string_scheduled_install_only_date
display_string_scheduled_install_only_date=$(date -r "${workflow_scheduled_install_epoch}" "+${DISPLAY_STRING_FORMAT_DATE}")
local display_string_scheduled_install_only_time
display_string_scheduled_install_only_time=$(date -r "${workflow_scheduled_install_epoch}" "+${DISPLAY_STRING_FORMAT_TIME}" | sed 's/^ *//g')
if [[ $(date -r "${workflow_scheduled_install_epoch}" +%H:%M) == "00:00" ]]; then
display_string_scheduled_install="${display_string_scheduled_install_only_date}"
else
display_string_scheduled_install="${display_string_scheduled_install_only_date}${DISPLAY_STRING_FORMAT_DATE_TIME_SEPARATOR}${display_string_scheduled_install_only_time}"
fi
scheduled_install_user_choice_active="TRUE"
set_scheduled_install_deferral
dialog_schedule_reminder &
disown -a
set_auto_launch_deferral
;;
2)
log_super "Status: User chose to cancel schedule selection."
dialog_user_choice # This function includes all workflows to facilitate user choices.
;;
4 | 255)
log_super "Status: Display timeout automatically chose to defer, using the default deferral of ${deferral_timer_minutes} minutes."
log_status "Pending: Display timeout automatically chose to defer, using the default deferral of ${deferral_timer_minutes} minutes."
set_auto_launch_deferral
;;
esac
}
# Display an interactive dialog that reminds the user of a pending scheduled installation and optionally allows for rescheduling.
dialog_schedule_reminder() {
set_display_strings_language
# Set the schedule reminder title and button display strings.
local display_string_schedule_reminder_title
local dialog_schedule_reminder_now_button
local dialog_schedule_reminder_dialog_timeout
if [[ "${workflow_target}" == "Non-system Software Updates" ]] || [[ "${workflow_target}" == "Jamf Pro Policy Triggers Without Restarting" ]]; then
display_string_schedule_reminder_title="${display_string_schedule_reminder_install_title}"
dialog_schedule_reminder_now_button="${display_string_install_now_button}"
dialog_schedule_reminder_dialog_timeout="${display_string_schedule_reminder_install_timeout}"
else # Default restart workflows.
display_string_schedule_reminder_title="${display_string_schedule_reminder_restart_title}"
dialog_schedule_reminder_now_button="${display_string_restart_now_button}"
dialog_schedule_reminder_dialog_timeout="${display_string_schedule_reminder_restart_timeout}"
fi
# There is an automatic timeout behavior if the scheduled install is about to start.
[[ "${verbose_mode_option}" == "TRUE" ]] && log_echo "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: scheduled_install_final_reminder is: ${scheduled_install_final_reminder}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_echo "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: scheduled_install_user_choice_active is: ${scheduled_install_user_choice_active}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_echo "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: scheduled_install_user_choice_adjusted is: ${scheduled_install_user_choice_adjusted}"
if [[ "${scheduled_install_final_reminder}" == "TRUE" ]]; then
log_super "IBM Notifier: Scheduled installation final warning dialog with a ${workflow_scheduled_install_difference} second timeout."
# Create initial ${ibm_notifier_array} settings for the dialog.
if [[ "${scheduled_install_user_choice_option}" == "TRUE" ]] && [[ "${scheduled_install_user_choice_active}" == "TRUE" ]]; then
{ [[ -z "${display_string_deadline}" ]] && [[ "${scheduled_install_user_choice_adjusted}" != "TRUE" ]]; } && ibm_notifier_array=(-type popup -bar_title "${display_string_schedule_reminder_title}" -icon_path "${display_icon}" -icon_width "${display_icon_size}" -icon_height "${display_icon_size}" -subtitle "${display_string_schedule_reminder_default_body}" -main_button_label "${dialog_schedule_reminder_now_button}" -secondary_button_label "${display_string_reschedule_button}")
{ [[ -n "${display_string_deadline}" ]] && [[ "${scheduled_install_user_choice_adjusted}" != "TRUE" ]]; } && ibm_notifier_array=(-type popup -bar_title "${display_string_schedule_reminder_title}" -icon_path "${display_icon}" -icon_width "${display_icon_size}" -icon_height "${display_icon_size}" -subtitle "${display_string_schedule_reminder_reschedule_date_body}" -main_button_label "${dialog_schedule_reminder_now_button}" -secondary_button_label "${display_string_reschedule_button}")
{ [[ -z "${display_string_deadline}" ]] && [[ "${scheduled_install_user_choice_adjusted}" == "TRUE" ]]; } && ibm_notifier_array=(-type popup -bar_title "${display_string_schedule_reminder_title}" -icon_path "${display_icon}" -icon_width "${display_icon_size}" -icon_height "${display_icon_size}" -subtitle "${display_string_schedule_reminder_adjusted_body}" -main_button_label "${dialog_schedule_reminder_now_button}" -secondary_button_label "${display_string_reschedule_button}")
{ [[ -n "${display_string_deadline}" ]] && [[ "${scheduled_install_user_choice_adjusted}" == "TRUE" ]]; } && ibm_notifier_array=(-type popup -bar_title "${display_string_schedule_reminder_title}" -icon_path "${display_icon}" -icon_width "${display_icon_size}" -icon_height "${display_icon_size}" -subtitle "${display_string_schedule_reminder_adjusted_reschedule_date_body}" -main_button_label "${dialog_schedule_reminder_now_button}" -secondary_button_label "${display_string_reschedule_button}")
else # Default scheduled restart warning dialog.
[[ "${scheduled_install_user_choice_adjusted}" != "TRUE" ]] && ibm_notifier_array=(-type popup -bar_title "${display_string_schedule_reminder_title}" -icon_path "${display_icon}" -icon_width "${display_icon_size}" -icon_height "${display_icon_size}" -subtitle "${display_string_schedule_reminder_default_body}" -main_button_label "${dialog_schedule_reminder_now_button}")
[[ "${scheduled_install_user_choice_adjusted}" == "TRUE" ]] && ibm_notifier_array=(-type popup -bar_title "${display_string_schedule_reminder_title}" -icon_path "${display_icon}" -icon_width "${display_icon_size}" -icon_height "${display_icon_size}" -subtitle "${display_string_schedule_reminder_adjusted_body}" -main_button_label "${dialog_schedule_reminder_now_button}")
fi
local dialog_timeout_seconds_previous
[[ -n "${dialog_timeout_seconds}" ]] && dialog_timeout_seconds_previous="${dialog_timeout_seconds}"
dialog_timeout_seconds=$((workflow_scheduled_install_difference + 1))
display_string_dialog_timeout="${dialog_schedule_reminder_dialog_timeout}"
# Start the dialog.
dialog_type="REMINDER"
open_dialog_ibm_notifier
unset dialog_type
[[ -n "${dialog_timeout_seconds_previous}" ]] && dialog_timeout_seconds="${dialog_timeout_seconds_previous}"
# The ${dialog_return} contains the IBM Notifier.app return code. If the user selected a scheduled restart or the dialog timed out then set ${deferral_timer_minutes}.
case "${dialog_return}" in
0)
log_super "Status: User chose to install now."
;;
2)
defaults delete "${SUPER_LOCAL_PLIST}" WorkflowScheduledInstall 2>/dev/null
log_super "Status: User chose to reschedule, removing previously scheduled installation and restarting super..."
restart_super_sleep_seconds=1
restart_super
;;
4 | 255)
log_super "Status: Schedule reminder dialog closed due to timeout."
;;
esac
else # The schedule installation is not about to start so this dialog can exit normally or reschedule.
unset dialog_timeout_seconds
log_super "IBM Notifier: Scheduled installation warning dialog (this dialog has no timeout)."
# Create initial ${ibm_notifier_array} settings for the dialog.
if [[ "${scheduled_install_user_choice_option}" == "TRUE" ]] && [[ "${scheduled_install_user_choice_active}" == "TRUE" ]]; then
{ [[ -z "${display_string_deadline}" ]] && [[ "${scheduled_install_user_choice_adjusted}" != "TRUE" ]]; } && ibm_notifier_array=(-type popup -bar_title "${display_string_schedule_reminder_title}" -icon_path "${display_icon}" -icon_width "${display_icon_size}" -icon_height "${display_icon_size}" -subtitle "${display_string_schedule_reminder_default_body}" -main_button_label "${display_string_ok_button}" -secondary_button_label "${display_string_reschedule_button}")
{ [[ -n "${display_string_deadline}" ]] && [[ "${scheduled_install_user_choice_adjusted}" != "TRUE" ]]; } && ibm_notifier_array=(-type popup -bar_title "${display_string_schedule_reminder_title}" -icon_path "${display_icon}" -icon_width "${display_icon_size}" -icon_height "${display_icon_size}" -subtitle "${display_string_schedule_reminder_reschedule_date_body}" -main_button_label "${display_string_ok_button}" -secondary_button_label "${display_string_reschedule_button}")
{ [[ -z "${display_string_deadline}" ]] && [[ "${scheduled_install_user_choice_adjusted}" == "TRUE" ]]; } && ibm_notifier_array=(-type popup -bar_title "${display_string_schedule_reminder_title}" -icon_path "${display_icon}" -icon_width "${display_icon_size}" -icon_height "${display_icon_size}" -subtitle "${display_string_schedule_reminder_adjusted_body}" -main_button_label "${display_string_ok_button}" -secondary_button_label "${display_string_reschedule_button}")
{ [[ -n "${display_string_deadline}" ]] && [[ "${scheduled_install_user_choice_adjusted}" == "TRUE" ]]; } && ibm_notifier_array=(-type popup -bar_title "${display_string_schedule_reminder_title}" -icon_path "${display_icon}" -icon_width "${display_icon_size}" -icon_height "${display_icon_size}" -subtitle "${display_string_schedule_reminder_adjusted_reschedule_date_body}" -main_button_label "${display_string_ok_button}" -secondary_button_label "${display_string_reschedule_button}")
else # Default scheduled restart warning dialog.
[[ "${scheduled_install_user_choice_adjusted}" != "TRUE" ]] && ibm_notifier_array=(-type popup -bar_title "${display_string_schedule_reminder_title}" -icon_path "${display_icon}" -icon_width "${display_icon_size}" -icon_height "${display_icon_size}" -subtitle "${display_string_schedule_reminder_default_body}" -main_button_label "${display_string_ok_button}")
[[ "${scheduled_install_user_choice_adjusted}" == "TRUE" ]] && ibm_notifier_array=(-type popup -bar_title "${display_string_schedule_reminder_title}" -icon_path "${display_icon}" -icon_width "${display_icon_size}" -icon_height "${display_icon_size}" -subtitle "${display_string_schedule_reminder_adjusted_body}" -main_button_label "${display_string_ok_button}")
fi
# Start the dialog.
dialog_type="REMINDER"
open_dialog_ibm_notifier
unset dialog_type
# The ${dialog_return} contains the IBM Notifier.app return code. If the user selected a scheduled restart or the dialog timed out then set ${deferral_timer_minutes}.
case "${dialog_return}" in
0)
log_super "Background Schedule Reminder Dialog: User dismissed the dialog."
;;
2)
defaults delete "${SUPER_LOCAL_PLIST}" WorkflowScheduledInstall 2>/dev/null
log_super "Background Schedule Reminder Dialog: User chose to reschedule, removing previously scheduled installation and restarting super..."
restart_super_sleep_seconds=1
restart_super
;;
4 | 255)
log_super "Background Schedule Reminder Dialog: Closed due to timeout."
;;
esac
exit 0
fi
}
# Display an interactive dialog when a soft deadline has passed, giving the user only one button to continue the workflow.
dialog_soft_deadline() {
set_display_strings_language
log_status "Running: Dialog soft deadline."
unset dialog_timeout_seconds
[[ -n "${dialog_timeout_soft_deadline_seconds}" ]] && dialog_timeout_seconds="${dialog_timeout_soft_deadline_seconds}"
{ [[ -z "${dialog_timeout_soft_deadline_seconds}" ]] && [[ -n "${dialog_timeout_default_seconds}" ]]; } && dialog_timeout_seconds="${dialog_timeout_default_seconds}"
# Set the soft deadline title and button display strings.
local display_string_soft_deadline_title
local dialog_soft_deadline_button
if [[ "${workflow_target}" == "Non-system Software Updates" ]] || [[ "${workflow_target}" == "Jamf Pro Policy Triggers Without Restarting" ]]; then
display_string_soft_deadline_title="${display_string_soft_deadline_install_title}"
dialog_soft_deadline_button="${display_string_install_now_button}"
[[ -n "${dialog_timeout_seconds}" ]] && display_string_dialog_timeout="${display_string_soft_deadline_install_timeout}"
else # Default restart workflows.
display_string_soft_deadline_title="${display_string_soft_deadline_restart_title}"
dialog_soft_deadline_button="${display_string_restart_now_button}"
[[ -n "${dialog_timeout_seconds}" ]] && display_string_dialog_timeout="${display_string_soft_deadline_restart_timeout}"
fi
# Create initial ${ibm_notifier_array} settings for the dialog.
ibm_notifier_array=(-type popup -bar_title "${display_string_soft_deadline_title}" -icon_path "${display_icon}" -icon_width "${display_icon_size}" -icon_height "${display_icon_size}" -main_button_label "${dialog_soft_deadline_button}")
# Variations for the main body text of the soft deadline dialog.
if [[ "${deadline_days_status}" == "SOFT" ]] || [[ "${deadline_date_status}" == "SOFT" ]]; then
[[ -n "${dialog_timeout_seconds}" ]] && log_super "IBM Notifier: Soft deadline date dialog with a ${dialog_timeout_seconds} second timeout."
[[ -z "${dialog_timeout_seconds}" ]] && log_super "IBM Notifier: Soft deadline date dialog with no timeout."
ibm_notifier_array+=(-subtitle "${display_string_soft_deadline_date_body}")
elif [[ "${deadline_count_status}" == "SOFT" ]]; then
[[ -n "${dialog_timeout_seconds}" ]] && log_super "IBM Notifier: Soft deadline count dialog with a ${dialog_timeout_seconds} second timeout."
[[ -z "${dialog_timeout_seconds}" ]] && log_super "IBM Notifier: Soft deadline count dialog with no timeout."
ibm_notifier_array+=(-subtitle "${display_string_soft_deadline_count_body}")
fi
# Start the dialog.
dialog_type="SOFT"
open_dialog_ibm_notifier
unset dialog_type
# The ${dialog_return} contains the IBM Notifier.app return code.
case "${dialog_return}" in
0)
log_super "Status: User chose to restart."
;;
255)
log_super "Status: Display timeout automatically chose to restart."
;;
esac
}
# Display an interactive IBM Notifier dialog to collect user credentials for macOS update/upgrade workflow.
dialog_user_auth() {
dialog_user_auth_error="FALSE"
# First, check to make sure the current user is a valid volume owner.
if [[ "${current_user_is_volume_owner}" == "FALSE" ]]; then
log_super "Error: Current user is not a volume owner: ${current_user_account_name}"
dialog_user_auth_error="TRUE"
return 0
fi
# The initial ${ibm_notifier_array} and variations for the main body text for the user authentication dialog.
set_display_strings_language
log_status "Running: Dialog user authentication."
unset dialog_timeout_seconds
[[ -n "${dialog_timeout_user_auth_seconds}" ]] && dialog_timeout_seconds="${dialog_timeout_user_auth_seconds}"
{ [[ -z "${dialog_timeout_user_auth_seconds}" ]] && [[ -n "${dialog_timeout_default_seconds}" ]]; } && dialog_timeout_seconds="${dialog_timeout_default_seconds}"
local dialog_user_auth_button
if [[ "${dialog_user_auth_type}" == "DOWNLOAD" ]]; then
dialog_user_auth_button="${display_string_download_now_button}"
elif [[ "${dialog_user_auth_type}" == "SCHEDULE" ]]; then
dialog_user_auth_button="${display_string_schedule_restart_button}"
else # Installation workflow.
dialog_user_auth_button="${display_string_restart_now_button}"
fi
# The initial ${ibm_notifier_array} settings for the initial run of the user authentication dialog.
ibm_notifier_array=(-type popup -bar_title "${display_string_user_auth_title}" -icon_path "${display_icon}" -icon_width "${display_icon_size}" -icon_height "${display_icon_size}" -main_button_label "${dialog_user_auth_button}")
# Variations for the main body text of the initial run of the user authentication dialog.
if [[ "${dialog_user_auth_type}" == "DOWNLOAD" ]]; then
[[ -n "${dialog_timeout_seconds}" ]] && log_super "IBM Notifier: User authentication download update/upgrade dialog with a ${dialog_timeout_seconds} second timeout."
[[ -z "${dialog_timeout_seconds}" ]] && log_super "IBM Notifier: User authentication download update/upgrade dialog with no timeout."
ibm_notifier_array+=(-subtitle "${display_string_user_auth_download_body}")
elif [[ "${dialog_user_auth_type}" == "SCHEDULE" ]]; then
[[ -n "${dialog_timeout_seconds}" ]] && log_super "IBM Notifier: User authentication secheduled installation dialog with a ${dialog_timeout_seconds} second timeout."
[[ -z "${dialog_timeout_seconds}" ]] && log_super "IBM Notifier: User authentication secheduled installation dialog with no timeout."
ibm_notifier_array+=(-subtitle "${display_string_user_auth_schedule_body}")
elif [[ "${deadline_date_status}" == "SOFT" ]] || [[ "${deadline_date_status}" == "HARD" ]] || [[ "${deadline_days_status}" == "SOFT" ]] || [[ "${deadline_days_status}" == "HARD" ]]; then
[[ -n "${dialog_timeout_seconds}" ]] && log_super "IBM Notifier: User authentication deadline date dialog with a ${dialog_timeout_seconds} second timeout."
[[ -z "${dialog_timeout_seconds}" ]] && log_super "IBM Notifier: User authentication deadline date dialog with no timeout."
ibm_notifier_array+=(-subtitle "${display_string_user_auth_deadline_date_body}")
elif [[ "${deadline_count_status}" == "SOFT" ]] || [[ "${deadline_count_status}" == "HARD" ]]; then
[[ -n "${dialog_timeout_seconds}" ]] && log_super "IBM Notifier: User authentication deadline count dialog with a ${dialog_timeout_seconds} second timeout."
[[ -z "${dialog_timeout_seconds}" ]] && log_super "IBM Notifier: User authentication deadline count dialog with no timeout."
ibm_notifier_array+=(-subtitle "${display_string_user_auth_deadline_count_body}")
else # No deadlines, this is the default user authentication dialog.
[[ -n "${dialog_timeout_seconds}" ]] && log_super "IBM Notifier: User authentication default dialog with a ${dialog_timeout_seconds} second timeout."
[[ -z "${dialog_timeout_seconds}" ]] && log_super "IBM Notifier: User authentication default dialog with no timeout."
ibm_notifier_array+=(-subtitle "${display_string_user_auth_default_body}")
fi
display_accessory_secure_payload="/title ${display_string_user_auth_password_title} /placeholder ${display_string_user_auth_password_placeholder} /required"
[[ -n "${dialog_timeout_seconds}" ]] && display_string_dialog_timeout="${display_string_user_auth_timeout}"
# Manage ${display_unmovable_option}, ${display_hide_background_option}, and ${display_silently_option} options.
{ [[ "${display_unmovable_status}" != "TRUE" ]] && [[ $(echo "${display_unmovable_option}" | grep -c 'DIALOG') -gt 0 ]]; } && display_unmovable_status="TEMP"
{ [[ "${display_hide_background_status}" != "TRUE" ]] && [[ $(echo "${display_hide_background_option}" | grep -c 'DIALOG') -gt 0 ]]; } && display_hide_background_status="TEMP"
{ [[ "${display_silently_status}" != "TRUE" ]] && [[ $(echo "${display_silently_option}" | grep -c 'DIALOG') -gt 0 ]]; } && display_silently_status="TEMP"
# Open the user authentication dialog including handling of the ${dialog_timeout_seconds} and user password validation.
dialog_type="AUTH"
dialog_user_auth_valid="FALSE"
local dialog_user_auth_timeout
dialog_user_auth_timeout="FALSE"
local dialog_user_auth_attempt
dialog_user_auth_attempt=0
while true; do
if [[ "${dialog_user_auth_attempt}" -eq 1 ]]; then
# Re-create ${ibm_notifier_array} settings for the user authentication dialog when the user authentication fails.
ibm_notifier_array=(-type popup -bar_title "${display_string_user_auth_title}" -icon_path "${display_icon}" -icon_width "${display_icon_size}" -icon_height "${display_icon_size}" -main_button_label "${dialog_user_auth_button}")
if [[ "${dialog_user_auth_type}" == "DOWNLOAD" ]]; then
ibm_notifier_array+=(-subtitle "${display_string_user_auth_retry_download_body}")
elif [[ "${dialog_user_auth_type}" == "SCHEDULE" ]]; then
ibm_notifier_array+=(-subtitle "${display_string_user_auth_retry_schedule_body}")
elif [[ "${deadline_date_status}" == "SOFT" ]] || [[ "${deadline_date_status}" == "HARD" ]] || [[ "${deadline_days_status}" == "SOFT" ]] || [[ "${deadline_days_status}" == "HARD" ]]; then
ibm_notifier_array+=(-subtitle "${display_string_user_auth_retry_deadline_date_body}")
elif [[ "${deadline_count_status}" == "SOFT" ]] || [[ "${deadline_count_status}" == "HARD" ]]; then
ibm_notifier_array+=(-subtitle "${display_string_user_auth_retry_deadline_count_body}")
else # No deadlines, this is the default user authentication dialog.
ibm_notifier_array+=(-subtitle "${display_string_user_auth_retry_default_body}")
fi
display_accessory_secure_payload="/title ${display_string_user_auth_retry_password_title} /placeholder ${display_string_user_auth_retry_password_placeholder} /required"
fi
open_dialog_ibm_notifier
((dialog_user_auth_attempt++))
if [[ "${dialog_return}" -eq 0 ]]; then
if [[ $(dscl /Local/Default -authonly "${current_user_account_name}" "${dialog_response}" 2>&1) == "" ]]; then
auth_local_account="${current_user_account_name}"
auth_local_password="${dialog_response}"
dialog_user_auth_valid="TRUE"
break
fi
elif [[ "${dialog_return}" -eq 4 ]]; then
dialog_user_auth_timeout="TRUE"
break
else # If ${dialog_return} contains any other error code.
break
fi
done
unset dialog_type
# Reset temporary ${display_unmovable_option}, ${display_hide_background_option}, and ${display_silently_option} options.
[[ "${display_unmovable_status}" == "TEMP" ]] && unset display_unmovable_status
[[ "${display_hide_background_status}" == "TEMP" ]] && unset display_hide_background_status
[[ "${display_silently_status}" == "TEMP" ]] && unset display_silently_status
# If user authentication was successful then evaluate options to save password or fix bootstrap token.
if [[ "${dialog_user_auth_valid}" == "TRUE" ]]; then
log_super "Status: Credentials verified for current user: ${auth_local_account}"
if [[ "${auth_ask_user_to_save_password}" -eq 1 ]] || [[ "${auth_ask_user_to_save_password}" == "TRUE" ]]; then
local auth_user_save_password_response
auth_user_save_password_response=$(launchctl asuser "${current_user_id}" sudo -u "${current_user_account_name}" security add-generic-password -a "super_auth_user_password" -s "Super Update Service" -w "${auth_local_password}" "/Users/${current_user_account_name}/Library/Keychains/login.keychain" 2>&1)
[[ "${verbose_mode_option}" == "TRUE" ]] && log_echo "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_user_save_password_response is: ${auth_user_save_password_response}"
local auth_user_password_keychain
auth_user_password_keychain=$(launchctl asuser "${current_user_id}" sudo -u "${current_user_account_name}" security find-generic-password -w -a "super_auth_user_password" "/Users/${current_user_account_name}/Library/Keychains/login.keychain" 2>&1)
[[ "${verbose_mode_option}" == "TRUE" ]] && log_echo "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_local_password is: ${auth_local_password}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_echo "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_user_password_keychain is: ${auth_user_password_keychain}"
if [[ "${auth_local_password}" == "${auth_user_password_keychain}" ]]; then
log_super "Status: Saved new --auth-ask-user-to-save-password credentials for current user: ${auth_local_account}"
else
log_super "Warning: Unable to validate keychain item for --auth-ask-user-to-save-password, deleting keychain item."
launchctl asuser "${current_user_id}" sudo -u "${current_user_account_name}" security delete-generic-password -a "super_auth_user_password" "/Users/${current_user_account_name}/Library/Keychains/login.keychain" >/dev/null 2>&1
fi
elif [[ "${mdm_enrolled}" == "TRUE" ]] && [[ "${auth_error_bootstrap_token}" == "TRUE" ]] && { [[ "${auth_mdm_failover_to_user_status}" == "TRUE" ]] || [[ $(echo "${auth_mdm_failover_to_user_option}" | grep -c 'ERROR') -gt 0 ]]; }; then
bootstrap_token_attempt_fix="TRUE"
if [[ "${current_user_is_admin}" == "FALSE" ]]; then
log_super "Warning: Local user account \"${auth_local_account}\" can not be used to escrow bootstrap token because they are not a local admin."
bootstrap_token_attempt_fix="FALSE"
fi
if [[ "${current_user_has_secure_token}" == "FALSE" ]]; then
log_super "Warning: Local user account \"${auth_local_account}\" can not be used to escrow bootstrap token because they do not have a secure token."
bootstrap_token_attempt_fix="FALSE"
fi
if [[ "${auth_error_mdm}" == "TRUE" ]]; then
log_super "Warning: Can not escrow bootstrap token because the MDM service is not available."
bootstrap_token_attempt_fix="FALSE"
fi
if [[ "${bootstrap_token_attempt_fix}" == "TRUE" ]]; then
log_super "Status: Attempting to use the credentials from user \"${auth_local_account}\" to escrow bootstrap token..."
local profiles_response
profiles_response=$(profiles install -type bootstraptoken -user "${auth_local_account}" -password "${auth_local_password}" 2>&1)
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: profiles_response is: ${profiles_response}"
check_bootstrap_token_escrow
fi
fi
else # The user authentication dialog timed out or failed.
if [[ "${dialog_user_auth_timeout}" == "TRUE" ]]; then
log_super "Error: Waiting for user authentication timed out after ${dialog_timeout_seconds} seconds."
else
log_super "Error: User authentication failed."
fi
dialog_user_auth_error="TRUE"
fi
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: dialog_user_auth_error is: ${dialog_user_auth_error}"
}
# MARK: *** Main Workflow ***
################################################################################
main() {
# Initial super workflow preparations.
set_defaults
get_options "$@"
workflow_startup
workflow_time_epoch=$(date +%s)
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: Setting new workflow_time_epoch to: ${workflow_time_epoch}"
[[ -n "${schedule_workflow_active_option}" ]] && check_schedule_workflow_active
# If restarting from a macOS update/upgrade this workflow starts before all others.
if [[ "${workflow_restart_validate_active}" == "TRUE" ]]; then
[[ "${schedule_workflow_active_deferral}" == "TRUE" ]] && log_super "Warning: The restart validation workflow is temporarily ignoring a workflow active deferral."
log_super "**** S.U.P.E.R.M.A.N. ${SUPER_VERSION} - RESTART VALIDATION WORKFLOW ****"
workflow_restart_validate # This function will automatically defer if there are any errors.
# At this point the workflow_restart_validate() function was succesfull, so reset for future workflow runs.
defaults delete "${SUPER_LOCAL_PLIST}" WorkflowRestartValidate 2>/dev/null
unset workflow_restart_validate_active
defaults write "${SUPER_LOCAL_PLIST}" MacLastStartup -string "${mac_last_startup}"
workflow_time_epoch=$(date +%s)
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: Setting new workflow_time_epoch to: ${workflow_time_epoch}"
defaults delete "${SUPER_LOCAL_PLIST}" WorkflowTarget 2>/dev/null
defaults delete "${SUPER_LOCAL_PLIST}" WorkflowScheduledInstall 2>/dev/null
defaults delete "${SUPER_LOCAL_PLIST}" WorkflowDownloadMacOSAuthRequired 2>/dev/null
reset_schedule_zero_date
reset_deadline_counters
# Handle the ${workflow_reset_super_after_completion_active} workflow option.
if [[ "${workflow_reset_super_after_completion_active}" == "TRUE" ]]; then
defaults delete "${SUPER_LOCAL_PLIST}" WorkflowResetSuperAfterCompletion 2>/dev/null
defaults write "${SUPER_LOCAL_PLIST}" WorkflowResetSuperAfterCompletionNow -bool true
log_super "Status: Workflow complete, restarting via LaunchDeamon to reset super..."
restart_super_sleep_seconds=5
restart_super
fi
# At this point the ${workflow_auth_error} condition is a workflow-stopper, so try again later.
if [[ "${workflow_auth_error}" == "TRUE" ]]; then
deferral_timer_minutes="${DEFERRAL_TIMER_RESTART_VALIDATION_ERROR_MINUTES}"
log_super "Workflow Error: Configured authentication workflow is not currently possible, trying again in ${deferral_timer_minutes} minutes."
log_status "Pending: Configured authentication workflow is not currently possible, trying again in ${deferral_timer_minutes} minutes."
set_auto_launch_deferral
fi
fi
# If requested then reset various counters.
{ [[ "${scheduled_install_delete_all_option}" == "TRUE" ]] || [[ "${deadline_days_restart_all_option}" == "TRUE" ]]; } && reset_schedule_zero_date
[[ "${deadline_count_restart_all_option}" == "TRUE" ]] && reset_deadline_counters
# Check for software update/upgrades workflow.
macos_installer_target="FALSE"
macos_msu_major_upgrade_target="FALSE"
macos_msu_minor_update_target="FALSE"
non_system_msu_targets="FALSE"
workflow_target="FALSE"
if [[ "${workflow_disable_update_check_option}" == "TRUE" ]]; then # Skip software updates/upgrade mode option.
log_super "**** S.U.P.E.R.M.A.N. ${SUPER_VERSION} - NOT CHECKING FOR SOFTWARE UPDATES/UPGRADES ****"
{ [[ "${workflow_disable_relaunch_option}" != "TRUE" ]] && [[ -z "${install_jamf_policy_triggers_option}" ]] && [[ "${workflow_restart_without_updates_option}" != "TRUE" ]]; } && log_super "Warning: When using the --workflow-disable-update-check option consider also using the --workflow-disable-relaunch option to prevent super from unecissarily re-launching."
if [[ "${test_mode_option}" == "TRUE" ]]; then
log_super "Test Mode: Simulating skip updates workflow."
if [[ "${install_jamf_policy_triggers_without_restarting_option}" == "TRUE" ]]; then
log_super "Test Mode: Simulating install Jamf Pro Policy Triggers without restarting workflow."
elif [[ "${workflow_restart_without_updates_option}" == "TRUE" ]]; then
log_super "Test Mode: Simulating restart without updates workflow."
else
log_super "Warning: When using the --workflow-disable-update-check option you need to also use the --install-jamf-policy-triggers-without-restarting or --workflow-restart-without-updates options to simulate notification and dialog workflows."
fi
fi
else # Default software update/upgrade workflows.
if [[ "${test_mode_option}" != "TRUE" ]]; then # Default workflow starts by checking for updates/upgrades.
log_super "**** S.U.P.E.R.M.A.N. ${SUPER_VERSION} - CHECK FOR SOFTWARE UPDATES/UPGRADES ****"
workflow_check_software_status
check_macos_downloads
workflow_time_epoch=$(date +%s)
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: Setting new workflow_time_epoch to: ${workflow_time_epoch}"
else # Test mode.
if [[ "${workflow_restart_without_updates_option}" == "TRUE" ]]; then
log_super "Test Mode: Simulating restart without updates workflow."
elif [[ "${install_jamf_policy_triggers_without_restarting_option}" == "TRUE" ]]; then
log_super "Test Mode: Simulating install Jamf Pro Policy Triggers without restarting workflow."
elif [[ "${install_non_system_updates_without_restarting_option}" == "TRUE" ]]; then
non_system_msu_targets="TRUE"
log_super "Test Mode: Simulating install non-system updates workflow."
elif [[ "${install_macos_major_upgrades}" == "TRUE" ]]; then
if [[ "${macos_version_number}" -lt 1203 ]]; then
macos_installer_target="Title:${macos_title},Version:${macos_version_major}.${macos_version_minor},Build:${macos_build},Size:14633014395"
macos_installer_title="${macos_title}"
macos_installer_version="${macos_version_major}.${macos_version_minor}"
macos_installer_build="${macos_build}"
macos_installer_size=15
macos_installer_download_required="TRUE"
else
macos_msu_major_upgrade_target="Title:${macos_title} ${macos_version_major}.${macos_version_minor},Build:${macos_build},Version:${macos_version_major}.${macos_version_minor}"
macos_msu_label="${macos_version_full}"
macos_msu_title="${macos_title}"
macos_msu_version="${macos_version_major}.${macos_version_minor}"
macos_msu_build="${macos_build}"
macos_msu_size=15
macos_msu_download_required="TRUE"
fi
log_super "Test Mode: Simulating macOS ${macos_msu_version} upgrade workflow."
else # Simulate a macOS update.
macos_msu_minor_update_target="Title:${macos_title} ${macos_version_major}.${macos_version_minor},Build:${macos_build},Version:${macos_version_major}.${macos_version_minor}"
macos_msu_label="${macos_version_full}"
macos_msu_title="${macos_title}"
macos_msu_version="${macos_version_major}.${macos_version_minor}"
macos_msu_build="${macos_build}"
macos_msu_size=5
macos_msu_download_required="TRUE"
log_super "Test Mode: Simulating a macOS ${macos_msu_version} update workflow."
fi
fi
fi
# At this point all available updates/upgrades have been evaluated.
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_installer_target is: ${macos_installer_target}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_msu_major_upgrade_target is: ${macos_msu_major_upgrade_target}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_msu_minor_update_target is: ${macos_msu_minor_update_target}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: non_system_msu_targets is: ${non_system_msu_targets}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: workflow_restart_without_updates_option is: ${workflow_restart_without_updates_option}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: install_jamf_policy_triggers_without_restarting_option is: ${install_jamf_policy_triggers_without_restarting_option}"
delete_unneeded_macos_installers # This function includes internal test mode logic.
# Prepare workflow for specific ${workflow_target} and, if needed, set ${display_accessory_content}.
workflow_target="FALSE"
if [[ "${macos_installer_target}" != "FALSE" ]]; then
workflow_target="${macos_installer_title} ${macos_installer_version}-${macos_build}"
workflow_target_log_string="${workflow_target} MAJOR UPGRADE VIA INSTALLER"
if [[ -n "${display_accessory_macos_major_upgrade}" ]]; then
display_accessory_content="${display_accessory_macos_major_upgrade}"
elif [[ -n "${display_accessory_default}" ]]; then
display_accessory_content="${display_accessory_default}"
fi
elif [[ "${macos_msu_major_upgrade_target}" != "FALSE" ]]; then
workflow_target="${macos_msu_title} ${macos_msu_version}-${macos_msu_build}"
workflow_target_log_string="${workflow_target} MAJOR UPGRADE VIA SOFTWAREUPDATE"
if [[ -n "${display_accessory_macos_major_upgrade}" ]]; then
display_accessory_content="${display_accessory_macos_major_upgrade}"
elif [[ -n "${display_accessory_default}" ]]; then
display_accessory_content="${display_accessory_default}"
fi
elif [[ "${macos_msu_minor_update_target}" != "FALSE" ]]; then
if [[ $(echo "${macos_msu_minor_update_target}" | grep -c '(') -gt 0 ]] && [[ "${install_rapid_security_responses_option}" != "TRUE" ]]; then
log_super "Warning: The default workflow ignores macOS Rapid Security Response (RSR) updates. Use the --install-rapid-security-responses option to always install macOS RSR updates as soon as they are available."
else
workflow_target="${macos_msu_title} ${macos_msu_version}-${macos_msu_build}"
workflow_target_log_string="${workflow_target} MINOR UPDATE VIA SOFTWAREUPDATE"
if [[ -n "${display_accessory_macos_minor_update}" ]]; then
display_accessory_content="${display_accessory_macos_minor_update}"
elif [[ -n "${display_accessory_default}" ]]; then
display_accessory_content="${display_accessory_default}"
fi
fi
elif [[ "${non_system_msu_targets}" == "TRUE" ]]; then
if [[ "${install_non_system_updates_without_restarting_option}" != "TRUE" ]]; then
log_super "Warning: The default workflow ignores non-system software updates if there is no macOS update/upgrade available. Use the --install-non-system-updates-without-restarting option to always install non-system updates as soon as they are available."
else
workflow_target="Non-system Software Updates"
workflow_target_log_string="NON-SYSTEM SOFTWARE UPDATES WITHOUT RESTARTING"
workflow_macos_auth="FALSE"
if [[ "${workflow_only_download_active}" == "TRUE" ]]; then
log_super "Warning: The --install-non-system-updates-without-restarting option is currently overriding the --only-download option."
workflow_only_download_active="FALSE"
fi
if [[ -n "${display_accessory_non_system_updates}" ]]; then
display_accessory_content="${display_accessory_non_system_updates}"
elif [[ -n "${display_accessory_default}" ]]; then
display_accessory_content="${display_accessory_default}"
fi
fi
elif [[ "${install_jamf_policy_triggers_without_restarting_option}" == "TRUE" ]]; then
workflow_target="Jamf Pro Policy Triggers Without Restarting"
workflow_target_log_string="JAMF PRO POLICY TRIGGERS WITHOUT RESTARTING"
workflow_macos_auth="FALSE"
if [[ "${workflow_only_download_active}" == "TRUE" ]]; then
log_super "Warning: The --install-jamf-policy-triggers-without-restarting option is currently overriding the --only-download option."
workflow_only_download_active="FALSE"
fi
if [[ -n "${display_accessory_jamf_policy_triggers}" ]]; then
display_accessory_content="${display_accessory_jamf_policy_triggers}"
elif [[ -n "${display_accessory_default}" ]]; then
display_accessory_content="${display_accessory_default}"
fi
elif [[ "${workflow_restart_without_updates_option}" == "TRUE" ]]; then
workflow_macos_auth="FALSE"
if [[ "${workflow_only_download_active}" == "TRUE" ]]; then
log_super "Warning: The --workflow-restart-without-updates option is currently overriding the --only-download option."
workflow_only_download_active="FALSE"
fi
if [[ -n "${install_jamf_policy_triggers_option}" ]]; then
workflow_target="Jamf Pro Policy Triggers With Restart"
workflow_target_log_string="JAMF PRO POLICY TRIGGERS WITH RESTART"
if [[ -n "${display_accessory_jamf_policy_triggers}" ]]; then
display_accessory_content="${display_accessory_jamf_policy_triggers}"
elif [[ -n "${display_accessory_default}" ]]; then
display_accessory_content="${display_accessory_default}"
fi
else
workflow_target="Restart Without Updates"
workflow_target_log_string="RESTART WITHOUT UPDATES/UPGRADES"
if [[ -n "${display_accessory_restart_without_updates}" ]]; then
display_accessory_content="${display_accessory_restart_without_updates}"
elif [[ -n "${display_accessory_default}" ]]; then
display_accessory_content="${display_accessory_default}"
fi
fi
fi
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: workflow_target is: ${workflow_target}"
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: display_accessory_content is: ${display_accessory_content}"
# Check for zero date, scheduled installations, and log workflow.
if [[ "${workflow_target}" != "FALSE" ]]; then
if [[ "${workflow_install_now_active}" == "TRUE" ]]; then
log_super "**** S.U.P.E.R.M.A.N. ${SUPER_VERSION} - INSTALL NOW ${workflow_target_log_string} ****"
elif [[ "${workflow_only_download_active}" == "TRUE" ]]; then
log_super "**** S.U.P.E.R.M.A.N. ${SUPER_VERSION} - ONLY DOWNLOAD ${workflow_target_log_string} ****"
else # Workflows that may leverage schedules, deferrals, or deadlines.
check_schedule_zero_date
check_scheduled_install
if [[ "${workflow_scheduled_install_active}" == "TRUE" ]]; then
log_super "**** S.U.P.E.R.M.A.N. ${SUPER_VERSION} - SCHEDULED RESTART ${workflow_target_log_string} ****"
else # Default super workflow.
log_super "**** S.U.P.E.R.M.A.N. ${SUPER_VERSION} - ${workflow_target_log_string} ****"
fi
fi
defaults write "${SUPER_LOCAL_PLIST}" WorkflowTarget -string "${workflow_target}"
else # No software updates/upgrade needed so reset any leftovers.
defaults delete "${SUPER_LOCAL_PLIST}" WorkflowTarget 2>/dev/null
defaults delete "${SUPER_LOCAL_PLIST}" WorkflowDownloadMacOSAuthRequired 2>/dev/null
reset_schedule_zero_date
reset_deadline_counters
fi
# If set, handle the ${schedule_workflow_active_option} before the any installation or restart workflow starts.
if [[ "${workflow_target}" != "FALSE" ]] && [[ -n "${schedule_workflow_active_option}" ]]; then
if [[ "${schedule_workflow_active_deferral}" == "TRUE" ]]; then
if [[ "${workflow_install_now_active}" == "TRUE" ]]; then
log_super "Warning: The --workflow-install-now option is currently overriding a schedule workflow active deferral."
elif [[ "${workflow_scheduled_install_active}" == "TRUE" ]]; then
[[ "${workflow_scheduled_install_now}" == "TRUE" ]] && log_super "Warning: A scheduled installation is currently overriding a schedule workflow active deferral."
[[ "${workflow_scheduled_install_now}" != "TRUE" ]] && log_super "Warning: A scheduled installation reminder is currently overriding a schedule workflow active deferral."
else # Schedule workflow active should force a deferral.
deferral_timer_minutes=$(((schedule_workflow_active_next_start_epoch - workflow_time_epoch) / 60 ))
log_super "Status: Automatic schedule workflow active deferral until ${schedule_workflow_active_next_start}, deferring for ${deferral_timer_minutes} minutes."
log_status "Pending: Automatic schedule workflow active deferral until ${schedule_workflow_active_next_start}, deferring for ${deferral_timer_minutes} minutes."
set_auto_launch_deferral
fi
else # The workflow can continue.
log_super "Status: Current schedule workflow active time frame ends at ${schedule_workflow_active_current_end}."
fi
fi
# This is the main logic for starting all installation or restart workflows.
if [[ "${workflow_target}" != "FALSE" ]]; then
workflow_time_epoch=$(date +%s)
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: Setting new workflow_time_epoch to: ${workflow_time_epoch}"
if [[ "${workflow_install_now_active}" == "TRUE" ]]; then
if [[ "${current_user_account_name}" == "FALSE" ]]; then # A normal user is not logged in, start installations immediately.
workflow_install_no_user # This function includes internal power, storage, and Apple silicon authentication checks. Sub-functions include test mode logic.
else # A normal user is currently logged in.
[[ $(echo "${auth_mdm_failover_to_user_option}" | grep -c 'INSTALLNOW') -gt 0 ]] && auth_mdm_failover_to_user_status="TRUE"
if [[ "${workflow_macos_auth}" == "USER" ]]; then # Apple silicon computer in need of user authentication.
[[ "${dialog_user_auth_valid}" != "TRUE" ]] && dialog_user_auth
if [[ "${dialog_user_auth_error}" == "TRUE" ]]; then
log_super "Error: No valid Apple slicon authentication, install now workflow can not continue."
log_status "Inactive Error: No valid Apple slicon authentication, install now workflow can not continue."
[[ "${current_user_account_name}" != "FALSE" ]] && notification_install_now_failed
exit_error
fi
fi
{ [[ "${workflow_target}" != "Non-system Software Updates" ]] && [[ "${workflow_target}" != "Jamf Pro Policy Triggers Without Restarting" ]] && [[ "${workflow_target}" != "Jamf Pro Policy Triggers With Restart" ]] && [[ "${workflow_target}" != "Restart Without Updates" ]]; } && workflow_download_macos # This function only downloads if needed and includes internal storage checks and test mode logic.
workflow_install_active_user # This function includes internal power, storage, and Apple silicon authentication checks. Sub-functions include test mode logic.
fi
elif [[ "${workflow_scheduled_install_active}" == "TRUE" ]]; then
if [[ "${workflow_scheduled_install_now}" == "TRUE" ]]; then
if [[ "${current_user_account_name}" == "FALSE" ]]; then # A normal user is not logged in, start installations immediately.
workflow_install_no_user # This function includes internal power, storage, and Apple silicon authentication checks. Sub-functions include test mode logic.
else # A normal user is currently logged in.
workflow_install_active_user # This function includes internal power, storage, and Apple silicon authentication checks. Sub-functions include test mode logic.
fi
else # It's not time yet for a scheduled installation.
set_scheduled_install_deferral
if [[ "${current_user_account_name}" != "FALSE" ]]; then # This will background the schedule reminder dialog, but super will still exit.
check_deadlines_days_date
if [[ "${scheduled_install_suppress_reminder}" != "TRUE" ]]; then
dialog_schedule_reminder &
disown -a
fi
fi
set_auto_launch_deferral
fi
elif [[ "${workflow_only_download_active}" == "TRUE" ]]; then # Only download workflow doesn't matter if the user is logged in.
if [[ "${current_user_account_name}" == "FALSE" ]]; then # A normal user is not logged in, the only download workflow can not continue.
deferral_timer_minutes="${deferral_timer_error_minutes}"
log_super "Error: No current active user, due to limitations in macOS the only download workflow can not continue, trying again in ${deferral_timer_minutes} minutes."
log_status "Pending: No current active user, due to limitations in macOS the only download workflow can not continue, trying again in ${deferral_timer_minutes} minutes."
set_auto_launch_deferral
else # A normal user is currently logged in.
workflow_download_macos # This function only downloads if needed and includes internal storage checks and test mode logic.
workflow_time_epoch=$(date +%s)
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: Setting new workflow_time_epoch to: ${workflow_time_epoch}"
fi
else # Default super workflow, can run with or without logged in user.
if [[ "${current_user_account_name}" == "FALSE" ]]; then # A normal user is not logged in, start installations immediately.
workflow_install_no_user # This function includes internal power, storage, and Apple silicon authentication checks. Sub-functions include test mode logic.
else # A normal user is currently logged in so complete any macOS downloads first.
{ [[ "${workflow_target}" != "Jamf Pro Policy Triggers Without Restarting" ]] && [[ "${workflow_target}" != "Jamf Pro Policy Triggers With Restart" ]] && [[ "${workflow_target}" != "Restart Without Updates" ]]; } && workflow_download_macos # This function only downloads if needed and includes internal storage checks and test mode logic.
workflow_time_epoch=$(date +%s)
[[ "${verbose_mode_option}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: Setting new workflow_time_epoch to: ${workflow_time_epoch}"
[[ "${workflow_download_macos_check_user}" == "TRUE" ]] && check_current_user # The download may have taken a while so another check to make sure the user is still logged in.
if [[ "${current_user_account_name}" == "FALSE" ]]; then
workflow_install_no_user # This function includes internal power, storage, and Apple silicon authentication checks. Sub-functions include test mode logic.
return 0
fi
# Check deadlines.
check_deadlines_days_date
# User Focus only needs to be checked if there are no date or day deadlines.
if [[ "${deadline_date_status}" == "FALSE" ]] && [[ "${deadline_days_status}" == "FALSE" ]]; then
check_user_focus
else # At this point any date or days deadline would rule out any ${user_focus_active} option.
user_focus_active="FALSE"
fi
check_deadlines_count
# Manage various workflow options when any deadline is active.
if [[ "${deadline_date_status}" == "HARD" ]] || [[ "${deadline_days_status}" == "HARD" ]] || [[ "${deadline_count_status}" == "HARD" ]] || [[ "${deadline_date_status}" == "SOFT" ]] || [[ "${deadline_days_status}" == "SOFT" ]] || [[ "${deadline_count_status}" == "SOFT" ]]; then
[[ $(echo "${display_unmovable_option}" | grep -c 'DEADLINE') -gt 0 ]] && display_unmovable_status="TRUE"
[[ $(echo "${display_hide_background_option}" | grep -c 'DEADLINE') -gt 0 ]] && display_hide_background_status="TRUE"
[[ $(echo "${display_silently_option}" | grep -c 'DEADLINE') -gt 0 ]] && display_silently_status="TRUE"
[[ $(echo "${display_notifications_centered_option}" | grep -c 'DEADLINE') -gt 0 ]] && display_notifications_centered_status="TRUE"
[[ $(echo "${display_hide_progress_bar_option}" | grep -c 'DEADLINE') -gt 0 ]] && display_hide_progress_bar_status="TRUE"
[[ $(echo "${auth_mdm_failover_to_user_option}" | grep -c 'DEADLINE') -gt 0 ]] && auth_mdm_failover_to_user_status="TRUE"
fi
# At this point all deferral and deadline options have been evaluated.
if [[ "${deadline_date_status}" == "HARD" ]] || [[ "${deadline_days_status}" == "HARD" ]] || [[ "${deadline_count_status}" == "HARD" ]]; then # A hard deadline has passed, similar to no logged in user but with a notification.
workflow_install_active_user # This function includes internal power, storage, and Apple silicon authentication checks. Sub-functions include test mode logic.
elif [[ "${deadline_date_status}" == "SOFT" ]] || [[ "${deadline_days_status}" == "SOFT" ]] || [[ "${deadline_count_status}" == "SOFT" ]]; then # A soft deadline has passed.
[[ "${workflow_macos_auth}" != "USER" ]] && dialog_soft_deadline
workflow_install_active_user # This function includes internal power, storage, and Apple silicon authentication checks. Sub-functions include test mode logic.
elif [[ "${user_focus_active}" == "TRUE" ]]; then # No deadlines have passed but a process has told the display to not sleep or the user has enabled Focus or Do Not Disturb.
deferral_timer_minutes="${deferral_timer_focus_minutes}"
log_status "Pending: Automatic user focus deferral, trying again in ${deferral_timer_minutes} minutes."
set_auto_launch_deferral
else # Logically, this is the only time the choice dialog is shown.
dialog_user_choice # This function includes all workflows to facilitate user choices.
fi
fi
fi
fi
# At this point super is about to exit, so wrap up for different workflow exit modes.
if [[ $(/usr/libexec/PlistBuddy -c "Print :WorkflowRestartValidate" "${SUPER_LOCAL_PLIST}.plist" 2> /dev/null) == "true" ]]; then # An update/upgrade is about to restart the computer.
log_super "Exit: System restart is imminent and the super restart validation workflow is scheduled to automatically relaunch at next startup."
log_status "Pending: System restart is imminent and the super restart validation workflow is scheduled to automatically relaunch at next startup."
else # The super workflow completed but not for a macOS update/upgrade workflow.
if [[ "${workflow_target}" != "FALSE" ]]; then
defaults delete "${SUPER_LOCAL_PLIST}" WorkflowDownloadMacOSAuthRequired 2>/dev/null
reset_schedule_zero_date
reset_deadline_counters
fi
# Wrap-up for the ${workflow_install_now_active} if there were no updates/upgrades.
if [[ "${workflow_install_now_active}" == "TRUE" ]]; then
[[ "${current_user_account_name}" != "FALSE" ]] && notification_install_now_up_to_date
if [[ "${test_mode_option}" == "TRUE" ]]; then
log_super "Test Mode: Pausing ${test_mode_timeout_seconds} seconds for the install now up to date notification..."
sleep "${test_mode_timeout_seconds}"
killall -9 "IBM Notifier" "IBM Notifier Popup" >/dev/null 2>&1
fi
fi
# Wrap-up for ${workflow_only_download_active}.
if [[ "${workflow_only_download_active}" == "TRUE" ]] && [[ "${workflow_download_macos_error}" == "FALSE" ]]; then
if [[ "${jamf_version_number}" != "FALSE" ]]; then
if [[ "${auth_error_jamf}" != "TRUE" ]]; then
log_super "Status: Submitting updated inventory to Jamf Pro. Use --verbose-mode or check /var/log/jamf.log for more detail..."
if [[ "${verbose_mode_option}" == "TRUE" ]]; then
local jamf_response
jamf_response=$("${JAMF_PRO_BINARY}" recon -verbose 2>&1)
log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: jamf_response is:\n${jamf_response}"
else
"${JAMF_PRO_BINARY}" recon >/dev/null 2>&1
fi
else # There was an earlier Jamf Pro validation error.
deferral_timer_minutes="${deferral_timer_error_minutes}"
log_super "Error: Unable to submit inventory to Jamf Pro, trying again in ${deferral_timer_minutes} minutes."
log_status "Pending: Unable to submit inventory to Jamf Pro, trying again in ${deferral_timer_minutes} minutes."
set_auto_launch_deferral
fi
fi
fi
# Handle the ${workflow_reset_super_after_completion_active} workflow option.
if [[ "${workflow_reset_super_after_completion_active}" == "TRUE" ]]; then
defaults delete "${SUPER_LOCAL_PLIST}" WorkflowResetSuperAfterCompletion 2>/dev/null
defaults write "${SUPER_LOCAL_PLIST}" WorkflowResetSuperAfterCompletionNow -bool true
log_super "Status: Workflow complete, restarting via LaunchDeamon to reset super..."
restart_super_sleep_seconds=5
restart_super
fi
# Logic for ${workflow_disable_relaunch_option} and ${deferral_timer_workflow_relaunch_minutes}.
if [[ "${workflow_disable_relaunch_option}" == "TRUE" ]]; then
log_super "Status: Full super workflow complete! Automatic relaunch is disabled."
log_status "Inactive: Full super workflow complete! Automatic relaunch is disabled."
/usr/libexec/PlistBuddy -c "Add :NextAutoLaunch string FALSE" "${SUPER_LOCAL_PLIST}.plist" 2> /dev/null
else # Default super workflow automatically relaunches.
deferral_timer_minutes="${deferral_timer_workflow_relaunch_minutes}"
log_super "Status: Full super workflow complete! The super workflow is scheduled to automatically relaunch in ${deferral_timer_minutes} minutes."
log_status "Pending: Full super workflow complete! The super workflow is scheduled to automatically relaunch in ${deferral_timer_minutes} minutes."
set_auto_launch_deferral
fi
fi
}
main "$@"
exit_clean