A nicer Software update tool

Taboc741
New Contributor III

Since Jamf hasn't implemented a few of the feature requests out there that would make this better, I have resorted to making a script and using Jamf helper. I stole a lot of it from https://www.jamf.com/jamf-nation/discussions/5404/jamfhelper-software-update-trigger. My goal is to incorporate the new softwareupdate -i -a -R feature as defined by Der Flounder (https://derflounder.wordpress.com/2018/03/29/new-automated-restart-option-added-to-10-13-4s-softwareupdate-command-line-tool/) in a work flow that works well and is less intensive for my users.

The biggest catch I've had so far is recording the success or failure of the reboot process because the reboot is triggered by the script thus the policy never completes and the logs are never submitted to Jamf. Below is what I have written, it will be in a policy that is scoped to a smart group of folks that have updates pending, any suggestions on functionality or workflow is appreciated.

Edit 4/25/2019: A new GitHub has been made for this project. it incorporates tons of bug fixes and feature additions discussed in the below posts. It is, as of now, an active project. Folks here continue to add great suggestions and bug finds to what has been built, so please review the code and use a pinch of salt when deploying in your environments. The git can be found here: https://github.com/taboc741/MacScripts/blob/master/A-Kinder-macOS-Update

96 REPLIES 96

mm2270
Legendary Contributor III

My first suggestion is to edit your post and highlight the entire script, then click the >_ button in the post editing toolbar to put script markdown around it so it formats like a script here. Without that, it's difficult to read correctly.
Doh! Looks like you did exactly that as I was writing the above post! :)

Taboc741
New Contributor III

Sorry for the rapid fire updates to post. After posting I found literally a half dozen things I didn't like and bad formatting. Thanks for looking @mm2270 .

mm2270
Legendary Contributor III

No worries. I see this that needs to be corrected

if [ "$RestartRequired" != "" -o "$ShotDownRequired" != ""]; then

Should be "$ShutDownRequired" right?

Also, the path you're using to the Software Update icon is... unique. I suppose it's fine if that works, but generally speaking that icon is located in the following path, which should be pretty standard across quite a few OS versions

/System/Library/CoreServices/Software Update.app/Contents/Resources/SoftwareUpdate.icns

nvandam
Contributor II

I'm glad someone is working on this. I would love a better way to update macOS, giving users the choice to defer. I just tested your script on a Mac I knew had the iTunes update from yesterday. It worked except that I never got the second prompt that a restart was happening, it just happened. I'd love if you could defer it for 4 hours and have the windows go away during that time. After 4 hours, it would come back, then you can defer again. After 4 more hours, it shows up but you can't defer it anymore. So 8 hours of deferrals, and then maybe just a countdown of like 10 minutes until the update is forced. I know it would make it much more complex of a script, and would likely need to write some text file to reference and a launchdaemon to trigger it again, and some sort of logic to know that 8 hour and 10 minutes has already began and the policy coming from the JPS doesn't start things from square one. It's something I don't have the knowledge to do, but just wanted to throw my 2 cents in for what my dream "tool" would look like.

bvrooman
Valued Contributor

@mm2270: Strangely, Software Update.app no longer has an icon file in its bundle, starting in High Sierra. The Info.plist still lists SoftwareUpdate.icns, but the actual file isn't there. I found a workable substitute in the same location as @Taboc741 on 10.11 through 10.13.

mm2270
Legendary Contributor III

@bvrooman Hmm. I'm looking now and, you're right. The "Software Update.app" is still there, but with a generic application icon, since, yes, the actual .icns is missing. How strange.
Actually, not really that strange since Apple has been moving further away from the old Software Update model with each release. So nuking some of the old files and icons related to it seems like SOP for them, even if it wasn't entirely on purpose, which it may not have been in this case.

stevewood
Honored Contributor II
Honored Contributor II

@nvandam actually, providing a deferral option is not too difficult if you want to provide a one day deferral. Where it gets tricky is the "4 hour at a time" deferral. You're correct in stating that it would require a LaunchDaemon and a text file or plist to keep track of.

We utilize a script that presents a deferral message to the user. The user can defer up to 3 times. After the third deferral the updates are mandatory. We do this with two policies: one to run the deferral script and the second to run the actual updates. The script drops a plist file on the system that is read in each day to determine how many deferrals there have been.

The main drawback to this is that the user can simply not click the Defer or Install button and the policy never completes which hangs up any other policies from running. So they could defer indefinitely causing the machine to not install updates and causing other policies from running (including inventory updates).

The script we are using is below.

#!/bin/bash

# Name: os-update-deferral.sh
# Date: 31 Jul 2017
# Author: Mike Levenick (mike.levenick@jamf.com) adjustments by Steve Wood (steve.wood@omnicomgroup.com)
# Purpose: to provide a way to defer software update up to a set number of times.

# setup logging
logFile="/var/log/os-update-deferral.log"

# Check for / create logFile
if [ ! -f "${logFile}" ]; then
    # logFile not found; Create logFile
    /usr/bin/touch "${logFile}"
fi

## logging courtesy Dan Snelson (@dan.snelson)
function ScriptLog() { # Re-direct logging to the log file ...

    exec 3>&1 4>&2        # Save standard output and standard error
    exec 1>>"${logFile}"    # Redirect standard output to logFile
    exec 2>>"${logFile}"    # Redirect standard error to logFile

    NOW=`date +%Y-%m-%d %H:%M:%S`    
    /bin/echo "${NOW}" " ${1}" >> ${logFile}

}

ScriptLog "Starting deferral run"

#path to jamfhelper
jhpath="/Library/Application Support/JAMF/bin/jamfhelper.app/Contents/MacOS/jamfhelper"

#path to counter file
counterpath="/Library/Application Support/JAMF/com.company.osupdatedeferral.plist"

#check if counter file exists. If it does, increment the count and store it
if [ -f "$counterpath" ]; then
    echo "Counter file found."
    count=`defaults read "$counterpath" DeferralCount`
    echo "Old count is $count"
    ((count++))
    echo "New count is $count"
    defaults write "$counterpath" DeferralCount -int $count

#if the counter file is not found, create one with count 0
else 
    echo "Counter file does not exist. Creating one now."
    defaults write "$counterpath" DeferralCount -int 0
    count=0
    echo "Count is $count"
fi

#deletes the count file. For testing and debugging
#rm "$counterpath"

