jamfhelper software update trigger

ImAMacGuy
Valued Contributor II

So I stole the script from https://jamfnation.jamfsoftware.com/featureRequest.html?id=751 and tweaked the wording a bit.

#!/bin/sh

HELPER=`/Library/Application Support/JAMF/bin/jamfHelper.app/Contents/MacOS/jamfHelper -windowType utility -icon /Library/Application Support/JAMF/bin/jamfHelper.app/Contents/Resources/Message.png -heading "Software Updates are Available" -description "Would you like to install updates?  System may need to be rebooted." -button2 "Install" -button1 "Cancel" -cancelButton "1"`


      echo "jamf helper result was $HELPER";

      if [ "$HELPER" == "2" ]; then
         /usr/sbin/jamf policy -trigger SWUJH
         exit 0
      else
         echo "user chose No";   
     exit 1
      fi

I also tweaked the cancel button due to when I was using it in it's default form everything returned "user chose No" and it wouldn't run the trigger.

Now when I select install, the box disappears then immediately reappears and I have to choose install again, and then teh box stays gone, but the trigger never starts the updates.

1 ACCEPTED SOLUTION

acdesigntech
Contributor II

Hey John, I went a few steps further and added some logic into the script to count how many times the user chooses no before the script runs automatically anyway. To me, it gives the user a greater sense of control even if only perceived.

#!/bin/sh

fRunUpdates ()
{

    ## Once the user OKs the updates or they run automatically, reset the timer to 5 
    echo "5" > /Library/Application Support/JAMF/.SoftwareUpdateTimer.txt

    /Library/Application Support/JAMF/bin/jamfHelper.app/Contents/MacOS/jamfHelper -windowType hud -lockhud -heading 'ISD is updating software on your computer' -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 our Service Desk at X4949. Please do not turn off this computer. This message will go away when updates are complete.' -icon /Library/Application Support/JAMF/EndUserSupport/AGRose.icns > /dev/null 2>&1 &

    ## We'll need the pid of jamfHelper to kill it once the updates are complete
    JHPID=`echo "$!"`

    /usr/sbin/jamf policy -trigger SoftwareUpdate & 
    ## Get the Process ID of the last command run in the background ($!) and wait for it to complete (wait)
    SUPID=`echo "$!"`
    wait $SUPID

    ## kill the jamfHelper. If a restart is needed, the user will be prompted. If not the hud will just go away 
    kill -s KILL $JHPID
    exit 0
}



######### Set variables for the script ############

########## Get the group membership for the client #####################
## Get MAC Address using networksetup
MAC=$( networksetup -getmacaddress en0 | awk '{ print $3 }' | sed 's/:/./g' )

