Files
MacAdmin/Jamf Pro/Extension Attributes/Get-LatestOSSupported.sh
Zack T 5a1e2dee96 Bump version string
And add Tahoe URL
2025-10-03 14:44:20 -07:00

545 lines
19 KiB
Bash

#!/bin/bash
# set -x
####################################################################################################
# Script Name: Get-LatestOSSupported.sh
# By: Zack Thompson / Created: 9/26/2017
# Version: 2.7.0 / Updated: 6/11/2025 / By: @HowardGMac
#
# Description: A Jamf Pro Extension Attribute to check the latest compatible version of macOS.
#
# New Feature: When running as standalone script, you can pass a Model number and OS version
# to the script to validate regex.
# EXAMPLE
# $ bash ./Get-LatestOSSupported.sh "MacBookPro13,3" "12.0"
# Test OS value: 12.0
# Test Model value: MacBookPro13,3
# <result>Monterey</result>
#
# Courtesy of Nic Wendlowsky (@hkystar35)
#
# Updates: For each OS version released, a new Regex string and each function will need to be
# updated.
#
# System Requirements can be found here:
# Tahoe - https://support.apple.com/en-us/122867
# Sequoia - https://support.apple.com/en-us/120282
# Sonoma - https://support.apple.com/en-us/105113
# Ventura - https://support.apple.com/en-us/102861
# * Apple has never publicly posted storage requirements for Ventura, which is why this
# script identifies Ventura support with an asterisk, e.g. `Ventura*`
# Monterey - https://support.apple.com/en-us/HT212551
# Big Sur - https://support.apple.com/en-us/HT211238 / https://support.apple.com/kb/sp833
# * If running Mountain Lion 10.8, device will need to upgrade to El Capitan 10.11 first.
# first. See:
# * https://web.archive.org/web/20211018042220/https://www.apple.com/macos/how-to-upgrade/
# Catalina - https://support.apple.com/en-us/HT210222 / https://support.apple.com/kb/SP803
# Mojave - https://support.apple.com/kb/SP777
# * MacPro5,1's = https://support.apple.com/en-us/HT208898
# High Sierra - https://support.apple.com/kb/SP765
# Sierra - https://support.apple.com/kb/sp742
# El Capitan - https://support.apple.com/kb/sp728
#
####################################################################################################
##################################################
# Define test values
# Allow for specifying a Model and OS Version to the script to validate regex.
# If these values are passed, the write_to_ea_history function is ignored so that incorrect
# information is not written to the test computer.
# Supported actions:
# TEST_MODEL - example: "MacBookPro13,3"
# TEST_OS - example: "14.0"
TEST_MODEL="${1}"
TEST_OS="${2}"
##################################################
# Define organization's environment values
# Locally log EA value which can be collected with a simple `defaults read` allowing this script
# to be ran from a Policy or other method, instead of an actual EA. Also allows the reported
# value to be collected and used within other scripts/workflows.
# Supported actions:
# true - Do locally Log
# false - Do not log locally
locally_log="true"
local_inventory="/opt/ManagedFrameworks/Inventory.plist"
##################################################
# Define Regex Strings to exclude Mac Models that *do not support* each OS Version
not_elcapitan_or_older_regex="^((MacPro|Macmini|MacBookPro)[1-2],[0-9]|iMac[1-6],[0-9]|MacBook[1-4],[0-9]|MacBookAir1,[0-9])$"
not_highsierra_regex="^(MacPro[1-4],[0-9]|iMac[1-9],[0-9]|Macmini[1-3],[0-9]|(MacBook|MacBookPro)[1-5],[0-9]|MacBookAir[1-2],[0-9])$"
not_mojave_regex="^(MacPro[1-4],[0-9]|iMac([1-9]|1[0-2]),[0-9]|Macmini[1-5],[0-9]|MacBook[1-7],[0-9]|MacBookAir[1-4],[0-9]|MacBookPro[1-8],[0-9])$"
not_catalina_regex="^(MacPro[1-5],[0-9]|iMac([1-9]|1[0-2]),[0-9]|Macmini[1-5],[0-9]|MacBook[1-7],[0-9]|MacBookAir[1-4],[0-9]|MacBookPro[1-8],[0-9])$"
not_bigsur_regex="^(MacPro[1-5],[0-9]|iMac((([1-9]|1[0-3]),[0-9])|14,[0-3])|Macmini[1-6],[0-9]|MacBook[1-7],[0-9]|MacBookAir[1-5],[0-9]|MacBookPro([1-9]|10),[0-9])$"
not_monterey_regex="^(MacPro[1-5],[0-9]|iMac([1-9]|1[0-5]),[0-9]|(Macmini|MacBookAir)[1-6],[0-9]|MacBook[1-8],[0-9]|MacBookPro(([1-9]|10),[0-9]|11,[0-3]))$"
not_ventura_regex="^(MacPro[1-6],[0-9]|iMac([1-9]|1[0-7]),[0-9]|(Macmini|MacBookAir)[1-7],[0-9]|MacBook[1-9],[0-9]|MacBookPro([1-9]|1[0-3]),[0-9])$"
not_sonoma_regex="^(MacPro[1-6],[0-9]|iMac([1-9]|1[0-8]),[0-9]|(Macmini|MacBookAir)[1-7],[0-9]|MacBook[0-9,]+|MacBookPro([1-9]|1[0-4]),[0-9])$"
not_sequoia_regex="^(MacPro[1-6],[0-9]|iMac([1-9]|1[0-8]),[0-9]|Macmini[1-7],[0-9]|MacBookAir[1-8],[0-9]|MacBookPro([1-9]|1[0-4]),[0-9])$"
not_tahoe_regex="^(MacPro[1-6],[0-9]|iMac([1-9]|1[0-9]),[0-9]|iMacPro1,1|Macmini[1-8],[0-9]|MacBookAir[1-9],[0-9]|MacBookPro((16,3)|([1-9]|1[0-5]),[0-9]))$"
##################################################
# Setup Functions
write_to_ea_history() {
# Arguments
# $1 = (str) Plist key that the message value will be assigned too
# $2 = (str) Message that will be assigned to the key
local key="${1}"
local value="${2}"
if [[ "${locally_log}" == "true" && -z "${TEST_OS}" && -z "${TEST_MODEL}" ]]; then
if [[ ! -e "${local_inventory}" ]]; then
/bin/mkdir -p "$( /usr/bin/dirname "${local_inventory}" )"
/usr/bin/touch "${local_inventory}"
fi
/usr/bin/defaults write "${local_inventory}" "${key}" "${value}"
fi
}
model_check() {
# Arguments
# $1 = Mac Model Identifier
local model="${1}"
if [[ $model =~ $not_elcapitan_or_older_regex || $model =~ ^Xserve.*$ ]]; then
echo "<result>Current Model Not Supported</result>"
exit 0
elif [[ $model =~ $not_highsierra_regex ]]; then
echo "El Capitan"
elif [[ $model =~ $not_mojave_regex ]]; then
echo "High Sierra"
elif [[ $model =~ $not_catalina_regex ]]; then
echo "Mojave"
elif [[ $model =~ $not_bigsur_regex ]]; then
echo "Catalina"
elif [[ $model =~ $not_monterey_regex ]]; then
echo "Big Sur"
elif [[ $model =~ $not_ventura_regex ]]; then
echo "Monterey"
elif [[ $model =~ $not_sonoma_regex ]]; then
echo "Ventura*"
elif [[ $model =~ $not_sequoia_regex ]]; then
echo "Sonoma*"
elif [[ $model =~ $not_tahoe_regex ]]; then
echo "Sequoia*"
else
echo "Tahoe*"
fi
}
os_check() {
# Arguments
# $1 = Max supported OS version based on hardware model
# $2 = Current OS major version
# $3 = Current OS minor version
# $4 = Current OS patch version
local validate_os="${1}"
local os_major="${2}"
local os_minor="${3}"
local os_patch="${4}"
if [[ ! "${mac_model}" =~ ^MacPro5,1$ ]]; then
# For all models except MacPro5,1...
if [[
"${validate_os}" == "Tahoe*" && \
( "${os_major}" -ge 11 || "${os_major}" -eq 10 && "${os_minor}" -ge 9 )
]]; then
echo "Tahoe*"
elif [[
"${validate_os}" == "Sequoia*" && \
( "${os_major}" -ge 11 || "${os_major}" -eq 10 && "${os_minor}" -ge 9 )
]]; then
echo "Sequoia*"
elif [[
"${validate_os}" == "Sonoma*" && \
( "${os_major}" -ge 11 || "${os_major}" -eq 10 && "${os_minor}" -ge 9 )
]]; then
echo "Sonoma*"
elif [[
"${validate_os}" == "Ventura*" && \
( "${os_major}" -ge 11 || "${os_major}" -eq 10 && "${os_minor}" -ge 9 )
]]; then
echo "Ventura*"
elif [[
"${validate_os}" == "Monterey" && \
( "${os_major}" -ge 11 || "${os_major}" -eq 10 && "${os_minor}" -ge 9 )
]]; then
echo "Monterey"
elif [[
"${validate_os}" == "Big Sur" && \
( "${os_major}" -ge 11 || "${os_major}" -eq 10 && "${os_minor}" -ge 9 )
]]; then
echo "Big Sur"
elif [[
"${validate_os}" == "Big Sur" && \
( "${os_major}" -ge 11 || "${os_major}" -eq 10 && "${os_minor}" -le 8 )
]]; then
echo "El Capitan / OS Limitation"
elif [[
"${validate_os}" == "Catalina" && "${os_major}" -eq 10 && "${os_minor}" -ge 9
]]; then
echo "Catalina"
elif [[
"${validate_os}" == "Catalina" && "${os_major}" -eq 10 && "${os_minor}" -le 8
]]; then
echo "Mojave / OS Limitation" # (Current OS Limitation, 10.15 Catalina)
elif [[
"${validate_os}" == "Mojave" && "${os_major}" -eq 10 && "${os_minor}" -ge 8
]]; then
echo "Mojave"
elif [[
"${validate_os}" == "High Sierra" && "${os_major}" -eq 10 && "${os_minor}" -ge 8
]]; then
echo "High Sierra"
elif [[
"${validate_os}" == "High Sierra" && "${os_major}" -eq 10 && \
( "${os_minor}" -ge 8 || "${os_minor}" -eq 7 && "${os_patch}" -ge 5 )
]]; then
echo "Sierra / OS Limitation" # (Current OS Limitation, 10.13 Compatible)
elif [[
"${validate_os}" == "El Capitan" && "${os_major}" -eq 10 && \
( "${os_minor}" -ge 7 || "${os_minor}" -eq 6 && "${os_patch}" -ge 8 )
]]; then
echo "El Capitan"
else
echo "<result>Current OS Not Supported</result>"
exit 0
fi
else
# For MacPro5,1 Mac Pros because Apple had to make Mojave support for MacPro's
# difficult...I had to add complexity to the original "simplistic" logic in this script.
if [[
"${validate_os}" == "Mojave" && "${os_major}" -eq 10 && \
( "${os_minor}" -ge 14 || "${os_minor}" -eq 13 && "${os_patch}" -ge 6 )
]]; then
# Supports Mojave, but required Metal Capable Graphics Cards and FileVault must be disabled.
mac_pro_result="Mojave"
# Check if the Graphics Card supports Metal
if [[
$( /usr/sbin/system_profiler SPDisplaysDataType | \
/usr/bin/awk -F 'Metal: ' '{print $2}' | /usr/bin/xargs ) != *"Supported"*
]]; then
mac_pro_result+=" / GFX unsupported"
fi
# Check if FileVault is enabled
if [[ $( /usr/bin/fdesetup status | /usr/bin/awk '{print $NF}' ) != "Off." ]]; then
mac_pro_result+=" / FV Enabled"
fi
echo "${mac_pro_result}"
elif [[
"${validate_os}" == "Mojave" && "${os_major}" -eq 10 && \
( "${os_minor}" -le 12 || "${os_minor}" -eq 13 && "${os_patch}" -le 5 )
]]; then
# Supports Mojave or newer, but requires a stepped upgrade path
echo "High Sierra / OS Limitation"
fi
fi
}
check_ram_upgradeable() {
ram_upgradeable=$( /usr/sbin/system_profiler SPMemoryDataType | \
/usr/bin/awk -F "Upgradeable Memory: " '{print $2}' | /usr/bin/xargs 2&> /dev/null )
# ARM Macs do not return the "Upgradeable Memory:" attribute as of early 2022
if [[ -z ${ram_upgradeable} ]]; then
ram_upgradeable="No"
fi
echo "${ram_upgradeable}"
}
ram_check() {
# Check if the current RAM meets specs
# Arguments
# $1 = Max supported OS version based on hardware model
local validate_os="${1}"
# Setting the minimum RAM required for compatibility
minimum_ram_mojave_and_older=2
minimum_ram_catalina_and_newer=4
# Get RAM Info
system_ram=$(( $( /usr/sbin/sysctl -n hw.memsize ) / bytes_in_gigabytes ))
if [[
"${validate_os}" =~ ^(Catalina|Big[[:space:]]Sur|Monterey|(Ventura|Sonoma|Sequoia|Tahoe)\*)$
]]; then
# OS version requires 4GB RAM minimum. For Ventura and newer, value's are inherited from
# Monterey as Apple has not publicly defined these requirements.
if [[ $system_ram -lt $minimum_ram_catalina_and_newer ]]; then
# Based on RAM, device does not have enough to support Catalina or newer
if [[ "$( check_ram_upgradeable )" == "No" ]]; then
# Device is not upgradable, so can never support Catalina or newer
if [[ $system_ram -ge $minimum_ram_mojave_and_older ]]; then
# Device has enough RAM to support Mojave
validate_os="Mojave"
else
# Device does not have enough RAM to support any upgrade!?
echo "<result>Not Upgradable</result>"
exit 0
fi
else
# Device does not have enough RAM to upgrade currently,
# but RAM capacity can be increased.
validate_os+=" / Insufficient RAM"
fi
fi
else
# Based on model, device supports Mojave or older
if [[ $system_ram -lt $minimum_ram_mojave_and_older ]]; then
# Based on RAM, device does not have enough to upgrade
if [[ "$( check_ram_upgradeable )" == "No" ]]; then
# Device does not have enough RAM to support any upgrade!?
echo "<result>Not Upgradable</result>"
exit 0
else
# Device does not have enough RAM to upgrade currently,
# but RAM capacity can be increased.
validate_os+=" / Insufficient RAM"
fi
fi
fi
echo "${validate_os}"
}
storage_check() {
# Check if the available free space is sufficient
# $1 = Max supported OS version based on hardware model
# $2 = Current OS major version
# $3 = Current OS minor version
# $4 = Current OS patch version
local validate_os="${1}"
local os_major="${2}"
local os_minor="${3}"
local os_patch="${4}"
# Get free space on the boot disk
storage_free_space=$( /usr/bin/osascript -l "JavaScript" -e '
ObjC.import("Foundation");
let freeSpaceBytesRef = Ref();
$.NSURL.fileURLWithPath("/").getResourceValueForKeyError(
freeSpaceBytesRef,
"NSURLVolumeAvailableCapacityForImportantUsageKey",
null
);
Math.round( ObjC.unwrap( freeSpaceBytesRef[0] ) )'
)
# Workaround for NSURLVolumeAvailableCapacityForImportantUsageKey returning
# 0 if no user is logged in - use NSURLVolumeAvailableCapacityKey instead
if [[ ${storage_free_space} -eq 0 ]]; then
storage_free_space=$( /usr/bin/osascript -l "JavaScript" -e '
ObjC.import("Foundation");
var freeSpaceBytesRef=Ref();
$.NSURL.fileURLWithPath("/").getResourceValueForKeyError(
freeSpaceBytesRef,
"NSURLVolumeAvailableCapacityKey",
null
);
Math.round( ObjC.unwrap( freeSpaceBytesRef[0] ) )'
)
fi
# Set the required free space to compare.
# Set space requirement in bytes: /usr/bin/bc <<< "<space in GB> * 1073741824"
case "${validate_os}" in
"Tahoe*"* )
# Value's inherited from Monterey, Apple has not defined these requirements
required_free_space_newer="27917287424" # 26GB if Sierra or later
os_newer="10.12.0"
required_free_space_older="47244640256" # 44GB if El Capitan or earlier
os_older="10.11.0"
;;
"Sequoia*"* )
# Value's inherited from Monterey, Apple has not defined these requirements
required_free_space_newer="27917287424" # 26GB if Sierra or later
os_newer="10.12.0"
required_free_space_older="47244640256" # 44GB if El Capitan or earlier
os_older="10.11.0"
;;
"Sonoma*"* )
# Value's inherited from Monterey, Apple has not defined these requirements
required_free_space_newer="27917287424" # 26GB if Sierra or later
os_newer="10.12.0"
required_free_space_older="47244640256" # 44GB if El Capitan or earlier
os_older="10.11.0"
;;
"Ventura*"* )
# Value's inherited from Monterey, Apple has not defined these requirements
required_free_space_newer="27917287424" # 26GB if Sierra or later
os_newer="10.12.0"
required_free_space_older="47244640256" # 44GB if El Capitan or earlier
os_older="10.11.0"
;;
"Monterey"* )
required_free_space_newer="27917287424" # 26GB if Sierra or later
os_newer="10.12.0"
required_free_space_older="47244640256" # 44GB if El Capitan or earlier
os_older="10.11.0"
;;
"Big Sur"* )
required_free_space_newer="38117834752" # 35.5GB if Sierra or later
os_newer="10.12.0"
required_free_space_older="47781511168" # 44.5GB if El Capitan or earlier
os_older="10.11.0"
;;
"Catalina"*|"Mojave"* )
required_free_space_newer="13421772800" # 12.5GB if El Capitan 10.11.5 or later
os_newer="10.11.5"
required_free_space_older="19864223744" # 18.5GB if Yosemite or earlier
os_older="10.10.0"
;;
"High Sierra"* )
required_free_space="15354508084" # 14.3GB
;;
"Sierra"*|"El Capitan"* )
required_free_space="9448928052" # 8.8GB
;;
* )
echo "<result>Not Supported</result>"
exit 0
;;
esac
if [[ -z $required_free_space ]]; then
newer_os_major=$( echo "${os_newer}" | /usr/bin/awk -F '.' '{print $1}' )
newer_os_minor=$( echo "${os_newer}" | /usr/bin/awk -F '.' '{print $2}' )
newer_os_patch=$( echo "${os_newer}" | /usr/bin/awk -F '.' '{print $3}' )
older_os_major=$( echo "${os_older}" | /usr/bin/awk -F '.' '{print $1}' )
older_os_minor=$( echo "${os_older}" | /usr/bin/awk -F '.' '{print $2}' )
older_os_patch=$( echo "${os_older}" | /usr/bin/awk -F '.' '{print $3}' )
# Check newer
if [[ "${os_major}" -gt "${newer_os_major}" ||
( "${os_major}" -eq "${newer_os_major}" &&
"${os_minor}" -ge "${newer_os_minor}" &&
"${os_patch}" -ge "${newer_os_patch}" )
]]; then
required_free_space=$required_free_space_newer
# Check older
elif [[ "${os_major}" -gt "${older_os_major}" ||
( "${os_major}" -eq "${older_os_major}" &&
"${os_minor}" -ge "${older_os_minor}" &&
"${os_patch}" -ge "${older_os_patch}" )
]]; then
required_free_space=$required_free_space_older
fi
fi
if [[ $storage_free_space -le $required_free_space ]]; then
echo " / Insufficient Storage"
fi
}
##################################################
# Bits Staged...
# Set the number of bytes in a gigabyte
bytes_in_gigabytes="1073741824" # $((1024 * 1024 * 1024)) # Transforms one gigabyte into bytes
# Get the current OS version
if [[ -z "${TEST_OS}" ]]; then
os_version=$( /usr/bin/sw_vers -productVersion )
else
os_version="${TEST_OS}"
echo "Test OS value: ${os_version}"
fi
current_os_major=$( echo "${os_version}" | /usr/bin/awk -F '.' '{print $1}' )
current_os_minor=$( echo "${os_version}" | /usr/bin/awk -F '.' '{print $2}' )
current_os_patch=$( echo "${os_version}" | /usr/bin/awk -F '.' '{print $3}' )
# Get the Model Type
if [[ -z "${TEST_MODEL}" ]]; then
mac_model=$( /usr/sbin/sysctl -n hw.model )
else
mac_model="${TEST_MODEL}"
echo "Test Model value: ${TEST_MODEL}"
fi
# Check for compatibility
model_result=$( model_check "${mac_model}" )
case "${model_result}" in
"Tahoe*" )
version_string="26"
;;
"Sequoia*" )
version_string="15"
;;
"Sonoma*" )
version_string="14"
;;
"Ventura*" )
version_string="13"
;;
"Monterey" )
version_string="12"
;;
"Big Sur" )
version_string="11"
;;
"Catalina" )
version_string="10.15"
;;
"Mojave" )
version_string="10.14"
;;
"High Sierra" )
version_string="10.13"
;;
"Sierra" )
version_string="10.12"
;;
"El Capitan" )
version_string="10.11"
;;
esac
if [[
( "${version_string}" =~ ^10[.].+ && \
"${current_os_minor}" -gt $( echo "${version_string}" | /usr/bin/awk -F '.' '{print $2}' ) ) \
|| "${current_os_major}" -gt $( echo "${version_string}" | /usr/bin/awk -F '.' '{print $1}' )
]]; then
# Check to see if device is running an OS version newer than what it supports.
# If so, no reason to check further specifications.
report_result="${model_result} (Model doesn't support current OS version)"
elif [[
"${version_string}" == "${current_os_major}.${current_os_minor}" ||
"${version_string}" == "${current_os_major}"
]]; then
# Is device already running the latest supported OS?
report_result="${model_result}"
else
# Run though compatibility check functions
os_result=$( os_check "${model_result}" "${current_os_major}" \
"${current_os_minor}" "${current_os_patch}" "${mac_model}" )
ram_check_results=$( ram_check "${os_result}" )
storage_check_results=$( storage_check "${os_result}" \
"${current_os_major}" "${current_os_minor}" "${current_os_patch}" )
report_result="${ram_check_results}${storage_check_results}"
model_result="${ram_check_results}${storage_check_results}"
fi
echo "<result>${report_result}</result>"
write_to_ea_history "latest_os_supported" "${model_result}"
exit 0