if [ "$count" -le 2 ]; then
    if [ "$count" -eq 2 ] ; then

        prompt=$("$jhpath" -startlaunchd -windowType hud -lockHUD -title "HelpDesk Notification - macOS Updates Available" -icon "/System/Library/CoreServices/Software Update.app/Contents/Resources/SoftwareUpdate.icns" -heading "HelpDesk Notification - Final Warning" -description "There are required updates and patches for your operating system that have been made available by the Paige team. These updates are required and need to be installed as soon as possible. Please click UPDATE to install them now or they can be deferred up to 3 times. If you have any questions you may contact the service desk at 1-888-555-1212." -button1 "Update" -button2 "Defer" -defaultButton 1)

    else

        prompt=$("$jhpath" -startlaunchd -windowType hud -lockHUD -title "HelpDesk Notification - macOS Updates Available" -icon "/System/Library/CoreServices/Software Update.app/Contents/Resources/SoftwareUpdate.icns" -heading "HelpDesk Notification - macOS Updates Available" -description "There are required updates and patches for your operating system that have been made available by the Paige team. These updates are required and need to be installed as soon as possible. Please click UPDATE to install them now or they can be deferred up to 3 times. If you have any questions you may contact the service desk at 1-888-555-1212." -button1 "Update" -button2 "Defer" -defaultButton 1)

    fi
    if [ $prompt = 0 ]; then

        #upgrade
        echo "upgrade"
        sudo jamf policy -event "updateOS" #uncomment to trigger upgrade

        # reset counter
        rm "$counterpath"
    else

        #dontupgrade
        echo "don't upgrade"

    fi
else

    final=$("$jhpath" -startlaunchd -windowType hud -lockHUD -title "HelpDesk Notification - macOS Updates Required" -icon "/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/AlertStopIcon.icns" -heading "HelpDesk Notification - macOS Updates Required" -description "You have deferred the required updates and patches for your operating system the maximum number of times. The update will now begin. If you have any questions you may contact the Paige service desk at 1-888-MY-PAIGE." -button1 "Ok" -defaultButton 1)

    echo "upgrade"
    sudo jamf policy -event "updateOS" #uncomment to trigger upgrade

    # reset counter
    rm "$counterpath"
fi

allanp81
Valued Contributor

I wrote one a while ago that we never actually got round to using:

#!/bin/bash
#Runs software update and installs any updates. 
#Forces a restart if required (and no one logged in).
#Will prompt user to restart if anyone logged in.

#make sure log directory exists
if [ ! -d "/Library/Application Support/KingstonUniversity" ]; then
  mkdir "/Library/Application Support/KingstonUniversity"
fi

#set log file location to log outcome of operation
logfile="/Library/Application Support/KingstonUniversity/softwareupdate.log"

#get the date and time
addDate(){
        while IFS= read -r line; do
            echo "$(date) $line"
        done
    }

#echo "Refreshing MDM"
#jamf removemdmprofile | addDate >> "$logfile";
#sleep 30
#jamf mdm | addDate >> "$logfile";

#check that the software update server is configured as ours and not Apple
check=$(defaults read /Library/Preferences/com.apple.SoftwareUpdate.plist | grep "kingston" )

if [[ ! -z $check ]]
then
    if softwareupdate -l | grep "Software Update found"
        then
            #store the list of updates
            items=$( softwareupdate -l | grep "*" )
            echo "$items"
            if softwareupdate -l |grep -i "restart"
                then
                    echo "1. Updates found that require a restart. Installing them" | addDate >> "$logfile";
                    echo "Seeing if a user is logged in"
                    isroot=$(stat -f "%Su" /dev/console)
                    if [ $isroot == "root" ];
                        then
                            echo "2. $items" | addDate >> "$logfile";
                            softwareupdate -ia
                                isroot=$(stat -f "%Su" /dev/console)
                                if [ $isroot == "root" ];
                                    then
                                    echo "3. No users logged in, restarting" | addDate >> "$logfile";
                                    shutdown -r now
                                else
                                    echo "3. Users logged in so asking if it's ok restart" | addDate >> "$logfile";
                                    binpath=/Library/Application Support/JAMF/bin/jamfHelper.app/Contents/MacOS/jamfHelper
                                    args=(
                                    -windowType hud 
                                    -description "Updates were installed that require a restart. Clicking OK will initiate a restart. Please save any work before clicking on OK." 
                                    -title "Software Updates" 
                                    -icon /System/Library/CoreServices/Software Update.app/Contents/Resources/SoftwareUpdate.icns 
                                    -button1 "OK" 
                                    -button2 "Cancel" 
                                    -cancelButton 2
                                    -startlaunchd 
                                    )

                                    "$binpath" "${args[@]}"

                                    if [ $? == "0" ];
                                        then
                                            echo "4. User chose to restart" | addDate >> "$logfile";
                                            echo "5. $items" | addDate >> "$logfile";
                                            shutdown -r now
                                        else
                                            echo "4. User chose not to install and restart" | addDate >> "$logfile";
                                            exit 0
                                    fi
                                fi
                       else
                       echo "2. Users logged in so asking if it's ok to install and restart" | addDate >> "$logfile";
                    binpath=/Library/Application Support/JAMF/bin/jamfHelper.app/Contents/MacOS/jamfHelper
                    args=(
                        -windowType hud 
                        -description "Updates need to be installed that require a restart. Clicking OK will initiate the update and will be followed by a restart. You may be prompted again following the restart depending on updates available. Please save any work before clicking on OK." 
                        -title "Software Updates" 
                        -icon /System/Library/CoreServices/Software Update.app/Contents/Resources/SoftwareUpdate.icns 
                        -button1 "OK" 
                        -button2 "Cancel" 
                        -cancelButton 2
                        -startlaunchd 
                    )

                    "$binpath" "${args[@]}"

                    if [ $? == "0" ];
                        then
                            echo "3. User chose to install and restart" | addDate >> "$logfile";
                                echo "4. $items" | addDate >> "$logfile";
                                softwareupdate -ia
                                #sleep 30
                                shutdown -r now
                           else
                           echo "3. User chose not to install and restart" | addDate >> "$logfile";
                            exit 0
                    fi
                    fi
                        else
                    echo "1. Updates found that don't require a restart. Installing these items" | addDate >> "$logfile";
                    echo "2. $items" | addDate >> "$logfile";
                    softwareupdate -ia
                    isroot=$(stat -f "%Su" /dev/console)
                    if [ $isroot == "root" ];
                        then
                            echo "3. No users logged in, restarting" | addDate >> "$logfile";
                            shutdown -r now
                       else
                            echo "3. User logged in so do nothing"  | addDate >> "$logfile";
                    fi   
            fi
        else
            echo "1. No updates available" | addDate >> "$logfile";
            isroot=$(stat -f "%Su" /dev/console)
                    if [ $isroot == "root" ];
                        then
                            echo "2. No users logged in, shutting down" | addDate >> "$logfile";
                            shutdown -h now
                       else
                            echo "2. User logged in so do nothing"  | addDate >> "$logfile";
                    fi       
    fi
fi

mm2270
Legendary Contributor III

