Skip to content

Commit

Permalink
v2.1
Browse files Browse the repository at this point in the history
  • Loading branch information
Graham Pugh (iMac Pro) committed Apr 21, 2022
1 parent 3fa4625 commit a534cf8
Show file tree
Hide file tree
Showing 7 changed files with 116 additions and 64 deletions.
6 changes: 6 additions & 0 deletions .prettierrc.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
semi: false
overrides:
- files:
- "*.md"
options:
tabWidth: 2
32 changes: 22 additions & 10 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,37 @@

No date

## [2.1]

21.04.2022

- Remove `softwareupdate` downloading stage as can cause unexpected installation.
- Bugfix for identifying updates that do not require a restart.
- New `/Library/Scripts/nice_updater_status.txt` file that can be read by a Jamf Extension Attribute for easy checking of run status.
- Changed default defer count to 8.
- Changed default wait after previous update to 7 days.
- Other minor bugfixes.

## [2.0.3]

21.05.2021

- Bugfix for issue #1
- Bugfix for issue #1

## [2.0.2]

12.05.2021

- Runs `jamfHelper` as the current user (this may not be necessary - was introduced to try and fix a problem where the tool was not showing though was running).
- Changed default defer count (back) to 10.
- Runs `jamfHelper` as the current user (this may not be necessary - was introduced to try and fix a problem where the tool was not showing though was running).
- Changed default defer count (back) to 10.

## [2.0.1]

30.04.2021

- The uninstaller script now forgets the package.
- A delay is introduced after a user closes the Software Update pane before bringing the dialog back. This is to primarily prevent the popup showing up while a restart is happening. (Ideally we would be able to check if the restart has been initiated, but that is not happening yet.)
- Added the CHANGELOG.md file.
- The uninstaller script now forgets the package.
- A delay is introduced after a user closes the Software Update pane before bringing the dialog back. This is to primarily prevent the popup showing up while a restart is happening. (Ideally we would be able to check if the restart has been initiated, but that is not happening yet.)
- Added the CHANGELOG.md file.

## [2.0]

Expand All @@ -44,9 +55,9 @@ Also, the last notification message no longer times out after 300s. It will stay

27.08.2019

- Replaced StartInterval with StartCalendarInterval to ensure script starts regularly.
- Created an uninstaller script
- Created a post-install script for Jamf which will allow parameters to be overridden in a policy.
- Replaced StartInterval with StartCalendarInterval to ensure script starts regularly.
- Created an uninstaller script
- Created a post-install script for Jamf which will allow parameters to be overridden in a policy.

## [1.6]

Expand Down Expand Up @@ -78,7 +89,8 @@ Changed the default button of the jamfHelper dialogs to Cancel, because after ti

Also shortened the timeout to 82800 from 99999 seconds to prevent overlap of two days' dialogs.

[untagged]: https://github.com/grahampugh/nice-updater/compare/v2.0.3...HEAD
[untagged]: https://github.com/grahampugh/nice-updater/compare/v2.1...HEAD
[2.1]: https://github.com/grahampugh/nice-updater/compare/v2.0.3...v2.1
[2.0.3]: https://github.com/grahampugh/nice-updater/compare/v2.0.2...v2.0.3
[2.0.2]: https://github.com/grahampugh/nice-updater/compare/v2.0.1...v2.0.2
[2.0.1]: https://github.com/grahampugh/nice-updater/compare/v2.0...v2.0.1
Expand Down
11 changes: 8 additions & 3 deletions build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
identifier="com.github.grahampugh.nice_updater"

# Default version of the build, you can leave this alone and specify as an argument like so: ./build.sh 1.7
version="2.0.3"
version="2.1"

# The title of the message that is displayed when software updates are in progress and a user is logged in
updateRequiredTitle="macOS Software Updates Required"
Expand All @@ -18,14 +18,17 @@ updateInProgressTitle="Software Update In Progress"
# The location of your log, keep in mind that if you nest the log into a folder that does not exist you'll need to mkdir -p the directory as well
log="/Library/Logs/Nice_Updater.log"

# The location of the status file, keep in mind that if the folder does not exist you'll need to mkdir -p the directory as well
EAFile="/Library/Scripts/nice_updater_status.txt"

# The number of days to check for updates after a full update has been performed
afterFullUpdateDelayDayCount="14"
afterFullUpdateDelayDayCount="7"

# The number of days to check for updates after a updates were checked, but no updates were available
afterEmptyUpdateDelayDayCount="3"

# The number of times to alert a single user prior to forcibly installing updates
maxNotificationCount="10"
maxNotificationCount="8"

