Files
2026-02-16 15:05:15 +01:00

368 lines
18 KiB
Bash

#!/bin/bash
# This script is meant to be used with Jamf Pro and makes use of Jamf Helper.
# The idea behind this script is that it alerts the user that there are required OS
# updates that need to be installed. Rather than forcing updates to take place through the
# command line using "softwareupdate", the user is encouraged to use the GUI to update.
# In recent OS versions, Apple has done a poor job of testing command line-based workflows
# of updates and failed to account for scenarios where users may or may not be logged in.
# The update process through the GUI has not suffered from these kind of issues. The
# script will allow end users to postpone/defer updates X amount of times and then will
# give them one last change to postpone.
# This script should work rather reliably going back to 10.12 and maybe further, but at
# this point the real testing has only been done on 10.14.
# Please note, that this script does NOT cache updates in advance. The reason for this is
# that sometimes Apple releases updates that get superseded in a short time frame.
# This can result in downloaded updates that are in the /Library/Updates path that cannot
# be removed in 10.14+ due to System Integrity Protection.
#
# JAMF Pro Script Parameters:
# Parameter 4: Optional. Number of postponements allowed. Default: 3
# Parameter 5: Optional. Number of seconds dialog should remain up. Default: 900 seconds
#
# Here is the expected workflow with this script:
# If no user is logged in, the script will install updates through the command line and
# shutdown/restart as required.
# If a user is logged in and there are updates that require a restart, the user will get
# prompted to update or to postpone.
# If a user is logged in and there are no updates that require a restart, the updates will
# get installed in the background (unless either Safari or iTunes are running.)
#
# There are a few exit codes in this script that may indicate points of failure:
# 11: No power source detected while doing CLI update.
# 12: Software Update failed.
# 13: FV encryption is still in progress.
# 14: Incorrect deferral type used.
# Potential feature improvement
# Allow user to postpone to a specific time with a popup menu of available times
########################## Deutsch #####################################################
# Dieses Skript ist für die Verwendung mit Jamf Pro vorgesehen und verwendet Jamf Helper.
# Die Idee hinter diesem Skript ist, dass der Benutzer darauf hingewiesen wird, dass ein Betriebssystem erforderlich ist
# Updates, die installiert werden müssen. Anstatt Aktualisierungen zu erzwingen, die über das Internet erfolgen
# Kommandozeile Mit "softwareupdate" wird der Benutzer aufgefordert, die GUI zum Aktualisieren zu verwenden.
# In neueren Betriebssystemversionen hat Apple befehlszeilenbasierte Workflows nur unzureichend getestet
# Anzahl der Aktualisierungen und nicht berücksichtigte Szenarien, in denen Benutzer möglicherweise angemeldet sind oder nicht.
# Der Aktualisierungsprozess über die GUI hat unter solchen Problemen nicht gelitten. Das Mit dem Skript
# können Endbenutzer Aktualisierungen x-mal verschieben oder verschieben
# Gib ihnen eine letzte Änderung, um sie zu verschieben.
# Dieses Skript sollte ziemlich zuverlässig funktionieren und zurück zu 10.12 und vielleicht weiter, aber um
# An diesem Punkt wurden die eigentlichen Tests erst am 10.14 durchgeführt.
# Bitte beachten Sie, dass dieses Skript KEINE Updates im Voraus zwischenspeichert. Der Grund dafür ist
# dass Apple manchmal Updates veröffentlicht, die in kurzer Zeit ersetzt werden.
# Dies kann dazu führen, dass Updates heruntergeladen werden, die sich im Pfad / Library / Updates befinden und nicht heruntergeladen werden können
# Wurde in 10.14+ aufgrund des Schutzes der Systemintegrität entfernt.
#
# JAMF Pro Script Parameter:
# Parameter 4: Optional. Anzahl der zulässigen Verschiebungen. Voreinstellung: 3
# Parameter 5: Optional. Das Dialogfeld für die Anzahl der Sekunden sollte geöffnet bleiben. Voreinstellung: 900 Sekunden
#
# Hier ist der erwartete Workflow mit diesem Skript:
# Wenn kein Benutzer angemeldet ist, installiert das Skript Updates über die Befehlszeile und
# Herunterfahren / Neustarten nach Bedarf.
# Wenn ein Benutzer angemeldet ist und es Updates gibt, die einen Neustart erfordern, erhält der Benutzer
# aufgefordert, zu aktualisieren oder zu verschieben.
# Wenn ein Benutzer angemeldet ist und keine Updates vorhanden sind, die einen Neustart erfordern, werden die Updates ausgeführt
# im Hintergrund installiert werden (es sei denn, Safari oder iTunes werden ausgeführt.)
#
# Es gibt einige Exit-Codes in diesem Skript, die auf Fehlerstellen hinweisen können:
# 11: Während der CLI-Aktualisierung wurde keine Stromquelle erkannt.
# 12: Softwareaktualisierung fehlgeschlagen.
# 13: Die FV-Verschlüsselung wird noch durchgeführt.
# 14: Falscher Stundungstyp verwendet.
# Mögliche Funktionsverbesserung
# Benutzer kann mit einem Popup-Menü mit verfügbaren Zeiten auf einen bestimmten Zeitpunkt verschoben werden
set -x
###### ACTUAL WORKING CODE BELOW #######
setDeferral (){
# Notes: PlistBuddy "print" will print stderr to stdout when file is not found.
# File Doesn't Exist, Will Create: /path/to/file.plist
# There is some unused code here with the idea that at some point in the future I can
# extend functionality of this script to support hard and relative dates.
BundleID="${1}"
DeferralType="${2}"
DeferralValue="${3}"
DeferralPlist="${4}"
if [[ "$DeferralType" == "date" ]]; then
DeferralDate="$(/usr/libexec/PlistBuddy -c "print :$BundleID:date" "$DeferralPlist" 2>/dev/null)"
# Set deferral date
if [[ -n "$DeferralDate" ]] && [[ ! "$DeferralDate" =~ "File Doesn't Exist" ]]; then
# /usr/libexec/PlistBuddy -c "set :$BundleID:date '07/04/2019 11:21:51 +0000'" "$DeferralPlist"
/usr/libexec/PlistBuddy -c "set :$BundleID:date $DeferralValue" "$DeferralPlist" 2>/dev/null
else
# /usr/libexec/PlistBuddy -c "add :$BundleID:date date '07/04/2019 11:21:51 +0000'" "$DeferralPlist"
/usr/libexec/PlistBuddy -c "add :$BundleID:date date $DeferralValue" "$DeferralPlist" 2>/dev/null
fi
elif [[ "$DeferralType" == "count" ]]; then
DeferralCount="$(/usr/libexec/PlistBuddy -c "print :$BundleID:count" "$DeferralPlist" 2>/dev/null)"
# Set deferral count
if [[ -n "$DeferralCount" ]] && [[ ! "$DeferralCount" =~ "File Doesn't Exist" ]]; then
/usr/libexec/PlistBuddy -c "set :$BundleID:count $DeferralValue" "$DeferralPlist" 2>/dev/null
else
/usr/libexec/PlistBuddy -c "add :$BundleID:count integer $DeferralValue" "$DeferralPlist" 2>/dev/null
fi
else
echo "Falscher Stundungstyp verwendet"
exit 14
fi
}
OSMajorVersion="$(/usr/bin/sw_vers -productVersion | /usr/bin/cut -d '.' -f 2)"
OSMinorVersion="$(/usr/bin/sw_vers -productVersion | /usr/bin/cut -d '.' -f 3)"
DeferralPlist="/Library/Application Support/JAMF/com.custom.deferrals.plist"
BundleID="com.apple.SoftwareUpdate"
DeferralType="count"
DeferralValue="${4}"
if [[ -z "$DeferralValue" ]]; then
DeferralValue=3
fi
CurrentDeferralValue="$(/usr/libexec/PlistBuddy -c "print :$BundleID:count" "$DeferralPlist" 2>/dev/null)"
# Set up the deferral value if it does not exist already
if [[ -z "$CurrentDeferralValue" ]] || [[ "$CurrentDeferralValue" =~ "File Doesn't Exist" ]]; then
setDeferral "$BundleID" "$DeferralType" "$DeferralValue" "$DeferralPlist"
CurrentDeferralValue="$(/usr/libexec/PlistBuddy -c "print :$BundleID:count" "$DeferralPlist" 2>/dev/null)"
fi
jamfHelper="/Library/Application Support/JAMF/bin/jamfHelper.app/Contents/MacOS/jamfHelper"
jamf="/usr/local/bin/jamf"
TimeOutinSec="${5}"
if [[ -z "$DeferralValue" ]]; then
TimeOutinSec="900"
fi
# Path to temporarily store list of software updates. Avoids having to re-run the softwareupdate command multiple times.
# Pfad zum temporären Speichern der Liste der Softwareupdates. Vermeidet, dass der Befehl softwareupdate mehrmals ausgeführt werden muss
ListOfSoftwareUpdates="/tmp/ListOfSoftwareUpdates"
# Set appropriate Software Update icon depending on OS version
# Stellen Sie das entsprechende Software-Aktualisierungssymbol je nach Betriebssystemversion ein
if [[ "$OSMajorVersion" -gt 13 ]]; then
AppleSUIcon="/System/Library/CoreServices/Software Update.app/Contents/Resources/SoftwareUpdate.icns"
elif [[ "$OSMajorVersion" -eq 13 ]]; then
AppleSUIcon="/System/Library/CoreServices/Install Command Line Developer Tools.app/Contents/Resources/SoftwareUpdate.icns"
elif [[ "$OSMajorVersion" -ge 8 ]] && [[ "$OSMajorVersion" -le 12 ]]; then
AppleSUIcon="/System/Library/CoreServices/Software Update.app/Contents/Resources/SoftwareUpdate.icns"
elif [[ "$OSMajorVersion" -lt 8 ]]; then
AppleSUIcon="/System/Library/CoreServices/Software Update.app/Contents/Resources/Software Update.icns"
fi
## Verbiage For Messages ##
## Sprachausgabe für Nachrichten ##
# Message to guide user to Software Update process
# Nachricht, die den Benutzer zum Software-Update-Vorgang führt
if [[ "$OSMajorVersion" -ge 14 ]]; then
#SUGuide="Klicken Sie im Apple-Menü auf "Systemeinstellungen" und dann auf "Software-Update", um alle verfügbaren Updates zu installieren."
SUGuide="by navigating to:
 > System Preferences > Software Update"
