#!/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.1.0-rc4" readonly SUPER_VERSION SUPER_DATE="2026/02/20" readonly SUPER_DATE SUPER_USER_AGENT="super/${SUPER_VERSION} $(curl --version | head -1 | sed -e 's/curl /curl\//')" readonly SUPER_USER_AGENT # MARK: *** Documentation *** ################################################################################ # Output the usage documentation to standard out. 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 Documentation Options: [--usage] [--help] [--version] [--config-status] macOS Installation Options: [--install-macos-minor-version-target=macOSVersionNumber] [--install-macos-major-upgrades] [--install-macos-major-upgrades-off] [--install-macos-major-version-target=macOSVersionNumber] [--install-macos-bsi-updates] [--install-macos-bsi-updates-off] [--install-rapid-security-responses] [--install-rapid-security-responses-off] Non-system Installation Options: [--install-non-system-updates-without-restarting] [--install-non-system-updates-without-restarting-off] [--install-safari-updates-without-restarting] [--install-safari-updates-without-restarting-off] [--install-prioritize-non-restart-updates] [--install-prioritize-non-restart-updates-off] Jamf Policy Installation Options: [--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-ignore-managed-deferrals] [--workflow-ignore-managed-deferrals-off] [--workflow-require-active-user] [--workflow-require-active-user-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] [--workflow-reset-super-after-completion-off] 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-deferred-start-file=/local/path] [--schedule-deferred-start-days=number] [--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-safari-update-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] Test Mode Options: [--test-mode] [--test-mode-off] [--test-mode-timeout=seconds] [--test-storage-update=gigabytes] [--test-storage-upgrade=gigabytes] [--test-battery-level=percentage] Alternate Configuration Options: [--config-edit-main] [--config-edit=ConfigurationName] [--config-clone=SourceConfigurationName] [--config-delete=ConfigurationName] [--config-delete-all] [--config-start-default=ConfigurationName] [--config-start-temp=ConfigurationName] [--config-status] 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] Troubleshooting Options: [--usage] [--help] [--version] [--config-status] [--open-logs] [--reset-super] [--verbose-mode] [--verbose-mode-off] ** Managed preferences override local options via domain: com.macjutsu.super InstallMacOSMinorVersionTarget macOSVersionNumber InstallMacOSMajorUpgrades | InstallMacOSMajorVersionTarget macOSVersionNumber InstallMacOSBSIUpdates | InstallRapidSecurityResponses | InstallNonSystemUpdatesWithoutRestarting | InstallSafariUpdatesWithoutRestarting | InstallPrioritizeNonRestartUpdates | InstallJamfPolicyTriggers PolicyTrigger,PolicyTrigger,etc... InstallJamfPolicyTriggersWithoutRestarting | WorkflowInstallNow | WorkflowOnlyDownload | WorkflowRequireActiveUser | 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... ScheduleDeferredStartFile path ScheduleDeferredStartDays number 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 DisplayAccessorySafariUpdateFile path or URL DisplayAccessoryJamfPolicyTriggersFile path or URL DisplayAccessoryRestartWithoutUpdatesFile path or URL DisplayHelpButtonString plain text or URL DisplayWarningButtonString plain text or URL ConfigCodeSignature Identifier ConfigTempOverride | 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." 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() { log_echo "**** S.U.P.E.R.M.A.N. ${SUPER_VERSION} - 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 SHOW 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 } # Output only the ${SUPER_VERSION} to standard out. show_version() { echo "${SUPER_VERSION}" exit 0 } # Output various configuration status to standard out. show_config_status() { log_echo "**** S.U.P.E.R.M.A.N. ${SUPER_VERSION} - CONFIGURATION STATUS ****" check_system check_macos_msu_download [[ "${macos_msu_downloaded_version}" == "FALSE" ]] && echo "Config Status: Mac softwareupdate system cache is empty (no downloaded macOS update/upgrade)." [[ "${macos_msu_downloaded_version}" != "FALSE" ]] && echo "Config Status: Mac softwareupdate system cache is macOS ${macos_msu_downloaded_version}-${macos_msu_downloaded_build}." local super_previous_pid super_previous_pid=$(pgrep -F "${SUPER_PID_FILE}" 2>/dev/null) local super_launch_daemon_active [[ $(launchctl print "system/${SUPER_LAUNCH_DAEMON_LABEL}" 2>&1 | grep -c "system/${SUPER_LAUNCH_DAEMON_LABEL}") -gt 0 ]] && super_launch_daemon_active="TRUE" if [[ -n "${super_previous_pid}" ]]; then [[ "${super_launch_daemon_active}" == "TRUE" ]] && echo "Config Status: The super workflow is RUNNING and LaunchDaemon system/${SUPER_LAUNCH_DAEMON_LABEL} is ENABLED." [[ "${super_launch_daemon_active}" != "TRUE" ]] && echo "Config Status: The super workflow is RUNNING and LaunchDaemon system/${SUPER_LAUNCH_DAEMON_LABEL} is NOT ENABLED." elif [[ "${super_launch_daemon_active}" == "TRUE" ]]; then local next_auto_launch next_auto_launch=$(/usr/libexec/PlistBuddy -c "Print :NextAutoLaunch" "${SUPER_MAIN_PLIST}.plist" 2> /dev/null) [[ "${next_auto_launch}" != "FALSE" ]] && echo "Config Status: LaunchDaemon system/${SUPER_LAUNCH_DAEMON_LABEL} is ENABLED and the super workflow is scheduled to automatically relaunch at ${next_auto_launch}." [[ "${next_auto_launch}" == "FALSE" ]] && echo "Config Status: LaunchDaemon system/${SUPER_LAUNCH_DAEMON_LABEL} is ENABLED but the super workflow is NOT scheduled to automatically relaunch." else echo "Config Status: LaunchDaemon system/${SUPER_LAUNCH_DAEMON_LABEL} is NOT ENABLED." fi if [[ -d "${SUPER_FOLDER}" ]]; then echo -e "\n**** Config Status: Contents of the super working folder located at ${SUPER_FOLDER}:" ls -lah "${SUPER_FOLDER}" | tail -n +4 else echo -e "\n**** Config Error: No super working folder in the expected location at ${SUPER_FOLDER}." fi if [[ -d "${SUPER_CONFIGS_FOLDER}" ]]; then echo -e "\n**** Config Status: Contents of the super configs folder located at ${SUPER_CONFIGS_FOLDER}:" ls -lah "${SUPER_CONFIGS_FOLDER}" | tail -n +4 else echo -e "\nConfig Error: No super working log folder in the expected location at ${SUPER_LOG_FOLDER}." fi if [[ -d "${SUPER_LOG_FOLDER}" ]]; then echo -e "\n**** Config Status: Contents of the super logs folder located at ${SUPER_LOG_FOLDER}:" ls -lah "${SUPER_LOG_FOLDER}" | tail -n +4 else echo -e "\nConfig Error: No super working log folder in the expected location at ${SUPER_LOG_FOLDER}." fi alternate_temporary_plist=$(get_pref "AlternateTemporaryPlist") config_temp_override=$(get_pref_managed "ConfigTempOverride") [[ -z "${config_temp_override}" ]] && config_temp_override="FALSE" if [[ -f "${SUPER_MANAGED_PLIST}.plist" ]]; then [[ "${config_temp_override}" == "FALSE" ]] || { [[ "${config_temp_override}" == "TRUE" ]] && [[ -z "${alternate_temporary_plist}" ]]; } && echo -e "\n**** Config Status: Contents of the CONFIGURATION PROFILE MANAGED super settings file located at ${SUPER_MANAGED_PLIST}.plist:" [[ "${config_temp_override}" == "FALSE" ]] || { [[ "${config_temp_override}" == "TRUE" ]] && [[ -z "${alternate_temporary_plist}" ]]; } && defaults read "${SUPER_MANAGED_PLIST}" | tail -n +2 | sed -e 's/ //g' -e 's/;//g' -e '$d' else echo -e "\n**** Config Status: No CONFIGURATION PROFILE MANAGED super settings file in the expected location at ${SUPER_MANAGED_PLIST}.plist." fi if [[ -n "${alternate_temporary_plist}" ]]; then if [[ -f "${alternate_temporary_plist}.plist" ]]; then { [[ -f "${SUPER_MANAGED_PLIST}.plist" ]] && [[ "${config_temp_override}" == "TRUE" ]]; } && echo -e "\n**** Config Status: The temporary workflow configuration will override conflicting settings in the managed workflow configuration." { [[ -f "${SUPER_MANAGED_PLIST}.plist" ]] && [[ "${config_temp_override}" == "FALSE" ]]; } && echo -e "\n**** Config Status: The managed workflow configuration will override conflicting settings in the temporary workflow configuration. You can reverse this behavior via the ConfigTempOverride managed preference setting." echo -e "\n**** Config Status: Contents of the ALTERNATE TEMPORARY super settings file located at ${alternate_temporary_plist}.plist:" defaults read "${alternate_temporary_plist}" | tail -n +2 | sed -e 's/ //g' -e 's/;//g' -e '$d' echo -e "$(get_config_code_signature "${alternate_temporary_plist}.plist")" { [[ -f "${SUPER_MANAGED_PLIST}.plist" ]] && [[ "${config_temp_override}" == "TRUE" ]]; } && echo -e "\n**** Config Status: Contents of the CONFIGURATION PROFILE MANAGED super settings file located at ${SUPER_MANAGED_PLIST}.plist:" { [[ -f "${SUPER_MANAGED_PLIST}.plist" ]] && [[ "${config_temp_override}" == "TRUE" ]]; } && defaults read "${SUPER_MANAGED_PLIST}" | tail -n +2 | sed -e 's/ //g' -e 's/;//g' -e '$d' else echo -e "\n**** Config Error: No ALTERNATE TEMPORARY super settings file in the expected location at ${alternate_temporary_plist}.plist." fi else echo -e "\n**** Config Status: No ALTERNATE TEMPORARY super settings file." fi alternate_default_plist=$(get_pref "AlternateDefaultPlist") if [[ -n "${alternate_default_plist}" ]]; then if [[ -f "${alternate_default_plist}.plist" ]]; then echo -e "\n**** Config Status: Contents of the ALTERNATE DEFAULT super settings file located at ${alternate_default_plist}.plist:" defaults read "${alternate_default_plist}" | tail -n +2 | sed -e 's/ //g' -e 's/;//g' -e '$d' echo -e "$(get_config_code_signature "${alternate_default_plist}.plist")" else echo -e "\n**** Config Error: No ALTERNATE DEFAULT super settings file in the expected location at ${alternate_default_plist}.plist." fi else echo -e "\n**** Config Status: No ALTERNATE DEFAULT super settings file." fi if [[ -f "${SUPER_MAIN_PLIST}.plist" ]]; then echo -e "\n**** Config Status: Contents of the MAIN super settings file located at ${SUPER_MAIN_PLIST}.plist:" defaults read "${SUPER_MAIN_PLIST}" | tail -n +2 | sed -e 's/ //g' -e 's/;//g' -e '$d' else echo -e "\n**** Config Error: No MAIN super settings file in the expected location at ${SUPER_MAIN_PLIST}.plist." fi if [[ $(ls "${SUPER_CONFIGS_FOLDER}") ]]; then for super_inactive_alt_plist in "${SUPER_CONFIGS_FOLDER:?}"/*.plist; do if [[ "${super_inactive_alt_plist}" != "${alternate_temporary_plist}.plist" ]] && [[ "${super_inactive_alt_plist}" != "${alternate_default_plist}.plist" ]]; then echo -e "\n**** Config Status: Contents of the INACTIVE super settings file located at ${super_inactive_alt_plist}:" defaults read "${super_inactive_alt_plist}" | tail -n +2 | sed -e 's/ //g' -e 's/;//g' -e '$d' echo -e "$(get_config_code_signature "${super_inactive_alt_plist}")" fi done fi local system_profiler_install_history_json system_profiler_install_history_json=$(system_profiler SPInstallHistoryDataType -json) if [[ $(echo "${system_profiler_install_history_json}" | grep -c '_name') -ge 1 ]]; then echo -e "\n**** Config Status: Installation history of macOS system updates:" echo "${system_profiler_install_history_json}" | grep -e '_name' -e 'install_date' | awk -F '"' '{print $4}' | tr '\n' ',' | sed -e 's/Z,/\n/g' -e 's/,/\t/g' | grep -e 'macOS' echo -e "\n**** Config Status: Installation history of macOS security updates:" echo "${system_profiler_install_history_json}" | grep -e '_name' -e 'install_date' | awk -F '"' '{print $4}' | tr '\n' ',' | sed -e 's/Z,/\n/g' -e 's/,/\t\t/g' -e 's/MRTConfigData/MRTConfigData\t/g' | grep -e 'XProtect' -e 'MRTConfigData' else echo -e "\n**** Config Error: The system_profiler command did not return a valid installation history list." fi echo "" log_echo "**** S.U.P.E.R.M.A.N. ${SUPER_VERSION} - EXIT CONFIGURATION STATUS ****" exit 0 } # MARK: *** Configuration & 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 main property list file: SUPER_MAIN_PLIST="${SUPER_FOLDER}/com.macjutsu.super" # No trailing ".plist" readonly SUPER_MAIN_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 alternate configuration folder: SUPER_CONFIGS_FOLDER="${SUPER_FOLDER}/configs" readonly SUPER_CONFIGS_FOLDER # 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 super audit log: SUPER_AUDIT_LOG="${SUPER_LOG_FOLDER}/super-audit.log" readonly SUPER_AUDIT_LOG # Path to the log for the super metrics log: SUPER_METRICS_LOG="${SUPER_LOG_FOLDER}/super-metrics.log" readonly SUPER_METRICS_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.3-b-135/IBM.Notifier.zip" readonly IBM_NOTIFIER_DOWNLOAD_URL # Target version for IBM Notifier.app: IBM_NOTIFIER_TARGET_VERSION="3.2.3" 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.2/mist-cli.2.2.pkg" readonly MIST_CLI_DOWNLOAD_URL # Target version for mist-cli: MIST_CLI_TARGET_VERSION="2.2" 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 Apple Setup (Assistant) Done file. APPLE_SETUP_DONE_FILE="/var/db/.AppleSetupDone" readonly APPLE_SETUP_DONE_FILE # IMPORTANT DETAIL: Changing this path provides no benefit as this is the default (and only) location for this file. # 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 maximum number of minutes between full software update status checks when the results were previously cached. SOFTWARE_STATUS_CACHE_AGE_MINUTES=360 readonly SOFTWARE_STATUS_CACHE_AGE_MINUTES # 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 minimum number of seconds after a recent system startup where it's safe to start a super workflow. If the system has started up too soon, the the workflow is automatically deferred. RECENT_STARTUP_AUTO_DEFERRAL_SECONDS=300 readonly RECENT_STARTUP_AUTO_DEFERRAL_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_WORKFLOW_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_TARGET_VERSION="^([1-9][0-9])(\.[0-9]{1,2})?(\.[0-9]{1,2})?$" readonly REGEX_MACOS_TARGET_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_ACITVE_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_ACITVE_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 config_update="TRUE" case "$1" in -u | -U | --usage) show_usage ;; -h | -H | --help) show_help ;; --version) show_version ;; --install-macos-minor-version-target=*) install_macos_minor_version_target_option="${1##*=}" ;; --install-macos-major-upgrades) install_macos_major_upgrades_option="TRUE" ;; --install-macos-major-upgrades-off) install_macos_major_upgrades_option="FALSE" ;; --install-macos-major-version-target=*) install_macos_major_version_target_option="${1##*=}" ;; --install-macos-bsi-updates) install_macos_bsi_updates_option="TRUE" ;; --install-macos-bsi-updates-off) install_macos_bsi_updates_option="FALSE" ;; --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-safari-updates-without-restarting) install_safari_updates_without_restarting_option="TRUE" ;; --install-safari-updates-without-restarting-off) install_safari_updates_without_restarting_option="FALSE" ;; --install-prioritize-non-restart-updates) install_prioritize_non_restart_updates_option="TRUE" ;; --install-prioritize-non-restart-updates-off) install_prioritize_non_restart_updates_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-require-active-user) workflow_require_active_user_option="TRUE" ;; --workflow-require-active-user-off) workflow_require_active_user_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_option="TRUE" ;; --workflow-reset-super-after-completion-off) workflow_reset_super_after_completion_option="FALSE" ;; --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-deferred-start-file=*) schedule_deferred_start_file_option="${1##*=}" ;; --schedule-deferred-start-days=*) schedule_deferred_start_days_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-safari-update-file=*) display_accessory_safari_update_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##*=}" ;; -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##*=}" ;; --config-edit-main) config_edit_main_option="TRUE" ;; --config-edit=*) config_edit_option="${1##*=}" ;; --config-clone=*) config_clone_option="${1##*=}" ;; --config-delete=*) config_delete_option="${1##*=}" ;; --config-delete-all) config_delete_all_option="TRUE" ;; --config-start-default=*) config_start_default_option="${1##*=}" ;; --config-start-temp=*) config_start_temp_option="${1##*=}" ;; --config-status) config_status_option="TRUE" ;; --auth-ask-user-to-save-password) auth_ask_user_to_save_password_option="TRUE" ;; --auth-ask-user-to-save-password-off) auth_ask_user_to_save_password_option="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##*=}" ;; -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 } # Save a preference by name ${1} and value ${2} to the main ${SUPER_MAIN_PLIST}. set_pref_main() { [[ "${2}" == "FALSE" ]] && defaults write "${SUPER_MAIN_PLIST}" "${1}" -bool false && return 0 [[ "${2}" == "TRUE" ]] && defaults write "${SUPER_MAIN_PLIST}" "${1}" -bool true && return 0 { [[ "${2}" != "FALSE" ]] && [[ "${2}" != "TRUE" ]]; } && defaults write "${SUPER_MAIN_PLIST}" "${1}" -string "${2}" && return 0 } # Save a preference by name ${1} and value ${2} to either the current ${super_alternate_plist} or the ${SUPER_MAIN_PLIST}. set_pref() { local set_plist set_plist="${SUPER_MAIN_PLIST}" [[ -n "${super_alternate_plist}" ]] && set_plist="${super_alternate_plist}" [[ "${2}" == "FALSE" ]] && defaults write "${set_plist}" "${1}" -bool false && return 0 [[ "${2}" == "TRUE" ]] && defaults write "${set_plist}" "${1}" -bool true && return 0 { [[ "${2}" != "FALSE" ]] && [[ "${2}" != "TRUE" ]]; } && defaults write "${set_plist}" "${1}" -string "${2}" && return 0 } # Delete a preference by name ${1} from the main ${SUPER_MAIN_PLIST}. delete_pref_main() { defaults delete "${SUPER_MAIN_PLIST}" "${1}" 2>/dev/null } # Delete a preference by name ${1} from either the current ${super_alternate_plist} or the ${SUPER_MAIN_PLIST}. delete_pref() { if [[ -n "${super_alternate_plist}" ]]; then defaults delete "${super_alternate_plist}" "${1}" 2>/dev/null else defaults delete "${SUPER_MAIN_PLIST}" "${1}" 2>/dev/null fi } get_pref_managed() { { [[ "${config_edit_main_option}" == "TRUE" ]] || [[ -n "${config_edit_option}" ]] || [[ -n "${config_delete_option}" ]] || [[ "${config_delete_all_option}" == "TRUE" ]]; } && return 1 local preference_result local preference_source if [[ -f "${SUPER_MANAGED_PLIST}.plist" ]] && [[ -n "${alternate_temporary_plist}" ]] && [[ "${config_temp_override}" == "TRUE" ]]; then preference_result=$(defaults read "${super_alternate_plist}" "${1}" 2>/dev/null) [[ -n "${preference_result}" ]] && preference_source="${super_alternate_plist}" fi if [[ -f "${SUPER_MANAGED_PLIST}.plist" ]] && [[ -z "${preference_result}" ]]; then preference_result=$(defaults read "${SUPER_MANAGED_PLIST}" "${1}" 2>/dev/null) [[ -n "${preference_result}" ]] && preference_source="${SUPER_MANAGED_PLIST}" fi if [[ -n "${preference_result}" ]]; then local preference_type preference_type=$(defaults read-type "${preference_source}" "${1}" 2>/dev/null) if [[ $(echo "${preference_type}" | grep -c 'boolean') -gt 0 ]]; then { [[ "${preference_result}" == "0" ]] || [[ "${preference_result}" == "FALSE" ]]; } && echo "FALSE" && return 0 { [[ "${preference_result}" == "1" ]] || [[ "${preference_result}" == "TRUE" ]]; } && echo "TRUE" && return 0 else echo "${preference_result}" && return 0 fi fi return 1 } # Gets the value of a preference by name ${1}. This searches for values in ${super_alternate_plist}, and ${SUPER_MAIN_PLIST}. The first found in this order is returned. get_pref() { local preference_result [[ -n "${super_alternate_plist}" ]] && preference_result=$(defaults read "${super_alternate_plist}" "${1}" 2>/dev/null) [[ -z "${preference_result}" ]] && preference_result=$(defaults read "${SUPER_MAIN_PLIST}" "${1}" 2>/dev/null) if [[ -n "${preference_result}" ]]; then local preference_type [[ -n "${super_alternate_plist}" ]] && preference_type=$(defaults read-type "${super_alternate_plist}" "${1}" 2>/dev/null) [[ -z "${preference_type}" ]] && preference_type=$(defaults read-type "${SUPER_MAIN_PLIST}" "${1}" 2>/dev/null) if [[ $(echo "${preference_type}" | grep -c 'boolean') -gt 0 ]]; then { [[ "${preference_result}" == "0" ]] || [[ "${preference_result}" == "FALSE" ]]; } && echo "FALSE" && return 0 { [[ "${preference_result}" == "1" ]] || [[ "${preference_result}" == "TRUE" ]]; } && echo "TRUE" && return 0 else echo "${preference_result}" && return 0 fi fi return 1 } # Validate the code signing signature of the specified ${1} plist and return "UNSIGNED", "INVALID", or the output the valid code signature details. get_config_code_signature(){ local codesign_verify_response codesign_verify_response=$(codesign --verify -v "${1}" 2>&1) [[ $(echo "${codesign_verify_response}" | grep -c 'not signed') -gt 0 ]] && echo -e "** UNSIGNED **" && return 0 [[ $(echo "${codesign_verify_response}" | grep -c 'invalid') -gt 0 ]] && echo -e "** INVALID CODE SIGNATURE **\n${codesign_verify_response}" && return 0 [[ $(echo "${codesign_verify_response}" | grep -c 'valid') -gt 0 ]] && echo -e "** VALID CODE SIGNATURE **\n$(codesign --display -v "${1}" 2>&1)" } # If needed, reset any parameters stored in ${SUPER_MAIN_PLIST}. manage_preference_resets() { # First handle any main preference deletion requests. if [[ "${reset_super_option}" == "TRUE" ]] || [[ $(get_pref "WorkflowResetSuperAfterCompletionNow") == "TRUE" ]]; then log_super_audit "Status: Resetting all local (non-managed and non-authentication) preferences." unset super_status_backup # 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=$(get_pref "AuthAskUserToSavePassword") [[ "${verbose_mode}" == "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=$(get_pref "AuthLocalAccount") [[ "${verbose_mode}" == "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=$(get_pref "AuthServiceAccount") [[ "${verbose_mode}" == "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=$(get_pref "AuthJamfClient") [[ "${verbose_mode}" == "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=$(get_pref "AuthJamfAccount") [[ "${verbose_mode}" == "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=$(get_pref "LocalAccount") [[ "${verbose_mode}" == "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=$(get_pref "SuperAccount") [[ "${verbose_mode}" == "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=$(get_pref "JamfAccount") [[ "${verbose_mode}" == "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_MAIN_PLIST}" >/dev/null 2>&1 set_pref_main "SuperVersion" "${SUPER_VERSION}" defaults read "${SUPER_MAIN_PLIST}" >/dev/null 2>&1 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 [[ "${verbose_mode}" == "TRUE" ]] && set_pref_main "VerboseMode" "TRUE" # Restore any saved preferences from backup. [[ "${auth_ask_user_to_save_password_backup}" == "TRUE" ]] && set_pref_main "AuthAskUserToSavePassword" "TRUE" [[ "${auth_local_account_backup}" == "TRUE" ]] && set_pref_main "AuthLocalAccount" "TRUE" [[ "${auth_service_account_backup}" == "TRUE" ]] && set_pref_main "AuthServiceAccount" "TRUE" [[ "${auth_jamf_client_backup}" == "TRUE" ]] && set_pref_main "AuthJamfClient" "TRUE" [[ "${auth_jamf_account_backup}" == "TRUE" ]] && set_pref_main "AuthJamfAccount" "TRUE" [[ -n "${auth_legacy_local_account_backup}" ]] && set_pref_main "LocalAccount" "${auth_legacy_local_account_backup}" [[ -n "${auth_legacy_super_account_backup}" ]] && set_pref_main "SuperAccount" "${auth_legacy_super_account_backup}" [[ -n "${auth_legacy_jamf_account_backup}" ]] && set_pref_main "JamfAccount" "${auth_legacy_jamf_account_backup}" else # Lesser main preference delete/reset options. if [[ "${deferral_timer_reset_all_option}" == "TRUE" ]]; then log_super_audit "Status: Resetting all deferral timer options in main preferences." delete_pref_main "DeferralTimerDefault" delete_pref_main "DeferralTimerMenu" delete_pref_main "DeferralTimerFocus" delete_pref_main "DeferralTimerError" delete_pref_main "DeferralTimerWorkflowRelaunch" fi if [[ "${scheduled_install_delete_all_option}" == "TRUE" ]]; then log_super_audit "Status: Deleting all scheduled installation options in main preferences." delete_pref_main "ScheduledInstallDays" delete_pref_main "ScheduledInstallDate" delete_pref_main "ScheduledInstallUserChoice" delete_pref_main "ScheduledInstallReminder" delete_pref_main "WorkflowScheduledInstall" fi if [[ "${deadline_count_delete_all_option}" == "TRUE" ]]; then log_super_audit "Status: Deleting all deadline count options in main preferences." delete_pref_main "DeadlineCountFocus" delete_pref_main "DeadlineCountSoft" delete_pref_main "DeadlineCountHard" delete_pref_main "DeadlineCounterFocus" delete_pref_main "DeadlineCounterSoft" delete_pref_main "DeadlineCounterHard" fi if [[ "${deadline_days_delete_all_option}" == "TRUE" ]]; then log_super_audit "Status: Deleting all deadline days options in main preferences." delete_pref_main "DeadlineDaysFocus" delete_pref_main "DeadlineDaysSoft" delete_pref_main "DeadlineDaysHard" fi if [[ "${deadline_date_delete_all_option}" == "TRUE" ]]; then log_super_audit "Status: Deleting all deadline date options in main preferences." delete_pref_main "DeadlineDateFocus" delete_pref_main "DeadlineDateSoft" delete_pref_main "DeadlineDateHard" fi if [[ "${dialog_timeout_delete_all_option}" == "TRUE" ]]; then log_super_audit "Status: Deleting all dialog timeout options in main preferences." delete_pref_main "DialogTimeoutDefault" delete_pref_main "DialogTimeoutUserChoice" delete_pref_main "DialogTimeoutSoftDeadline" delete_pref_main "DialogTimeoutUserAuth" delete_pref_main "DialogTimeoutInsufficientStorage" delete_pref_main "DialogTimeoutPowerRequired" fi fi # Handle any alternate workflow configuration delete requests. alternate_default_plist=$(get_pref "AlternateDefaultPlist") alternate_temporary_plist=$(get_pref "AlternateTemporaryPlist") config_delete_reset_workflow="FALSE" if [[ "${config_delete_all_option}" == "TRUE" ]]; then log_super_audit "Status: Deleting all workflow configurations in ${SUPER_CONFIGS_FOLDER}." for delete_alt_plist in "${SUPER_CONFIGS_FOLDER:?}"/*; do log_super_audit "Status: Deleting alternate workflow configuration ${delete_alt_plist}." rm -Rf "${delete_alt_plist}" >/dev/null 2>&1 done if [[ -n "${alternate_default_plist}" ]]; then log_super_audit "Status: Workflow is no longer using deleted alternate default configuration at ${alternate_default_plist}.plist." delete_pref_main "AlternateDefaultPlist" config_delete_reset_workflow="TRUE" fi if [[ -n "${alternate_temporary_plist}" ]]; then log_super_audit "Status: Workflow is no longer using deleted alternate temporary configuration at ${alternate_temporary_plist}.plist." delete_pref_main "AlternateTemporaryPlist" config_delete_reset_workflow="TRUE" fi elif [[ -n "${config_delete_option}" ]]; then # Only deleting an individual alternate plist. if [[ -f "${SUPER_CONFIGS_FOLDER}/com.macjutsu.super.${config_delete_option}.plist" ]]; then log_super_audit "Status: Deleting alternate workflow configuration ${SUPER_CONFIGS_FOLDER}/com.macjutsu.super.${config_delete_option}.plist." rm -Rf "${SUPER_CONFIGS_FOLDER:?}/com.macjutsu.super.${config_delete_option}.plist" >/dev/null 2>&1 else log_super "Error: Unable to locate alternate workflow configuration for deletion at ${SUPER_CONFIGS_FOLDER}/com.macjutsu.super.${config_delete_option}.plist." fi if [[ "${alternate_default_plist}" == "${SUPER_CONFIGS_FOLDER}/com.macjutsu.super.${config_delete_option}" ]]; then log_super_audit "Status: Workflow is no longer using deleted alternate default configuration at ${alternate_default_plist}.plist." delete_pref_main "AlternateDefaultPlist" config_delete_reset_workflow="TRUE" fi if [[ "${alternate_temporary_plist}" == "${SUPER_CONFIGS_FOLDER}/com.macjutsu.super.${config_delete_option}" ]]; then log_super_audit "Status: Workflow is no longer using deleted alternate temporary configuration at ${alternate_temporary_plist}.plist." delete_pref_main "AlternateTemporaryPlist" config_delete_reset_workflow="TRUE" fi fi unset alternate_default_plist unset alternate_temporary_plist [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: config_delete_reset_workflow is: ${config_delete_reset_workflow}" } # Validate non-authentication parameters and if necessary modifications to local preferences. Any errors set ${parameter_error}. manage_parameter_options() { local previous_ifs local extract_date local sanitized_date local extract_hours local extract_minutes parameter_error="FALSE" config_temp_override=$(get_pref_managed "ConfigTempOverride") [[ -z "${config_temp_override}" ]] && config_temp_override="FALSE" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: config_temp_override: ${config_temp_override}" config_code_signature=$(get_pref_managed "ConfigCodeSignature") [[ -z "${config_code_signature}" ]] && config_code_signature="FALSE" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: config_code_signature is: ${config_code_signature}" [[ -f "${SUPER_MANAGED_PLIST}.plist" ]] && log_super "Status: Active managed workflow configuration located at ${SUPER_MANAGED_PLIST}.plist." [[ "${verbose_mode}" == "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)" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: Main preference file before startup validation: ${SUPER_MAIN_PLIST}:\n$(defaults read "${SUPER_MAIN_PLIST}" 2>/dev/null)" # First handle incompatible combinations of alternate config options. { [[ "${config_edit_main_option}" == "TRUE" ]] && [[ -n "${config_edit_option}" ]]; } && log_super "Parameter Error: You can not use both the --config-edit-main and the --config-edit=ConfigurationName options a the same time. Instead, use multiple seperate runs of super for each configuration you want to edit." && parameter_error="TRUE" { [[ -n "${config_clone_option}" ]] && [[ -z "${config_edit_option}" ]]; } && log_super "Parameter Error: To use the --config-clone=SourceConfigurationName option you must also use the --config-edit=ConfigurationName option in order to specify a desination for the cloned configuration." && parameter_error="TRUE" { { [[ "${config_edit_main_option}" == "TRUE" ]] || [[ -n "${config_edit_option}" ]]; } && { [[ -n "${config_start_default_option}" ]] && [[ -n "${config_start_temp_option}" ]]; }; } && log_super "Parameter Error: You can not use both a --config-edit* option and a --config-start* option a the same time. Instead, first run super to edit configurations and then run super again to start configurations." && parameter_error="TRUE" { { [[ -n "${config_delete_option}" ]] || [[ -n "${config_delete_all_option}" ]]; } && { [[ -n "${config_start_default_option}" ]] && [[ -n "${config_start_temp_option}" ]]; }; } && log_super "Parameter Error: You can not use both a --config-delete* option and a --config-start* option a the same time. Instead, first run super to delete configurations and then run super again to start configurations." && parameter_error="TRUE" [[ "${parameter_error}" == "TRUE" ]] && return 0 # Evaluate ${config_edit_option} and ${config_clone_option}, if appropriate then set ${super_alternate_plist}. This also logs changes to configuration files and sets a new ConfigEditTimestamp. if [[ -n "${config_clone_option}" ]]; then local alternate_clone_plist alternate_clone_plist="${SUPER_CONFIGS_FOLDER}/com.macjutsu.super.${config_clone_option}" super_alternate_plist="${SUPER_CONFIGS_FOLDER}/com.macjutsu.super.${config_edit_option}" if [[ -f "${alternate_clone_plist}.plist" ]]; then if [[ ! -f "${super_alternate_plist}.plist" ]]; then log_super_audit "Status: Cloning workflow configuration ${config_clone_option} to new alternate preferences at ${super_alternate_plist}.plist." else log_super_audit "Status: Cloning workflow configuration ${config_clone_option} to replace alternate preferences at ${super_alternate_plist}.plist." rm -Rf "${SUPER_CONFIGS_FOLDER:?}/com.macjutsu.super.${config_edit_option}.plist" >/dev/null 2>&1 fi cp "${alternate_clone_plist}.plist" "${super_alternate_plist}.plist" >/dev/null 2>&1 set_pref "ConfigEditTimestamp" "$(date +%Y-%m-%d:%H:%M:%S)" else log_super "Parameter Error: Unable to clone workflow configuration because the preferences could not be located at ${alternate_clone_plist}.plist." parameter_error="TRUE" fi elif [[ -n "${config_edit_option}" ]]; then super_alternate_plist="${SUPER_CONFIGS_FOLDER}/com.macjutsu.super.${config_edit_option}" log_super_audit "Status: Saving workflow configuration to alternate preferences at ${super_alternate_plist}.plist." set_pref "ConfigEditTimestamp" "$(date +%Y-%m-%d:%H:%M:%S)" fi if [[ -z "${super_alternate_plist}" ]] && [[ "${config_update}" == "TRUE" ]]; then log_super_audit "Status: Saving workflow configuration to main preferences at ${SUPER_MAIN_PLIST}.plist." set_pref_main "ConfigEditTimestamp" "$(date +%Y-%m-%d:%H:%M:%S)" fi [[ "${parameter_error}" == "TRUE" ]] && return 0 # If the workflow should start, then evaluate ${config_start_default_option} and ${config_start_temp_option} and possibly save to ${SUPER_MAIN_PLIST} and set ${super_alternate_plist}. # If the workflow should start, then evaluate ${config_start_default_option} and ${config_start_temp_option} and possibly save to ${SUPER_MAIN_PLIST} and set ${super_alternate_plist}. if [[ "${config_edit_main_option}" != "TRUE" ]] && [[ -z "${config_edit_option}" ]] && [[ -z "${config_delete_option}" ]] && [[ -z "${config_delete_all_option}" ]]; then if [[ "${config_start_default_option}" == "X" ]]; then unset_alternate_plist=$(get_pref "AlternateDefaultPlist") if [[ -n "${unset_alternate_plist}" ]]; then delete_pref_main "AlternateDefaultPlist" delete_pref_main "AlternateDefaultPlistStatus" log_super_audit "Status: Workflow is no longer using alternate default configuration at ${unset_alternate_plist}.plist." unset unset_alternate_plist fi unset config_start_default_option fi if [[ -n "${config_start_default_option}" ]]; then alternate_default_plist="${SUPER_CONFIGS_FOLDER}/com.macjutsu.super.${config_start_default_option}" if [[ -f "${alternate_default_plist}.plist" ]]; then log_super_audit "Status: Setting default workflow configuration to ${alternate_default_plist}.plist." set_pref_main "AlternateDefaultPlist" "${alternate_default_plist}" else log_super "Parameter Error: Unable to set new default workflow configuration because the preferences could not be located at ${alternate_default_plist}.plist." parameter_error="TRUE" fi fi if [[ "${config_start_temp_option}" == "X" ]]; then unset_alternate_plist=$(get_pref "AlternateTemporaryPlist") if [[ -n "${unset_alternate_plist}" ]]; then delete_pref_main "AlternateTemporaryPlist" delete_pref_main "AlternateTemporaryPlistStatus" log_super_audit "Status: Workflow is no longer using alternate temporary configuration at ${unset_alternate_plist}.plist." unset unset_alternate_plist fi unset config_start_temp_option fi if [[ -n "${config_start_temp_option}" ]]; then alternate_temporary_plist="${SUPER_CONFIGS_FOLDER}/com.macjutsu.super.${config_start_temp_option}" if [[ -f "${alternate_temporary_plist}.plist" ]]; then log_super_audit "Status: Setting temporary workflow configuration to ${alternate_temporary_plist}.plist." set_pref_main "AlternateTemporaryPlist" "${alternate_temporary_plist}" else log_super "Parameter Error: Unable to set new temporary workflow configuration because the preferences could not be located at ${alternate_temporary_plist}.plist." parameter_error="TRUE" fi fi { [[ -n "${config_start_default_option}" ]] && [[ -n "${config_start_temp_option}" ]]; } && log_super "Parameter Warning: When starting with both the --config-start-default=* and the --config-start-temp=* options at the same time, the temporary workflow configuration always starts first. The default workflow configuration will only start after the temporary workflow has completed." [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: alternate_default_plist is: ${alternate_default_plist}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: alternate_temporary_plist is: ${alternate_temporary_plist}" [[ "${parameter_error}" == "TRUE" ]] && return 0 # Evaluate previously saved AlternateDefaultPlist and AlternateTemporaryPlist. If valid set ${alternate_default_plist} and/or ${alternate_temporary_plist}. [[ -z "${alternate_default_plist}" ]] && alternate_default_plist=$(get_pref "AlternateDefaultPlist") if [[ -n "${alternate_default_plist}" ]]; then if [[ -f "${alternate_default_plist}.plist" ]]; then get_config_code_signature_response=$(get_config_code_signature "${alternate_default_plist}.plist") [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: get_config_code_signature_response is:\n${get_config_code_signature_response}" if [[ $(echo "${get_config_code_signature_response}" | grep -c ' UNSIGNED') -gt 0 ]] || [[ $(echo "${get_config_code_signature_response}" | grep -c ' VALID') -gt 0 ]]; then if [[ "${config_code_signature}" == "FALSE" ]] || [[ $(echo "${get_config_code_signature_response}" | grep -c "${config_code_signature}") -gt 0 ]]; then set_pref_main "AlternateDefaultPlistStatus" "VALID" else log_super "Parameter Error: The default workflow configuration at ${alternate_default_plist}.plist does not match the code signature requirement of ${config_code_signature}." set_pref_main "AlternateDefaultPlistStatus" "UNMATCHED" parameter_error="TRUE" fi else log_super "Parameter Error: The default workflow configuration at ${alternate_default_plist}.plist has an invalid code signature." set_pref_main "AlternateDefaultPlistStatus" "INVALID" parameter_error="TRUE" fi unset get_config_code_signature_response else log_super "Parameter Error: Unable to locate default workflow configuration at ${alternate_default_plist}.plist." set_pref_main "AlternateDefaultPlistStatus" "MISSING" parameter_error="TRUE" fi fi [[ -z "${alternate_temporary_plist}" ]] && alternate_temporary_plist=$(get_pref "AlternateTemporaryPlist") if [[ -n "${alternate_temporary_plist}" ]]; then if [[ -f "${alternate_temporary_plist}.plist" ]]; then get_config_code_signature_response=$(get_config_code_signature "${alternate_temporary_plist}.plist") [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: get_config_code_signature_response is:\n${get_config_code_signature_response}" if [[ $(echo "${get_config_code_signature_response}" | grep -c ' UNSIGNED') -gt 0 ]] || [[ $(echo "${get_config_code_signature_response}" | grep -c ' VALID') -gt 0 ]]; then if [[ "${config_code_signature}" == "FALSE" ]] || [[ $(echo "${get_config_code_signature_response}" | grep -c "${config_code_signature}") -gt 0 ]]; then set_pref_main "AlternateTemporaryPlistStatus" "VALID" else log_super "Parameter Error: The default workflow configuration at ${alternate_default_plist}.plist does not match the code signature requirement of ${config_code_signature}." set_pref_main "AlternateTemporaryPlistStatus" "UNMATCHED" parameter_error="TRUE" fi else log_super "Parameter Error: The temporary workflow configuration at ${alternate_temporary_plist}.plist has an invalid code signature." set_pref_main "AlternateTemporaryPlistStatus" "INVALID" parameter_error="TRUE" fi unset get_config_code_signature_response else log_super "Parameter Error: Unable to locate temporary workflow configuration at ${alternate_temporary_plist}.plist." set_pref_main "AlternateTemporaryPlistStatus" "MISSING" parameter_error="TRUE" fi fi [[ "${parameter_error}" == "TRUE" ]] && return 0 # At this point if there are valid ${alternate_default_plist} or ${alternate_temporary_plist} options, evaluate both and set ${super_alternate_plist}. if [[ -n "${alternate_temporary_plist}" ]]; then super_alternate_plist="${alternate_temporary_plist}" log_super "Status: Active temporary workflow configuration located at ${super_alternate_plist}.plist." { [[ -f "${SUPER_MANAGED_PLIST}.plist" ]] && [[ "${config_temp_override}" == "TRUE" ]]; } && log_super "Warning: The temporary workflow configuration will override conflicting settings in the managed workflow configuration." { [[ -f "${SUPER_MANAGED_PLIST}.plist" ]] && [[ "${config_temp_override}" == "FALSE" ]]; } && log_super "Warning: The managed workflow configuration will override conflicting settings in the temporary workflow configuration. You can reverse this behavior via the ConfigTempOverride managed preference setting." elif [[ -n "${alternate_default_plist}" ]]; then super_alternate_plist="${alternate_default_plist}" log_super "Status: Active default workflow configuration located at ${super_alternate_plist}.plist." [[ -f "${SUPER_MANAGED_PLIST}.plist" ]] && log_super "Warning: The managed workflow configuration will override conflicting settings in the default workflow configuration." fi fi [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: super_alternate_plist is: ${super_alternate_plist}" { [[ "${verbose_mode}" == "TRUE" ]] && [[ -n "${super_alternate_plist}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: Alternate preference file before startup validation: ${super_alternate_plist}:\n$(defaults read "${super_alternate_plist}" 2>/dev/null)" # Validate ${install_macos_minor_version_target_option} and if a valid set the ${install_macos_minor_version_target} parameter. install_macos_minor_version_target=$(get_pref_managed "InstallMacOSMinorVersionTarget") if [[ "${install_macos_minor_version_target}" == "X" ]] || { [[ -z "${install_macos_minor_version_target}" ]] && [[ "${install_macos_minor_version_target_option}" == "X" ]]; }; then delete_pref "InstallMacOSMinorVersionTarget" unset install_macos_minor_version_target elif [[ -z "${install_macos_minor_version_target}" ]] && [[ -n "${install_macos_minor_version_target_option}" ]]; then install_macos_minor_version_target="${install_macos_minor_version_target_option}" set_pref "InstallMacOSMinorVersionTarget" "${install_macos_minor_version_target}" fi [[ -z "${install_macos_minor_version_target}" ]] && install_macos_minor_version_target=$(get_pref "InstallMacOSMinorVersionTarget") { [[ -n "${install_macos_minor_version_target}" ]] && [[ ! "${install_macos_minor_version_target}" =~ ${REGEX_MACOS_TARGET_VERSION} ]]; } && log_super "Parameter Error: The --install-macos-minor-version-target=number value must be a contemporary macOS version number (15.0 | 15.1 | 15.1.1)." && parameter_error="TRUE" { [[ -n "${install_macos_minor_version_target}" ]] && [[ $(echo "${install_macos_minor_version_target}" | grep -c '\.') -eq 0 ]]; } && log_super "Parameter Error: The --install-macos-minor-version-target=number value must inlucde macOS major and minor version numbers (15.0 | 15.1 | 15.1.1)." && parameter_error="TRUE" { [[ "${verbose_mode}" == "TRUE" ]] && [[ -n "${install_macos_minor_version_target}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: install_macos_minor_version_target is: ${install_macos_minor_version_target}" [[ -n "${install_macos_minor_version_target}" ]] && install_macos_minor_version_normalized_target=$(echo "${install_macos_minor_version_target}" | awk -F'.' '{printf "%02d%02d%02d", $1, $2, $3}') { [[ "${verbose_mode}" == "TRUE" ]] && [[ -n $install_macos_minor_version_normalized_target ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: install_macos_minor_version_normalized_target is: ${install_macos_minor_version_normalized_target}" # Validate the ${install_macos_minor_updates} and ${install_macos_minor_version_normalized_target} as compared to the current macOS ${macos_version_normalized}. install_macos_minor_updates="TRUE" if [[ -n $install_macos_minor_version_normalized_target ]] && [[ ${install_macos_minor_version_normalized_target:0:2} -gt $macos_version_major ]]; then log_super "Parameter Warning: The --install-macos-minor-version-target=${install_macos_minor_version_target} option will be ignored as it represents a major upgrade comparted to the current ${macos_version_full}." unset install_macos_minor_version_target unset install_macos_minor_version_normalized_target elif [[ -n $install_macos_minor_version_normalized_target ]] && [[ $install_macos_minor_version_normalized_target -le $macos_version_normalized ]]; then [[ $install_macos_minor_version_normalized_target -lt $macos_version_normalized ]] && log_super "Parameter Warning: The --install-macos-minor-version-target=${install_macos_minor_version_target} option is less than current ${macos_version_full}. Disabling macOS minor update workflow." [[ $install_macos_minor_version_normalized_target -eq $macos_version_normalized ]] && log_super "Status: The --install-macos-minor-version-target=${install_macos_minor_version_target} option is the same as current ${macos_version_full}. Disabling macOS minor update workflow." install_macos_minor_updates="FALSE" fi [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: install_macos_minor_updates is: ${install_macos_minor_updates}" # Manage ${install_macos_major_upgrades} parameter. install_macos_major_upgrades=$(get_pref_managed "InstallMacOSMajorUpgrades") { [[ -z "${install_macos_major_upgrades}" ]] && [[ -n "${install_macos_major_upgrades_option}" ]]; } && install_macos_major_upgrades="${install_macos_major_upgrades_option}" && set_pref "InstallMacOSMajorUpgrades" "${install_macos_major_upgrades_option}" [[ -z "${install_macos_major_upgrades}" ]] && install_macos_major_upgrades=$(get_pref "InstallMacOSMajorUpgrades") { [[ "${verbose_mode}" == "TRUE" ]] && [[ -n "${install_macos_major_upgrades}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: install_macos_major_upgrades is: ${install_macos_major_upgrades}" # Validate ${install_macos_major_version_target_option} and if a valid set the ${install_macos_major_version_target} parameter. install_macos_major_version_target=$(get_pref_managed "InstallMacOSMajorVersionTarget") if [[ "${install_macos_major_version_target}" == "X" ]] || { [[ -z "${install_macos_major_version_target}" ]] && [[ "${install_macos_major_version_target_option}" == "X" ]]; }; then delete_pref "InstallMacOSMajorVersionTarget" unset install_macos_major_version_target elif [[ -z "${install_macos_major_version_target}" ]] && [[ -n "${install_macos_major_version_target_option}" ]]; then install_macos_major_version_target="${install_macos_major_version_target_option}" set_pref "InstallMacOSMajorVersionTarget" "${install_macos_major_version_target}" fi [[ -z "${install_macos_major_version_target}" ]] && install_macos_major_version_target=$(get_pref "InstallMacOSMajorVersionTarget") { [[ -n "${install_macos_major_version_target}" ]] && [[ ! "${install_macos_major_version_target}" =~ ${REGEX_MACOS_TARGET_VERSION} ]]; } && log_super "Parameter Error: The --install-macos-major-version-target=number value must be a contemporary macOS version number (15 | 15.1 | 15.1.1)." && parameter_error="TRUE" { [[ "${verbose_mode}" == "TRUE" ]] && [[ -n "${install_macos_major_version_target}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: install_macos_major_version_target is: ${install_macos_major_version_target}" [[ -n "${install_macos_major_version_target}" ]] && install_macos_major_version_normalized_target=$(echo "${install_macos_major_version_target}" | awk -F'.' '{printf "%02d%02d%02d", $1, $2, $3}') { [[ "${verbose_mode}" == "TRUE" ]] && [[ -n $install_macos_major_version_normalized_target ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: install_macos_major_version_normalized_target is: ${install_macos_major_version_normalized_target}" # Validate the ${install_macos_major_upgrades} and ${install_macos_major_version_normalized_target} as compared to the current macOS ${macos_version_normalized}. if [[ "${install_macos_major_upgrades}" == "TRUE" ]] && [[ -n $install_macos_major_version_normalized_target ]] && [[ $install_macos_major_version_normalized_target -le $macos_version_normalized ]]; then [[ $install_macos_major_version_normalized_target -lt $macos_version_normalized ]] && log_super "Parameter Warning: The --install-macos-major-version-target=${install_macos_major_version_target} option is less than current ${macos_version_full}. Disabling macOS major upgrade workflow." [[ $install_macos_major_version_normalized_target -eq $macos_version_normalized ]] && log_super "Status: The --install-macos-major-version-target=${install_macos_major_version_target} option is the same as current ${macos_version_full}. Disabling macOS major upgrade workflow." install_macos_major_upgrades="FALSE" fi [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: install_macos_major_upgrades is: ${install_macos_major_upgrades}" # Manage the ${install_macos_bsi_updates} and ${install_rapid_security_responses} parameters. install_macos_bsi_updates=$(get_pref_managed "InstallMacOSBSIUpdates") { [[ -z "${install_macos_bsi_updates}" ]] && [[ -n "${install_macos_bsi_updates_option}" ]]; } && install_macos_bsi_updates="${install_macos_bsi_updates_option}" && set_pref "InstallMacOSBSIUpdates" "${install_macos_bsi_updates_option}" [[ -z "${install_macos_bsi_updates}" ]] && install_macos_bsi_updates=$(get_pref "InstallMacOSBSIUpdates") install_rapid_security_responses=$(get_pref_managed "InstallRapidSecurityResponses") { [[ -z "${install_rapid_security_responses}" ]] && [[ -n "${install_rapid_security_responses_option}" ]]; } && install_rapid_security_responses="${install_rapid_security_responses_option}" && set_pref "InstallRapidSecurityResponses" "${install_rapid_security_responses_option}" [[ -z "${install_rapid_security_responses}" ]] && install_rapid_security_responses=$(get_pref "InstallRapidSecurityResponses") { [[ "${verbose_mode}" == "TRUE" ]] && [[ -n "${install_rapid_security_responses}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: install_rapid_security_responses is: ${install_rapid_security_responses}" if [[ -n "${install_rapid_security_responses}" ]]; then log_super "Parameter Warning: The --install-rapid-security-responses option is deprecated and will be removed in a future version of super. It's superseded by the --install-macos-bsi-updates option." [[ -z "${install_macos_bsi_updates}" ]] && install_macos_bsi_updates="${install_rapid_security_responses}" unset install_rapid_security_responses fi { [[ "${verbose_mode}" == "TRUE" ]] && [[ -n "${install_macos_bsi_updates}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: install_macos_bsi_updates is: ${install_macos_bsi_updates}" # Manage ${install_non_system_updates_without_restarting} parameter. install_non_system_updates_without_restarting=$(get_pref_managed "InstallNonSystemUpdatesWithoutRestarting") { [[ -z "${install_non_system_updates_without_restarting}" ]] && [[ -n "${install_non_system_updates_without_restarting_option}" ]]; } && install_non_system_updates_without_restarting="${install_non_system_updates_without_restarting_option}" && set_pref "InstallNonSystemUpdatesWithoutRestarting" "${install_non_system_updates_without_restarting_option}" [[ -z "${install_non_system_updates_without_restarting}" ]] && install_non_system_updates_without_restarting=$(get_pref "InstallNonSystemUpdatesWithoutRestarting") { [[ "${verbose_mode}" == "TRUE" ]] && [[ -n "${install_non_system_updates_without_restarting}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: install_non_system_updates_without_restarting is: ${install_non_system_updates_without_restarting}" # Manage ${install_safari_updates_without_restarting} parameter. install_safari_updates_without_restarting=$(get_pref_managed "InstallSafariUpdatesWithoutRestarting") { [[ -z "${install_safari_updates_without_restarting}" ]] && [[ -n "${install_safari_updates_without_restarting_option}" ]]; } && install_safari_updates_without_restarting="${install_safari_updates_without_restarting_option}" && set_pref "InstallSafariUpdatesWithoutRestarting" "${install_safari_updates_without_restarting_option}" [[ -z "${install_safari_updates_without_restarting}" ]] && install_safari_updates_without_restarting=$(get_pref "InstallSafariUpdatesWithoutRestarting") { [[ "${verbose_mode}" == "TRUE" ]] && [[ -n "${install_safari_updates_without_restarting}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: install_safari_updates_without_restarting is: ${install_safari_updates_without_restarting}" # Manage ${install_prioritize_non_restart_updates} parameter. install_prioritize_non_restart_updates=$(get_pref_managed "InstallPrioritizeNonRestartUpdates") { [[ -z "${install_prioritize_non_restart_updates}" ]] && [[ -n "${install_prioritize_non_restart_updates_option}" ]]; } && install_prioritize_non_restart_updates="${install_prioritize_non_restart_updates_option}" && set_pref "InstallPrioritizeNonRestartUpdates" "${install_prioritize_non_restart_updates_option}" [[ -z "${install_prioritize_non_restart_updates}" ]] && install_prioritize_non_restart_updates=$(get_pref "InstallPrioritizeNonRestartUpdates") { [[ "${install_prioritize_non_restart_updates}" == "TRUE" ]] && { [[ -z "${install_non_system_updates_without_restarting}" ]] || [[ "${install_non_system_updates_without_restarting}" == "FALSE" ]]; } && { [[ -z "${install_safari_updates_without_restarting}" ]] || [[ "${install_safari_updates_without_restarting}" == "FALSE" ]]; }; } && log_super "Parameter Error: To use the --install-prioritize-non-restart-updates option, you must also specifiy either the --install-non-system-updates-without-restarting option or the --install-safari-updates-without-restarting option." && parameter_error="TRUE" { [[ "${verbose_mode}" == "TRUE" ]] && [[ -n "${install_prioritize_non_restart_updates}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: install_prioritize_non_restart_updates is: ${install_prioritize_non_restart_updates}" # Validate ${install_jamf_policy_triggers_option} and if a valid set the ${install_jamf_policy_triggers} parameter. install_jamf_policy_triggers=$(get_pref_managed "InstallJamfPolicyTriggers") if [[ "${install_jamf_policy_triggers}" == "X" ]] || { [[ -z "${install_jamf_policy_triggers}" ]] && [[ "${install_jamf_policy_triggers_option}" == "X" ]]; }; then delete_pref "InstallJamfPolicyTriggers" unset install_jamf_policy_triggers elif [[ -z "${install_jamf_policy_triggers}" ]] && [[ -n "${install_jamf_policy_triggers_option}" ]]; then install_jamf_policy_triggers="${install_jamf_policy_triggers_option}" set_pref "InstallJamfPolicyTriggers" "${install_jamf_policy_triggers}" fi [[ -z "${install_jamf_policy_triggers}" ]] && install_jamf_policy_triggers=$(get_pref "InstallJamfPolicyTriggers") { [[ -n "${install_jamf_policy_triggers}" ]] && [[ ! -f "${JAMF_PRO_BINARY}" ]]; } && log_super "Parameter Error: Unable to use the --install-jamf-policy-triggers option due to missing Jamf binary." && parameter_error="TRUE" { [[ "${verbose_mode}" == "TRUE" ]] && [[ -n "${install_jamf_policy_triggers}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: install_jamf_policy_triggers is: ${install_jamf_policy_triggers}" # Manage ${install_jamf_policy_triggers_without_restarting} parameter. install_jamf_policy_triggers_without_restarting=$(get_pref_managed "InstallJamfPolicyTriggersWithoutRestarting") { [[ -z "${install_jamf_policy_triggers_without_restarting}" ]] && [[ -n "${install_jamf_policy_triggers_without_restarting_option}" ]]; } && install_jamf_policy_triggers_without_restarting="${install_jamf_policy_triggers_without_restarting_option}" && set_pref "InstallJamfPolicyTriggersWithoutRestarting" "${install_jamf_policy_triggers_without_restarting_option}" [[ -z "${install_jamf_policy_triggers_without_restarting}" ]] && install_jamf_policy_triggers_without_restarting=$(get_pref "InstallJamfPolicyTriggersWithoutRestarting") { [[ "${verbose_mode}" == "TRUE" ]] && [[ -n "${install_jamf_policy_triggers_without_restarting}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: install_jamf_policy_triggers_without_restarting is: ${install_jamf_policy_triggers_without_restarting}" { [[ "${install_jamf_policy_triggers_without_restarting}" == "TRUE" ]] && [[ ! -f "${JAMF_PRO_BINARY}" ]]; } && log_super "Parameter Error: Unable to use the --install-jamf-policy-triggers-without-restarting option due to missing Jamf binary." && parameter_error="TRUE" # Manage ${workflow_install_now} parameter. workflow_install_now=$(get_pref_managed "WorkflowInstallNow") { [[ -z "${workflow_install_now}" ]] && [[ -n "${workflow_install_now_option}" ]]; } && workflow_install_now="${workflow_install_now_option}" && set_pref "WorkflowInstallNow" "${workflow_install_now_option}" [[ -z "${workflow_install_now}" ]] && workflow_install_now=$(get_pref "WorkflowInstallNow") { [[ "${verbose_mode}" == "TRUE" ]] && [[ -n "${workflow_install_now}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: workflow_install_now is: ${workflow_install_now}" # Manage ${workflow_only_download} parameter. workflow_only_download=$(get_pref_managed "WorkflowOnlyDownload") { [[ -z "${workflow_only_download}" ]] && [[ -n "${workflow_only_download_option}" ]]; } && workflow_only_download="${workflow_only_download_option}" && set_pref "WorkflowOnlyDownload" "${workflow_only_download_option}" [[ -z "${workflow_only_download}" ]] && workflow_only_download=$(get_pref "WorkflowOnlyDownload") { [[ "${verbose_mode}" == "TRUE" ]] && [[ -n "${workflow_only_download}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: workflow_only_download is: ${workflow_only_download}" # Manage ${workflow_require_active_user} parameter. workflow_require_active_user=$(get_pref_managed "WorkflowRequireActiveUser") { [[ -z "${workflow_require_active_user}" ]] && [[ -n "${workflow_require_active_user_option}" ]]; } && workflow_require_active_user="${workflow_require_active_user_option}" && set_pref "WorkflowRequireActiveUser" "${workflow_require_active_user_option}" [[ -z "${workflow_require_active_user}" ]] && workflow_require_active_user=$(get_pref "WorkflowRequireActiveUser") { [[ "${verbose_mode}" == "TRUE" ]] && [[ -n "${workflow_require_active_user}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: workflow_require_active_user is: ${workflow_require_active_user}" # Manage ${workflow_restart_without_updates} parameter. workflow_restart_without_updates=$(get_pref_managed "WorkflowRestartWithoutUpdates") { [[ -z "${workflow_restart_without_updates}" ]] && [[ -n "${workflow_restart_without_updates_option}" ]]; } && workflow_restart_without_updates="${workflow_restart_without_updates_option}" && set_pref "WorkflowRestartWithoutUpdates" "${workflow_restart_without_updates_option}" [[ -z "${workflow_restart_without_updates}" ]] && workflow_restart_without_updates=$(get_pref "WorkflowRestartWithoutUpdates") { [[ "${verbose_mode}" == "TRUE" ]] && [[ -n "${workflow_restart_without_updates}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: workflow_restart_without_updates is: ${workflow_restart_without_updates}" # Manage ${workflow_disable_update_check} parameter. workflow_disable_update_check=$(get_pref_managed "WorkflowDisableUpdateCheck") { [[ -z "${workflow_disable_update_check}" ]] && [[ -n "${workflow_disable_update_check_option}" ]]; } && workflow_disable_update_check="${workflow_disable_update_check_option}" && set_pref "WorkflowDisableUpdateCheck" "${workflow_disable_update_check_option}" [[ -z "${workflow_disable_update_check}" ]] && workflow_disable_update_check=$(get_pref "WorkflowDisableUpdateCheck") { [[ "${verbose_mode}" == "TRUE" ]] && [[ -n "${workflow_disable_update_check}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: workflow_disable_update_check is: ${workflow_disable_update_check}" # Manage ${workflow_disable_relaunch} parameter. workflow_disable_relaunch=$(get_pref_managed "WorkflowDisableRelaunch") { [[ -z "${workflow_disable_relaunch}" ]] && [[ -n "${workflow_disable_relaunch_option}" ]]; } && workflow_disable_relaunch="${workflow_disable_relaunch_option}" && set_pref "WorkflowDisableRelaunch" "${workflow_disable_relaunch_option}" [[ -z "${workflow_disable_relaunch}" ]] && workflow_disable_relaunch=$(get_pref "WorkflowDisableRelaunch") { [[ "${verbose_mode}" == "TRUE" ]] && [[ -n "${workflow_disable_relaunch}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: workflow_disable_relaunch is: ${workflow_disable_relaunch}" # Validate ${deferral_timer_default_option} input and if valid set the ${deferral_timer_minutes} parameter. If there is no ${deferral_timer_minutes} then set it to ${DEFERRAL_TIMER_DEFAULT_MINUTES}. deferral_timer_minutes=$(get_pref_managed "DeferralTimerDefault") if [[ "${deferral_timer_minutes}" == "X" ]] || { [[ -z $deferral_timer_minutes ]] && [[ "${deferral_timer_default_option}" == "X" ]]; }; then delete_pref "DeferralTimerDefault" unset deferral_timer_minutes elif [[ -z $deferral_timer_minutes ]] && [[ -n "${deferral_timer_default_option}" ]]; then deferral_timer_minutes="${deferral_timer_default_option}" set_pref "DeferralTimerDefault" "${deferral_timer_minutes}" fi [[ -z $deferral_timer_minutes ]] && deferral_timer_minutes=$(get_pref "DeferralTimerDefault") if [[ -n $deferral_timer_minutes ]] && [[ $deferral_timer_minutes =~ ${REGEX_ANY_WHOLE_NUMBER} ]]; then if [[ $deferral_timer_minutes -lt 2 ]]; then log_super "Parameter Warning: Specified --deferral-timer-default=minutes value of ${deferral_timer_minutes} is too low, rounding up to 2 minutes." deferral_timer_minutes=2 elif [[ $deferral_timer_minutes -gt 10080 ]]; then log_super "Parameter Warning: Specified --deferral-timer-default=minutes value of ${deferral_timer_minutes} is too high, rounding down to 10080 minutes (1 week)." deferral_timer_minutes=10080 fi fi { [[ -n $deferral_timer_minutes ]] && [[ ! $deferral_timer_minutes =~ ${REGEX_ANY_WHOLE_NUMBER} ]]; } && log_super "Parameter Error: The --deferral-timer-default=minutes value must only be a number." && parameter_error="TRUE" [[ -z $deferral_timer_minutes ]] && deferral_timer_minutes="${DEFERRAL_TIMER_DEFAULT_MINUTES}" [[ "${verbose_mode}" == "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 the ${deferral_timer_menu_minutes} parameter. deferral_timer_menu_minutes=$(get_pref_managed "DeferralTimerMenu") if [[ "${deferral_timer_menu_minutes}" == "X" ]] || { [[ -z "${deferral_timer_menu_minutes}" ]] && [[ "${deferral_timer_menu_option}" == "X" ]]; }; then delete_pref "DeferralTimerMenu" unset deferral_timer_menu_minutes elif [[ -z "${deferral_timer_menu_minutes}" ]] && [[ -n "${deferral_timer_menu_option}" ]]; then deferral_timer_menu_minutes="${deferral_timer_menu_option}" set_pref "DeferralTimerMenu" "${deferral_timer_menu_minutes}" fi [[ -z "${deferral_timer_menu_minutes}" ]] && deferral_timer_menu_minutes=$(get_pref "DeferralTimerMenu") if [[ -n "${deferral_timer_menu_minutes}" ]] && [[ "${deferral_timer_menu_minutes}" =~ ${REGEX_CSV_WHOLE_NUMBERS} ]]; then previous_ifs="${IFS}" IFS=',' local deferral_timer_menu_minutes_array read -r -a deferral_timer_menu_minutes_array <<<"${deferral_timer_menu_minutes}" for array_index in "${!deferral_timer_menu_minutes_array[@]}"; do if [[ ${deferral_timer_menu_minutes_array[array_index]} -lt 2 ]]; then log_super "Parameter Warning: Specified --deferral-timer-menu=minutes value of ${deferral_timer_menu_minutes_array[array_index]} minutes is too low, rounding up to 2 minutes." deferral_timer_menu_minutes_array[array_index]=2 elif [[ ${deferral_timer_menu_minutes_array[array_index]} -gt 10080 ]]; then log_super "Parameter Warning: Specified --deferral-timer-menu=minutes value of ${deferral_timer_menu_minutes_array[array_index]} minutes is too high, rounding down to 10080 minutes (1 week)." deferral_timer_menu_minutes_array[array_index]=10080 fi done IFS="${previous_ifs}" fi { [[ -n "${deferral_timer_menu_minutes}" ]] && [[ ! "${deferral_timer_menu_minutes}" =~ ${REGEX_CSV_WHOLE_NUMBERS} ]]; } && log_super "Parameter Error: The --deferral-timer-menu=minutes,minutes,etc... value must only contain numbers and commas (no spaces)." && parameter_error="TRUE" { [[ "${verbose_mode}" == "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 the ${deferral_timer_focus_minutes} parameter. If there is no ${deferral_timer_focus_minutes} then set it to ${deferral_timer_minutes}. deferral_timer_focus_minutes=$(get_pref_managed "DeferralTimerFocus") if [[ "${deferral_timer_focus_minutes}" == "X" ]] || { [[ -z $deferral_timer_focus_minutes ]] && [[ "${deferral_timer_focus_option}" == "X" ]]; } then delete_pref "DeferralTimerFocus" unset deferral_timer_focus_minutes elif [[ -z $deferral_timer_focus_minutes ]] && [[ -n "${deferral_timer_focus_option}" ]]; then deferral_timer_focus_minutes="${deferral_timer_focus_option}" set_pref "DeferralTimerFocus" "${deferral_timer_focus_minutes}" fi [[ -z $deferral_timer_focus_minutes ]] && deferral_timer_focus_minutes=$(get_pref "DeferralTimerFocus") if [[ -n $deferral_timer_focus_minutes ]] && [[ $deferral_timer_focus_minutes =~ ${REGEX_ANY_WHOLE_NUMBER} ]]; then if [[ $deferral_timer_focus_minutes -lt 2 ]]; then log_super "Parameter Warning: Specified --deferral-timer-focus=minutes value of ${deferral_timer_focus_minutes} is too low, rounding up to 2 minutes." deferral_timer_focus_minutes=2 elif [[ $deferral_timer_focus_minutes -gt 10080 ]]; then log_super "Parameter Warning: Specified --deferral-timer-focus=minutes value of ${deferral_timer_focus_minutes} is too high, rounding down to 10080 minutes (1 week)." deferral_timer_focus_minutes=10080 fi fi { [[ -n $deferral_timer_focus_minutes ]] && [[ ! $deferral_timer_focus_minutes =~ ${REGEX_ANY_WHOLE_NUMBER} ]]; } && log_super "Parameter Error: The --deferral-timer-default=minutes value must only be a number." && parameter_error="TRUE" [[ -z $deferral_timer_focus_minutes ]] && deferral_timer_focus_minutes=$deferral_timer_minutes [[ "${verbose_mode}" == "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 the ${deferral_timer_error_minutes} parameter. If there is no ${deferral_timer_error_minutes} then set it to ${deferral_timer_minutes}. deferral_timer_error_minutes=$(get_pref_managed "DeferralTimerError") if [[ "${deferral_timer_error_minutes}" == "X" ]] || { [[ -z $deferral_timer_error_minutes ]] && [[ "${deferral_timer_error_option}" == "X" ]]; }; then delete_pref "DeferralTimerError" unset deferral_timer_error_minutes elif [[ -z $deferral_timer_error_minutes ]] && [[ -n "${deferral_timer_error_option}" ]]; then deferral_timer_error_minutes="${deferral_timer_error_option}" set_pref "DeferralTimerError" "${deferral_timer_error_minutes}" fi [[ -z $deferral_timer_error_minutes ]] && deferral_timer_error_minutes=$(get_pref "DeferralTimerError") if [[ -n $deferral_timer_error_minutes ]] && [[ $deferral_timer_error_minutes =~ ${REGEX_ANY_WHOLE_NUMBER} ]]; then if [[ $deferral_timer_error_minutes -lt 2 ]]; then log_super "Parameter Warning: Specified --deferral-timer-error=minutes value of ${deferral_timer_error_minutes} is too low, rounding up to 2 minutes." deferral_timer_error_minutes=2 elif [[ $deferral_timer_error_minutes -gt 10080 ]]; then log_super "Parameter Warning: Specified --deferral-timer-error=minutes value of ${deferral_timer_error_minutes} is too high, rounding down to 10080 minutes (1 week)." deferral_timer_error_minutes=10080 fi fi { [[ -n $deferral_timer_error_minutes ]] && [[ ! $deferral_timer_error_minutes =~ ${REGEX_ANY_WHOLE_NUMBER} ]]; } && log_super "Parameter Error: The --deferral-timer-default=minutes value must only be a number." && parameter_error="TRUE" [[ -z $deferral_timer_error_minutes ]] && deferral_timer_error_minutes=$deferral_timer_minutes [[ "${verbose_mode}" == "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 the ${deferral_timer_workflow_relaunch_minutes} parameter. If there is no ${deferral_timer_workflow_relaunch_minutes} then set it to ${DEFERRAL_TIMER_WORKFLOW_RELAUNCH_DEFAULT_MINUTES}. deferral_timer_workflow_relaunch_minutes=$(get_pref_managed "DeferralTimerWorkflowRelaunch") if [[ "${deferral_timer_workflow_relaunch_minutes}" == "X" ]] || { [[ -z $deferral_timer_workflow_relaunch_minutes ]] && [[ "${deferral_timer_workflow_relaunch_option}" == "X" ]]; }; then delete_pref "DeferralTimerWorkflowRelaunch" unset deferral_timer_workflow_relaunch_minutes elif [[ -z $deferral_timer_workflow_relaunch_minutes ]] && [[ -n "${deferral_timer_workflow_relaunch_option}" ]]; then deferral_timer_workflow_relaunch_minutes="${deferral_timer_workflow_relaunch_option}" set_pref "DeferralTimerWorkflowRelaunch" "${deferral_timer_workflow_relaunch_minutes}" fi [[ -z $deferral_timer_workflow_relaunch_minutes ]] && deferral_timer_workflow_relaunch_minutes=$(get_pref "DeferralTimerWorkflowRelaunch") if [[ -n $deferral_timer_workflow_relaunch_minutes ]] && [[ $deferral_timer_workflow_relaunch_minutes =~ ${REGEX_ANY_WHOLE_NUMBER} ]]; then if [[ $deferral_timer_workflow_relaunch_minutes -lt 2 ]]; then log_super "Parameter Warning: Specified --deferral-timer-workflow-relaunch=minutes value of ${deferral_timer_workflow_relaunch_minutes} is too low, rounding up to 2 minutes." deferral_timer_workflow_relaunch_minutes=2 elif [[ $deferral_timer_workflow_relaunch_minutes -gt 10080 ]]; then log_super "Parameter Warning: Specified --deferral-timer-workflow-relaunch=minutes value of ${deferral_timer_workflow_relaunch_minutes} is too high, rounding down to 10080 minutes (1 week)." deferral_timer_workflow_relaunch_minutes=10080 fi fi { [[ -n $deferral_timer_workflow_relaunch_minutes ]] && [[ ! $deferral_timer_workflow_relaunch_minutes =~ ${REGEX_ANY_WHOLE_NUMBER} ]]; } && log_super "Parameter Error: The --deferral-timer-default=minutes value must only be a number." && parameter_error="TRUE" [[ -z $deferral_timer_workflow_relaunch_minutes ]] && deferral_timer_workflow_relaunch_minutes="${DEFERRAL_TIMER_WORKFLOW_RELAUNCH_DEFAULT_MINUTES}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: deferral_timer_workflow_relaunch_minutes is: ${deferral_timer_workflow_relaunch_minutes}" # Validate ${schedule_workflow_active_option} input and if valid set the ${schedule_workflow_active} parameter. schedule_workflow_active=$(get_pref_managed "ScheduleWorkflowActive") if [[ "${schedule_workflow_active}" == "X" ]] || { [[ -z "${schedule_workflow_active}" ]] && [[ "${schedule_workflow_active_option}" == "X" ]]; }; then delete_pref "ScheduleWorkflowActive" unset schedule_workflow_active elif [[ -z "${schedule_workflow_active}" ]] && [[ -n "${schedule_workflow_active_option}" ]]; then schedule_workflow_active="${schedule_workflow_active_option}" set_pref "ScheduleWorkflowActive" "${schedule_workflow_active}" fi [[ -z "${schedule_workflow_active}" ]] && schedule_workflow_active=$(get_pref "ScheduleWorkflowActive") if [[ -n "${schedule_workflow_active}" ]]; then previous_ifs="${IFS}" IFS=',' read -r -a schedule_workflow_active_array <<<"${schedule_workflow_active}" for schedule_workflow_active_time_frame in "${schedule_workflow_active_array[@]}"; do { [[ ! "${schedule_workflow_active_time_frame:0:3}" =~ ${REGEX_WEEKDAY} ]]; } && log_super "Parameter Error: Unrecognized --schedule-workflow-active weekday of ${schedule_workflow_active_time_frame:0:3}. The weekdays must be specified as MON|TUE|WED|THU|FRI|SAT|SUN." && parameter_error="TRUE" { [[ ! "${schedule_workflow_active_time_frame:4:11}" =~ ${REGEX_HOURS_MINUTES_RANGE} ]]; } && log_super "Parameter Error: Each --schedule-workflow-active time frame value must be formated in 24-hour time as hh:mm-hh:mm." && parameter_error="TRUE" { [[ ! "${schedule_workflow_active_time_frame}" =~ ${REGEX_SCHEDULE_WORKFLOW_ACITVE_TIME_FRAME} ]]; } && log_super "Parameter Error: Each --schedule-workflow-active weekday and time frame must be formated as DAY:hh:mm-hh:mm." && parameter_error="TRUE" done IFS="${previous_ifs}" fi { [[ "${verbose_mode}" == "TRUE" ]] && [[ -n "${schedule_workflow_active}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: schedule_workflow_active is: ${schedule_workflow_active}" # Validate ${schedule_deferred_start_file_option} input and if valid set the ${schedule_deferred_start_file} parameter. schedule_deferred_start_file=$(get_pref_managed "ScheduleDeferredStartFile") if [[ "${schedule_deferred_start_file}" == "X" ]] || { [[ -z "${schedule_deferred_start_file}" ]] && [[ "${schedule_deferred_start_file_option}" == "X" ]]; }; then delete_pref "ScheduleDeferredStartFile" unset schedule_deferred_start_file elif [[ -z "${schedule_deferred_start_file}" ]] && [[ -n "${schedule_deferred_start_file_option}" ]]; then schedule_deferred_start_file="${schedule_deferred_start_file_option}" set_pref "ScheduleDeferredStartFile" "${schedule_deferred_start_file}" fi [[ -z "${schedule_deferred_start_file}" ]] && schedule_deferred_start_file=$(get_pref "ScheduleDeferredStartFile") { [[ "${verbose_mode}" == "TRUE" ]] && [[ -n "${schedule_deferred_start_file}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: schedule_deferred_start_file is: ${schedule_deferred_start_file}" # Validate ${schedule_deferred_start_days_option} input and if valid set the ${schedule_deferred_start_days} parameter. schedule_deferred_start_days=$(get_pref_managed "ScheduleDeferredStartDays") if [[ "${schedule_deferred_start_days}" == "X" ]] || { [[ -z $schedule_deferred_start_days ]] && [[ "${schedule_deferred_start_days_option}" == "X" ]]; }; then delete_pref "ScheduleDeferredStartDays" unset schedule_deferred_start_days elif [[ -z $schedule_deferred_start_days ]] && [[ -n "${schedule_deferred_start_days_option}" ]]; then schedule_deferred_start_days="${schedule_deferred_start_days_option}" set_pref "ScheduleDeferredStartDays" "${schedule_deferred_start_days}" fi [[ -z $schedule_deferred_start_days ]] && schedule_deferred_start_days=$(get_pref "ScheduleDeferredStartDays") if [[ -n $schedule_deferred_start_days ]] && [[ $schedule_deferred_start_days =~ ${REGEX_ANY_WHOLE_NUMBER} ]]; then if [[ $schedule_deferred_start_days -lt 1 ]]; then log_super "Parameter Warning: Specified --schedule-deferred-start-days=number value of ${schedule_deferred_start_days} must be greater than zero days to have any affect." unset schedule_deferred_start_days elif [[ $schedule_deferred_start_days -gt 90 ]]; then log_super "Parameter Warning: Specified --schedule-deferred-start-days=number value of ${schedule_deferred_start_days} days is too high, rounding down to 90 days." schedule_deferred_start_days=90 fi fi { [[ -n $schedule_deferred_start_days ]] && [[ ! $schedule_deferred_start_days =~ ${REGEX_ANY_WHOLE_NUMBER} ]]; } && log_super "Parameter Error: The --schedule-deferred-start-days=number value must only be a number." && parameter_error="TRUE" { [[ "${verbose_mode}" == "TRUE" ]] && [[ -n $schedule_deferred_start_days ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: schedule_deferred_start_days is: ${schedule_deferred_start_days}" # Manage ${schedule_zero_date_release} parameter. schedule_zero_date_release=$(get_pref_managed "ScheduleZeroDateRelease") { [[ -z "${schedule_zero_date_release}" ]] && [[ -n "${schedule_zero_date_release_option}" ]]; } && schedule_zero_date_release="${schedule_zero_date_release_option}" && set_pref "ScheduleZeroDateRelease" "${schedule_zero_date_release_option}" [[ -z "${schedule_zero_date_release}" ]] && schedule_zero_date_release=$(get_pref "ScheduleZeroDateRelease") { [[ "${verbose_mode}" == "TRUE" ]] && [[ -n "${schedule_zero_date_release}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: schedule_zero_date_release is: ${schedule_zero_date_release}" # Validate ${schedule_zero_date_sofa_custom_url_option} input and if valid set the ${sofa_macos_url} parameter. If there is no ${sofa_macos_url} then set it to ${SOFA_MACOS_DEFAULT_URL}. sofa_macos_url=$(get_pref_managed "ScheduleZeroDateSOFACustomURL") if [[ "${sofa_macos_url}" == "X" ]] || { [[ -z "${sofa_macos_url}" ]] && [[ "${schedule_zero_date_sofa_custom_url_option}" == "X" ]]; }; then delete_pref "ScheduleZeroDateSOFACustomURL" unset sofa_macos_url elif [[ -z "${sofa_macos_url}" ]] && [[ -n "${schedule_zero_date_sofa_custom_url_option}" ]]; then sofa_macos_url="${schedule_zero_date_sofa_custom_url_option}" set_pref "ScheduleZeroDateSOFACustomURL" "${sofa_macos_url}" fi [[ -z "${sofa_macos_url}" ]] && sofa_macos_url=$(get_pref "ScheduleZeroDateSOFACustomURL") { [[ -n "${sofa_macos_url}" ]] && [[ ! "${sofa_macos_url}" =~ ${REGEX_HTTPS} ]]; } && log_super "Parameter Error: Invalid ---schedule-zero-date-sofa-custom-url VALUE must start with 'https://': ${sofa_macos_url}" && parameter_error="TRUE" { [[ -n "${sofa_macos_url}" ]] && [[ "${schedule_zero_date_release}" != "TRUE" ]]; } && log_super "Parameter Error: To use the --schedule-zero-date-sofa-custom-url option you must also use the --schedule-zero-date-release option." && parameter_error="TRUE" [[ -z "${sofa_macos_url}" ]] && sofa_macos_url="${SOFA_MACOS_DEFAULT_URL}" { [[ "${verbose_mode}" == "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} input and if valid set the ${schedule_zero_date_manual} parameter. schedule_zero_date_manual=$(get_pref_managed "ScheduleZeroDateManual") if [[ "${schedule_zero_date_manual}" == "X" ]] || { [[ -z "${schedule_zero_date_manual}" ]] && [[ "${schedule_zero_date_manual_option}" == "X" ]]; }; then delete_pref "ScheduleZeroDateManual" unset schedule_zero_date_manual elif [[ -z "${schedule_zero_date_manual}" ]] && [[ -n "${schedule_zero_date_manual_option}" ]]; then schedule_zero_date_manual="${schedule_zero_date_manual_option}" set_pref "ScheduleZeroDateManual" "${schedule_zero_date_manual}" fi [[ -z "${schedule_zero_date_manual}" ]] && schedule_zero_date_manual=$(get_pref "ScheduleZeroDateManual") if [[ -n "${schedule_zero_date_manual}" ]]; then extract_date="${schedule_zero_date_manual:0:10}" if [[ "${extract_date}" =~ ${REGEX_DATE} ]]; then extract_time="${schedule_zero_date_manual: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." parameter_error="TRUE" fi else log_super "Parameter Error: The --schedule-zero-date-manual value for date must be formated as YYYY-MM-DD." parameter_error="TRUE" fi if [[ "${sanitized_date}" =~ ${REGEX_DATE_HOURS_MINUTES} ]]; then schedule_zero_date_manual="${sanitized_date}" else log_super "Parameter Error: The --schedule-zero-date-manual value must be formatted as YYYY-MM-DD:hh:mm." parameter_error="TRUE" fi fi { [[ -n "${schedule_zero_date_manual}" ]] && [[ "${schedule_zero_date_release}" == "TRUE" ]]; } && 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." && unset schedule_zero_date_release { [[ -n "${schedule_zero_date_manual}" ]] && [[ -n $schedule_deferred_start_days ]]; } && log_super "Parameter Warning: When both the --schedule-zero-date-manual and the --schedule-deferred-start-days options are enabled the --schedule-zero-date-manual option takes priority." && unset schedule_deferred_start_days { [[ "${verbose_mode}" == "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 the ${scheduled_install_days} parameter. scheduled_install_days=$(get_pref_managed "ScheduledInstallDays") if [[ "${scheduled_install_days}" == "X" ]] || { [[ -z "${scheduled_install_days}" ]] && [[ "${scheduled_install_days_option}" == "X" ]]; }; then delete_pref "ScheduledInstallDays" unset scheduled_install_days elif [[ -z "${scheduled_install_days}" ]] && [[ -n "${scheduled_install_days_option}" ]]; then scheduled_install_days="${scheduled_install_days_option}" set_pref "ScheduledInstallDays" "${scheduled_install_days}" fi [[ -z "${scheduled_install_days}" ]] && scheduled_install_days=$(get_pref "ScheduledInstallDays") { [[ -n "${scheduled_install_days}" ]] && [[ ! "${scheduled_install_days}" =~ ${REGEX_ANY_WHOLE_NUMBER} ]]; } && log_super "Parameter Error: The --scheduled-install-days=number value must only be a number." && parameter_error="TRUE" { [[ "${verbose_mode}" == "TRUE" ]] && [[ -n "${scheduled_install_days}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: scheduled_install_days is: ${scheduled_install_days}" # Validate ${scheduled_install_date_option} input and if valid set the ${scheduled_install_date} parameter. scheduled_install_date=$(get_pref_managed "ScheduledInstallDate") if [[ "${scheduled_install_date}" == "X" ]] || { [[ -z "${scheduled_install_date}" ]] && [[ "${scheduled_install_date_option}" == "X" ]]; }; then delete_pref "scheduled_install_date" elif [[ -z "${scheduled_install_date}" ]] && [[ -n "${scheduled_install_date_option}" ]]; then scheduled_install_date="${scheduled_install_date_option}" set_pref "ScheduledInstallDate" "${scheduled_install_date}" fi [[ -z "${scheduled_install_date}" ]] && scheduled_install_date=$(get_pref "ScheduledInstallDate") if [[ -n "${scheduled_install_date}" ]]; then extract_date="${scheduled_install_date:0:10}" if [[ "${extract_date}" =~ ${REGEX_DATE} ]]; then extract_time="${scheduled_install_date: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." parameter_error="TRUE" fi else log_super "Parameter Error: The --scheduled-install-date value for date must be formated as YYYY-MM-DD." parameter_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." parameter_error="TRUE" fi fi { [[ "${verbose_mode}" == "TRUE" ]] && [[ -n "${scheduled_install_date}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: scheduled_install_date is: ${scheduled_install_date}" # Manage ${scheduled_install_user_choice} parameter. scheduled_install_user_choice=$(get_pref_managed "ScheduledInstallUserChoice") { [[ -z "${scheduled_install_user_choice}" ]] && [[ -n "${scheduled_install_user_choice_option}" ]]; } && scheduled_install_user_choice="${scheduled_install_user_choice_option}" && set_pref "ScheduledInstallUserChoice" "${scheduled_install_user_choice_option}" [[ -z "${scheduled_install_user_choice}" ]] && scheduled_install_user_choice=$(get_pref "ScheduledInstallUserChoice") { [[ "${verbose_mode}" == "TRUE" ]] && [[ -n "${scheduled_install_user_choice}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: scheduled_install_user_choice is: ${scheduled_install_user_choice}" # Validated that ${scheduled_install_days}, ${scheduled_install_date}, and ${scheduled_install_user_choice} are not simultaneously active. 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}" == "TRUE" ]] && ((scheduled_install_option_counter++)) [[ $scheduled_install_option_counter -gt 1 ]] && 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." && parameter_error="TRUE" { [[ "${verbose_mode}" == "TRUE" ]] && [[ -n $scheduled_install_option_counter ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: scheduled_install_option_counter is: ${scheduled_install_option_counter}" # Validate ${scheduled_install_reminder_option} input and if valid set the ${scheduled_install_reminder_minutes} parameter. scheduled_install_reminder_minutes=$(get_pref_managed "ScheduledInstallReminder") if [[ "${scheduled_install_reminder_minutes}" == "X" ]] || { [[ -z "${scheduled_install_reminder_minutes}" ]] && [[ "${scheduled_install_reminder_option}" == "X" ]]; }; then delete_pref "ScheduledInstallReminder" unset scheduled_install_reminder_minutes elif [[ -z "${scheduled_install_reminder_minutes}" ]] && [[ -n "${scheduled_install_reminder_option}" ]]; then scheduled_install_reminder_minutes="${scheduled_install_reminder_option}" set_pref "ScheduledInstallReminder" "${scheduled_install_reminder_minutes}" fi [[ -z "${scheduled_install_reminder_minutes}" ]] && scheduled_install_reminder_minutes=$(get_pref "ScheduledInstallReminder") if [[ -n "${scheduled_install_reminder_minutes}" ]] && [[ "${scheduled_install_reminder_minutes}" =~ ${REGEX_CSV_WHOLE_NUMBERS} ]]; then previous_ifs="${IFS}" IFS=',' local scheduled_install_reminder_minutes_array read -r -a scheduled_install_reminder_minutes_array <<<"${scheduled_install_reminder_minutes}" for array_index in "${!scheduled_install_reminder_minutes_array[@]}"; do if [[ ${scheduled_install_reminder_minutes_array[array_index]} -lt 2 ]]; then log_super "Parameter Warning: Specified --scheduled-install-reminder=minutes value of ${scheduled_install_reminder_minutes_array[array_index]} minutes is too low, rounding up to 2 minutes." scheduled_install_reminder_minutes_array[array_index]=2 elif [[ ${scheduled_install_reminder_minutes_array[array_index]} -gt 10080 ]]; then log_super "Parameter Warning: Specified --scheduled-install-reminder=minutes value of ${scheduled_install_reminder_minutes_array[array_index]} minutes is too high, rounding down to 10080 minutes (1 week)." scheduled_install_reminder_minutes_array[array_index]=10080 fi done IFS="${previous_ifs}" fi { [[ -n "${scheduled_install_reminder_minutes}" ]] && [[ ! "${scheduled_install_reminder_minutes}" =~ ${REGEX_CSV_WHOLE_NUMBERS} ]]; } && log_super "Parameter Error: The --scheduled-install-reminder=minutes,minutes,etc... value must only contain numbers and commas (no spaces)." && parameter_error="TRUE" { [[ -n "${scheduled_install_reminder_minutes}" ]] && [[ $scheduled_install_option_counter -eq 0 ]]; } && 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." && parameter_error="TRUE" { [[ "${verbose_mode}" == "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 the ${deadline_count_focus} parameter. deadline_count_focus=$(get_pref_managed "DeadlineCountFocus") if [[ "${deadline_count_focus}" == "X" ]] || { [[ -z "${deadline_count_focus}" ]] && [[ "${deadline_count_focus_option}" == "X" ]]; }; then delete_pref "DeadlineCountFocus" unset deadline_count_focus elif [[ -z "${deadline_count_focus}" ]] && [[ -n "${deadline_count_focus_option}" ]]; then deadline_count_focus="${deadline_count_focus_option}" set_pref "DeadlineCountFocus" "${deadline_count_focus}" fi [[ -z "${deadline_count_focus}" ]] && deadline_count_focus=$(get_pref "DeadlineCountFocus") { [[ -n "${deadline_count_focus}" ]] && [[ ! "${deadline_count_focus}" =~ ${REGEX_ANY_WHOLE_NUMBER} ]]; } && log_super "Parameter Error: The --deadline-count-focus=number value must only be a number." && parameter_error="TRUE" { [[ "${verbose_mode}" == "TRUE" ]] && [[ -n "${deadline_count_focus}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: deadline_count_focus is: ${deadline_count_focus}" # Validate ${deadline_count_soft_option} input and if valid set the ${deadline_count_soft} parameter. deadline_count_soft=$(get_pref_managed "DeadlineCountSoft") if [[ "${deadline_count_soft}" == "X" ]] || { [[ -z "${deadline_count_soft}" ]] && [[ "${deadline_count_soft_option}" == "X" ]]; }; then delete_pref "DeadlineCountSoft" unset deadline_count_soft elif [[ -z "${deadline_count_soft}" ]] && [[ -n "${deadline_count_soft_option}" ]]; then deadline_count_soft="${deadline_count_soft_option}" set_pref "DeadlineCountSoft" "${deadline_count_soft}" fi [[ -z "${deadline_count_soft}" ]] && deadline_count_soft=$(get_pref "DeadlineCountSoft") { [[ -n "${deadline_count_soft}" ]] && [[ ! "${deadline_count_soft}" =~ ${REGEX_ANY_WHOLE_NUMBER} ]]; } && log_super "Parameter Error: The --deadline-count-soft=number value must only be a number." && parameter_error="TRUE" { [[ "${verbose_mode}" == "TRUE" ]] && [[ -n "${deadline_count_soft}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: deadline_count_soft is: ${deadline_count_soft}" # Validate ${deadline_count_hard_option} input and if valid set the ${deadline_count_hard} parameter. deadline_count_hard=$(get_pref_managed "DeadlineCountHard") if [[ "${deadline_count_hard}" == "X" ]] || { [[ -z "${deadline_count_hard}" ]] && [[ "${deadline_count_hard_option}" == "X" ]]; }; then delete_pref "DeadlineCountHard" unset deadline_count_hard elif [[ -z "${deadline_count_hard}" ]] && [[ -n "${deadline_count_hard_option}" ]]; then deadline_count_hard="${deadline_count_hard_option}" set_pref "DeadlineCountHard" "${deadline_count_hard}" fi [[ -z "${deadline_count_hard}" ]] && deadline_count_hard=$(get_pref "DeadlineCountHard") { [[ -n "${deadline_count_hard}" ]] && [[ ! "${deadline_count_hard}" =~ ${REGEX_ANY_WHOLE_NUMBER} ]]; } && log_super "Parameter Error: The --deadline-count-hard=number value must only be a number." && parameter_error="TRUE" { [[ -n "${deadline_count_soft}" ]] && [[ -n "${deadline_count_hard}" ]]; } && 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." && parameter_error="TRUE" { [[ "${verbose_mode}" == "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 the ${deadline_days_focus} parameter. deadline_days_focus=$(get_pref_managed "DeadlineDaysFocus") if [[ "${deadline_days_focus}" == "X" ]] || { [[ -z "${deadline_days_focus}" ]] && [[ "${deadline_days_focus_option}" == "X" ]]; }; then delete_pref "DeadlineDaysFocus" unset deadline_days_focus elif [[ -z "${deadline_days_focus}" ]] && [[ -n "${deadline_days_focus_option}" ]]; then deadline_days_focus="${deadline_days_focus_option}" set_pref "DeadlineDaysFocus" "${deadline_days_focus}" fi [[ -z "${deadline_days_focus}" ]] && deadline_days_focus=$(get_pref "DeadlineDaysFocus") { [[ -n "${deadline_days_focus}" ]] && [[ ! "${deadline_days_focus}" =~ ${REGEX_ANY_WHOLE_NUMBER} ]]; } && log_super "Parameter Error: The --deadline-days-focus=number value must only be a number." && parameter_error="TRUE" { [[ "${verbose_mode}" == "TRUE" ]] && [[ -n "${deadline_days_focus}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: deadline_days_focus is: ${deadline_days_focus}" # Validate ${deadline_days_soft_option} input and if valid set the ${deadline_days_soft} parameter. deadline_days_soft=$(get_pref_managed "DeadlineDaysSoft") if [[ "${deadline_days_soft}" == "X" ]] || { [[ -z "${deadline_days_soft}" ]] && [[ "${deadline_days_soft_option}" == "X" ]]; }; then delete_pref "DeadlineDaysSoft" unset deadline_days_soft elif [[ -z "${deadline_days_soft}" ]] && [[ -n "${deadline_days_soft_option}" ]]; then deadline_days_soft="${deadline_days_soft_option}" set_pref "DeadlineDaysSoft" "${deadline_days_soft}" fi [[ -z "${deadline_days_soft}" ]] && deadline_days_soft=$(get_pref "DeadlineDaysSoft") { [[ -n "${deadline_days_soft}" ]] && [[ ! "${deadline_days_soft}" =~ ${REGEX_ANY_WHOLE_NUMBER} ]]; } && log_super "Parameter Error: The --deadline-days-soft=number value must only be a number." && parameter_error="TRUE" { [[ "${verbose_mode}" == "TRUE" ]] && [[ -n "${deadline_days_soft}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: deadline_days_soft is: ${deadline_days_soft}" # Validate ${deadline_days_hard_option} input and if valid set the ${deadline_days_hard} parameter. deadline_days_hard=$(get_pref_managed "DeadlineDaysHard") if [[ "${deadline_days_hard}" == "X" ]] || { [[ -z "${deadline_days_hard}" ]] && [[ "${deadline_days_hard_option}" == "X" ]]; }; then delete_pref "DeadlineDaysHard" unset deadline_days_hard elif [[ -z "${deadline_days_hard}" ]] && [[ -n "${deadline_days_hard_option}" ]]; then deadline_days_hard="${deadline_days_hard_option}" set_pref "DeadlineDaysHard" "${deadline_days_hard}" fi [[ -z "${deadline_days_hard}" ]] && deadline_days_hard=$(get_pref "DeadlineDaysHard") { [[ -n "${deadline_days_hard}" ]] && [[ ! "${deadline_days_hard}" =~ ${REGEX_ANY_WHOLE_NUMBER} ]]; } && log_super "Parameter Error: The --deadline-days-hard=number value must only be a number." && parameter_error="TRUE" { [[ "${verbose_mode}" == "TRUE" ]] && [[ -n "${deadline_days_hard}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: deadline_days_hard is: ${deadline_days_hard}" # Validate ${deadline_days_focus}, ${deadline_days_soft}, ${deadline_days_hard}, and ${schedule_deferred_start_days} parameters in relation to each other. { [[ -n "${deadline_days_hard}" ]] && [[ -n "${deadline_days_soft}" ]] && [[ $deadline_days_hard -le $deadline_days_soft ]]; } && 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)." && parameter_error="TRUE" { [[ -n "${deadline_days_hard}" ]] && [[ -n "${deadline_days_focus}" ]] && [[ $deadline_days_hard -le $deadline_days_focus ]]; } && 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)." && parameter_error="TRUE" { [[ -n "${deadline_days_soft}" ]] && [[ -n "${deadline_days_focus}" ]] && [[ $deadline_days_soft -le $deadline_days_focus ]]; } && 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)." && parameter_error="TRUE" { [[ -n "${deadline_days_focus}" ]] && [[ -n $schedule_deferred_start_days ]] && [[ $deadline_days_focus -le $schedule_deferred_start_days ]]; } && log_super "Parameter Error: The --deadline-days-focus=number value of ${deadline_days_focus} day(s) must be more than the --schedule-deferred-start-days=number value of ${schedule_deferred_start_days} day(s)." && parameter_error="TRUE" { [[ -n "${deadline_days_soft}" ]] && [[ -n $schedule_deferred_start_days ]] && [[ $deadline_days_soft -le $schedule_deferred_start_days ]]; } && log_super "Parameter Error: The --deadline-days-soft=number value of ${deadline_days_soft} day(s) must be more than the --schedule-deferred-start-days=number value of ${schedule_deferred_start_days} day(s)." && parameter_error="TRUE" { [[ -n "${deadline_days_hard}" ]] && [[ -n $schedule_deferred_start_days ]] && [[ $deadline_days_hard -le $schedule_deferred_start_days ]]; } && log_super "Parameter Error: The --deadline-days-hard=number value of ${deadline_days_hard} day(s) must be more than the --schedule-deferred-start-days=number value of ${schedule_deferred_start_days} day(s)." && parameter_error="TRUE" # Validate ${deadline_date_focus_option} input and if valid set the ${deadline_date_focus} and ${deadline_date_focus_epoch} parameters. deadline_date_focus=$(get_pref_managed "DeadlineDateFocus") if [[ "${deadline_date_focus}" == "X" ]] || { [[ -z "${deadline_date_focus}" ]] && [[ "${deadline_date_focus_option}" == "X" ]]; }; then delete_pref "DeadlineDateFocus" unset deadline_date_focus elif [[ -z "${deadline_date_focus}" ]] && [[ -n "${deadline_date_focus_option}" ]]; then deadline_date_focus="${deadline_date_focus_option}" set_pref "DeadlineDateFocus" "${deadline_date_focus}" fi [[ -z "${deadline_date_focus}" ]] && deadline_date_focus=$(get_pref "DeadlineDateFocus") if [[ -n "${deadline_date_focus}" ]]; then extract_date="${deadline_date_focus:0:10}" if [[ "${extract_date}" =~ ${REGEX_DATE} ]]; then extract_time="${deadline_date_focus: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." parameter_error="TRUE" fi else log_super "Parameter Error: The --deadline-date-focus value for date must be formated as YYYY-MM-DD." parameter_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." parameter_error="TRUE" fi fi { [[ "${verbose_mode}" == "TRUE" ]] && [[ -n "${deadline_date_focus}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: deadline_date_focus is: ${deadline_date_focus}" { [[ "${verbose_mode}" == "TRUE" ]] && [[ -n "${deadline_date_focus_epoch}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: deadline_date_focus_epoch is: ${deadline_date_focus_epoch}" # Validate ${deadline_date_soft_option} input and if valid set the ${deadline_date_soft} and ${deadline_date_soft_epoch} parameters. deadline_date_soft=$(get_pref_managed "DeadlineDateSoft") if [[ "${deadline_date_soft}" == "X" ]] || { [[ -z "${deadline_date_soft}" ]] && [[ "${deadline_date_soft_option}" == "X" ]]; }; then delete_pref "DeadlineDateSoft" unset deadline_date_soft elif [[ -z "${deadline_date_soft}" ]] && [[ -n "${deadline_date_soft_option}" ]]; then deadline_date_soft="${deadline_date_soft_option}" set_pref "DeadlineDateSoft" "${deadline_date_soft}" fi [[ -z "${deadline_date_soft}" ]] && deadline_date_soft=$(get_pref "DeadlineDateSoft") if [[ -n "${deadline_date_soft}" ]]; then extract_date="${deadline_date_soft:0:10}" if [[ "${extract_date}" =~ ${REGEX_DATE} ]]; then extract_time="${deadline_date_soft: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." parameter_error="TRUE" fi else log_super "Parameter Error: The --deadline-date-soft value for date must be formated as YYYY-MM-DD." parameter_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." parameter_error="TRUE" fi fi { [[ "${verbose_mode}" == "TRUE" ]] && [[ -n "${deadline_date_soft}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: deadline_date_soft is: ${deadline_date_soft}" { [[ "${verbose_mode}" == "TRUE" ]] && [[ -n "${deadline_date_soft_epoch}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: deadline_date_soft_epoch is: ${deadline_date_soft_epoch}" # Validate ${deadline_date_hard_option} input and if valid set the ${deadline_date_hard} and ${deadline_date_hard_epoch} parameters. deadline_date_hard=$(get_pref_managed "DeadlineDateHard") if [[ "${deadline_date_hard}" == "X" ]] || { [[ -z "${deadline_date_hard}" ]] && [[ "${deadline_date_hard_option}" == "X" ]]; }; then delete_pref "DeadlineDateHard" unset deadline_date_hard elif [[ -z "${deadline_date_hard}" ]] && [[ -n "${deadline_date_hard_option}" ]]; then deadline_date_hard="${deadline_date_hard_option}" set_pref "DeadlineDateHard" "${deadline_date_hard}" fi [[ -z "${deadline_date_hard}" ]] && deadline_date_hard=$(get_pref "DeadlineDateHard") if [[ -n "${deadline_date_hard}" ]]; then extract_date="${deadline_date_hard:0:10}" if [[ "${extract_date}" =~ ${REGEX_DATE} ]]; then extract_time="${deadline_date_hard: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." parameter_error="TRUE" fi else log_super "Parameter Error: The --deadline-date-hard value for date must be formated as YYYY-MM-DD." parameter_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." parameter_error="TRUE" fi fi { [[ "${verbose_mode}" == "TRUE" ]] && [[ -n "${deadline_date_hard}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: deadline_date_hard is: ${deadline_date_hard}" { [[ "${verbose_mode}" == "TRUE" ]] && [[ -n "${deadline_date_hard_epoch}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: deadline_date_hard_epoch is: ${deadline_date_hard_epoch}" # Validate ${deadline_date_focus_epoch}, ${deadline_date_soft_epoch}, and ${deadline_date_hard_epoch} in relation to each other. { [[ -n "${deadline_date_hard_epoch}" ]] && [[ -n "${deadline_date_soft_epoch}" ]] && [[ $deadline_date_hard_epoch -le $deadline_date_soft_epoch ]]; } && 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}." && parameter_error="TRUE" { [[ -n "${deadline_date_hard_epoch}" ]] && [[ -n "${deadline_date_focus_epoch}" ]] && [[ $deadline_date_hard_epoch -le $deadline_date_focus_epoch ]]; } && 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}." && parameter_error="TRUE" { [[ -n "${deadline_date_soft_epoch}" ]] && [[ -n "${deadline_date_focus_epoch}" ]] && [[ $deadline_date_soft_epoch -le $deadline_date_focus_epoch ]]; } && 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}." && parameter_error="TRUE" # 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." parameter_error="TRUE" fi # Validate ${dialog_timeout_default_option} input and if valid set the ${dialog_timeout_default_seconds} parameter. dialog_timeout_default_seconds=$(get_pref_managed "DialogTimeoutDefault") if [[ "${dialog_timeout_default_seconds}" == "X" ]] || { [[ -z $dialog_timeout_default_seconds ]] && [[ "${dialog_timeout_default_option}" == "X" ]]; }; then delete_pref "DialogTimeoutDefault" unset dialog_timeout_default_seconds elif [[ -z $dialog_timeout_default_seconds ]] && [[ -n "${dialog_timeout_default_option}" ]]; then dialog_timeout_default_seconds="${dialog_timeout_default_option}" set_pref "DialogTimeoutDefault" "${dialog_timeout_default_seconds}" fi [[ -z $dialog_timeout_default_seconds ]] && dialog_timeout_default_seconds=$(get_pref "DialogTimeoutDefault") if [[ -n $dialog_timeout_default_seconds ]] && [[ $dialog_timeout_default_seconds =~ ${REGEX_ANY_WHOLE_NUMBER} ]]; then if [[ $dialog_timeout_default_seconds -lt 60 ]]; then log_super "Parameter Warning: Specified --dialog-timeout-default=seconds value of ${dialog_timeout_default_seconds} is too low, rounding up to 60 seconds." dialog_timeout_default_seconds=60 elif [[ $dialog_timeout_default_seconds -gt 86400 ]]; then log_super "Parameter Warning: Specified --dialog-timeout-default=seconds value of ${dialog_timeout_default_seconds} is too high, rounding down to 86400 seconds (1 day)." dialog_timeout_default_seconds=86400 fi fi { [[ -n $dialog_timeout_default_seconds ]] && [[ ! $dialog_timeout_default_seconds =~ ${REGEX_ANY_WHOLE_NUMBER} ]]; } && log_super "Parameter Error: The --dialog-timeout-default=seconds value must only be a number." && parameter_error="TRUE" { [[ "${verbose_mode}" == "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} input and if valid set the ${dialog_timeout_user_auth_seconds} parameter. dialog_timeout_user_auth_seconds=$(get_pref_managed "DialogTimeoutUserAuth") if [[ "${dialog_timeout_user_auth_seconds}" == "X" ]] || { [[ -z $dialog_timeout_user_auth_seconds ]] && [[ "${dialog_timeout_user_auth_option}" == "X" ]]; }; then delete_pref "DialogTimeoutUserAuth" unset dialog_timeout_user_auth_seconds elif [[ -z $dialog_timeout_user_auth_seconds ]] && [[ -n "${dialog_timeout_user_auth_option}" ]]; then dialog_timeout_user_auth_seconds="${dialog_timeout_user_auth_option}" set_pref "DialogTimeoutUserAuth" "${dialog_timeout_user_auth_seconds}" fi [[ -z $dialog_timeout_user_auth_seconds ]] && dialog_timeout_user_auth_seconds=$(get_pref "DialogTimeoutUserAuth") if [[ -n $dialog_timeout_user_auth_seconds ]] && [[ $dialog_timeout_user_auth_seconds =~ ${REGEX_ANY_WHOLE_NUMBER} ]]; then if [[ $dialog_timeout_user_auth_seconds -lt 60 ]]; then log_super "Parameter Warning: Specified --dialog-timeout-user-auth=seconds value of ${dialog_timeout_user_auth_seconds} is too low, rounding up to 60 seconds." dialog_timeout_user_auth_seconds=60 elif [[ $dialog_timeout_user_auth_seconds -gt 86400 ]]; then log_super "Parameter Warning: Specified --dialog-timeout-user-auth=seconds value of ${dialog_timeout_user_auth_seconds} is too high, rounding down to 86400 seconds (1 day)." dialog_timeout_user_auth_seconds=86400 fi fi { [[ -n $dialog_timeout_user_auth_seconds ]] && [[ ! $dialog_timeout_user_auth_seconds =~ ${REGEX_ANY_WHOLE_NUMBER} ]]; } && log_super "Parameter Error: The --dialog-timeout-user-auth=seconds value must only be a number." && parameter_error="TRUE" { [[ "${verbose_mode}" == "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} input and if valid set the ${dialog_timeout_user_choice_seconds} parameter. dialog_timeout_user_choice_seconds=$(get_pref_managed "DialogTimeoutUserChoice") if [[ "${dialog_timeout_user_choice_seconds}" == "X" ]] || { [[ -z $dialog_timeout_user_choice_seconds ]] && [[ "${dialog_timeout_user_choice_option}" == "X" ]]; }; then delete_pref "DialogTimeoutUserChoice" unset dialog_timeout_user_choice_seconds elif [[ -z $dialog_timeout_user_choice_seconds ]] && [[ -n "${dialog_timeout_user_choice_option}" ]]; then dialog_timeout_user_choice_seconds="${dialog_timeout_user_choice_option}" set_pref "DialogTimeoutUserChoice" "${dialog_timeout_user_choice_seconds}" fi [[ -z $dialog_timeout_user_choice_seconds ]] && dialog_timeout_user_choice_seconds=$(get_pref "DialogTimeoutUserChoice") if [[ -n $dialog_timeout_user_choice_seconds ]] && [[ $dialog_timeout_user_choice_seconds =~ ${REGEX_ANY_WHOLE_NUMBER} ]]; then if [[ $dialog_timeout_user_choice_seconds -lt 60 ]]; then log_super "Parameter Warning: Specified --dialog-timeout-user-choice=seconds value of ${dialog_timeout_user_choice_seconds} is too low, rounding up to 60 seconds." dialog_timeout_user_choice_seconds=60 elif [[ $dialog_timeout_user_choice_seconds -gt 86400 ]]; then log_super "Parameter Warning: Specified --dialog-timeout-user-choice=seconds value of ${dialog_timeout_user_choice_seconds} is too high, rounding down to 86400 seconds (1 day)." dialog_timeout_user_choice_seconds=86400 fi fi { [[ -n $dialog_timeout_user_choice_seconds ]] && [[ ! $dialog_timeout_user_choice_seconds =~ ${REGEX_ANY_WHOLE_NUMBER} ]]; } && log_super "Parameter Error: The --dialog-timeout-user-choice=seconds value must only be a number." && parameter_error="TRUE" { [[ "${verbose_mode}" == "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} input and if valid set the ${dialog_timeout_user_schedule_seconds} parameter. dialog_timeout_user_schedule_seconds=$(get_pref_managed "DialogTimeoutUserSchedule") if [[ "${dialog_timeout_user_schedule_seconds}" == "X" ]] || { [[ -z $dialog_timeout_user_schedule_seconds ]] && [[ "${dialog_timeout_user_schedule_option}" == "X" ]]; }; then delete_pref "DialogTimeoutUserSchedule" unset dialog_timeout_user_schedule_seconds elif [[ -z $dialog_timeout_user_schedule_seconds ]] && [[ -n "${dialog_timeout_user_schedule_option}" ]]; then dialog_timeout_user_schedule_seconds="${dialog_timeout_user_schedule_option}" set_pref "DialogTimeoutUserSchedule" "${dialog_timeout_user_schedule_seconds}" fi [[ -z $dialog_timeout_user_schedule_seconds ]] && dialog_timeout_user_schedule_seconds=$(get_pref "DialogTimeoutUserSchedule") if [[ -n $dialog_timeout_user_schedule_seconds ]] && [[ $dialog_timeout_user_schedule_seconds =~ ${REGEX_ANY_WHOLE_NUMBER} ]]; then if [[ $dialog_timeout_user_schedule_seconds -lt 60 ]]; then log_super "Parameter Warning: Specified --dialog-timeout-user-schedule=seconds value of ${dialog_timeout_user_schedule_seconds} is too low, rounding up to 60 seconds." dialog_timeout_user_schedule_seconds=60 elif [[ $dialog_timeout_user_schedule_seconds -gt 86400 ]]; then log_super "Parameter Warning: Specified --dialog-timeout-user-schedule=seconds value of ${dialog_timeout_user_schedule_seconds} is too high, rounding down to 86400 seconds (1 day)." dialog_timeout_user_schedule_seconds=86400 fi fi { [[ -n $dialog_timeout_user_schedule_seconds ]] && [[ ! $dialog_timeout_user_schedule_seconds =~ ${REGEX_ANY_WHOLE_NUMBER} ]]; } && log_super "Parameter Error: The --dialog-timeout-user-schedule=seconds value must only be a number." && parameter_error="TRUE" { [[ "${verbose_mode}" == "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} input and if valid set the ${dialog_timeout_soft_deadline_seconds} parameter. dialog_timeout_soft_deadline_seconds=$(get_pref_managed "DialogTimeoutSoftDeadline") if [[ "${dialog_timeout_soft_deadline_seconds}" == "X" ]] || { [[ -z $dialog_timeout_soft_deadline_seconds ]] && [[ "${dialog_timeout_soft_deadline_option}" == "X" ]]; }; then delete_pref "DialogTimeoutSoftDeadline" unset dialog_timeout_soft_deadline_seconds elif [[ -z $dialog_timeout_soft_deadline_seconds ]] && [[ -n "${dialog_timeout_soft_deadline_option}" ]]; then dialog_timeout_soft_deadline_seconds="${dialog_timeout_soft_deadline_option}" set_pref "DialogTimeoutSoftDeadline" "${dialog_timeout_soft_deadline_seconds}" fi [[ -z $dialog_timeout_soft_deadline_seconds ]] && dialog_timeout_soft_deadline_seconds=$(get_pref "DialogTimeoutSoftDeadline") if [[ -n $dialog_timeout_soft_deadline_seconds ]] && [[ $dialog_timeout_soft_deadline_seconds =~ ${REGEX_ANY_WHOLE_NUMBER} ]]; then if [[ $dialog_timeout_soft_deadline_seconds -lt 60 ]]; then log_super "Parameter Warning: Specified --dialog-timeout-soft-deadline=seconds value of ${dialog_timeout_soft_deadline_seconds} is too low, rounding up to 60 seconds." dialog_timeout_soft_deadline_seconds=60 elif [[ $dialog_timeout_soft_deadline_seconds -gt 86400 ]]; then log_super "Parameter Warning: Specified --dialog-timeout-soft-deadline=seconds value of ${dialog_timeout_soft_deadline_seconds} is too high, rounding down to 86400 seconds (1 day)." dialog_timeout_soft_deadline_seconds=86400 fi fi { [[ -n $dialog_timeout_soft_deadline_seconds ]] && [[ ! $dialog_timeout_soft_deadline_seconds =~ ${REGEX_ANY_WHOLE_NUMBER} ]]; } && log_super "Parameter Error: The --dialog-timeout-soft-deadline=seconds value must only be a number." && parameter_error="TRUE" { [[ "${verbose_mode}" == "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} input and if valid set the ${dialog_timeout_insufficient_storage_seconds} parameter. dialog_timeout_insufficient_storage_seconds=$(get_pref_managed "DialogTimeoutInsufficientStorage") if [[ "${dialog_timeout_insufficient_storage_seconds}" == "X" ]] || { [[ -z $dialog_timeout_insufficient_storage_seconds ]] && [[ "${dialog_timeout_insufficient_storage_option}" == "X" ]]; }; then delete_pref "DialogTimeoutInsufficientStorage" unset dialog_timeout_insufficient_storage_seconds elif [[ -z $dialog_timeout_insufficient_storage_seconds ]] && [[ -n "${dialog_timeout_insufficient_storage_option}" ]]; then dialog_timeout_insufficient_storage_seconds="${dialog_timeout_insufficient_storage_option}" set_pref "DialogTimeoutInsufficientStorage" "${dialog_timeout_insufficient_storage_seconds}" fi [[ -z $dialog_timeout_insufficient_storage_seconds ]] && dialog_timeout_insufficient_storage_seconds=$(get_pref "DialogTimeoutInsufficientStorage") if [[ -n $dialog_timeout_insufficient_storage_seconds ]] && [[ $dialog_timeout_insufficient_storage_seconds =~ ${REGEX_ANY_WHOLE_NUMBER} ]]; then if [[ $dialog_timeout_insufficient_storage_seconds -lt 60 ]]; then log_super "Parameter Warning: Specified --dialog-timeout-insufficient-storage=seconds value of ${dialog_timeout_insufficient_storage_seconds} is too low, rounding up to 60 seconds." dialog_timeout_insufficient_storage_seconds=60 elif [[ $dialog_timeout_insufficient_storage_seconds -gt 86400 ]]; then log_super "Parameter Warning: Specified --dialog-timeout-insufficient-storage=seconds value of ${dialog_timeout_insufficient_storage_seconds} is too high, rounding down to 86400 seconds (1 day)." dialog_timeout_insufficient_storage_seconds=86400 fi fi { [[ -n $dialog_timeout_insufficient_storage_seconds ]] && [[ ! $dialog_timeout_insufficient_storage_seconds =~ ${REGEX_ANY_WHOLE_NUMBER} ]]; } && log_super "Parameter Error: The --dialog-timeout-insufficient-storage=seconds value must only be a number." && parameter_error="TRUE" { [[ "${verbose_mode}" == "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} input and if valid set the ${dialog_timeout_power_required_seconds} parameter. dialog_timeout_power_required_seconds=$(get_pref_managed "DialogTimeoutPowerRequired") if [[ "${dialog_timeout_power_required_seconds}" == "X" ]] || { [[ -z $dialog_timeout_power_required_seconds ]] && [[ "${dialog_timeout_power_required_option}" == "X" ]]; }; then delete_pref "DialogTimeoutPowerRequired" unset dialog_timeout_power_required_seconds elif [[ -z $dialog_timeout_power_required_seconds ]] && [[ -n "${dialog_timeout_power_required_option}" ]]; then dialog_timeout_power_required_seconds="${dialog_timeout_power_required_option}" set_pref "DialogTimeoutPowerRequired" "${dialog_timeout_power_required_seconds}" fi [[ -z $dialog_timeout_power_required_seconds ]] && dialog_timeout_power_required_seconds=$(get_pref "DialogTimeoutPowerRequired") if [[ -n $dialog_timeout_power_required_seconds ]] && [[ $dialog_timeout_power_required_seconds =~ ${REGEX_ANY_WHOLE_NUMBER} ]]; then if [[ $dialog_timeout_power_required_seconds -lt 60 ]]; then log_super "Parameter Warning: Specified -dialog-timeout-power-required=seconds value of ${dialog_timeout_power_required_seconds} is too low, rounding up to 60 seconds." dialog_timeout_power_required_seconds=60 elif [[ $dialog_timeout_power_required_seconds -gt 86400 ]]; then log_super "Parameter Warning: Specified -dialog-timeout-power-required=seconds value of ${dialog_timeout_power_required_seconds} is too high, rounding down to 86400 seconds (1 day)." dialog_timeout_power_required_seconds=86400 fi fi { [[ -n $dialog_timeout_power_required_seconds ]] && [[ ! $dialog_timeout_power_required_seconds =~ ${REGEX_ANY_WHOLE_NUMBER} ]]; } && log_super "Parameter Error: The -dialog-timeout-power-required=seconds value must only be a number." && parameter_error="TRUE" { [[ "${verbose_mode}" == "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} input and if valid set the ${display_unmovable} parameter. display_unmovable=$(get_pref_managed "DisplayUnmovable") if [[ "${display_unmovable}" == "X" ]] || { [[ -z "${display_unmovable}" ]] && [[ "${display_unmovable_option}" == "X" ]]; }; then delete_pref "DisplayUnmovable" unset display_unmovable elif [[ -z "${display_unmovable}" ]] && [[ -n "${display_unmovable_option}" ]]; then display_unmovable="${display_unmovable_option}" set_pref "DisplayUnmovable" "${display_unmovable}" fi [[ -z "${display_unmovable}" ]] && display_unmovable=$(get_pref "DisplayUnmovable") if [[ -n "${display_unmovable}" ]]; then previous_ifs="${IFS}" IFS=',' local display_unmovable_array read -r -a display_unmovable_array <<<"${display_unmovable}" for option_type in "${display_unmovable_array[@]}"; do { [[ ! "${option_type}" =~ ${REGEX_WORKFLOW_OPTIONS} ]]; } && 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" && parameter_error="TRUE" done IFS="${previous_ifs}" [[ $(echo "${display_unmovable}" | grep -c 'ALWAYS') -gt 0 ]] && display_unmovable_status="TRUE" fi { [[ "${verbose_mode}" == "TRUE" ]] && [[ -n "${display_unmovable}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: display_unmovable is: ${display_unmovable}" { [[ "${verbose_mode}" == "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} input and if valid set the ${display_hide_background} parameter. display_hide_background=$(get_pref_managed "DisplayHideBackground") if [[ "${display_hide_background}" == "X" ]] || { [[ -z "${display_hide_background}" ]] && [[ "${display_hide_background_option}" == "X" ]]; }; then delete_pref "DisplayHideBackground" unset display_hide_background elif [[ -z "${display_hide_background}" ]] && [[ -n "${display_hide_background_option}" ]]; then display_hide_background="${display_hide_background_option}" set_pref "DisplayHideBackground" "${display_hide_background}" fi [[ -z "${display_hide_background}" ]] && display_hide_background=$(get_pref "DisplayHideBackground") if [[ -n "${display_hide_background}" ]]; then previous_ifs="${IFS}" IFS=',' local display_hide_background_array read -r -a display_hide_background_array <<<"${display_hide_background}" for option_type in "${display_hide_background_array[@]}"; do { [[ ! "${option_type}" =~ ${REGEX_WORKFLOW_OPTIONS} ]]; } && 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" && parameter_error="TRUE" done IFS="${previous_ifs}" [[ $(echo "${display_hide_background}" | grep -c 'ALWAYS') -gt 0 ]] && display_hide_background_status="TRUE" fi { [[ "${verbose_mode}" == "TRUE" ]] && [[ -n "${display_hide_background}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: display_hide_background is: ${display_hide_background}" { [[ "${verbose_mode}" == "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} input and if valid set the ${display_silently} parameter. display_silently=$(get_pref_managed "DisplaySilently") if [[ "${display_silently}" == "X" ]] || { [[ -z "${display_silently}" ]] && [[ "${display_silently_option}" == "X" ]]; }; then delete_pref "DisplaySilently" unset display_silently elif [[ -z "${display_silently}" ]] && [[ -n "${display_silently_option}" ]]; then display_silently="${display_silently_option}" set_pref "DisplaySilently" "${display_silently}" fi [[ -z "${display_silently}" ]] && display_silently=$(get_pref "DisplaySilently") if [[ -n "${display_silently}" ]]; then previous_ifs="${IFS}" IFS=',' local display_silently_array read -r -a display_silently_array <<<"${display_silently}" for option_type in "${display_silently_array[@]}"; do { [[ ! "${option_type}" =~ ${REGEX_WORKFLOW_OPTIONS} ]]; } && 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" && parameter_error="TRUE" done IFS="${previous_ifs}" [[ $(echo "${display_silently}" | grep -c 'ALWAYS') -gt 0 ]] && display_silently_status="TRUE" fi { [[ "${verbose_mode}" == "TRUE" ]] && [[ -n "${display_silently}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: display_silently is: ${display_silently}" { [[ "${verbose_mode}" == "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} input and if valid set the ${display_hide_progress_bar} parameter. display_hide_progress_bar=$(get_pref_managed "DisplayHideProgressBar") if [[ "${display_hide_progress_bar}" == "X" ]] || { [[ -z "${display_hide_progress_bar}" ]] && [[ "${display_hide_progress_bar_option}" == "X" ]]; }; then delete_pref "DisplayHideProgressBar" unset display_hide_progress_bar elif [[ -z "${display_hide_progress_bar}" ]] && [[ -n "${display_hide_progress_bar_option}" ]]; then display_hide_progress_bar="${display_hide_progress_bar_option}" set_pref "DisplayHideProgressBar" "${display_hide_progress_bar}" fi [[ -z "${display_hide_progress_bar}" ]] && display_hide_progress_bar=$(get_pref "DisplayHideProgressBar") if [[ -n "${display_hide_progress_bar}" ]]; then previous_ifs="${IFS}" IFS=',' local display_hide_progress_bar_array read -r -a display_hide_progress_bar_array <<<"${display_hide_progress_bar}" for option_type in "${display_hide_progress_bar_array[@]}"; do { [[ ! "${option_type}" =~ ${REGEX_WORKFLOW_OPTIONS} ]]; } && 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" && parameter_error="TRUE" done IFS="${previous_ifs}" [[ $(echo "${display_hide_progress_bar}" | grep -c 'ALWAYS') -gt 0 ]] && display_hide_progress_bar_status="TRUE" fi { [[ "${verbose_mode}" == "TRUE" ]] && [[ -n "${display_hide_progress_bar}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: display_hide_progress_bar is: ${display_hide_progress_bar}" { [[ "${verbose_mode}" == "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} input and if valid set the ${display_notifications_centered} parameter. display_notifications_centered=$(get_pref_managed "DisplayNotificationsCentered") if [[ "${display_notifications_centered}" == "X" ]] || { [[ -z "${display_notifications_centered}" ]] && [[ "${display_notifications_centered_option}" == "X" ]]; }; then delete_pref "DisplayNotificationsCentered" unset display_notifications_centered elif [[ -z "${display_notifications_centered}" ]] && [[ -n "${display_notifications_centered_option}" ]]; then display_notifications_centered="${display_notifications_centered_option}" set_pref "DisplayNotificationsCentered" "${display_notifications_centered}" fi [[ -z "${display_notifications_centered}" ]] && display_notifications_centered=$(get_pref "DisplayNotificationsCentered") if [[ -n "${display_notifications_centered}" ]]; then previous_ifs="${IFS}" IFS=',' local display_notifications_centered_array read -r -a display_notifications_centered_array <<<"${display_notifications_centered}" for option_type in "${display_notifications_centered_array[@]}"; do { [[ ! "${option_type}" =~ ${REGEX_WORKFLOW_OPTIONS} ]]; } && 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,DIALOG,DEADLINE,SCHEDULED,INSTALLNOW,ERROR" && parameter_error="TRUE" done IFS="${previous_ifs}" [[ $(echo "${display_notifications_centered}" | grep -c 'ALWAYS') -gt 0 ]] && display_notifications_centered_status="TRUE" fi { [[ "${verbose_mode}" == "TRUE" ]] && [[ -n "${display_notifications_centered}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: display_notifications_centered is: ${display_notifications_centered}" { [[ "${verbose_mode}" == "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 set the ${display_icon_size} parameter. If there is no ${display_icon_size} then set it to ${DISPLAY_ICON_DEFAULT_SIZE}. display_icon_size=$(get_pref_managed "DisplayIconSize") if [[ "${display_icon_size}" == "X" ]] || { [[ -z $display_icon_size ]] && [[ "${display_icon_size_option}" == "X" ]]; }; then delete_pref "DisplayIconSize" unset display_icon_size elif [[ -z $display_icon_size ]] && [[ -n "${display_icon_size_option}" ]]; then display_icon_size="${display_icon_size_option}" set_pref "DisplayIconSize" "${display_icon_size}" fi [[ -z $display_icon_size ]] && display_icon_size=$(get_pref "DisplayIconSize") if [[ -n $display_icon_size ]] && [[ $display_icon_size =~ ${REGEX_ANY_WHOLE_NUMBER} ]]; then if [[ $display_icon_size -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 -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 fi fi { [[ -n $display_icon_size ]] && [[ ! $display_icon_size =~ ${REGEX_ANY_WHOLE_NUMBER} ]]; } && log_super "Parameter Error: The --display-icon-size=pixels value must only be a number." && parameter_error="TRUE" [[ -z $display_icon_size ]] && display_icon_size="${DISPLAY_ICON_DEFAULT_SIZE}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: display_icon_size is: ${display_icon_size}" # Validate ${display_icon_file_option} input and if valid set the ${display_icon_file_path} parameter. If there is no ${display_icon_file_path} then set it to ${DISPLAY_ICON_DEFAULT_FILE}. display_icon_file_path=$(get_pref_managed "DisplayIconFile") if [[ "${display_icon_file_path}" == "X" ]] || { [[ -z "${display_icon_file_path}" ]] && [[ "${display_icon_file_option}" == "X" ]]; }; then delete_pref "DisplayIconFile" delete_pref "DisplayIconLightFileCachedOrigin" delete_pref "DisplayIconDarkFileCachedOrigin" unset display_icon_file_path unset display_icon_light_file_cached_origin unset display_icon_dark_file_cached_origin elif [[ -z "${display_icon_file_path}" ]] && [[ -n "${display_icon_file_option}" ]]; then display_icon_file_path="${display_icon_file_option}" set_pref "DisplayIconFile" "${display_icon_file_path}" fi [[ -z "${display_icon_file_path}" ]] && display_icon_file_path=$(get_pref "DisplayIconFile") [[ -z "${display_icon_file_path}" ]] && display_icon_file_path="${DISPLAY_ICON_DEFAULT_FILE}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: display_icon_file_path is: ${display_icon_file_path}" # Validate ${display_icon_light_file_option} input and if valid set the ${display_icon_light_file_path} parameter. If there is no ${display_icon_light_file_path} then set it to ${display_icon_file_path}. display_icon_light_file_path=$(get_pref_managed "DisplayIconLightFile") if [[ "${display_icon_light_file_path}" == "X" ]] || { [[ -z "${display_icon_light_file_path}" ]] && [[ "${display_icon_light_file_option}" == "X" ]]; }; then delete_pref "DisplayIconLightFile" delete_pref "DisplayIconLightFileCachedOrigin" unset display_icon_light_file_path unset display_icon_light_file_cached_origin elif [[ -z "${display_icon_light_file_path}" ]] && [[ -n "${display_icon_light_file_option}" ]]; then display_icon_light_file_path="${display_icon_light_file_option}" set_pref "DisplayIconLightFile" "${display_icon_light_file_path}" fi [[ -z "${display_icon_light_file_path}" ]] && display_icon_light_file_path=$(get_pref "DisplayIconLightFile") [[ -z "${display_icon_light_file_path}" ]] && display_icon_light_file_path="${display_icon_file_path}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: display_icon_light_file_path is: ${display_icon_light_file_path}" # Validate ${display_icon_dark_file_option} input and if valid set the ${display_icon_dark_file_path} parameter. If there is no ${display_icon_dark_file_path} then set it to ${display_icon_file_path}. display_icon_dark_file_path=$(get_pref_managed "DisplayIconDarkFile") if [[ "${display_icon_dark_file_path}" == "X" ]] || { [[ -z "${display_icon_dark_file_path}" ]] && [[ "${display_icon_dark_file_option}" == "X" ]]; }; then delete_pref "DisplayIconDarkFile" delete_pref "DisplayIconDarkFileCachedOrigin" unset display_icon_dark_file_path unset display_icon_dark_file_cached_origin elif [[ -z "${display_icon_dark_file_path}" ]] && [[ -n "${display_icon_dark_file_option}" ]]; then display_icon_dark_file_path="${display_icon_dark_file_option}" set_pref "DisplayIconDarkFile" "${display_icon_dark_file_path}" fi [[ -z "${display_icon_dark_file_path}" ]] && display_icon_dark_file_path=$(get_pref "DisplayIconDarkFile") [[ -z "${display_icon_dark_file_path}" ]] && display_icon_dark_file_path="${display_icon_file_path}" [[ "${verbose_mode}" == "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} input and if valid set the ${display_accessory_type} parameter. Validating the actual display accessory files themselves are handled later during the workflow. display_accessory_type=$(get_pref_managed "DisplayAccessoryType") if [[ "${display_accessory_type}" == "X" ]] || { [[ -z "${display_accessory_type}" ]] && [[ "${display_accessory_type_option}" == "X" ]]; }; then delete_pref "DisplayAccessoryType" unset display_accessory_type elif [[ -z "${display_accessory_type}" ]] && [[ -n "${display_accessory_type_option}" ]]; then display_accessory_type="${display_accessory_type_option}" set_pref "DisplayAccessoryType" "${display_accessory_type}" fi [[ -z "${display_accessory_type}" ]] && display_accessory_type=$(get_pref "DisplayAccessoryType") { [[ -n "${display_accessory_type}" ]] && [[ ! "${display_accessory_type}" =~ ^TEXTBOX$|^HTMLBOX$|^HTML$|^IMAGE$|^VIDEO$|^VIDEOAUTO$ ]]; } && log_super "Parameter Error: Unrecognized --display-accessory-type value of ${display_accessory_type}. You must specify one of the following; TEXTBOX, HTMLBOX, HTML, IMAGE, VIDEO, or VIDEOAUTO." && parameter_error="TRUE" { [[ "${verbose_mode}" == "TRUE" ]] && [[ -n "${display_accessory_type}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: display_accessory_type is: ${display_accessory_type}" # Validate ${display_accessory_default_file_option} input and if valid set the ${display_accessory_default} parameter. Validating the actual display accessory files themselves are handled later during the workflow. display_accessory_default=$(get_pref_managed "DisplayAccessoryDefaultFile") if [[ "${display_accessory_default}" == "X" ]] || { [[ -z "${display_accessory_default}" ]] && [[ "${display_accessory_default_file_option}" == "X" ]]; }; then delete_pref "DisplayAccessoryDefaultFile" unset display_accessory_default elif [[ -z "${display_accessory_default}" ]] && [[ -n "${display_accessory_default_file_option}" ]]; then display_accessory_default="${display_accessory_default_file_option}" set_pref "DisplayAccessoryDefaultFile" "${display_accessory_default}" fi [[ -z "${display_accessory_default}" ]] && display_accessory_default=$(get_pref "DisplayAccessoryDefaultFile") { [[ "${verbose_mode}" == "TRUE" ]] && [[ -n "${display_accessory_default}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: display_accessory_default is: ${display_accessory_default}" # Validate ${display_accessory_macos_minor_update_file_option} input and if valid set the ${display_accessory_macos_minor_update} parameter. Validating the actual display accessory files themselves are handled later during the workflow. display_accessory_macos_minor_update=$(get_pref_managed "DisplayAccessoryMacOSMinorUpdateFile") if [[ "${display_accessory_macos_minor_update}" == "X" ]] || { [[ -z "${display_accessory_macos_minor_update}" ]] && [[ "${display_accessory_macos_minor_update_file_option}" == "X" ]]; }; then delete_pref "DisplayAccessoryMacOSMinorUpdateFile" unset display_accessory_macos_minor_update elif [[ -z "${display_accessory_macos_minor_update}" ]] && [[ -n "${display_accessory_macos_minor_update_file_option}" ]]; then display_accessory_macos_minor_update="${display_accessory_macos_minor_update_file_option}" set_pref "DisplayAccessoryMacOSMinorUpdateFile" "${display_accessory_macos_minor_update}" fi [[ -z "${display_accessory_macos_minor_update}" ]] && display_accessory_macos_minor_update=$(get_pref "DisplayAccessoryMacOSMinorUpdateFile") { [[ "${verbose_mode}" == "TRUE" ]] && [[ -n "${display_accessory_macos_minor_update}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: display_accessory_macos_minor_update is: ${display_accessory_macos_minor_update}" # Validate ${display_accessory_macos_major_upgrade_file_option} input and if valid set the ${display_accessory_macos_major_upgrade} parameter. Validating the actual display accessory files themselves are handled later during the workflow. display_accessory_macos_major_upgrade=$(get_pref_managed "DisplayAccessoryMacOSMajorUpgradeFile") if [[ "${display_accessory_macos_major_upgrade}" == "X" ]] || { [[ -z "${display_accessory_macos_major_upgrade}" ]] && [[ "${display_accessory_macos_major_upgrade_file_option}" == "X" ]]; }; then delete_pref "DisplayAccessoryMacOSMajorUpgradeFile" unset display_accessory_macos_major_upgrade elif [[ -z "${display_accessory_macos_major_upgrade}" ]] && [[ -n "${display_accessory_macos_major_upgrade_file_option}" ]]; then display_accessory_macos_major_upgrade="${display_accessory_macos_major_upgrade_file_option}" set_pref "DisplayAccessoryMacOSMajorUpgradeFile" "${display_accessory_macos_major_upgrade}" fi [[ -z "${display_accessory_macos_major_upgrade}" ]] && display_accessory_macos_major_upgrade=$(get_pref "DisplayAccessoryMacOSMajorUpgradeFile") { [[ "${verbose_mode}" == "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}" # Validate ${display_accessory_non_system_updates_file_option} input and if valid set the ${display_accessory_non_system_updates} parameter. Validating the actual display accessory files themselves are handled later during the workflow. display_accessory_non_system_updates=$(get_pref_managed "DisplayAccessoryNonSystemUpdatesFile") if [[ "${display_accessory_non_system_updates}" == "X" ]] || { [[ -z "${display_accessory_non_system_updates}" ]] && [[ "${display_accessory_non_system_updates_file_option}" == "X" ]]; }; then delete_pref "DisplayAccessoryNonSystemUpdatesFile" unset display_accessory_non_system_updates elif [[ -z "${display_accessory_non_system_updates}" ]] && [[ -n "${display_accessory_non_system_updates_file_option}" ]]; then display_accessory_non_system_updates="${display_accessory_non_system_updates_file_option}" set_pref "DisplayAccessoryNonSystemUpdatesFile" "${display_accessory_non_system_updates}" fi [[ -z "${display_accessory_non_system_updates}" ]] && display_accessory_non_system_updates=$(get_pref "DisplayAccessoryNonSystemUpdatesFile") { [[ "${verbose_mode}" == "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}" # Validate ${display_accessory_safari_update_file_option} input and if valid set the ${display_accessory_safari_update} parameter. Validating the actual display accessory files themselves are handled later during the workflow. display_accessory_safari_update=$(get_pref_managed "DisplayAccessorySafariUpdateFile") if [[ "${display_accessory_safari_update}" == "X" ]] || { [[ -z "${display_accessory_safari_update}" ]] && [[ "${display_accessory_safari_update_file_option}" == "X" ]]; }; then delete_pref "DisplayAccessorySafariUpdateFile" unset display_accessory_safari_update elif [[ -z "${display_accessory_safari_update}" ]] && [[ -n "${display_accessory_safari_update_file_option}" ]]; then display_accessory_safari_update="${display_accessory_safari_update_file_option}" set_pref "DisplayAccessorySafariUpdateFile" "${display_accessory_safari_update}" fi [[ -z "${display_accessory_safari_update}" ]] && display_accessory_safari_update=$(get_pref "DisplayAccessorySafariUpdateFile") { [[ "${verbose_mode}" == "TRUE" ]] && [[ -n "${display_accessory_safari_update}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: display_accessory_safari_update is: ${display_accessory_safari_update}" # Validate ${display_accessory_jamf_policy_triggers_file_option} input and if valid set the ${display_accessory_jamf_policy_triggers} parameter. Validating the actual display accessory files themselves are handled later during the workflow. display_accessory_jamf_policy_triggers=$(get_pref_managed "DisplayAccessoryJamfPolicyTriggersFile") if [[ "${display_accessory_jamf_policy_triggers}" == "X" ]] || { [[ -z "${display_accessory_jamf_policy_triggers}" ]] && [[ "${display_accessory_jamf_policy_triggers_file_option}" == "X" ]]; }; then delete_pref "DisplayAccessoryJamfPolicyTriggersFile" unset display_accessory_jamf_policy_triggers elif [[ -z "${display_accessory_jamf_policy_triggers}" ]] && [[ -n "${display_accessory_jamf_policy_triggers_file_option}" ]]; then display_accessory_jamf_policy_triggers="${display_accessory_jamf_policy_triggers_file_option}" set_pref "DisplayAccessoryJamfPolicyTriggersFile" "${display_accessory_jamf_policy_triggers}" fi [[ -z "${display_accessory_jamf_policy_triggers}" ]] && display_accessory_jamf_policy_triggers=$(get_pref "DisplayAccessoryJamfPolicyTriggersFile") { [[ "${verbose_mode}" == "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}" # Validate ${display_accessory_restart_without_updates_file_option} input and if valid set the ${display_accessory_restart_without_updates} parameter. Validating the actual display accessory files themselves are handled later during the workflow. display_accessory_restart_without_updates=$(get_pref_managed "DisplayAccessoryRestartWithoutUpdatesFile") if [[ "${display_accessory_restart_without_updates}" == "X" ]] || { [[ -z "${display_accessory_restart_without_updates}" ]] && [[ "${display_accessory_restart_without_updates_file_option}" == "X" ]]; }; then delete_pref "DisplayAccessoryRestartWithoutUpdatesFile" unset display_accessory_restart_without_updates elif [[ -z "${display_accessory_restart_without_updates}" ]] && [[ -n "${display_accessory_restart_without_updates_file_option}" ]]; then display_accessory_restart_without_updates="${display_accessory_restart_without_updates_file_option}" set_pref "DisplayAccessoryRestartWithoutUpdatesFile" "${display_accessory_restart_without_updates}" fi [[ -z "${display_accessory_restart_without_updates}" ]] && display_accessory_restart_without_updates=$(get_pref "DisplayAccessoryRestartWithoutUpdatesFile") { [[ "${verbose_mode}" == "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_accessory_type} and the various ${display_accessory_*_file} parameters in relation to each other. { [[ -n "${display_accessory_type}" ]] && [[ -z "${display_accessory_default}" ]] && [[ -z "${display_accessory_macos_minor_update}" ]] && [[ -z "${display_accessory_macos_major_upgrade}" ]] && [[ -z "${display_accessory_non_system_updates}" ]] && [[ -z "${display_accessory_safari_update}" ]] && [[ -z "${display_accessory_jamf_policy_triggers}" ]] && [[ -z "${display_accessory_restart_without_updates}" ]]; } && log_super "Parameter Error: To use the --display-accessory-type option you must also specify one of the display accessory file options." && parameter_error="TRUE" { [[ -z "${display_accessory_type}" ]] && { [[ -n "${display_accessory_default}" ]] || [[ -n "${display_accessory_macos_minor_update}" ]] || [[ -n "${display_accessory_macos_major_upgrade}" ]] || [[ -n "${display_accessory_non_system_updates}" ]] || [[ -n "${display_accessory_safari_update}" ]] || [[ -n "${display_accessory_jamf_policy_triggers}" ]] || [[ -n "${display_accessory_restart_without_updates}" ]]; }; } && log_super "Parameter Error: To use any of the display accessory file options you must also specify the --display-accessory-type option." && parameter_error="TRUE" # Validate ${display_help_button_string_option} input and if valid set the ${display_help_button_string} parameter. display_help_button_string=$(get_pref_managed "DisplayHelpButtonString") if [[ "${display_help_button_string}" == "X" ]] || { [[ -z "${display_help_button_string}" ]] && [[ "${display_help_button_string_option}" == "X" ]]; }; then delete_pref "DisplayHelpButtonString" unset display_help_button_string elif [[ -z "${display_help_button_string}" ]] && [[ -n "${display_help_button_string_option}" ]]; then display_help_button_string="${display_help_button_string_option}" set_pref "DisplayHelpButtonString" "${display_help_button_string}" fi [[ -z "${display_help_button_string}" ]] && display_help_button_string=$(get_pref "DisplayHelpButtonString") { [[ "${verbose_mode}" == "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} input and if valid set the ${display_warning_button_string} parameter. display_warning_button_string=$(get_pref_managed "DisplayWarningButtonString") if [[ "${display_warning_button_string}" == "X" ]] || { [[ -z "${display_warning_button_string}" ]] && [[ "${display_warning_button_string_option}" == "X" ]]; }; then delete_pref "DisplayWarningButtonString" unset display_warning_button_string elif [[ -z "${display_warning_button_string}" ]] && [[ -n "${display_warning_button_string_option}" ]]; then display_warning_button_string="${display_warning_button_string_option}" set_pref "DisplayWarningButtonString" "${display_warning_button_string}" fi [[ -z "${display_warning_button_string}" ]] && display_warning_button_string=$(get_pref "DisplayWarningButtonString") { [[ "${verbose_mode}" == "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 # Manage ${auth_ask_user_to_save_password} parameter. auth_ask_user_to_save_password=$(get_pref_managed "AuthAskUserToSavePassword") { [[ -z "${auth_ask_user_to_save_password}" ]] && [[ -n "${auth_ask_user_to_save_password_option}" ]] && [[ "${auth_delete_all_option}" != "TRUE" ]]; } && auth_ask_user_to_save_password="${auth_ask_user_to_save_password_option}" && set_pref_main "AuthAskUserToSavePassword" "${auth_ask_user_to_save_password_option}" { [[ -z "${auth_ask_user_to_save_password}" ]] && [[ "${auth_delete_all_option}" != "TRUE" ]]; } && auth_ask_user_to_save_password=$(get_pref "AuthAskUserToSavePassword") { [[ "${verbose_mode}" == "TRUE" ]] && [[ -n "${auth_ask_user_to_save_password}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_ask_user_to_save_password is: ${auth_ask_user_to_save_password}" # Identify and log authentication option conflicts. { [[ -n "${auth_local_account_option}" ]] && [[ -z "${auth_local_password_option}" ]]; } && log_super "Auth Error: The --auth-local-account option requires that you also use the --auth-local-password option." && parameter_error="TRUE" { [[ -z "${auth_local_account_option}" ]] && [[ -n "${auth_local_password_option}" ]]; } && log_super "Auth Error: The --auth-local-password option requires that you also set the --auth-local-account option." && parameter_error="TRUE" { [[ -n "${auth_service_add_via_admin_account_option}" ]] && [[ -z "${auth_service_add_via_admin_password_option}" ]]; } && 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." && parameter_error="TRUE" { [[ -z "${auth_service_add_via_admin_account_option}" ]] && [[ -n "${auth_service_add_via_admin_password_option}" ]]; } && 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." && parameter_error="TRUE" { [[ -n "${auth_service_account_option}" ]] && { [[ -z "${auth_service_add_via_admin_account_option}" ]] || [[ -z "${auth_service_add_via_admin_password_option}" ]]; }; } && 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." && parameter_error="TRUE" { [[ -n "${auth_service_password_option}" ]] && { [[ -z "${auth_service_add_via_admin_account_option}" ]] || [[ -z "${auth_service_add_via_admin_password_option}" ]]; }; } && 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." && parameter_error="TRUE" { [[ -n "${auth_jamf_client_option}" ]] && [[ -z "${auth_jamf_secret_option}" ]]; } && log_super "Auth Error: The --auth-jamf-client option requires that you also set the --auth-jamf-secret option." && parameter_error="TRUE" { [[ -z "${auth_jamf_client_option}" ]] && [[ -n "${auth_jamf_secret_option}" ]]; } && log_super "Auth Error: The --auth-jamf-secret option requires that you also set the --auth-jamf-client." && parameter_error="TRUE" { [[ -n "${auth_jamf_client_option}" ]] && [[ $jamf_version_normalized -lt 104900 ]]; } && 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_full} does not support this option." && parameter_error="TRUE" { [[ -n "${auth_jamf_account_option}" ]] && [[ -z "${auth_jamf_password_option}" ]]; } && log_super "Auth Error: The --auth-jamf-account option requires that you also set the --auth-jamf-password option." && parameter_error="TRUE" { [[ -z "${auth_jamf_account_option}" ]] && [[ -n "${auth_jamf_password_option}" ]]; } && log_super "Auth Error: The --auth-jamf-password option requires that you also set the --auth-jamf-account." && parameter_error="TRUE" { [[ -n "${auth_jamf_account_option}" ]] && [[ $jamf_version_normalized -ge 104900 ]]; } && 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." # Validate ${auth_jamf_custom_url_option} input and if valid set the ${auth_jamf_custom_url} parameter. auth_jamf_custom_url=$(get_pref_managed "AuthJamfCustomURL") if [[ "${auth_jamf_custom_url}" == "X" ]] || { [[ -z "${auth_jamf_custom_url}" ]] && [[ "${auth_jamf_custom_url_option}" == "X" ]]; }; then delete_pref_main "AuthJamfCustomURL" unset auth_jamf_custom_url elif [[ -z "${auth_jamf_custom_url}" ]] && [[ -n "${auth_jamf_custom_url_option}" ]]; then auth_jamf_custom_url="${auth_jamf_custom_url_option}" set_pref_main "AuthJamfCustomURL" "${auth_jamf_custom_url}" fi [[ -z "${auth_jamf_custom_url}" ]] && auth_jamf_custom_url=$(get_pref "AuthJamfCustomURL") if [[ -n "${auth_jamf_custom_url}" ]] && [[ "${auth_jamf_custom_url}" =~ ${REGEX_HTTPS} ]] && [[ ! "${auth_jamf_custom_url}" =~ .*\/$ ]]; then auth_jamf_custom_url="${auth_jamf_custom_url}/" log_super "Parameter Warning: Adding a trailing slash to the --auth-jamf-custom-url value: ${auth_jamf_custom_url}" fi { [[ -n "${auth_jamf_custom_url}" ]] && [[ ! "${auth_jamf_custom_url}" =~ ${REGEX_HTTPS} ]]; } && log_super "Parameter Error: Invalid --auth-jamf-custom-url=URL VALUE must start with 'https://': ${auth_jamf_custom_url}" && parameter_error="TRUE" { [[ "${verbose_mode}" == "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} parameter. auth_credential_failover_to_user=$(get_pref_managed "AuthCredentialFailoverToUser") { [[ -z "${auth_credential_failover_to_user}" ]] && [[ -n "${auth_credential_failover_to_user_option}" ]]; } && auth_credential_failover_to_user="${auth_credential_failover_to_user_option}" && set_pref_main "AuthCredentialFailoverToUser" "${auth_credential_failover_to_user_option}" [[ -z "${auth_credential_failover_to_user}" ]] && auth_credential_failover_to_user=$(get_pref "AuthCredentialFailoverToUser") { [[ "${verbose_mode}" == "TRUE" ]] && [[ -n "${auth_credential_failover_to_user}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_credential_failover_to_user is: ${auth_credential_failover_to_user}" # Validate ${auth_mdm_failover_to_user_option} input and if valid set the ${auth_mdm_failover_to_user} parameter. auth_mdm_failover_to_user=$(get_pref_managed "AuthMDMFailoverToUser") if [[ "${auth_mdm_failover_to_user}" == "X" ]] || { [[ -z "${auth_mdm_failover_to_user}" ]] && [[ "${auth_mdm_failover_to_user_option}" == "X" ]]; }; then delete_pref_main "AuthMDMFailoverToUser" unset auth_mdm_failover_to_user elif [[ -z "${auth_mdm_failover_to_user}" ]] && [[ -n "${auth_mdm_failover_to_user_option}" ]]; then auth_mdm_failover_to_user="${auth_mdm_failover_to_user_option}" set_pref_main "AuthMDMFailoverToUser" "${auth_mdm_failover_to_user}" fi [[ -z "${auth_mdm_failover_to_user}" ]] && auth_mdm_failover_to_user=$(get_pref "AuthMDMFailoverToUser") if [[ -n "${auth_mdm_failover_to_user}" ]]; 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}" for option_type in "${auth_mdm_failover_to_user_array[@]}"; do { [[ ! "${option_type}" =~ ${REGEX_WORKFLOW_OPTIONS} ]]; } && 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" && parameter_error="TRUE" done IFS="${previous_ifs}" [[ $(echo "${auth_mdm_failover_to_user}" | grep -c 'ALWAYS') -gt 0 ]] && auth_mdm_failover_to_user_status="TRUE" fi { [[ "${verbose_mode}" == "TRUE" ]] && [[ -n "${auth_mdm_failover_to_user}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_mdm_failover_to_user is: ${auth_mdm_failover_to_user}" { [[ "${verbose_mode}" == "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}" fi # Manage ${test_mode} parameter. test_mode=$(get_pref_managed "TestMode") { [[ -z "${test_mode}" ]] && [[ -n "${test_mode_option}" ]]; } && test_mode="${test_mode_option}" && set_pref "TestMode" "${test_mode_option}" [[ -z "${test_mode}" ]] && test_mode=$(get_pref "TestMode") [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: test_mode is: ${test_mode}" # Validate ${test_mode_timeout_option} input and if valid set the ${test_mode_timeout_seconds} parameter. If there is no ${test_mode_timeout_seconds} then set it to ${TEST_MODE_DEFAULT_TIMEOUT}. test_mode_timeout_seconds=$(get_pref_managed "TestModeTimeout") if [[ "${test_mode_timeout_seconds}" == "X" ]] || { [[ -z $test_mode_timeout_seconds ]] && [[ "${test_mode_timeout_option}" == "X" ]]; }; then delete_pref "TestModeTimeout" unset test_mode_timeout_seconds elif [[ -z $test_mode_timeout_seconds ]] && [[ -n "${test_mode_timeout_option}" ]]; then test_mode_timeout_seconds="${test_mode_timeout_option}" set_pref "TestModeTimeout" "${test_mode_timeout_seconds}" fi [[ -z $test_mode_timeout_seconds ]] && test_mode_timeout_seconds=$(get_pref "TestModeTimeout") if [[ -n $test_mode_timeout_seconds ]] && [[ $test_mode_timeout_seconds =~ ${REGEX_ANY_WHOLE_NUMBER} ]]; then if [[ $test_mode_timeout_seconds -lt 10 ]]; then log_super "Parameter Warning: Specified --test-mode-timeout=seconds value of ${test_mode_timeout_seconds} seconds is too low, rounding up to 10 seconds." test_mode_timeout_seconds=10 elif [[ $test_mode_timeout_seconds -gt 120 ]]; then log_super "Parameter Warning: Specified --test-mode-timeout=seconds value of ${test_mode_timeout_seconds} seconds is too high, rounding down to 120 seconds (2 minutes)." test_mode_timeout_seconds=120 fi fi { [[ -n $test_mode_timeout_seconds ]] && [[ ! $test_mode_timeout_seconds =~ ${REGEX_ANY_WHOLE_NUMBER} ]]; } && log_super "Parameter Error: The --test-mode-timeout=seconds value must only be a number." && parameter_error="TRUE" [[ -z $test_mode_timeout_seconds ]] && test_mode_timeout_seconds="${TEST_MODE_DEFAULT_TIMEOUT}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: test_mode_timeout_seconds is: ${test_mode_timeout_seconds}" if [[ "${test_mode}" == "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 # Validate ${test_storage_update_option} input and if valid set the ${storage_required_update_gb} parameter. If there is no ${storage_required_update_gb} then set it to ${STORAGE_REQUIRED_UPDATE_DEFAULT_GB}. storage_required_update_gb=$(get_pref_managed "TestStorageUpdate") if [[ "${storage_required_update_gb}" == "X" ]] || { [[ -z "${storage_required_update_gb}" ]] && [[ "${test_storage_update_option}" == "X" ]]; }; then delete_pref "TestStorageUpdate" unset storage_required_update_gb elif [[ -z "${storage_required_update_gb}" ]] && [[ -n "${test_storage_update_option}" ]]; then storage_required_update_gb="${test_storage_update_option}" set_pref "TestStorageUpdate" "${storage_required_update_gb}" fi [[ -z "${storage_required_update_gb}" ]] && storage_required_update_gb=$(get_pref "TestStorageUpdate") [[ -n "${storage_required_upgrade_gb}" ]] && log_super "Parameter Warning: Specifying the --test-storage-update option should only be used for testing purposes." { [[ -n "${storage_required_update_gb}" ]] && [[ ! "${storage_required_update_gb}" =~ ${REGEX_ANY_WHOLE_NUMBER} ]]; } && log_super "Parameter Error: The --test-storage-update=gigabytes value must only be a number." && parameter_error="TRUE" [[ -z "${storage_required_update_gb}" ]] && storage_required_update_gb="${STORAGE_REQUIRED_UPDATE_DEFAULT_GB}" [[ "${verbose_mode}" == "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 the ${storage_required_upgrade_gb} parameter. If there is no ${storage_required_upgrade_gb} then set it to ${STORAGE_REQUIRED_UPGRADE_DEFAULT_GB}. storage_required_upgrade_gb=$(get_pref_managed "TestStorageUpgrade") if [[ "${storage_required_upgrade_gb}" == "X" ]] || { [[ -z "${storage_required_upgrade_gb}" ]] && [[ "${test_storage_upgrade_option}" == "X" ]]; }; then delete_pref "TestStorageUpgrade" unset storage_required_upgrade_gb elif [[ -z "${storage_required_upgrade_gb}" ]] && [[ -n "${test_storage_upgrade_option}" ]]; then storage_required_upgrade_gb="${test_storage_upgrade_option}" set_pref "TestStorageUpgrade" "${storage_required_upgrade_gb}" fi [[ -z "${storage_required_upgrade_gb}" ]] && storage_required_upgrade_gb=$(get_pref "TestStorageUpgrade") [[ -n "${storage_required_upgrade_gb}" ]] && log_super "Parameter Warning: Specifying the --test-storage-upgrade option should only be used for testing purposes." { [[ -n "${storage_required_upgrade_gb}" ]] && [[ ! "${storage_required_upgrade_gb}" =~ ${REGEX_ANY_WHOLE_NUMBER} ]]; } && log_super "Parameter Error: The --test-storage-upgrade=gigabytes value must only be a number." && parameter_error="TRUE" [[ -z "${storage_required_upgrade_gb}" ]] && storage_required_upgrade_gb="${STORAGE_REQUIRED_UPGRADE_DEFAULT_GB}" [[ "${verbose_mode}" == "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 the ${power_required_battery_percent} parameter. If there is no ${power_required_battery_percent} then set it to ${STORAGE_REQUIRED_UPGRADE_DEFAULT_GB}. power_required_battery_percent=$(get_pref_managed "TestBatteryLevel") if [[ "${power_required_battery_percent}" == "X" ]] || { [[ -z "${power_required_battery_percent}" ]] && [[ "${test_battery_level_option}" == "X" ]]; }; then delete_pref "TestBatteryLevel" unset power_required_battery_percent elif [[ -z "${power_required_battery_percent}" ]] && [[ -n "${test_battery_level_option}" ]]; then power_required_battery_percent="${test_battery_level_option}" set_pref "TestBatteryLevel" "${power_required_battery_percent}" fi [[ -z "${power_required_battery_percent}" ]] && power_required_battery_percent=$(get_pref "TestBatteryLevel") [[ -n "${power_required_battery_percent}" ]] && log_super "Parameter Warning: Specifying the --test-battery-level option should only be used for testing purposes." { [[ -n "${power_required_battery_percent}" ]] && [[ ! "${power_required_battery_percent}" =~ ${REGEX_ANY_WHOLE_NUMBER} ]]; } && log_super "Parameter Error: The --test-battery-level=percentage value must only be a number." && parameter_error="TRUE" { [[ -z "${power_required_battery_percent}" ]] && [[ "${mac_cpu_architecture}" == "arm64" ]]; } && power_required_battery_percent="${POWER_REQUIRED_BATTERY_APPLE_SILICON_PERCENT}" { [[ -z "${power_required_battery_percent}" ]] && [[ "${mac_cpu_architecture}" != "arm64" ]]; } && power_required_battery_percent="${POWER_REQUIRED_BATTERY_INTEL_PERCENT}" [[ "${verbose_mode}" == "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_MAIN_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}" == "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=$(get_pref "AuthLocalAccount") auth_service_account_saved=$(get_pref "AuthServiceAccount") auth_jamf_client_saved=$(get_pref "AuthJamfClient") auth_jamf_account_saved=$(get_pref "AuthJamfAccount") local auth_legacy_local_account_saved auth_legacy_local_account_saved=$(get_pref "LocalAccount") local auth_legacy_super_account_saved auth_legacy_super_account_saved=$(get_pref "SuperAccount") local auth_legacy_jamf_account_saved auth_legacy_jamf_account_saved=$(get_pref "JamfAccount") [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_ask_user_to_save_password: ${auth_ask_user_to_save_password}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_user_account_saved: ${auth_user_account_saved}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_local_account_saved: ${auth_local_account_saved}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_service_account_saved: ${auth_service_account_saved}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_jamf_client_saved: ${auth_jamf_client_saved}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_jamf_account_saved: ${auth_jamf_account_saved}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_legacy_local_account_saved: ${auth_legacy_local_account_saved}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_legacy_super_account_saved: ${auth_legacy_super_account_saved}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_legacy_jamf_account_saved: ${auth_legacy_jamf_account_saved}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_local_account_option: ${auth_local_account_option}" [[ "${verbose_mode}" == "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}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_jamf_client_option: ${auth_jamf_client_option}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_jamf_account_option: ${auth_jamf_account_option}" [[ "${verbose_mode}" == "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_audit "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 delete_pref_main "AuthAskUserToSavePassword" 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_audit "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 delete_pref_main "AuthLocalAccount" auth_local_account_saved="FALSE" fi if [[ "${auth_service_account_saved}" == "TRUE" ]]; then log_super_audit "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 delete_pref_main "AuthServiceAccount" auth_service_account_saved="FALSE" fi if [[ "${auth_jamf_client_saved}" == "TRUE" ]]; then log_super_audit "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 delete_pref_main "AuthJamfClient" auth_jamf_client_saved="FALSE" fi if [[ "${auth_jamf_account_saved}" == "TRUE" ]]; then log_super_audit "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 delete_pref_main "AuthJamfAccount" auth_jamf_account_saved="FALSE" fi if [[ -n "${auth_legacy_local_account_saved}" ]]; then log_super_audit "Status: Deleting saved credentials for legacy local account." delete_pref_main "LocalAccount" 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_audit "Status: Deleting local account and saved credentials for legacy super service account." sysadminctl -deleteUser "${auth_legacy_super_account_saved}" >/dev/null 2>&1 delete_pref_main "SuperAccount" 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_audit "Status: Deleting saved credentials for legacy Jamf Pro API account." delete_pref_main "JamfAccount" 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_audit "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_audit "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_audit "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}" == "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}" == "TRUE" ]] && log_echo "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_local_account_option is: ${auth_local_account_option}" [[ "${verbose_mode}" == "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}" == "TRUE" ]] && log_echo "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_local_password_option is: ${auth_local_password_option}" [[ "${verbose_mode}" == "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_MAIN_PLIST}. if [[ "${auth_error_new}" != "TRUE" ]]; then [[ "${auth_legacy_local_migrate}" != "TRUE" ]] && log_super_audit "Status: Saved new credentials for the --auth-local-account option." [[ "${auth_legacy_local_migrate}" == "TRUE" ]] && log_super_audit "Status: Saved migrated credentials for the --auth-local-account option." set_pref_main "AuthLocalAccount" "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}" == "TRUE" ]] && log_echo "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_service_account is: ${auth_service_account}" [[ "${verbose_mode}" == "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}" == "TRUE" ]] && log_echo "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_service_password is: ${auth_service_password}" [[ "${verbose_mode}" == "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}" == "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_MAIN_PLIST}. if [[ "${auth_error_new}" != "TRUE" ]]; then [[ "${auth_legacy_service_migrate}" != "TRUE" ]] && log_super_audit "log_super_audit: Created new super service account." [[ "${auth_legacy_service_migrate}" == "TRUE" ]] && log_super_audit "log_super_audit: Validated migrated super service account." set_pref_main "AuthServiceAccount" "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}" == "TRUE" ]] && log_echo "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_jamf_client_option is: ${auth_jamf_client_option}" [[ "${verbose_mode}" == "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}" == "TRUE" ]] && log_echo "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_jamf_secret_option is: ${auth_jamf_secret_option}" [[ "${verbose_mode}" == "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_MAIN_PLIST}. if [[ "${auth_error_new}" != "TRUE" ]]; then log_super_audit "Status: Saved new credentials for the --auth-jamf-client option." set_pref_main "AuthJamfClient" "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}" == "TRUE" ]] && log_echo "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_jamf_account_option is: ${auth_jamf_account_option}" [[ "${verbose_mode}" == "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}" == "TRUE" ]] && log_echo "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_jamf_password_option is: ${auth_jamf_password_option}" [[ "${verbose_mode}" == "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_MAIN_PLIST}. if [[ "${auth_error_new}" != "TRUE" ]]; then [[ "${auth_legacy_jamf_migrate}" != "TRUE" ]] && log_super_audit "Status: Saved new credentials for the --auth-jamf-account option." [[ "${auth_legacy_jamf_migrate}" == "TRUE" ]] && log_super_audit "Status: Saved migrated credentials for the --auth-jamf-account option." set_pref_main "AuthJamfAccount" "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}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_user_account_saved: ${auth_user_account_saved}" [[ "${verbose_mode}" == "TRUE" ]] && log_echo "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_user_password_keychain: ${auth_user_password_keychain}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_local_account_saved: ${auth_local_account_saved}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_service_account_saved: ${auth_service_account_saved}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_jamf_client_saved: ${auth_jamf_client_saved}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_jamf_account_saved: ${auth_jamf_account_saved}" [[ "${verbose_mode}" == "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_audit "Status: Deleting saved credentials for legacy local account." delete_pref_main "LocalAccount" 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_audit "Status: Deleting saved credentials for legacy super service account." delete_pref_main "SuperAccount" 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_audit "Status: Deleting saved credentials for legacy Jamf Pro API account." delete_pref_main "JamfAccount" 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} is enabled then there is no reason to continue this function. [[ "${workflow_disable_update_check}" == "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_normalized -ge 110300 ]]; then [[ -n "${auth_mdm_failover_to_user}" ]] && log_super "Status: macOS update/upgrade workflows automatically authenticated via Jamf Pro API with --auth-mdm-failover-to-user=${auth_mdm_failover_to_user}." [[ -z "${auth_mdm_failover_to_user}" ]] && 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}" == "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_account_saved}" == "TRUE" ]] && { [[ "${auth_mdm_failover_to_user_status}" == "TRUE" ]] || [[ $(echo "${auth_mdm_failover_to_user}" | grep -c 'ERROR') -gt 0 ]] || { [[ "${workflow_install_now_active}" == "TRUE" ]] && [[ $(echo "${auth_mdm_failover_to_user}" | 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}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: workflow_auth_error is: ${workflow_auth_error}" [[ "${verbose_mode}" == "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}" == "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}" == "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}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_ask_user_to_save_password: ${auth_ask_user_to_save_password}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_user_saved_password_valid: ${auth_user_saved_password_valid}" [[ "${verbose_mode}" == "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}" == "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}" == "TRUE" ]] && log_echo "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_local_account_keychain: ${auth_local_account_keychain}" [[ "${verbose_mode}" == "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}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_local_account_saved: ${auth_local_account_saved}" [[ "${verbose_mode}" == "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}" == "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}" == "TRUE" ]] && log_echo "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_service_account_keychain: ${auth_service_account_keychain}" [[ "${verbose_mode}" == "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}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_service_account_saved: ${auth_service_account_saved}" [[ "${verbose_mode}" == "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}" == "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}" == "TRUE" ]] && log_echo "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_jamf_client_keychain: ${auth_jamf_client_keychain}" [[ "${verbose_mode}" == "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}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_jamf_client_saved: ${auth_jamf_client_saved}" [[ "${verbose_mode}" == "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}" == "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}" == "TRUE" ]] && log_echo "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_jamf_account_keychain: ${auth_jamf_account_keychain}" [[ "${verbose_mode}" == "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}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_jamf_account_saved: ${auth_jamf_account_saved}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_jamf_account_valid: ${auth_jamf_account_valid}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_error_saved: ${auth_error_saved}" [[ "${verbose_mode}" == "TRUE" ]] && log_echo "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_local_account: ${auth_local_account}" [[ "${verbose_mode}" == "TRUE" ]] && log_echo "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_local_password: ${auth_local_password}" [[ "${verbose_mode}" == "TRUE" ]] && log_echo "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_jamf_client: ${auth_jamf_client}" [[ "${verbose_mode}" == "TRUE" ]] && log_echo "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_jamf_secret: ${auth_jamf_secret}" [[ "${verbose_mode}" == "TRUE" ]] && log_echo "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_jamf_account: ${auth_jamf_account}" [[ "${verbose_mode}" == "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_CONFIGS_FOLDER}" ]] && mkdir -p "${SUPER_CONFIGS_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_audit "Installation: Copying super ${SUPER_VERSION} 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 necessary. # 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_MAIN_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_MAIN_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_MAIN_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/${SUPER_LAUNCH_DAEMON_LABEL}" >/dev/null 2>&1 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 EOLD log_super "Installation: Setting permissions for installed super items." chown -R root:wheel "${SUPER_FOLDER}" >/dev/null 2>&1 chmod -R a+r "${SUPER_FOLDER}" >/dev/null 2>&1 chmod -R go-w "${SUPER_FOLDER}" >/dev/null 2>&1 chmod a+x "${SUPER_FOLDER}/super" >/dev/null 2>&1 chmod a+x "${SUPER_FOLDER}/super-starter" >/dev/null 2>&1 chown root:wheel "${SUPER_LINK}" >/dev/null 2>&1 chmod a+rx "${SUPER_LINK}" >/dev/null 2>&1 chmod go-w "${SUPER_LINK}" >/dev/null 2>&1 chmod 644 "/Library/LaunchDaemons/${SUPER_LAUNCH_DAEMON_LABEL}.plist" >/dev/null 2>&1 chown root:wheel "/Library/LaunchDaemons/${SUPER_LAUNCH_DAEMON_LABEL}.plist" >/dev/null 2>&1 set_pref_main "SuperVersion" "${SUPER_VERSION}" >/dev/null 2>&1 } # 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 --user-agent "${SUPER_USER_AGENT}" --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}" == "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}" == "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}" == "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 download_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..." download_response=$(curl --user-agent "${SUPER_USER_AGENT}" --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}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: download_response is:\n${download_response}" fi else # Local file copy. log_super "Status: Attempting to copy new requested display icon from local path..." download_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}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: download_response is:\n${download_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}" == "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}" == "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 --user-agent "${SUPER_USER_AGENT}" --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}" == "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}" == "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}" == "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" # If needed, validate mist-cli and if missing or invalid then install and check again. if [[ "${install_macos_major_upgrades}" == "TRUE" ]] || [[ -n "${install_macos_major_version_target}" ]] || [[ -n "${install_macos_minor_version_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 # If user is only making alternate configuration changes then this function should exit. { [[ "${config_edit_main_option}" == "TRUE" ]] || [[ -n "${config_edit_option}" ]] || [[ -n "${config_delete_option}" ]] || [[ "${config_delete_all_option}" == "TRUE" ]]; } && return 0 # Set the ${display_icon_light} using either the ${DISPLAY_ICON_DEFAULT_FILE} or the ${display_icon_light_file_path} option. local display_icon_light_file_cached_origin display_icon_light_file_cached_origin=$(get_pref "DisplayIconLightFileCachedOrigin") [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: display_icon_light_file_cached_origin is: ${display_icon_light_file_cached_origin}" if [[ "${display_icon_light_file_path}" == "${DISPLAY_ICON_DEFAULT_FILE}" ]]; then # Using the ${DISPLAY_ICON_DEFAULT_FILE}. display_icon_light="${DISPLAY_ICON_DEFAULT_FILE}" elif [[ "${display_icon_light_file_path}" == "${display_icon_light_file_cached_origin}" ]] && [[ -f "${DISPLAY_ICON_LIGHT_FILE_CACHE}" ]]; then # Cached copy of the ${display_icon_light_file_path} is intact. display_icon_light="${DISPLAY_ICON_LIGHT_FILE_CACHE}" else # Need to acquire a new copy of the ${display_icon_light_file_path}. [[ -f "${DISPLAY_ICON_LIGHT_FILE_CACHE}" ]] && rm -f "${DISPLAY_ICON_LIGHT_FILE_CACHE}" 2>/dev/null delete_pref_main "DisplayIconLightFileCachedOrigin" 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 set_pref_main "DisplayIconLightFileCachedOrigin" "${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 fi [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: display_icon_light is: ${display_icon_light}" # Set the ${display_icon_dark} using either the ${DISPLAY_ICON_DEFAULT_FILE} or the ${display_icon_dark_file_path} option. local display_icon_dark_file_cached_origin display_icon_dark_file_cached_origin=$(get_pref "DisplayIconDarkFileCachedOrigin") [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: display_icon_dark_file_cached_origin is: ${display_icon_dark_file_cached_origin}" if [[ "${display_icon_dark_file_path}" == "${DISPLAY_ICON_DEFAULT_FILE}" ]]; then # Using the ${DISPLAY_ICON_DEFAULT_FILE}. display_icon_dark="${DISPLAY_ICON_DEFAULT_FILE}" elif [[ "${display_icon_dark_file_path}" == "${display_icon_dark_file_cached_origin}" ]] && [[ -f "${DISPLAY_ICON_DARK_FILE_CACHE}" ]]; then # Cached copy of the ${display_icon_dark_file_path} is intact. display_icon_dark="${DISPLAY_ICON_DARK_FILE_CACHE}" else # Need to acquire a new copy of the ${display_icon_dark_file_path}. [[ -f "${DISPLAY_ICON_DARK_FILE_CACHE}" ]] && rm -f "${DISPLAY_ICON_DARK_FILE_CACHE}" 2>/dev/null delete_pref_main "DisplayIconDarkFileCachedOrigin" 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 set_pref_main "DisplayIconDarkFileCachedOrigin" "${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 fi [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: display_icon_dark is: ${display_icon_dark}" } # 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 # Start the ${config_status_option} function. This function will exit the workflow when complete. [[ "${config_status_option}" == "TRUE" ]] && show_config_status # 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 using an alternate configuration mode then save a ${super_status_backup} otherwise delete the NextAutoLaunch preference in case the workflow crashes or the system restarts unexpectedly before super exits. A "missing" NextAutoLaunch preference will cause super to automatically restart via the LaunchDaemon. if [[ "${config_edit_main_option}" == "TRUE" ]] || [[ -n "${config_edit_option}" ]] || [[ -n "${config_delete_option}" ]] || [[ "${config_delete_all_option}" == "TRUE" ]]; then local super_status_backup super_status_backup=$(get_pref "SuperStatus") else /usr/libexec/PlistBuddy -c "Delete :NextAutoLaunch" "${SUPER_MAIN_PLIST}.plist" 2> /dev/null fi # 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} parameter and if enabled start additional logging. [[ "${reset_super_option}" == "TRUE" ]] && delete_pref_main "VerboseMode" verbose_mode=$(get_pref_managed "VerboseMode") { [[ -z "${verbose_mode}" ]] && [[ -n "${verbose_mode_option}" ]]; } && verbose_mode="${verbose_mode_option}" && set_pref_main "VerboseMode" "${verbose_mode_option}" [[ -z "${verbose_mode}" ]] && verbose_mode=$(get_pref "VerboseMode") if [[ "${verbose_mode}" == "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 # Initial system and user checks. check_system check_current_user # Workflow 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_AUDIT_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_METRICS_LOG}" sudo -u "${current_user_account_name}" open "${SUPER_AUDIT_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 # Configuration management and helper validations, if any of these fail then it's unsafe for the workflow to continue. if [[ "${check_system_error}" == "FALSE" ]]; then check_jamf_management_framework manage_preference_resets manage_parameter_options fi { [[ "${check_system_error}" == "FALSE" ]] && [[ "${parameter_error}" == "FALSE" ]]; } && manage_helpers if [[ "${check_system_error}" == "TRUE" ]] || [[ "${parameter_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}" == "TRUE" ]] && log_super "Status: Test mode active with ${test_mode_timeout_seconds} second timeout." if [[ $(get_pref "WorkflowRestartValidate") == "TRUE" ]]; then log_super "Status: Restart validation workflow active." workflow_restart_validate_active="TRUE" elif [[ "${workflow_install_now}" == "TRUE" ]]; then log_super "Status: Install now workflow active." workflow_install_now_active="TRUE" [[ $(echo "${display_unmovable}" | grep -c 'INSTALLNOW') -gt 0 ]] && display_unmovable_status="TRUE" [[ $(echo "${display_hide_background}" | grep -c 'INSTALLNOW') -gt 0 ]] && display_hide_background_status="TRUE" [[ $(echo "${display_silently}" | grep -c 'INSTALLNOW') -gt 0 ]] && display_silently_status="TRUE" [[ $(echo "${display_hide_progress_bar}" | grep -c 'INSTALLNOW') -gt 0 ]] && display_hide_progress_bar_status="TRUE" [[ $(echo "${display_notifications_centered}" | grep -c 'INSTALLNOW') -gt 0 ]] && display_notifications_centered_status="TRUE" if [[ "${current_user_account_name}" != "FALSE" ]]; then notification_install_now_start if [[ "${test_mode}" == "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 [[ $(get_pref "WorkflowScheduledInstall") == "TRUE" ]]; then log_super "Warning: Removing previously scheduled installation workflow due to current install now workflow." delete_pref_main "WorkflowScheduledInstall" fi elif [[ $(get_pref "WorkflowScheduledInstall") =~ ${REGEX_DATE_HOURS_MINUTES} ]]; then workflow_scheduled_install=$(get_pref "WorkflowScheduledInstall") log_super "Status: Previously scheduled installation workflow active for: ${workflow_scheduled_install}" workflow_scheduled_install_active="TRUE" elif [[ "${workflow_only_download}" == "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}" ]]; } && log_super "Status: Jamf Pro Policy triggers: ${install_jamf_policy_triggers}" [[ "${install_jamf_policy_triggers_without_restarting}" == "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}" == "TRUE" ]] && log_super "Warning: Restart without updates workflow is enabled, this computer will restart even if there is no macOS update or upgrade available." workflow_reset_super_after_completion_active="FALSE" if [[ "${workflow_reset_super_after_completion_option}" == "FALSE" ]]; then delete_pref "WorkflowResetSuperAfterCompletion" delete_pref_main "WorkflowResetSuperAfterCompletionNow" elif [[ "${workflow_reset_super_after_completion_option}" == "TRUE" ]] || [[ $(get_pref "WorkflowResetSuperAfterCompletion") == "TRUE" ]]; 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" set_pref "WorkflowResetSuperAfterCompletion" "TRUE" fi [[ "${workflow_restart_validate_active}" != "TRUE" ]] && set_pref_main "MacLastStartup" "${mac_last_startup}" # Apple silicon authentication and workflow validations. [[ "${mac_cpu_architecture}" == "arm64" ]] && manage_authentication_options manage_workflow_options [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: Main preference file after startup validation: ${SUPER_MAIN_PLIST}:\n$(defaults read "${SUPER_MAIN_PLIST}" 2>/dev/null)" { [[ "${verbose_mode}" == "TRUE" ]] && [[ -n "${super_alternate_plist}" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: Alternate preference file after startup validation: ${super_alternate_plist}:\n$(defaults read "${super_alternate_plist}" 2>/dev/null)" # If the macOS Setup Assistant is incomplete, or the ${schedule_deferred_start_file} is not found, or the admin is only making alternate configuration changes then the workflow should automatically defer or exit. if [[ ! -f "${APPLE_SETUP_DONE_FILE}" ]] || { [[ -n "${schedule_deferred_start_file}" ]] && [[ ! -f "${schedule_deferred_start_file}" ]]; } || [[ "${config_edit_main_option}" == "TRUE" ]] || [[ -n "${config_edit_option}" ]] || [[ -n "${config_delete_option}" ]] || [[ "${config_delete_all_option}" == "TRUE" ]]; then { [[ "${config_delete_reset_workflow}" == "TRUE" ]] && [[ "${verbose_mode}" == "TRUE" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: Calling reset_workflow() function." [[ "${config_delete_reset_workflow}" == "TRUE" ]] && reset_workflow local next_auto_launch if [[ "${workflow_disable_relaunch}" == "TRUE" ]]; then /usr/libexec/PlistBuddy -c "Delete :NextAutoLaunch" "${SUPER_MAIN_PLIST}.plist" 2> /dev/null /usr/libexec/PlistBuddy -c "Add :NextAutoLaunch string FALSE" "${SUPER_MAIN_PLIST}.plist" 2> /dev/null next_auto_launch="FALSE" else next_auto_launch=$(/usr/libexec/PlistBuddy -c "Print :NextAutoLaunch" "${SUPER_MAIN_PLIST}.plist" 2> /dev/null) fi [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: next_auto_launch: ${next_auto_launch}" if [[ $(launchctl print "system/${SUPER_LAUNCH_DAEMON_LABEL}" 2>&1 | grep -c "system/${SUPER_LAUNCH_DAEMON_LABEL}") -gt 0 ]] && [[ "${next_auto_launch}" != "FALSE" ]]; then if [[ ! -f "${APPLE_SETUP_DONE_FILE}" ]] || { [[ -n "${schedule_deferred_start_file}" ]] && [[ ! -f "${schedule_deferred_start_file}" ]]; } || [[ "${config_delete_reset_workflow}" == "TRUE" ]]; then if [[ ! -f "${APPLE_SETUP_DONE_FILE}" ]]; then log_super "Exit: macOS Setup Assistant is not done. The super workflow is scheduled to automatically relaunch in ${deferral_timer_minutes} minutes." log_status "Pending: macOS Setup Assistant is not done. The super workflow is scheduled to automatically relaunch in ${deferral_timer_minutes} minutes." elif [[ -n "${schedule_deferred_start_file}" ]] && [[ ! -f "${schedule_deferred_start_file}" ]]; then log_super "Status: The following schedule deferred start file was not found on the system ${schedule_deferred_start_file}." log_super "Exit: Schedule deferred start file not found. The super workflow is scheduled to automatically relaunch in ${deferral_timer_minutes} minutes." log_status "Pending: Schedule deferred start file not found. The super workflow is scheduled to automatically relaunch in ${deferral_timer_minutes} minutes." else log_super "Exit: Configuration mode complete. The super workflow is scheduled to automatically relaunch in ${deferral_timer_minutes} minutes." log_status "Pending: Configuration mode complete. The super workflow is scheduled to automatically relaunch in ${deferral_timer_minutes} minutes." fi set_auto_launch_deferral else if [[ -n "${next_auto_launch}" ]]; then log_super "Exit: Configuration mode complete. The super workflow is scheduled to automatically relaunch at ${next_auto_launch}." [[ -n "${super_status_backup}" ]] && set_pref_main "SuperStatus" "${super_status_backup}" [[ -z "${super_status_backup}" ]] && log_status "Pending: Configuration mode complete. The super workflow is scheduled to automatically relaunch at ${next_auto_launch}." else log_super "Exit: Configuration mode complete. The super workflow is scheduled to automatically relaunch in ${deferral_timer_minutes} minutes." log_status "Pending: Configuration mode complete. The super workflow is scheduled to automatically relaunch in ${deferral_timer_minutes} minutes." set_auto_launch_deferral fi fi else if [[ ! -f "${APPLE_SETUP_DONE_FILE}" ]]; then log_super "Exit: macOS Setup Assistant is not done. The super workflow is not scheduled to automatically relaunch. You must run super again to start a workflow." log_status "Inactive: macOS Setup Assistant is not done. The super workflow is not scheduled to automatically relaunch. You must run super again to start a workflow." elif [[ -n "${schedule_deferred_start_file}" ]] && [[ ! -f "${schedule_deferred_start_file}" ]]; then log_super "Status: The following schedule deferred start file was not found on the system ${schedule_deferred_start_file}." log_super "Exit: Schedule deferred start file not found. The super workflow is not scheduled to automatically relaunch. You must run super again to start a workflow." log_status "Inactive: Schedule deferred start file not found. The super workflow is not scheduled to automatically relaunch. You must run super again to start a workflow." else log_super "Exit: Configuration mode complete. The super workflow is not scheduled to automatically relaunch. You must run super again to start a workflow." log_status "Inactive: Configuration mode complete. The super workflow is not scheduled to automatically relaunch. You must run super again to start a workflow." fi fi exit_clean fi # 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() { log_super "**** S.U.P.E.R.M.A.N. ${SUPER_VERSION} - INTERACTIVE INTERRUPT - PRESS ENTER TO CONTINUE OR CTRL-C TO EXIT ****" 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_MAIN_PLIST}.plist" 2> /dev/null { sleep $restart_super_sleep_seconds launchctl bootout "system/${SUPER_LAUNCH_DAEMON_LABEL}" >/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}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: Main preference file at restart exit: ${SUPER_MAIN_PLIST}:\n$(defaults read "${SUPER_MAIN_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_MAIN_PLIST}. set_auto_launch_deferral() { [[ "${verbose_mode}" == "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 next_auto_launch next_auto_launch="$(date -j -f %s "${deferral_timer_epoch}" +%Y-%m-%d:%H:%M:00 | xargs)" /usr/libexec/PlistBuddy -c "Add :NextAutoLaunch string ${next_auto_launch}" "${SUPER_MAIN_PLIST}.plist" 2> /dev/null launchctl bootstrap system "/Library/LaunchDaemons/${SUPER_LAUNCH_DAEMON_LABEL}.plist" >/dev/null 2>&1 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}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: Main preference file at clean exit: ${SUPER_MAIN_PLIST}:\n$(defaults read "${SUPER_MAIN_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}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: Main preference file at error exit: ${SUPER_MAIN_PLIST}:\n$(defaults read "${SUPER_MAIN_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}" } # Append input to the command line and logs located at ${SUPER_LOG} and ${SUPER_AUDIT_LOG}. log_super_audit() { echo -e "$(date +"%a %b %d %T") $(hostname -s) $(basename "$0")[$$]: $*" | tee -a "${SUPER_LOG}" "${SUPER_AUDIT_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 the log located at ${SUPER_METRICS_LOG} including an elapsed time based on the log text ${1} and timestamp ${2}. The timestamp can be either an integer of seconds, or a variable of a timestamp, or a property list key containing a timestamp. log_metrics() { local log_metrics_elapsed_seconds local log_metrics_timestamp if [[ "${2}" =~ ^[0-9]+$ ]]; then log_metrics_elapsed_seconds="${2}" else # ${2} is not an integer. [[ ! "${2}" =~ ^[[:upper:]] ]] && log_metrics_timestamp="${2}" [[ "${2}" =~ ^[[:upper:]] ]] && log_metrics_timestamp=$(get_pref "${2}") [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: log_metrics_timestamp is: ${log_metrics_timestamp}" [[ -n "${log_metrics_timestamp}" ]] && log_metrics_elapsed_seconds=$(( $(date +%s) - $(date -j -f %Y-%m-%d:%H:%M:%S "${log_metrics_timestamp}" +%s) )) fi [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: log_metrics_elapsed_seconds is: ${log_metrics_elapsed_seconds}" if [[ -n "${log_metrics_elapsed_seconds}" ]]; then { [[ -n "${workflow_target}" ]] && [[ "${workflow_target}" != "FALSE" ]]; } && echo -e "$(date +"%a %b %d %T") $(hostname -s) $(basename "$0")[$$]: ${workflow_target}: ${1} (DHMS): $(printf "%02d" $(( log_metrics_elapsed_seconds / 86400))):$(printf "%02d" $(( log_metrics_elapsed_seconds / 3600 % 24 ))):$(printf "%02d" $(( log_metrics_elapsed_seconds / 60 % 60 ))):$(printf "%02d" $(( log_metrics_elapsed_seconds %60 )))" >> "${SUPER_METRICS_LOG}" { [[ -z "${workflow_target}" ]] || [[ "${workflow_target}" == "FALSE" ]]; } && echo -e "$(date +"%a %b %d %T") $(hostname -s) $(basename "$0")[$$]: No Target: ${1} (DHMS): $(printf "%02d" $(( log_metrics_elapsed_seconds / 86400))):$(printf "%02d" $(( log_metrics_elapsed_seconds / 3600 % 24 ))):$(printf "%02d" $(( log_metrics_elapsed_seconds / 60 % 60 ))):$(printf "%02d" $(( log_metrics_elapsed_seconds %60 )))" >> "${SUPER_METRICS_LOG}" else { [[ -n "${workflow_target}" ]] && [[ "${workflow_target}" != "FALSE" ]]; } && echo -e "$(date +"%a %b %d %T") $(hostname -s) $(basename "$0")[$$]: ${workflow_target}: ${1}" >> "${SUPER_METRICS_LOG}" { [[ -z "${workflow_target}" ]] || [[ "${workflow_target}" == "FALSE" ]]; } && echo -e "$(date +"%a %b %d %T") $(hostname -s) $(basename "$0")[$$]: No Target: ${1}" >> "${SUPER_METRICS_LOG}" fi } # Update the SuperStatus key in the ${SUPER_MAIN_PLIST}. log_status() { set_pref_main "SuperStatus" "$(date +"%a %b %d %T"): $*" } # 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}" } # 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 "${SUPER_AUDIT_LOG}" 2>/dev/null | awk '{print int($5/1000)}') -gt $SUPER_LOG_ARCHIVE_SIZE ]] && archive_logs_needed="TRUE" [[ $(ls -l "${SUPER_METRICS_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 "${SUPER_AUDIT_LOG}" ]] && mv "${SUPER_AUDIT_LOG}" "${SUPER_LOG_ARCHIVE_FOLDER}/${log_archive_name}/$(basename ${SUPER_AUDIT_LOG})" [[ -f "${SUPER_METRICS_LOG}" ]] && mv "${SUPER_METRICS_LOG}" "${SUPER_LOG_ARCHIVE_FOLDER}/${log_archive_name}/$(basename ${SUPER_METRICS_LOG})" [[ -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}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: current_user_account_name is: ${current_user_account_name}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: current_user_id is: ${current_user_id}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: current_user_account_name_response is: ${current_user_account_name_response}" [[ "${verbose_mode}" == "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}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: current_user_account_name is: ${current_user_account_name}" [[ "${verbose_mode}" == "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}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: current_user_guid is: ${current_user_guid}" [[ "${verbose_mode}" == "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}" == "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}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: current_user_is_admin is: ${current_user_is_admin}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: current_user_has_secure_token is: ${current_user_has_secure_token}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: current_user_is_volume_owner is: ${current_user_is_volume_owner}" [[ "${verbose_mode}" == "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}" check_system_error="TRUE" fi fi } # Collect parameters for detailed system information setting a variety of parameters including ${macos_version_minor}, ${macos_version_patch}, ${macos_version_normalized}, ${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_system_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_normalized=$(sw_vers -productVersion | awk -F'.' '{printf "%02d%02d%02d", $1, $2, $3}') # Expected output: 100502, 140701, 260100 [[ $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)}' | sed -e 's/[0-9]//g' | xargs)" # 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. [[ "${config_status_option}" != "TRUE" ]] && log_super "Status: Mac computer with Apple silicon running: ${macos_version_full}" [[ "${config_status_option}" == "TRUE" ]] && echo "Config Status: Mac computer with Apple silicon running: ${macos_version_full}" else # Mac computers with Intel. [[ "${config_status_option}" != "TRUE" ]] && log_super "Status: Mac computer with Intel running: ${macos_version_full}" [[ "${config_status_option}" == "TRUE" ]] && echo "Config Status: Mac computer with Intel running: ${macos_version_full}" fi [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_version_normalized is: ${macos_version_normalized}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: mac_model_name is: ${mac_model_name}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: mac_is_portable is: ${mac_is_portable}" 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:13:25:45 else log_super "Error: Unrecognized kernel boot epoch time: ${kernel_boot_epoch}" check_system_error="TRUE" fi if [[ "${mac_last_startup}" =~ ${REGEX_DATE_HOURS_MINUTES_SECONDS} ]]; then [[ "${config_status_option}" != "TRUE" ]] && log_super "Status: Last macOS startup was: ${mac_last_startup}" [[ "${config_status_option}" == "TRUE" ]] && echo "Config Status: Last macOS startup was: ${mac_last_startup}" else log_super "Error: Unrecognized last startup date and time: ${mac_last_startup}" check_system_error="TRUE" fi [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: check_system_error is: ${check_system_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}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: msu_automatic_check_managed is: ${msu_automatic_check_managed}" [[ "${verbose_mode}" == "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}" == "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}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: msu_automatic_download_managed is: ${msu_automatic_download_managed}" [[ "${verbose_mode}" == "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}" == "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}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: msu_automatic_macos_installation_managed is: ${msu_automatic_macos_installation_managed}" [[ "${verbose_mode}" == "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}" == "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}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: msu_automatic_config_data_managed is: ${msu_automatic_config_data_managed}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: msu_automatic_config_data_local is: ${msu_automatic_config_data_local}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: msu_automatic_critical_update_managed is: ${msu_automatic_critical_update_managed}" [[ "${verbose_mode}" == "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}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: msu_automatic_security_updates is: ${msu_automatic_security_updates}" # Log any warnings. [[ "${msu_automatic_security_updates}" == "FALSE" ]] && log_super "Warning: Automatic installation of macOS security updates is disabled, this is a significant security risk." [[ "${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." 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}" == "TRUE" ]] && log_echo "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_local_account: ${auth_local_account}" [[ "${verbose_mode}" == "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') if [[ -z "${storage_available_gigabytes}" ]] || [[ "${storage_available_gigabytes}" == "0" ]] || [[ ! "${storage_available_gigabytes}" =~ ${REGEX_ANY_WHOLE_NUMBER} ]]; then log_super "Warning: Initial attempt to determine available storage returned an unexpected value of ${storage_available_gigabytes}, attempting fallback methods..." [[ "${current_user_account_name}" != "FALSE" ]] && storage_available_gigabytes=$(osascript -l 'JavaScript' -e "ObjC.import('Foundation'); var freeSpaceBytesRef=Ref(); $.NSURL.fileURLWithPath('/').getResourceValueForKeyError(freeSpaceBytesRef, 'NSURLVolumeAvailableCapacityKey', null); Math.round(ObjC.unwrap(freeSpaceBytesRef[0]) / 1000000000)") if [[ -z "${storage_available_gigabytes}" ]] || [[ "${storage_available_gigabytes}" == "0" ]] || [[ ! "${storage_available_gigabytes}" =~ ${REGEX_ANY_WHOLE_NUMBER} ]]; then log_super "Warning: Further attempts to determine available storage returned an unexpected value of ${storage_available_gigabytes}, trying with another method..." storage_available_gigabytes=$(df -g / | tail -1 | awk '{print $4}') fi fi [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: storage_available_gigabytes: ${storage_available_gigabytes}" if [[ -z "${storage_available_gigabytes}" ]] || [[ "${storage_available_gigabytes}" == "0" ]] || [[ ! "${storage_available_gigabytes}" =~ ${REGEX_ANY_WHOLE_NUMBER} ]]; then log_super "Error: Unable to determine available storage." check_storage_available_error="TRUE" elif [[ "${macos_installer_major_upgrade_target}" != "FALSE" ]] || [[ "${macos_installer_minor_update_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}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_installer_download_required: ${macos_installer_download_required}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_installer_size: ${macos_installer_size}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_msu_download_required: ${macos_msu_download_required}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_msu_size: ${macos_msu_size}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: storage_required_upgrade_gb: ${storage_required_upgrade_gb}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: storage_required_update_gb: ${storage_required_update_gb}" if [[ "${macos_installer_major_upgrade_target}" != "FALSE" ]] || [[ "${macos_installer_minor_update_target}" != "FALSE" ]]; then # Target is a macOS 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}" == "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}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: check_storage_available_error: ${check_storage_available_error}" [[ "${verbose_mode}" == "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}" == "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}" == "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}" == "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}" == "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}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: check_power_required_error: ${check_power_required_error}" [[ "${verbose_mode}" == "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}" == "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 --user-agent "${SUPER_USER_AGENT}" --silent --head --location "${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}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: mdm_enrolled: ${mdm_enrolled}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: mdm_automatically_enrolled: ${mdm_automatically_enrolled}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: mdm_service_url: ${mdm_service_url}" [[ "${verbose_mode}" == "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}" == "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_normalized -ge 130300 ]]; 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}" == "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_MAIN_PLIST} to reset the counters. reset_deadline_counters() { log_super "Status: Resetting any deadline counters." delete_pref_main "DeadlineCounterFocus" delete_pref_main "DeadlineCounterSoft" delete_pref_main "DeadlineCounterHard" } # Delete any automatic zero dates and associated cached dates in ${SUPER_MAIN_PLIST}. reset_schedule_zero_date() { log_super "Status: Resetting any workflow dates." delete_pref_main "ScheduleZeroDateAutomaticRelease" delete_pref_main "ScheduleZeroDateAutomaticStart" delete_pref_main "DeadlineDaysFocusDate" delete_pref_main "DeadlineDaysSoftDate" delete_pref_main "DeadlineDaysHardDate" } # Delete all workflow items, counters, and zero dates in ${SUPER_MAIN_PLIST} to reset the workflow target. Also update ${SUPER_METRICS_LOG}. reset_workflow() { log_super "Status: Resetting workflow target." [[ -n "${workflow_target}" ]] && log_metrics "Workflow Complete: Total Elapsed Time" "WorkflowTargetTimestamp" set_pref_main "WorkflowTarget" "FALSE" unset workflow_target delete_pref_main "WorkflowTargetTimestamp" delete_pref_main "WorkflowScheduledInstall" delete_pref_main "WorkflowDownloadMacOSAuthRequired" reset_schedule_zero_date reset_deadline_counters } # Evaluate ${schedule_workflow_active} 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}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: schedule_workflow_active is: ${schedule_workflow_active}" [[ "${verbose_mode}" == "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}" 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}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: schedule_workflow_active_deferral is: ${schedule_workflow_active_deferral}" [[ "${verbose_mode}" == "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}" == "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}" == "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}" == "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}" == "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}. # 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}" 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}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: workflow_time_epoch: ${workflow_time_epoch}" [[ "${verbose_mode}" == "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} 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}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: deferral_timer_epoch_temp: ${deferral_timer_epoch_temp}" [[ "${verbose_mode}" == "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}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: deferral_timer_match: ${deferral_timer_match}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: schedule_workflow_active_matches_now: ${schedule_workflow_active_matches_now}" [[ "${verbose_mode}" == "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}" == "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}" == "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}" == "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} 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}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: deadline_epoch_temp: ${deadline_epoch_temp}" [[ "${verbose_mode}" == "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}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: schedule_workflow_active_matches_now: ${schedule_workflow_active_matches_now}" [[ "${verbose_mode}" == "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}" == "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}" == "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}" == "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} 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}" == "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}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: schedule_workflow_active_matches_now: ${schedule_workflow_active_matches_now}" [[ "${verbose_mode}" == "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}" == "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}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: workflow_scheduled_install_days_away: ${workflow_scheduled_install_days_away}" [[ "${verbose_mode}" == "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}" == "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}" == "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}" == "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} then compare it to the ${schedule_workflow_active} for possible adjustment. if [[ "${scheduled_install_user_choice}" == "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}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: workflow_scheduled_install: ${workflow_scheduled_install}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: workflow_scheduled_install_epoch_temp: ${workflow_scheduled_install_epoch_temp}" [[ "${verbose_mode}" == "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}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: scheduled_install_user_choice_match: ${scheduled_install_user_choice_match}" [[ "${verbose_mode}" == "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}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: workflow_scheduled_install_days_away: ${workflow_scheduled_install_days_away}" [[ "${verbose_mode}" == "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}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: scheduled_install_user_choice_match: ${scheduled_install_user_choice_match}" [[ "${verbose_mode}" == "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}" == "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}" == "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}" == "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}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: scheduled_install_user_choice_adjusted: ${scheduled_install_user_choice_adjusted}" [[ "${verbose_mode}" == "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=$(get_pref "SOFAMacOSJsonCacheChecksum") [[ "${verbose_mode}" == "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." delete_pref_main "SOFAMacOSJsonCacheChecksum" rm -f "${SOFA_MACOS_JSON_ETAG_CACHE}" 2>/dev/null rm -f "${SOFA_MACOS_JSON_CACHE}" 2>/dev/null fi fi [[ "${verbose_mode}" == "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 --user-agent "${SUPER_USER_AGENT}" --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 --user-agent "${SUPER_USER_AGENT}" --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 set_pref_main "SOFAMacOSJsonCacheChecksum" "$(md5 -q "${SOFA_MACOS_JSON_CACHE}" 2>/dev/null)" else delete_pref_main "SOFAMacOSJsonCacheChecksum" rm -f "${SOFA_MACOS_JSON_ETAG_CACHE}" 2>/dev/null fi } # Evaluate ${schedule_zero_date_manual} and ${schedule_zero_date_release} to set ${schedule_zero_date}, ${schedule_zero_date_epoch}, and ${display_string_schedule_zero_date} accordingly. check_schedule_zero_date() { schedule_zero_date="FALSE" schedule_deferred_start_days_active="FALSE" local schedule_zero_date_automatic_release_previous schedule_zero_date_automatic_release_previous=$(get_pref "ScheduleZeroDateAutomaticRelease") [[ "${verbose_mode}" == "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=$(get_pref "ScheduleZeroDateAutomaticReleaseFailover") [[ "${verbose_mode}" == "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=$(get_pref "ScheduleZeroDateAutomaticStart") [[ "${verbose_mode}" == "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}" ]] && delete_pref_main "ScheduleZeroDateAutomaticStart" && unset schedule_zero_date_automatic_start_previous [[ -n "${schedule_zero_date_automatic_release_failover_previous}" ]] && delete_pref_main "ScheduleZeroDateAutomaticReleaseFailover" && unset schedule_zero_date_automatic_release_failover_previous [[ -n "${schedule_zero_date_automatic_release_previous}" ]] && delete_pref_main "ScheduleZeroDateAutomaticRelease" && 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}" == "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="FALSE" fi # If the current ${workflow_target} matches the previously set workflow target then collect any previously saved zero dates. [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: schedule_zero_date_release: ${schedule_zero_date_release}" if [[ "${workflow_target}" == "${workflow_target_previous}" ]]; then if [[ "${schedule_zero_date_release}" == "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}" ]] && delete_pref_main "ScheduleZeroDateAutomaticReleaseFailover" && unset schedule_zero_date_automatic_release_failover_previous [[ -n "${schedule_zero_date_automatic_start_previous}" ]] && delete_pref_main "ScheduleZeroDateAutomaticStart" && 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}" ]] && delete_pref_main "ScheduleZeroDateAutomaticRelease" && unset schedule_zero_date_automatic_release_previous fi fi fi # If no ${schedule_zero_date} yet and the ${schedule_zero_date_release} is enabled then download ${SOFA_MACOS_JSON_CACHE} if it hasn't already been cached. if [[ "${schedule_zero_date}" == "FALSE" ]] && [[ "${schedule_zero_date_release}" == "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_major_upgrade_target}" != "FALSE" ]] || [[ "${macos_installer_minor_update_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}" == "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} 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}" == "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}" == "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_audit "Status: Setting new automatic zero date based on the ${workflow_target} release date of ${schedule_zero_date}." set_pref_main "ScheduleZeroDateAutomaticRelease" "${schedule_zero_date}" [[ -n "${schedule_zero_date_automatic_start_previous}" ]] && delete_pref_main "ScheduleZeroDateAutomaticStart" && 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}" == "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 -r "${workflow_time_epoch}" +%Y-%m-%d:%H:%M) log_super_audit "Status: Setting new automatic zero date based on the ${workflow_target} workflow start date of ${schedule_zero_date}." set_pref_main "ScheduleZeroDateAutomaticStart" "${schedule_zero_date}" delete_pref_main "ScheduleZeroDateAutomaticRelease" [[ -n "${schedule_zero_date_automatic_release_previous}" ]] && delete_pref_main "ScheduleZeroDateAutomaticRelease" && unset schedule_zero_date_automatic_release_previous if [[ "${schedule_zero_date_release_failover}" == "TRUE" ]]; then set_pref_main "ScheduleZeroDateAutomaticReleaseFailover" "TRUE" else [[ -n "${schedule_zero_date_automatic_release_failover_previous}" ]] && delete_pref_main "ScheduleZeroDateAutomaticReleaseFailover" && unset schedule_zero_date_automatic_release_failover_previous fi fi # Set the ${schedule_zero_date_epoch} and ${display_string_schedule_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}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: schedule_zero_date_epoch: ${schedule_zero_date_epoch}" [[ "${verbose_mode}" == "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}" == "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}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: display_string_schedule_zero_date: ${display_string_schedule_zero_date}" # If required, handle the ${schedule_deferred_start_days} option and ${schedule_deferred_start_days_active} accordingly. if [[ -n $schedule_deferred_start_days ]]; then schedule_zero_date_deferred_start_date=$(date -r $((schedule_zero_date_epoch + (schedule_deferred_start_days * 86400))) +%Y-%m-%d:%H:%M) schedule_zero_date_deferred_start_epoch=$(date -j -f %Y-%m-%d:%H:%M:%S "${schedule_zero_date_deferred_start_date}:00" +%s) [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: schedule_zero_date_deferred_start_epoch: ${schedule_zero_date_deferred_start_epoch}" if [[ $schedule_zero_date_deferred_start_epoch -lt $workflow_time_epoch ]]; then log_super "Status: Schedule deferred start of ${schedule_zero_date_deferred_start_date} (${schedule_deferred_start_days} day(s) after zero date of ${schedule_zero_date}) has passed." else local schedule_zero_date_deferred_start_difference schedule_zero_date_deferred_start_difference=$((schedule_zero_date_deferred_start_epoch - workflow_time_epoch)) [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: schedule_zero_date_deferred_start_difference is: ${schedule_zero_date_deferred_start_difference}" if [[ $schedule_zero_date_deferred_start_difference -le 180 ]]; then log_super "Status: Schedule deferred start of ${schedule_zero_date_deferred_start_date} (${schedule_deferred_start_days} day(s) after zero date of ${schedule_zero_date}) is only ${schedule_zero_date_deferred_start_difference} seconds away, waiting for deferred start to pass..." sleep $((schedule_zero_date_deferred_start_difference + 1)) log_super "Status: Schedule deferred start of ${schedule_zero_date_deferred_start_date} (${schedule_deferred_start_days} day(s) after zero date of ${schedule_zero_date}) has passed." else log_super "Status: Schedule deferred start of ${schedule_zero_date_deferred_start_date} (${schedule_deferred_start_days} day(s) after zero date of ${schedule_zero_date}) has not passed." schedule_deferred_start_days_active="TRUE" fi fi fi [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: schedule_deferred_start_days_active: ${schedule_deferred_start_days_active}" } # 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}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: scheduled_install_days: ${scheduled_install_days}" [[ "${verbose_mode}" == "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}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: scheduled_install_past_deadline: ${scheduled_install_past_deadline}" fi [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: workflow_scheduled_install: ${workflow_scheduled_install}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: scheduled_install_suppress_reminder: ${scheduled_install_suppress_reminder}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: workflow_scheduled_install: ${workflow_scheduled_install}" [[ "${verbose_mode}" == "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} and also write to log. if [[ -n "${schedule_workflow_active}" ]] && { [[ -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_audit "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_audit "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_audit "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}" ]]; 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_audit "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 set_pref_main "WorkflowScheduledInstall" "${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}" == "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}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: workflow_scheduled_install_epoch: ${workflow_scheduled_install_epoch}" [[ "${verbose_mode}" == "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}" | grep -c 'SCHEDULED') -gt 0 ]] && display_unmovable_status="TRUE" [[ $(echo "${display_hide_background}" | grep -c 'SCHEDULED') -gt 0 ]] && display_hide_background_status="TRUE" [[ $(echo "${display_silently}" | grep -c 'SCHEDULED') -gt 0 ]] && display_silently_status="TRUE" [[ $(echo "${display_hide_progress_bar}" | grep -c 'SCHEDULED') -gt 0 ]] && display_hide_progress_bar_status="TRUE" [[ $(echo "${display_notifications_centered}" | grep -c 'SCHEDULED') -gt 0 ]] && display_notifications_centered_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}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: display_string_scheduled_install_only_date: ${display_string_scheduled_install_only_date}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: display_string_scheduled_install_only_time: ${display_string_scheduled_install_only_time}" [[ "${verbose_mode}" == "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}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: workflow_scheduled_install_difference is: ${workflow_scheduled_install_difference}" { [[ "${scheduled_install_user_choice}" == "TRUE" ]] && [[ $workflow_scheduled_install_difference -gt $deferral_timer_minutes ]]; } && scheduled_install_user_choice_active="TRUE" [[ "${verbose_mode}" == "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}" == "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}" == "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}" == "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) set_pref_main "DeadlineDaysFocusDate" "${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}" == "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}" == "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) set_pref_main "DeadlineDaysSoftDate" "${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}" == "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}" == "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) set_pref_main "DeadlineDaysHardDate" "${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}" == "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}" == "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}" == "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}" == "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}" == "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}" == "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}" == "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}" == "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}" == "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}" == "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}" == "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}" == "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}" == "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} and also write to log. if [[ -n "${schedule_workflow_active}" ]] && [[ -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}" == "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}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: display_string_deadline_only_date is: ${display_string_deadline_only_date}" [[ "${verbose_mode}" == "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}" == "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}" == "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}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: deferral_timer_menu_reduced is: ${deferral_timer_menu_reduced}" [[ "${verbose_mode}" == "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}" == "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}" == "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}" == "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}" == "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=$(get_pref "DeadlineCounterFocus") if [[ -z "${deadline_counter_focus_previous}" ]]; then deadline_counter_focus_current=0 set_pref_main "DeadlineCounterFocus" "${deadline_counter_focus_current}" elif [[ "${test_mode}" == "TRUE" ]] || { [[ "${macos_installer_download_required}" != "TRUE" ]] && [[ "${macos_msu_download_required}" != "TRUE" ]]; }; then deadline_counter_focus_current=$((deadline_counter_focus_previous + 1)) set_pref_main "DeadlineCounterFocus" "${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." log_metrics "Focus Count Expired: Workflow Elapsed Time" "WorkflowTargetTimestamp" 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." log_metrics "Focus Count Remaining ${display_string_deadline_count_focus}: Workflow Elapsed Time" "WorkflowTargetTimestamp" 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=$(get_pref "DeadlineCounterSoft") if [[ -z "${deadline_counter_soft_previous}" ]]; then deadline_counter_soft_current=0 set_pref_main "DeadlineCounterSoft" "${deadline_counter_soft_current}" elif [[ "${test_mode}" == "TRUE" ]] || { [[ "${macos_installer_download_required}" != "TRUE" ]] && [[ "${macos_msu_download_required}" != "TRUE" ]]; }; then deadline_counter_soft_current=$((deadline_counter_soft_previous + 1)) set_pref_main "DeadlineCounterSoft" "${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." log_metrics "Soft Count Expired: Workflow Elapsed Time" "WorkflowTargetTimestamp" 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." log_metrics "Soft Count Remaining ${display_string_deadline_count_soft}: Workflow Elapsed Time" "WorkflowTargetTimestamp" 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=$(get_pref "DeadlineCounterHard") if [[ -z "${deadline_counter_hard_previous}" ]]; then deadline_counter_hard_current=0 set_pref_main "DeadlineCounterHard" "${deadline_counter_hard_current}" elif [[ "${test_mode}" == "TRUE" ]] || { [[ "${macos_installer_download_required}" != "TRUE" ]] && [[ "${macos_msu_download_required}" != "TRUE" ]]; }; then deadline_counter_hard_current=$((deadline_counter_hard_previous + 1)) set_pref_main "DeadlineCounterHard" "${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." log_metrics "Hard Count Expired: Workflow Elapsed Time" "WorkflowTargetTimestamp" 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." log_metrics "Hard Count Remaining ${display_string_deadline_count_hard}: Workflow Elapsed Time" "WorkflowTargetTimestamp" fi display_string_deadline_count="${display_string_deadline_count_hard}" display_string_deadline_count_maximum="${deadline_count_hard}" fi fi [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: deadline_count_status is: ${deadline_count_status}" [[ "${verbose_mode}" == "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_check_date msu_last_sccessful_check_date=$(defaults read "${MSU_LOCAL_PLIST}" LastSuccessfulDate 2>/dev/null) local msu_last_sccessful_check_epoch [[ -n "${msu_last_sccessful_check_date}" ]] && msu_last_sccessful_check_epoch=$(date -j -u -f "%Y-%m-%d %H:%M:%S %z" "${msu_last_sccessful_check_date}" +%s) [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: msu_last_sccessful_check_date is: ${msu_last_sccessful_check_date}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: msu_last_sccessful_check_epoch is: ${msu_last_sccessful_check_epoch}" local super_last_sccessful_check_date super_last_sccessful_check_date=$(get_pref "LastSuccessfulCheckDate") local super_last_sccessful_check_epoch [[ -n "${super_last_sccessful_check_date}" ]] && super_last_sccessful_check_epoch=$(date -j -u -f "%Y-%m-%d:%H:%M:%S" "${super_last_sccessful_check_date}" +%s) [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: super_last_sccessful_check_date is: ${super_last_sccessful_check_date}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: super_last_sccessful_check_epoch is: ${super_last_sccessful_check_epoch}" if [[ $msu_last_sccessful_check_epoch -lt $((workflow_time_epoch - (SOFTWARE_STATUS_CACHE_AGE_MINUTES * 60))) ]] && [[ $super_last_sccessful_check_epoch -lt $((workflow_time_epoch - (SOFTWARE_STATUS_CACHE_AGE_MINUTES * 60))) ]]; then log_super "Status: Last macOS software status check was more than $((SOFTWARE_STATUS_CACHE_AGE_MINUTES / 60)) hours ago, full software status check required." check_software_status_required="TRUE" return 0 fi local mac_last_startup_epoch mac_last_startup_epoch=$(date -j -u -f "%Y-%m-%d:%H:%M:%S" "${mac_last_startup}" +%s) [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: mac_last_startup_epoch is: ${mac_last_startup_epoch}" if [[ $super_last_sccessful_check_epoch -lt $mac_last_startup_epoch ]]; then log_super "Status: Last macOS software status check was before last system startup time, full software status check required." check_software_status_required="TRUE" return 0 fi # Check macOS deferral restrictions cache. local deferral_restrictions_checksum deferral_restrictions_checksum=$(get_pref "DeferralRestrictionsChecksum") [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: deferral_restrictions_checksum is: ${deferral_restrictions_checksum}" if { [[ $deferral_restrictions_checksum -eq 0 ]] && [[ ! -f "${APPLICATION_ACCESS_MANAGED_PLIST}.plist" ]]; } || [[ "${deferral_restrictions_checksum}" == "$(md5 -q "${APPLICATION_ACCESS_MANAGED_PLIST}.plist" 2>/dev/null)" ]]; then local deferral_restrictions_cache deferral_restrictions_cache=$(get_pref "DeferralRestrictionsCache") [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: deferral_restrictions_cache is: ${deferral_restrictions_cache}" if [[ -n "${deferral_restrictions_cache}" ]]; then check_deferral_restrictions 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=$(get_pref "MacOSBetaProgramCache") [[ "${verbose_mode}" == "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}" == "TRUE" ]]; 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}" == "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=$(get_pref "MDMClientListChecksum") [[ "${verbose_mode}" == "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=$(get_pref "MDMClientAvailableCache") [[ "${verbose_mode}" == "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}" == "TRUE" ]]; then mdmclient_available="TRUE" mdmclient_list=$(sed -e '/^Available updates/q' < "${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}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: mdmclient_list is:\n${mdmclient_list}" [[ "${verbose_mode}" == "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=$(get_pref "MacOSInstallersListChecksum") [[ "${verbose_mode}" == "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}" == "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=$(get_pref "MSUListChecksum") [[ "${verbose_mode}" == "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}" == "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. deferral_restrictions_macOS_minor_updates="FALSE" deferral_restrictions_macOS_major_upgrades="FALSE" deferral_restrictions_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. delete_pref_main "DeferralRestrictionsChecksum" delete_pref_main "DeferralRestrictionsCache" delete_pref_main "MacOSBetaProgramCache" delete_pref_main "MDMClientListChecksum" delete_pref_main "MDMClientAvailableCache" delete_pref_main "MacOSInstallerDownloaded" delete_pref_main "MacOSMSULabelDownloaded" rm -f "${MDMCLIENT_LIST_LOG}" 2>/dev/null delete_pref_main "MacOSInstallersListChecksum" rm -f "${MACOS_INSTALLERS_LIST_LOG}" 2>/dev/null delete_pref_main "MSUListChecksum" rm -f "${MSU_LIST_LOG}" 2>/dev/null [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: Main preference file after reset_software_update_status: ${SUPER_MAIN_PLIST}:\n$(defaults read "${SUPER_MAIN_PLIST}" 2>/dev/null)" } # Check for restrictions configuration profile deferrals and set ${deferral_restrictions_macOS_minor_updates}, ${deferral_restrictions_macOS_major_upgrades}, and ${deferral_restrictions_non_system_updates} accordingly. check_deferral_restrictions() { deferral_restrictions_macOS_major_upgrades="FALSE" deferral_restrictions_macOS_minor_updates="FALSE" deferral_restrictions_non_system_updates="FALSE" if [[ -f "${APPLICATION_ACCESS_MANAGED_PLIST}.plist" ]]; then set_pref_main "DeferralRestrictionsChecksum" "$(md5 -q "${APPLICATION_ACCESS_MANAGED_PLIST}.plist" 2>/dev/null)" local deferral_restrictions_response deferral_restrictions_response=$(defaults read "${APPLICATION_ACCESS_MANAGED_PLIST}" 2>&1 | grep -E 'enforcedSoftware|forceDelayed' | tr -d ';') fi [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: deferral_restrictions_response is:\n${deferral_restrictions_response}" if [[ -n "${deferral_restrictions_response}" ]]; then set_pref_main "DeferralRestrictionsCache" "TRUE" if [[ $(echo "${deferral_restrictions_response}" | awk '/forceDelayedMajorSoftwareUpdates/ {print $3}') -gt 0 ]]; then deferral_restrictions_macOS_major_upgrades=30 [[ $(echo "${deferral_restrictions_response}" | awk '/enforcedSoftwareUpdateMajorOSDeferredInstallDelay/ {print $3}') -gt 0 ]] && deferral_restrictions_macOS_major_upgrades=$(echo "${deferral_restrictions_response}" | awk '/enforcedSoftwareUpdateMajorOSDeferredInstallDelay/ {print $3}') log_super "Status: Restrictions configuration profile is deferring macOS major upgrades for ${deferral_restrictions_macOS_major_upgrades} days." fi if [[ $(echo "${deferral_restrictions_response}" | awk '/forceDelayedSoftwareUpdates/ {print $3}') -gt 0 ]]; then deferral_restrictions_macOS_minor_updates=30 [[ $(echo "${deferral_restrictions_response}" | awk '/enforcedSoftwareUpdateDelay/ {print $3}') -gt 0 ]] && deferral_restrictions_macOS_minor_updates=$(echo "${deferral_restrictions_response}" | awk '/enforcedSoftwareUpdateDelay/ {print $3}') [[ $(echo "${deferral_restrictions_response}" | awk '/enforcedSoftwareUpdateMinorOSDeferredInstallDelay/ {print $3}') -gt 0 ]] && deferral_restrictions_macOS_minor_updates=$(echo "${deferral_restrictions_response}" | awk '/enforcedSoftwareUpdateMinorOSDeferredInstallDelay/ {print $3}') log_super "Status: Restrictions configuration profile is deferring macOS minor updates for ${deferral_restrictions_macOS_minor_updates} days." fi if [[ $(echo "${deferral_restrictions_response}" | awk '/forceDelayedAppSoftwareUpdates/ {print $3}') -gt 0 ]]; then deferral_restrictions_non_system_updates=30 [[ $(echo "${deferral_restrictions_response}" | awk '/enforcedSoftwareUpdateDelay/ {print $3}') -gt 0 ]] && deferral_restrictions_non_system_updates=$(echo "${deferral_restrictions_response}" | awk '/enforcedSoftwareUpdateDelay/ {print $3}') [[ $(echo "${deferral_restrictions_response}" | awk '/enforcedSoftwareUpdateNonOSDeferredInstallDelay/ {print $3}') -gt 0 ]] && deferral_restrictions_non_system_updates=$(echo "${deferral_restrictions_response}" | awk '/enforcedSoftwareUpdateNonOSDeferredInstallDelay/ {print $3}') log_super "Status: Restrictions configuration profile is deferring non-system updates for ${deferral_restrictions_non_system_updates} days." fi else set_pref_main "DeferralRestrictionsChecksum" "0" set_pref_main "DeferralRestrictionsCache" "FALSE" fi [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: deferral_restrictions_macOS_major_upgrades is: ${deferral_restrictions_macOS_major_upgrades}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: deferral_restrictions_macOS_minor_updates is: ${deferral_restrictions_macOS_minor_updates}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: deferral_restrictions_non_system_updates is: ${deferral_restrictions_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_normalized -ge 130400 ]]; then local mdmclient_response mdmclient_response=$(/usr/libexec/mdmclient QueryDeviceInformation 2>/dev/null | grep 'IsDefaultCatalog') [[ "${verbose_mode}" == "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}" == "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}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_beta_program is: ${macos_beta_program}" if [[ "${macos_beta_program}" == "FALSE" ]]; then set_pref_main "MacOSBetaProgramCache" "FALSE" else # "${macos_beta_program}" == "TRUE" set_pref_main "MacOSBetaProgramCache" "TRUE" fi } # This restarts various softwareupdate daemon processes for systems older than macOS 14.4. kick_softwareupdated() { if [[ $macos_version_normalized -ge 140400 ]]; 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 '/^Available updates/q' < "${MDMCLIENT_LIST_LOG}") set_pref_main "MDMClientListChecksum" "$(md5 -q "${MDMCLIENT_LIST_LOG}" 2>/dev/null)" if [[ $(echo "${mdmclient_list}" | grep -c 'OS Update Item') -gt 0 ]]; then mdmclient_available="TRUE" set_pref_main "MDMClientAvailableCache" "TRUE" else set_pref_main "MDMClientAvailableCache" "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}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: get_mdmclient_list_error is: ${get_mdmclient_list_error}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: get_mdmclient_list_timeout is: ${get_mdmclient_list_timeout}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: mdmclient_available is: ${mdmclient_available}" [[ "${verbose_mode}" == "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}") set_pref_main "MacOSInstallersListChecksum" "$(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}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: get_macos_installers_list_error is: ${get_macos_installers_list_error}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: get_macos_installers_list_timeout is: ${get_macos_installers_list_timeout}" [[ "${verbose_mode}" == "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}" == "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}") set_pref_main "MSUListChecksum" "$(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}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: get_msu_list_error is: ${get_msu_list_error}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: get_msu_list_timeout is: ${get_msu_list_timeout}" [[ "${verbose_mode}" == "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_msu_major_upgrade_target}, ${macos_installer_major_upgrade_target}, ${macos_msu_minor_update_target}, ${macos_installer_minor_update_target}, ${non_system_msu_targets}, and ${non_system_msu_safari_target}. # 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 a ${macos_installer_major_upgrade_target} or ${macos_installer_minor_update_target}, then this also sets ${macos_installer_title}, ${macos_installer_version}, ${macos_installer_build}, and ${macos_installer_size}. # If there is ${non_system_msu_targets} then this also sets ${non_system_msu_safari_target}, ${non_system_msu_labels_array[]} and ${non_system_msu_titles_array[]}. workflow_check_software_status() { log_status "Running: Check for software update status workflow." macos_msu_major_upgrade_target="FALSE" macos_installer_major_upgrade_target="FALSE" macos_major_upgrade_latest="FALSE" macos_msu_minor_update_target="FALSE" macos_installer_minor_update_target="FALSE" macos_minor_update_latest="FALSE" macos_target_mdm_compatible="FALSE" non_system_msu_targets="FALSE" non_system_msu_safari_target="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}" == "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_deferral_restrictions check_macos_beta_program fi check_msu_settings # 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_normalized -lt 130300 ]] && [[ "${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." set_pref_main "LastSuccessfulCheckDate" "$(date +%Y-%m-%d:%H:%M:%S)" 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*Product Key' -e '^\s*Title' -e '^\s*Version' | tr '\n' ',' | sed -e 's/Product/\nProduct/g' -e 's/: */:/g' -e 's/ <[^,]*,//g' -e 's/^[[:space:]]//') [[ "${verbose_mode}" == "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_security_array mdmclient_security_array=($(echo "${mdmclient_sanitized_list}" | grep -E 'XProtect|MRT|Gatekeeper' | uniq)) local mdmclient_macos_msu_array mdmclient_macos_msu_array=($(echo "${mdmclient_sanitized_list}" | grep -E 'MSU_UPDATE' | uniq | sort -t ':' -k3 -V -r)) local mdmclient_macos_installer_array mdmclient_macos_installer_array=($(echo "${mdmclient_sanitized_list}" | grep -Ev 'MSU_UPDATE' | grep -E 'macOS' | uniq | sort -t ':' -k3 -V -r)) local mdmclient_non_system_array mdmclient_non_system_array=($(echo "${mdmclient_sanitized_list}" | grep -Ev 'macOS|XProtect|MRT|Gatekeeper' | uniq | sort -t ':' -k3 -V -r)) # Evaluate the numer of available updates and make sure there is at least one update available. if [[ ${#mdmclient_macos_msu_array[@]} -eq 0 ]] && [[ ${#mdmclient_macos_installer_array[@]} -eq 0 ]] && [[ ${#mdmclient_non_system_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}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: mdmclient_security_array is:\n${mdmclient_security_array[*]}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: mdmclient_macos_msu_array is:\n${mdmclient_macos_msu_array[*]}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: mdmclient_macos_installer_array is:\n${mdmclient_macos_installer_array[*]}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: mdmclient_non_system_array is:\n${mdmclient_non_system_array[*]}" # 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_non_system_array[@]} -eq 0 ]] && [[ ${#mdmclient_macos_msu_array[@]} -eq 0 ]] && [[ ${#mdmclient_macos_installer_array[@]} -eq 0 ]]; then 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}" set_pref_main "LastSuccessfulCheckDate" "$(date +%Y-%m-%d:%H:%M:%S)" return 0 fi # If (no errors) there are available items then collect the ${msu_list} and parse to create ${macos_msu_major_upgrade}, ${macos_msu_minor_update}, and ${non_system_msu_array[]}. if [[ "${workflow_check_software_status_error}" == "FALSE" ]] && { [[ ${#mdmclient_non_system_array[@]} -gt 0 ]] || [[ ${#mdmclient_macos_msu_array[@]} -gt 0 ]]; }; 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_normalized -lt 130300 ]] && [[ $(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 # If there is a ${msu_list} then parse items to appropriate variables. 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" else 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}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: msu_sanitized_list is:\n${msu_sanitized_list}" local non_system_msu_array non_system_msu_array=($(echo "${msu_sanitized_list}" | awk -F ',' '!/macOS/ {print $1 "," $2 "," $4 "," $3}')) [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: non_system_msu_array is:\n${non_system_msu_array[*]}" 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)) [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_msu_array is:\n${macos_msu_array[*]}" local macos_msu_major_upgrade macos_msu_major_upgrade="FALSE" local macos_msu_minor_update macos_msu_minor_update="FALSE" for macos_msu_item in "${macos_msu_array[@]}"; do [[ "$(echo "${macos_msu_item}" | awk -F ',' '{print $4}' | sed -e 's/.*://g' -e 's/\..*//g')" != "${macos_version_major}" ]] && macos_msu_major_upgrade="${macos_msu_item}" [[ "$(echo "${macos_msu_item}" | awk -F ',' '{print $4}' | sed -e 's/.*://g' -e 's/\..*//g')" == "${macos_version_major}" ]] && macos_msu_minor_update="${macos_msu_item}" done [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_msu_major_upgrade is: ${macos_msu_major_upgrade}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_msu_minor_update is: ${macos_msu_minor_update}" fi fi # If (no errors) there is the possibility of targeting a macOS installer then collect the ${macos_installers_list} and parse to create ${macos_installer_major_upgrades_array[]} and ${macos_installer_minor_updates_array[]}. if [[ "${workflow_check_software_status_error}" == "FALSE" ]] && { [[ "${install_macos_major_upgrades}" == "TRUE" ]] || [[ -n "${install_macos_major_version_target}" ]] || [[ -n "${install_macos_minor_version_target}" ]]; } && [[ ${#mdmclient_macos_installer_array[@]} -gt 0 ]]; 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 # If there is a ${macos_installers_list} then parse items to create arrays.. 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" else local macos_installers_sanitized_list macos_installers_sanitized_list=$(echo "${macos_installers_list}" | sed -e 's/"//g' -e 's/=//g') [[ "${verbose_mode}" == "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}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_installers_array is:\n${macos_installers_array[*]}" local macos_installer_major_upgrades_array macos_installer_major_upgrades_array=() local macos_installer_minor_updates_array macos_installer_minor_updates_array=() for macos_installer_item in "${macos_installers_array[@]}"; do [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_installer_item is: ${macos_installer_item}" local macos_installer_item_version_normalized macos_installer_item_version_normalized=$(echo "${macos_installer_item}" | awk -F ',' '{print $2}' | sed -e 's/.*://g' | awk -F'.' '{printf "%02d%02d%02d", $1, $2, $3}') [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_installer_item_version_normalized is: ${macos_installer_item_version_normalized}" [[ $(echo "${macos_installer_item}" | awk -F ',' '{print $2}' | sed -e 's/.*://g' -e 's/\..*//g') -gt $macos_version_major ]] && macos_installer_major_upgrades_array+=("${macos_installer_item}") { [[ $(echo "${macos_installer_item}" | awk -F ',' '{print $2}' | sed -e 's/.*://g' -e 's/\..*//g') -eq $macos_version_major ]] && [[ $macos_installer_item_version_normalized -gt $macos_version_normalized ]]; } && macos_installer_minor_updates_array+=("${macos_installer_item}") done [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_installer_major_upgrades_array is:\n${macos_installer_major_upgrades_array[*]}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_installer_minor_updates_array is:\n${macos_installer_minor_updates_array[*]}" fi fi # If (no errors) then evaluate versions in ${macos_msu_major_upgrade}, ${macos_installer_major_upgrades_array[]}, ${macos_msu_minor_update}, and ${macos_installer_minor_updates_array[]} for target selection. Also, log any available non-targetable macOS updates/upgrades. if [[ "${workflow_check_software_status_error}" == "FALSE" ]] && { [[ -n "${macos_msu_major_upgrade}" ]] || [[ ${#macos_installer_major_upgrades_array[@]} -gt 0 ]] || [[ -n "${macos_msu_minor_update}" ]] || [[ ${#macos_installer_minor_updates_array[@]} -gt 0 ]]; }; then [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: install_macos_major_upgrades is: ${install_macos_major_upgrades}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: install_macos_major_version_target is: ${install_macos_major_version_target}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: install_macos_major_version_normalized_target is: ${install_macos_major_version_normalized_target}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: install_macos_minor_updates is: ${install_macos_minor_updates}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: install_macos_minor_version_target is: ${install_macos_minor_version_target}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: install_macos_minor_version_normalized_target is: ${install_macos_minor_version_normalized_target}" # If ${install_macos_major_upgrades} then attempt to resolve a ${macos_msu_major_upgrade_target} given ${install_macos_major_version_target} and ${macos_msu_major_upgrade}. if [[ "${install_macos_major_upgrades}" == "TRUE" ]] && [[ -n "${macos_msu_major_upgrade}" ]]; then if [[ -z "${install_macos_major_version_target}" ]]; then macos_msu_major_upgrade_target="${macos_msu_major_upgrade}" else if [[ $(echo "${install_macos_major_version_target}" | grep -o '\.' | wc -l | xargs) -eq 0 ]] && [[ $(echo "${macos_msu_major_upgrade}" | awk -F ',' '{print $4}' | sed -e 's/.*://g' -e 's/\..*//g') -eq ${install_macos_major_version_target} ]]; then macos_msu_major_upgrade_target="${macos_msu_major_upgrade}" elif [[ $(echo "${install_macos_major_version_target}" | grep -o '\.' | wc -l | xargs) -eq 1 ]] && [[ $(echo "${macos_msu_major_upgrade}" | awk -F ',' '{print $4}' | sed -e 's/.*://g' | awk -F'.' '{printf "%02d%02d", $1, $2}') -eq ${install_macos_major_version_normalized_target:0:4} ]]; then macos_msu_major_upgrade_target="${macos_msu_major_upgrade}" elif [[ $(echo "${install_macos_major_version_target}" | grep -o '\.' | wc -l | xargs) -eq 2 ]] && [[ $(echo "${macos_msu_major_upgrade}" | awk -F ',' '{print $4}' | sed -e 's/.*://g' | awk -F'.' '{printf "%02d%02d%02d", $1, $2, $3}') -eq ${install_macos_major_version_normalized_target} ]]; then macos_msu_major_upgrade_target="${macos_msu_major_upgrade}" else log_super "Warning: Unable to target the latest available \"over-the-air\" macOS major upgrade given the specified --install-macos-major-version-target option of ${install_macos_major_version_target}." fi fi if [[ "${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: ${macos_msu_major_upgrade_target}" macos_msu_major_upgrade_target="FALSE" fi if [[ "${macos_msu_major_upgrade_target}" != "FALSE" ]]; then macos_msu_major_upgrade_target_version_normalized=$(echo "${macos_msu_major_upgrade_target}" | awk -F ',' '{print $4}' | sed -e 's/.*://g' | awk -F'.' '{printf "%02d%02d%02d", $1, $2, $3}') [[ "${deferral_restrictions_macOS_major_upgrades}" == "FALSE" ]] && macos_major_upgrade_latest="TRUE" [[ $(echo "${mdmclient_macos_msu_array[*]}" | grep -c "$(echo "${macos_msu_major_upgrade_target}" | awk -F ':' '{print $5}')") -gt 0 ]] && macos_target_mdm_compatible="TRUE" fi fi [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_msu_major_upgrade_target is: ${macos_msu_major_upgrade_target}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_msu_major_upgrade_target_version_normalized is: ${macos_msu_major_upgrade_target_version_normalized}" # If ${install_macos_major_upgrades} and there is no ${macos_msu_major_upgrade_target} then attempt to resolve a ${macos_installer_major_upgrade_target} given ${install_macos_major_version_target} and ${macos_installer_major_upgrades_array[]}. if [[ "${install_macos_major_upgrades}" == "TRUE" ]] && [[ "${macos_msu_major_upgrade_target}" == "FALSE" ]] && [[ ${#macos_installer_major_upgrades_array[@]} -gt 0 ]]; then if [[ -z "${install_macos_major_version_target}" ]]; then macos_installer_major_upgrade_target="${macos_installer_major_upgrades_array[0]}" [[ "${deferral_restrictions_macOS_major_upgrades}" == "FALSE" ]] && macos_major_upgrade_latest="TRUE" else for macos_installer_item in "${macos_installer_major_upgrades_array[@]}"; do [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_installer_item is: ${macos_installer_item}" if [[ $(echo "${install_macos_major_version_target}" | grep -o '\.' | wc -l | xargs) -eq 0 ]] && [[ $(echo "${macos_installer_item}" | awk -F ',' '{print $2}' | sed -e 's/.*://g' -e 's/\..*//g') -eq ${install_macos_major_version_target} ]]; then macos_installer_major_upgrade_target="${macos_installer_item}" break elif [[ $(echo "${install_macos_major_version_target}" | grep -o '\.' | wc -l | xargs) -eq 1 ]] && [[ $(echo "${macos_installer_item}" | awk -F ',' '{print $2}' | sed -e 's/.*://g' | awk -F'.' '{printf "%02d%02d", $1, $2}') -eq ${install_macos_major_version_normalized_target:0:4} ]]; then macos_installer_major_upgrade_target="${macos_installer_item}" break elif [[ $(echo "${install_macos_major_version_target}" | grep -o '\.' | wc -l | xargs) -eq 2 ]] && [[ $(echo "${macos_installer_item}" | awk -F ',' '{print $2}' | sed -e 's/.*://g' | awk -F'.' '{printf "%02d%02d%02d", $1, $2, $3}') -eq ${install_macos_major_version_normalized_target} ]]; then macos_installer_major_upgrade_target="${macos_installer_item}" break fi done fi if [[ "${macos_installer_major_upgrade_target}" != "FALSE" ]]; then macos_installer_major_upgrade_target_version_normalized=$(echo "${macos_installer_major_upgrade_target}" | awk -F ',' '{print $2}' | sed -e 's/.*://g' | awk -F'.' '{printf "%02d%02d%02d", $1, $2, $3}') [[ $(echo "${mdmclient_macos_installer_array[*]}" | grep -c "$(echo "${macos_installer_major_upgrade_target}" | awk -F ',' '{print $2}' | sed -e 's/.*://g')") -gt 0 ]] && macos_target_mdm_compatible="TRUE" else log_super "Warning: Unable to target any available macOS major upgrade installer given the specified --install-macos-major-version-target option of ${install_macos_major_version_target}." fi fi [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_installer_major_upgrade_target is: ${macos_installer_major_upgrade_target}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_installer_major_upgrade_target_version_normalized is: ${macos_installer_major_upgrade_target_version_normalized}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_major_upgrade_latest is: ${macos_major_upgrade_latest}" # If ${install_macos_minor_updates} and there is no ${macos_msu_major_upgrade_target} or ${macos_installer_major_upgrade_target} then attempt to resolve a ${macos_msu_minor_update_target} target given ${install_macos_minor_version_target} and ${macos_msu_minor_update}. if [[ "${install_macos_minor_updates}" == "TRUE" ]] && [[ "${macos_msu_major_upgrade_target}" == "FALSE" ]] && [[ "${macos_installer_major_upgrade_target}" == "FALSE" ]] && [[ -n "${macos_msu_minor_update}" ]]; then if [[ -z "${install_macos_minor_version_target}" ]]; then macos_msu_minor_update_target="${macos_msu_minor_update}" else if [[ $(echo "${install_macos_minor_version_target}" | grep -o '\.' | wc -l | xargs) -eq 0 ]] && [[ $(echo "${macos_msu_minor_update}" | awk -F ',' '{print $4}' | sed -e 's/.*://g' -e 's/\..*//g') -eq ${install_macos_minor_version_target} ]]; then macos_msu_minor_update_target="${macos_msu_minor_update}" elif [[ $(echo "${install_macos_minor_version_target}" | grep -o '\.' | wc -l | xargs) -eq 1 ]] && [[ $(echo "${macos_msu_minor_update}" | awk -F ',' '{print $4}' | sed -e 's/.*://g' | awk -F'.' '{printf "%02d%02d", $1, $2}') -eq ${install_macos_minor_version_normalized_target:0:4} ]]; then macos_msu_minor_update_target="${macos_msu_minor_update}" elif [[ $(echo "${install_macos_minor_version_target}" | grep -o '\.' | wc -l | xargs) -eq 2 ]] && [[ $(echo "${macos_msu_minor_update}" | awk -F ',' '{print $4}' | sed -e 's/.*://g' | awk -F'.' '{printf "%02d%02d%02d", $1, $2, $3}') -eq ${install_macos_minor_version_normalized_target} ]]; then macos_msu_minor_update_target="${macos_msu_minor_update}" else log_super "Warning: Unable to target the latest available \"over-the-air\" macOS minor update given the specified --install-macos-minor-version-target option of ${install_macos_minor_version_target}." fi fi { [[ $(echo "${macos_msu_minor_update_target}" | grep -c '(') -gt 0 ]] && [[ "${install_macos_bsi_updates}" != "TRUE" ]]; } && log_super "Warning: The default workflow ignores macOS Background Security Improvement (BSI) updates. Use the --install-macos-bsi-updates option to always install macOS BSI updates as soon as they are available." && macos_msu_minor_update_target="FALSE" if [[ "${macos_msu_minor_update_target}" != "FALSE" ]]; then macos_msu_minor_update_target_version_normalized=$(echo "${macos_msu_minor_update_target}" | awk -F ',' '{print $4}' | sed -e 's/.*://g' | awk -F'.' '{printf "%02d%02d%02d", $1, $2, $3}') [[ "${deferral_restrictions_macOS_minor_updates}" == "FALSE" ]] && macos_minor_update_latest="TRUE" [[ $(echo "${mdmclient_macos_msu_array[*]}" | grep -c "$(echo "${macos_msu_minor_update_target}" | awk -F ':' '{print $5}')") -gt 0 ]] && macos_target_mdm_compatible="TRUE" fi fi [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_msu_minor_update_target is: ${macos_msu_minor_update_target}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_msu_minor_update_target_version_normalized is: ${macos_msu_minor_update_target_version_normalized}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_minor_update_latest is: ${macos_minor_update_latest}" # If ${install_macos_minor_updates} and there is no ${macos_msu_major_upgrade_target} or ${macos_installer_major_upgrade_target} or ${macos_msu_minor_update} then attempt to resolve a ${macos_installer_minor_update_target} given ${install_macos_minor_version_target} and ${macos_installer_minor_updates_array[]}. if [[ "${install_macos_minor_updates}" == "TRUE" ]] && [[ "${macos_msu_major_upgrade_target}" == "FALSE" ]] && [[ "${macos_installer_major_upgrade_target}" == "FALSE" ]] && [[ "${macos_msu_minor_update_target}" == "FALSE" ]] && [[ ${#macos_installer_minor_updates_array[@]} -gt 0 ]]; then if [[ -z "${install_macos_minor_version_target}" ]]; then macos_installer_minor_update_target="${macos_installer_minor_updates_array[0]}" else for macos_installer_item in "${macos_installer_minor_updates_array[@]}"; do [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_installer_item is: ${macos_installer_item}" if [[ $(echo "${install_macos_minor_version_target}" | grep -o '\.' | wc -l | xargs) -eq 0 ]] && [[ $(echo "${macos_installer_item}" | awk -F ',' '{print $2}' | sed -e 's/.*://g' -e 's/\..*//g') -eq ${install_macos_minor_version_target} ]]; then macos_installer_minor_update_target="${macos_installer_item}" break elif [[ $(echo "${install_macos_minor_version_target}" | grep -o '\.' | wc -l | xargs) -eq 1 ]] && [[ $(echo "${macos_installer_item}" | awk -F ',' '{print $2}' | sed -e 's/.*://g' | awk -F'.' '{printf "%02d%02d", $1, $2}') -eq ${install_macos_minor_version_normalized_target:0:4} ]]; then macos_installer_minor_update_target="${macos_installer_item}" break elif [[ $(echo "${install_macos_minor_version_target}" | grep -o '\.' | wc -l | xargs) -eq 2 ]] && [[ $(echo "${macos_installer_item}" | awk -F ',' '{print $2}' | sed -e 's/.*://g' | awk -F'.' '{printf "%02d%02d%02d", $1, $2, $3}') -eq ${install_macos_minor_version_normalized_target} ]]; then macos_installer_minor_update_target="${macos_installer_item}" break fi done fi if [[ "${macos_installer_minor_update_target}" != "FALSE" ]]; then macos_installer_minor_update_target_version_normalized=$(echo "${macos_installer_minor_update_target}" | awk -F ',' '{print $2}' | sed -e 's/.*://g' | awk -F'.' '{printf "%02d%02d%02d", $1, $2, $3}') [[ $(echo "${mdmclient_macos_installer_array[*]}" | grep -c "$(echo "${macos_installer_minor_update_target}" | awk -F ',' '{print $2}' | sed -e 's/.*://g')") -gt 0 ]] && macos_target_mdm_compatible="TRUE" else [[ "${macos_installer_minor_update_target}" == "FALSE" ]] && log_super "Warning: Unable to target any available macOS minor update installer given the specified --install-macos-minor-version-target option of ${install_macos_minor_version_target}." fi fi [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_installer_minor_update_target is: ${macos_installer_minor_update_target}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_installer_minor_update_target_version_normalized is: ${macos_installer_minor_update_target_version_normalized}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_target_mdm_compatible is: ${macos_target_mdm_compatible}" fi # If (no errors) then log any disallowed or inefficient macOS targets. if [[ "${workflow_check_software_status_error}" == "FALSE" ]]; then # Log disallowed macOS MSU items. if [[ ${#mdmclient_macos_msu_array[@]} -gt 0 ]]; then # Product Key:MSU_UPDATE_25C56_patch_26.2_major,Title:macOS Tahoe 26.2,Version:26.2 local mdmclient_macos_item_version_normalized for mdmclient_macos_item in "${mdmclient_macos_msu_array[@]}"; do mdmclient_macos_item_version_normalized=$(echo "${mdmclient_macos_item}" | awk -F ',' '{print $3}' | sed -e 's/.*://g' | awk -F'.' '{printf "%02d%02d%02d", $1, $2, $3}') [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: mdmclient_macos_item_version_normalized is: ${mdmclient_macos_item_version_normalized}" [[ ${mdmclient_macos_item_version_normalized:0:2} -gt ${macos_version_major} ]] && [[ "${macos_msu_major_upgrade_target}" == "FALSE" ]] && log_super "Disallowed: \"over-the-air\" macOS major upgrade is available but not allowed: $(echo "${mdmclient_macos_item}" | awk -F ',' '{print $2}' | sed -e 's/.*://g')" [[ ${mdmclient_macos_item_version_normalized:0:2} -eq ${macos_version_major} ]] && [[ "${macos_msu_major_upgrade_target}" == "FALSE" ]] && [[ "${macos_msu_minor_update_target}" == "FALSE" ]] && log_super "Disallowed: \"over-the-air\" macOS minor update is available but not allowed: $(echo "${mdmclient_macos_item}" | awk -F ',' '{print $2}' | sed -e 's/.*://g')" done fi #Log disallowed macOS installers. if [[ ${#macos_installer_major_upgrades_array[@]} -gt 0 ]] || [[ ${#macos_installer_minor_updates_array[@]} -gt 0 ]]; then # Title:macOS Tahoe,Version:26.2,Build:25C56,Size:17389952037 for macos_installer_item in "${macos_installer_major_upgrades_array[@]}"; do macos_installer_item_version_normalized=$(echo "${macos_installer_item}" | awk -F ',' '{print $2}' | sed -e 's/.*://g' | awk -F'.' '{printf "%02d%02d%02d", $1, $2, $3}') [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_installer_item_version_normalized is: ${macos_installer_item_version_normalized}" { { [[ "${macos_msu_major_upgrade_target}" == "FALSE" ]] && [[ "${macos_installer_major_upgrade_target}" == "FALSE" ]]; } || { [[ "${macos_msu_major_upgrade_target}" != "FALSE" ]] && [[ ${macos_installer_item_version_normalized} -gt ${macos_msu_major_upgrade_target_version_normalized} ]]; } || { [[ "${macos_installer_major_upgrade_target}" != "FALSE" ]] && [[ ${macos_installer_item_version_normalized} -gt ${macos_installer_major_upgrade_target_version_normalized} ]]; }; } && log_super "Disallowed: macOS major upgrade installer is available but not allowed: $(echo "${macos_installer_item}" | awk -F ',' '{print $1 $2}' | sed -e 's/Title://' -e 's/Version:/ /')" done if [[ "${macos_msu_major_upgrade_target}" == "FALSE" ]] && [[ "${macos_installer_major_upgrade_target}" == "FALSE" ]]; then for macos_installer_item in "${macos_installer_minor_updates_array[@]}"; do macos_installer_item_version_normalized=$(echo "${macos_installer_item}" | awk -F ',' '{print $2}' | sed -e 's/.*://g' | awk -F'.' '{printf "%02d%02d%02d", $1, $2, $3}') [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_installer_item_version_normalized is: ${macos_installer_item_version_normalized}" { { [[ "${macos_msu_minor_update_target}" == "FALSE" ]] && [[ "${macos_installer_minor_update_target}" == "FALSE" ]]; } || { [[ "${macos_msu_minor_update_target}" != "FALSE" ]] && [[ ${macos_installer_item_version_normalized} -gt ${macos_msu_minor_update_target_version_normalized} ]]; } || { [[ "${macos_installer_minor_update_target}" != "FALSE" ]] && [[ ${macos_installer_item_version_normalized} -gt ${macos_installer_minor_update_target_version_normalized} ]]; }; } && log_super "Disallowed: macOS minor update installer is available but not allowed: $(echo "${macos_installer_item}" | awk -F ',' '{print $1 $2}' | sed -e 's/Title://' -e 's/Version:/ /')" done fi elif [[ ${#mdmclient_macos_installer_array[@]} -gt 0 ]] && [[ "${macos_msu_major_upgrade_target}" == "FALSE" ]] && [[ "${macos_msu_minor_update_target}" == "FALSE" ]]; then # Product Key:093-37361,Title:macOS Tahoe,Version:26.2 local mdmclient_macos_item_version_normalized for mdmclient_macos_item in "${mdmclient_macos_installer_array[@]}"; do mdmclient_macos_item_version_normalized=$(echo "${mdmclient_macos_item}" | awk -F ':' '{print $4}' | awk -F'.' '{printf "%02d%02d%02d", $1, $2, $3}') [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: mdmclient_macos_item_version_normalized is: ${mdmclient_macos_item_version_normalized}" [[ ${mdmclient_macos_item_version_normalized:0:2} -gt ${macos_version_major} ]] && { [[ "${macos_installer_major_upgrade_target}" == "FALSE" ]] || { [[ "${macos_installer_major_upgrade_target}" != "FALSE" ]] && [[ ${mdmclient_macos_item_version_normalized} -ne $(echo "${macos_installer_major_upgrade_target}" | awk -F ',' '{print $2}' | sed -e 's/.*://g' | awk -F'.' '{printf "%02d%02d%02d", $1, $2, $3}') ]]; }; } && log_super "Disallowed: macOS major upgrade installer is available but not allowed: $(echo "${mdmclient_macos_item}" | awk -F ',' '{print $2 $3}' | sed -e 's/Title://' -e 's/Version:/ /')" [[ ${mdmclient_macos_item_version_normalized:0:2} -eq ${macos_version_major} ]] && [[ "${macos_installer_major_upgrade_target}" == "FALSE" ]] && { [[ "${macos_installer_minor_update_target}" == "FALSE" ]] || { [[ "${macos_installer_minor_update_target}" != "FALSE" ]] && [[ ${mdmclient_macos_item_version_normalized} -ne $(echo "${macos_installer_major_upgrade_target}" | awk -F ',' '{print $2}' | sed -e 's/.*://g' | awk -F'.' '{printf "%02d%02d%02d", $1, $2, $3}') ]]; }; } && log_super "Disallowed: macOS minor update installer is available but not allowed: $(echo "${mdmclient_macos_item}" | awk -F ',' '{print $2 $3}' | sed -e 's/Title://' -e 's/Version:/ /')" done fi # Log inefficient installers. { [[ -n "${install_macos_minor_version_target}" ]] && [[ "${macos_msu_minor_update_target}" == "FALSE" ]] && [[ "${macos_installer_minor_update_target}" != "FALSE" ]]; } && log_super "Warning: The --install-macos-minor-version-target=${install_macos_minor_version_target} option is forcing the workflow to target a full macOS installer (as opposed to a more efficient \"over-the-air\" macOS minor update)." { [[ -n "${install_macos_major_version_target}" ]] && [[ "${macos_msu_major_upgrade_target}" == "FALSE" ]] && [[ "${macos_installer_major_upgrade_target}" != "FALSE" ]]; } && log_super "Warning: The --install-macos-major-version-target=${install_macos_major_version_target} option is forcing the workflow to target a full macOS installer (as opposed to a more efficient \"over-the-air\" macOS major upgrade)." fi # If (no errors) check for macOS targets that are incompatible with MDM authenticated workflows. if [[ "${workflow_check_software_status_error}" == "FALSE" ]] && [[ "${workflow_macos_auth}" == "JAMF" ]]; then if [[ "${macos_target_mdm_compatible}" == "FALSE" ]]; then if [[ $(echo "${auth_mdm_failover_to_user}" | grep -c 'ALWAYS') -gt 0 ]] || [[ $(echo "${auth_mdm_failover_to_user}" | grep -c 'ERROR') -gt 0 ]]; then log_super "Warning: The target macOS update or upgrade can not be authenticated via MDM, failing over to user authenticated workflow." auth_mdm_failover_to_user_status="TRUE" else log_super "Error: The target macOS update or upgrade can not be authenticated via MDM, you must enable the --auth-mdm-failover-to-user option when targeting older macOS versions." workflow_check_software_status_error="TRUE" fi fi if [[ "${macos_beta_program}" == "TRUE" ]] && { [[ -n "${install_macos_major_version_target}" ]] || [[ -n "${install_macos_minor_version_target}" ]]; }; then if { [[ -n "${install_macos_major_version_target}" ]] && [[ "${macos_major_upgrade_latest}" == "FALSE" ]]; } || { [[ -n "${install_macos_minor_version_target}" ]] && [[ "${macos_minor_update_latest}" == "FALSE" ]]; }; 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: 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 fi # If (no errors) there is a ${macos_msu_major_upgrade_target} or ${macos_msu_minor_update_target} then set the remaining individual parameters. if [[ "${workflow_check_software_status_error}" == "FALSE" ]] && { [[ "${macos_msu_major_upgrade_target}" != "FALSE" ]] || [[ "${macos_msu_minor_update_target}" != "FALSE" ]]; }; then local macos_msu_target [[ "${macos_msu_major_upgrade_target}" != "FALSE" ]] && macos_msu_target="${macos_msu_major_upgrade_target}" [[ "${macos_msu_minor_update_target}" != "FALSE" ]] && macos_msu_target="${macos_msu_minor_update_target}" [[ $(echo "${macos_msu_target}" | grep -c 'Beta') -eq 0 ]] && macos_msu_title=$(echo "${macos_msu_target}" | awk -F ',' '{print $2}' | sed -e 's/.*://g' -e 's/ [1-9].*//g') [[ $(echo "${macos_msu_target}" | grep -c 'Beta') -gt 0 ]] && macos_msu_title=$(echo "${macos_msu_target}" | awk -F ',' '{print $2}' | sed -e 's/.*://g' -e 's/ [1-9].*/ Beta/g') macos_msu_version=$(echo "${macos_msu_target}" | awk -F ',' '{print $4}' | sed -e 's/.*://g') macos_msu_build=$(echo "${macos_msu_target}" | awk -F ',' '{print $1}' | sed -e 's/.*://g' -e 's/^.*-//g') macos_msu_size=$(echo "${macos_msu_target}" | awk -F ',' '{print $3}' | sed -e 's/.*://g' -e 's/[^0-9]//g' | awk '{print $1"/1000000 +1"}' | bc) [[ "${macos_msu_major_upgrade_target}" != "FALSE" ]] && 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." [[ "${macos_msu_minor_update_target}" != "FALSE" ]] && log_super "Target: \"Over-the-air\" macOS minor update ${macos_msu_title} ${macos_msu_version}-${macos_msu_build} which is a ${macos_msu_size}GB download." macos_msu_label=$(echo "${macos_msu_target}" | awk -F ',' '{print $1}' | sed -e 's/.*://g') [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_msu_label is: ${macos_msu_label}" fi # If (no errors) there is a ${macos_installer_major_upgrade_target} or ${macos_installer_minor_update_target} then set the remaining individual parameters. if [[ "${workflow_check_software_status_error}" == "FALSE" ]] && { [[ "${macos_installer_major_upgrade_target}" != "FALSE" ]] || [[ "${macos_installer_minor_update_target}" != "FALSE" ]]; }; then local macos_installer_target [[ "${macos_installer_major_upgrade_target}" != "FALSE" ]] && macos_installer_target="${macos_installer_major_upgrade_target}" [[ "${macos_installer_minor_update_target}" != "FALSE" ]] && macos_installer_target="${macos_installer_minor_update_target}" 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) [[ "${macos_installer_major_upgrade_target}" != "FALSE" ]] && log_super "Target: macOS major upgrade installer ${macos_installer_title} ${macos_installer_version}-${macos_installer_build} which is a ${macos_installer_size}GB download." [[ "${macos_installer_minor_update_target}" != "FALSE" ]] && log_super "Target: macOS minor update installer ${macos_installer_title} ${macos_installer_version}-${macos_installer_build} which is a ${macos_installer_size}GB download." fi # If (no errors) then evaluate the ${non_system_msu_array[]} and set ${non_system_msu_targets} and ${non_system_msu_safari_target}. # 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" ]] && [[ ${#non_system_msu_array[@]} -gt 0 ]]; 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')) [[ $(echo "${non_system_msu_array[array_index]}" | grep -c 'Safari') -ge 1 ]] && non_system_msu_safari_target=$(echo "${non_system_msu_array[array_index]}" | awk -F ',' '{print $4}' | sed -e 's/Version://') 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}" == "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 fi [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: non_system_msu_targets is: ${non_system_msu_targets}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: non_system_msu_safari_target is: ${non_system_msu_safari_target}" # Handle ${workflow_check_software_status_error} or complete the workflow. 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 else set_pref_main "LastSuccessfulCheckDate" "$(date +%Y-%m-%d:%H:%M:%S)" fi } # MARK: *** Downloads *** ################################################################################ # This function checks for a downloaded and prepared softwareupdate macOS update/upgrade cache and sets ${macos_msu_downloaded_version}, ${macos_msu_downloaded_version_extra} and ${macos_msu_downloaded_build} accordingly. check_macos_msu_download() { macos_msu_downloaded_version="FALSE" macos_msu_downloaded_version_extra="FALSE" macos_msu_downloaded_build="FALSE" local update_asset_attributes update_asset_attributes=$(defaults read /System/Volumes/Update/Update update-asset-attributes 2>&1) [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: update_asset_attributes is:\n${update_asset_attributes}" if [[ ! $(echo "${update_asset_attributes}" | grep -c 'does not exist') -gt 0 ]]; then macos_msu_downloaded_version=$(echo "${update_asset_attributes}" | grep -w 'OSVersion' | awk -F '"' '{print $2;}') if [[ $macos_version_major -ge 13 ]]; then macos_msu_downloaded_version_extra=$(echo "${update_asset_attributes}" | grep -w 'ProductVersionExtra' | awk -F '"' '{print $2;}') [[ -n "${macos_msu_downloaded_version_extra}" ]] && macos_msu_downloaded_version="${macos_msu_downloaded_version} ${macos_msu_downloaded_version_extra}" fi macos_msu_downloaded_build=$(echo "${update_asset_attributes}" | grep -w 'Build' | awk '{print substr($3, 1, length($3)-1)}') fi [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_msu_downloaded_version is: ${macos_msu_downloaded_version}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_msu_downloaded_version_extra is: ${macos_msu_downloaded_version_extra}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_msu_downloaded_build is: ${macos_msu_downloaded_build}" } # Returns the macOS marketing name, version, and build that will be installed given a macOS installer application path ${1}. get_macos_installer_info() { [[ -d "/Volumes/Shared Support" ]] && diskutil unmount force "/Volumes/Shared Support" >/dev/null 2>&1 if [[ -f "${1}/Contents/SharedSupport/SharedSupport.dmg" ]]; then local hidiutil_response hidiutil_response=$(hdiutil attach -quiet -noverify -nobrowse "${1}/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_version_downloaded macos_installer_version_downloaded=$(/usr/libexec/PlistBuddy -c "Print :Assets:0:OSVersion" "/Volumes/Shared Support/com_apple_MobileAsset_MacSoftwareUpdate/com_apple_MobileAsset_MacSoftwareUpdate.xml") 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") diskutil unmount force "/Volumes/Shared Support" >/dev/null 2>&1 if [[ -n "${macos_installer_version_downloaded}" ]] && [[ -n "${macos_installer_build_downloaded}" ]]; then [[ -d "/Volumes/Shared Support" ]] && diskutil unmount force "/Volumes/Shared Support" >/dev/null 2>&1 echo "$(basename "${1}" | sed -e 's/Install //g' -e 's/\..*//g') ${macos_installer_version_downloaded}-${macos_installer_build_downloaded}" && return 0 else log_super "Error: Unable to resolve information for macOS installer located at: ${1}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_installer_version_downloaded is: ${macos_installer_version_downloaded}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_installer_build_downloaded is: ${macos_installer_build_downloaded}" fi else log_super "Error: Unable to locate macOS installer com_apple_MobileAsset_MacSoftwareUpdate.xml file for validation." fi else log_super "Error: Unable to mount macOS installer SharedSupport.dmg for validation." [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: hidiutil_response is:\n${hidiutil_response}" fi else log_super "Error: Unable to locate macOS installer at: ${1}" fi return 1 } # This function checks the macOS installer to be used for upgrades matches the ${macos_installer_build} and passes Gatekeeper validation. check_macos_installer_download() { 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}. if [[ "$(get_macos_installer_info "/Applications/Install ${macos_installer_title}.app")" != "${macos_installer_title} ${macos_installer_version}-${macos_installer_build}" ]]; then log_super "Warning: Downloaded macOS installer does not match the target macOS installer." check_macos_installer_error="TRUE" fi [[ "${verbose_mode}" == "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 "Warning: Downloaded macOS installer failed Gatekeeper validation." check_macos_installer_error="TRUE" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: startosinstall_response is:\n${startosinstall_response}" fi fi [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: check_macos_installer_error is: ${check_macos_installer_error}" } # Delete any unneeded or incorrect version macOS installers. delete_unneeded_macos_installers() { local previous_ifs previous_ifs="${IFS}" IFS=$'\n' local macos_installer_local_paths_array macos_installer_local_paths_array=($(mdfind kind:app -name "Install macOS" 2>/dev/null)) [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_installer_local_paths_array is:\n${macos_installer_local_paths_array[*]}" if [[ -n "${macos_installer_local_paths_array[*]}" ]]; then for macos_installer_path in "${macos_installer_local_paths_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_major_upgrade_target}" == "FALSE" ]] && [[ "${macos_installer_minor_update_target}" == "FALSE" ]]; then if [[ "${test_mode}" == "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" ]] || [[ "$(get_macos_installer_info "${macos_installer_path}")" != "${macos_installer_title} ${macos_installer_version}-${macos_installer_build}" ]]; then if [[ "${test_mode}" == "TRUE" ]]; then # Test mode. log_super "Test Mode: Found incorrect version target macOS installer at: ${macos_installer_path}" else # Normal workflow. log_super "Warning: Removing incorrect version target macOS installer at: ${macos_installer_path}" rm -Rf "${macos_installer_path}" >/dev/null 2>&1 fi fi fi done fi IFS="${previous_ifs}" } # This function determines which macOS updates, upgrades, or installers have been downloaded and validates any previously downloaded macOS updates, upgrades, or installers and sets ${macos_installer_download_required}, ${macos_msu_download_required}, and ${macos_msu_automatic_download_deferral} accordingly. workflow_check_download_status() { macos_installer_download_required="FALSE" macos_msu_download_required="FALSE" macos_msu_automatic_download_deferral="FALSE" macos_msu_automatic_download_ramped="FALSE" # If a macOS installer is targeted then evaluate any local installers and set ${macos_installer_download_required}. if [[ "${macos_installer_major_upgrade_target}" != "FALSE" ]] || [[ "${macos_installer_minor_update_target}" != "FALSE" ]]; then local macos_installer_download macos_installer_download=$(get_pref "MacOSInstallerDownloaded") [[ "${verbose_mode}" == "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_download 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" delete_pref_main "MacOSInstallerDownloaded" rm -Rf "/Applications/Install ${macos_installer_title}.app" >/dev/null 2>&1 fi set_pref_main "MacOSInstallerDownloaded" "${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" delete_pref_main "MacOSInstallerDownloaded" fi fi [[ "${verbose_mode}" == "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 if there is a cached macOS update/upgrade. if [[ "${macos_msu_major_upgrade_target}" != "FALSE" ]] || [[ "${macos_msu_minor_update_target}" != "FALSE" ]]; then local macos_msu_downloaded_error macos_msu_downloaded_error="FALSE" check_macos_msu_download macos_msu_label_downloaded=$(get_pref "MacOSMSULabelDownloaded") [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_msu_label_downloaded is: ${macos_msu_label_downloaded}" [[ $(get_pref "WorkflowDownloadMacOSAuthRequired") == "TRUE" ]] && workflow_download_macos_auth_required="TRUE" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: workflow_download_macos_auth_required is: ${workflow_download_macos_auth_required}" # If an MSU macOS version is cached then determine if it's valid. if [[ "${macos_msu_downloaded_version}" != "FALSE" ]]; then if [[ "${macos_msu_downloaded_version}" == "${macos_msu_version}" ]] && [[ -z "${macos_msu_label_downloaded}" ]]; then log_metrics "MSU Download Complete: Workflow Elapsed Time" "WorkflowTargetTimestamp" macos_msu_label_downloaded="${macos_msu_label}" && set_pref_main "MacOSMSULabelDownloaded" "${macos_msu_label}" fi [[ "${macos_msu_downloaded_version}" != "${macos_msu_version}" ]] && log_super "Warning: Previously downloaded macOS update/upgrade version ${macos_msu_downloaded_version} doesn't match workflow target version ${macos_msu_version}." && macos_msu_downloaded_error="TRUE" else # No msu download is prepared. macos_msu_downloaded_error="TRUE" [[ -n "${macos_msu_label_downloaded}" ]] && log_super "Warning: Previously downloaded macOS update/upgrade is no longer valid." fi if [[ "${macos_msu_downloaded_error}" == "TRUE" ]]; then macos_msu_download_required="TRUE" unset macos_msu_label_downloaded delete_pref_main "MacOSMSULabelDownloaded" fi [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_msu_download_required is: ${macos_msu_download_required}" # If the target is a macOS minor update and it should have been downloaded automatically, then evaluate if the workflow should automatically defer. if [[ "${macos_msu_minor_update_target}" != "FALSE" ]]; then { [[ "${msu_automatic_download}" == "TRUE" ]] && [[ "${macos_msu_download_required}" == "TRUE" ]]; } && macos_msu_automatic_download_deferral="TRUE" [[ "${macos_msu_automatic_download_deferral}" == "TRUE" ]] && macos_msu_automatic_download_ramped="$(grep "${macos_msu_build}" "/var/log/install.log" | grep "ramped, not downloadable at this time" | tail -1)" [[ "${msu_automatic_download}" == "FALSE" ]] && log_super "Warning: Automatic download of macOS updates is disabled, this is not recommended for macOS minor update workflows." fi [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_msu_automatic_download_deferral is: ${macos_msu_automatic_download_deferral}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_msu_automatic_download_ramped is: ${macos_msu_automatic_download_ramped}" fi } # Download macOS update or upgrade via softwareupdate command, and also save responses to ${SUPER_LOG}, ${MSU_WORKFLOW_LOG}, and ${SUPER_MAIN_PLIST}. download_macos_msu() { # If ${test_mode} then it's not necessary to continue this function. if [[ "${test_mode}" == "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}" == "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}" == "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}" == "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}" == "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}" == "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_audit "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}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: download_macos_msu_start_timeout is: ${download_macos_msu_start_timeout}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: download_macos_msu_start_error is: ${download_macos_msu_start_error}" [[ "${verbose_mode}" == "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}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_msu_title is: ${macos_msu_title}" [[ "${verbose_mode}" == "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" check_macos_msu_download [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_msu_version is: ${macos_msu_version}" [[ "${macos_msu_downloaded_version}" == "${macos_msu_version}" ]] && download_macos_msu_validation_error="FALSE" fi fi [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: download_macos_msu_title_error is: ${download_macos_msu_title_error}" [[ "${verbose_mode}" == "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 ****" log_metrics "MSU Download Complete: Workflow Elapsed Time" "WorkflowTargetTimestamp" download_maocs_msu_error="FALSE" macos_msu_download_required="FALSE" set_pref_main "MacOSMSULabelDownloaded" "${macos_msu_label}" 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" ]] && set_pref_main "WorkflowDownloadMacOSAuthRequired" "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_downloaded_version} doesn't match expected version ${macos_msu_version}." log_super "Error: Downloaded macOS major upgrade version of ${macos_msu_downloaded_version} 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_downloaded_version} doesn't match expected version ${macos_msu_version}." log_super "Error: Downloaded macOS mintor update version of ${macos_msu_downloaded_version} 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" delete_pref_main "MacOSMSULabelDownloaded" kill -9 "${download_macos_msu_pid}" >/dev/null 2>&1 kick_softwareupdated fi [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: download_maocs_msu_error is: ${download_maocs_msu_error}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_msu_download_required is: ${macos_msu_download_required}" [[ "${verbose_mode}" == "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_MAIN_PLIST}. download_macos_installer() { # If ${test_mode} then it's not necessary to continue this function. if [[ "${test_mode}" == "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}" == "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}" == "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}" == "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}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: download_macos_installer_start_error is: ${download_macos_installer_start_error}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: download_macos_installer_start_timeout is: ${download_macos_installer_start_timeout}" [[ "${verbose_mode}" == "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_download [[ "${check_macos_installer_error}" == "FALSE" ]] && download_macos_installer_validation_error="FALSE" fi fi [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: download_macos_installer_app_error is: ${download_macos_installer_app_error}" [[ "${verbose_mode}" == "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_audit "Status: A macOS installer is now available at: /Applications/Install ${macos_installer_title}.app" log_metrics "Installer Download Complete: Workflow Elapsed Time" "WorkflowTargetTimestamp" download_macos_installer_error="FALSE" macos_installer_download_required="FALSE" set_pref_main "MacOSInstallerDownloaded" "${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" delete_pref_main "MacOSInstallerDownloaded" fi [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: download_macos_installer_error is: ${download_macos_installer_error}" [[ "${verbose_mode}" == "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}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_installer_download_required is: ${macos_installer_download_required}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_msu_download_required is: ${macos_msu_download_required}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_msu_automatic_download_deferral is: ${macos_msu_automatic_download_deferral}" # If downloads are required then check for possible automatic download deferral and available storage. if [[ "${macos_installer_download_required}" == "TRUE" ]] || [[ "${macos_msu_download_required}" == "TRUE" ]]; then # If a macOS automatic download should be possible then automatically defer. if [[ "${macos_msu_automatic_download_deferral}" == "TRUE" ]]; then if [[ "${workflow_install_now_active}" != "TRUE" ]] && [[ "${deadline_date_status}" == "FALSE" ]] && [[ "${deadline_days_status}" == "FALSE" ]] && [[ "${deadline_count_status}" == "FALSE" ]]; then if [[ "${macos_msu_automatic_download_ramped}" != "FALSE" ]]; then log_super "Warning: The target macOS minor update has not yet automatically downloaded to the system because it's being delayed by Apple's incremental rollout mechanism, trying again in ${deferral_timer_minutes} minutes." log_status "Pending: The target macOS minor update has not yet automatically downloaded to the system because it's being delayed by Apple's incremental rollout mechanism, trying again in ${deferral_timer_minutes} minutes." else log_super "Warning: The target macOS minor update has not yet automatically downloaded to the system, trying again in ${deferral_timer_minutes} minutes." log_status "Pending: The target macOS minor update has not yet automatically downloaded to the system, trying again in ${deferral_timer_minutes} minutes." fi set_auto_launch_deferral else [[ "${workflow_install_now_active}" == "TRUE" ]] && log_super "Warning: Ignoring automatic macOS minor update download deferral due to current install now workflow." { [[ "${deadline_date_status}" != "FALSE" ]] || [[ "${deadline_days_status}" != "FALSE" ]] || [[ "${deadline_count_status}" != "FALSE" ]]; } && log_super "Warning: Ignoring automatic macOS minor update download deferral due to current deadline workflow." fi fi # Check for available storage. 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_major_upgrade_target}" != "FALSE" ]] || [[ "${macos_installer_minor_update_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}" == "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} then simulate dialogs and notifications for potential user authenticated download dialog. if [[ "${workflow_download_macos_error}" == "FALSE" ]] && [[ "${test_mode}" == "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}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: current_user_password_policies is: ${current_user_password_policies}" [[ "${verbose_mode}" == "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" set_pref_main "WorkflowDownloadMacOSAuthRequired" "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}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: workflow_download_macos_auth_required is: ${workflow_download_macos_auth_required}" [[ "${verbose_mode}" == "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}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: workflow_macos_auth is: ${workflow_macos_auth}" [[ "${verbose_mode}" == "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}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_credential_failover_to_user is: ${auth_credential_failover_to_user}" # 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}" | grep -c 'ERROR') -gt 0 ]] || { [[ "${workflow_install_now_active}" == "TRUE" ]] && [[ $(echo "${auth_mdm_failover_to_user}" | 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}" | grep -c 'ERROR') -gt 0 ]] || { [[ "${workflow_install_now_active}" == "TRUE" ]] && [[ $(echo "${auth_mdm_failover_to_user}" | 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}" == "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}" == "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}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: workflow_download_macos_error is: ${workflow_download_macos_error}" [[ "${verbose_mode}" == "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}" == "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 install_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[@]}" --force --agree-to-license >> "${MSU_WORKFLOW_LOG}" 2>&1 & install_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[@]}" --force --agree-to-license >> "${MSU_WORKFLOW_LOG}" 2>&1 & install_msu_pid="$!" fi else # macOS 11 softwareupdate --install "${non_system_msu_labels_array[@]}" --force --agree-to-license >> "${MSU_WORKFLOW_LOG}" 2>&1 & install_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}" == "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 ]] || [[ $(echo "${log_line}" | grep -c 'No updates are available') -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}" == "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}" == "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}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: non_system_msu_titles_array is:\n${non_system_msu_titles_array[*]}" [[ "${verbose_mode}" == "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 "${install_msu_pid}" >/dev/null 2>&1 kick_softwareupdated fi IFS="${previous_ifs}" } # Install only a Safari update via the softwareupdate command, and also save responses to ${SUPER_LOG} and ${MSU_WORKFLOW_LOG}. install_safari_update_msu() { install_safari_update_msu_error="TRUE" log_super "softwareupdate: Starting Safari ${non_system_msu_safari_target} update installation workflow, check ${MSU_WORKFLOW_LOG} for more detail." log_status "Running: softwareupdate: Starting Safari ${non_system_msu_safari_target} update installation workflow." log_msu "**** S.U.P.E.R.M.A.N. ${SUPER_VERSION} - INSTALL SAFARI ${non_system_msu_safari_target} UPDATE VIA SOFTWAREUPDATE START ****" # The update process is backgrounded and is watched via a while loop later on. Also note the different requirements between macOS versions. local install_msu_pid if [[ $macos_version_major -ge 12 ]]; then if [[ "${current_user_account_name}" == "FALSE" ]]; then sudo -i softwareupdate --install --safari-only --force --agree-to-license >> "${MSU_WORKFLOW_LOG}" 2>&1 & install_msu_pid="$!" else # Local user is logged in. launchctl asuser "${current_user_id}" sudo -u "${current_user_account_name}" softwareupdate --install --safari-only --force --agree-to-license >> "${MSU_WORKFLOW_LOG}" 2>&1 & install_msu_pid="$!" fi else # macOS 11 softwareupdate --install --safari-only --force --agree-to-license >> "${MSU_WORKFLOW_LOG}" 2>&1 & install_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_safari_update_msu_start_timeout install_safari_update_msu_start_timeout="TRUE" local install_safari_update_msu_start_error install_safari_update_msu_start_error="TRUE" local install_safari_update_msu_timeout_error install_safari_update_msu_timeout_error="TRUE" local install_safari_update_msu_timeout_seconds install_safari_update_msu_timeout_seconds="${TIMEOUT_START_SECONDS}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: install_safari_update_msu_timeout_seconds is: ${install_safari_update_msu_timeout_seconds}" while read -t "${install_safari_update_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 'No updates are available') -gt 0 ]]; then install_safari_update_msu_start_timeout="FALSE" break elif [[ $(echo "${log_line}" | grep -c 'Downloading') -gt 0 ]]; then log_super "softwareupdate: Safari ${non_system_msu_safari_target} update is downloading..." log_msu "**** TIMESTAMP ****" install_safari_update_msu_timeout_seconds="${TIMEOUT_NON_SYSTEM_MSU_SECONDS}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: install_safari_update_msu_timeout_seconds is: ${install_safari_update_msu_timeout_seconds}" install_safari_update_msu_start_timeout="FALSE" install_safari_update_msu_start_error="FALSE" elif [[ $(echo "${log_line}" | grep -c 'Downloaded') -gt 0 ]]; then log_super "softwareupdate: Safari ${non_system_msu_safari_target} update download complete." install_safari_update_msu_timeout_seconds="${TIMEOUT_NON_SYSTEM_MSU_SECONDS}" log_msu "**** TIMESTAMP ****" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: install_safari_update_msu_timeout_seconds is: ${install_safari_update_msu_timeout_seconds}" install_safari_update_msu_start_timeout="FALSE" install_safari_update_msu_start_error="FALSE" elif [[ $(echo "${log_line}" | grep -c 'Done with Safari') -gt 0 ]]; then log_super "softwareupdate: Safari ${non_system_msu_safari_target} update installed." log_msu "**** TIMESTAMP ****" install_safari_update_msu_start_timeout="FALSE" install_safari_update_msu_start_error="FALSE" elif [[ $(echo "${log_line}" | grep -c 'Done.') -gt 0 ]]; then log_msu "**** TIMESTAMP ****" install_safari_update_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_safari_update_msu_start_timeout}" == "FALSE" ]] && [[ "${install_safari_update_msu_start_error}" == "FALSE" ]] && [[ "${install_safari_update_msu_timeout_error}" == "FALSE" ]]; then log_msu "**** S.U.P.E.R.M.A.N. ${SUPER_VERSION} - INSTALL SAFARI ${non_system_msu_safari_target} UPDATE VIA SOFTWAREUPDATE COMPLETED ****" install_safari_update_msu_error="FALSE" else # The softwareupdate installation workflow failed. log_msu "**** S.U.P.E.R.M.A.N. ${SUPER_VERSION} - INSTALL SAFARI ${non_system_msu_safari_target} UPDATE VIA SOFTWAREUPDATE FAILED ****" if [[ "${install_safari_update_msu_start_timeout}" == "TRUE" ]]; then log_msu "Error: Installation of Safari ${non_system_msu_safari_target} update failed to start after waiting for ${install_safari_update_msu_timeout_seconds} seconds." log_super "Error: Installation of Safari ${non_system_msu_safari_target} update failed to start after waiting for ${install_safari_update_msu_timeout_seconds} seconds." elif [[ "${install_safari_update_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_safari_update_msu_timeout_error}" == "TRUE" ]]; then log_msu "Error: Installation of Safari ${non_system_msu_safari_target} update failed, as indicated by no progress after waiting for ${install_safari_update_msu_timeout_seconds} seconds." log_super "Error: Installation of Safari ${non_system_msu_safari_target} update failed, as indicated by no progress after waiting for ${install_safari_update_msu_timeout_seconds} seconds." fi kill -9 "${install_msu_pid}" >/dev/null 2>&1 kick_softwareupdated fi } # Install macOS updates via the softwareupdate command, and also save responses to ${SUPER_LOG}, ${MSU_WORKFLOW_LOG}, and ${SUPER_MAIN_PLIST}. install_macos_msu() { [[ "${current_user_account_name}" != "FALSE" ]] && notification_restart # If ${test_mode} then it's not necessary to continue this function. if [[ "${test_mode}" == "TRUE" ]]; then [[ "${workflow_scheduled_install_active}" == "TRUE" ]] && delete_pref_main "WorkflowScheduledInstall" 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 [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: Calling reset_workflow() function." reset_workflow 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}" == "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}" == "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}" == "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}" == "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}" == "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}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: install_macos_msu_start_error is: ${install_macos_msu_start_error}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: install_macos_msu_start_timeout is: ${install_macos_msu_start_timeout}" [[ "${verbose_mode}" == "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_MAIN_PLIST}.plist" 2> /dev/null [[ "${workflow_scheduled_install_active}" == "TRUE" ]] && /usr/libexec/PlistBuddy -c "Delete :WorkflowScheduledInstall" "${SUPER_MAIN_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_audit "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 via macOS installer application, and also save responses to ${SUPER_LOG}, ${INSTALLER_WORKFLOW_LOG}, and ${SUPER_MAIN_PLIST}. install_macos_app() { # If ${test_mode} then it's not necessary to continue this function. if [[ "${test_mode}" == "TRUE" ]]; then [[ "${workflow_scheduled_install_active}" == "TRUE" ]] && delete_pref_main "WorkflowScheduledInstall" log_super "Test Mode: Skipping the macOS insaller workflow." if [[ "${current_user_account_name}" != "FALSE" ]]; then log_super "Test Mode: Pausing ${test_mode_timeout_seconds} seconds for the macOS 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 [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: Calling reset_workflow() function." reset_workflow return 0 fi # Start with log and status updates. log_super "startosinstall: Starting ${macos_installer_title} ${macos_installer_version}-${macos_installer_build} install workflow, check ${INSTALLER_WORKFLOW_LOG} for more detail." log_status "Running: Starting ${macos_installer_title} ${macos_installer_version}-${macos_installer_build} install 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}" == "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}" == "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}" == "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}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: install_macos_app_start_error is: ${install_macos_app_start_error}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: install_macos_app_start_timeout is: ${install_macos_app_start_timeout}" [[ "${verbose_mode}" == "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_MAIN_PLIST}.plist" 2> /dev/null [[ "${workflow_scheduled_install_active}" == "TRUE" ]] && /usr/libexec/PlistBuddy -c "Delete :WorkflowScheduledInstall" "${SUPER_MAIN_PLIST}.plist" 2> /dev/null log_installer "**** S.U.P.E.R.M.A.N. ${SUPER_VERSION} - INSTALL MACOS COMPLETED ****" log_super_audit "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 installer application failed to start." log_super "Error: Installation of macOS installer application failed to start." elif [[ "${install_macos_app_start_timeout}" == "TRUE" ]]; then log_installer "Error: Installation of macOS installer application failed to start preparing after waiting for ${install_macos_app_timeout_seconds} seconds." log_super "Error: Installation of macOS 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 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 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 installer application failed, install now workflow can not continue." log_super "Error: Installation of macOS installer application failed, install now workflow can not continue." log_status "Inactive Error: Installation of macOS 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 installer application failed, trying again in ${deferral_timer_minutes} minutes." log_super "Error: Installation of macOS installer application failed, trying again in ${deferral_timer_minutes} minutes." log_status "Pending: Installation of macOS 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_MAIN_PLIST}. push_macos_mdm() { # If ${test_mode} then it's not necessary to continue this function. if [[ "${test_mode}" == "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}" == "TRUE" ]]; then # [[ "${push_macos_mdm_workflow}" == "INSTALL" ]] [[ "${workflow_scheduled_install_active}" == "TRUE" ]] && delete_pref_main "WorkflowScheduledInstall" 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 [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: Calling reset_workflow() function." reset_workflow return 0 fi # Start with log and status updates. [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: push_macos_mdm_workflow is: ${push_macos_mdm_workflow}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_msu_download_required is: ${macos_msu_download_required}" [[ "${verbose_mode}" == "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_major_upgrade_target}" != "FALSE" ]] || [[ "${macos_installer_minor_update_target}" != "FALSE" ]]; then # Target is a macOS 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}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: push_macos_mdm_target_type is: ${push_macos_mdm_target_type}" [[ "${verbose_mode}" == "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}" == "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}" == "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}" == "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" ]] || [[ "${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_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}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: jamf_api_update_workflow is: ${jamf_api_update_workflow}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: jamf_api_update_url is: ${jamf_api_update_url}" [[ "${verbose_mode}" == "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 --user-agent "${SUPER_USER_AGENT}" --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}" == "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}" == "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}" == "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 [[ "${mac_cpu_architecture}" == "arm64" ]] && display_string_prepare_time_estimate="15-20" [[ "${mac_cpu_architecture}" == "i386" ]] && display_string_prepare_time_estimate="20-30" 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}" == "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}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: push_macos_mdm_start_error is: ${push_macos_mdm_start_error}" [[ "${verbose_mode}" == "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}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: push_macos_mdm_target_type is: ${push_macos_mdm_target_type}" [[ "${verbose_mode}" == "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}" == "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}" == "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}" == "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}" == "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}" == "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}" == "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 [[ "${mac_cpu_architecture}" == "arm64" ]] && display_string_prepare_time_estimate="10-15" [[ "${mac_cpu_architecture}" == "i386" ]] && display_string_prepare_time_estimate="15-25" 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}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: push_macos_mdm_timeout_error is: ${push_macos_mdm_timeout_error}" [[ "${verbose_mode}" == "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}" == "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}" == "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}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_mdm_prepared_version_extra is: ${macos_mdm_prepared_version_extra}" fi [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_mdm_version_prepared is: ${macos_mdm_version_prepared}" [[ "${verbose_mode}" == "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." log_metrics "MSU Download Complete: Workflow Elapsed Time" "WorkflowTargetTimestamp" push_macos_mdm_download_error="FALSE" macos_msu_download_required="FALSE" set_pref_main "MacOSMSULabelDownloaded" "${macos_msu_label}" fi else # Install workflow at this point is a success. /usr/libexec/PlistBuddy -c "Add :WorkflowRestartValidate bool true" "${SUPER_MAIN_PLIST}.plist" 2> /dev/null [[ "${workflow_scheduled_install_active}" == "TRUE" ]] && /usr/libexec/PlistBuddy -c "Delete :WorkflowScheduledInstall" "${SUPER_MAIN_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_audit "MDM: ${macos_msu_label} upgrade is prepared and ready for restart!" [[ "${push_macos_mdm_target_type}" == "UPDATE" ]] && log_super_audit "MDM: ${macos_msu_label} update is prepared and ready for restart!" [[ "${push_macos_mdm_target_type}" == "INSTALLER" ]] && log_super_audit "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}" == "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}" == "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" delete_pref_main "MacOSMSULabelDownloaded" 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}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: push_macos_mdm_download_error is: ${push_macos_mdm_download_error}" [[ "${verbose_mode}" == "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 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 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 software updates workflow_install_non_system_msu() { [[ "${current_user_account_name}" != "FALSE" ]] && notification_non_system_updates # If ${test_mode} then it's not necessary to continue this function. if [[ "${test_mode}" == "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 non-system updates 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 installation 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" ]] && delete_pref_main "WorkflowScheduledInstall" if [[ "${install_non_system_msu_error}" == "FALSE" ]] && [[ "${non_system_msu_targets}" == "FALSE" ]]; then log_super_audit "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_normalized != "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}" == "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" ]] && [[ "${workflow_install_now_suppress_notifications}" != "TRUE" ]]; } && 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 workflow for the Safari software update. workflow_install_safari_update_msu() { { [[ "${current_user_account_name}" != "FALSE" ]] && [[ "${workflow_target_safari_active}" == "TRUE" ]]; } && notification_safari_update # If ${test_mode} then it's not necessary to continue this function. if [[ "${test_mode}" == "TRUE" ]]; then log_super "Test Mode: Skipping the install Safari update workflow." log_super "Test Mode: Pausing ${test_mode_timeout_seconds} seconds for the Safari update notification..." sleep "${test_mode_timeout_seconds}" killall -9 "IBM Notifier" "IBM Notifier Popup" >/dev/null 2>&1 return 0 fi # Install Safari update and check to make sure the Safari update is complete. install_safari_update_msu check_software_status_required="TRUE" workflow_check_software_status # If we had installation failures or there are a Safari update, then try again. if [[ "${install_safari_update_msu_error}" == "TRUE" ]] || [[ "${non_system_msu_safari_target}" != "FALSE" ]]; then log_super "Warning: Failed to install Safari update, re-trying installation workflow." install_safari_update_msu check_software_status_required="TRUE" workflow_check_software_status fi # Log status of Safari update completion. [[ "${workflow_scheduled_install_active}" == "TRUE" ]] && delete_pref_main "WorkflowScheduledInstall" if [[ "${install_safari_update_msu_error}" == "FALSE" ]] && [[ "${non_system_msu_safari_target}" == "FALSE" ]]; then log_super_audit "Status: Completed installation of Safari update!" if [[ "${current_user_account_name}" != "FALSE" ]] && [[ "${workflow_target_safari_active}" == "TRUE" ]]; then killall -9 "IBM Notifier" "IBM Notifier Popup" >/dev/null 2>&1 sudo -u "${current_user_account_name}" open "/Applications/Safari.app" & fi # For computers managed via Jamf Pro, submit inventory. if [[ $jamf_version_normalized != "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}" == "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 deferral_timer_minutes=$deferral_timer_error_minutes log_super "Warning: Safari update did not complete, trying again in ${deferral_timer_minutes} minutes." log_status "Pending: Safari update did not complete, trying again in ${deferral_timer_minutes} minutes." if [[ "${current_user_account_name}" != "FALSE" ]] && [[ "${workflow_target_safari_active}" == "TRUE" ]]; then notification_failed sudo -u "${current_user_account_name}" open "/Applications/Safari.app" & fi set_auto_launch_deferral 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}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_installer_major_upgrade_target is: ${macos_installer_major_upgrade_target}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_installer_minor_update_target is: ${macos_installer_minor_update_target}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_msu_major_upgrade_target is: ${macos_msu_major_upgrade_target}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_msu_minor_update_target is: ${macos_msu_minor_update_target}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: workflow_macos_auth is: ${workflow_macos_auth}" [[ "${verbose_mode}" == "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_major_upgrade_target}" != "FALSE" ]] || [[ "${macos_installer_minor_update_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 installer and a download is required. if [[ "${workflow_install_macos_no_user_error}" == "FALSE" ]] && { [[ "${macos_installer_major_upgrade_target}" != "FALSE" ]] || [[ "${macos_installer_minor_update_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}" == "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}" ]] && run_jamf_policy_triggers if [[ "${macos_installer_major_upgrade_target}" != "FALSE" ]] || [[ "${macos_installer_minor_update_target}" != "FALSE" ]]; then # Target is a macOS 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}" ]] && 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 elif [[ "${workflow_target}" == "Safari Update" ]]; then workflow_install_safari_update_msu else # Workflows when there are no macOS updates/upgrades. if [[ "${workflow_restart_without_updates}" == "TRUE" ]]; then # If requested, restart without updates. [[ -n "${install_jamf_policy_triggers}" ]] && run_jamf_policy_triggers set_pref_main "WorkflowRestartValidate" "TRUE" [[ "${workflow_scheduled_install_active}" == "TRUE" ]] && delete_pref_main "WorkflowScheduledInstall" log_super_audit "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}" == "TRUE" ]] && [[ -n "${install_jamf_policy_triggers}" ]]; then run_jamf_policy_triggers [[ "${workflow_scheduled_install_active}" == "TRUE" ]] && delete_pref_main "WorkflowScheduledInstall" 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}" == "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}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_installer_major_upgrade_target is: ${macos_installer_major_upgrade_target}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_installer_minor_update_target is: ${macos_installer_minor_update_target}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_msu_major_upgrade_target is: ${macos_msu_major_upgrade_target}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_msu_minor_update_target is: ${macos_msu_minor_update_target}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: workflow_macos_auth is: ${workflow_macos_auth}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: workflow_install_now_active is: ${workflow_install_now_active}" [[ "${verbose_mode}" == "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_major_upgrade_target}" != "FALSE" ]] || [[ "${macos_installer_minor_update_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}" | grep -c 'ERROR') -gt 0 ]] || { [[ "${workflow_install_now_active}" == "TRUE" ]] && [[ $(echo "${auth_mdm_failover_to_user}" | 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}" | grep -c 'ERROR') -gt 0 ]] || { [[ "${workflow_install_now_active}" == "TRUE" ]] && [[ $(echo "${auth_mdm_failover_to_user}" | 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}" == "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_major_upgrade_target}" != "FALSE" ]] || [[ "${macos_installer_minor_update_target}" != "FALSE" ]]; then # Target is a macOS installer. [[ "${mac_cpu_architecture}" == "arm64" ]] && display_string_prepare_time_estimate="10-15" [[ "${mac_cpu_architecture}" == "i386" ]] && display_string_prepare_time_estimate="15-25" notification_prepare [[ -n "${install_jamf_policy_triggers}" ]] && run_jamf_policy_triggers install_macos_app else # Target is a macOS upgrade/update via MSU. [[ -n "${install_jamf_policy_triggers}" ]] && run_jamf_policy_triggers install_macos_msu fi else # [[ "${workflow_macos_auth}" == "JAMF" ]] if [[ "${macos_installer_major_upgrade_target}" != "FALSE" ]] || [[ "${macos_installer_minor_update_target}" != "FALSE" ]]; then # Target is a macOS installer. [[ "${mac_cpu_architecture}" == "arm64" ]] && display_string_prepare_time_estimate="15-20" [[ "${mac_cpu_architecture}" == "i386" ]] && display_string_prepare_time_estimate="20-30" else # Target is a macOS upgrade/update via MSU. display_string_prepare_time_estimate="5" fi notification_prepare [[ -n "${install_jamf_policy_triggers}" ]] && 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 elif [[ "${workflow_target}" == "Safari Update" ]]; then workflow_install_safari_update_msu else # Workflows when there are no macOS updates/upgrades. if [[ "${workflow_restart_without_updates}" == "TRUE" ]]; then # If requested, restart without updates. [[ -n "${install_jamf_policy_triggers}" ]] && run_jamf_policy_triggers notification_restart if [[ "${test_mode}" != "TRUE" ]]; then set_pref_main "WorkflowRestartValidate" "TRUE" [[ "${workflow_scheduled_install_active}" == "TRUE" ]] && delete_pref_main "WorkflowScheduledInstall" log_super_audit "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}" == "TRUE" ]] && [[ -n "${install_jamf_policy_triggers}" ]]; then run_jamf_policy_triggers [[ "${workflow_scheduled_install_active}" == "TRUE" ]] && delete_pref_main "WorkflowScheduledInstall" 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}" == "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}" != "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_audit "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_major_upgrade_target}" == "FALSE" ]] && [[ "${macos_installer_minor_update_target}" == "FALSE" ]] && [[ "${macos_msu_major_upgrade_target}" == "FALSE" ]] && [[ "${macos_msu_minor_update_target}" == "FALSE" ]] && [[ "${non_system_msu_targets}" == "FALSE" ]]; then log_super_audit "Status: All available and enabled macOS software updates/upgrades completed!" check_software_status_required="FALSE" elif [[ "${macos_installer_major_upgrade_target}" == "FALSE" ]] && [[ "${macos_installer_minor_update_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_audit "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_normalized != "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}" == "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}" == "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}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: Main preference file after workflow_restart_validate: ${SUPER_MAIN_PLIST}:\n$(defaults read "${SUPER_MAIN_PLIST}" 2>/dev/null)" } # MARK: *** Jamf Pro *** ################################################################################ # Validate connectivity to Jamf Pro service and set ${jamf_version_normalized}, ${jamf_management_url}, and ${jamf_api_url}. check_jamf_management_framework() { jamf_version_normalized="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_normalized=$(echo "${jamf_version_number_full}" | awk -F'.' '{printf "%02d%02d%02d", $1, $2, $3}') # Expected output: 112001 if [[ "${mac_cpu_architecture}" == "arm64" ]] && [[ $jamf_version_normalized -lt 103800 ]]; then log_super "Error: super requires Jamf Pro version 10.38 or later, the currently installed version of Jamf Pro ${jamf_version_number_full} is not supported." auth_error_jamf="TRUE" elif [[ "${mac_cpu_architecture}" != "arm64" ]] && [[ $jamf_version_normalized -lt 100000 ]]; then log_super "Error: super requires Jamf Pro version 10.00 or later, the currently installed version of Jamf Pro ${jamf_version_number_full} 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}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: jamf_version_normalized is: ${jamf_version_normalized}" # Check for ${auth_jamf_custom_url} and sets ${jamf_api_url} accordingly. if [[ $jamf_version_normalized != "FALSE" ]] && [[ "${auth_error_jamf}" != "TRUE" ]] && [[ -n "${auth_jamf_custom_url}" ]]; then local curl_response curl_response=$(curl --user-agent "${SUPER_USER_AGENT}" --silent --head --location "${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 managed preferences or a jamf recon to resolve the computer's ${jamf_computer_id}. get_jamf_api_computer_id() { jamf_computer_id=$(get_pref_managed "AuthJamfComputerID") [[ -z "${jamf_computer_id}" ]] && jamf_computer_id=$(get_pref "AuthJamfComputerID") # Resolve the computer's Jamf Pro ID via jamf recon. if [[ -z "${jamf_computer_id}" ]]; then 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') set_pref_main "AuthJamfComputerID" "${jamf_computer_id}" else log_super "Error: Jamf Pro inventory collection failed." auth_error_jamf="TRUE" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: jamf_response is:\n${jamf_response}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: jamf_return is: ${jamf_return}" fi fi [[ ! "${jamf_computer_id}" =~ ${REGEX_ANY_WHOLE_NUMBER} ]] && log_super "Error: Unable to resolve Jamf Pro Computer ID." && auth_error_jamf="TRUE" [[ "${verbose_mode}" == "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 --user-agent "${SUPER_USER_AGENT}" --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 --user-agent "${SUPER_USER_AGENT}" --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}" == "TRUE" ]] && log_echo "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_jamf_client is: ${auth_jamf_client}" [[ "${verbose_mode}" == "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}" == "TRUE" ]] && log_echo "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_jamf_account is: ${auth_jamf_account}" [[ "${verbose_mode}" == "TRUE" ]] && log_echo "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_jamf_password is: ${auth_jamf_password}" fi auth_error_jamf="TRUE" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: jamf_api_url is: ${jamf_api_url}" [[ "${verbose_mode}" == "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 --user-agent "${SUPER_USER_AGENT}" --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}" == "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 --user-agent "${SUPER_USER_AGENT}" --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}" == "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 --user-agent "${SUPER_USER_AGENT}" --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}" == "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_normalized -ge 104800 ]]; then local curl_response curl_response=$(curl --user-agent "${SUPER_USER_AGENT}" --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}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: curl_response is: ${curl_response}" fi fi [[ "${verbose_mode}" == "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 --user-agent "${SUPER_USER_AGENT}" --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}" == "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}" == "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}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: curl_response is:\n${curl_response}" fi } # Install any optional ${install_jamf_policy_triggers}. 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}" for jamf_policy_trigger in "${jamf_policy_triggers_array[@]}"; do [[ "${current_user_account_name}" != "FALSE" ]] && notification_jamf_pro_policy if [[ "${test_mode}" != "TRUE" ]]; then log_super "Status: Jamf Pro Policy with Trigger \"${jamf_policy_trigger}\" is starting..." local jamf_response local jamf_return if [[ "${verbose_mode}" == "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_audit "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_audit "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 installation workflow title . if [[ "${workflow_target}" == "Non-system Software Updates" ]]; then display_string_workflow_title="Apple Software Updates" elif [[ "${workflow_target}" == "Safari Update" ]]; then display_string_workflow_title="Safari ${non_system_msu_safari_target} Update" 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 interfere with the update process." #### Language for notification_safari_update(), a non-interactive notification informing the user that the workflow is installing a Safari update. display_string_safari_update_title="Installing Safari ${non_system_msu_safari_target} Update (No Restart)" display_string_safari_update_body="Required Safari update is now installing and Safari will automatically restart when the update is complete.\n\nThis should only take a few moments, but please do not restart or sleep the computer as it will interfere with 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}. set_deferral_button() { # If needed adjust the ${deferral_timer_minutes} for the ${schedule_workflow_active} and also write to log. if [[ -n "${schedule_workflow_active}" ]]; 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}" == "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}. 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}. if [[ -n "${schedule_workflow_active}" ]]; 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}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: deferral_timer_menu_adjusted is: ${deferral_timer_menu_adjusted}" [[ "${verbose_mode}" == "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 --user-agent "${SUPER_USER_AGENT}" --silent --head --location "${display_help_button_string_option}" | head -1) [[ "${verbose_mode}" == "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 --user-agent "${SUPER_USER_AGENT}" --silent --head --location "${display_warning_button_string_option}" | head -1) [[ "${verbose_mode}" == "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 } # This function is designed to run the the background and when killed will output "Active:Inactive:Total" seconds as a string to the file located at input ${1}. dialog_metrics_counter() { local dialog_metrics_active_seconds dialog_metrics_active_seconds=0 local dialog_metrics_inactive_seconds dialog_metrics_inactive_seconds=0 trap 'echo $dialog_metrics_active_seconds:$dialog_metrics_inactive_seconds:$((dialog_metrics_active_seconds + dialog_metrics_inactive_seconds)) > "${1}"' SIGTERM while true; do if [[ $(($(ioreg -c IOHIDSystem | awk '/HIDIdleTime/ {print $6}') / 1000000000)) -gt 0 ]]; then ((dialog_metrics_inactive_seconds+=1)) else ((dialog_metrics_active_seconds+=1)) fi sleep 1 done } # 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}" == "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}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: notification_response is: ${notification_response}" [[ "${verbose_mode}" == "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}" == "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 --user-agent "${SUPER_USER_AGENT}" --silent --fail --location "${display_accessory_content}") [[ "${verbose_mode}" == "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 --user-agent "${SUPER_USER_AGENT}" --silent --head --location "${display_accessory_content}" | head -1) [[ "${verbose_mode}" == "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}" == "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}" == "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}" == "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}" == "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 background metrics collection for IBM Notifier dialog. log_metrics "Open Dialog $(echo "${dialog_type}" | sed -e 's/ALERT/Interactive Alert/' -e 's/CHOICE/User Choice/' -e 's/SCHEDULE/User Schedule/' -e 's/REMINDER/Schedule Reminder/' -e 's/SOFT/Soft Deadline/' -e 's/AUTH/User Authentication/'): Workflow Elapsed Time" "WorkflowTargetTimestamp" local dialog_metrics_counter_fifo dialog_metrics_counter_fifo="$(mktemp).fifo" mkfifo "${dialog_metrics_counter_fifo}" dialog_metrics_counter "${dialog_metrics_counter_fifo}" & local dialog_metrics_counter_pid dialog_metrics_counter_pid="$!" # 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}" == "TRUE" ]] && [[ "${dialog_type}" != "AUTH" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: dialog_response is: ${dialog_response}" { [[ "${verbose_mode}" == "TRUE" ]] && [[ "${dialog_type}" == "AUTH" ]]; } && log_echo "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: dialog_response is: ${dialog_response}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: dialog_return is: ${dialog_return}" # Collect and report dialog metrics. kill "${dialog_metrics_counter_pid}" local dialog_metrics_response dialog_metrics_response=$(cat "${dialog_metrics_counter_fifo}") [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: dialog_metrics_response is: ${dialog_metrics_response}" rm -Rf "${dialog_metrics_counter_fifo}" >/dev/null 2>&1 log_metrics "Close Dialog $(echo "${dialog_type}" | sed -e 's/ALERT/Interactive Alert/' -e 's/CHOICE/User Choice/' -e 's/SCHEDULE/User Schedule/' -e 's/REMINDER/Schedule Reminder/' -e 's/SOFT/Soft Deadline/' -e 's/AUTH/User Authentication/'): User Active Elapsed Time" "$(echo "${dialog_metrics_response}" | awk -F ':' '{print $1}')" log_metrics "Close Dialog $(echo "${dialog_type}" | sed -e 's/ALERT/Interactive Alert/' -e 's/CHOICE/User Choice/' -e 's/SCHEDULE/User Schedule/' -e 's/REMINDER/Schedule Reminder/' -e 's/SOFT/Soft Deadline/' -e 's/AUTH/User Authentication/'): User Inactive Elapsed Time" "$(echo "${dialog_metrics_response}" | awk -F ':' '{print $2}')" log_metrics "Close Dialog $(echo "${dialog_type}" | sed -e 's/ALERT/Interactive Alert/' -e 's/CHOICE/User Choice/' -e 's/SCHEDULE/User Schedule/' -e 's/REMINDER/Schedule Reminder/' -e 's/SOFT/Soft Deadline/' -e 's/AUTH/User Authentication/'): Dialog Elapsed Time" "$(echo "${dialog_metrics_response}" | awk -F ':' '{print $3}')" } # 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." log_metrics "Open Notification Install Now Start: Workflow Elapsed Time" "WorkflowTargetTimestamp" 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}" == "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." log_metrics "Open Notification Install Now Downloading: Workflow Elapsed Time" "WorkflowTargetTimestamp" 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}" == "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." log_metrics "Open Notification Install Now Completed: Workflow Elapsed Time" "WorkflowTargetTimestamp" 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}" == "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." log_metrics "Open Notification Install Now Failed: Workflow Elapsed Time" "WorkflowTargetTimestamp" 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}" == "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 scheduled 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 log_metrics "Open Notification Jamf Pro Policy Installation: Workflow Elapsed Time" "WorkflowTargetTimestamp" # 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 scheduled 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 log_metrics "Open Notification Preparing Update/Upgrade: Workflow Elapsed Time" "WorkflowTargetTimestamp" # 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 scheduled 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 log_metrics "Open Notification Restart: Workflow Elapsed Time" "WorkflowTargetTimestamp" # 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 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." log_metrics "Open Notification Installing Non-System Updates: Workflow Elapsed Time" "WorkflowTargetTimestamp" 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}" == "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 workflow is installing a Safari update. notification_safari_update() { set_display_strings_language log_super "IBM Notifier: Installing Safari update notification." log_metrics "Open Notification Installing Safari Update: Workflow Elapsed Time" "WorkflowTargetTimestamp" ibm_notifier_array=(-type popup -bar_title "${display_string_safari_update_title}" -subtitle "${display_string_safari_update_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}" == "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." log_metrics "Open Notification Installation Failed: Workflow Elapsed Time" "WorkflowTargetTimestamp" 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}" == "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 scheduled installation dialog with a ${dialog_timeout_seconds} second timeout." [[ -z "${dialog_timeout_seconds}" ]] && log_super "IBM Notifier: Insufficient storage scheduled 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}, ${display_hide_background}, ${display_silently}, and ${display_hide_progress_bar}. { [[ "${display_unmovable_status}" != "TRUE" ]] && [[ $(echo "${display_unmovable}" | 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}" | 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}" == "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 log_metrics "Dialog Insufficient Storage Success: Workflow Elapsed Time" "WorkflowTargetTimestamp" 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}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: dialog_insufficient_storage_error is: ${dialog_insufficient_storage_error}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: storage_ready is: ${storage_ready}" # Reset temporary ${display_unmovable}, ${display_hide_background}, ${display_silently}, and ${display_hide_progress_bar}. [[ "${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" log_metrics "Dialog Insufficient Storage Timeout: Workflow Elapsed Time" "WorkflowTargetTimestamp" 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 scheduled installation dialog with a ${dialog_timeout_seconds} second timeout." [[ -z "${dialog_timeout_seconds}" ]] && log_super "IBM Notifier: Power required scheduled 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}, ${display_hide_background}, ${display_silently}, and ${display_hide_progress_bar}. { [[ "${display_unmovable_status}" != "TRUE" ]] && [[ $(echo "${display_unmovable}" | grep -c 'ERROR') -gt 0 ]]; } && display_unmovable_status="TEMP" { [[ "${display_hide_background_status}" != "TRUE" ]] && [[ $(echo "${display_hide_background}" | grep -c 'ERROR') -gt 0 ]]; } && display_hide_background_status="TEMP" { [[ "${display_silently_status}" != "TRUE" ]] && [[ $(echo "${display_silently}" | 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}" == "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}" == "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 log_metrics "Dialog Power Required Success: Workflow Elapsed Time" "WorkflowTargetTimestamp" break fi [[ -n "${dialog_timeout_seconds}" ]] && dialog_power_required_check_timeout=$((dialog_power_required_check_timeout - POWER_REQUIRED_RECHECK_SECONDS)) done [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: power_required_charger_connected is: ${power_required_charger_connected}" # Reset temporary ${display_unmovable}, ${display_hide_background}, ${display_silently}, and ${display_hide_progress_bar}. [[ "${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" log_metrics "Dialog Power Required Timeout: Workflow Elapsed Time" "WorkflowTargetTimestamp" fi [[ "${verbose_mode}" == "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}" == "Safari Update" ]] || [[ "${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}. if [[ "${scheduled_install_user_choice}" == "TRUE" ]] && { [[ "${workflow_target}" != "Non-system Software Updates" ]] && [[ "${workflow_target}" != "Safari Update" ]] && [[ "${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="FALSE" fi # Set the user choice buttons including handling of ${scheduled_install_user_choice}. if [[ "${scheduled_install_user_choice}" == "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}, ${display_hide_background}, and ${display_silently} options. { [[ "${display_unmovable_status}" != "TRUE" ]] && [[ $(echo "${display_unmovable}" | grep -c 'DIALOG') -gt 0 ]]; } && display_unmovable_status="TEMP" { [[ "${display_hide_background_status}" != "TRUE" ]] && [[ $(echo "${display_hide_background}" | grep -c 'DIALOG') -gt 0 ]]; } && display_hide_background_status="TEMP" { [[ "${display_silently_status}" != "TRUE" ]] && [[ $(echo "${display_silently}" | 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}, ${display_hide_background}, and ${display_silently} 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 log_metrics "Dialog User Choice Deferral: Workflow Elapsed Time" "WorkflowTargetTimestamp" set_auto_launch_deferral ;; 2) log_super "Status: User chose to install now." log_metrics "Dialog User Choice Installation: Workflow Elapsed Time" "WorkflowTargetTimestamp" 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." log_metrics "Dialog User Choice Schedule Installation: Workflow Elapsed Time" "WorkflowTargetTimestamp" dialog_user_schedule # This function includes all workflows to facilitate user schedule selection. ;; 4 | 137 | 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 log_metrics "Dialog User Choice Timeout Deferral: Workflow Elapsed Time" "WorkflowTargetTimestamp" 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}" != "Safari Update" ]] && [[ "${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}" == "Safari Update" ]] || [[ "${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=$((workflow_time_epoch + (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}" == "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}, ${display_hide_background}, and ${display_silently} options. { [[ "${display_unmovable_status}" != "TRUE" ]] && [[ $(echo "${display_unmovable}" | grep -c 'DIALOG') -gt 0 ]]; } && display_unmovable_status="TEMP" { [[ "${display_hide_background_status}" != "TRUE" ]] && [[ $(echo "${display_hide_background}" | grep -c 'DIALOG') -gt 0 ]]; } && display_hide_background_status="TEMP" { [[ "${display_silently_status}" != "TRUE" ]] && [[ $(echo "${display_silently}" | 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}, ${display_hide_background}, and ${display_silently} 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=$((workflow_time_epoch + 121)) [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: dialog_user_schedule_selection is: ${dialog_user_schedule_selection}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: dialog_user_schedule_selection_epoch is: ${dialog_user_schedule_selection_epoch}" [[ "${verbose_mode}" == "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}." log_metrics "Dialog User Schedule Set: Workflow Elapsed Time" "WorkflowTargetTimestamp" if [[ -n "${schedule_workflow_active}" ]]; 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 set_pref_main "WorkflowScheduledInstall" "${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." log_metrics "Dialog User Schedule Cancel: Workflow Elapsed Time" "WorkflowTargetTimestamp" dialog_user_choice # This function includes all workflows to facilitate user choices. ;; 4 | 137 | 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." log_metrics "Dialog User Schedule Timeout Deferral: Workflow Elapsed Time" "WorkflowTargetTimestamp" 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}" == "Safari Update" ]] || [[ "${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}" == "TRUE" ]] && log_echo "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: scheduled_install_final_reminder is: ${scheduled_install_final_reminder}" [[ "${verbose_mode}" == "TRUE" ]] && log_echo "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: scheduled_install_user_choice_active is: ${scheduled_install_user_choice_active}" [[ "${verbose_mode}" == "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}" == "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." log_metrics "Dialog Schedule Reminder User Installation: Workflow Elapsed Time" "WorkflowTargetTimestamp" ;; 2) delete_pref_main "WorkflowScheduledInstall" log_super "Status: User chose to reschedule, removing previously scheduled installation and restarting super..." log_metrics "Dialog Schedule Reminder User Reschedule: Workflow Elapsed Time" "WorkflowTargetTimestamp" restart_super_sleep_seconds=1 restart_super ;; 4 | 137 | 255) log_super "Status: Schedule reminder dialog closed due to timeout." log_metrics "Dialog Schedule Reminder Timeout: Workflow Elapsed Time" "WorkflowTargetTimestamp" ;; 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}" == "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." log_metrics "Dialog Schedule Reminder User Dimsissed: Workflow Elapsed Time" "WorkflowTargetTimestamp" ;; 2) delete_pref_main "WorkflowScheduledInstall" log_super "Background Schedule Reminder Dialog: User chose to reschedule, removing previously scheduled installation and restarting super..." log_metrics "Dialog Schedule Reminder User Reschedule: Workflow Elapsed Time" "WorkflowTargetTimestamp" restart_super_sleep_seconds=1 restart_super ;; 4 | 137 | 255) log_super "Background Schedule Reminder Dialog: Closed due to timeout." log_metrics "Dialog Schedule Reminder Timeout: Workflow Elapsed Time" "WorkflowTargetTimestamp" ;; 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}" == "Safari Update" ]] || [[ "${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." log_metrics "Dialog Soft Deadline User Installation: Workflow Elapsed Time" "WorkflowTargetTimestamp" ;; 4 | 137 | 255) log_super "Status: Display timeout automatically chose to restart." log_metrics "Dialog Soft Deadline Timeout Installation: Workflow Elapsed Time" "WorkflowTargetTimestamp" ;; 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 scheduled installation dialog with a ${dialog_timeout_seconds} second timeout." [[ -z "${dialog_timeout_seconds}" ]] && log_super "IBM Notifier: User authentication scheduled 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}, ${display_hide_background}, and ${display_silently} options. { [[ "${display_unmovable_status}" != "TRUE" ]] && [[ $(echo "${display_unmovable}" | grep -c 'DIALOG') -gt 0 ]]; } && display_unmovable_status="TEMP" { [[ "${display_hide_background_status}" != "TRUE" ]] && [[ $(echo "${display_hide_background}" | grep -c 'DIALOG') -gt 0 ]]; } && display_hide_background_status="TEMP" { [[ "${display_silently_status}" != "TRUE" ]] && [[ $(echo "${display_silently}" | 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}, ${display_hide_background}, and ${display_silently} 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}" log_metrics "Dialog User Authentication Successful: Workflow Elapsed Time" "WorkflowTargetTimestamp" if [[ "${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}" == "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}" == "TRUE" ]] && log_echo "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: auth_local_password is: ${auth_local_password}" [[ "${verbose_mode}" == "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}" | 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}" == "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." log_metrics "Dialog User Authentication Timeout: Workflow Elapsed Time" "WorkflowTargetTimestamp" else log_super "Error: User authentication failed." log_metrics "Dialog User Authentication Failed: Workflow Elapsed Time" "WorkflowTargetTimestamp" fi dialog_user_auth_error="TRUE" fi [[ "${verbose_mode}" == "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}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: Setting new workflow_time_epoch to: ${workflow_time_epoch}" [[ -n "${schedule_workflow_active}" ]] && check_schedule_workflow_active # If restarting from a macOS update/upgrade the restart validation 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. delete_pref_main "WorkflowRestartValidate" unset workflow_restart_validate_active set_pref_main "MacLastStartup" "${mac_last_startup}" workflow_time_epoch=$(date +%s) [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: Setting new workflow_time_epoch to: ${workflow_time_epoch}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: Calling reset_workflow() function." reset_workflow # Handle the ${workflow_reset_super_after_completion_active} or ${alternate_temporary_plist} workflow options. if [[ "${workflow_reset_super_after_completion_active}" == "TRUE" ]] || [[ -n "${alternate_temporary_plist}" ]]; then if [[ "${workflow_reset_super_after_completion_active}" == "TRUE" ]]; then delete_pref "WorkflowResetSuperAfterCompletion" set_pref_main "WorkflowResetSuperAfterCompletionNow" "TRUE" log_super "Status: Workflow complete, restarting via LaunchDeamon to fully reset super..." elif [[ -n "${alternate_temporary_plist}" ]]; then delete_pref_main "AlternateTemporaryPlist" log_super "Status: Alternate temporary workflow complete, restarting super to reload workflow settings..." fi 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. if [[ "${workflow_disable_update_check}" == "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 ****" non_system_msu_targets="FALSE" non_system_msu_safari_target="FALSE" macos_msu_major_upgrade_target="FALSE" macos_installer_major_upgrade_target="FALSE" macos_major_upgrade_latest="FALSE" macos_msu_minor_update_target="FALSE" macos_installer_minor_update_target="FALSE" macos_minor_update_latest="FALSE" macos_target_mdm_compatible="FALSE" { [[ "${workflow_disable_relaunch}" != "TRUE" ]] && [[ -z "${install_jamf_policy_triggers}" ]] && [[ "${workflow_restart_without_updates}" != "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}" == "TRUE" ]]; then log_super "Test Mode: Simulating skip updates workflow." if [[ "${install_jamf_policy_triggers_without_restarting}" == "TRUE" ]]; then log_super "Test Mode: Simulating install Jamf Pro Policy Triggers without restarting workflow." elif [[ "${workflow_restart_without_updates}" == "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}" != "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 workflow_check_download_status workflow_time_epoch=$(date +%s) [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: Setting new workflow_time_epoch to: ${workflow_time_epoch}" else # Test mode. macos_installer_major_upgrade_target="FALSE" macos_installer_minor_update_target="FALSE" macos_msu_major_upgrade_target="FALSE" macos_msu_minor_update_target="FALSE" if [[ "${workflow_restart_without_updates}" == "TRUE" ]]; then log_super "Test Mode: Simulating restart without updates workflow." elif [[ "${install_jamf_policy_triggers_without_restarting}" == "TRUE" ]]; then log_super "Test Mode: Simulating install Jamf Pro Policy Triggers without restarting workflow." elif [[ "${install_non_system_updates_without_restarting}" == "TRUE" ]]; then non_system_msu_targets="TRUE" log_super "Test Mode: Simulating install non-system updates workflow." elif [[ "${install_safari_updates_without_restarting}" == "TRUE" ]]; then non_system_msu_targets="TRUE" non_system_msu_safari_target="Safari 18.x" log_super "Test Mode: Simulating install Safari update workflow." elif [[ "${install_macos_major_upgrades}" == "TRUE" ]]; then if [[ $macos_version_normalized -lt 120300 ]]; then macos_installer_major_upgrade_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}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_installer_major_upgrade_target is: ${macos_installer_major_upgrade_target}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_installer_minor_update_target is: ${macos_installer_minor_update_target}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_msu_major_upgrade_target is: ${macos_msu_major_upgrade_target}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: macos_msu_minor_update_target is: ${macos_msu_minor_update_target}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: non_system_msu_targets is: ${non_system_msu_targets}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: non_system_msu_safari_target is: ${non_system_msu_safari_target}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: install_prioritize_non_restart_updates is: ${install_prioritize_non_restart_updates}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: install_jamf_policy_triggers_without_restarting is: ${install_jamf_policy_triggers_without_restarting}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: workflow_restart_without_updates is: ${workflow_restart_without_updates}" 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 [[ "${install_prioritize_non_restart_updates}" == "TRUE" ]] && [[ "${non_system_msu_targets}" == "TRUE" ]]; then [[ "${macos_installer_major_upgrade_target}" != "FALSE" ]] && log_super "Warning: Non-system update workflow is being prioritized over ${macos_installer_title} ${macos_installer_version}-${macos_build} major upgrade via installer." [[ "${macos_installer_minor_update_target}" != "FALSE" ]] && log_super "Warning: Non-system update workflow is being prioritized over ${macos_installer_title} ${macos_installer_version}-${macos_build} minor update via installer." [[ "${macos_msu_major_upgrade_target}" != "FALSE" ]] && log_super "Warning: Non-system update workflow is being prioritized over ${macos_msu_title} ${macos_msu_version}-${macos_msu_build} major upgrade via softwareupdate." [[ "${macos_msu_minor_update_target}" != "FALSE" ]] && log_super "Warning: Non-system update workflow is being prioritized over ${macos_msu_title} ${macos_msu_version}-${macos_msu_build} minor update via softwareupdate." macos_installer_major_upgrade_target="FALSE" macos_installer_minor_update_target="FALSE" macos_msu_major_upgrade_target="FALSE" macos_msu_minor_update_target="FALSE" if [[ "${install_safari_updates_without_restarting}" == "TRUE" ]] && [[ "${non_system_msu_safari_target}" != "FALSE" ]]; then workflow_target="Safari Update" workflow_target_log_string="SAFARI UPDATE PRIORITY" [[ -n "${display_accessory_safari_update}" ]] && display_accessory_content="${display_accessory_safari_update}" [[ "${install_non_system_updates_without_restarting}" == "TRUE" ]] && log_super "Warning: The --install-safari-updates-without-restarting option is currently overriding the --install-non-system-updates-without-restarting option." [[ "${workflow_only_download_active}" == "TRUE" ]] && log_super "Warning: The --install-safari-updates-without-restarting option is currently overriding the --only-download option." elif [[ "${install_non_system_updates_without_restarting}" == "TRUE" ]]; then workflow_target="Non-system Software Updates" workflow_target_log_string="NON-SYSTEM SOFTWARE UPDATES PRIORITY" [[ -n "${display_accessory_non_system_updates}" ]] && display_accessory_content="${display_accessory_non_system_updates}" [[ "${workflow_only_download_active}" == "TRUE" ]] && log_super "Warning: The --install-non-system-updates-without-restarting option is currently overriding the --only-download option." fi workflow_macos_auth="FALSE" [[ "${workflow_only_download_active}" == "TRUE" ]] && workflow_only_download_active="FALSE" elif [[ "${macos_installer_major_upgrade_target}" != "FALSE" ]] || [[ "${macos_installer_minor_update_target}" != "FALSE" ]]; then workflow_target="${macos_installer_title} ${macos_installer_version}-${macos_build}" if [[ "${macos_installer_major_upgrade_target}" != "FALSE" ]]; then workflow_target_log_string="${workflow_target} MAJOR UPGRADE VIA INSTALLER" [[ -n "${display_accessory_macos_major_upgrade}" ]] && display_accessory_content="${display_accessory_macos_major_upgrade}" else # ${macos_installer_minor_update_target} workflow_target_log_string="${workflow_target} MINOR UPDATE VIA INSTALLER" [[ -n "${display_accessory_macos_minor_update}" ]] && display_accessory_content="${display_accessory_macos_minor_update}" fi if [[ "${non_system_msu_targets}" == "TRUE" ]]; then log_super "Status: Non-system updates will be installed immediately after the system restarts from the pending macOS major upgrade." if [[ "${install_safari_updates_without_restarting}" == "TRUE" ]] && [[ "${non_system_msu_safari_target}" != "FALSE" ]]; then log_super "Warning: To force Safari updates to install before a macOS update/upgrade then you must also enable the --install-prioritize-non-restart-updates option." elif [[ "${install_non_system_updates_without_restarting}" == "TRUE" ]]; then log_super "Warning: To force non-system updates to install before a macOS update/upgrade then you must also enable the --install-prioritize-non-restart-updates option." fi 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" [[ -n "${display_accessory_macos_major_upgrade}" ]] && display_accessory_content="${display_accessory_macos_major_upgrade}" if [[ "${non_system_msu_targets}" == "TRUE" ]]; then log_super "Status: Non-system updates will be installed immediately after the system restarts from the pending macOS major upgrade." if [[ "${install_safari_updates_without_restarting}" == "TRUE" ]] && [[ "${non_system_msu_safari_target}" != "FALSE" ]]; then log_super "Warning: To force Safari updates to install before a macOS update/upgrade then you must also enable the --install-prioritize-non-restart-updates option." elif [[ "${install_non_system_updates_without_restarting}" == "TRUE" ]]; then log_super "Warning: To force non-system updates to install before a macOS update/upgrade then you must also enable the --install-prioritize-non-restart-updates option." fi fi elif [[ "${macos_msu_minor_update_target}" != "FALSE" ]]; then workflow_target="${macos_msu_title} ${macos_msu_version}-${macos_msu_build}" workflow_target_log_string="${workflow_target} MINOR UPDATE VIA SOFTWAREUPDATE" [[ -n "${display_accessory_macos_minor_update}" ]] && display_accessory_content="${display_accessory_macos_minor_update}" if [[ "${non_system_msu_targets}" == "TRUE" ]]; then log_super "Status: Non-system updates will be installed immediately after the system restarts from the pending macOS minor update." if [[ "${install_safari_updates_without_restarting}" == "TRUE" ]] && [[ "${non_system_msu_safari_target}" != "FALSE" ]]; then log_super "Warning: To force Safari updates to install before a macOS update/upgrade then you must also enable the --install-prioritize-non-restart-updates option." elif [[ "${install_non_system_updates_without_restarting}" == "TRUE" ]]; then log_super "Warning: To force non-system updates to install before a macOS update/upgrade then you must also enable the --install-prioritize-non-restart-updates option." fi fi elif [[ "${non_system_msu_targets}" == "TRUE" ]]; then if [[ "${install_safari_updates_without_restarting}" == "TRUE" ]] && [[ "${non_system_msu_safari_target}" != "FALSE" ]]; then workflow_target="Safari Update" workflow_target_log_string="SAFARI UPDATE PRIORITY" [[ -n "${display_accessory_safari_update}" ]] && display_accessory_content="${display_accessory_safari_update}" [[ "${workflow_only_download_active}" == "TRUE" ]] && log_super "Warning: The --install-safari-updates-without-restarting option is currently overriding the --only-download option." elif [[ "${install_non_system_updates_without_restarting}" == "TRUE" ]]; then workflow_target="Non-system Software Updates" workflow_target_log_string="NON-SYSTEM SOFTWARE UPDATES PRIORITY" [[ -n "${display_accessory_non_system_updates}" ]] && display_accessory_content="${display_accessory_non_system_updates}" [[ "${workflow_only_download_active}" == "TRUE" ]] && log_super "Warning: The --install-non-system-updates-without-restarting option is currently overriding the --only-download option." else 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 or the --install-safari-updates-without-restarting option to install non-system items even if there is no macOS update/upgrade available." fi workflow_macos_auth="FALSE" [[ "${workflow_only_download_active}" == "TRUE" ]] && workflow_only_download_active="FALSE" elif [[ "${install_jamf_policy_triggers_without_restarting}" == "TRUE" ]]; then workflow_target="Jamf Pro Policy Triggers Without Restarting" workflow_target_log_string="JAMF PRO POLICY TRIGGERS WITHOUT RESTARTING" [[ -n "${display_accessory_jamf_policy_triggers}" ]] && display_accessory_content="${display_accessory_jamf_policy_triggers}" 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 elif [[ "${workflow_restart_without_updates}" == "TRUE" ]]; then if [[ -n "${install_jamf_policy_triggers}" ]]; then workflow_target="Jamf Pro Policy Triggers With Restart" workflow_target_log_string="JAMF PRO POLICY TRIGGERS WITH RESTART" [[ -n "${display_accessory_jamf_policy_triggers}" ]] && display_accessory_content="${display_accessory_jamf_policy_triggers}" else workflow_target="Restart Without Updates" workflow_target_log_string="RESTART WITHOUT UPDATES/UPGRADES" [[ -n "${display_accessory_restart_without_updates}" ]] && display_accessory_content="${display_accessory_restart_without_updates}" fi 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 fi { [[ -z "${display_accessory_content}" ]] && [[ -n "${display_accessory_default}" ]]; } && display_accessory_content="${display_accessory_default}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: workflow_target is: ${workflow_target}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: display_accessory_content is: ${display_accessory_content}" # Log any changes regarding the workflow target. workflow_target_previous=$(get_pref "WorkflowTarget") [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: workflow_target_previous is: ${workflow_target_previous}" if [[ "${workflow_target}" != "FALSE" ]] && [[ -z "${workflow_target_previous}" ]]; then log_super_audit "Status: Setting new workflow target to ${workflow_target}". log_metrics "New Workflow Target" set_pref_main "WorkflowTargetTimestamp" "$(date -r "${workflow_time_epoch}" +%Y-%m-%d:%H:%M:%S)" fi if [[ "${workflow_target}" != "FALSE" ]] && [[ -n "${workflow_target_previous}" ]] && [[ "${workflow_target}" != "${workflow_target_previous}" ]]; then log_super_audit "Warning: Previous workflow target of ${workflow_target_previous} has been changed to ${workflow_target}." log_metrics "Replaced Previous Workflow Target" set_pref_main "WorkflowTargetTimestamp" "$(date -r "${workflow_time_epoch}" +%Y-%m-%d:%H:%M:%S)" fi # If the workflow target is only Safari, check to see if Safari is active or in test mode. If not, force an Install Now workflow. if [[ "${workflow_target}" == "Safari Update" ]]; then if [[ "${current_user_account_name}" != "FALSE" ]] && { [[ $(pgrep '^Safari$') ]] || [[ "${test_mode}" == "TRUE" ]]; }; then workflow_target_safari_active="TRUE" else log_super "Status: Safari is not currently in use and can be installed immediately, forcing the Install Now workflow." workflow_target_safari_active="FALSE" workflow_install_now_active="TRUE" workflow_install_now_suppress_notifications="TRUE" fi [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: workflow_target_safari_active is: ${workflow_target_safari_active}" [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: workflow_install_now_suppress_notifications is: ${workflow_install_now_suppress_notifications}" fi # Check for zero date, scheduled installation, 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 set_pref_main "WorkflowTarget" "${workflow_target}" else # No software updates/upgrade needed so reset any leftovers. { [[ "${workflow_target_previous}" != "FALSE" ]] && [[ "${verbose_mode}" == "TRUE" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: Calling reset_workflow() function." [[ "${workflow_target_previous}" != "FALSE" ]] && reset_workflow workflow_target="FALSE" fi # If needed, handle ${schedule_deferred_start_days_active} before the any installation or restart workflow starts. if [[ "${workflow_target}" != "FALSE" ]] && [[ "${schedule_deferred_start_days_active}" == "TRUE" ]]; then if [[ "${workflow_install_now_active}" == "TRUE" ]]; then log_super "Warning: The --workflow-install-now option is currently overriding the schedule deferred start." elif [[ "${workflow_scheduled_install_active}" == "TRUE" ]]; then [[ "${workflow_scheduled_install_now}" == "TRUE" ]] && log_super "Warning: A scheduled installation is currently overriding the schedule deferred start." [[ "${workflow_scheduled_install_now}" != "TRUE" ]] && log_super "Warning: A scheduled installation reminder is currently overriding the schedule deferred start." else # Schedule deferred start should force a deferral. deferral_timer_minutes=$(((schedule_zero_date_deferred_start_epoch - workflow_time_epoch) / 60 )) log_super "Status: Automatic deferred start until ${schedule_zero_date_deferred_start_date}, deferring for ${deferral_timer_minutes} minutes." log_status "Pending: Automatic deferred start until ${schedule_zero_date_deferred_start_date}, deferring for ${deferral_timer_minutes} minutes." set_auto_launch_deferral fi fi # If set, handle the ${schedule_workflow_active} before the any installation or restart workflow starts. if [[ "${workflow_target}" != "FALSE" ]] && [[ -n "${schedule_workflow_active}" ]]; 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 # Double-check for local user, and handle the ${workflow_require_active_user}. [[ "${current_user_account_name}" == "FALSE" ]] && check_current_user if [[ "${current_user_account_name}" == "FALSE" ]] && [[ "${workflow_require_active_user}" == "TRUE" ]]; then deferral_timer_minutes=$deferral_timer_error_minutes log_super "Error: No currently logged in user and workflow require active user is enabled, trying again in ${deferral_timer_minutes} minutes." log_status "Pending: No currently logged in user and workflow require active user is enabled, trying again in ${deferral_timer_minutes} minutes." set_auto_launch_deferral 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}" == "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}" | 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}" != "Safari Update" ]] && [[ "${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" ]] && [[ "${macos_msu_automatic_download_deferral}" == "FALSE" ]]; then # A normal user is not logged in and the system can not automatically download the update, the only download workflow can not continue. deferral_timer_minutes=$deferral_timer_error_minutes log_super "Error: No current active user and automatic macOS update downloads are disabled, 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 and automatic macOS update downloads are disabled, 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 or automatic macOS update downloads is possible. 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}" == "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. # Double-check for local user, and if recently started up then wait until later. [[ "${current_user_account_name}" == "FALSE" ]] && check_current_user if [[ "${current_user_account_name}" == "FALSE" ]] && [[ $((workflow_time_epoch - kernel_boot_epoch)) -lt $RECENT_STARTUP_AUTO_DEFERRAL_SECONDS ]]; then deferral_timer_minutes=$deferral_timer_error_minutes log_super "Error: The system started up less than $RECENT_STARTUP_AUTO_DEFERRAL_SECONDS seconds ago so it's unsafe to assume current active user status, trying again in ${deferral_timer_minutes} minutes." log_status "Pending: The system started up less than $RECENT_STARTUP_AUTO_DEFERRAL_SECONDS seconds ago so it's unsafe to assume current active user status, trying again in ${deferral_timer_minutes} minutes." set_auto_launch_deferral fi # At this point pick a workflow based on if the user is active. 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 check for deadlines and if necessary download macOS update/upgrade. check_deadlines_days_date { [[ "${deadline_date_status}" == "FALSE" ]] && [[ "${deadline_days_status}" == "FALSE" ]]; } && check_user_focus { [[ "${deadline_date_status}" != "FALSE" ]] || [[ "${deadline_days_status}" != "FALSE" ]]; } && user_focus_active="FALSE" check_deadlines_count { [[ "${workflow_target}" != "Non-system Software Updates" ]] && [[ "${workflow_target}" != "Safari Update" ]] && [[ "${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. if [[ "${workflow_download_macos_check_user}" == "TRUE" ]]; then # The download may have taken a while so another check to make sure the user is still logged in. check_current_user workflow_time_epoch=$(date +%s) [[ "${verbose_mode}" == "TRUE" ]] && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: Setting new workflow_time_epoch to: ${workflow_time_epoch}" if [[ "${current_user_account_name}" == "FALSE" ]]; then deferral_timer_minutes=$deferral_timer_error_minutes log_super "Error: The active user logged out while the update was downloading, trying again in ${deferral_timer_minutes} minutes." log_status "Pending: The active user logged out while the update was downloading, trying again in ${deferral_timer_minutes} minutes." set_auto_launch_deferral fi fi # 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}" | grep -c 'DEADLINE') -gt 0 ]] && display_unmovable_status="TRUE" [[ $(echo "${display_hide_background}" | grep -c 'DEADLINE') -gt 0 ]] && display_hide_background_status="TRUE" [[ $(echo "${display_silently}" | grep -c 'DEADLINE') -gt 0 ]] && display_silently_status="TRUE" [[ $(echo "${display_hide_progress_bar}" | grep -c 'DEADLINE') -gt 0 ]] && display_hide_progress_bar_status="TRUE" [[ $(echo "${display_notifications_centered}" | grep -c 'DEADLINE') -gt 0 ]] && display_notifications_centered_status="TRUE" [[ $(echo "${auth_mdm_failover_to_user}" | 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_MAIN_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. { [[ "${workflow_target}" != "FALSE" ]] && [[ "${verbose_mode}" == "TRUE" ]]; } && log_super "Verbose Mode: Function ${FUNCNAME[0]}: Line ${LINENO}: Calling reset_workflow() function." [[ "${workflow_target}" != "FALSE" ]] && reset_workflow # 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" ]] && [[ "${workflow_install_now_suppress_notifications}" != "TRUE" ]]; } && notification_install_now_up_to_date if [[ "${test_mode}" == "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_normalized != "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}" == "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} or ${alternate_temporary_plist} workflow options. if [[ "${workflow_reset_super_after_completion_active}" == "TRUE" ]] || [[ -n "${alternate_temporary_plist}" ]]; then if [[ "${workflow_reset_super_after_completion_active}" == "TRUE" ]]; then delete_pref "WorkflowResetSuperAfterCompletion" set_pref_main "WorkflowResetSuperAfterCompletionNow" "TRUE" log_super "Status: Workflow complete, restarting via LaunchDeamon to fully reset super..." elif [[ -n "${alternate_temporary_plist}" ]]; then delete_pref_main "AlternateTemporaryPlist" log_super "Status: Alternate temporary workflow complete, restarting super to reload workflow settings..." fi restart_super_sleep_seconds=5 restart_super fi # Logic for ${workflow_disable_relaunch} and ${deferral_timer_workflow_relaunch_minutes}. if [[ "${workflow_disable_relaunch}" == "TRUE" ]]; then log_super_audit "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_MAIN_PLIST}.plist" 2> /dev/null else # Default super workflow automatically relaunches. deferral_timer_minutes=$deferral_timer_workflow_relaunch_minutes log_super_audit "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