# Calendar based start interval - hours and minutes.
startIntervalHour="13" # valid is 0-23. If left blank, daemon will launch every hour instead of once per day.
Expand Down Expand Up @@ -104,6 +107,7 @@ defaults write "$PWD/$preferenceFileName" UpdateRequiredTitle -string "$updateRe
defaults write "$PWD/$preferenceFileName" UpdateRequiredMessage -string "$updateRequiredMessage"
defaults write "$PWD/$preferenceFileName" UpdateInProgressTitle -string "$updateInProgressTitle"
defaults write "$PWD/$preferenceFileName" Log -string "$log"
defaults write "$PWD/$preferenceFileName" EAFile -string "$EAFile"
defaults write "$PWD/$preferenceFileName" AfterFullUpdateDelayDayCount -int "$afterFullUpdateDelayDayCount"
defaults write "$PWD/$preferenceFileName" AfterEmptyUpdateDelayDayCount -int "$afterEmptyUpdateDelayDayCount"
defaults write "$PWD/$preferenceFileName" MaxNotificationCount -int "$maxNotificationCount"
Expand All @@ -127,6 +131,7 @@ if find "$PWD/custom_icon" -name "*.png" ; then
defaults write "$PWD/$preferenceFileName" IconCustomPath -string "$icon_path"
else
echo "Nothing found at $PWD/custom_icon/*.png"
defaults write "$PWD/$preferenceFileName" IconCustomPath -string ""
fi

# Copy the LaunchDaemon plists to the temp build directory
Expand Down
8 changes: 5 additions & 3 deletions com.github.grahampugh.nice_updater.prefs.plist
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,17 @@
<key>AfterEmptyUpdateDelayDayCount</key>
<integer>3</integer>
<key>AfterFullUpdateDelayDayCount</key>
<integer>14</integer>
<integer>7</integer>
<key>AlertTimeout</key>
<integer>3540</integer>
<key>EAFile</key>
<string>/Library/Scripts/nice_updater_status.txt</string>
<key>IconCustomPath</key>
<string>/Library/Scripts/nice_updater_custom_icon.png</string>
<string></string>
<key>Log</key>
<string>/Library/Logs/Nice_Updater.log</string>
<key>MaxNotificationCount</key>
<integer>10</integer>
<integer>8</integer>
<key>UpdateInProgressTitle</key>
<string>Software Update In Progress</string>
<key>UpdateRequiredMessage</key>
Expand Down
112 changes: 68 additions & 44 deletions nice_updater.sh
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#!/bin/bash

# Nice Updater 2
version="2.0.3"
version="2.1.0"

# These variables will be automagically updated if you run build.sh, no need to modify them
preferenceFileFullPath="/Library/Preferences/com.github.grahampugh.nice_updater.prefs.plist"
Expand All @@ -11,6 +11,7 @@ helperTitle=$(defaults read "$preferenceFileFullPath" UpdateRequiredTitle)
helperDesc=$(defaults read "$preferenceFileFullPath" UpdateRequiredMessage)
alertTimeout=$(defaults read "$preferenceFileFullPath" AlertTimeout)
log=$(defaults read "$preferenceFileFullPath" Log)
EAFile=$(defaults read "$preferenceFileFullPath" EAFile)
afterFullUpdateDelayDayCount=$(defaults read "$preferenceFileFullPath" AfterFullUpdateDelayDayCount)
afterEmptyUpdateDelayDayCount=$(defaults read "$preferenceFileFullPath" AfterEmptyUpdateDelayDayCount)
maxNotificationCount=$(defaults read "$preferenceFileFullPath" MaxNotificationCount)
Expand All @@ -37,34 +38,34 @@ writelog() {
/bin/echo "$DATE" " $1" >> "$log"
}

write_status() {
/bin/echo "$1" > "$EAFile"
}

finish() {
writelog "======== Finished $scriptName ========"
exit "$1"
}

random_delay() {
delay_time=$(( (RANDOM % 60)+1 ))
writelog "Delaying software update check by ${delay_time}s."
sleep ${delay_time}s
delay_time=$(( (RANDOM % 10)+1 ))
writelog "Delaying software update check by ${delay_time}s"
# sleep ${delay_time}
}

record_last_full_update() {
writelog "Done with update process; recording last full update time."
writelog "Done with update process; recording last full update time"
/usr/libexec/PlistBuddy -c "Delete :last_full_update_time" $preferenceFileFullPath 2> /dev/null
/usr/libexec/PlistBuddy -c "Add :last_full_update_time string $(date +%Y-%m-%d\ %H:%M:%S)" $preferenceFileFullPath

writelog "Clearing user alert data."
writelog "Clearing user alert data"
/usr/libexec/PlistBuddy -c "Delete :users" $preferenceFileFullPath

writelog "Clearing On-Demand Update Key."
writelog "Clearing On-Demand Update Key"
/usr/libexec/PlistBuddy -c "Delete :update_key" $preferenceFileFullPath 2> /dev/null
/usr/libexec/PlistBuddy -c "Add :update_key array" $preferenceFileFullPath 2> /dev/null
}

