Posted on 07-01-2014 07:13 AM
Just wanted to get a general consensus as to what method people prefer when running software updates on their clients.
Options I've seen so far are:
Your preference???
Posted on 07-01-2014 08:01 AM
There is:
Patchoo - https://jamfnation.jamfsoftware.com/discussion.html?id=10636
Munki - https://code.google.com/p/munki/
And various scripts on this site...
Posted on 07-01-2014 08:19 AM
Internal software update server.
Touch a flag file on the machines, checked at logout.
Use iHook to inform the user what is going on.
Posted on 07-01-2014 08:29 AM
Twice a day we will display a message and open Self Service for the end-user to install any cached packages and ASU's. Once they install all cached updates and all software updates, we stop harassing them.
Of course if there is a security issue, we just push it and let them know we are pushing it.
Posted on 07-01-2014 09:03 AM
@ooshnoo FWIW, might want to peruse this thread... :)
Will JSS 10 finally bring us easy patch management?
https://jamfnation.jamfsoftware.com/discussion.html?id=10961
Posted on 07-01-2014 09:49 AM
Thanks fellas... I've seen Munki and Patchoo, but unfortunately I'm a one man show here, managing over 500 Macs and 3000 Windows machines using SCCM, so my time is very limited and have not been able to implement, let alone research either of the two. So I'm kind of at the mercy of the built in Casper tools at this point.
Posted on 07-01-2014 10:32 AM
We do software and security patching once every 30 days, so we created a Once Per Computer policy that's scoped to the All Managed Clients smart group. Using that policy we direct all of the client machines to an internal Software Update Server (Mac mini, Software Update Service enabled, managed manually).
This allows us to automatically download a local copy of all updates Apple releases to the server, but only enable the ones that we want our clients to get during that time. Once we're ready, we simply flush the policy log and the Casper agent does the rest.
Posted on 07-01-2014 11:46 AM
@ooshnoo Using a supported product has a tendency to free up more of your time for other stuff.
Thanks fellas... I've seen Munki and Patchoo, but unfortunately I'm a one man show here, managing over 500 Macs and 3000 Windows machines using SCCM, so my time is very limited and have not been able to implement, let alone research either of the two. So I'm kind of at the mercy of the built in Casper tools at this point.
Posted on 07-01-2014 12:02 PM
I'd use the built in tools for now as a stop gap, but definitely look at the 3rd party stuff for long term.
There's quite a bit you can do to automate things, even automate the automated package building (Jenkins and AutoPKG). A few hours up front spent can save you countless hours tomorrow.
Posted on 06-13-2018 09:37 AM
Thread necromancy I know, but I'm having way too much trouble finding what should be a simple answer and this thread does not give me hope. Where is the simple option that really has to exist that is "every day at noon check for software updates and install them if available"? I mean, this is basic system management built into Windows GP, I can't believe for a second you can't do this on Mac with an expensive MDM...
Posted on 06-13-2018 10:23 AM
@ebonweaver short answer, there isn't one. You could get close to noon, or whatever time, by using the Client Side Limitations of a policy. But the jamf
binary randomizes the time that it runs, so it may not be right at noon, it may be shortly after noon, or before.
The better answer would be to use a LaunchDaemon on the system that is scheduled to run right at noon. This would be like running a cron
job in the old days before LaunchD.
Posted on 06-13-2018 10:31 AM
You can use a script to do this calling the softwareupdate
binary, but be aware some updates naturally require restarts. The problem is, when installed from command line softwareupdate
, the Mac won't just restart after it installed. It states a restart is needed after installing any updates that require that, but it will be up to you to ensure that actually happens. Otherwise any Macs that install such updates will be left in an odd state that could lead to stability issues for them.
But for basic simplicity, the following command will install all available updates, if any
/usr/sbin/softwareupdate -ia
You can drop that into the Execute Command field in the Files and Processes tab of a policy, as an example, or you could build a more involved script that first checks to see what's available and takes things from there. All this of course requires your Macs to be pointed to some kind of Software Update service, whether something you run internally, or just the default Apple one.
Posted on 06-13-2018 01:23 PM
Thanks guys. Real disappointing to hear this simply does not exist. I was baffled why under the Software Update policy/profile settings there simply were no settings, now I know. Other than if you ran your own SUS, which as I understand has been removed from Server... well that pretty much makes this a useless non-setting only there to confuse and disappoint. I guess I shouldn't be too shocked since there's no ability to manage computer names at all, negating the entire purpose of smart groups... but I digress.
Posted on 06-14-2018 05:26 PM
We are waiting on approval for a policy that checks available critical software updates and splashes a jamfhelper dialog box up to the end user. If the employee clicks "Cancel" it executes again the next day. If they cancel too many times, it executes on the hour until they comply.
If the update requires a reboot, once the employee clicks "Install" they will get a second dialog box urging them not to reboot their computer until that dialog box quits.
Posted on 06-15-2018 09:39 AM
Signet can you provide information on how you did that? I've never seen anything like that in JAMF, and see no options that would support what you describe.
Posted on 06-15-2018 09:53 AM
@ebonweaver they are using jamfhelper
to provide the dialog boxes: /Library/Application Support/JAMF/bin/jamfhelper.app/Contents/MacOS/jamfhelper
There is a helper GUI app that you can use to build the code necessary:
Posted on 06-15-2018 11:41 AM
Steve, I'm referring more to the rest of what he said.
"a policy that checks available critical software updates and splashes a jamfhelper dialog box up to the end user"
How is this policy crafted? What commands are being used in what way? Since you can't seem to do this with Software Update, is this all in a script that is being called from the Policy? Or are other functions being used?
"If the update requires a reboot"
Again, how is this determined? Where does the branching logic happen? If it's all one shell script, can we see said script? If there are multiple moving parts, how do they fit together?
It looks and sounds like an elegant solution, but based on other info I'm not getting how it was created.
Thanks!
Posted on 06-18-2018 07:38 AM
Here you go @ebonweaver
My apologies for the delayed response. I took vacation last Friday to bring my kid back home after his freshman year at CalPoly.
The following script does the trick. The function "execution_period_check" writes a file and adds to it every day the policy runs for the first X days as defined in the global DAILY_LIMIT. Set the policy execution to hourly, and limit the hours to work/school/daylight hours appropriately so the prompt doesn't come up at 1AM. If it detects that it has already been run that day, and it is still on the "daily run cycle" it will exit before any further action is taken. After $DAILY_LIMIT days, it will just run the full script hourly until the person takes action.
The function "remove_from_scope" requires that you have created a blank Integer based Extension Attrib beforehand and given it's ID number and name in the globals. Then create a Smart Group whose membership is computers with numeric values of this EA that are greater than the number you set. To set the number in the Smart Group to compare the EA to when you make the policy live by feeding it the epoch date at that moment
date +%s
...and exclude from the softwareupdate policy members of that Smart Group. On the client computer, the script will write to JSS with an API call the epoch date upon the employee/student successfully executing an install, and thus the computer will drop out of scope.
#!/bin/sh
#set -x
# Written Feb, 2018 by:
# Mac Scott: [email redacted] and Chris Collins: [email redacted]
# PURPOSE: A good natured, limitless deferral prompt to the computer user to remind them that
# there are critical security updates that need to be applied.
# Method: Set the frequency of your policy to hourly. Set the DAILY_LIMIT variable to the
# number of days you are willing to prompt only once per day. After the policy has run
# out the DAILY_LIMIT variable, it will be allowed to execute hourly.
############################
##### Global Variables #####
UPDATES_NO_RESTART=""
RESTART_REQUIRED=""
JAMFHELPER_PATH="/Library/Application Support/JAMF/bin/jamfHelper.app/Contents/MacOS/jamfHelper"
# Set the following for your environment
DAILY_LIMIT=X # Number of days before going hourly
RUNINDEX="/var/root/Library/Preferences/execution_period" # Path to file tracking executions
ICON_PATH="/Library/Application Support/JAMF/bin/XXXX.png" # Corp logo branding option for dialog boxes
UPDATE_TRIGGER="/usr/local/bin/jamf policy -event XXX" # jamf policy event command to run updates
CONTACT_INFO="IT Support at XXX.XXX.XXXX or it@yourorg.com" # Contact info for your IT Support center
JSSURL="https://jss.yourorg.com:8443" # For curl to record successful execution
EPOCH_EXT_ATTRIB_ID=XX # Extension Attrib ID number for recording success
EPOCH_EXT_ATTRIB_NAME="XXXXXXXX XXXXXXX XXXXXXXX" # Extension Attrib name for recording success
API_AUTH="Basic XXXXXXXXXXXXXXXXXXXXXXXXXXX"
############################
######### Functions ########
get_logged_in_user() {
/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 + "
");'
}
execution_period_check() {
day=$(date '+%d')
# Check if an RUNINDEX file has been started by a previous execution
if [[ -e $RUNINDEX ]]; then
executions=$(wc -l $RUNINDEX | awk '{print $1}')
last_run=$(tail -1 $RUNINDEX)
# If it hasn't executed DAILY_LIMIT times, and has executed today, exit 0
if [[ $executions -le $DAILY_LIMIT ]] || [[ $day -eq $last_run ]]; then
echo "Has already run today and we are still in the 'daily' cycle. Exiting"
exit 0
else
echo "Hasn't been run in the appropriate period. Executing upgrade check."
fi
else
echo "Hasn't ever been run. Executing upgrade check."
fi
# If this execution is within the DAILY_LIMIT then register that it has been run today
if [[ $executions -lt $DAILY_LIMIT ]]; then
echo $day >> $RUNINDEX
fi
}
get_rec_updates() {
rec_updates=$(softwareupdate --list --recommended)
UPDATES_NO_RESTART=$(grep recommended <<"$rec_updates" | grep -v restart)
RESTART_REQUIRED=$(grep restart <<<"$rec_updates"
| grep -v '*'
| cut -d , -f 1)
# If there are no recommended updates, quit
if [[ "$UPDATES_NO_RESTART" == "" ]] && [[ "$RESTART_REQUIRED" == "" ]]; then
echo "No updates at this time"
# Computer has no pending recommended updates, so remove computer from scope to prevent repitition
remove_from_scope
rm "$RUNINDEX"
exit 0
fi
}
decide_updates() {
local current_user=$1
model_id=$(sysctl hw.model | awk -F'MacBookPro|,' '{print $2}')
# if there is no one logged in, just run the updates
if [[ "$current_user" == "" ]]; then
$UPDATE_TRIGGER
# Software updates have finally run, remove computer from scope to prevent repitition
remove_from_scope
rm "$RUNINDEX"
shutdown -r now
else
# Someone is logged in. prompt if any updates require a restart
if [[ "$RESTART_REQUIRED" != "" ]]; then
# Preconfigure message to end user for dialog box
# If this is a TouchBar Mac, warn user that update will take longer
if [[ $model_id -gt 12 ]]; then
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"
else
description_text="These updates will require you to restart your Mac. Please
click 'Install' if you would like to do this now. If you have concerns, please contact $CONTACT_INFO"
fi
# Execute jamfhelper dialog box, and collect result to variable
install_choice=$("$JAMFHELPER_PATH" -windowType hud -icon "$ICON_PATH" -heading "Critical Security Updates are Available" -description "$description_text
The following updates require a restart of your Mac:
$RESTART_REQUIRED" -button1 "Install" -button2 "Cancel" -cancelButton "2" -defaultButton 2)
if [[ "$install_choice" == "0" ]]; then
# Restart is required, so throw up another dialog box telling user that
# the dialog box will disappear when updates are staged/complete, and
# they should reboot the computer at that time.
run_updates
else
exit 1
fi
else
$UPDATE_TRIGGER
# Software updates have finally run, remove computer from scope to prevent repitition
remove_from_scope
rm "$RUNINDEX"
fi
fi
}
run_updates() {
"$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 &
# We'll need the pid of jamfHelper to kill it once the updates are complete
jamf_helper_PID=$(echo "$!")
$UPDATE_TRIGGER &
# Get the Process ID of the last command run in the background ($!)
# and wait for it to complete (wait)
softw_upd_PID=$(echo "$!")
wait $softw_upd_PID
# Kill the jamfHelper. If a restart is needed, the user will be prompted.
# If not the hud will just go away
kill -s KILL $jamf_helper_PID
# Software updates have finally run, remove computer from scope to prevent repitition
remove_from_scope
rm "$RUNINDEX"
exit 0
}
remove_from_scope() {
# Solves the problem of "How do I run a policy at a continuous interval until first successful completion?"
# This function writes the epoch date of script's SUCCESSFUL execution to an ext attrib on JSS defined as an
# Integer value. If you define a Smart Group whose members are computers with a value higher than the epoch
# of the moment you enable a policy, then you can exclude members of this group from a policy's scope, such
# that the policy will continue to run at whatever frequency until successful, and then drop out of scope.
# Globals are in ALL_CAPS and need to be defined either in this function or in the script's globals.
epoch=$(date +%s)
udid=$(system_profiler SPHardwareDataType
| grep UUID
| awk '{print $3}')
curl -k -s -X "PUT" "$JSSURL/JSSResource/computers/udid/$udid/subset/extension_attributes"
-H "Content-Type: application/xml"
-H "Authorization: $API_AUTH"
-H "Accept: application/xml"
-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>"
& > /dev/null 2>&1
}
main() {
execution_period_check
get_rec_updates
logged_in_user=$(get_logged_in_user)
decide_updates $logged_in_user
}
############################
######## Execution #########
main
Posted on 06-19-2018 04:35 PM
We take an approach of. We give our staff a weeks notice to do os updates on their own.. And after that we'll assist them by giving them notice Fri eve and to the nest week that we're updating their machine. The notice gives them 10 minutes to finish off their work before it kicks off a software install.
The following is what we use, this is just the notifier, we automatically install any updates that don't require a reboot, and then upon any of our staff rebooting their macs we update it then. This only furthers that process when they aren't going to reboot.
#!/bin/bash
###################################################################################
## Forced reboot for Software that needs updating
## “Lisa, I’d like to buy your rock.”
## Created: Dec 2017
## Last Edit: Dec 01, 2017
## Created by Ross Derewianko
###################################################################################
/usr/local/bin/jamf recon
echo "prompting user for reboot"
curl -o /var/tmp/<templogo> <logourl>
sudo -u $(ls -l /dev/console | awk '{print $3}') /Library/Application Support/JAMF/bin/jamfHelper.app/Contents/MacOS/jamfHelper -windowType utility -title "Software Update Notification" -timeout 600 -countdown -description "Software Update(s) are ready to install on your machine. Please save all your work and then click RESTART to proceed. You have ten minutes to save your work before this window auto-closes and the update begins." -icon "/var/tmp/<templogo>" -button1 "RESTART"
echo "notifying user"
sudo -u $(ls -l /dev/console | awk '{print $3}') osascript -e 'tell application "System Events" to restart'
osascript -e 'tell application "System Events" to tell process "SecurityAgent" to click button "Restart" of window 1'
sleep 600
echo "restarting"
shutdown -r now
exit 0
Posted on 09-03-2018 08:17 AM
@signetmac
This looks like it something we have been looking for but we are a little lost on how to start. Could you explain which policies you have running? How many policies are required? Do we have to fill in the EA manually, then scope the exclusion to anything higher than that EA integer value? Do we need to put our API password in the script in plain text, not too happy about doing this?
Anymore info would be great.
Cheers.
Posted on 09-03-2018 11:40 AM
No you don't have to preset the EA. Leave it blank the first time you run it. The Smart Group is where you define the epoch date in order to control the scope. The epoch date as I write this to you is:
US-ML:~ macscott$ date +%s 1535998930
When the Smart Group is scoped for the first time, the blank value will cause the if statement to fail and then the computer will run the policy and set it's own epoch value after it successfully runs software updates, dropping the computer out of scope because that EA value it writes will be higher than the one you defined in the Smart Group.
You don't have to include the API pass in the actual script. The workflow in the script above relies on a hash of the password for simplicity's sake... when I posted this script, I didn't want to make it overly complicated and difficult to understand. But we actually use the workflow defined here by JAMF: Encrypted Script Parameters
You could alternatively (and less securely than the JAMF workflow cited above) simply pass the hashed password when you call the script from the policy in one of the script variables.
Posted on 05-08-2019 08:49 AM
@signetmac this is awesome, I am testing this out today, I am a little confused about EPOCH. Am I just assigning a random number to EPOCH_EXT_ATTRIB_ID like the number 01 for example? And then for EPOCH_EXT_ATTRIB_NAME the name of the attribute I created in Jamf? And for API_AUTH, can I just use my password here? Am I supposed to put username/password?
Posted on 05-08-2019 09:12 AM
I put the name of the policy in EPOCH_EXT_ATTRIB_NAME and that worked fine. I left EPOCH_EXT_ATTRIB_ID blank and left API_AUTH and the deferral prompt worked. I deferred for one day. I am not sure if I need to do anything to EPOCH_EXT_ATTRIB_ID and API_AUTH? I guess I don't understand the importance of EPOCH in this script. But this works like a charm and is not intrusive until the end user reaches their daily_limit. This is awesome. Ty @signetmac you are awesome!!!
Edit: Also when did you set the policy to execute? Once per Day, Ongoing, etc?
Posted on 04-13-2020 07:57 AM
@signetmac I know this is almost 2 years old now, but I wanted to adapt and simplify it for our environment. I removed the API call since we'd handle the trigger by scope (ie once a machine has upgraded to Catalina, the policy will be excluded).
I had a questions about these lines, though:
# If it hasn't executed DAILY_LIMIT times, and has executed today, exit 0 if [[ $executions -le $DAILY_LIMIT ]] || [[ $day -eq $last_run ]]; then
Shouldnt it be "if [[ $executions -le $DAILY_LIMIT ]] && [[ $day -eq $last_run ]]; then" instead, since your if statement is inclusive (If x AND Y)?
I also noticed some issues with string substitutions, so needed to add quotes around the $RUNINDEX variable in the following locations:
executions=$(wc -l "$RUNINDEX" | awk '{print $1}')
last_run=$(tail -1 "$RUNINDEX")