Just a quick note on getting deferrals to come up at some timeframe other than once per day, like every 4 hours, 8 hours, etc. It's not necessary to use a LaunchDaemon for this as mentioned, as long as you are ok with one caveat to an alternate solution. It won't be exactly every 4 or 8 hours, etc, but will be at least that long before the next pop up, but shouldn't be significantly longer (assuming the Mac in question is in regular use)
The alt solution is to store the last actual deferral dialog attempt in unix time in a local file or plist. Have the policy that runs the deferral software update/patch process run on each recurring check-in. Each time it runs, check the last time stamp and compare the current unix time with the last timestamp and do some math to see if the time is greater or equal to the number of hours you specify.
To better illustrate that, here's a quick example script.

#!/bin/bash

hoursToDefer="4"  ## This can be replaced with a script parameter to make it more flexible

maxDeferrals="2"  ## This can be replaced with a script parameter to make it more flexible

## Path to the deferral options folder
deferralOptions="/Library/Application Support/Company/DeferralOptions"

if [ ! -d "$deferralOptions" ]; then
    mkdir -p "$deferralOptions"
fi

## Path to the deferral counter file
deferralCount="${deferralOptions}/deferralCount"

## Path to the last deferral unix time stamp
lastDeferralTime="${deferralOptions}/lastDeferralTime"


if [ -e "$lastDeferralTime" ]; then
    echo "A deferral time stamp is present. Checking contents"
    lastDeferralTimeStamp=$(cat "$lastDeferralTime")
else
    echo "No last deferral time stamp. Might be first run."
    ## Do something here to kick off a deferral dialog
fi

if [[ ! -z "$lastDeferralTimeStamp" ]]; then
    hoursToDeferSecs=$((60*60*hoursToDefer))
    currentTime=$(date +"%s")
    timeSinceDeferral=$((currentTime-lastDeferralTimeStamp))

    ## Check the difference between current time and last time stamp to see if it's at or above the time to wait in seconds
    if [[ "$timeSinceDeferral" -ge "$hoursToDeferSecs" ]]; then
        echo "Time since last deferral is equal or greater than ${hoursToDefer} hours"
        if [[ "$deferralCount" -ge "$maxDeferrals" ]]; then
            echo "Deferral count is at or above max deferrals allowed. Starting installations..."
            ## Do something here to kick off install
        else
            echo "Deferral count is below max threshold. Kicking off a deferral dialog..."
            ## Do something here to kick off another deferral dialog
        fi
    else
        echo "Time since last deferral is less than ${hoursToDefer} hours. Exiting..."
        exit 0
    fi
else
    echo "The last deferral time stamp is empty. Kicking off deferral dialog..."
    ## Do something here to kick off the deferral dialog
fi

Basically, the lastDeferralTime file stores when the last deferral message appeared to the user in standard unix seconds. On each run, the script checks to see if that file exists and has contents. If it does, it compares that time with the current time and figures out if enough time (hours) has passed for it to show the dialog again to the user, OR, if the updates just need to be installed, meaning the max deferrals have been reached.
In this way, you can use the standard Recurring Check-in trigger to run your script, but only have it take action if the criteria is met. Otherwise it just notes that it's not time yet and exits silently. This might be better than relying on a LaunchDaemon that you then need to remove later, or otherwise update in some way to make adjustments to it. If you use script parameters as noted in the initial notes, you can even change the time to wait on the fly by changing the 'hours' integer.

The script above doesn't include the deferral message or running the updates or anything like that, so you'd need to add those functions in or graft this onto another script. But hopefully it gets the basic idea across on how this can be used.

Taboc741
New Contributor III

ok folks, So I've incorporated some of the typo corrections @mm2270 pointed out, and a bug about unexpected reboots that @nvandam noted in his testing (basically I didn't have logic for if there were non-restart updates. It was rebooting for all updates, thanks for catching that nvandam). I have also swapped up the logging so both the console and a log file get what is happening so I at least have something to come back to in the event of unexpected behavior since the reboot at the end probably won't result in the logs being submitted to Jamf. I have also swapped to using the Jamf binary to reboot the machine so I have a better shot at getting the results back in the logs. I did not incorporate the try again features suggested above. They are neat but are more complex and I've been asked to make the pop-up box stay in their face to make the reboot harder to ignore at this place.

#!/bin/sh
#  SoftwareUpdate2.sh
#  Created by Chris on 5/31/18.
#
######### Create settings for logging and create log file #########
## Path to Log file
LogPath=/tmp/log
if [ ! -d "$LogPath" ];then
    mkdir /tmp/log
fi
## set log file and console to recieve output of commands
LOG_FILE=/tmp/log/SoftwareUpdateScript.log
exec > >(tee -a ${LOG_FILE} )
exec 2> >(tee -a ${LOG_FILE} >&2)
## begin log file
Echo `date`" : Script Started"
######### Set variables for the script ############
## Determine OS version
OSVersion=`sw_vers -productVersion`
## Get the currently logged in user, if any.
LoggedInUser=`who | grep console | awk '{print $1}'`
## Check for updates that require a restart and ones that do not.
updates=`softwareupdate -l`
UpdatesNoRestart=`echo $updates | grep recommended | grep -v restart`
RestartRequired=`echo $updates -l | grep restart | grep -v '*' | cut -d , -f 1`
ShutDownRequired=`echo $updates -l | grep shutdown | grep -v '*' | cut -d , -f 1`

################ End Variable Set ################
echo `date`" : OS version is $OSVersion"

## If there are no system updates, quit
if [ "$UpdatesNoRestart" == "" -a "$RestartRequired" == "" -a "$ShutDownRequired" == "" ]; then
    echo `date`" : No updates at this time, updating Jamf inventory"
    jamf recon
    Echo `date`" : Script exit"
    exit 0
fi
######### If we get to this point and beyond, there are updates. #########
Echo `date`" : Updates found."
Echo `date`" : Warning user"
prompt=`"/Library/Application Support/JAMF/bin/jamfHelper.app/Contents/MacOS/jamfHelper" -windowType hud -title "Software Update Required" -heading "Required Mac OS update" -alignHeading justified -description 'A required OS update is available for your Mac.  You will be prompted again if a reboot is required.' -alignDescription left -icon '/System/Library/CoreServices/Install Command Line Developer Tools.app/Contents/Resources/SoftwareUpdate.icns' -button1 'Start Update' -timeout 14400 -countdown -lockHUD`
echo `date`" : prompt equaled $prompt. 0=start 1=failed to prompt 2=canceled 239=exited"
    softwareupdate -i -r && echo `date`" : Updates Applied"
if [ "$ShutDownRequired" != "" -a "$RestartRequired" != "" ]; then
    echo `date`" : no reboot required, exiting"
    Echo `date`" : Script exit"
    exit 0
