Automated macOS Sequoia Upgrade Script – Seamless Upgrades with System Checks and User Interaction

kishoth_p
New Contributor III

Hello Mac Admins!

Not a shell script expert!! But still managed to achieve this comprehensive script. Give it a shot and let me know how it performs in your environment. As always, feedback is welcome!

I’m excited to share a robust, advanced macOS Sequoia upgrade automation script tailored for enterprise environments. This script ensures a seamless upgrade experience by performing pre-upgrade checks, deferral management, Secure Token validation, and notifying users at every step to keep them informed. It has been designed to address common challenges in macOS upgrades and provides full automation with error handling to reduce user friction and IT overhead.

This solution leverages JAMF Pro to manage the upgrade workflow and works well for both JAMF Self Service policies and automated deployments


#!/bin/bash

LOGFILE="/var/log/com.scb.sequoia_update.log"
DEFERRAL_FILE="/Library/Preferences/com.scb.sequoia_update_deferral.plist"
MAX_DEFERRAL_DAYS=1
CURRENT_DATE=$(date +%Y-%m-%d)
CURRENT_EPOCH=$(date +%s)
CURRENT_USER=$(stat -f "%Su" /dev/console)

# Function to log messages to file and JAMF Pro logs
logMessage() {
echo "$(date) - $1" | tee -a "$LOGFILE"
}

# Function to notify the user via AppleScript with enhanced messaging
notifyUser() {
local message="$1"
local title="$2"
CURRENT_USER_ID=$(id -u "$CURRENT_USER")
launchctl asuser "$CURRENT_USER_ID" osascript -e "display notification \"$message\" with title \"$title\" sound name \"default\""
}

# System condition checks for battery and free space with improved messaging
checkSystemConditions() {
logMessage "Performing system checks for upgrade readiness..."

# Check if the device is a MacBook (portable)
IS_PORTABLE=$(pmset -g batt | grep -c "Battery")

if [[ "$IS_PORTABLE" -eq 1 ]]; then
BATTERY_PERCENT=$(pmset -g batt | grep -Eo "\d+%" | cut -d% -f1)
POWER_SOURCE=$(pmset -g batt | grep "AC Power")

if [[ "$BATTERY_PERCENT" -lt 20 && -z "$POWER_SOURCE" ]]; then
logMessage "Battery below 20% and not connected to AC. Pausing upgrade."
notifyUser "Battery level is critically low (<20%) and your Mac is not connected to a power source. Please connect your Mac to proceed with the upgrade." "Upgrade Paused"

while [[ -z $(pmset -g batt | grep "AC Power") ]]; do
sleep 60 # Check every minute
logMessage "Waiting for AC power connection..."
done

logMessage "AC power connected. Resuming upgrade."
notifyUser "Your Mac is now connected to power. The macOS Sequoia upgrade will resume." "Upgrade Resumed"
else
logMessage "Battery level sufficient for upgrade."
fi
else
logMessage "Non-portable device detected. Skipping battery checks."
fi

# Check disk space
FREE_SPACE=$(df / | tail -1 | awk '{print $4}')
if [[ "$FREE_SPACE" -lt 10000000 ]]; then
logMessage "Insufficient disk space. Aborting upgrade."
notifyUser "Your Mac has less than 10GB of free space, which is required for the upgrade. Please free up space and try again." "Upgrade Aborted"
exit 1
fi

logMessage "System conditions met for the upgrade."
}

# Function to check Secure Token status with improved feedback
checkSecureToken() {
TOKEN_STATUS=$(sysadminctl -secureTokenStatus "$CURRENT_USER" 2>&1)
if [[ "$TOKEN_STATUS" == *"DISABLED"* ]]; then
logMessage "Secure Token is disabled for $CURRENT_USER. Elevation issues may occur."
notifyUser "Your account does not have a Secure Token, which may cause issues during the upgrade. Please contact IT support for assistance." "Secure Token Issue"
exit 1
fi
logMessage "Secure Token is enabled for $CURRENT_USER."
}

# Function to check FileVault status with appropriate logging
checkFileVault() {
FV_STATUS=$(fdesetup status)
if [[ "$FV_STATUS" == *"FileVault is On."* ]]; then
logMessage "FileVault is enabled. Proceeding with caution."
fi
}

# Function to securely prompt for user password with error handling
promptForPassword() {
CURRENT_USER_ID=$(id -u "$CURRENT_USER")
USER_PASSWORD=$(launchctl asuser "$CURRENT_USER_ID" osascript -e 'display dialog "Please enter your macOS password to authorize the update." default answer "" with hidden answer' -e 'text returned of result' 2>/dev/null)

if [[ -z "$USER_PASSWORD" ]]; then
logMessage "No password entered. Exiting the update process."
notifyUser "No password entered. The update process has been cancelled." "Update Cancelled"
exit 1
fi
}