## Use the JSS API to get the Mac's group memberships
JSSGroups=$( curl -s -u username:password https://<casper server>:8443/JSSResource/computers/macaddress/$MAC 
| xpath //computer/groups_accounts/computer_group_memberships[1] 
| sed -e 's/<computer_group_memberships>//g;s/</computer_group_memberships>//g;s/<group>//g;s/</group>/
/g' )

## Set up the software update time if it does not exist already
if [ ! -e /Library/Application Support/JAMF/.SoftwareUpdateTimer.txt ]; then
    echo "5" > /Library/Application Support/JAMF/.SoftwareUpdateTimer.txt
fi

## Get the timer value
Timer=`cat /Library/Application Support/JAMF/.SoftwareUpdateTimer.txt`

## Get the currently logged in user, if any. Also check for updates that require a restart and ones that do not.
UpdatesNoRestart=`softwareupdate -l | grep recommended | grep -v restart`
RestartRequired=`softwareupdate -l | grep restart | grep -v '*' | cut -d , -f 1`
LoggedInUser=`who | grep console | awk '{print $1}'`

################ End Variable Set ################

## Use echo and grep to find known-core (non system) software update groups. If these groups are found, run these installers silently since no restarts are required for these updates. Use an array to see which updates we take account of. The names of the array elements are also trigger names for each update. This way when there's a new software package to keep updated, we add the trigger name into the array, and the update policy to the JSS. Casper does the rest
NonSysCore=( 'SoftwareUplift-FlashPlayer' 'SoftwareUplift-Flip4Mac' 'SoftwareUplift-FontNuke' 'SoftwareUplift-PrintWindow' 'SoftwareUplift-MicrosoftOffice' 'SoftwareUplift-MicrosoftOutlook' )

for (( i = 0; i < ${#NonSysCore[@]}; i++ ))
do
    CheckUpdate=`echo "$JSSGroups" | grep "${NonSysCore[$i]}"`
    if [ "$CheckUpdate" != "" ]; then
        jamf policy -trigger "${NonSysCore[$i]}"
    fi
done

## If there are no system updates, quit
if [ "$UpdatesNoRestart" == "" -a "$RestartRequired" == "" ]; then
    echo "No updates at this time"
    exit 0
fi

## If we get to this point and beyond, there are updates. 
## if there is no one logged in, just run the updates
if [ "$LoggedInUser" == "" ]; then
    /usr/sbin/jamf policy -trigger SoftwareUpdate
else
    ## someone is logged in. prompt if any updates require a restart ONLY IF the update timer has not reached zero
    if [ "$RestartRequired" != "" ]; then
            ## If someone is logged in and they have not canceled 5 times already, prompt them to install updates that require a restart and state how many more times they can press 'cancel' before updates run automatically.
        if [ $Timer -gt 0 ]; then
            HELPER=`/Library/Application Support/JAMF/bin/jamfHelper.app/Contents/MacOS/jamfHelper -windowType utility -icon /Library/Application Support/JAMF/EndUserSupport/AGRose.icns -heading "AG ISD Approved Software Updates are Available for Your Mac" -description "These updates will require you to restart your Mac. If you would like to install these now, click 'Install Updates.' If you would not like to install now, click 'Cancel Updates.' You may choose not to install updates $Timer more times before this computer will automatically install them. These updates require a restart of your Mac: $RestartRequired" -button1 "Install Updates" -button2 "Cancel Updates" -cancelButton "2" -defaultButton 2 -timeout 60`
            echo "jamf helper result was $HELPER";
            ## If they click Install Updates then run the updates
            if [ "$HELPER" == "0" ]; then
                fRunUpdates
            else
            ## If no, then reduce the timer by 1. The script will run again the next day 
                let CurrTimer=$Timer-1
                echo "user chose No"
                echo "$CurrTimer" > /Library/Application Support/JAMF/.SoftwareUpdateTimer.txt
                exit 1
            fi
        else
            ## If Timer is already 0, run the updates automatically, the user has been warned!
            fRunUpdates
        fi
    fi
fi

## Install updates that do not require a restart
if [ "$UpdatesNoRestart" != "" ]; then
    /usr/sbin/jamf policy -trigger SoftwareUpdate 
fi

View solution in original post

226 REPLIES 226

dmw3
Contributor III

Also at line 635 there is another change needed:

"honkin' huge!" to "honkin' huge!"

mm2270
Legendary Contributor III

@dmw3 Thanks! Although I don't understand why you'd be seeing a syntax error on the line you mentioned. That entire section is quoted, so there shouldn't be a need to escape the apostrophe in "didn't" or any other word there. To be clear, I always test run my scripts through TextWrangler to be sure they aren't throwing syntax errors, and I wasn't seeing any issue with those lines.

Truthfully in looking it over, i can just remove that entire sub dialog anyone in the modified script anyway, since it was only there in case a user chose nothing to install but clicked the Install button, which actually can't happen in this modified script now, since it will auto select and force checks for any available updates. I'll rip out that whole section and repost the version above, which should fix anything you were seeing.

@daydreamer Thanks! The original version of this script, which is posted much further up, is actually on my github page (https://github.com/mm2270/CasperSuiteScripts) along with other scripts, so feel free to fork and change or submit issues if you see anything that needs to be addressed there. I'll admit I don't always get to addressing any submitted issues in a timely manner, but eventually I will get to it if you submit something.

dmw3
Contributor III

@mm2270 We are seeing this returned in the logs , even though the script runs fine and all dialog boxes appear;

cocoaDialog[2880:30881] CFPasteboardRef CFPasteboardCreate(CFAllocatorRef, CFStringRef) : failed to create global data

There is a discussion about something similar here:
https://jamfnation.jamfsoftware.com/discussion.html?id=17457

Definitely need the deferral section added to this to make it complete, as seeing a number of users just canceling the script on first dialog box.

OSX 10.11.5
JSS 9.91

mm2270
Legendary Contributor III

@dmw3 Yeah, I see that error show up in the logs too, though not all the time. Nothing I can do about it, as its simply because cocoaDialog is ancient and hasn't seen an update since around the 10.7 days. I don't know programming, but its obviously using some code in it that the current OSes (since around 10.10) don't like, so it throws that error. The only good news is it doesn't seem to affect the actual functionality. Its more of an annoyance than anything else.

Stay tuned, as I am looking to work in a deferral option to the script. Once I have something I can show I will post back.

BC2016
New Contributor

@mm2270 I can email you separately if you prefer but I'm a Casper noob and was just wondering, I assume cocoaDialog needs to be pushed to all my user base for this script to work correct? Are there instructions on what to do for that?

Thanks and really great work with this, exactly what we're looking for.

mm2270
Legendary Contributor III

Hi @DL2016 yes, cocoaDialog needs to be physically installed somewhere on your managed Macs for the script to function. As for where, its entirely up to you. Some people put it right in /Applications/ or maybe /Applications/Utilities/ Others, like us, put it in /Library/Application Support/JAMF/bin/ which is the same location jamfHelper lives in.
As for how to deploy it, high level is:

  • You would install it on your admin Mac wherever you want it to go, then open Composer.app. Cancel from choosing one of the default choices for a package if that screen comes up.
  • Locate cocoaDialog where you installed it in the Finder, for example in /Applications/Utilities/ and drag it from the Finder into the Composer Sources sidebar. It will copy it and create a new source in one step.
  • Then, for good measure adjust any permissions on the app in Composer, like make it owned by root/wheel, or root/admin for example and then create a package.
  • Either pkg or dmg should be fine, unless you need to have some kind of script to ride along with it, in which case, only a pkg format can do that.
  • Build it and upload to your Casper share and do a test deployment by adding it into a policy to push it.

Wherever you end up putting it, you need to adjust the cdPath variable in the script to match that path. Since its double quoted in the script, don't use backslash escapes for any spaces or other characters as that's not needed.

Hope that helps.

Also, I'm still planning on posting another version that will just list the updates in a straight list, not checkboxes and also incorporate a deferral option as requested. Haven't had the time to do much with that yet, but I will get to it soon-ish.

FritzsCorner
Contributor III

@mm2270 I had a chance to test this out over the past week and it's working great. I have started taking a crack at adding the deferral stuff but nothing is in a state I would be ready to share just yet.

BC2016
New Contributor

CC: @mm2270

Has anyone experienced any issues with this script where it seems like El Capitan machines are not given the reboot dialog prior to clicking install? Today's Apple security updates have caused some issues for us. Some users get the normal dialog info. Others get a dialog window that they can install but they aren't warned about reboot and it just goes without their consent. Any thoughts? I've had this script up since almost the beginning of August with no issue until today.

Thanks!

FritzsCorner
Contributor III

I took my first crack at adding the deferral functionality to @mm2270's script (I hope you don't mind) and so far it seems to be working ok, although I wouldn't say it is production ready quite yet. I borrowed the deferral logic from @loceee's amazing patchoo script.

I still need to improve the deferral logic a bit. For now the defermode needs to be set to true. The other is the deferral counter reset. Right now, if a user installs any of the available patches, it will reset the counter. So basically it would buy the user another 5 days or whatever threshold you have set to install the remaining updates. I have some ideas on how to address this but wanted to share what I have so far.

Prompt when there are deferrals remaining.

a348c0a1d3414c5093bf36f9b2f6105a

Prompt when there are no deferrals remaining.

eb673f1b80ea4e549aae8d569e78acd1

Here is the modified script.

#!/bin/bash

##  Script Name:        Selectable_SoftwareUpdate.sh    (v005)
##  Script Author:      Mike Morales, @mm2270 on JAMFNation
##  Last Update:        2016-07-19
##  
##  Modified by:        Aaron Stovall
##  Modification Date:  9/7/2016
##
##  Notes:              Deferral Logic borrowed from the Patchoo script by Lachlan Stewart (aka. @loceee)

## Defer Variables
datafolder="/Library/Application Support/JAMF/PatchDeferral"
prefs="$datafolder/com.swu.deferral"

# users can defer x update prompts
defermode=true
defaultdeferthresold="5"

# defer is the # of times a user can defer updates
deferthreshold=$(defaults read "$prefs" DeferThreshold 2> /dev/null)
if [ "$?" != "0" ]
then
    defaults write "$prefs" DeferThreshold -int $defaultdeferthresold
    deferthreshold=$defaultdeferthresold
fi
defercount=$(defaults read "$prefs" DeferCount 2> /dev/null)
if [ "$?" != "0" ]
then
    defaults write "$prefs" DeferCount -int 0
    defercount=0
fi

## Check how many Referrals remain
deferremain=$(( deferthreshold - defercount ))

##  Path to cocoaDialog (customize to your own location)
cdPath="/Applications/Utilities/cocoaDialog.app/Contents/MacOS/cocoaDialog"

##  Quick sanity check to make sure cocoaDialog is installed in the path specified
if [ ! -e "$cdPath" ]; then
    echo "cocoaDialog was not found in the path specified. It may not be installed, or the path is wrong. Exiting..."
    exit 1
fi

##  Set the installAllAtLogin flag here to 'yes' or leave it blank (equivalent to 'no')
##  Function: When the script is run on a Mac that is at the login window, if the flag is set to 'yes',
##  it will lock the login window to prevent unintended logins and proceed to install all available updates.
##  Once completed, the login window will either be unlocked in the case of no restarts needed,
##  or a restart will be done immediately to complete the installations.

installAllAtLogin="yes"

##  Set the forceEnableUpdates flag below to 'yes' (or any value) to enable it. This option will change the
##  checkbox display to show updates as enabled and non-selectable to the user, meaning they will be forced to
##  an "on" state for all. Leaving this value blank will retain the original functionality, allowing the end
##  user to select the updates they would like to install.

if [[ "$deferremain" -eq 0 ]]; then
    echo "You have 0 deferrals remaining. Updates will be installed now"
    forceEnableUpdates="Yes"
else
    echo "You have $deferremain deferrals remaining"
    forceEnableUpdates=""
fi


##  Get minor version of OS X
osVers=$( sw_vers -productVersion | cut -d. -f2 )

##  Set appropriate Software Update icon depending on OS version
if [[ "$osVers" -lt 8 ]]; then
    swuIcon="/System/Library/CoreServices/Software Update.app/Contents/Resources/Software Update.icns"
else
    swuIcon="/System/Library/CoreServices/Software Update.app/Contents/Resources/SoftwareUpdate.icns"
fi

##  Set appropriate Restart icon depending on OS version
if [[ "$osVers" == "9" ]]; then
    restartIcon="/System/Library/CoreServices/loginwindow.app/Contents/Resources/Restart.tiff"
else
    restartIcon="/System/Library/CoreServices/loginwindow.app/Contents/Resources/Restart.png"
fi

##  Start - Check Casper Suite script parameters and assign any that were passed to the script

##  PARAMETER 4: Set the Organization/Department/Division name. Used in dialog titles
##  Default string of "Managed" is used if no script parameter is passed
if [[ "$4" != "" ]]; then
    orgName="$4"
else
    orgName="Managed"
fi

##  PARAMETER 5: Set to "no" (case insensitive) to show a single progress bar update for all installations.
##  Default value of "yes" will be used if no script parameter is passed
if [[ "$5" != "" ]]; then
    shopt -s nocasematch
    if [[ "$5" == "no" ]]; then
        showProgEachUpdate="no"
    else
        showProgEachUpdate="yes"
    fi
    shopt -u nocasematch
else
    showProgEachUpdate="yes"
fi

##  PARAMETER 6: Set the number of minutes until reboot (only used if installations require it)
##  Default value of 5 minutes is assigned if no script parameter is passed
##  Special note: Only full integers can be used. No decimals.
##  If the script detects a non whole integer, it will fall back on the default 5 minute setting.
if [[ "$6" != "" ]]; then
    ## Run test to make sure we have a non floating point integer
    if [[ $(expr "$6" / "$6") == "1" ]]; then
        minToRestart="$6"
    else
        echo "Non integer, or a decimal value was passed. Setting reboot time to default (5 minutes)"
        minToRestart="5"
    fi
else
    minToRestart="5"
fi

##  Parameter 7: Set to the full path of an icon or image file for any dialogs that are not using the
##  Apple Software Update icon. This could be a company logo icon for example
##  Default icon is set in the following manner:
##      If no script parameter is passed, or the icon/image can not be found and JAMF Self Service is present on the Mac, its icon will be used
##      If Self Service is not found, the Software Update icon will be used
if [[ "$7" != "" ]]; then
    if [[ -e "$7" ]]; then
        echo "A custom dialog icon was set: $7"
        msgIcon="$7"
    else
        if [[ -e "/Applications/Self Service.app/Contents/Resources/Self Service.icns" ]]; then
            ##  Self Service present. Use a default Self Service icon if the file specified could not be found
            msgIcon="/Applications/Self Service.app/Contents/Resources/Self Service.icns"
        else
            ##  Icon file not found, and Self Service not present. Set icon to Software Update
            msgIcon="$swuIcon"
        fi
    fi
else
    if [[ -e "/Applications/Self Service.app/Contents/Resources/Self Service.icns" ]]; then
        ##  Self Service present. Use a default Self Service icon if no parameter was passed
        msgIcon="/Applications/Self Service.app/Contents/Resources/Self Service.icns"
    else
        ##  No parameter passed, and Self Service not present. Set icon to Software Update
        msgIcon="$swuIcon"
    fi
fi

##  End - Check Casper Suite script parameters


##  Text displayed in dialog prompting for selections. Customize if desired.
##  Two versions:
##      One,for when reboot *required* updates are found.
##      Two,for when only non-reboot updates are found.
if [[ ! -z "$forceEnableUpdates" ]]; then
    swuTextReboots="The following Apple Software Updates will be installed on your Mac when you click "Install".  No Deferrals are available.

◀  =  Indicates updates that will REQUIRE a reboot of your Mac to complete.

"

else
    swuTextReboots="Select the Apple Software Update items you would like to install now from the list below. You can choose to defer the updates up to 5 times. After 5 deferrals, the updates will be installed automatically.

◀  =  Indicates updates that will REQUIRE a reboot of your Mac to complete.

To install all updates that will not require a reboot, click "Install No Reboot Updates"

"

fi

if [[ ! -z "$forceEnableUpdates" ]]; then
    swuTextNoReboots="The following Apple Software Updates will be installed on your Mac when you click "Install". No Deferrals are available.

"

else
    swuTextNoReboots="Select the Apple Software Update items you would like to install now from the list below. You can choose to defer the updates up to 5 times. After 5 deferrals, the updates will be installed automatically.

"

fi

################################################## ENV VARIABLES #####################################################
##                                                                                                                  ##
##  These variables are gathered to set up the visual environment of the messaging to match the logged in user's   ##
##  settings. We gather the settings, then change the root account's settings to match.                                ##
##                                                                                                                  ##
######################################################################################################################

## Get current logged in user name
loggedInUser=$( ls -l /dev/console | /usr/bin/awk '{ print $3 }' )
echo "Current user is: $loggedInUser"

##  Determine logged in user's home directory path
HomeDir=$( dscl . read /Users/$loggedInUser NFSHomeDirectory | awk '{ print $NF }' )

##  Get logged in user's Appearance color settings
AquaColor=$( defaults read "$HomeDir/Library/Preferences/.GlobalPreferences" AppleAquaColorVariant 2> /dev/null )

##  If user has not changed their settings, value will be null. Set to default 'Aqua' color
if [[ -z "$AquaColor" ]]; then
    AquaColor="1"
else
    AquaColor="$AquaColor"
fi

##  Get logged in user's Keyboard access settings
KeybdMode=$( defaults read "$HomeDir/Library/Preferences/.GlobalPreferences" AppleKeyboardUIMode 2> /dev/null )

##  If user has not changed their settings, value will be null. Set to default 'Text boxes and lists only'
if [[ -z "$KeybdMode" ]]; then
    KeybdMode="0"
else
    KeybdMode="$KeybdMode"
fi

##  Set the root account environment settings to match current logged in user's
defaults write /private/var/root/Library/Preferences/.GlobalPreferences AppleAquaColorVariant -int "${AquaColor}"
defaults write /private/var/root/Library/Preferences/.GlobalPreferences AppleKeyboardUIMode -int "${KeybdMode}"

##  Restart cfprefsd so new settings will be recognized
killall cfprefsd

################################# Do not modify below this line ########################################

##  Function to run when installations are complete
doneRestart ()
{

doneMSG="The installations have completed, but your Mac needs to reboot to finalize the updates.

Your Mac will automatically reboot in $minToRestart minutes. Begin to save any open work and close applications now.

If you want to restart immediately instead, click the "Restart Now" button."

##  Display initial message for 30 seconds before starting the progress bar countdown
doneRestartMsg=$( "$cdPath" msgbox --title "$orgName Software Update > Updates Complete" 
--text "Updates installed successfully" --informative-text "$doneMSG" 
--button1 "     OK     " --button2 "Restart Now" --icon-file "$msgIcon" --posY top --width 450 --timeout 30 --timeout-format " " )

    if [ "$doneRestartMsg" == "1" ]; then
        echo "User pressed OK. Moving on to reboot timer..."
    elif [ "$doneRestartMsg" == "2" ]; then
        echo "User pressed Reboot Now. Rebooting immediately..."
        /sbin/shutdown -r now
    else
        echo "The message timed out. Moving on to reboot timer..."
    fi

    ##  Sub-function to (re)display the progressbar window. Developed to work around the fact that
    ##  CD responds to Cmd+Q and will quit. The script continues the countdown. The sub-function
    ##  causes the progress bar to reappear. When the countdown is done we quit all CD windows
    showProgress ()
    {

    ##  Display progress bar
    "$cdPath" progressbar --title "" --text " Preparing to restart this Mac..." 
    --width 500 --height 90 --icon-file "$restartIcon" --icon-height 48 --icon-width 48 < /tmp/hpipe &

    ##  Send progress through the named pipe
    exec 20<> /tmp/hpipe

    }

##  Close file descriptor 20 if in use, and remove any instance of /tmp/hpipe
exec 20>&-
rm -f /tmp/hpipe

##  Create the name pipe input for the progressbar
mkfifo /tmp/hpipe
sleep 0.2

## Run progress bar sub-function
showProgress

echo "100" >&20

timerSeconds=$((minToRestart*60))
startTime=$( date +"%s" )
stopTime=$((startTime+timerSeconds))
secsLeft=$timerSeconds
progLeft="100"

while [[ "$secsLeft" -gt 0 ]]; do
    sleep 1
    currTime=$( date +"%s" )
    progLeft=$((secsLeft*100/timerSeconds))
    secsLeft=$((stopTime-currTime))
    minRem=$((secsLeft/60))
    secRem=$((secsLeft%60))
    if [[ $(ps axc | grep "cocoaDialog") == "" ]]; then
        showProgress
    fi
    echo "$progLeft $minRem minutes, $secRem seconds until reboot. Please save any work now." >&20
done

echo "Closing progress bar."
exec 20>&-
rm -f /tmp/hpipe

## Close cocoaDialog. This block is necessary for when multiple runs of the sub-function were called in the script
for process in $(ps axc | awk '/cocoaDialog/{print $1}'); do
    /usr/bin/osascript -e 'tell application "cocoaDialog" to quit'
done

##  Clean up by deleting the SWUList file in /tmp/
rm /tmp/SWULIST

##  Delay 1/2 second, then force reboot
sleep 0.5
shutdown -r now

}

##  Function to install selected updates, updating progress bar with information
installUpdates ()
{

if [[ "${restartReq}" == "yes" ]]; then
    installMSG="Installations are now running. Please do not shut down your Mac or put it to sleep until the installs finish.

IMPORTANT:
Because you chose some updates that require a restart, we recommend saving any important documents now. Your Mac will reboot soon after the installations are complete."

elif [[ "${restartReq}" == "no" ]] || [[ "${restartReq}" == "" ]]; then
    installMSG="Updates are now installing. Please do not shut down your Mac or put it to sleep until the installs finish."
fi

    ##  Sub-function to display both a button-less CD window and a progress bar
    ##  This sub routine gets called by the enclosing function. It can also be called by
    ##  the install process if it does not see 2 instances of CD running
    showInstallProgress ()
    {

    ##  Display button-less window above progress bar, push to background
    "$cdPath" msgbox --title "$orgName Software Update > Installation" --text "Installations in progress" 
    --informative-text "${installMSG}" --icon-file "${msgIcon}" --width 450 --height 184 --posY top &

    ##  Display progress bar
    echo "Displaying progress bar window."
    "$cdPath" progressbar --title "" --text " Preparing to install selected updates..." 
    --posX "center" --posY 198 --width 450 --float --icon installer < /tmp/hpipe &

    ##  Send progress through the named pipe
    exec 10<> /tmp/hpipe

    }

##  Close file descriptor 10 if in use, and remove any instance of /tmp/hpipe
exec 10>&-
rm -f /tmp/hpipe

##  Create the name pipe input for the progressbar
mkfifo /tmp/hpipe
sleep 0.2

## Run the install progress sub-function (shows button-less CD window and progressbar
showInstallProgress

if [[ "$showProgEachUpdate" == "yes" ]]; then
    echo "Showing individual update progress."
    ##  Run softwareupdate in verbose mode for each selected update, parsing output to feed the progressbar
    ##  Set initial index loop value to 0; set initial update count value to 1; set variable for total updates count
    i=0;
    pkgCnt=1
    pkgTotal="${#selectedItems[@]}"
    for index in "${selectedItems[@]}"; do
        UpdateName="${progSelectedItems[$i]}"
        echo "Now installing ${UpdateName}..."
        /usr/sbin/softwareupdate --verbose -i "${index}" 2>&1 | while read line; do
            ##  Re-run the sub-function to display the cocoaDialog window and progress
            ##  if we are not seeing 2 items for CD in the process list
            if [[ $(ps axc | grep "cocoaDialog" | wc -l | sed 's/^ *//') != "2" ]]; then
                killall cocoaDialog
                showInstallProgress
            fi
            pct=$( echo "$line" | awk '/Progress:/{print $NF}' | cut -d% -f1 )
            echo "$pct Installing ${pkgCnt} of ${pkgTotal}: ${UpdateName}..." >&10
        done
        let i+=1
        let pkgCnt+=1
    done
else
    ## Show a generic progress bar that progresses through all installs at once from 0-100 %
    echo "Parameter 5 was set to "no". Showing single progress bar for all updates"
    softwareupdate --verbose -i "${SWUItems[@]}" 2>&1 | while read line; do
        ##  if we are not seeing 2 items for CD in the process list
        if [[ $(ps axc | grep "cocoaDialog" | wc -l | sed 's/^ *//') != "2" ]]; then
            killall cocoaDialog
            showInstallProgress
        fi
        pct=$( echo "$line" | awk '/Progress:/{print $NF}' | cut -d% -f1 )
        echo "$pct Installing ${#SWUItems[@]} updates..." >&10
    done
fi

echo "Closing progress bar."
exec 10>&-
rm -f /tmp/hpipe

##  Close all instances of cocoaDialog
echo "Closing all cocoaDialog windows."
for process in $(ps axc | awk '/cocoaDialog/{print $1}'); do
    /usr/bin/osascript -e 'tell application "cocoaDialog" to quit'
done

##  If any installed updates required a reboot...
if [[ "${restartReq}" == "yes" ]]; then
    ## ...then move to the restart phase
    ## Reset deferral counter
    defaults write "$prefs" DeferCount -int 0
    doneRestart
##  If no installed updates required a reboot, display updates complete message instead
elif [[ "${restartReq}" == "no" ]]; then
    echo "Showing updates complete message."
    doneMSG="The installations have completed successfully. You can resume working on your Mac."
    "$cdPath" msgbox --title "$orgName Software Update > Updates Complete" 
    --text "Updates installed successfully" --informative-text "$doneMSG" 
    --button1 "    OK    " --posY top --width 450 --icon-file "$msgIcon"

    ## Clean up by deleting the SWUList file in /tmp/ before exiting the script
    echo "Cleaning up SWU list file."
    rm /tmp/SWULIST
    ## Reset deferral counter
    defaults write "$prefs" DeferCount -int 0
    exit 0
fi


}

##  Function to assess which items were checked, and create new arrays
##  used for installations and other functions
assessChecks ()
{

##  Check to see if the installNoReboots flag was set by the user
if [[ "$installNoReboots" == "yes" ]]; then
    echo "User chose to install all non reboot updates. Creating update(s) array and moving to install phase"
    ##  If flag was set, build update arrays from the noReboots array
    for index in "${noReboots[@]}"; do
        selectedItems+=( "${SWUItems[$index]}" )
        hrSelectedItems+=( "${SWUList[$index]}" )
        progSelectedItems+=( "${SWUProg[$index]}" )
    done

    ##  Automatically set the restart required flag to "no"
    restartReq="no"

    ##  Then move on to install updates function
    installUpdates
fi

##  If installNoReboots flag was not set, generate array of formatted
##  checkbox indexes for parsing based on the selections from the user
i=0;
for state in ${Checks[*]}; do
    checkboxstates=$( echo "${i}-${state}" )
    let i+=1
    ##  Set up an array we can read through later with the state of each checkbox
    checkboxfinal+=( "${checkboxstates[@]}" )
done

for check in "${checkboxfinal[@]}"; do
    if [[ "$check" =~ "-1" ]]; then
        ##  First, get the index of the checked item
        index=$( echo "$check" | cut -d- -f1 )
        ##  Second, generate 3 new arrays:
        ##  1) Short names of the updates for the installation
        ##  2) Names of updates as presented in the dialog (for checking restart status)
        ##  3) Names of the updates for updating the progress bar
        selectedItems+=( "${SWUItems[$index]}" )
        hrSelectedItems+=( "${SWUList[$index]}" )
        progSelectedItems+=( "${SWUProg[$index]}" )
    fi
done

echo "The following updates will be installed: ${progSelectedItems[@]}"

##  Determine if any of the checked items require a reboot
restartReq="no"
for item in "${hrSelectedItems[@]}"; do
    if [[ $(echo "${item}" | grep "^◀") != "" ]]; then
        echo "At least one selected update will require reboot. Setting the restartReq flag to "yes""
        restartReq="yes"
        break
    fi
done

echo "Restart required?:   ${restartReq}"

##  If we have some selected items, move to install phase
if [[ ! -z "${selectedItems[@]}" ]]; then
    echo "Updates were selected"
    installUpdates
fi

}

##  The initial message function
startDialog ()
{

##  Generate array of SWUs for dialog
z=0
while read SWU; do
    SWUList+=( "$SWU" )
    if [[ ! -z "$forceEnableUpdates" ]]; then
        checksOnArr+=("$z")
        let z=$((z+1))
    fi
done < <(echo "${readSWUs}")

##  Generate array of SWUs for progress bar
while read item; do
    SWUProg+=( "${item}" )
done < <(echo "${progSWUs}")

##  Generate array of SWUs for installation
while read swuitem; do
    SWUItems+=( "$swuitem" )
done < <(echo "${installSWUs}")


##  Generate an array of indexes for any non-reboot updates
for index in "${!SWUList[@]}"; do
    if [[ $(echo "${SWUList[$index]}" | grep "^◀") == "" ]]; then
        noReboots+=( "$index" )
    fi
done

if [[ ! -z "$forceEnableUpdates" ]]; then
    button3Label=""
    button2Label=""
else
    button3Label=" Install No Reboot Updates "
    button2Label=" Later ($deferremain Deferrals Remain) "
fi

##  Show dialog with selectable options
if [[ ! -z "${noReboots[@]}" ]]; then
    echo "There are some non reboot updates available. Showing selection screen to user"
    SWUDiag=$( "$cdPath" checkbox --title "$orgName Software Update" --items "${SWUList[@]}" --checked "${checksOnArr[@]}" --disabled "${checksOnArr[@]}" 
    --label "$swuTextReboots" --button1 " Install " --button2 "$button2Label" --cancel "button2" --button3 "$button3Label" 
    --icon-file "$msgIcon" --icon-height 80 --icon-width 80 --width 500 --posY top )

    ##  Get the button pressed and the options checked
    Button=$( echo "$SWUDiag" | awk 'NR==1{print $0}' )
    Checks=($( echo "$SWUDiag" | awk 'NR==2{print $0}' ))
    ##  Set up a non array string from the checkboxes returned
    ChecksNonArray=$( echo "$SWUDiag" | awk 'NR==2{print $0}' )

    ##  If the "Install" button was clicked
    if [[ "$Button" == "1" ]]; then
        echo "User clicked the "Install" button."
        ##  Check to see if at least one box was checked
        if [[ $( echo "${ChecksNonArray}" | grep "1" ) == "" ]]; then
            echo "No selections made. Alerting user and returning to selection screen."
            "$cdPath" msgbox --title "$orgName Software Update" --text "No selections were made" 
            --informative-text "$(echo -e "You didn't select any updates to install.

If you want to cancel out of this application, click the "Later" button in the window instead, or press the Esc key.

The Software Update window will appear again momentarily.")" 
            --button1 "    OK    " --timeout 10 --timeout-format " " --width 500 --posY top --icon caution
            ##  Because we are restarting the function, first empty all previously built arrays
            ##  Credit to Cem Baykara (@Cem - JAMFNation) for discovering this issue during testing
            SWUList=()
            SWUProg=()
            SWUItems=()
            ##  Now restart this function after the alert message times out
            startDialog
        else
            ##  "Install" button was clicked and items checked. Run the assess checkbox function
            echo "Selections were made. Moving to assessment function..."
            assessChecks
        fi
    elif [[ "$Button" == "3" ]]; then
        ##  "Install No Reboot Updates" button was clicked. Set the installNoReboots flag to "yes" and skip to check assessment
        echo "User clicked the "Install No Reboot Updates" button."
        installNoReboots="yes"
        assessChecks
    else
        echo "User chose to Defer. Exiting..."
        deferralCheck
        exit 0
    fi

else
    ##  No non-reboot updates were available. Display a different dialog to the user
    echo "No non-reboot updates found, but other updates available. Showing selection dialog to user"
    SWUDiag=$( "$cdPath" checkbox --title "$orgName Software Update" --items "${SWUList[@]}" --checked "${checksOnArr[@]}" --disabled "${checksOnArr[@]}" 
    --label "$swuTextNoReboots" --button1 " Install " --button2 "$button2Label" --cancel "button2" 
    --icon-file "$swuIcon" --icon-height 80 --icon-width 80 --width 500 --posY top --value-required 
    --empty-text "$(echo -e "You must check at least one item before clicking "Install".

If you want to defer, click "Later" or press the esc key.")" )

    ##  Get the button pressed and the options checked
    Button=$( echo "$SWUDiag" | awk 'NR==1{print $0}' )
    Checks=($( echo "$SWUDiag" | awk 'NR==2{print $0}' ))

    if [[ "$Button" == "1" ]]; then
        ##  "Install" button was clicked. Run the assess checkbox function
        echo "User clicked the "Install" button"
        assessChecks
    else
        echo "User chose to Defer from the selection dialog."
        deferralCheck
        echo "Cleaning up SWU list file. Exiting..."
        rm /tmp/SWULIST
        exit 0
    fi
fi

}

##  Function to check the deferral count
deferralCheck ()
{
(( defercount ++ ))
defaults write "$prefs" DeferCount -int $defercount
deferremain=$(( deferthreshold - defercount ))


if $defermode
then
    # check to see if they are allowed to defer anymore
    deferremain=$(( deferthreshold - defercount ))
    if [ $deferremain -eq 0 ] || [ $deferremain -lt 0 ]
    then
        # if the defercounter has run out, FORCED INSTALLATION! set timeout to 30 minutes
        echo "You have no deferrals left"
    else
        # prompt user with defer option 
        echo "You have $deferremain deferrals remaining"

    fi
else
        # if we don't have deferals enabled
        echo "Deferrals are not enabled"
fi

}

##  Function to lock the login window and install all available updates
startLockScreenAgent ()
{

##  Note on this function: To make the script usable outside of a Casper Suite environment,
##  we are using the Apple Remote Management LockScreen.app, located inside the AppleVNCServer bundle.
##  This bundle and corresponding app is installed by default in all recent versions of OS X

##  Set a flag to yes if any updates in the list will require a reboot
while read line; do
    if [[ $(echo "$line" | grep "^◀") != "" ]]; then
        rebootsPresent="yes"
        break
    fi
done < <(echo "$readSWUs")

## Define the name and path to the LaunchAgent plist
PLIST="/Library/LaunchAgents/com.LockLoginScreen.plist"

## Define the text for the xml plist file
LAgentCore="<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>com.LockLoginScreen</string>
    <key>RunAtLoad</key>
    <true/>
    <key>LimitLoadToSessionType</key>
    <string>LoginWindow</string>
    <key>ProgramArguments</key>
    <array>
        <string>/System/Library/CoreServices/RemoteManagement/AppleVNCServer.bundle/Contents/Support/LockScreen.app/Contents/MacOS/LockScreen</string>
        <string>-session</string>
        <string>256</string>
        <string>-msg</string>
        <string>Updates are currently being installed on this Mac. It will automatically be restarted or returned to the login window when installations are complete.</string>
    </array>
</dict>
</plist>"

## Create the LaunchAgent file
echo "Creating the LockLoginScreen LaunchAgent..."
echo "$LAgentCore" > "$PLIST"

## Set the owner, group and permissions on the LaunchAgent plist
echo "Setting proper ownership and permissions on the LaunchAgent..."
chown root:wheel "$PLIST"
chmod 644 "$PLIST"

## Use SIPS to copy and convert the SWU icon to use as the LockScreen icon

## First, back up the original Lock.jpg image
echo "Backing up Lock.jpg image..."
mv /System/Library/CoreServices/RemoteManagement/AppleVNCServer.bundle/Contents/Support/LockScreen.app/Contents/Resources/Lock.jpg 
/System/Library/CoreServices/RemoteManagement/AppleVNCServer.bundle/Contents/Support/LockScreen.app/Contents/Resources/Lock.jpg.bak

## Now, copy and convert the SWU icns file into a new Lock.jpg file
## Note: We are converting it to a png to preserve transparency, but saving it with the .jpg extension so LockScreen.app will recognize it.
## Also resize the image to 400 x 400 pixels so its not so honkin' huge!
echo "Creating SoftwareUpdate icon as png and converting to Lock.jpg..."
sips -s format png "$swuIcon" --out /System/Library/CoreServices/RemoteManagement/AppleVNCServer.bundle/Contents/Support/LockScreen.app/Contents/Resources/Lock.jpg 
--resampleWidth 400 --resampleHeight 400

## Now, kill/restart the loginwindow process to load the LaunchAgent
echo "Ready to lock screen. Restarting loginwindow process..."
kill -9 $(ps axc | awk '/loginwindow/{print $1}')

## Install all available Software Updates
echo "Screen locked. Installing all available Software Updates..."
/usr/sbin/softwareupdate --install --all

if [ "$?" == "0" ]; then
    ## Delete LaunchAgent and reload the Login Window
    echo "Deleting the LaunchAgent..."
    rm "$PLIST"
    sleep 1

    if [[ "$rebootsPresent" == "yes" ]]; then
        ## Put the original Lock.jpg image back where it was, overwriting the SWU Icon image
        echo "The rebootsPresent flag was set to 'yes' Replacing Lock.jpg image and immediately rebooting the Mac..."
        mv /System/Library/CoreServices/RemoteManagement/AppleVNCServer.bundle/Contents/Support/LockScreen.app/Contents/Resources/Lock.jpg.bak 
        /System/Library/CoreServices/RemoteManagement/AppleVNCServer.bundle/Contents/Support/LockScreen.app/Contents/Resources/Lock.jpg

        ## Kill the LockScreen app and restart immediately
        killall LockScreen
        /sbin/shutdown -r now
    else
        ## Put the original Lock.jpg image back where it was, overwriting the SWU Icon image
        echo "The rebootsPresent flag was not set. Replacing Lock.jpg image and restoring the loginwindow..."
        mv /System/Library/CoreServices/RemoteManagement/AppleVNCServer.bundle/Contents/Support/LockScreen.app/Contents/Resources/Lock.jpg.bak 
        /System/Library/CoreServices/RemoteManagement/AppleVNCServer.bundle/Contents/Support/LockScreen.app/Contents/Resources/Lock.jpg

        ## Kill/restart the login window process to return to the login window
        kill -9 $(ps axc | awk '/loginwindow/{print $1}')
    fi

else

    echo "There was an error with the installations. Removing the Agent and unlocking the login window..."

    rm "$PLIST"
    sleep 1

    mv /System/Library/CoreServices/RemoteManagement/AppleVNCServer.bundle/Contents/Support/LockScreen.app/Contents/Resources/Lock.jpg.bak 
    /System/Library/CoreServices/RemoteManagement/AppleVNCServer.bundle/Contents/Support/LockScreen.app/Contents/Resources/Lock.jpg

    ## Kill/restart the login window process to return to the login window
    kill -9 $(ps axc | awk '/loginwindow/{print $1}')
    exit 0
fi

}

##  The script starts here

##  Gather available Software Updates and export to a file
echo "Pulling available Software Updates..."
/usr/sbin/softwareupdate -l > /tmp/SWULIST
echo "Finished pulling available Software Updates into local file"

echo "Checking to see what updates are available..."
##  Generate list of readable items and installable items from file
readSWUs=$( cat /tmp/SWULIST | awk -F"," '/recommended/{print $2,$1}' | sed -e 's/[0-9]*K [recommended][ *]//g;s/[restart] */◀ /g' | sed 's/[    ]//g' )
progSWUs=$( cat /tmp/SWULIST | awk -F"," '/recommended/{print $2,$1}' | sed -e 's/[0-9]*K [recommended][ *]//g;s/[restart] *//g' | sed 's/[  ]//g' )
installSWUs=$( cat /tmp/SWULIST | grep -v 'recommended' | awk -F'\* ' '/*/{print $NF}' )

##  First, make sure there's at least one update from Software Update
if [[ -z "$readSWUs" ]]; then
    echo "No pending Software Updates found for this Mac. Exiting..."
    exit 0
elif [[ ! -z "$readSWUs" ]] && [[ "$loggedInUser" != "root" ]]; then
    echo "Software Updates are available, and a user is logged in. Moving to initial dialog..."
    startDialog
elif [[ ! -z "$readSWUs" ]] && [[ "$loggedInUser" == "root" ]]; then
    if [ "$installAllAtLogin" == "yes" ]; then
        echo "SWUs are available, no-one logged in and the installAllAtLogin flag was set. Locking screen and installing all updates..."
        startLockScreenAgent
    else
        echo "SWUs are available, no-one logged in but the installAllAtLogin flag was not set. Exiting..."
        exit 0
    fi
fi

ImAMacGuy
Valued Contributor II

@FritzsCorner wow, awesome, thank you for your (and everybody else) work!

Couple things thoughts. one is a different icon came up on the 2nd run through, purely cosmetic, but the first was a jamf icon, the 2nd was the apple software update icon..

5ba76875c12843d1bb2b2f403cfac593
a003fde9ee414654ba75edbb1331be74

the other is, can the install buttons be greyed out unless a package is selected?
30ad1f518845488f8dba89da08628372

mm2270
Legendary Contributor III

@jwojda No, there's no way in cocoaDialog to grey out buttons. The sheet you see that comes down is there to prevent someone from clicking Install without something selected. Its a little known feature of the CD 3 beta called --value-required and its companion --empty-text

Nice work @FritzsCorner! I will do some testing and incorporate this in or make any necessary adjustments, as soon as I get the chance.

FritzsCorner
Contributor III

@jwojda I never noticed the icon change, but I just checked and see the same thing myself.

@mm2270 thanks for the amazing script. I have been looking for an easy way of letting the end user have a choice to install software updates that don't require a reboot without logging them out. But I still needed some bit of enforcement to make sure they also get the updates that do require a reboot. I am not ready to put this in my prod environment yet, but I will continue to make a few tweaks on my end as well.

mm2270
Legendary Contributor III

I can explain the icon difference. Just a slip up in my script. If you look for the following line:

SWUDiag=$( "$cdPath" checkbox --title "$orgName Software Update" --items "${SWUList[@]}" --checked "${checksOnArr[@]}" --disabled "${checksOnArr[@]}"

Look 2 lines below that and you'll see that the --icon-file flag is set to "$swuIcon" which points to the local Software Update icon. This dialog is called when there are no non-reboot updates available, so it presents that icon when the only updates it can show don't call for the "Install No Reboot Updates" button.

In the other cocoaDialog dialog lines, I'm specifying "$msgIcon" as the icon variable, which, unless you designate something specific, will use the Self Service icon.

So the answer on how to fix it all depends on what you'd like to use as the icon for all those update dialogs so they are consistent. If you want them all as the SS icon, change the $swuIcon to $msgIcon. If the other way around, swap them, change all instances of $msgIcon to $swuIcon

I'll update my script accordingly to correct that when I get a moment. Thanks for pointing it out.

perrycj
Contributor III

@FritzsCorner Thanks for posting your work on @mm2270 script and adding the deferral option. For me, in testing, the deferral option works but I also can just quit out of the script at any time.

If I get to no deferrals and install is the only option, I quit ( using command+q) and it just quits. When I call it again, it then has a negative referral count (-1 deferrals left, -2 deferrals left, etc). I've tried running from terminal (calling the policy), pushing just the script through Casper Remote and running from Self Service, and all modes I can just quit the script. Although, with terminal and SS, it is probably just quitting that service which forces the script to stop running. However, doesn't explain why it happens from Casper Remote.

Have you seen anything like this in your testing?

ImAMacGuy
Valued Contributor II

i tested it on my machine (admittedly on 10.12 GM), but the dialog box appeared as expected, I selected the install, and it said it completed, but it still showed as an available update on the App Store and the installed version still had the previous one..

Script exit code: 0
Script result: You have 5 deferrals remaining
Current user is: jwojda
Pulling available Software Updates...
Finished pulling available Software Updates into local file
Checking to see what updates are available...
Software Updates are available, and a user is logged in. Moving to initial dialog...
There are some non reboot updates available. Showing selection screen to user
User clicked the "Install" button.
No selections made. Alerting user and returning to selection screen.
1
There are some non reboot updates available. Showing selection screen to user
User clicked the "Install" button.
Selections were made. Moving to assessment function...
The following updates will be installed: iTunes(12.5.1)
Restart required?: no
Updates were selected
Displaying progress bar window.
Showing individual update progress.
Now installing iTunes(12.5.1)...
Closing progress bar.
Closing all cocoaDialog windows.
Showing updates complete message.
1
Cleaning up SWU list file.
Running Recon...
Retrieving inventory preferences from https://jss.urlh.ere/...
Locating accounts...
Searching path: /Applications
Locating package receipts...
Locating software updates...
Locating plugins...

FritzsCorner
Contributor III

@perrycj The closing of cocoaDialog and the negative deferral count I have seen. To prevent closing the window via command+q I don't really have the best solution right now... but I do have a very weak, easy to defeat deterrent that I have tested before. That is to change the keyboard shortcut combination to quit cocoaDialog itself. Like I said... it's weak. :)

a1ce217edbfd4081aa0e14f92cd1f548

This script, with my modifications at least, is not a production worthy solution as of yet. I am making tweaks as I have time.

The deferral method I used was based on what is in patchoo, but there was another script earlier in the thread that uses a different approach.

https://github.com/matthewbodaly/ShellScripts/blob/master/SWUpdateMainv1.sh

FritzsCorner
Contributor III

@jwojda I have not run into this issue with this particular script. I have seen something similar before when I called softwareupdate via script without the full path to the binary ( /usr/sbin/softwareupdate) but that shouldn't be the case with this script. @mm2270 has a lot of cool stuff going on that is a bit more advanced then my scripting knowledge. It has been fun going through the script to see how he does a lot of this.. I am still learning.. I still have a long way to go.

mm2270
Legendary Contributor III

As soon as I can, I will do some thorough tests and make some adjustments to help prevent issues like described above.
Being able to quit a cocoaDialog message is unfortunately a known issue. Since its an application, the developer designed it to respond to Cmd-Q, though personally I think this doesn't make sense, since the app doesn't show up in the Dock like other apps.
There are some ways you can work around this. For example, when a cocoaDialog dialog is shut down by pressing Cmd-Q, it exits with a different button exit code if I'm not mistaken (or maybe a blank button code), than when a button is clicked, so one thought I had would be, once conditions are met that the installs are to be forced, check to see if the user simply quit cocoaDialog and if so, run the installs, since that's the intention anyway. That should prevent anyone from escaping the enforced installs, and also prevent the negative deferral values from showing up since it would get reset after doing the installations.

I'll post an example script on what I'm talking about in a little bit.

perrycj
Contributor III

@mm2270 That is unfortunate but at least now I know I'm not crazy.

I agree that the button exit workflow might be the way to go. I'd be interested to see what you come up with. I've poked around with the documentation from CocoaDialog in the past, might be time to give it another look.

mm2270
Legendary Contributor III

Hi @FritzsCorner @perrycj @jwojda and anyone else that wants to do some testing, I'm posting an update to the script here. I rewrote the deferral functionality to make it a little more simple (to me at least) and updated the code to detect when cocoaDialog gets "quit" by the user. Not just on that final enforcement dialog, but on any of them. Essentially what should happen is, if the user uses Cmd-Q while cocoaDialog is up, it detects this and simply drops the deferral count down by one, same as if they clicked the deferral button. It notes the difference in the log output however. If the user uses Quit it would show something like this:

User force canceled from dialog. Remaining deferral count reduced by 1
New deferral remaining count will be 4

If the user uses the Defer button in the dialog, it instead notes this:

User chose to Defer. Remaining deferral count reduced by 1
New deferral remaining count will be 4

If the dialog is in that final enforce mode, instead of simply quitting, it should start running the updates as planned, even if the use the Cmd-Q trick to try to escape it.

A few other changes. As noted, I changed up the deferral counter so its a simple file that keeps track of the number of deferrals left to the client. I also reduced the icon size a little bit as I was thinking the size I had it at (80 x 80 pixels) was looking a little big. I also fixed the issue with the 2 different icons appearing depending on which type of dialog was shown.

I haven't done very thorough testing with this new version just yet, so consider this very much a beta. Feel free to do some testing with it and please let me know if you see any issues or if anything is broken so I can address it.

#!/bin/bash

##  Script Name:        Selectable_SoftwareUpdate.sh    (v005)
##  Script Author:      Mike Morales, @mm2270 on JAMFNation
##  Last Update:        2016-07-19
##
##  Modified by:        Aaron Stovall
##  Modification Date:  9/7/2016
##
##  Modified further by Mike Morales on 2016-09-14

##  Path to cocoaDialog (customize to your own location)
cdPath="/Library/Application Support/JAMF/bin/cocoaDialog.app/Contents/MacOS/cocoaDialog"


## Defer Variables

## Default starting number of deferrals
defaultDeferralCount="5"

## Assign the deferral folder location
dataFolder="/Library/Application Support/JAMF/bin/PatchDeferral"

## Create the data folder if it doesn't already exist
if [ ! -d "$dataFolder" ]; then
    echo "Initial PatchDeferral folder isn't present. Creating it..."
    mkdir "$dataFolder"
fi

## Create the deferral counter file with a default value...
if [ ! -e "${dataFolder}/deferralCount" ]; then
    echo "Initial deferralCount file isn't present. Creating it with default value of ${defaultDeferralCount}..."
    echo "$defaultDeferralCount" > "${dataFolder}/deferralCount"
    deferralsLeft="$defaultDeferralCount"
else
    ## or pick up the remaining deferrals left in the file
    deferralsLeft=$(cat "${dataFolder}/deferralCount")
    if [ "$deferralsLeft" == "" ]; then
        deferralsLeft="$defaultDeferralCount"
    fi
fi

## users can defer x update prompts
deferMode=true

##  Quick sanity check to make sure cocoaDialog is installed in the path specified
if [ ! -e "$cdPath" ]; then
    echo "cocoaDialog was not found in the path specified. It may not be installed, or the path is wrong. Exiting..."
    exit 1
fi

##  Set the installAllAtLogin flag here to 'yes' or leave it blank (equivalent to 'no')
##  Function: When the script is run on a Mac that is at the login window, if the flag is set to 'yes',
##  it will lock the login window to prevent unintended logins and proceed to install all available updates.
##  Once completed, the login window will either be unlocked in the case of no restarts needed,
##  or a restart will be done immediately to complete the installations.

installAllAtLogin="yes"

##  Set the forceEnableUpdates flag below to 'yes' (or any value) to enable it. This option will change the
##  checkbox display to show updates as enabled and non-selectable to the user, meaning they will be forced to
##  an "on" state for all. Leaving this value blank will retain the original functionality, allowing the end
##  user to select the updates they would like to install.

if [[ "$deferralsLeft" -eq 0 ]]; then
    echo "0 deferrals remaining. Updates will be installed now"
    forceEnableUpdates="Yes"
else
    echo "You have $deferralsLeft deferrals remaining"
    forceEnableUpdates=""
fi


##  Get minor version of OS X
osVers=$( sw_vers -productVersion | cut -d. -f2 )

##  Set appropriate Software Update icon depending on OS version
if [[ "$osVers" -lt 8 ]]; then
    swuIcon="/System/Library/CoreServices/Software Update.app/Contents/Resources/Software Update.icns"
else
    swuIcon="/System/Library/CoreServices/Software Update.app/Contents/Resources/SoftwareUpdate.icns"
fi

##  Set appropriate Restart icon depending on OS version
if [[ "$osVers" == "9" ]]; then
    restartIcon="/System/Library/CoreServices/loginwindow.app/Contents/Resources/Restart.tiff"
else
    restartIcon="/System/Library/CoreServices/loginwindow.app/Contents/Resources/Restart.png"
fi

##  Start - Check Casper Suite script parameters and assign any that were passed to the script

##  PARAMETER 4: Set the Organization/Department/Division name. Used in dialog titles
##  Default string of "Managed" is used if no script parameter is passed
if [[ "$4" != "" ]]; then
    orgName="$4"
else
    orgName="Managed"
fi

##  PARAMETER 5: Set to "no" (case insensitive) to show a single progress bar update for all installations.
##  Default value of "yes" will be used if no script parameter is passed
if [[ "$5" != "" ]]; then
    shopt -s nocasematch
    if [[ "$5" == "no" ]]; then
        showProgEachUpdate="no"
    else
        showProgEachUpdate="yes"
    fi
    shopt -u nocasematch
else
    showProgEachUpdate="yes"
fi

##  PARAMETER 6: Set the number of minutes until reboot (only used if installations require it)
##  Default value of 5 minutes is assigned if no script parameter is passed
##  Special note: Only full integers can be used. No decimals.
##  If the script detects a non whole integer, it will fall back on the default 5 minute setting.
if [[ "$6" != "" ]]; then
    ## Run test to make sure we have a non floating point integer
    if [[ $(expr "$6" / "$6") == "1" ]]; then
        minToRestart="$6"
    else
        echo "Non integer, or a decimal value was passed. Setting reboot time to default (5 minutes)"
        minToRestart="5"
    fi
else
    minToRestart="5"
fi

##  Parameter 7: Set to the full path of an icon or image file for any dialogs that are not using the
##  Apple Software Update icon. This could be a company logo icon for example
##  Default icon is set in the following manner:
##      If no script parameter is passed, or the icon/image can not be found and JAMF Self Service is present on the Mac, its icon will be used
##      If Self Service is not found, the Software Update icon will be used
if [[ "$7" != "" ]]; then
    if [[ -e "$7" ]]; then
        echo "A custom dialog icon was set: $7"
        msgIcon="$7"
    else
        if [[ -e "/Applications/Self Service.app/Contents/Resources/Self Service.icns" ]]; then
            ##  Self Service present. Use a default Self Service icon if the file specified could not be found
            msgIcon="/Applications/Self Service.app/Contents/Resources/Self Service.icns"
        else
            ##  Icon file not found, and Self Service not present. Set icon to Software Update
            msgIcon="$swuIcon"
        fi
    fi
else
    if [[ -e "/Applications/Self Service.app/Contents/Resources/Self Service.icns" ]]; then
        ##  Self Service present. Use a default Self Service icon if no parameter was passed
        msgIcon="/Applications/Self Service.app/Contents/Resources/Self Service.icns"
    else
        ##  No parameter passed, and Self Service not present. Set icon to Software Update
        msgIcon="$swuIcon"
    fi
fi

##  End - Check Casper Suite script parameters


##  Text displayed in dialog prompting for selections. Customize if desired.
##  Two versions:
##      One,for when reboot *required* updates are found.
##      Two,for when only non-reboot updates are found.
if [[ ! -z "$forceEnableUpdates" ]]; then
    swuTextReboots="The following Apple Software Updates will be installed on your Mac when you click "Install".  No Deferrals are available.

◀  =  Indicates updates that will REQUIRE a reboot of your Mac to complete.

"

else
    swuTextReboots="Select the Apple Software Update items you would like to install now from the list below. You can choose to defer the updates up to 5 times. After 5 deferrals, the updates will be installed automatically.

◀  =  Indicates updates that will REQUIRE a reboot of your Mac to complete.

To install all updates that will not require a reboot, click "Install No Reboot Updates"

"

fi

if [[ ! -z "$forceEnableUpdates" ]]; then
    swuTextNoReboots="The following Apple Software Updates will be installed on your Mac when you click "Install". No Deferrals are available.

"

else
    swuTextNoReboots="Select the Apple Software Update items you would like to install now from the list below. You can choose to defer the updates up to 5 times. After 5 deferrals, the updates will be installed automatically.

"

fi

################################################## ENV VARIABLES #####################################################
##                                                                                                                  ##
##  These variables are gathered to set up the visual environment of the messaging to match the logged in user's   ##
##  settings. We gather the settings, then change the root account's settings to match.                                ##
##                                                                                                                  ##
######################################################################################################################

## Get current logged in user name
loggedInUser=$( ls -l /dev/console | /usr/bin/awk '{ print $3 }' )
echo "Current user is: $loggedInUser"

##  Determine logged in user's home directory path
HomeDir=$( dscl . read /Users/$loggedInUser NFSHomeDirectory | awk '{ print $NF }' )

##  Get logged in user's Appearance color settings
AquaColor=$( defaults read "$HomeDir/Library/Preferences/.GlobalPreferences" AppleAquaColorVariant 2> /dev/null )

##  If user has not changed their settings, value will be null. Set to default 'Aqua' color
if [[ -z "$AquaColor" ]]; then
    AquaColor="1"
else
    AquaColor="$AquaColor"
fi

##  Get logged in user's Keyboard access settings
KeybdMode=$( defaults read "$HomeDir/Library/Preferences/.GlobalPreferences" AppleKeyboardUIMode 2> /dev/null )

##  If user has not changed their settings, value will be null. Set to default 'Text boxes and lists only'
if [[ -z "$KeybdMode" ]]; then
    KeybdMode="0"
else
    KeybdMode="$KeybdMode"
fi

##  Set the root account environment settings to match current logged in user's
defaults write /private/var/root/Library/Preferences/.GlobalPreferences AppleAquaColorVariant -int "${AquaColor}"
defaults write /private/var/root/Library/Preferences/.GlobalPreferences AppleKeyboardUIMode -int "${KeybdMode}"

##  Restart cfprefsd so new settings will be recognized
killall cfprefsd

################################# Do not modify below this line ########################################

##  Function to run when installations are complete
doneRestart ()
{

doneMSG="The installations have completed, but your Mac needs to reboot to finalize the updates.

Your Mac will automatically reboot in $minToRestart minutes. Begin to save any open work and close applications now.

If you want to restart immediately instead, click the "Restart Now" button."

##  Display initial message for 30 seconds before starting the progress bar countdown
doneRestartMsg=$( "$cdPath" msgbox --title "$orgName Software Update > Updates Complete" 
--text "Updates installed successfully" --informative-text "$doneMSG" 
--button1 "     OK     " --button2 "Restart Now" --icon-file "$msgIcon" --posY top --width 450 --timeout 30 --timeout-format " " )

    if [ "$doneRestartMsg" == "1" ]; then
        echo "User pressed OK. Moving on to reboot timer..."
    elif [ "$doneRestartMsg" == "2" ]; then
        echo "User pressed Reboot Now. Rebooting immediately..."
        /sbin/shutdown -r now
    else
        echo "The message timed out. Moving on to reboot timer..."
    fi

    ##  Sub-function to (re)display the progressbar window. Developed to work around the fact that
    ##  CD responds to Cmd+Q and will quit. The script continues the countdown. The sub-function
    ##  causes the progress bar to reappear. When the countdown is done we quit all CD windows
    showProgress ()
    {

    ##  Display progress bar
    "$cdPath" progressbar --title "" --text " Preparing to restart this Mac..." 
    --width 500 --height 90 --icon-file "$restartIcon" --icon-height 48 --icon-width 48 < /tmp/hpipe &

    ##  Send progress through the named pipe
    exec 20<> /tmp/hpipe

    }

##  Close file descriptor 20 if in use, and remove any instance of /tmp/hpipe
exec 20>&-
rm -f /tmp/hpipe

##  Create the name pipe input for the progressbar
mkfifo /tmp/hpipe
sleep 0.2

## Run progress bar sub-function
showProgress

echo "100" >&20

timerSeconds=$((minToRestart*60))
startTime=$( date +"%s" )
stopTime=$((startTime+timerSeconds))
secsLeft=$timerSeconds
progLeft="100"

while [[ "$secsLeft" -gt 0 ]]; do
    sleep 1
    currTime=$( date +"%s" )
    progLeft=$((secsLeft*100/timerSeconds))
    secsLeft=$((stopTime-currTime))
    minRem=$((secsLeft/60))
    secRem=$((secsLeft%60))
    if [[ $(ps axc | grep "cocoaDialog") == "" ]]; then
        showProgress
    fi
    echo "$progLeft $minRem minutes, $secRem seconds until reboot. Please save any work now." >&20
done

echo "Closing progress bar."
exec 20>&-
rm -f /tmp/hpipe

##  Close cocoaDialog. This block is necessary for when multiple runs of the sub-function were called in the script
for process in $(ps axc | awk '/cocoaDialog/{print $1}'); do
    /usr/bin/osascript -e 'tell application "cocoaDialog" to quit'
done

##  Clean up by deleting the SWUList file in /tmp/
rm /tmp/SWULIST

##  Delay 1/2 second, then force reboot
sleep 0.5
shutdown -r now

}

##  Function to install selected updates, updating progress bar with information
installUpdates ()
{

if [[ "${restartReq}" == "yes" ]]; then
    installMSG="Installations are now running. Please do not shut down your Mac or put it to sleep until the installs finish.

IMPORTANT:
Because you chose some updates that require a restart, we recommend saving any important documents now. Your Mac will reboot soon after the installations are complete."

elif [[ "${restartReq}" == "no" ]] || [[ "${restartReq}" == "" ]]; then
    installMSG="Updates are now installing. Please do not shut down your Mac or put it to sleep until the installs finish."
fi

    ##  Sub-function to display both a button-less CD window and a progress bar
    ##  This sub routine gets called by the enclosing function. It can also be called by
    ##  the install process if it does not see 2 instances of CD running
    showInstallProgress ()
    {

    ##  Display button-less window above progress bar, push to background
    "$cdPath" msgbox --title "$orgName Software Update > Installation" --text "Installations in progress" 
    --informative-text "${installMSG}" --icon-file "${msgIcon}" --width 450 --height 184 --posY top &

    ##  Display progress bar
    echo "Displaying progress bar window."
    "$cdPath" progressbar --title "" --text " Preparing to install selected updates..." 
    --posX "center" --posY 198 --width 450 --float --icon installer < /tmp/hpipe &

    ##  Send progress through the named pipe
    exec 10<> /tmp/hpipe

    }

##  Close file descriptor 10 if in use, and remove any instance of /tmp/hpipe
exec 10>&-
rm -f /tmp/hpipe

##  Create the name pipe input for the progressbar
mkfifo /tmp/hpipe
sleep 0.2

## Run the install progress sub-function (shows button-less CD window and progressbar
showInstallProgress

if [[ "$showProgEachUpdate" == "yes" ]]; then
    echo "Showing individual update progress."
    ##  Run softwareupdate in verbose mode for each selected update, parsing output to feed the progressbar
    ##  Set initial index loop value to 0; set initial update count value to 1; set variable for total updates count
    i=0;
    pkgCnt=1
    pkgTotal="${#selectedItems[@]}"
    for index in "${selectedItems[@]}"; do
        UpdateName="${progSelectedItems[$i]}"
        echo "Now installing ${UpdateName}..."
        /usr/sbin/softwareupdate --verbose -i "${index}" 2>&1 | while read line; do
            ##  Re-run the sub-function to display the cocoaDialog window and progress
            ##  if we are not seeing 2 items for CD in the process list
            if [[ $(ps axc | grep "cocoaDialog" | wc -l | sed 's/^ *//') != "2" ]]; then
                killall cocoaDialog
                showInstallProgress
            fi
            pct=$( echo "$line" | awk '/Progress:/{print $NF}' | cut -d% -f1 )
            echo "$pct Installing ${pkgCnt} of ${pkgTotal}: ${UpdateName}..." >&10
        done
        let i+=1
        let pkgCnt+=1
    done
else
    ## Show a generic progress bar that progresses through all installs at once from 0-100 %
    echo "Parameter 5 was set to "no". Showing single progress bar for all updates"
    softwareupdate --verbose -i "${SWUItems[@]}" 2>&1 | while read line; do
        ##  if we are not seeing 2 items for CD in the process list
        if [[ $(ps axc | grep "cocoaDialog" | wc -l | sed 's/^ *//') != "2" ]]; then
            killall cocoaDialog
            showInstallProgress
        fi
        pct=$( echo "$line" | awk '/Progress:/{print $NF}' | cut -d% -f1 )
        echo "$pct Installing ${#SWUItems[@]} updates..." >&10
    done
fi

echo "Closing progress bar."
exec 10>&-
rm -f /tmp/hpipe

##  Close all instances of cocoaDialog
echo "Closing all cocoaDialog windows."
for process in $(ps axc | awk '/cocoaDialog/{print $1}'); do
    /usr/bin/osascript -e 'tell application "cocoaDialog" to quit'
done

##  Reset the deferral counter file back to the default value after installations complete
echo "$defaultDeferralCount" > "${dataFolder}/deferralCount"

##  If any installed updates required a reboot...
if [[ "${restartReq}" == "yes" ]]; then
    ## ...then move to the restart phase
    ## Reset deferral counter
    defaults write "$prefs" DeferCount -int 0
    doneRestart
##  If no installed updates required a reboot, display updates complete message instead
elif [[ "${restartReq}" == "no" ]]; then
    echo "Showing updates complete message."
    doneMSG="The installations have completed successfully. You can resume working on your Mac."
    "$cdPath" msgbox --title "$orgName Software Update > Updates Complete" 
    --text "Updates installed successfully" --informative-text "$doneMSG" 
    --button1 "    OK    " --posY top --width 450 --icon-file "$msgIcon"

    ## Clean up by deleting the SWUList file in /tmp/ before exiting the script
    echo "Cleaning up SWU list file."
    rm /tmp/SWULIST
    ## Reset deferral counter
    defaults write "$prefs" DeferCount -int 0
    exit 0
fi


}

##  Function to assess which items were checked, and create new arrays
##  used for installations and other functions
assessChecks ()
{

##  Check to see if the installNoReboots flag was set by the user
if [[ "$installNoReboots" == "yes" ]]; then
    echo "User chose to install all non reboot updates. Creating update(s) array and moving to install phase"
    ##  If flag was set, build update arrays from the noReboots array
    for index in "${noReboots[@]}"; do
        selectedItems+=( "${SWUItems[$index]}" )
        hrSelectedItems+=( "${SWUList[$index]}" )
        progSelectedItems+=( "${SWUProg[$index]}" )
    done

    ##  Automatically set the restart required flag to "no"
    restartReq="no"

    ##  Then move on to install updates function
    installUpdates
fi

##  If installNoReboots flag was not set, generate array of formatted
##  checkbox indexes for parsing based on the selections from the user
i=0;
for state in ${Checks[*]}; do
    checkboxstates=$( echo "${i}-${state}" )
    let i+=1
    ##  Set up an array we can read through later with the state of each checkbox
    checkboxfinal+=( "${checkboxstates[@]}" )
done

for check in "${checkboxfinal[@]}"; do
    if [[ "$check" =~ "-1" ]]; then
        ##  First, get the index of the checked item
        index=$( echo "$check" | cut -d- -f1 )
        ##  Second, generate 3 new arrays:
        ##  1) Short names of the updates for the installation
        ##  2) Names of updates as presented in the dialog (for checking restart status)
        ##  3) Names of the updates for updating the progress bar
        selectedItems+=( "${SWUItems[$index]}" )
        hrSelectedItems+=( "${SWUList[$index]}" )
        progSelectedItems+=( "${SWUProg[$index]}" )
    fi
done

echo "The following updates will be installed: ${progSelectedItems[@]}"

##  Determine if any of the checked items require a reboot
restartReq="no"
for item in "${hrSelectedItems[@]}"; do
    if [[ $(echo "${item}" | grep "^◀") != "" ]]; then
        echo "At least one selected update will require reboot. Setting the restartReq flag to "yes""
        restartReq="yes"
        break
    fi
done

echo "Restart required?:   ${restartReq}"

##  If we have some selected items, move to install phase
if [[ ! -z "${selectedItems[@]}" ]]; then
    echo "Updates were selected"
    installUpdates
fi

}

##  The initial message function
startDialog ()
{

if [ "$deferralsLeft" -eq 0 ]; then
    forceEnableUpdates="Yes"
fi

##  Generate array of SWUs for dialog
z=0
while read SWU; do
    SWUList+=( "$SWU" )
    if [[ ! -z "$forceEnableUpdates" ]]; then
        checksOnArr+=("$z")
        let z=$((z+1))
    fi
done < <(echo "${readSWUs}")

##  Generate array of SWUs for progress bar
while read item; do
    SWUProg+=( "${item}" )
done < <(echo "${progSWUs}")

##  Generate array of SWUs for installation
while read swuitem; do
    SWUItems+=( "$swuitem" )
done < <(echo "${installSWUs}")


##  Generate an array of indexes for any non-reboot updates
for index in "${!SWUList[@]}"; do
    if [[ $(echo "${SWUList[$index]}" | grep "^◀") == "" ]]; then
        noReboots+=( "$index" )
    fi
done

if [[ ! -z "$forceEnableUpdates" ]]; then
    button3Label=""
    button2Label=""
else
    button3Label=" Install No Reboot Updates "
    button2Label="Later ($deferralsLeft deferrals left)"
fi

##  Show dialog with selectable options
if [[ ! -z "${noReboots[@]}" ]]; then
    echo "There are some non reboot updates available. Showing selection screen to user"
    SWUDiag=$( "$cdPath" checkbox --title "$orgName Software Update" --items "${SWUList[@]}" --checked "${checksOnArr[@]}" --disabled "${checksOnArr[@]}" 
    --label "$swuTextReboots" --button1 " Install " --button2 "$button2Label" --cancel "button2" --button3 "$button3Label" 
    --icon-file "$msgIcon" --icon-height 64 --icon-width 64 --width 500 --posY top )

    ##  Get the button pressed and the options checked
    Button=$( echo "$SWUDiag" | awk 'NR==1{print $0}' )
    Checks=($( echo "$SWUDiag" | awk 'NR==2{print $0}' ))
    ##  Set up a non array string from the checkboxes returned
    ChecksNonArray=$( echo "$SWUDiag" | awk 'NR==2{print $0}' )

    ##  If the "Install" button was clicked
    if [[ "$Button" == "1" ]]; then
        echo "User clicked the "Install" button."
        ##  Check to see if at least one box was checked
        if [[ $( echo "${ChecksNonArray}" | grep "1" ) == "" ]]; then
            echo "No selections made. Alerting user and returning to selection screen."
            "$cdPath" msgbox --title "$orgName Software Update" --text "No selections were made" 
            --informative-text "$(echo -e "You didn't select any updates to install.

If you want to cancel out of this application, click the "Later" button in the window instead, or press the Esc key.

The Software Update window will appear again momentarily.")" 
            --button1 "    OK    " --timeout 10 --timeout-format " " --width 500 --posY top --icon caution
            ##  Because we are restarting the function, first empty all previously built arrays
            ##  Credit to Cem Baykara (@Cem - JAMFNation) for discovering this issue during testing
            SWUList=()
            SWUProg=()
            SWUItems=()
            ##  Now restart this function after the alert message times out
            startDialog
        else
            ##  "Install" button was clicked and items checked. Run the assess checkbox function
            echo "Selections were made. Moving to assessment function..."
            assessChecks
        fi
    elif [[ "$Button" == "3" ]]; then
        ##  "Install No Reboot Updates" button was clicked. Set the installNoReboots flag to "yes" and skip to check assessment
        echo "User clicked the "Install No Reboot Updates" button."
        installNoReboots="yes"
        assessChecks
    elif [[ "$Button" == "2" ]]; then
        echo "User chose to Defer. Remaining deferral count reduced by 1"
        newDeferralCount=$((deferralsLeft-1))
        echo "New deferral remaining count will be ${newDeferralCount}"
        echo "$newDeferralCount" > "${dataFolder}/deferralCount"
        echo "Cleaning up SWU list file. Exiting..."
        rm /tmp/SWULIST
        exit 0
    elif [[ -z "$Button" ]]; then
        echo "User force canceled from dialog. Remaining deferral count reduced by 1"
        newDeferralCount=$((deferralsLeft-1))
        echo "New deferral remaining count will be ${newDeferralCount}"
        echo "$newDeferralCount" > "${dataFolder}/deferralCount"
        echo "Cleaning up SWU list file. Exiting..."
        rm /tmp/SWULIST
        exit 0
    fi

else
    ##  No non-reboot updates were available. Display a different dialog to the user
    echo "No non-reboot updates found, but other updates available. Showing selection dialog to user"
    SWUDiag=$( "$cdPath" checkbox --title "$orgName Software Update" --items "${SWUList[@]}" --checked "${checksOnArr[@]}" --disabled "${checksOnArr[@]}" 
    --label "$swuTextNoReboots" --button1 " Install " --button2 "$button2Label" --cancel "button2" 
    --icon-file "$msgIcon" --icon-height 64 --icon-width 64 --width 500 --posY top --value-required 
    --empty-text "$(echo -e "You must check at least one item before clicking "Install".

If you want to defer, click "Later" or press the esc key.")" )

    ##  Get the button pressed and the options checked
    Button=$( echo "$SWUDiag" | awk 'NR==1{print $0}' )
    Checks=($( echo "$SWUDiag" | awk 'NR==2{print $0}' ))

    if [[ "$Button" == "1" ]]; then
        ##  "Install" button was clicked. Run the assess checkbox function
        echo "User clicked the "Install" button"
        assessChecks
    elif [[ "$Button" == "2" ]]; then
        echo "User chose to Defer. Remaining deferral count reduced by 1"
        newDeferralCount=$((deferralsLeft-1))
        echo "New deferral remaining count will be ${newDeferralCount}"
        echo "$newDeferralCount" > "${dataFolder}/deferralCount"
        echo "Cleaning up SWU list file. Exiting..."
        rm /tmp/SWULIST
        exit 0
    ##  If the user quit the dialog, a deferral gets used up
    elif [[ -z "$Button" ]]; then
        echo "User force canceled from dialog. Remaining deferral count reduced by 1"
        newDeferralCount=$((deferralsLeft-1))
        echo "New deferral remaining count will be ${newDeferralCount}"
        echo "$newDeferralCount" > "${dataFolder}/deferralCount"
        echo "Cleaning up SWU list file. Exiting..."
        rm /tmp/SWULIST
        exit 0
    fi
fi

}



##  Function to lock the login window and install all available updates
startLockScreenAgent ()
{

##  Note on this function: To make the script usable outside of a Casper Suite environment,
##  we are using the Apple Remote Management LockScreen.app, located inside the AppleVNCServer bundle.
##  This bundle and corresponding app is installed by default in all recent versions of OS X

##  Set a flag to yes if any updates in the list will require a reboot
while read line; do
    if [[ $(echo "$line" | grep "^◀") != "" ]]; then
        rebootsPresent="yes"
        break
    fi
done < <(echo "$readSWUs")

##  Define the name and path to the LaunchAgent plist
PLIST="/Library/LaunchAgents/com.LockLoginScreen.plist"

##  Define the text for the xml plist file
LAgentCore="<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>com.LockLoginScreen</string>
    <key>RunAtLoad</key>
    <true/>
    <key>LimitLoadToSessionType</key>
    <string>LoginWindow</string>
    <key>ProgramArguments</key>
    <array>
        <string>/System/Library/CoreServices/RemoteManagement/AppleVNCServer.bundle/Contents/Support/LockScreen.app/Contents/MacOS/LockScreen</string>
        <string>-session</string>
        <string>256</string>
        <string>-msg</string>
        <string>Updates are currently being installed on this Mac. It will automatically be restarted or returned to the login window when installations are complete.</string>
    </array>
</dict>
</plist>"

##  Create the LaunchAgent file
echo "Creating the LockLoginScreen LaunchAgent..."
echo "$LAgentCore" > "$PLIST"

##  Set the owner, group and permissions on the LaunchAgent plist
echo "Setting proper ownership and permissions on the LaunchAgent..."
chown root:wheel "$PLIST"
chmod 644 "$PLIST"

##  Use SIPS to copy and convert the SWU icon to use as the LockScreen icon

##  First, back up the original Lock.jpg image
echo "Backing up Lock.jpg image..."
mv /System/Library/CoreServices/RemoteManagement/AppleVNCServer.bundle/Contents/Support/LockScreen.app/Contents/Resources/Lock.jpg 
/System/Library/CoreServices/RemoteManagement/AppleVNCServer.bundle/Contents/Support/LockScreen.app/Contents/Resources/Lock.jpg.bak

##  Now, copy and convert the SWU icns file into a new Lock.jpg file
##  Note: We are converting it to a png to preserve transparency, but saving it with the .jpg extension so LockScreen.app will recognize it.
##  Also resize the image to 400 x 400 pixels so its not so honkin' huge!
echo "Creating SoftwareUpdate icon as png and converting to Lock.jpg..."
sips -s format png "$swuIcon" --out /System/Library/CoreServices/RemoteManagement/AppleVNCServer.bundle/Contents/Support/LockScreen.app/Contents/Resources/Lock.jpg 
--resampleWidth 400 --resampleHeight 400

##  Now, kill/restart the loginwindow process to load the LaunchAgent
echo "Ready to lock screen. Restarting loginwindow process..."
kill -9 $(ps axc | awk '/loginwindow/{print $1}')

##  Install all available Software Updates
echo "Screen locked. Installing all available Software Updates..."
/usr/sbin/softwareupdate --install --all

if [ "$?" == "0" ]; then
    ## Delete LaunchAgent and reload the Login Window
    echo "Deleting the LaunchAgent..."
    rm "$PLIST"
    sleep 1

    if [[ "$rebootsPresent" == "yes" ]]; then
        ##  Put the original Lock.jpg image back where it was, overwriting the SWU Icon image
        echo "The rebootsPresent flag was set to 'yes' Replacing Lock.jpg image and immediately rebooting the Mac..."
        mv /System/Library/CoreServices/RemoteManagement/AppleVNCServer.bundle/Contents/Support/LockScreen.app/Contents/Resources/Lock.jpg.bak 
        /System/Library/CoreServices/RemoteManagement/AppleVNCServer.bundle/Contents/Support/LockScreen.app/Contents/Resources/Lock.jpg

        ##  Kill the LockScreen app and restart immediately
        killall LockScreen
        /sbin/shutdown -r now
    else
        ##  Put the original Lock.jpg image back where it was, overwriting the SWU Icon image
        echo "The rebootsPresent flag was not set. Replacing Lock.jpg image and restoring the loginwindow..."
        mv /System/Library/CoreServices/RemoteManagement/AppleVNCServer.bundle/Contents/Support/LockScreen.app/Contents/Resources/Lock.jpg.bak 
        /System/Library/CoreServices/RemoteManagement/AppleVNCServer.bundle/Contents/Support/LockScreen.app/Contents/Resources/Lock.jpg

        ##  Kill/restart the login window process to return to the login window
        kill -9 $(ps axc | awk '/loginwindow/{print $1}')
    fi

else

    echo "There was an error with the installations. Removing the Agent and unlocking the login window..."

    rm "$PLIST"
    sleep 1

    mv /System/Library/CoreServices/RemoteManagement/AppleVNCServer.bundle/Contents/Support/LockScreen.app/Contents/Resources/Lock.jpg.bak 
    /System/Library/CoreServices/RemoteManagement/AppleVNCServer.bundle/Contents/Support/LockScreen.app/Contents/Resources/Lock.jpg

    ##  Kill/restart the login window process to return to the login window
    kill -9 $(ps axc | awk '/loginwindow/{print $1}')
    exit 0
fi

}

##  The script starts here

##  Gather available Software Updates and export to a file
echo "Pulling available Software Updates..."
/usr/sbin/softwareupdate -l > /tmp/SWULIST
echo "Finished pulling available Software Updates into local file"

echo "Checking to see what updates are available..."
##  Generate list of readable items and installable items from file
readSWUs=$( cat /tmp/SWULIST | awk -F"," '/recommended/{print $2,$1}' | sed -e 's/[0-9]*K [recommended][ *]//g;s/[restart] */◀ /g' | sed 's/[    ]//g' )
progSWUs=$( cat /tmp/SWULIST | awk -F"," '/recommended/{print $2,$1}' | sed -e 's/[0-9]*K [recommended][ *]//g;s/[restart] *//g' | sed 's/[  ]//g' )
installSWUs=$( cat /tmp/SWULIST | grep -v 'recommended' | awk -F'\* ' '/*/{print $NF}' )

##  First, make sure there's at least one update from Software Update
if [[ -z "$readSWUs" ]]; then
    echo "No pending Software Updates found for this Mac. Exiting..."
    exit 0
elif [[ ! -z "$readSWUs" ]] && [[ "$loggedInUser" != "root" ]]; then
    echo "Software Updates are available, and a user is logged in. Moving to initial dialog..."
    startDialog
elif [[ ! -z "$readSWUs" ]] && [[ "$loggedInUser" == "root" ]]; then
    if [ "$installAllAtLogin" == "yes" ]; then
        echo "SWUs are available, no-one logged in and the installAllAtLogin flag was set. Locking screen and installing all updates..."
        startLockScreenAgent
    else
        echo "SWUs are available, no-one logged in but the installAllAtLogin flag was not set. Exiting..."
        exit 0
    fi
fi

perrycj
Contributor III

@mm2270 Thanks for that. I'll play around with it today and see how it goes.

dgreening
Valued Contributor II

Hey Mike,

I did some testing on the latest and though my CoDi is the the location you reference, I don't see any dialog:

bash-3.2# /Users/p_dgreen/Desktop/SUS_mm2270.sh You have 5 deferrals remaining
Current user is: p_dgreen
Pulling available Software Updates...
Finished pulling available Software Updates into local file
Checking to see what updates are available...
Software Updates are available, and a user is logged in. Moving to initial dialog...
There are some non reboot updates available. Showing selection screen to user

mm2270
Legendary Contributor III

Hey @dgreening A few questions for you.

Only so I can duplicates the steps you took, it looks like you su'd to root and then ran the script. Is that correct? Not that there's any issue with doing it that way. I just want to be sure I do the same thing in any testing.

Second, are you using the cocoaDialog 3.0 beta 7 release? Or any 3.0 beta version? if you're using the 2.0 version it won't work since the script uses the checkbox window style which only exists in the 3.0 betas.

Let me know on those when you can. I did local script tests on my Mac, not from a Casper policy and the dialog showed up every time.

FritzsCorner
Contributor III

@mm2270 Much Cleaner! I like it a lot!

I took a look through the script and noticed on line 399 SoftwareUpdate didn't include the full path to the binary. In my testing, when calling the SoftwareUpdate binary within a script without the full path, it will almost always return 0 results. All the other call's to SoftwareUpdate in your script have the full path.. just looks like this one was missed.

Line 399 currently looks like

softwareupdate --verbose -i "${SWUItems[@]}" 2>&1 | while read line; do

but it should be changed to look like

/usr/sbin/softwareupdate --verbose -i "${SWUItems[@]}" 2>&1 | while read line; do

Outside of that, if the script is run multiple times in a short time span it will sometimes report back that no Software Updates are found. Waiting a few minutes and running again will typically work and it will find the updates. I don't think this would be an issue in regular use, as there would be no reason to run it several times in such a short time span.

EDIT: I should mention that my clients are pointed to a NetSUS server to get the available updates list, and then download them directly from Apple. I haven't tested without the NetSUS so not sure if those who go directly to Apple for updates would experience this behavior or not.

I am also going to add an 8th parameter that will allow us to define the deferral threshold via the policy script options.

I'm really liking this script.. I appreciate all the effort you put into it!

perrycj
Contributor III

Hey @mm2270 so did some testing this morning on a couple Macs and it seems it is still going to negative deferrals.

However, it is updating a cmd+q as a deferral, so that's good. And once it is at 0, it does grey everything out and only have the install button as available. Also, the dialog is updating to say you have 0 deferrals left. However, as you can see by the below casper remote log, it is just continuing to count down:

User force canceled from dialog. Remaining deferral count reduced by 1
New deferral remaining count will be -1
Cleaning up SWU list file. Exiting...
Submitting log to https://abcde.com/
Finished.

And then no updates proceed to run, even if I restart or log out after.

I took a look and made a change here:

About line 79

if [ "$deferralsLeft" -eq "0" ]; then
echo "0 deferrals remaining. Updates will be installed now"
forceEnableUpdates="Yes"
else
echo "You have $deferralsLeft deferrals remaining"
forceEnableUpdates=""
fi

About line 534

if [ "$deferralsLeft" -eq "0" ]; then
forceEnableUpdates="Yes"
fi

which put 0 in "", which should more clearly define it. However, it still isn't forcing the Mac to do the updates if the user quits out with 0 deferrals left.

There is a lot of code, so I probably missed it, but I didn't see anywhere that actually was forcing updates if it quit with 0 deferrals left. Meaning.. no function or anything to run the actual updates in that scenario.

Besides the above mentioned, every other aspect of the script is working fine for me.

mm2270
Legendary Contributor III

Thanks for all the feedback everyone. Working on a few adjustments and will do some more actual testing with it to see if I can make it enforce the updates. I thought I had that in there to do that, but let me look more closely at it. I must have miscoded something.

mm2270
Legendary Contributor III

Ok, I see where I missed the function to do the updates. I forgot to call the assessChecks function when in that force quit block at the final run. I'm updating it now. See? I did say I didn't get to do much testing with it so thanks for helping find that issue!

@FritzsCorner I haven't seen issues with not calling the full path to the softwareupdate binary, but I will include that. Usually as long as there isn't anything strange about your $PATH variables, it should resolve softwareupdate to the full /usr/bin/softwareupdate Still, good practice to include the full path for something like that, so I'm making that adjustment.

FritzsCorner
Contributor III

@mm2270 Cool.. I am thinking my issue with the script randomly not finding any available updates might have something to do with our NetSUS more than anything. I am going to continue going down that path of troubleshooting. Thank's again!

FritzsCorner
Contributor III

The only other comment I think I have (for now) is that the deferral counter is reset after any of the updates are installed. So if I have a list of like 5 updates and only choose to install 1 or 2.. it will still reset the deferral counter to 5. I have some ideas on how to handle this, and I think your updated version of the script might make it easier to accomplish. I will post back once I have had some time to test thing's out.

mm2270
Legendary Contributor III

Yes, it does reset the counter as you described. I did that to err on the side of the user, since it seemed unfair to me if a user chose to install 4 of 5 updates that they would still get an enforcement perhaps on the next run of the script. But, in doing so I realize that may not be good for environments where you are tasked with making sure your Macs fit into a compliance bucket by a certain time each month, or whatever.

Let me know your ideas on that when you can. I have an idea also which is simply that an extra flag can be included in the script which states not to reset the counter until the number of updates installed equals or exceeds the number of available updates it originally found.
One concern in thinking about this is that there may be cases where, on first run it locates 4 updates available, and continues to prompt the user for these on each run. But by the 4th or 5th run, what if an extra one or two updates are now available? At that point the total goes up (to 5 or 6) and doesn't equal what it originally found, so it could be complicated to ensure that all updates are installed. I guess I'm saying, what constitutes "eventually installing all updates" since that number can vary potentially each time the script runs? Its easy to determine when the user installs all available updates, but could get complicated when the do onesy twosey's.
Does that make any sense? I'm sure I can figure out some way to make it work, but I'll need to give it some more thought.

perrycj
Contributor III

@mm2270 No worries! It is a massive (and impressive) script. I checked 3 or 4 times myself before I noticed it wasn't there.

Adding on to your last post... that's a good point. If a Mac doesn't do updates for a while (months, etc), it could potentially have 6, 7 or 8 updates. And then it might be a point where they do one set, and they come back and 3 more new ones are there. However, I will say Apple has gotten a lot better in combining or clunking updates together so someone who hasn't ran software updates in a while, doesn't get bombarded.

Potentially you could just have 2 versions of the script in your environment. One policy runs a version with lets say, 3 deferrals and another runs one with 0 deferrals. Maybe something along the lines that if the 3 deferrals get to zero, sets a flag or touches a file on the Mac, and then it calls the version of the script with 0 deferrals enabled with a custom trigger or the like. This way, once the 0 deferrals version is called, they have to install updates no matter what. Kind of going into left field with that one, but just a thought.

jhalvorson
Valued Contributor

Cheers! You probably can't hear me, but I am cheering all of you on for this effort. I'll test when I can. Please keep up the good work to all that are contributing to this. (A follower of this thread since 2012.)

FritzsCorner
Contributor III

@mm2270 My thought is that even if there are more software updates available from what was found in the first check, they would still have to install the original software updates anyways so why not tack on the new updates and keep the current deferral status. Might not be ideal for all environments, but in my shop we are under tight timelines to get everyone updated.

My original thought would be that the deferral counter would be reset when 0 updates were available for install. In my environment that might cause issues right now as I am randomly getting no updates found when I know there are updates available, but I am assuming that isn't the case for the others testing in this thread.

One of the other options I was thinking of would be to do a hybrid of allowing the user which updates to install and enforcing all updates. What I mean by this is that you would only let the user manually select between updates that don't require a reboot and updates that do require a reboot. So if there are 5 updates and 3 of them don't require a reboot and the other 2 do.. they would only have the options to install all patches that don't require a reboot or install all available updates including the reboot required, and not have the option to individually check specific updates. With this method, the deferral counter would continue to increment each time they either chose the deferral or chose the install all the no reboot updates. The only way to reset the counter would be if they selected install all patches including reboots option. In the case where all the available updates don't require a reboot and they chose to install all, it would reset the counter as well.

There are a million different ways to approach this... and I know these idea's might not be totally fleshed out, but just brainstorming here. :)

mm2270
Legendary Contributor III

Ok, I think I've got this right now. Turned out the array for the updates to install wasn't being built when in the state where the updates were being forced and when you simply quit the dialog. I was finally able to get that to work. Just ran thru it on a Mac that needed 3 updates. I tried force quitting out of the final enforcement dialog and it proceeded to install them all anyway.

I still need to do some cleanup with it since I'd added some debugging echo lines in there to see what was happening. Once I have it ready I'll post it up again so you can folks can run thru another test with it.

mm2270
Legendary Contributor III

Ok, finally getting around to posting the updated script here. From my tests, limited as they were, it looks to be all working as expected, but I'll need to lean on some of you to run it through its paces to make sure there isn't anything I'm missing.

I fixed up a few things, like mentioned above, and also fixed something I was seeing where the spacing between the "◀" symbol and the name of the update seemed like a full tab stop. It should now show up as a single space instead.
I also added an extra cocoaDialog sanity check to make sure the version the script is being pointed to is at least a 3.0 beta, since the older 2.0 release simply won't work with this script due to the checkbox window style. Hopefully that will help catch instances where an older version is used and the windows don't appear.

At some point, I'd like to really sit down with this and see how I can simplify it a bit more since its becoming a bit sprawling now. That's natural since I keep adding things to it, but I'm sure I can find some consolidation points in it to make it shorter and more concise.

Please let me know how its working for everyone once you get to do some testing. I'll be doing my own testing more tomorrow to make sure there aren't any other big issues.

#!/bin/bash

##  Script Name:        Selectable_SoftwareUpdate.sh (v006)
##  Script Author:      Mike Morales, @mm2270 on JAMFNation
##  Last Update:        2016-09-15

##  Last Modification Notes:
##  - Finished incorporation of deferral timer
##  - Fixed issue of update not being installed during enforced install mode
##  - Fixed issue of deferral counter not being reduced when user 'quits' cocoaDialog
##  - Added extra cocoaDialog sanity check to make sure the version installed is a 3.0 beta

## Credits & Thanks:
## Credit to Aaron Stovall (original incorporation of deferral functionality)
## Thanks for all those on JAMFNation who continue to show interest in the script,
## done testing with it, and have managed to get me to keep working on it!

## Future modifications?
##  - Force enabling only non-reboot updates and leaving reboot updates optional
##  - Additional 'mode' to show updates strictly as text strings, not checkboxes

##  Path to cocoaDialog (customize to your own location)
cdPath="/Library/Application Support/JAMF/bin/cocoaDialog.app/Contents/MacOS/cocoaDialog"


## Defer Variables

## Default starting number of deferrals
defaultDeferralCount="5"

## Assign the deferral folder location
dataFolder="/Library/Application Support/SWUPatchDeferral"

## Create the data folder if it doesn't already exist
if [ ! -d "$dataFolder" ]; then
    echo "Initial PatchDeferral folder isn't present. Creating it..."
    mkdir "$dataFolder"
fi

## Create the deferral counter file with a default value...
if [ ! -e "${dataFolder}/deferralCount" ]; then
    echo "Initial deferralCount file isn't present. Creating it with default value of ${defaultDeferralCount}..."
    echo "$defaultDeferralCount" > "${dataFolder}/deferralCount"
    deferralsLeft="$defaultDeferralCount"
else
    ## or pick up the remaining deferrals left in the file
    deferralsLeft=$(cat "${dataFolder}/deferralCount")
    if [ "$deferralsLeft" == "" ]; then
        deferralsLeft="$defaultDeferralCount"
    fi
fi

## Set deferMode to true or yes to enable deferral modality in the script
deferMode=true

##  Quick sanity check to make sure cocoaDialog is installed in the path specified
if [ ! -e "$cdPath" ]; then
    echo "cocoaDialog was not found in the path specified. It may not be installed, or the path is wrong. Exiting..."
    exit 1
    ## If its installed, let's also check to see that its the 3.0 beta version
elif [ -e "$cdPath" ]; then
    coreCDPath=$(echo "$cdPath" | sed 's|/MacOS/.*||')
    cdVers=$(/usr/bin/defaults read "${coreCDPath}/Info.plist" CFBundleShortVersionString)
    if [[ ! "$cdVers" =~ "3.0" ]]; then
        echo "Error: Installed version of cocoaDialog is not a 3.0 beta version. A 3.0 beta version is required for this script to function."
        exit 1
    fi
fi

##  Set the installAllAtLogin flag here to 'yes' or leave it blank (equivalent to 'no')
##  Function: When the script is run on a Mac that is at the login window, if the flag is set to 'yes',
##  it will lock the login window to prevent unintended logins and proceed to install all available updates.
##  Once completed, the login window will either be unlocked in the case of no restarts needed,
##  or a restart will be done immediately to complete the installations.

installAllAtLogin="yes"

##  Set the forceEnableUpdates flag below to 'yes' (or any value) to enable it. This option will change the
##  checkbox display to show updates as enabled and non-selectable to the user, meaning they will be forced to
##  an "on" state for all. Leaving this value blank will retain the original functionality, allowing the end
##  user to select the updates they would like to install.

if [[ "$deferralsLeft" -eq 0 ]]; then
    echo "0 deferrals remaining. Updates will be installed now"
    forceEnableUpdates="Yes"
else
    echo "You have $deferralsLeft deferrals remaining"
    forceEnableUpdates=""
fi


##  Get minor version of OS X
osVers=$( sw_vers -productVersion | cut -d. -f2 )

##  Set appropriate Software Update icon depending on OS version
if [[ "$osVers" -lt 8 ]]; then
    swuIcon="/System/Library/CoreServices/Software Update.app/Contents/Resources/Software Update.icns"
else
    swuIcon="/System/Library/CoreServices/Software Update.app/Contents/Resources/SoftwareUpdate.icns"
fi

##  Set appropriate Restart icon depending on OS version
if [[ "$osVers" == "9" ]]; then
    restartIcon="/System/Library/CoreServices/loginwindow.app/Contents/Resources/Restart.tiff"
else
    restartIcon="/System/Library/CoreServices/loginwindow.app/Contents/Resources/Restart.png"
fi

##  Start - Check Casper Suite script parameters and assign any that were passed to the script

##  PARAMETER 4: Set the Organization/Department/Division name. Used in dialog titles
##  Default string of "Managed" is used if no script parameter is passed
if [[ "$4" != "" ]]; then
    orgName="$4"
else
    orgName="Managed"
fi

##  PARAMETER 5: Set to "no" (case insensitive) to show a single progress bar update for all installations.
##  Default value of "yes" will be used if no script parameter is passed
if [[ "$5" != "" ]]; then
    shopt -s nocasematch
    if [[ "$5" == "no" ]]; then
        showProgEachUpdate="no"
    else
        showProgEachUpdate="yes"
    fi
    shopt -u nocasematch
else
    showProgEachUpdate="yes"
fi

##  PARAMETER 6: Set the number of minutes until reboot (only used if installations require it)
##  Default value of 5 minutes is assigned if no script parameter is passed
##  Special note: Only full integers can be used. No decimals.
##  If the script detects a non whole integer, it will fall back on the default 5 minute setting.
if [[ "$6" != "" ]]; then
    ## Run test to make sure we have a non floating point integer
    if [[ $(expr "$6" / "$6") == "1" ]]; then
        minToRestart="$6"
    else
        echo "Non integer, or a decimal value was passed. Setting reboot time to default (5 minutes)"
        minToRestart="5"
    fi
else
    minToRestart="5"
fi

##  Parameter 7: Set to the full path of an icon or image file for any dialogs that are not using the
##  Apple Software Update icon. This could be a company logo icon for example
##  Default icon is set in the following manner:
##      If no script parameter is passed, or the icon/image can not be found and JAMF Self Service is present on the Mac, its icon will be used
##      If Self Service is not found, the Software Update icon will be used
if [[ "$7" != "" ]]; then
    if [[ -e "$7" ]]; then
        echo "A custom dialog icon was set: $7"
        msgIcon="$7"
    else
        if [[ -e "/Applications/Self Service.app/Contents/Resources/Self Service.icns" ]]; then
            ##  Self Service present. Use a default Self Service icon if the file specified could not be found
            msgIcon="/Applications/Self Service.app/Contents/Resources/Self Service.icns"
        else
            ##  Icon file not found, and Self Service not present. Set icon to Software Update
            msgIcon="$swuIcon"
        fi
    fi
else
    if [[ -e "/Applications/Self Service.app/Contents/Resources/Self Service.icns" ]]; then
        ##  Self Service present. Use a default Self Service icon if no parameter was passed
        msgIcon="/Applications/Self Service.app/Contents/Resources/Self Service.icns"
    else
        ##  No parameter passed, and Self Service not present. Set icon to Software Update
        msgIcon="$swuIcon"
    fi
fi

##  End - Check Casper Suite script parameters


##  Text displayed in dialog prompting for selections. Customize if desired.
##  Two versions:
##      One,for when reboot *required* updates are found.
##      Two,for when only non-reboot updates are found.
if [[ ! -z "$forceEnableUpdates" ]]; then
    swuTextReboots="The following Apple Software Updates will be installed on your Mac when you click "Install".  No Deferrals are available.

◀  =  Indicates updates that will REQUIRE a reboot of your Mac to complete.

"

else
    swuTextReboots="Select the Apple Software Update items you would like to install now from the list below. You can choose to defer the updates up to 5 times. After 5 deferrals, the updates will be installed automatically.

◀  =  Indicates updates that will REQUIRE a reboot of your Mac to complete.

To install all updates that will not require a reboot, click "Install No Reboot Updates"

"

fi

if [[ ! -z "$forceEnableUpdates" ]]; then
    swuTextNoReboots="The following Apple Software Updates will be installed on your Mac when you click "Install". No Deferrals are available.

"

else
    swuTextNoReboots="Select the Apple Software Update items you would like to install now from the list below. You can choose to defer the updates up to 5 times. After 5 deferrals, the updates will be installed automatically.

"

fi

################################################## ENV VARIABLES #####################################################
##                                                                                                                  ##
##  These variables are gathered to set up the visual environment of the messaging to match the logged in user's   ##
##  settings. We gather the settings, then change the root account's settings to match.                                ##
##                                                                                                                  ##
######################################################################################################################

## Get current logged in user name
loggedInUser=$( ls -l /dev/console | /usr/bin/awk '{ print $3 }' )
echo "Current user is: $loggedInUser"

##  Determine logged in user's home directory path
HomeDir=$( dscl . read /Users/$loggedInUser NFSHomeDirectory | awk '{ print $NF }' )

##  Get logged in user's Appearance color settings
AquaColor=$( defaults read "$HomeDir/Library/Preferences/.GlobalPreferences" AppleAquaColorVariant 2> /dev/null )

##  If user has not changed their settings, value will be null. Set to default 'Aqua' color
if [[ -z "$AquaColor" ]]; then
    AquaColor="1"
else
    AquaColor="$AquaColor"
fi

##  Get logged in user's Keyboard access settings
KeybdMode=$( defaults read "$HomeDir/Library/Preferences/.GlobalPreferences" AppleKeyboardUIMode 2> /dev/null )

##  If user has not changed their settings, value will be null. Set to default 'Text boxes and lists only'
if [[ -z "$KeybdMode" ]]; then
    KeybdMode="0"
else
    KeybdMode="$KeybdMode"
fi

##  Set the root account environment settings to match current logged in user's
defaults write /private/var/root/Library/Preferences/.GlobalPreferences AppleAquaColorVariant -int "${AquaColor}"
defaults write /private/var/root/Library/Preferences/.GlobalPreferences AppleKeyboardUIMode -int "${KeybdMode}"

##  Restart cfprefsd so new settings will be recognized
killall cfprefsd

################################# Do not modify below this line ########################################

##  Function to run when installations are complete
doneRestart ()
{

doneMSG="The installations have completed, but your Mac needs to reboot to finalize the updates.

Your Mac will automatically reboot in $minToRestart minutes. Begin to save any open work and close applications now.

If you want to restart immediately instead, click the "Restart Now" button."

##  Display initial message for 30 seconds before starting the progress bar countdown
doneRestartMsg=$( "$cdPath" msgbox --title "$orgName Software Update > Updates Complete" 
--text "Updates installed successfully" --informative-text "$doneMSG" 
--button1 "     OK     " --button2 "Restart Now" --icon-file "$msgIcon" --posY top --width 450 --timeout 30 --timeout-format " " )

    if [ "$doneRestartMsg" == "1" ]; then
        echo "User pressed OK. Moving on to reboot timer..."
    elif [ "$doneRestartMsg" == "2" ]; then
        echo "User pressed Reboot Now. Rebooting immediately..."
        /sbin/shutdown -r now
    else
        echo "The message timed out. Moving on to reboot timer..."
    fi

    ##  Sub-function to (re)display the progressbar window. Developed to work around the fact that
    ##  CD responds to Cmd+Q and will quit. The script continues the countdown. The sub-function
    ##  causes the progress bar to reappear. When the countdown is done we quit all CD windows
    showProgress ()
    {

    ##  Display progress bar
    "$cdPath" progressbar --title "" --text " Preparing to restart this Mac..." 
    --width 500 --height 90 --icon-file "$restartIcon" --icon-height 48 --icon-width 48 < /tmp/hpipe &

    ##  Send progress through the named pipe
    exec 20<> /tmp/hpipe

    }

##  Close file descriptor 20 if in use, and remove any instance of /tmp/hpipe
exec 20>&-
rm -f /tmp/hpipe

##  Create the name pipe input for the progressbar
mkfifo /tmp/hpipe
sleep 0.2

## Run progress bar sub-function
showProgress

echo "100" >&20

timerSeconds=$((minToRestart*60))
startTime=$( date +"%s" )
stopTime=$((startTime+timerSeconds))
secsLeft=$timerSeconds
progLeft="100"

while [[ "$secsLeft" -gt 0 ]]; do
    sleep 1
    currTime=$( date +"%s" )
    progLeft=$((secsLeft*100/timerSeconds))
    secsLeft=$((stopTime-currTime))
    minRem=$((secsLeft/60))
    secRem=$((secsLeft%60))
    if [[ $(ps axc | grep "cocoaDialog") == "" ]]; then
        showProgress
    fi
    echo "$progLeft $minRem minutes, $secRem seconds until reboot. Please save any work now." >&20
done

echo "Closing progress bar."
exec 20>&-
rm -f /tmp/hpipe

##  Close cocoaDialog. This block is necessary for when multiple runs of the sub-function were called in the script
for process in $(ps axc | awk '/cocoaDialog/{print $1}'); do
    /usr/bin/osascript -e 'tell application "cocoaDialog" to quit'
done

##  Clean up by deleting the SWUList file in /tmp/
rm /tmp/SWULIST

##  Delay 1/2 second, then force reboot
sleep 0.5
shutdown -r now

}

##  Function to install selected updates, updating progress bar with information
installUpdates ()
{

echo "Restart required?:   ${restartReq}"

if [[ "${restartReq}" == "yes" ]]; then
    installMSG="Installations are now running. Please do not shut down your Mac or put it to sleep until the installs finish.

IMPORTANT:
Because you chose some updates that require a restart, we recommend saving any important documents now. Your Mac will reboot soon after the installations are complete."

elif [[ "${restartReq}" == "no" ]] || [[ "${restartReq}" == "" ]]; then
    installMSG="Updates are now installing. Please do not shut down your Mac or put it to sleep until the installs finish."
fi

    ##  Sub-function to display both a button-less CD window and a progress bar
    ##  This sub routine gets called by the enclosing function. It can also be called by
    ##  the install process if it does not see 2 instances of CD running
    showInstallProgress ()
    {

    ##  Display button-less window above progress bar, push to background
    "$cdPath" msgbox --title "$orgName Software Update > Installation" --text "Installations in progress" 
    --informative-text "${installMSG}" --icon-file "${msgIcon}" --width 450 --height 184 --posY top &

    ##  Display progress bar
    echo "Displaying progress bar window."
    "$cdPath" progressbar --title "" --text " Preparing to install selected updates..." 
    --posX "center" --posY 198 --width 450 --float --icon installer < /tmp/hpipe &

    ##  Send progress through the named pipe
    exec 10<> /tmp/hpipe

    }

##  Close file descriptor 10 if in use, and remove any instance of /tmp/hpipe
exec 10>&-
rm -f /tmp/hpipe

##  Create the name pipe input for the progressbar
mkfifo /tmp/hpipe
sleep 0.2

## Run the install progress sub-function (shows button-less CD window and progressbar
showInstallProgress

if [[ "$showProgEachUpdate" == "yes" ]]; then
    echo "Showing individual update progress."
    ##  Run softwareupdate in verbose mode for each selected update, parsing output to feed the progressbar
    ##  Set initial index loop value to 0; set initial update count value to 1; set variable for total updates count
    i=0;
    pkgCnt=1
    pkgTotal="${#selectedItems[@]}"
    for index in "${selectedItems[@]}"; do
        UpdateName="${progSelectedItems[$i]}"
        echo "Now installing ${UpdateName}..."
        /usr/sbin/softwareupdate --verbose -i "${index}" 2>&1 | while read line; do
            ##  Re-run the sub-function to display the cocoaDialog window and progress
            ##  if we are not seeing 2 items for CD in the process list
            if [[ $(ps axc | grep "cocoaDialog" | wc -l | sed 's/^ *//') != "2" ]]; then
                killall cocoaDialog
                showInstallProgress
            fi
            pct=$( echo "$line" | awk '/Progress:/{print $NF}' | cut -d% -f1 )
            echo "$pct Installing ${pkgCnt} of ${pkgTotal}: ${UpdateName}..." >&10
        done
        let i+=1
        let pkgCnt+=1
    done
else
    ## Show a generic progress bar that progresses through all installs at once from 0-100 %
    echo "Parameter 5 was set to "no". Showing single progress bar for all updates"
    /usr/sbin/softwareupdate --verbose -i "${SWUItems[@]}" 2>&1 | while read line; do
        ##  if we are not seeing 2 items for CD in the process list
        if [[ $(ps axc | grep "cocoaDialog" | wc -l | sed 's/^ *//') != "2" ]]; then
            killall cocoaDialog
            showInstallProgress
        fi
        pct=$( echo "$line" | awk '/Progress:/{print $NF}' | cut -d% -f1 )
        echo "$pct Installing ${#SWUItems[@]} updates..." >&10
    done
fi

echo "Closing progress bar."
exec 10>&-
rm -f /tmp/hpipe

##  Close all instances of cocoaDialog
echo "Closing all cocoaDialog windows."
for process in $(ps axc | awk '/cocoaDialog/{print $1}'); do
    /usr/bin/osascript -e 'tell application "cocoaDialog" to quit'
done

##  Reset the deferral counter file back to the default value after installations complete
echo "$defaultDeferralCount" > "${dataFolder}/deferralCount"

##  If any installed updates required a reboot...
if [[ "${restartReq}" == "yes" ]]; then
    ## ...then move to the restart phase
    ## Reset deferral counter
    defaults write "$prefs" DeferCount -int 0
    doneRestart
##  If no installed updates required a reboot, display updates complete message instead
elif [[ "${restartReq}" == "no" ]]; then
    echo "Showing updates complete message."
    doneMSG="The installations have completed successfully. You can resume working on your Mac."
    "$cdPath" msgbox --title "$orgName Software Update > Updates Complete" 
    --text "Updates installed successfully" --informative-text "$doneMSG" 
    --button1 "    OK    " --posY top --width 450 --icon-file "$msgIcon"

    ## Clean up by deleting the SWUList file in /tmp/ before exiting the script
    echo "Cleaning up SWU list file."
    rm /tmp/SWULIST
    ## Reset deferral counter
    defaults write "$prefs" DeferCount -int 0
    exit 0
fi


}

##  Function to assess which items were checked, and create new arrays
##  used for installations and other functions
assessChecks ()
{

## If forceEnableUpdates is true, set the array to checksOnArr and build the update arrays
if [[ ! -z "$forceEnableUpdates" ]]; then 
    for index in "${checksOnArr[@]}"; do
        selectedItems+=( "${SWUItems[$index]}" )
        hrSelectedItems+=( "${SWUList[$index]}" )
        progSelectedItems+=( "${SWUProg[$index]}" )
    done

    echo "The following updates will be installed: ${progSelectedItems[@]}"

    ##  Determine if any of the checked items require a reboot
    restartReq="no"
    for item in "${hrSelectedItems[@]}"; do
        if [[ $(echo "${item}" | grep "^◀") != "" ]]; then
            echo "At least one selected update will require reboot. Setting the restartReq flag to "yes""
            restartReq="yes"
            break
        fi
    done

    ## Move on to the installUpdates function since we are forcing the updates at this point
    installUpdates
fi


## If forceEnableUpdates is not true, asses the checks made by the user

##  Check to see if the installNoReboots flag was set by the user
if [[ "$installNoReboots" == "yes" ]]; then
    echo "User chose to install all non reboot updates. Creating update(s) array and moving to install phase"
    ##  If flag was set, build update arrays from the noReboots array
    for index in "${noReboots[@]}"; do
        selectedItems+=( "${SWUItems[$index]}" )
        hrSelectedItems+=( "${SWUList[$index]}" )
        progSelectedItems+=( "${SWUProg[$index]}" )
    done

    ##  Automatically set the restart required flag to "no"
    restartReq="no"

    ##  Then move on to install updates function
    installUpdates
fi

##  If installNoReboots flag was not set, generate array of formatted
##  checkbox indexes for parsing based on the selections from the user
i=0;
for state in ${Checks[*]}; do
    checkboxstates=$( echo "${i}-${state}" )
    let i+=1
    ##  Set up an array we can read through later with the state of each checkbox
    checkboxfinal+=( "${checkboxstates[@]}" )
done

for check in "${checkboxfinal[@]}"; do
    if [[ "$check" =~ "-1" ]]; then
        ##  First, get the index of the checked item
        index=$( echo "$check" | cut -d- -f1 )
        ##  Second, generate 3 new arrays:
        ##  1) Short names of the updates for the installation
        ##  2) Names of updates as presented in the dialog (for checking restart status)
        ##  3) Names of the updates for updating the progress bar
        selectedItems+=( "${SWUItems[$index]}" )
        hrSelectedItems+=( "${SWUList[$index]}" )
        progSelectedItems+=( "${SWUProg[$index]}" )
    fi
done

echo "The following updates will be installed: ${progSelectedItems[@]}"

##  Determine if any of the checked items require a reboot
restartReq="no"
for item in "${hrSelectedItems[@]}"; do
    if [[ $(echo "${item}" | grep "^◀") != "" ]]; then
        echo "At least one selected update will require reboot. Setting the restartReq flag to "yes""
        restartReq="yes"
        break
    fi
done

##  If we have some selected items, move to install phase
if [[ ! -z "${selectedItems[@]}" ]]; then
    echo "Updates were selected"
    installUpdates
fi

}

##  The initial message function
startDialog ()
{

if [ "$deferralsLeft" -eq 0 ]; then
    forceEnableUpdates="Yes"
fi

##  Generate array of SWUs for dialog
z=0
while read SWU; do
    SWUList+=( "$SWU" )
    if [[ ! -z "$forceEnableUpdates" ]]; then
        checksOnArr+=("$z")
        let z=$((z+1))
    fi
done < <(echo "${readSWUs}")

##  Generate array of SWUs for progress bar
while read item; do
    SWUProg+=( "${item}" )
done < <(echo "${progSWUs}")

##  Generate array of SWUs for installation
while read swuitem; do
    SWUItems+=( "$swuitem" )
done < <(echo "${installSWUs}")


##  Generate an array of indexes for any non-reboot updates
for index in "${!SWUList[@]}"; do
    if [[ $(echo "${SWUList[$index]}" | grep "^◀") == "" ]]; then
        noReboots+=( "$index" )
    fi
done

if [[ ! -z "$forceEnableUpdates" ]]; then
    button3Label=""
    button2Label=""
else
    button3Label=" Install No Reboot Updates "
    button2Label="Later ($deferralsLeft deferrals left)"
fi

##  Show dialog with selectable options
if [[ ! -z "${noReboots[@]}" ]]; then
    echo "There are some non reboot updates available. Showing selection screen to user"
    SWUDiag=$( "$cdPath" checkbox --title "$orgName Software Update" --items "${SWUList[@]}" --checked "${checksOnArr[@]}" --disabled "${checksOnArr[@]}" 
    --label "$swuTextReboots" --button1 " Install " --button2 "$button2Label" --cancel "button2" --button3 "$button3Label" 
    --icon-file "$msgIcon" --icon-height 64 --icon-width 64 --width 500 --posY top )

    ##  Get the button pressed and the options checked
    Button=$( echo "$SWUDiag" | awk 'NR==1{print $0}' )
    Checks=($( echo "$SWUDiag" | awk 'NR==2{print $0}' ))
    ##  Set up a non array string from the checkboxes returned
    ChecksNonArray=$( echo "$SWUDiag" | awk 'NR==2{print $0}' )

    ## This block handles the case when the user force quits the install dialog
    if [[ -z "$Button" ]]; then
        ## If no deferrals are left, and...
        if [ "$deferralsLeft" == 0 ]; then
            ##  the user quit the dialog, we force all installations
            echo "User force canceled from dialog. No deferrals remain. Forcing installations"
            assessChecks
        else
            ##  If the user quit the dialog, and deferral remain, we drop the deferral count by 1
            echo "User force canceled from dialog. Remaining deferral count reduced by 1"
            newDeferralCount=$((deferralsLeft-1))
            echo "New deferral remaining count will be ${newDeferralCount}"
            echo "$newDeferralCount" > "${dataFolder}/deferralCount"
            echo "Cleaning up SWU list file. Exiting..."
            rm /tmp/SWULIST
            exit 0
        fi
    fi

    ##  If the "Install" button was clicked
    if [[ "$Button" == "1" ]]; then
        echo "User clicked the "Install" button."
        ##  Check to see if at least one box was checked
        if [[ $( echo "${ChecksNonArray}" | grep "1" ) == "" ]]; then
            echo "No selections made. Alerting user and returning to selection screen."
            "$cdPath" msgbox --title "$orgName Software Update" --text "No selections were made" 
            --informative-text "$(echo -e "You didn't select any updates to install.

If you want to cancel out of this application, click the "Later" button in the window instead, or press the Esc key.

The Software Update window will appear again momentarily.")" 
            --button1 "    OK    " --timeout 10 --timeout-format " " --width 500 --posY top --icon caution
            ##  Because we are restarting the function, first empty all previously built arrays
            ##  Credit to Cem Baykara (@Cem - JAMFNation) for discovering this issue during testing
            SWUList=()
            SWUProg=()
            SWUItems=()
            ##  Now restart this function after the alert message times out
            startDialog
        else
            ##  "Install" button was clicked and items checked. Run the assess checkbox function
            echo "Selections were made. Moving to assessment function..."
            assessChecks
        fi
    elif [[ "$Button" == "3" ]]; then
        ##  "Install No Reboot Updates" button was clicked. Set the installNoReboots flag to "yes" and skip to check assessment
        echo "User clicked the "Install No Reboot Updates" button."
        installNoReboots="yes"
        assessChecks
    elif [[ "$Button" == "2" ]]; then
        echo "User chose to Defer. Remaining deferral count reduced by 1"
        newDeferralCount=$((deferralsLeft-1))
        echo "New deferral remaining count will be ${newDeferralCount}"
        echo "$newDeferralCount" > "${dataFolder}/deferralCount"
        echo "Cleaning up SWU list file. Exiting..."
        rm /tmp/SWULIST
        exit 0
    fi
else
    ##  No non-reboot updates were available. Display a different dialog to the user
    echo "No non-reboot updates found, but other updates available. Showing selection dialog to user"
    SWUDiag=$( "$cdPath" checkbox --title "$orgName Software Update" --items "${SWUList[@]}" --checked "${checksOnArr[@]}" --disabled "${checksOnArr[@]}" 
    --label "$swuTextNoReboots" --button1 " Install " --button2 "$button2Label" --cancel "button2" 
    --icon-file "$msgIcon" --icon-height 64 --icon-width 64 --width 500 --posY top --value-required 
    --empty-text "$(echo -e "You must check at least one item before clicking "Install".

If you want to defer, click "Later" or press the esc key.")" )

    ##  Get the button pressed and the options checked
    Button=$( echo "$SWUDiag" | awk 'NR==1{print $0}' )
    Checks=($( echo "$SWUDiag" | awk 'NR==2{print $0}' ))
    ##  Set up a non array string from the checkboxes returned
    ChecksNonArray=$( echo "$SWUDiag" | awk 'NR==2{print $0}' )

    ## This block handles the case when the user force quits the install dialog
    if [[ -z "$Button" ]]; then
        ## If no deferrals are left, and...
        if [ "$deferralsLeft" == 0 ]; then
            ##  the user quit the dialog, we force all installations
            echo "User force canceled from dialog. No deferrals remain. Forcing installations"
            assessChecks
        else
            ##  If the user quit the dialog, and deferral remain, we drop the deferral count by 1
            echo "User force canceled from dialog. Remaining deferral count reduced by 1"
            newDeferralCount=$((deferralsLeft-1))
            echo "New deferral remaining count will be ${newDeferralCount}"
            echo "$newDeferralCount" > "${dataFolder}/deferralCount"
            echo "Cleaning up SWU list file. Exiting..."
            rm /tmp/SWULIST
            exit 0
        fi
    fi

    if [[ "$Button" == "1" ]]; then
        ##  "Install" button was clicked. Run the assess checkbox function
        echo "User clicked the "Install" button"
        assessChecks
    elif [[ "$Button" == "2" ]]; then
        echo "User chose to Defer. Remaining deferral count reduced by 1"
        newDeferralCount=$((deferralsLeft-1))
        echo "New deferral remaining count will be ${newDeferralCount}"
        echo "$newDeferralCount" > "${dataFolder}/deferralCount"
        echo "Cleaning up SWU list file. Exiting..."
        rm /tmp/SWULIST
        exit 0
    fi
fi

}



##  Function to lock the login window and install all available updates
startLockScreenAgent ()
{

##  Note on this function: To make the script usable outside of a Casper Suite environment,
##  we are using the Apple Remote Management LockScreen.app, located inside the AppleVNCServer bundle.
##  This bundle and corresponding app is installed by default in all recent versions of OS X

##  Set a flag to yes if any updates in the list will require a reboot
while read line; do
    if [[ $(echo "$line" | grep "^◀") != "" ]]; then
        rebootsPresent="yes"
        break
    fi
done < <(echo "$readSWUs")

##  Define the name and path to the LaunchAgent plist
PLIST="/Library/LaunchAgents/com.LockLoginScreen.plist"

##  Define the text for the xml plist file
LAgentCore="<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>com.LockLoginScreen</string>
    <key>RunAtLoad</key>
    <true/>
    <key>LimitLoadToSessionType</key>
    <string>LoginWindow</string>
    <key>ProgramArguments</key>
    <array>
        <string>/System/Library/CoreServices/RemoteManagement/AppleVNCServer.bundle/Contents/Support/LockScreen.app/Contents/MacOS/LockScreen</string>
        <string>-session</string>
        <string>256</string>
        <string>-msg</string>
        <string>Updates are currently being installed on this Mac. It will automatically be restarted or returned to the login window when installations are complete.</string>
    </array>
</dict>
</plist>"

##  Create the LaunchAgent file
echo "Creating the LockLoginScreen LaunchAgent..."
echo "$LAgentCore" > "$PLIST"

##  Set the owner, group and permissions on the LaunchAgent plist
echo "Setting proper ownership and permissions on the LaunchAgent..."
chown root:wheel "$PLIST"
chmod 644 "$PLIST"

##  Use SIPS to copy and convert the SWU icon to use as the LockScreen icon

##  First, back up the original Lock.jpg image
echo "Backing up Lock.jpg image..."
mv /System/Library/CoreServices/RemoteManagement/AppleVNCServer.bundle/Contents/Support/LockScreen.app/Contents/Resources/Lock.jpg 
/System/Library/CoreServices/RemoteManagement/AppleVNCServer.bundle/Contents/Support/LockScreen.app/Contents/Resources/Lock.jpg.bak

##  Now, copy and convert the SWU icns file into a new Lock.jpg file
##  Note: We are converting it to a png to preserve transparency, but saving it with the .jpg extension so LockScreen.app will recognize it.
##  Also resize the image to 400 x 400 pixels so its not so honkin' huge!
echo "Creating SoftwareUpdate icon as png and converting to Lock.jpg..."
sips -s format png "$swuIcon" --out /System/Library/CoreServices/RemoteManagement/AppleVNCServer.bundle/Contents/Support/LockScreen.app/Contents/Resources/Lock.jpg 
--resampleWidth 400 --resampleHeight 400

##  Now, kill/restart the loginwindow process to load the LaunchAgent
echo "Ready to lock screen. Restarting loginwindow process..."
kill -9 $(ps axc | awk '/loginwindow/{print $1}')

##  Install all available Software Updates
echo "Screen locked. Installing all available Software Updates..."
/usr/sbin/softwareupdate --install --all

if [ "$?" == "0" ]; then
    ## Delete LaunchAgent and reload the Login Window
    echo "Deleting the LaunchAgent..."
    rm "$PLIST"
    sleep 1

    if [[ "$rebootsPresent" == "yes" ]]; then
        ##  Put the original Lock.jpg image back where it was, overwriting the SWU Icon image
        echo "The rebootsPresent flag was set to 'yes' Replacing Lock.jpg image and immediately rebooting the Mac..."
        mv /System/Library/CoreServices/RemoteManagement/AppleVNCServer.bundle/Contents/Support/LockScreen.app/Contents/Resources/Lock.jpg.bak 
        /System/Library/CoreServices/RemoteManagement/AppleVNCServer.bundle/Contents/Support/LockScreen.app/Contents/Resources/Lock.jpg

        ##  Kill the LockScreen app and restart immediately
        killall LockScreen
        /sbin/shutdown -r now
    else
        ##  Put the original Lock.jpg image back where it was, overwriting the SWU Icon image
        echo "The rebootsPresent flag was not set. Replacing Lock.jpg image and restoring the loginwindow..."
        mv /System/Library/CoreServices/RemoteManagement/AppleVNCServer.bundle/Contents/Support/LockScreen.app/Contents/Resources/Lock.jpg.bak 
        /System/Library/CoreServices/RemoteManagement/AppleVNCServer.bundle/Contents/Support/LockScreen.app/Contents/Resources/Lock.jpg

        ##  Kill/restart the login window process to return to the login window
        kill -9 $(ps axc | awk '/loginwindow/{print $1}')
    fi

else

    echo "There was an error with the installations. Removing the Agent and unlocking the login window..."

    rm "$PLIST"
    sleep 1

    mv /System/Library/CoreServices/RemoteManagement/AppleVNCServer.bundle/Contents/Support/LockScreen.app/Contents/Resources/Lock.jpg.bak 
    /System/Library/CoreServices/RemoteManagement/AppleVNCServer.bundle/Contents/Support/LockScreen.app/Contents/Resources/Lock.jpg

    ##  Kill/restart the login window process to return to the login window
    kill -9 $(ps axc | awk '/loginwindow/{print $1}')
    exit 0
fi

}

##  The script starts here

##  Gather available Software Updates and export to a file
echo "Pulling available Software Updates..."
/usr/sbin/softwareupdate -l > /tmp/SWULIST
echo "Finished pulling available Software Updates into local file"

echo "Checking to see what updates are available..."
##  Generate list of readable items and installable items from file

readSWUs=$(awk -F"," '/recommended/{print $2,$1}' /tmp/SWULIST | sed -e 's/[0-9]*K [recommended][ *]//g;s/[restart] */◀ /g' | sed 's/    / /g;s/^ *//g')
progSWUs=$(awk -F"," '/recommended/{print $2,$1}' /tmp/SWULIST | sed -e 's/[0-9]*K [recommended][ *]//g;s/[restart] *//g' | sed 's/  / /g;s/^ */ /g')
installSWUs=$(grep -v 'recommended' /tmp/SWULIST | awk -F'\* ' '/*/{print $NF}')

##  First, make sure there's at least one update from Software Update
if [[ -z "$readSWUs" ]]; then
    echo "No pending Software Updates found for this Mac. Exiting..."
    exit 0
elif [[ ! -z "$readSWUs" ]] && [[ "$loggedInUser" != "root" ]]; then
    echo "Software Updates are available, and a user is logged in. Moving to initial dialog..."
    startDialog
elif [[ ! -z "$readSWUs" ]] && [[ "$loggedInUser" == "root" ]]; then
    if [ "$installAllAtLogin" == "yes" ]; then
        echo "SWUs are available, no-one logged in and the installAllAtLogin flag was set. Locking screen and installing all updates..."
        startLockScreenAgent
    else
        echo "SWUs are available, no-one logged in but the installAllAtLogin flag was not set. Exiting..."
        exit 0
    fi
fi

FritzsCorner
Contributor III

@mm2270 Looking good! The script works as expected when a user quits the app via CMD+Q and the updates get installed. I am having issues with the updates not installing on systems that have no current user logged in. I still think this is an isolated issue to my NetSUS though so I will do some testing on a system pointed to Apple's update servers and see how it goes.

As for your future modifications section in the comments... I think both of those would be great additions.

Thanks again!

perrycj
Contributor III

@mm2270 I'm in agreement with @FritzsCorner and am able to say in my testing yesterday and today.. deferrals are working properly and after a user quits when it's 0 left, updates are automatically installing.

The only thing I noticed is that it seems to be ignoring the $6 parameter for custom time on restart. Not the biggest of deals at all (since 5 mins is fine) but I set $6 to 2 mins and it was ignored and the script did 5 mins on the restart instead. Again, not a showstopper but wanted to let you know. Otherwise, it's been working great for me and I'll continue to test and let you know if anything else pops up.

mm2270
Legendary Contributor III

Hi @perrycj Thanks for the heads up on the $6 issue. I'll definitely check on that as soon as I can. It's likely just not getting passed to the script correctly so I'll dig into that to see why.

ostermmg
New Contributor

@mm2270 I see that you have an earlier iteration of this script here:
[https://github.com/mm2270/CasperSuiteScripts](link URL)

Would you be willing to merge in your latest to your GitHub repo? I'd like to be able to fork from GitHub, and could help with some of the consolidation/streamlining work. Should be as simple as submitting you a pull request for merge consideration.

perrycj
Contributor III

@mm2270 No problem. So I'm running into another issue in testing. It seems updates that require restart, are not actually forcing the mac to restart. Those updates are still getting installed, or at least leaving receipts/reflecting changes that they were installed but a restart isn't happening.

While that's awesome that a restart isn't being done and the updates are still (most likely) being installed (i.e., security update 2016-001 for 10.11.6, the 10.11.6 update from 10.11.5 or earlier).. pretty sure that's not the intended behavior of the script. Plus, in most of the other previous versions it would restart like it said it would. Here is what I am getting from a Mac that has an update that the script identifies as needing a restart but no restart occurs:

Pulling available Software Updates...
Finished pulling available Software Updates into local file
Checking to see what updates are available...
Software Updates are available, and a user is logged in. Moving to initial dialog...
There are some non reboot updates available. Showing selection screen to user
User clicked the "Install" button.
Selections were made. Moving to assessment function...
The following updates will be installed: Security Update 2016-001 (10.11.6)
Updates were selected
Restart required?: no
Displaying progress bar window.
Showing individual update progress.
Now installing Security Update 2016-001 (10.11.6)...
Closing progress bar.
Closing all cocoaDialog windows.
Showing updates complete message.
1
Cleaning up SWU list file.

It seems at first it recognizes it as needing a restart but when it goes to actually install it, it loses that flag or just forgets.

I noticed in earlier versions of the script this was in a different place, around line 520:

echo "Restart required?:   ${restartReq}"

##  If we have some selected items, move to install phase
if [[ ! -z "${selectedItems[@]}" ]]; then
echo "Updates were selected"
installUpdates
fi

}

More so, the "restart required?" line and its also in a different function. I bring this up because in the earlier version with it in this place, it seems updates were forcing restarts and now they are not.

In the current version, the same line is about 200 lines up around line 356:

##  Function to install selected updates, updating progress bar with information
installUpdates ()
{

echo "Restart required?:   ${restartReq}"

if [[ "${restartReq}" == "yes" ]]; then
installMSG="Installations are now running. Please do not shut down your Mac or put it to sleep until the installs finish.

IMPORTANT:
Because you chose some updates that require a restart, we recommend saving any important documents now. Your Mac will reboot soon after the installations are complete."

elif [[ "${restartReq}" == "no" ]] || [[ "${restartReq}" == "" ]]; then
installMSG="Updates are now installing. Please do not shut down your Mac or put it to sleep until the installs finish."
fi

Is it possible it is calling that function to run before it knows if it's yes or no for requiring restarts? Or just that the flag for restarts isn't being passed to the function that will actually enforce the restart?

Sorry for the long comment but wanted to make sure I got everything in and could give all the information necessary to show what I am seeing.