fi
######### If we get to this point a reboot is required #########
if [ "$RestartRequired" != "" -o "$ShutDownRequired" != "" ]; then
    prompt=`"/Library/Application Support/JAMF/bin/jamfHelper.app/Contents/MacOS/jamfHelper" -windowType hud -title "Software Update Required" -heading "Required Mac OS update" -alignHeading justified -description 'A reboot is required to apply the OS updates to your Mac.  Please close all open applications before restarting your mac.' -alignDescription left -icon '/System/Library/CoreServices/Install Command Line Developer Tools.app/Contents/Resources/SoftwareUpdate.icns' -button1 Reboot -timeout 86400 -countdown -lockHUD`
    echo `date`" : prompt equaled $prompt."
    echo `date`" : placing reboot message"
    /Library/Application Support/JAMF/bin/jamfHelper.app/Contents/MacOS/jamfHelper -windowType hud -lockHUD -heading 'Required Mac OS update' -description 'We are now updating your Mac System software. These updates should not take longer than 30 to 45 minutes depending on how many updates your Mac needs. If you see this screen for more than 45 minutes please call Service Desk. Your machine may reboot or shutdown.  Please do not manually turn off this computer. This message will go away when updates are complete.' -icon '/System/Library/CoreServices/Install Command Line Developer Tools.app/Contents/Resources/SoftwareUpdate.icns' > /dev/null 2>&1 &
fi
if [ "$ShutDownRequired" != "" ]; then
    echo `date`" : Starting softwareupdate --install --recommended --restart"
    softwareupdate --install --recommended --restart
else
    Echo `date`" : starting reboot"
    Jamf reboot -background -minutes 1
    Echo `date`" : Script exit"
    exit 0
fi

nvandam
Contributor II

This is what I'm getting for my output when the latest version runs.

Script exit code: 2
Script result: /Library/Application Support/JAMF/tmp/softwareUpdate2.sh: line 13: syntax error near unexpected token `>'
/Library/Application Support/JAMF/tmp/softwareUpdate2.sh: line 13: `exec > >(tee -a ${LOG_FILE} )'
Error running script: return code was 2.

cubandave
Contributor

@Taboc741 Your script looks awesome. I’ll try it out. we should work together. I have some a lot of this with a tool I call UEX for Jamf. It is multi-purposed tool for not just software updates but also any policy you’d like to use. I just got permission to make it open source. I’ll be incorporating the features to auto restart for 10.13.4+

https://github.com/cubandave/Jamf-Interaction-Toolkit

cstout
Contributor III
Contributor III

@Taboc741 , this script is great. Thank you for sharing.

I got the same error as @nvandam and what fixed it for me and properly wrote to the log file was replacing line 13 and 14 with this single line:

exec >> $LOG_FILE 2>&1 && tail $LOG_FILE

That said, I ended up commenting that out anyway because I wanted the script output to be logged to the submitted policy results instead of a text file locally. To achieve that, I changed the first couple lines of the script to the following:

#!/bin/sh
exec 2>&1

#

One additional modification I made to tailor it to my needs was commenting out lines 53 and 54 which is the final "placing reboot message" and jamf helper window since in testing it caused overlapping pop up notifications and introduced the possibility for the end user to indefinitely suppress the reboot by simply ignoring/moving/hiding the jamf helper window.

So, I commented out the final jamf helper window command and changed out the jamf reboot command to:

/usr/local/bin/jamf reboot -background -immediately

By changing out the wording for the 'restart required' prompt I am able to allow the user the original delay of the first pop up, the next delay offered in the second pop up, and then a restart as soon as the countdown finishes or the button on the window is pressed.

Great work on this script and thanks again for sharing it!

mm2270
Legendary Contributor III

@cstout Just so you're aware, you can have script output go both to stdout (which shows up in the final Jamf policy log) as well as to a local file. Take a look at the tee command for that. For example

echo "Some output..." | tee -a /Library/Logs/somelog.log

The above will print "Some output..." to stdout and write the same line to the somelog.log file. The -a flag makes tee 'append' rather than overwrite the log.

You could also add a logging function to the script, which can take $1 input and do something with it, like send to a log file and stdout as in above. This generally tends to be easier with more involved scripts since you can just reference the function rather than making sure the tee command is on every line where there is a script output.

Edit: Oops! Looks like @Taboc741 already has the tee command in his script, looking at the most recent posting his has here. Was that not working for you to get the commands into the jamf log as well as the local log? Or did you just prefer not to write to a local log file?local log file?

Taboc741
New Contributor III

@mm2270 The issue appears to be that when the script is run as a user it runs without error, but running the same script as root kicks out an error. The trouble here is I am pretty novice at bash and while I have learned a lot from this script and from the "peer review" here I haven't quite figured out why/how the exec/tee combo I stole from the interwebs works and thus can't troubleshoot why it only works as a user.

cstout
Contributor III
Contributor III

@mm2270 Yeah, I tried various tees and each method resulted in syntax errors and the script halting when I was testing on High Sierra. I’m not sure why though. I used tee examples from this post as well as recent GitHub examples and none worked for me.

mm2270
Legendary Contributor III

@cstout & @Taboc741 Apologies. My reading comprehension is a little off this morning. I didn't really thoroughly read thru the thread (obviously) or I would have seen there were errors being kicked out.

I'm also not sure why what you have would generate errors, so for the moment I would suggest then swapping out the exec line for a function that does what you want, which I believe should work just fine when the script is run from a Jamf root level policy. For example, add the following to your script, somewhere preferably near the top, before any expected output from the script, but after the point where the LogPath variable is established.

function SendToLog ()
{

echo "$1" | tee -a "$LogPath"

}

Then everywhere in the script where it does something like

echo `date`" : no reboot required, exiting"

Replace the echo with SendToLog For example

SendToLog `date`" : no reboot required, exiting"

What this does is runs the above function, which takes everything that comes after it as $1 and echoes that to stdout and the log file, using tee
The only thing you might need to change is where the first double quote is placed. It might have to come in front of the date command or the date might not make it into the file. I haven't tested that so I don't know for certain. You could also just put the date command into the function itself, like so:

echo "$(date) : $1" | tee -a "$LogPath"

And remove it from all the echo lines, which kind of simplifies things. Also makes it easier later to change the date command if you'd like the date in a different format, since it would only be one line and not multiple ones.

echo "$(date +"%Y-%b-%d %T") : $1" | tee -a "$LogPath"

the above would show something like this in the log:

2018-Jul-12 10:22:30 : no reboot required, exiting

cstout
Contributor III
Contributor III

I’ll hopefully be able to test this out later this morning. I also modified the date output using awk but I completely forgot you could do it like you mentioned. I’ll make those recommended changes, test, and report back with what I see. Thanks!

cstout
Contributor III
Contributor III

Alright @mm2270 I've done some testing and here's what I've got. For good measure, here's a copy of the edited script that I'm testing so we're on the same page. Definitely love having the date in the first echo line. Really cleans it up.

#!/bin/sh
#  SoftwareUpdate2.sh
#  Created by Chris on 5/31/18.

# Log all script output
LogPath=/tmp/log
if [ ! -d "$LogPath" ];then
    mkdir /tmp/log
