Determining if available updates require a restart or not

kitzy
Contributor III

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?

20 REPLIES 20

mscottblake
Valued Contributor

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.

thoule
Valued Contributor II

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
}

sean
Valued Contributor

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.

thoule
Valued Contributor II

@sean I didn't know that plist existed. Thanks for that tip! That'll clean up my code a bit!

jason_bracy
Contributor III

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.

mm2270
Legendary Contributor II

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.

Look
Valued Contributor III
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 🙂

jason_bracy
Contributor III

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

mm2270
Legendary Contributor II

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.

jason_bracy
Contributor III

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?

thoule
Valued Contributor II

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.

thoule
Valued Contributor II

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>"

jason_bracy
Contributor III

@thoule Your bash skills are much greater than mine.

Thanks.

thoule
Valued Contributor II

@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!

gregneagle
Valued Contributor

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.

mm2270
Legendary Contributor II

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.

gregneagle
Valued Contributor

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.

mm2270
Legendary Contributor II

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

Snickasaurus
Contributor

@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}'

kadams
Contributor

@mm2270 Based on your script, how can i make a policy that only runs when a machine needs a reboot.