1#!/bin/sh
2#set -x
3# Written Feb, 2018 by:
4# Mac Scott: [email redacted] and Chris Collins: [email redacted]
5# PURPOSE: A good natured, limitless deferral prompt to the computer user to remind them that
6# there are critical security updates that need to be applied.
7# Method: Set the frequency of your policy to hourly. Set the DAILY_LIMIT variable to the
8# number of days you are willing to prompt only once per day. After the policy has run
9# out the DAILY_LIMIT variable, it will be allowed to execute hourly.
10
11############################
12##### Global Variables #####
13UPDATES_NO_RESTART=""
14RESTART_REQUIRED=""
15JAMFHELPER_PATH="/Library/Application Support/JAMF/bin/jamfHelper.app/Contents/MacOS/jamfHelper"
16
17# Set the following for your environment
18DAILY_LIMIT=X # Number of days before going hourly
19RUNINDEX="/var/root/Library/Preferences/execution_period" # Path to file tracking executions
20ICON_PATH="/Library/Application Support/JAMF/bin/XXXX.png" # Corp logo branding option for dialog boxes
21UPDATE_TRIGGER="/usr/local/bin/jamf policy -event XXX" # jamf policy event command to run updates
22CONTACT_INFO="IT Support at XXX.XXX.XXXX or it@yourorg.com" # Contact info for your IT Support center
23JSSURL="https://jss.yourorg.com:8443" # For curl to record successful execution
24EPOCH_EXT_ATTRIB_ID=XX # Extension Attrib ID number for recording success
25EPOCH_EXT_ATTRIB_NAME="XXXXXXXX XXXXXXX XXXXXXXX" # Extension Attrib name for recording success
26API_AUTH="Basic XXXXXXXXXXXXXXXXXXXXXXXXXXX"
27
28############################
29######### Functions ########
30get_logged_in_user() {
31 /usr/bin/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 + "
32");'
33}
34
35execution_period_check() {
36 day=$(date '+%d')
37
38 # Check if an RUNINDEX file has been started by a previous execution
39 if [[ -e $RUNINDEX ]]; then
40 executions=$(wc -l $RUNINDEX | awk '{print $1}')
41 last_run=$(tail -1 $RUNINDEX)
42
43 # If it hasn't executed DAILY_LIMIT times, and has executed today, exit 0
44 if [[ $executions -le $DAILY_LIMIT ]] || [[ $day -eq $last_run ]]; then
45 echo "Has already run today and we are still in the 'daily' cycle. Exiting"
46 exit 0
47 else
48 echo "Hasn't been run in the appropriate period. Executing upgrade check."
49 fi
50 else
51 echo "Hasn't ever been run. Executing upgrade check."
52 fi
53
54 # If this execution is within the DAILY_LIMIT then register that it has been run today
55 if [[ $executions -lt $DAILY_LIMIT ]]; then
56 echo $day >> $RUNINDEX
57 fi
58}
59
60get_rec_updates() {
61 rec_updates=$(softwareupdate --list --recommended)
62 UPDATES_NO_RESTART=$(grep recommended <<"$rec_updates" | grep -v restart)
63 RESTART_REQUIRED=$(grep restart <<<"$rec_updates"
64 | grep -v '*'
65 | cut -d , -f 1)
66
67 # If there are no recommended updates, quit
68 if [[ "$UPDATES_NO_RESTART" == "" ]] && [[ "$RESTART_REQUIRED" == "" ]]; then
69 echo "No updates at this time"
70
71 # Computer has no pending recommended updates, so remove computer from scope to prevent repitition
72 remove_from_scope
73 rm "$RUNINDEX"
74 exit 0
75 fi
76}
77
78decide_updates() {
79 local current_user=$1
80 model_id=$(sysctl hw.model | awk -F'MacBookPro|,' '{print $2}')
81
82 # if there is no one logged in, just run the updates
83 if [[ "$current_user" == "" ]]; then
84 $UPDATE_TRIGGER
85
86 # Software updates have finally run, remove computer from scope to prevent repitition
87 remove_from_scope
88 rm "$RUNINDEX"
89 shutdown -r now
90 else
91 # Someone is logged in. prompt if any updates require a restart
92 if [[ "$RESTART_REQUIRED" != "" ]]; then
93
94 # Preconfigure message to end user for dialog box
95 # If this is a TouchBar Mac, warn user that update will take longer
96 if [[ $model_id -gt 12 ]]; then
97 description_text="These updates will require you to restart your Mac and could take 30 minutes. Please click 'Install' if you would like to do this now. If you have concerns, please contact $CONTACT_INFO"
98 else
99 description_text="These updates will require you to restart your Mac. Please
100click 'Install' if you would like to do this now. If you have concerns, please contact $CONTACT_INFO"
101 fi
102
103 # Execute jamfhelper dialog box, and collect result to variable
104 install_choice=$("$JAMFHELPER_PATH" -windowType hud -icon "$ICON_PATH" -heading "Critical Security Updates are Available" -description "$description_text
105
106The following updates require a restart of your Mac:
107$RESTART_REQUIRED" -button1 "Install" -button2 "Cancel" -cancelButton "2" -defaultButton 2)
108
109 if [[ "$install_choice" == "0" ]]; then
110 # Restart is required, so throw up another dialog box telling user that
111 # the dialog box will disappear when updates are staged/complete, and
112 # they should reboot the computer at that time.
113 run_updates
114 else
115 exit 1
116 fi
117 else
118 $UPDATE_TRIGGER
119
120 # Software updates have finally run, remove computer from scope to prevent repitition
121 remove_from_scope
122 rm "$RUNINDEX"
123 fi
124 fi
125}
126
127run_updates() {
128
129 "$JAMFHELPER_PATH" -windowType hud -lockHUD -heading "Security Updates have begun" -description "Your security updates are being applied. Please do not turn off this computer until this dialog box disappears. This message will go away when updates are complete. Note: the initial restart may take longer depending on the updates applied." -icon "$ICON_PATH" > /dev/null 2>&1 &
130
131 # We'll need the pid of jamfHelper to kill it once the updates are complete
132 jamf_helper_PID=$(echo "$!")
133
134 $UPDATE_TRIGGER &
135 # Get the Process ID of the last command run in the background ($!)
136 # and wait for it to complete (wait)
137 softw_upd_PID=$(echo "$!")
138 wait $softw_upd_PID
139
140 # Kill the jamfHelper. If a restart is needed, the user will be prompted.
141 # If not the hud will just go away
142 kill -s KILL $jamf_helper_PID
143
144 # Software updates have finally run, remove computer from scope to prevent repitition
145 remove_from_scope
146 rm "$RUNINDEX"
147 exit 0
148}
149
150remove_from_scope() {
151 # Solves the problem of "How do I run a policy at a continuous interval until first successful completion?"
152 # This function writes the epoch date of script's SUCCESSFUL execution to an ext attrib on JSS defined as an
153 # Integer value. If you define a Smart Group whose members are computers with a value higher than the epoch
154 # of the moment you enable a policy, then you can exclude members of this group from a policy's scope, such
155 # that the policy will continue to run at whatever frequency until successful, and then drop out of scope.
156
157 # Globals are in ALL_CAPS and need to be defined either in this function or in the script's globals.
158 epoch=$(date +%s)
159 udid=$(system_profiler SPHardwareDataType
160 | grep UUID
161 | awk '{print $3}')
162
163 curl -k -s -X "PUT" "$JSSURL/JSSResource/computers/udid/$udid/subset/extension_attributes"
164 -H "Content-Type: application/xml"
165 -H "Authorization: $API_AUTH"
166 -H "Accept: application/xml"
167 -d "<computer><extension_attributes><extension_attribute><id>$EPOCH_EXT_ATTRIB_ID</id><name>$EPOCH_EXT_ATTRIB_NAME</name><type>String</type><value>$epoch</value></extension_attribute></extension_attributes></computer>"
168 & > /dev/null 2>&1
169}
170
171main() {
172 execution_period_check
173 get_rec_updates
174 logged_in_user=$(get_logged_in_user)
175 decide_updates $logged_in_user
176}
177
178############################
179######## Execution #########
180
181main