fi

# Set log filename and path
LogFile=$LogPath/SoftwareUpdateScript.log
function SendToLog ()
{

echo "$(date +"%Y-%b-%d %T") : $1" | tee -a "$LogFile"

}

# Log start of script
SendToLog "Script start"

# Determine OS version
OSVersion=`sw_vers -productVersion`

# Get the currently logged in user, if any.
LoggedInUser=`who | grep console | awk '{print $1}'`

# Check for updates that require a restart and ones that do not.
updates=`softwareupdate -l`
UpdatesNoRestart=`SendToLog $updates | grep recommended | grep -v restart`
RestartRequired=`SendToLog $updates -l | grep restart | grep -v '*' | cut -d , -f 1`
ShutDownRequired=`SendToLog $updates -l | grep shutdown | grep -v '*' | cut -d , -f 1`

# End Variable Set
SendToLog "OS version is: $OSVersion"

## If there are no system updates, quit
if [ "$UpdatesNoRestart" == "" -a "$RestartRequired" == "" -a "$ShutDownRequired" == "" ]; then
    SendToLog "No updates at this time. Updating Inventory."
    /usr/local/bin/jamf recon
    SendToLog "Script exit"
    exit 0
fi

# If we get to this point and beyond, there are updates.
SendToLog "OS version is $OSVersion"" : Updates found."
prompt=`"/Library/Application Support/JAMF/bin/jamfHelper.app/Contents/MacOS/jamfHelper" -windowType hud -title "Company-Name Software Update" -heading "Required macOS Update" -alignHeading justified -description "A required OS update is available for your Mac.

You can start this update any time during the countdown by pressing the Start Update button below.

When the countdown is complete the update will start automatically.

You will be prompted again if a reboot is required." -alignDescription left -icon '/System/Library/CoreServices/Install Command Line Developer Tools.app/Contents/Resources/SoftwareUpdate.icns' -button1 'Start Update' -timeout 14400 -countdown -lockHUD`
SendToLog "OS version is $OSVersion"" : prompt equaled $prompt. 0=start 1=failed to prompt 2=canceled 239=exited"
    softwareupdate -i -r && SendToLog "OS version is $OSVersion"" : Updates Applied"
if [ "$ShutDownRequired" != "" -a "$RestartRequired" != "" ]; then
    SendToLog "No reboot required. Exiting."
    SendToLog "Script exit"
    exit 0
fi

# If we get to this point a reboot is required
if [ "$RestartRequired" != "" -o "$ShutDownRequired" != "" ]; then
    prompt=`"/Library/Application Support/JAMF/bin/jamfHelper.app/Contents/MacOS/jamfHelper" -windowType hud -title "Company-Name Software Update" -heading "Update Requires Restart" -alignHeading justified -description "A reboot is required to complete this macOS update.  Please save your work and close any open applications before restarting your Mac.

An automatic restart has been scheduled to start in 15 minutes." -alignDescription left -icon '/System/Library/CoreServices/Install Command Line Developer Tools.app/Contents/Resources/SoftwareUpdate.icns' -button1 Restart -timeout 900 -countdown -lockHUD`
    SendToLog "prompt equaled $prompt."
fi
if [ "$ShutDownRequired" != "" ]; then
    echo `date`" : Starting softwareupdate --install --recommended --restart"
    softwareupdate --install --recommended --restart
else
    SendToLog "Initiating reboot"
    /usr/local/bin/jamf reboot -background -immediately
    SendToLog "Script exit"
    exit 0
fi

So, here's my Terminal output from this command on an update to date computer:

MBP:Desktop cstout$ sudo sh update.sh 
2018-Jul-12 14:33:18 : Script start
No new software available.
2018-Jul-12 14:33:24 : OS version is: 10.13.6
2018-Jul-12 14:33:24 : No updates at this time. Updating Inventory.
Retrieving inventory preferences from https://jps/...
Locating applications...
Locating package receipts...
Locating accounts...
Locating hard drive information...
Locating printers...
Searching path: /Applications
Locating hardware information (Mac OS X 10.13.6)...
Gathering application usage information...
Submitting data to https://jps/...
<computer_id>1</computer_id>
2018-Jul-12 14:33:36 : Script exit

My log file output, however, is less verbose than that and has three lines that simply say "Software":

2018-Jul-12 14:33:18 : Script start
2018-Jul-12 14:33:24 : Software
2018-Jul-12 14:33:24 : Software
2018-Jul-12 14:33:24 : Software
2018-Jul-12 14:33:24 : OS version is: 10.13.6
2018-Jul-12 14:33:24 : No updates at this time. Updating Inventory.
2018-Jul-12 14:33:36 : Script exit

Thoughts?

mm2270
Legendary Contributor III

@cstout it took me a little while to find it, but it seems the issue is it looks like you did a global find/replace to replace all echos with SendToLog, which kind of broke the script. There were a few legitimate echo lines in the script that needed to stay that way. Specifically, these 3 lines:

UpdatesNoRestart=`SendToLog $updates | grep recommended | grep -v restart`
RestartRequired=`SendToLog $updates -l | grep restart | grep -v '*' | cut -d , -f 1`
ShutDownRequired=`SendToLog $updates -l | grep shutdown | grep -v '*' | cut -d , -f 1`

Those should all be written as:

UpdatesNoRestart=`echo $updates | grep recommended | grep -v restart`
RestartRequired=`echo $updates -l | grep restart | grep -v '*' | cut -d , -f 1`
ShutDownRequired=`echo $updates -l | grep shutdown | grep -v '*' | cut -d , -f 1`

Those echo the output of softwareupdate -l through grep and cut to get some data. Since you replaced them with SendToLog, it's just echoing the first bit of the softwareupdate -l output to the log, hence why you see that "Software" item in the log. The script isn't going to work since it can't properly populate any of those variables.

Replace them with the lines I have above with the echoes in them and it should fix it. I would scan the rest of the script to make sure there aren't any others, but I think that's it.

cstout
Contributor III
Contributor III

Oh man, @mm2270 what a find. You’re absolutely right. I didn’t read through the script thoroughly before hitting it with the lazy find and replace all. I’ll give that a go. What a day. Haha, thank you.

Taboc741
New Contributor III

@mm2270 and @cstout thank you guys so much for your help on this. In the background I actually did a few clean up items and found an interesting article for how to execute "softwareupdate --install --recommended --restart" and still allow the Jamf logs to make it back to Jamf. Mostly it was related to this article on how to have a push button reinstall of OSX. (www.jamf.com/blog/reinstall-a-clean-macos-with-one-button/) . You can see my latest code which is a bit more user intensive than cstout's below or on pastebin here: https://pastebin.com/ENpyYEP8

#!/bin/sh
##  SoftwareUpdate2.sh
##  Created by Chris on 5/31/18. Last edited by Chris 7/13/2018
##### Special thanks to mm2270, cstout, nvandam from JamfNation for helping me with some testing and code suggestions to problems I couldn't solve on my own.#####