else
#SUGuide="Öffnen Sie den App Store im Ordner "Programme" und klicken Sie auf die Registerkarte "Updates", um alle verfügbaren Updates zu installieren."
SUIGuide="by navigating to:
 > App Store > Updates tab"
fi
# Message to let user to contact IT
# Nachricht, damit der Benutzer Kontakt mit der IT aufnehmen kann
ITContact=""
if [[ -z "$ITContact" ]]; then
ITContact="IT"
fi
ContactMsg="Bei der Installation der Updates ist anscheinend ein Fehler aufgetreten. Sie können es erneut versuchen $SUGuide
Wenn der Fehler weiterhin besteht, wenden Sie sich bitte an $ITContact."
# Message to display when computer is running off battery
# Meldung, die angezeigt wird, wenn der Akku des Computers leer ist
no_ac_power="Der Computer ist derzeit im Akkubetrieb und nicht an eine Stromquelle angeschlossen. Schließen Sie den Computer an eine Stromquelle an und versuchen Sie es erneut."
# Standard Update Message
# Standard Update Nachricht
StandardUpdatePrompt="Für Ihren Mac ist ein Betriebssystem-Update verfügbar. Klicken Sie auf Weiter, um mit dem Software-Update fortzufahren und dieses Update auszuführen. Wenn Sie den Vorgang zu diesem Zeitpunkt nicht starten können, können Sie den Vorgang um einen Tag verschieben.
Versuche bleiben zu verschieben: $CurrentDeferralValue
Sie können jederzeit MacOS-Software-Updates installieren $SUGuide"
# Forced Update Message
# Erzwungene Aktualisierungsnachricht
ForcedUpdatePrompt="Für Ihren Mac sind Software-Updates verfügbar, die einen Neustart erfordern. Sie haben Aktualisierungen bereits so oft wie möglich verschoben.
Bitte speichern Sie Ihre Arbeit und klicken Sie auf Aktualisieren. Anderenfalls verschwindet diese Meldung und der Computer wird automatisch neu gestartet."
# Message shown when running CLI updates
# Meldung, die beim Ausführen von CLI-Updates angezeigt wird
HUDMessage="Bitte speichern Sie Ihre Arbeit und beenden Sie alle anderen Anwendungen. Im Hintergrund werden MacOS-Software-Updates installiert. Schalten Sie diesen Computer während dieser Zeit nicht aus. Diese Meldung wird ausgeblendet, wenn die Aktualisierungen abgeschlossen sind. Wenn Sie sie schließen, wird der Aktualisierungsvorgang nicht abgebrochen.
Wenn Sie das Gefühl haben, dass zu viel Zeit vergangen ist, wenden Sie sich bitte an $ITContact
"
## Functions ##
powerCheck (){
# This is meant to be used when doing CLI update installs.
# Updates through the GUI can already determine its own requirements to proceed with
# the update process.
# Let's wait 5 minutes to see if computer gets plugged into power.
for (( i = 1; i <= 5; ++i )); do
if [[ "$(/usr/bin/pmset -g ps | /usr/bin/grep "Battery Power")" = "Now drawing from 'Battery Power'" ]]; then
echo "$no_ac_power"
/bin/sleep 60
else
return 0
fi
done
exit 11
}
updateCLI (){
# Install all software updates
/usr/sbin/softwareupdate -ia --verbose 2>&1 >> "$ListOfSoftwareUpdates" &
## Get the Process ID of the last command run in the background ($!) and wait for it to complete (wait)
SUPID=$(echo "$!")
wait $SUPID
SU_EC=$?
ShutdownRequired=$(/bin/cat "$ListOfSoftwareUpdates" | /usr/bin/grep -E "halt|shut down" | /usr/bin/wc -l | /usr/bin/awk '{ print $1 }')
return $SU_EC
}
updateRestartAction (){
# On T2 hardware, we need to shutdown on certain updates
if [[ "$ShutdownRequired" == "1" ]] && [[ "$SEPType" ]]; then
if [[ "$OSMajorVersion" -eq 13 ]] && [[ "$OSMinorVersion" -ge 4 ]] || [[ "$OSMajorVersion" -ge 14 ]]; then
/sbin/shutdown -h now
exit 0
fi
fi
# If no shutdown is required then let's go ahead and restart
/sbin/shutdown -r now
exit 0
}
updateGUI (){
# Update through the GUI
if [[ "$OSMajorVersion" -ge 14 ]]; then
/usr/bin/open "/System/Library/CoreServices/Software Update.app"
elif [[ "$OSMajorVersion" -ge 8 ]] && [[ "$OSMajorVersion" -le 13 ]]; then
/usr/bin/open macappstore://showUpdatesPage
fi
}
fvStatusCheck (){
# Check to see if the encryption process is complete
FVStatus="$(/usr/bin/fdesetup status)"
if [[ $(/usr/bin/grep -q "Encryption in progress" <<< "$FVStatus") ]]; then
echo "Der Verschlüsselungsprozess ist noch nicht abgeschlossen."
echo "$FVStatus"
exit 13
fi
}
runUpdates (){
"$jamfHelper" -windowType hud -lockhud -title "Apple Software Update" -description "$HUDMessage""START TIME: $(/bin/date +"%b %d %Y %T")" -icon "$AppleSUIcon" &>/dev/null &
## We'll need the pid of jamfHelper to kill it once the updates are complete
JHPID=$(echo "$!")
## Run the jamf policy to insall software updates
updateCLI
## Kill the jamfHelper. If a restart is needed, the user will be prompted. If not the hud will just go away
/bin/kill -s KILL "$JHPID" &>/dev/null
if [[ "$SU_EC" == 0 ]]; then
updateRestartAction
else
echo "/usr/bin/softwareupdate failed. Exit Code: $SU_EC"
"$jamfHelper" -windowType utility -icon "$AppleSUIcon" -title "Apple Software Updates" -description "$ContactMsg" -button1 "OK"
exit 12
fi
exit 0
}
# Store list of software updates in /tmp which gets cleared periodically by the OS and on restarts
/usr/sbin/softwareupdate -l 2>&1 > "$ListOfSoftwareUpdates"
UpdatesNoRestart=$(/bin/cat "$ListOfSoftwareUpdates" | /usr/bin/grep recommended | /usr/bin/grep -v restart | /usr/bin/cut -d , -f 1 | /usr/bin/sed -e 's/^[[:space:]]*//')
RestartRequired=$(/bin/cat "$ListOfSoftwareUpdates" | /usr/bin/grep restart | /usr/bin/grep -v '\*' | /usr/bin/cut -d , -f 1 | /usr/bin/sed -e 's/^[[:space:]]*//')
# Determine Secure Enclave version
SEPType="$(/usr/sbin/system_profiler SPiBridgeDataType | /usr/bin/awk -F: '/Model Name/ { gsub(/.*: /,""); print $0}')"
# Determine currently logged in user
#LoggedInUser=$(python -c 'from SystemConfiguration import SCDynamicStoreCopyConsoleUser; import sys; username = (SCDynamicStoreCopyConsoleUser(None, None, None) or [None])[0]; username = [username,""][username in [u"loginwindow", None, u""]]; sys.stdout.write(username + "\n");')
LoggedInUser="$(/usr/sbin/scutil <<< "show State:/Users/ConsoleUser" | /usr/bin/awk '/Name :/ && ! /loginwindow/ { print $3 }')"
# Let's make sure FileVault isn't encrypting before proceeding any further
fvStatusCheck
# If there are no system updates, reset timer and exit script
if [[ "$UpdatesNoRestart" == "" ]] && [[ "$RestartRequired" == "" ]]; then
echo "No updates at this time."
setDeferral "$BundleID" "$DeferralType" "$DeferralValue" "$DeferralPlist"
exit 0
fi
# If we get to this point, there are updates available.
# If there is no one logged in, let's try to run the updates.
if [[ "$LoggedInUser" == "" ]]; then
powerCheck
updateCLI
updateRestartAction
else
# Someone is logged in. Prompt if any updates require a restart ONLY IF the update timer has not reached zero
if [[ "$RestartRequired" != "" ]]; then
if [[ "$CurrentDeferralValue" -gt 0 ]]; then
# Reduce the timer by 1. The script will run again the next day
let CurrTimer=$CurrentDeferralValue-1
setDeferral "$BundleID" "$DeferralType" "$CurrTimer" "$DeferralPlist"
# If someone is logged in and they have not canceled $DeferralValue times already, prompt them to install updates that require a restart and state how many more times they can press 'cancel' before updates run automatically.
# Wenn jemand angemeldet ist und $ DeferralValue noch nicht abgebrochen hat, fordern Sie ihn auf, Updates zu installieren, die einen Neustart erfordern, und anzugeben, wie oft er "Abbrechen" drücken kann, bevor Updates automatisch ausgeführt werden
HELPER=$("$jamfHelper" -windowType utility -icon "$AppleSUIcon" -title "Apple Software Updates" -description "$StandardUpdatePrompt" -button1 "Fortsetzen" -button2 "Verschieben" -cancelButton "2" -defaultButton 2 -timeout "$TimeOutinSec")
echo "Jamf Helper Exit Code: $HELPER"
# If they click "Update" then take them to the software update preference pane
if [ "$HELPER" == "0" ]; then
updateGUI
fi
exit 0
else
HELPER=$("$jamfHelper" -windowType utility -icon "$AppleSUIcon" -title "Apple Software Update" -description "$ForcedUpdatePrompt" -button1 "Update" -defaultButton 1 -timeout "$TimeOutinSec" -countdown -alignCountdown "right")
echo "Jamf Helper Exit Code: $HELPER"
# If they click Install Updates then run the updates
# Looks like someone tried to quit jamfHelper or the jamfHelper screen timed out
# The Timer is already 0, run the updates automatically, the end user has been warned!
if [[ "$HELPER" == "0" ]] || [[ "$HELPER" == "239" ]]; then
runUpdates
fi
fi
fi
fi
# Install updates that do not require a restart
# Future Fix: Might want to see if Safari and iTunes are running as sometimes these apps sometimes do not require a restart but do require that the apps be closed
# A simple stop gap to see if either process is running.
if [[ "$UpdatesNoRestart" != "" ]] && [[ ! "$(/bin/ps -axc | /usr/bin/grep -e Safari$)" ]] && [[ "$(/bin/ps -axc | /usr/bin/grep -e iTunes$)" ]]; then
powerCheck
updateCLI
fi
exit 0