# Function to check for available macOS updates with enhanced logic
checkForUpdates() {
LABEL="macOS Sequoia"
AVAILABLE_UPDATES=()

logMessage "Checking for macOS Sequoia updates."

FULL_LABEL=$(softwareupdate --list | grep -A1 "$LABEL" | grep 'Label' | grep -v "Sonoma" | awk -F': ' '{print $2}' | sed 's/^[ \t]*//;s/[ \t]*$//')

if [[ -n "$FULL_LABEL" ]]; then
logMessage "$FULL_LABEL is available for update."
AVAILABLE_UPDATES+=("$FULL_LABEL")
else
logMessage "No updates available for $LABEL."
notifyUser "Your macOS Sequoia is already up to date." "Update Status"
exit 0
fi
}

# Function to prompt user for deferral or installation with more detailed choices
promptUser() {
CURRENT_USER_ID=$(id -u "$CURRENT_USER")
USER_CHOICE=$(launchctl asuser "$CURRENT_USER_ID" osascript -e 'display dialog "An important update is available for macOS Sequoia. Would you like to install it now or defer for later?" buttons {"Defer", "Install Now"} default button "Install Now"')

if [[ "$USER_CHOICE" == "button returned:Defer" ]]; then
logMessage "User chose to defer the update."
handleDeferrals
else
logMessage "User chose to install the update immediately."
installMajorUpdates
fi
}

# Function to handle deferral logic with advanced deferral prompts
handleDeferrals() {
if [[ ! -f "$DEFERRAL_FILE" ]]; then
logMessage "No deferral file found, creating a new one."
defaults write "$DEFERRAL_FILE" deferralDate "$CURRENT_DATE"
fi

LAST_DEFERRAL=$(defaults read "$DEFERRAL_FILE" deferralDate)
LAST_DEFERRAL_EPOCH=$(date -j -f "%Y-%m-%d" "$LAST_DEFERRAL" +%s)
DEFERRAL_DIFF=$(( (CURRENT_EPOCH - LAST_DEFERRAL_EPOCH) / 86400 ))

if [[ "$DEFERRAL_DIFF" -ge "$MAX_DEFERRAL_DAYS" ]]; then
logMessage "Deferral period exceeded. Proceeding with the update."
notifyUser "Your deferral period has expired. The macOS Sequoia update will now be installed." "Update in Progress"
installMajorUpdates
else
REMAINING_DAYS=$((MAX_DEFERRAL_DAYS - DEFERRAL_DIFF))
logMessage "Deferral active. $REMAINING_DAYS days remaining."
notifyUser "You have deferred the macOS Sequoia update. You have $REMAINING_DAYS day(s) remaining." "Update Deferred"
exit 0
fi
}

# Function to install macOS Sequoia updates with robust feedback mechanisms
installMajorUpdates() {
promptForPassword
for label in "${AVAILABLE_UPDATES[@]}"; do
logMessage "Starting installation of $label..."
notifyUser "Installing $label. This may take some time." "Update in Progress"

CURRENT_USER_ID=$(id -u "$CURRENT_USER")
echo "$USER_PASSWORD" | launchctl asuser "$CURRENT_USER_ID" sudo -S softwareupdate --install "$label" --restart --no-scan --agree-to-license --stdinpass --user "$CURRENT_USER" >> "$LOGFILE" 2>&1

if [[ $? -eq 0 ]]; then
logMessage "$label installed successfully."
notifyUser "Installation of $label completed. Your Mac will restart shortly." "Update Successful"
else
logMessage "Failed to install $label."
notifyUser "The installation of $label failed. Please contact IT for assistance." "Update Failed"
exit 1
fi
done

logMessage "macOS Sequoia update process completed."
}

# Main execution
logMessage "Starting macOS Sequoia update process..."
checkSystemConditions
checkSecureToken
checkFileVault
checkForUpdates
promptUser


Troubleshooting Tips

  • If the installer isn’t detected: Ensure the macOS Sequoia installer is available with softwareupdate --list.
  • Secure Token issues: Advise users to contact IT if they lack Secure Token privileges.
  • Use JAMF Pro logs for detailed insights in case of failures.

Conclusion

This macOS Sequoia Upgrade Automation Script simplifies and streamlines the upgrade process while ensuring compliance and transparency throughout. It’s designed for smooth execution with JAMF Pro, making upgrades less cumbersome for both IT teams and end users.

Give it a try, and feel free to share your feedback and suggestions here! If you run into any issues, I’ll be happy to assist.

Looking forward to hearing how this performs in your environment! 🚀



1 REPLY 1

AJPinto
Honored Contributor III

The script would not pass the security check of a lot of enterprise organizations as you are prompting for the user's password to reuse later. Something a simple as echo $userpassword would put the password for every user that ran this in to the log for the policy, or could be modified to output to a file. I have used similar in the past, but in the end I gave up and just use Apples MDM workflow. The scripting is decent and certainly not what I would call novice level so kudos on that, messing with secure tokens is no easy task.

 

#!/bin/bash
echo "A tip, next time you share a script use code blocks :)."