######### Create settings for logging and create log file #########
## Path to Log file
LogPath=/tmp/log
if [ ! -d "$LogPath" ];then
    mkdir /tmp/log
fi
## Set log file and console to recieve output of commands
LOG_FILE=/tmp/log/SoftwareUpdateScript.log
#Below lines for redirecting terminal output to both console and log file doesn't work.  Saving for idea stealing later.
#exec > >(tee -a ${LOG_FILE} )
#exec 2> >(tee -a ${LOG_FILE} >&2)
function SendToLog ()
{

echo "$(date +"%Y-%b-%d %T") : $1" | tee -a "$LogFile"

}
## begin log file
SendToLog "Script Started"
######### Set variables for the script ############
## Determine OS version
OSVersion=`sw_vers -productVersion`
## Get the currently logged in user, if any.
LoggedInUser=`who | grep console | awk '{print $1}'`
## Check for updates that require a restart and ones that do not.
updates=`softwareupdate -l`
UpdatesNoRestart=`echo $updates | grep recommended | grep -v restart`
RestartRequired=`echo $updates | grep restart | grep -v '*' | cut -d , -f 1`
ShutDownRequired=`echo $updates | grep shutdown | grep -v '*' | cut -d , -f 1`

################ End Variable Set ################
SendToLog "OS version is $OSVersion"
SendToLog "Logged in user is $LoggedInUser"

## If there are no system updates, quit
if [ "$UpdatesNoRestart" == "" -a "$RestartRequired" == "" -a "$ShutDownRequired" == "" ]; then
    SendToLog "No updates at this time, updating Jamf inventory"
    jamf recon
    SendToLog "Inventory update complete, script exit."
    exit 0
fi
######### If we get to this point and beyond, there are updates. #########
SendToLog "Updates found."
SendToLog "Warning user"
prompt=`"/Library/Application Support/JAMF/bin/jamfHelper.app/Contents/MacOS/jamfHelper" -windowType hud -title "Software Update Required" -heading "Required Mac OS update" -alignHeading justified -description 'A required OS update is available for your Mac.  You will be prompted again if a reboot is required.' -alignDescription left -icon '/System/Library/CoreServices/Install Command Line Developer Tools.app/Contents/Resources/SoftwareUpdate.icns' -button1 'Start Update' -timeout 14400 -countdown -lockHUD`
SendToLog "prompt equaled $prompt. 0=start 1=failed to prompt 2=canceled 239=exited"
    softwareupdate -i -r && SendToLog "Updates Applied"
if [ "$ShutDownRequired" != "" -a "$RestartRequired" != "" ]; then
    SendToLog "no reboot required, exiting"
    SendToLog "Script exit"
    exit 0
fi
######### If we get to this point a reboot is required #########
if [ "$RestartRequired" != "" -o "$ShutDownRequired" != "" ]; then
    prompt=`"/Library/Application Support/JAMF/bin/jamfHelper.app/Contents/MacOS/jamfHelper" -windowType hud -title "Software Update Required" -heading "Required Mac OS update" -alignHeading justified -description 'A reboot is required to apply the OS updates to your Mac.  Please close all open applications before restarting your mac.' -alignDescription left -icon '/System/Library/CoreServices/Install Command Line Developer Tools.app/Contents/Resources/SoftwareUpdate.icns' -button1 Reboot -timeout 86400 -countdown -lockHUD`
    SendToLog "prompt equaled $prompt."
    SendToLog "placing reboot message"
    /Library/Application Support/JAMF/bin/jamfHelper.app/Contents/MacOS/jamfHelper -windowType hud -lockHUD -heading 'Required Mac OS update' -description 'We are now updating your Mac System software. These updates should not take longer than 30 to 45 minutes depending on how many updates your Mac needs. If you see this screen for more than 45 minutes please call Service Desk. Your machine may reboot or shutdown.  Please do not manually turn off this computer. This message will go away when updates are complete.' -icon '/System/Library/CoreServices/Install Command Line Developer Tools.app/Contents/Resources/SoftwareUpdate.icns' > /dev/null 2>&1 &
fi
if [ "$ShutDownRequired" != "" ]; then
    SendToLog "Starting softwareupdate --install --recommended --restart"
    softwareupdate --install --recommended --restart &
    exit 0
else
    SendToLog "starting reboot"
    Jamf reboot -background -minutes 1
    SendToLog "Script exit"
    exit 0
fi

cstout
Contributor III
Contributor III

@Taboc741 Are you seeing this script going straight from

   softwareupdate -i -r && SendToLog "Updates Applied"

to

 SendToLog "starting reboot"

with this latest Safari update? Currently troubleshooting but may need to continue after some sleep. Curious if you're seeing the same behavior though.

Finding available software
Software Update found the following new or updated software:
   * Safari12.0HighSierraAuto-12.0
    Safari (12.0), 78915K [recommended]

Edit: Tried again on a refreshed VM and it's not skipping straight to the reboot without prompting, so never mind that first part. I am curious, and looking into, why the script is processing a reboot for an update that doesn't require it though.

shawnis43
New Contributor III

Hey @cstout , any chance you figured out why the script is rebooting when its not required? I'm running into the issue you described. It skips straight to the reboot without the prompt for that same Safari update and also iTunes, and does a reboot

cstout
Contributor III
Contributor III

Hi @shawnis43, this is certainly on my To Do list but with JNUC and other higher priority work items I haven't had the time to sit down and troubleshoot it. Yeah, I'm still experiencing the same issue and what I did as a band-aid for the last non-restart required update (Safari 12) was to disable the update script and simply made a Safari 12 install policy that would not restart.

I'm hoping to have some time in the next week or so to take a deeper look at it but if someone else beats me to it, I won't be upset. ;-)

cstout
Contributor III
Contributor III

@shawnis43 Unrelated to the unnecessary restart issue is a new one I've added to the list. The countdown timer function of the script is not working on my Mojave Macs. It simply displays the total amount of time for the counter doesn't animate. The button on the pop-up window is also shaded poorly and is hard to read in both regular and dark mode. Plenty more to troubleshoot now.

shawnis43
New Contributor III

@cstout , Thanks for the heads up! I think I figured out why it was restarting when not necessary, though my scripting knowledge is very weak so I'm not 100% on it. In the if statement of the "warning user" section, I changed the "!=" to "==" and that seems to do the trick. In my testing the Mac did not restart for the Safari update. I look forward to seeing any updates you or anyone else comes up with for Mojave!!

KRIECCO
Contributor

..

KRIECCO
Contributor

Just tried the script on Mojave 10.14.2 update, and it starts well up, but then just reboot at once without any prompt ? So the update is working, but not so user friendly if just rebooting without any notice

@shawnis43 can you try and put up the script that is working for you, so it does not reboot without prompting first on updates