trigger_nonrestart_updates() {
/usr/sbin/softwareupdate --install "$1"
}

open_software_update() {
/usr/bin/open -W /System/Library/PreferencePanes/SoftwareUpdate.prefPane &
suPID=$!
Expand All @@ -74,6 +75,7 @@ open_software_update() {
sleep 1
done
writelog "Software Update was closed"
write_status "Software Update was closed"
was_closed=1
}

Expand All @@ -88,7 +90,7 @@ compare_date() {

alert_user() {
local subtitle="$1"
[[ "$notificationsLeft" == "1" ]] && local subtitle="1 remaining alert before auto-install."
[[ "$notificationsLeft" == "1" ]] && local subtitle="1 remaining deferral"
[[ "$notificationsLeft" == "0" ]] && local subtitle="No deferrals remaining! Click on \"Install Now\" to proceed"

if /usr/bin/pgrep jamfHelper ; then
Expand Down Expand Up @@ -126,18 +128,21 @@ alert_user() {
pkill jamfHelper
helperExitCode=1
else
writelog "A button was pressed."
writelog "A button was pressed"
fi
fi

# writelog "Response: $helperExitCode"
if [[ $helperExitCode == 0 ]]; then
writelog "User initiated installation."
writelog "User initiated installation"
write_status "User initiated installation"
open_software_update
elif [[ $helperExitCode == 2 ]]; then
writelog "User cancelled installation."
writelog "User cancelled installation"
write_status "User cancelled installation"
else
writelog "Alert timed out without response."
writelog "Alert timed out without response"
write_status "Alert timed out without response"
((notificationCount--))
fi

Expand All @@ -154,45 +159,59 @@ alert_logic() {
if [[ "$notificationCount" -ge "$maxNotificationCount" ]]; then
notificationsLeft="$((maxNotificationCount - notificationCount))"
writelog "$loggedInUser has been notified $notificationCount times; not waiting any longer."
alert_user "$notificationsLeft remaining alerts before auto-install." "$notificationCount"
alert_user "$notificationsLeft remaining deferrals." "$notificationCount"
else
((notificationCount++))
notificationsLeft="$((maxNotificationCount - notificationCount))"
writelog "$notificationsLeft remaining alerts before auto-install."
alert_user "$notificationsLeft remaining alerts before auto-install." "$notificationCount"
writelog "$notificationsLeft remaining deferrals."
alert_user "$notificationsLeft remaining deferrals." "$notificationCount"
fi
}

update_check() {
osVersion=$( /usr/bin/sw_vers -productVersion )
writelog "Determining available Software Updates for macOS $osVersion..."
updates=$(/usr/sbin/softwareupdate -l)
updatesNoRestart=$(echo "$updates" | grep -v restart | grep -B1 recommended | grep -v recommended | grep -v "\-\-" | sed 's|.*\* ||g')
updatesRestart=$(echo "$updates" | grep -i restart | grep -v '\*' | cut -d , -f 1)
updateCount=$(echo "$updates" | grep -i -c recommended)
update_file="/tmp/nice_updater_updates.txt"
/usr/sbin/softwareupdate --list > "$update_file"

# create list of updates that do not require a restart
updatesNoRestart=()
while IFS= read -r line; do
if [[ -n "$line" ]]; then
updatesNoRestart+=("$line")
writelog "Added '$line' to list of updates that do not require a restart"
fi
done <<< "$(grep -v restart "$update_file" | grep -B1 'Recommended: YES' | grep -v -i Recommended | grep -v '\-\-' | sed 's|.*\* ||g' | sed 's|^Label: ||')"

# create list of updates that do require a restart
updatesRestart=()
while IFS= read -r line; do
if [[ -n "$line" ]]; then
updatesRestart+=("$line")
writelog "Added '$line' to list of updates that require a restart"
fi
done <<< "$(grep -B1 'Recommended: YES, Action: restart' "$update_file" | grep -v restart | grep -v '\-\-' | sed 's|.*\* ||g' | sed 's|^Label: ||')"

updateCount=$(grep -c "Recommended: YES" "$update_file")

if [[ "$updateCount" -gt "0" ]]; then
# Download the updates
writelog "Downloading $updateCount update(s)..."
/usr/sbin/softwareupdate --download --recommended | grep --line-buffered Downloaded | while read -r LINE; do writelog "$LINE"; done
# writelog "Downloading $updateCount update(s)..."
# /usr/sbin/softwareupdate --download "${updatesNoRestart[@]}" | grep --line-buffered Downloaded | while read -r LINE; do writelog "$LINE"; done

# Don't waste the user's time - install any updates that do not require a restart first.
if [[ -n "$updatesNoRestart" ]]; then
# install any updates that do not require a restart, as these do not require authentication.
if [[ "${#updatesNoRestart[@]}" -gt 0 ]]; then
writelog "Installing updates that DO NOT require a restart in the background..."
while IFS='' read -r line; do
writelog "Updating: $line"
trigger_nonrestart_updates "$line"
done <<< "$updatesNoRestart"
/usr/sbin/softwareupdate --install "${updatesNoRestart[@]}"
fi

# If the script moves past this point, a restart is required.
if [[ -n "$updatesRestart" ]]; then
writelog "A restart is required for remaining updates."
# If no user is logged in, just update and restart. Check the user now as some time has past since the script began.
if [[ "${#updatesRestart[@]}" -gt 0 ]]; then
writelog "A restart is required for remaining updates"
# Abort if no user is logged in. Check the user now as some time has past since the script began.
loggedInUser=$(/usr/sbin/scutil <<< "show State:/Users/ConsoleUser" | /usr/bin/awk -F': ' '/[[:space:]]+Name[[:space:]]:/ { if ( $2 != "loginwindow" ) { print $2 }}')
# loggedInUID=$(id -u "$loggedInUser")
if [[ "$loggedInUser" == "root" ]] || [[ -z "$loggedInUser" ]]; then
writelog "No user logged in. Cannot proceed."
writelog "No user logged in. Cannot proceed"
else
# Getting here means a user is logged in, alert them that they will need to install and restart
alert_logic
Expand All @@ -204,11 +223,13 @@ update_check() {
fi
else
record_last_full_update
writelog "No updates that require a restart available; exiting."
writelog "No updates that require a restart available; exiting"
write_status "No updates that require a restart available; exiting"
finish 0
fi
else
writelog "No updates at this time; exiting."
writelog "No updates at this time; exiting"
write_status "No updates at this time; exiting"
/usr/libexec/PlistBuddy -c "Delete :last_empty_update_time" $preferenceFileFullPath 2> /dev/null
/usr/libexec/PlistBuddy -c "Add :last_empty_update_time string $(date +%Y-%m-%d\ %H:%M:%S)" $preferenceFileFullPath
/usr/libexec/PlistBuddy -c "Delete :users" $preferenceFileFullPath 2> /dev/null
Expand All @@ -225,7 +246,8 @@ main() {
# See if we are blocking updates, if so exit
updatesBlocked=$(/usr/libexec/PlistBuddy -c "Print :updates_blocked" $preferenceFileFullPath 2> /dev/null | xargs 2> /dev/null)
if [[ "$updatesBlocked" == "true" ]]; then
writelog "Updates are blocked for this client at this time; exiting."
writelog "Updates are blocked for this client at this time; exiting"
write_status "Updates are blocked for this client at this time; exiting"
finish 0
fi

Expand All @@ -241,27 +263,29 @@ main() {
if [[ -n "$lastFullUpdateTime" ]]; then
daysSinceLastFullUpdate="$(compare_date "$lastFullUpdateTime")"
if [[ "$daysSinceLastFullUpdate" -ge "$afterFullUpdateDelayDayCount" ]]; then
writelog "$afterFullUpdateDelayDayCount or more days have passed since last full update."
writelog "$afterFullUpdateDelayDayCount or more days have passed since last full update"
# delay script's actions by up to 1 min to prevent all computers running software update at the same time
random_delay
update_check
else
writelog "Less than $afterFullUpdateDelayDayCount days since last full update; exiting."
writelog "Less than $afterFullUpdateDelayDayCount days since last full update; exiting"
write_status "Less than $afterFullUpdateDelayDayCount days since last full update; exiting"
finish 0
fi
elif [[ -n "$lastEmptyUpdateTime" ]]; then
daysSinceLastEmptyUpdate="$(compare_date "$lastEmptyUpdateTime")"
if [[ "$daysSinceLastEmptyUpdate" -ge "$afterEmptyUpdateDelayDayCount" ]]; then
writelog "$afterEmptyUpdateDelayDayCount or more days have passed since last empty update check."
writelog "$afterEmptyUpdateDelayDayCount or more days have passed since last empty update check"
# delay script's actions by up to 1 min to prevent all computers running software update at the same time
random_delay
update_check
else
writelog "Less than $afterEmptyUpdateDelayDayCount days since last empty update check; exiting."
writelog "Less than $afterEmptyUpdateDelayDayCount days since last empty update check; exiting"
write_status "Less than $afterEmptyUpdateDelayDayCount days since last empty update check; exiting"
finish 0
fi
else
writelog "This device might not have performed a full update yet."
writelog "This device might not have performed a full update yet"
# delay script's actions by up to 1 min to prevent all computers running software update at the same time
random_delay
update_check
Expand Down
Loading

0 comments on commit a534cf8

Please sign in to comment.