Posted on 03-28-2016 11:57 AM
Has anyone come up with a good way to determine if pending Apple updates require a restart?
I've heard of folks writing an EA that runs
softwareupdate -l | grep "restart"
but that seems a bit time consuming since software update is already running during recon.
Does anyone have an easier way to do it?
Posted on 03-28-2016 12:01 PM
In a previous gig, I turned off the automatic Software Update checking in recon, and created an EA that performed a single softwareupdate -l
, stored that in a variable, then parsed if it required restarts or not. The EA had different results like "No Updates Available", "Updates Available: Restart Required", and "Updates Available: No Restart Required"
I don't have access to that EA anymore, but if you decide to go that route, it shouldn't be too hard to write.
Posted on 03-28-2016 01:18 PM
This is the section of code I use for managing updates. You might be able to pull something useful out of it.
checkAppleUpdates(){
echo "Checking for Apple Updates"
updateList=`softwareupdate -l 2>&1` #2>&1 redirects stderr to stdout so it'll be available to grep. No New software available is a STDERR message instead of STDOUT
nextWeek=`date -v +1d +%m-%d-%Y`
updatesNeeded=`echo "$updateList" |grep "No new software available"`
hasError=`echo $updateList |grep "load data from the Software Update server"`
`echo "$updateList" > /tmp/appleSWupdates.txt`
if [[ ! $updatesNeeded =~ "No new software available" ]]; then
if [[ ! $hasError =~ "Can't load data from the Software Update server" ]]; then
updateMiniList=`echo "$updateList" |grep *` #gives list of updates with no other crap
echo "$updateMiniList"| while read line;do
jssPolicy=`echo "$line" |sed 's/* //g'`
appNameWithWhiteSpace=`echo "$updateList" |grep -A1 "$jssPolicy"|tail -1|awk -F( '{print $1}'`
appName="$(echo "${appNameWithWhiteSpace}" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')"
latestVersion=`echo "$updateList" |grep -A1 "$jssPolicy"|tail -1|awk -F( '{print $2}'|awk -F) '{print $1}'`
installBy="$nextWeek 11:30"
rebootNeeded=`echo "$updateList" |grep -A1 "$jssPolicy"|tail -1|grep restart`
if [[ -z $rebootNeeded ]]; then
rebootStatus=false
else
rebootStatus=true
fi
#copy over existing record (to preserve due date) if it already already exists
echo "{source:"Apple",appName:"$appName",appVersion:"$latestVersion",jssPolicy:"$jssPolicy",dueDate:"$installBy",appInstallChk:true,reboot:$rebootStatus}" >> /usr/local/Todd/outfile.txt
fi
done
fi
fi
}
Posted on 03-29-2016 01:35 AM
Just the act of running softwareupdate populates a file of information for you (unfortunately lacking restart!) and they even provide information as to how many updates there are (the length of the array). Running softwareupdate and using grep for restart will still return "No new software available." if there aren't any.
# softwareupdate -l | grep -i restart
No new software available.
After running this, check the update plist if you have updates.
defaults read /Library/Preferences/com.apple.SoftwareUpdate.plist RecommendedUpdates
defaults read /Library/Preferences/com.apple.SoftwareUpdate.plist LastRecommendedUpdatesAvailable
Reading updates this way has a small bonus. For example, if you have two updates that are still active where one supersedes the other, then only the latest required will show in the plist. Populating your own file will mean you may run unnecessary updates. (Of course, not that any of us ever forget to turn off an older update).
Finding available software
Software Update found the following new or updated software:
* Gatekeeper Configuration Data-86
Gatekeeper Configuration Data (86), 3443K [recommended]
* Gatekeeper Configuration Data-87
Gatekeeper Configuration Data (87), 3443K [recommended]
defaults read /Library/Preferences/com.apple.SoftwareUpdate.plist RecommendedUpdates
(
{
"Display Name" = "Gatekeeper Configuration Data";
"Display Version" = 87;
Identifier = "Gatekeeper Configuration Data";
"Product Key" = "031-54379";
},
)
And the update names are effectively "[Identifier]-[Display Version]"
So with a bit of working through the array and PlistBuddy you could end up with a neater solution. (It's on my to do list. I've kind of done some, but it is wrapped with other stuff).
As for the original question, yeah, why run softwareudpate, which is slow, inside an EA. I assume you can't grab the info from the recon then (I don't have Casper at the mo). There are a bunch of ways to do this, e.g. LaunchDaemons or LaunchAgents doing Calendar or WatchPaths, to limit when it runs, populate your own plist and read that in the EA.
Posted on 03-29-2016 06:03 AM
@sean I didn't know that plist existed. Thanks for that tip! That'll clean up my code a bit!
Posted on 05-12-2016 11:08 AM
So, borrowing from some of the scripts I've found here, I created an EA using the following script that will return:
NoUpdate (If no Updates are available)
NoRestart (If Updates are available, but none require a restart)
Restart (If updates requiring a restart are available)
#!/bin/sh
#check for apple software updates that require a restart
updateList=`softwareupdate -l 2>&1`
#2>&1 redirects stderr to stdout so it'll be available to grep. No New software available is a STDERR message instead of STDOUT
updatesNeeded=`echo "$updateList" |grep -i "No new software available"`
rebootNeeded=`echo $updateList |grep -i "Restart"`
if [[ ! $updatesNeeded =~ "No new software available" ]]; then
if [[ -z $rebootNeeded ]]; then
rebootStatus=NoRestart
else
rebootStatus=Restart
fi
else
rebootStatus=NoUpdate
fi
echo "<result>$rebootStatus</result>"
I then have 2 Software Update policies one for restart required and one for no restart required that I scope to a Smart group based on the output of the EA and make them available in Self Service.
Posted on 05-12-2016 11:19 AM
But, as @kitzy already mentioned, by calling softwareupdate -l
you are invoking a full blown check against Apple's servers, or your own internal SUS to get that data, when it may in fact already be done by your regular inventory collection process. That's a lot of overhead it seems to me. If Apple could make the Software Update check run a bit faster it wouldn't be a real issue, but as it is, it remains one of the slower processes you can call. On a moderate to slow connection, you might be looking at adding almost 2 minutes to your recon times, every time it runs. Doesn't sound like a great idea to me.
Instead, I might look at having a policy run maybe once or at most twice a day that pipes the softwareupdate -l command into a local file and then use an EA to scrape info out of it. I realize that it won't always be accurate, but it shouldn't be "off" too often I'd imagine.
If Apple was adding details about restart needed to that plist that @sean pointed out, we could use that almost exclusively, so too bad that important information isn't contained there.
Posted on 05-12-2016 04:06 PM
defaults read /Library/Preferences/com.apple.SoftwareUpdate.plist
Also has a few date fields that are updated by certain events (i.e. softwarreupdate -l updates the LastSuccessfulDate field, as I imagine an actual install probably does to).
You might be able to use these in some way to determine whether a softwarreupdate -l was neccesary or not, they are in +0000 time format so take that into consideration if you don't live in Greenwich :-)
Posted on 05-13-2016 09:10 AM
That's a great point. I will test and see if I can replace the line:
updateList=softwareupdate -l 2>&1
with:
updateList=`defaults read /Library/Preferences/com.apple.SoftwareUpdate.plist
and grep that file to set the reboot status.`
Didn't know about that file.
Thanks
Posted on 05-13-2016 09:15 AM
Ah, but the problem as was already mentioned is, that file, while having a nice amount of info, doesn't show if any of the recommended updates require a reboot sadly. I've been looking at this and doing some investigation to see if there's some method of determining that using the product IDs perhaps, but Apple doesn't seem to have a consistent way of getting details on their updates in a scripted manner. I have a feeling it may be possible, but its going to take some digging and experimentation.
Posted on 05-13-2016 09:35 AM
So... I'm thinking to maybe run the "defaults read /Library/Preferences/com.apple.SoftwareUpdate.plist" to determine if any updates are available. If not then end, but if there are then run "softwareupdate -l" to determine if they need a reboot?
#!/bin/sh
#check for apple software updates that require a restart
updatePlist='defaults read /Library/Preferences/com.apple.SoftwareUpdate.plist'
updatesNeeded=`echo "$updatePlist" |grep -i "LastUpdatesAvailable = 0;"`
if [[ ! $updatesNeeded =~ "LastUpdatesAvailable = 0" ]]; then
updateList='softwareupdate -l'
rebootNeeded='echo $updateList |grep -i "Restart"'
if [[ -z $rebootNeeded ]]; then
rebootStatus=NoRestart
else
rebootStatus=Restart
fi
else
rebootStatus=NoUpdate
fi
echo "<result>$rebootStatus</result>"
Thoughts?
Posted on 05-13-2016 09:49 AM
I suppose I would look to see if updates require a reboot, then save the info in a file in /tmp/. If file is gone, then look again. The file would be accurate because /tmp/ is cleared at reboot.
Posted on 05-13-2016 10:29 AM
Here's a version of this EA that returns if a reboot is needed or not. It'll check Apples servers after a reboot or every 24 hours.
#!/bin/sh
#check for apple software updates that require a restart
#thoule
updatePlist=`defaults read /Library/Preferences/com.apple.SoftwareUpdate.plist`
updatesNeeded=`echo "$updatePlist" |grep -i "LastUpdatesAvailable = 0;"`
maxAge=86400 #24 hours
function checkASU {
updateList=`softwareupdate -l`
rebootNeeded=`echo $updateList |grep -i "Restart"`
defaults write /tmp/ASUReboot.plist dateChecked -int $now
if [[ -z $rebootNeeded ]]; then
rebootStatus=NoRestart
defaults write /tmp/ASUReboot.plist rebootNeeded -string "NoReboot"
else
rebootStatus=Restart
defaults write /tmp/ASUReboot.plist rebootNeeded -string "Reboot"
fi
}
if [[ ! $updatesNeeded =~ "LastUpdatesAvailable = 0" ]]; then
if [ -f /tmp/ASUReboot.plist ]; then
dateChecked=`defaults read /tmp/ASUReboot.plist dateChecked`
else
dateChecked=0
fi
now=`date +%s`
dateDiff=`expr $now - $dateChecked`
if [ "$dateDiff" -gt $maxAge ]; then
checkASU
else
rebootStatus=`defaults read /tmp/ASUReboot.plist rebootNeeded`
fi
else
echo "<result>Updates:0 Reboot:NoReboot</result>"
fi
updateCount=`defaults read /Library/Preferences/com.apple.SoftwareUpdate.plist LastRecommendedUpdatesAvailable`
echo "<result>Updates:$updateCount Reboot:$rebootStatus</result>"
Posted on 05-13-2016 11:03 AM
@thoule Your bash skills are much greater than mine.
Thanks.
Posted on 05-13-2016 11:08 AM
@jason.bracy Thanks for the applause, but yours will be if you keep it up. I posted in this thread so people can see the development of each script iteration and learn. Feel free to ask questions!
Posted on 05-13-2016 11:31 AM
Just to bring this discussion full circle:
In El Capitan: softwareupdate -l --no-scan | grep restart
will tell you if any of the already-downloaded Apple updates need a restart without triggering a time-and-resource-intensive full softwareupdate scan.
This of course, implies that the data you seek already exists on disk. Since updates are cached in /Library/Updates, we can look in there...
Each update is stored in a folder named like "041-12345" and contains at least one dist file. Reading the dist file, you can look for onConclusion="RequireRestart" -- if you find that, that update requires a restart.
Writing a script to scan and parse the /Library/Updates dist files is an exercise left for the reader.
Posted on 05-13-2016 12:03 PM
Yep, I'm already exploring the angle that @gregneagle pointed out above. To be clear, I don't necessarily require having an EA that reports if any reboot updates are needed, we don't really care here, but I was naturally curious about different possible ways to approach this problem. I think, as Greg points out, the best approach is to pre-download any available updates to /Library/Updates/ which all come down into separate directories labeled with the "Product Key" identifier, and then scan those for ones that need reboots. Working on a script that I may post here once I'm got it all set that may help with that.
Posted on 05-13-2016 04:21 PM
There's code one can steal/repurpose from Reposado to parse softwareupdate .dist files -- no need to start from scratch.
https://github.com/wdas/reposado/blob/master/code/repo_sync#L152
Might be a good start.
Posted on 05-19-2016 02:16 PM
So circling back on this for a moment, here's the script I came up with. Note that for this to actually work effectively, its best to have the "Download newly available updates in the background" option enabled in System Preferences > App Store. Its a good idea to have that enabled anyway, though the script could still work in cases where its not on by default.
Quick explanation of what its doing.
It combines reading the RecommendedUpdates array from the com.apple.SoftwareUpdate.plist
file, which as noted above, gets updated each time a software update check occurs, and looks through the locally downloaded updates. The script matches up the local downloads with the ProductKeys
from the array, making sure any recommended installs are also downloaded. If they are all present, it creates arrays from the plist data, loops over the local folders and determines which ones need restarts by grepping against the .dist file in that folder.
If it needs to download updates first, it will do that and then look over the results. (hence why its best to just have them download in the background automatically)
Since its totally possible to have now deprecated or superseded updates lurking in the /Library/Updates/ directory, the scipt will only scan over the ones it sees from the plist and ignore the others. That way if a Security update was downloaded but never installed and gets superseded by a newer Security Update, it will only check for the one that Software Update recommends from the last successful check it performed.
I also added in a little flexibility with some options you can enable/disable near the top. You can make the script run purely as an Extension Attribute, so it will print out any results it finds wrapped in result tags, or it can send the results to a local log file to be picked up later by an EA. Or you can have it do both at the same time.
Here's the script in case its useful to anyone:
#!/bin/bash
## Get available Software Updates and determine which need reboots when installed
##################### Script behavior options (Choose one or both options) #####################
## Set the below variable to Yes to make the script operate as an Extension Attribute
EAScript="Yes"
## Set the below variable to Yes to have the script export the results out to a local log file
LogScript="Yes"
################################################################################################
## Local file where reboot required update names get stored (if option above is enabled)
Reboot_Needed_Log="/Library/Application Support/Reboot_Updates_Needed"
## Gets the names of downloaded updates that require a reboot by scanning for the reboot flag in the dist file
function get_Reboot_Updates ()
{
i=0
while read folder; do
if [[ -e "/Library/Updates/${folder}" ]]; then
while read distFile; do
if [[ $(grep -i restart "$distFile") ]]; then
RestartPkgs+=("${updateIdentifiers[$i]}-${updateVersions[$i]}")
fi
done < <(find "/Library/Updates/${folder}" -name *.dist)
let i=$((i+1))
else
echo "Could not find path /Library/Updates/${folder}"
fi
done < <(printf '%s
' "${productKeys[@]}")
if [[ "${RestartPkgs[@]}" != "" ]]; then
echo "Some updates require a reboot"
if [ "$EAScript" ]; then
## prints the reboots needed results in Casper Suite Extension Attribute format
echo "<result>$(printf '%s
' "${RestartPkgs[@]}")</result>"
fi
if [ "$LogScript" ]; then
echo "Adding details to local file..."
## Sends the reboots needed result into local log file
printf '%s
' "${RestartPkgs[@]}" > "$Reboot_Needed_Log"
exit 0
fi
else
echo "No reboot updates are available at this time..."
if [ "$EAScript" ]; then
echo "<result>None</result>"
fi
if [ "$LogScript" ]; then
echo "Nothing to write to log file"
exit 0
fi
fi
}
## Creates several arrays with values extracted from the SoftwareUpdate plist (to be used later)
function get_Update_Stats ()
{
echo "Getting update stats..."
RecommendedUpdatesData=$(/usr/bin/defaults read /Library/Preferences/com.apple.SoftwareUpdate.plist RecommendedUpdates)
while read identifier; do
updateIdentifiers+=("$identifier")
done < <(awk -F'= ' '/Identifier/{print $NF}' <<< "$RecommendedUpdatesData" | sed 's/"//g;s/;//g')
while read version; do
updateVersions+=("$version")
done < <(awk -F'= ' '/Display Version/{print $NF}' <<< "$RecommendedUpdatesData" | sed 's/"//g;s/;//g')
while read Key; do
productKeys+=("$Key")
done < <(awk -F'= ' '/Product Key/{print $NF}' <<< "$RecommendedUpdatesData" | sed 's/"//g;s/;//g')
while read Name; do
displayNames+=("$Name")
done < <(awk -F'= ' '/Display Name/{print $NF}' <<< "$RecommendedUpdatesData" | sed 's/"//g;s/;//g')
echo "Looking for reboot required updates..."
get_Reboot_Updates
}
## Checks the current downloaded updates versus what Software Update's last check saw as being available
check_Downloads_Vs_AvailUpdates ()
{
## While reading over the recommended update IDs, check to see if a matching directory exists in /Library/Updates/
## If any are not found, set a flag to get new downloads
while read item; do
if [[ $(echo "$downloadedUpdates" | grep "$item") == "" ]]; then
getDownloads="Yes"
fi
done < <(printf '%s
' "$RecommendedUpdateKeys")
## If the get downloads flag was set, first download available updates before continuing
if [ "$getDownloads" ]; then
echo "Some available updates are not already downloaded. Downloading all available updates..."
softwareupdate -d -a
get_Update_Stats
else
## Or, if all downloads are accounted for, move to grabbing the values from the plist
echo "All available updates have been downloaded. Skipping to checking the update stats..."
get_Update_Stats
fi
}
## Remove any previous reboot needed log file
if [ -e "/Library/Application Support/Reboot_Updates_Needed" ]; then
rm "/Library/Application Support/Reboot_Updates_Needed"
fi
## Get the last recommended update IDs from the SoftwareUpdate plist
RecommendedUpdateKeys=$(/usr/bin/defaults read /Library/Preferences/com.apple.SoftwareUpdate.plist RecommendedUpdates | awk -F'= ' '/Product Key/{print $NF}' | sed 's/"//g;s/;//g')
## Get a list of directories in /Library/Updates/ to scan through
downloadedUpdates=$(find "/Library/Updates" -maxdepth 1 -type d | sed '1d')
echo "Running check for available updates..."
check_Downloads_Vs_AvailUpdates
Posted on 05-19-2016 05:04 PM
@mm2270 I'm currently rewriting my software update script while combining it with bits and pieces of ones I've found here on JN by people such as yourself. I've included a check to see if the machine is a "MacBook" and then check to see if the "lid" is open or closed since some updates won't apply if the laptops lid is closed. I'd like to post mine to my Github once I've made a bit more forward progress to it and perhaps one of you fine chaps could take it from there and add to it or more importantly tell me where I went wrong or where it needs reorganizing.
And to those that are curious for checking if the lid is open
ioreg -r -k AppleClamshellState | grep AppleClamshellState | awk '{print $4}'
Posted on 04-26-2018 02:51 PM
@mm2270 Based on your script, how can i make a policy that only runs when a machine needs a reboot.