mlitton
New Contributor II

Script works very nicely. Just trying to understand ... what is the purpose of:

#!/bin/sh
ShutDownRequired=`echo $updates | grep shutdown | grep -v '*' | cut -d , -f 1`

rkovelman
New Contributor III

Ditto to what @mlitton said. Awesome script, but the shutdown is a bit weird. Maybe have a button that gives the option to reboot or shutdown?

ryan_ball
Valued Contributor

Here is one that I've worked on:

#!/bin/bash

log="/Library/Logs/YourCorp/SoftwareUpdate.log"
scriptName=$(basename "$0")
osVersion=$(sw_vers -productVersion)
osMinorVersion=$(echo "$osVersion" | awk -F. '{print $2}')
[[ "$osMinorVersion" -le 12 ]] && icon="/System/Library/CoreServices/Software Update.app/Contents/Resources/SoftwareUpdate.icns"
[[ "$osMinorVersion" -ge 13 ]] && icon="/System/Library/CoreServices/Install Command Line Developer Tools.app/Contents/Resources/SoftwareUpdate.icns"

/bin/mkdir -p /Library/Logs/YourCorp

function writelog () {
    DATE=$(date +%Y-%m-%d %H:%M:%S)
    /bin/echo "${1}"
    /bin/echo "$DATE" " $1" >> "$log"
}

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

function trigger_updates () {
    # Run softwareupdate and clean up the output so we only see what is necessary.
    /usr/sbin/softwareupdate --install $1 | 
        grep --line-buffered -v -E 'Software Update Tool|Copyright|Finding|Downloaded|Done.|You have installed one|Please restart immediately.|^$' | 
        while read -r LINE; do writelog "$LINE"; done
    sleep 5
}

function initiate_restart () {
    writelog "Initiating restart now..."
    kill "$jamfHelperPID" > /dev/null 2>&1 && wait $! > /dev/null
    /usr/local/bin/jamf reboot -background -immediately
    finish 0
}

writelog " "
writelog "======== Starting $scriptName ========"
writelog "Determining available Software Updates for macOS $osVersion..."

# Check for updates and determine if a restart is required for any
updates=$(/usr/sbin/softwareupdate -l)
updatesNoRestart=$(echo "$updates" | /usr/bin/grep -v restart | /usr/bin/grep -B1 recommended | /usr/bin/grep -v recommended | /usr/bin/awk '{print $2}' | /usr/bin/awk '{printf "%s ", $0}')
updatesRestart=$(echo "$updates" | grep -i restart | grep -v '*' | cut -d , -f 1)
updateCount=$(echo "$updates" | grep -i -c recommended)

# If there are no system updates, quit
if [[ "$updateCount" -eq 0 ]]; then
    writelog "No updates at this time; exiting."
    finish 0
fi

# If we get to this point and beyond, there are updates, let's download them.
writelog "Downloading $updateCount update(s)..."
/usr/sbin/softwareupdate --download --recommended | 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
    writelog "Installing updates that DO NOT require a restart..."
    trigger_updates "$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.
    loggedInUser=$(/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 + "
");')
    if [[ "$loggedInUser" == "root" ]] || [[ -z "$loggedInUser" ]]; then
        writelog "No user logged in."
        writelog "Installing updates that DO require a restart..."
        trigger_updates "--recommended"
        initiate_restart
    fi

    # Past this point means a user is logged in.
    writelog "Warning user $loggedInUser of impending restart."
    result=$("/Library/Application Support/JAMF/bin/jamfHelper.app/Contents/MacOS/jamfHelper" -windowType utility -title "Software Update Required" -heading "Required Mac OS update" -alignHeading justified -description 'A required OS update is available for your Mac. This will require a reboot AFTER the update has been installed. Please start the update now and you will be prompted again to reboot after the update has completed.' -alignDescription left -icon "$icon" -button1 'Start Update' -timeout 14400 -countdown -lockHUD)
    if [[ "$result" =~ ^0|239$ ]]; then
        writelog "Timer expired, user clicked begin, or user force-quit the prompt."
        /Library/Application Support/JAMF/bin/jamfHelper.app/Contents/MacOS/jamfHelper -windowType utility -lockHUD -heading 'Software Update Required' -description 'We are now updating your Mac System software. These updates should not take longer than 30 to 45 minutes depending on how many updates your Mac needs. If you see this screen for more than 45 minutes please call Service Desk. Your machine may reboot or shutdown.  Please do not manually turn off this computer. This message will go away when updates are complete.' -icon "$icon" > /dev/null 2>&1 &
        jamfHelperPID=$(echo $!)
        writelog "Installing updates that DO require a restart..."
        trigger_updates "--recommended"
        initiate_restart
    else
        writelog "jamfHelper failed to prompt the user; exiting."
        finish 1
    fi
else
    writelog "No updates that require a restart available; exiting."
    finish 0
fi

willsmithcc
New Contributor II

@ryan.ball That script is awesome! Only problem I'm having is that the user isn't warned in between "starting" the update and the computer restarting. Are you experiencing the same thing?

ryan_ball
Valued Contributor

@willsmithcc So are you saying between the jamfHelper dialog and the initiate_restart function? So if the updates take 10 minutes to install, and during that 10 minute window a user logs in, and then the Mac just restarts when the updates are done I guess is what might be happening.

You'd need to check one more time for a logged in user after the trigger_updates "--recommended" part again if you are trying to avoid that.

I never used this in production. Moved on to a version where the user is notified 3 times using yo.app before forcing the install.

ryan_ball
Valued Contributor

@willsmithcc I did not test this in this script, but I took care of that issue in my own version and copied it over for you.

#!/bin/bash

# Posted here: https://www.jamf.com/jamf-nation/discussions/28280/a-nicer-software-update-tool

log="/Library/Logs/YourCorp/SoftwareUpdate.log"
scriptName=$(basename "$0")
osVersion=$(sw_vers -productVersion)
osMinorVersion=$(echo "$osVersion" | awk -F. '{print $2}')
[[ "$osMinorVersion" -le 12 ]] && icon="/System/Library/CoreServices/Software Update.app/Contents/Resources/SoftwareUpdate.icns"
[[ "$osMinorVersion" -ge 13 ]] && icon="/System/Library/CoreServices/Install Command Line Developer Tools.app/Contents/Resources/SoftwareUpdate.icns"

/bin/mkdir -p /Library/Logs/YourCorp

function writelog () {
    DATE=$(date +%Y-%m-%d %H:%M:%S)
    /bin/echo "${1}"
    /bin/echo "$DATE" " $1" >> "$log"
}

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

function trigger_updates () {
    # Run softwareupdate and clean up the output so we only see what is necessary.
    /usr/sbin/softwareupdate --install $1 | 
        grep --line-buffered -v -E 'Software Update Tool|Copyright|Finding|Downloaded|Done.|You have installed one|Please restart immediately.|^$' | 
        while read -r LINE; do writelog "$LINE"; done
    sleep 5
}

function initiate_restart () {
    writelog "Initiating restart now..."
    kill "$jamfHelperPID" > /dev/null 2>&1 && wait $! > /dev/null
    /usr/local/bin/jamf reboot -background -immediately
    finish 0
}

writelog " "
writelog "======== Starting $scriptName ========"
writelog "Determining available Software Updates for macOS $osVersion..."

# Check for updates and determine if a restart is required for any
updates=$(/usr/sbin/softwareupdate -l)
updatesNoRestart=$(echo "$updates" | /usr/bin/grep -v restart | /usr/bin/grep -B1 recommended | /usr/bin/grep -v recommended | /usr/bin/awk '{print $2}' | /usr/bin/awk '{printf "%s ", $0}')
updatesRestart=$(echo "$updates" | grep -i restart | grep -v '*' | cut -d , -f 1)
updateCount=$(echo "$updates" | grep -i -c recommended)

# If there are no system updates, quit
if [[ "$updateCount" -eq 0 ]]; then
    writelog "No updates at this time; exiting."
    finish 0
fi

# If we get to this point and beyond, there are updates, let's download them.
writelog "Downloading $updateCount update(s)..."
/usr/sbin/softwareupdate --download --recommended | 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
    writelog "Installing updates that DO NOT require a restart..."
    trigger_updates "$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.
    loggedInUser=$(/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 + "
");')
    if [[ "$loggedInUser" == "root" ]] || [[ -z "$loggedInUser" ]]; then
        writelog "No user logged in."
        writelog "Installing updates that DO require a restart..."
        trigger_updates "--recommended"
        loggedInUser=$(/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 + "
");')
        if [[ ! "$loggedInUser" == "root" ]] && [[ -n "$loggedInUser" ]]; then
            writelog "$loggedInUser has logged in since we started to install updates, alerting them of pending restart."
            /Library/Application Support/JAMF/bin/jamfHelper.app/Contents/MacOS/jamfHelper -windowType hud -lockHUD -title 'Software Update In Progress' -alignHeading center -alignDescription natural -description 'Unfortunately you logged in while software updates were being installed. This Mac will restart in 60 seconds.

If you have any questions please call the Help Desk.' -icon "$icon" -iconSize 100 -timeout "60"
            initiate_restart
        else
            # Still nobody is logged in, restart
            initiate_restart
        fi
    fi

    # Past this point means a user is logged in.
    writelog "Warning user $loggedInUser of impending restart."
    result=$("/Library/Application Support/JAMF/bin/jamfHelper.app/Contents/MacOS/jamfHelper" -windowType utility -title "Software Update Required" -heading "Required Mac OS update" -alignHeading justified -description 'A required OS update is available for your Mac. This will require a reboot AFTER the update has been installed. Please start the update now and you will be prompted again to reboot after the update has completed.' -alignDescription left -icon "$icon" -button1 'Start Update' -timeout 14400 -countdown -lockHUD)
    if [[ "$result" =~ ^0|239$ ]]; then
        writelog "Timer expired, user clicked begin, or user force-quit the prompt."
        /Library/Application Support/JAMF/bin/jamfHelper.app/Contents/MacOS/jamfHelper -windowType utility -lockHUD -heading 'Software Update Required' -description 'We are now updating your Mac System software. These updates should not take longer than 30 to 45 minutes depending on how many updates your Mac needs. If you see this screen for more than 45 minutes please call Service Desk. Your machine may reboot or shutdown.  Please do not manually turn off this computer. This message will go away when updates are complete.' -icon "$icon" > /dev/null 2>&1 &
        jamfHelperPID=$(echo $!)
        writelog "Installing updates that DO require a restart..."
        trigger_updates "--recommended"
        initiate_restart
    else
        writelog "jamfHelper failed to prompt the user; exiting."
        finish 1
    fi
else
    writelog "No updates that require a restart available; exiting."
    finish 0
fi

Taboc741
New Contributor III

@mlitton and @rkovelman The point of testing for the shutdown state was that last year Apple introduced a new update reboot state that Jamf has (had) not incorporated into their built-in reboot options.

My goal is to incorporate the new softwareupdate -i -a -R feature as defined by Der Flounder (https://derflounder.wordpress.com/2018/03/29/new-automated-restart-option-added-to-10-13-4s-softwareupdate-command-line-tool/

After talking with Apple support the expected flag for updates requiring the strange reboot model is "shutdown", thus I tried to capture and contain the future state. Note: I have not seen it be used yet, but this is Apple we're talking about. They do what they want when they want.

@cstout and @shawnis43 , I think figured out why we would get the random reboots, it was a race condition in the if statement. Turns out the test of a null variable does not compute and can cause sh to process the code after the null evaluation strangely. I discovered this while trying to get a deferral process working. (The users eventually got the better of my management who was happy with the simpler solution). I am working on sanitizing the scripts and moving to a github since paste-bin doesn't really do multi-file solutions well. I'll post back when I get that done.

To add more color, essentially I am taking

#!/bin/sh
updates=`softwareupdate -l`
updatesNoRestart=`echo $updates | grep recommended | grep -v restart`
restartRequired=`echo $updates | grep restart | grep -v '*' | cut -d , -f 1`
shutDownRequired=`echo $updates | grep shutdown | grep -v '*' | cut -d , -f 1`

and turned it into

#!/bin/sh
updates=`softwareupdate -l`
updatesNoRestart=`echo $updates | grep recommended | grep -v restart`
[[ -z $updatesNoRestart ]] && updatesNoRestart="none"
restartRequired=`echo $updates | grep restart | grep -v '*' | cut -d , -f 1`
[[ -z $restartRequired ]] && restartRequired="none"
shutDownRequired=`echo $updates | grep shutdown | grep -v '*' | cut -d , -f 1`
[[ -z $shutDownRequired ]] && shutDownRequired="none"

Now the test statements always have a real variable to test against and the race condition of a "real test that works" versus "a test that will fail and cause weird problems" has been prevented.

Taboc741
New Contributor III

......And the Git up is up. Please visit the taboc741 github to see my hastily propped up GIT. The new set of files should allow for deferrals and, as mentioned previously, this update contains the "unary operator" error I was seeing earlier.

Please feel to comment there on my shoddy instructions and/or provide suggestions for making them more clear.

TheDecline
New Contributor III

Question, I would love to utilize this but with our environment we have to hold back on Safari because of internal applications. Is there a way to just update the OS security update?

Taboc741
New Contributor III

@TheDecline Not as the script exists today. Someone better at parsing the string passed back from softwareupdate into $updates could in theory turn that into an array and build a foreach loop to apply the updates 1 at a time excluding ones that containing the words Safari. The catch here is I think Safari updates are often bundled with Apple's security updates so I'm not sure how well it will work in practice.