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

acdesigntech
Contributor II

yeah JH has a character/line limit. not sure what it is, though it looks like 6 lines is the Max. Might switch to using cocoaDialog

johnnasset
Contributor

@acdesigntech

Okay so some odd behavior with your script. When logged in as a local admin, I'm not given the option to install or cancel, just the JH popup about installing software. This is the following I get in the log:

Script result: Found 1 nodes: -- NODE -- cat: /Library/Application Support/JAMF/.SoftwareUpdateTimer.txt: No such file or directory 2014-03-19 12:43:02.314 softwareupdate[1089:4403] PackageKit: Missing bundle path, skipping: 2014-03-19 12:43:17.283 softwareupdate[1103:2a07] PackageKit: Missing bundle path, skipping: /Library/Application Support/JAMF/tmp/notouch_SWU.sh: line 76: [: -gt: unary operator expected Checking for policies triggered by "SoftwareUpdate"... No policies were found for the "SoftwareUpdate" trigger. /Library/Application Support/JAMF/tmp/notouch_SWU.sh: line 20: kill: (1128) - No such process

Any ideas (other than I forgot to change the SoftwareUpdate trigger to softwareupdate -ai)?

acdesigntech
Contributor II

yes

/Library/Application Support/JAMF/tmp/notouch_SWU.sh: line 76: [: -gt: unary operator expected

That error means that the '$Timer' variable that holds the value in the .SoftwareUpdateTime.txt file (5, 4, 3, 2, 1, or 0 in my script) did not evaluate to a integer -- it either was nothing or a string, in which case the comparison would fail ( -gt is only for integer comparisons). It most likely did not evaulate to anything - NULL or a blank, or something. If that comparison fails, the entire condition won't be met and it will default to the next item in the if..then..else statement which is to run the fRunUpdates function that shows the JH hud window about running updates.

Make sure you are starting your script off by creating the timer file if it's not already there:

if [ ! -e /Library/Application Support/JAMF/.SoftwareUpdateTimer.txt ]; then
    echo "5" > /Library/Application Support/JAMF/.SoftwareUpdateTimer.txt
fi

and then writing the new value (in my case it is current value - 1) to the same file if the user chooses not to run updates. Make sure to use a > to overwrite the value already stored there, not a >> to append:

let CurrTimer=$Timer-1
echo "user chose No"
echo "$CurrTimer" > /Library/Application Support/JAMF/.SoftwareUpdateTimer.txt

Same goes for when the script actually DOES run updates. You need to reset that timer file for the next round of updates, whenever that is:

echo "5" > /Library/Application Support/JAMF/.SoftwareUpdateTimer.txt

acdesigntech
Contributor II

derp... you'll want to make sure the line where your .SoftwareUpdateTimer.txt file is checked for a value is created before you try to get a value stored in there. On a first run of the script above it will fail to get a value -- thats the error you are seeing. On subsequent runs there will be a file created and you won't see that error any longer... I just love logic errors. Script above has been fixed.

dvasquez
Valued Contributor

let CurrTimer=$Timer-1
echo "user chose No"
echo "$CurrTimer" > /Library/Application Support/JAMF/.SoftwareUpdateTimer.txt

Seems to already have been set in the script I have. Is there an issue with that?

I also noticed what @johnnasset mentioned regarding the go ahead to install updates. Also is there a way to make more room in the helper windows for "reboot" updates? Or is this truly a limitation. I also cut down on text in buttons. This helped greatly with look and feel.

I also added the creation of the SoftwareUpdateTime.txt file. I should have mentioned that. After my last edit things seemed to be better. I just figured I would have the script create the file other than having another policy to push it out. I was not aware that this needed to happen for other reasons.

echo "5" > /Library/Application Support/JAMF/.SoftwareUpdateTimer.txt

Was also already there and correct in my script. I think you had that right before. does that need to be places elsewhere?

Thank you for the help.

RaulSantos
Contributor

Look at the way Robotcloud is using the same type of idea. I would love to see there script. http://robotcloud.screenstepslive.com/s/2459/m/5322/l/87479-install-all-updates-policy

johnnasset
Contributor

And look at their innovative Self Service portal!! This seems familiar:

http://robotcloud.screenstepslive.com/s/2459/m/5322/l/76738-self-service

mm2270
Legendary Contributor III

Its no secret RobotCloud is using the Casper Suite. They've been using it for years, but they've done some nice stuff around pulling data via the API and other methods into a completely custom UI. Looks nothing like the JSS, but the data is all from the JSS.

@dvasquez - as far as I know, there is no way to get around the line limitation in jamfHelper. I ran into it several times myself when doing this kind of thing a year or so back. Its partly why I chose to do my windowing in cocoaDialog. jamfHelper still has some useful modes, like fullscreen and hud, but for general user facing window stuff, I stick with cocoaDialog.

dvasquez
Valued Contributor

@acdesigntech][/url Hello. I am still seeing in test where when new updates are available the Helper prompt that lists updates that require an update does not come up. Instead it displays right away the "Installing Updates prompt". I made the adjustments you mentioned above. The logs do not mention anything specific or mentions successful completion. Also if I manually run the script and cancel at first sign of no list prompt then manually run it again it works correctly, I see the prompt with the list of updates. I also check the .SoftwareUpdateTimer.txt and it has been reset. Any ideas why this might still be happening. It seems to me that the script when run triggered by policy is not properly reading or locating the .SoftwareUpdateTimer.txt files when it first runs. I could be wrong though, of course.

Here is what I have:

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

and

if [ ! -e /Library/Application Support/JAMF/.SoftwareUpdateTimer.txt ]; then echo "5" > /Library/Application Support/JAMF/.SoftwareUpdateTimer.txt
fi

and

let CurrTimer=$Timer-1 echo "user chose No" echo "$CurrTimer" > /Library/Application Support/JAMF/.SoftwareUpdateTimer.txt exit 1 fi

I also had cut off text but shortened the text lines.
Thank you for your help.

acdesigntech
Contributor II

dominic, if you post your script I might be able to help you. If you copy and paste my script above into your text editor and save it up to the JSS, it should work via policy. Make sure you check your line breaks and formatting though when copy/pasting. Copying from a website sometimes screws with my formatting.

dvasquez
Valued Contributor

@acdesigntech I should check that. It is essentially your script edited to fit. I did not make any changes to major parts of your script. Thank you.

agirardi
New Contributor II

I hope that I am just doing something stupid, and this is a quick fix here. I just started testing this out, and I really like where this could go. My first time trying this on a machine running 10.9.1, I get the following error:

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 chose to Cancel. Exiting...

Tried this a few times, rebooted, tried different user, but I don't see a selection box pop up, and I am certainly not hitting cancel.

Yup, it was pretty stupid. Answer was here:

The only real requirements are the 3.0 beta 7 version of cocoaDialog to be on the Mac, and a Mac with at least one available Software Update to run it against. The script exits silently if no updates are available,

loceee
Contributor

Ladies and gentlemenssss.

junki is here..

https://jamfnation.jamfsoftware.com/discussion.html?id=10636

RobErnemt
New Contributor

Is there's a way to tweak this script to look for a policy with manual trigger instead of looking for an updates and let user postpone installation 3 times?

ksanborn
New Contributor III

I am fairly new to Casper to take it easy on me. We would like to automate the deployment of OS updates. All of the scripts in this thread great. We are running on to the following problem for the scripts we have tried so far. The message is No policies were found for the "runsoftwareupdate" trigger.

What does this mean? Do we need to create a second policy with a custom trigger value of "runsoftwareupdate"?

mm2270
Legendary Contributor III

@ksanborn

Do we need to create a second policy with a custom trigger value of "runsoftwareupdate"?

Yes, anytime you do something like sudo jamf policy -trigger <sometriggername> The sometriggername must be set up as a policy with a custom trigger (referred to as "events" in Casper Suite 9). Generally this is enabled in place of any other triggers, like Recurring check-in, login, etc, but its possible to check more than one trigger.

bpavlov
Honored Contributor

Yes, a trigger would be the custom trigger for another policy. As to what that other policy would contain, I'm not sure. As you can tell from this thread, there are a lot of different ways people are approaching this so I'm not sure what that particular script you are using had in mind specifically.

ksanborn
New Contributor III

@bpavlov Thank you. This helped. I don't have many computers in our testing environment that need updates still but I just ran the two policies on a computer and the update didn't install even though an update was found. Is this normal? e2f169cf24724b09aca70b124658d388

mm2270
Legendary Contributor III

@ksanborn There are probably like 15 different scripts on this thread. I'm not sure if anyone can help you troubleshoot unless you let us know which script you are using and perhaps even post the script itself, even if its basically identical to what is already here, and some details on how you have this setup in your JSS. Otherwise, its going to be pretty hard to help out.

lisacherie
Contributor II

The last version I posted in this crazy long thread should still work, though would need the syntax changed to be Casper 9 compatible on the line that calls the second policy.

If you are still stuck I can give you a little bit of help, though I don't know whose script and which iteration you are using :)

ksanborn
New Contributor III

All, thank you for your assistance. Everything is working now. My only question is, whether or not we can add a company logo to the message box? We already have the logo on the Macs. You guys are great. I did the following:

1.) I copied and pasted the script below in to a script in to JSS.

!/usr/bin/perl -w

use strict;

my $AVAILABLEUPDATES="";
my $CHANCESTOUPDATE=3;
my $COUNTFILE='/etc/SUScount.txt';
my $UPDATECOUNT=0;

$AVAILABLEUPDATES=/usr/sbin/softwareupdate --list;
chomp $AVAILABLEUPDATES;

printf "available updates is %s ", "$AVAILABLEUPDATES";

unless (-e $COUNTFILE){ system "/bin/echo 0 > $COUNTFILE";
}

$UPDATECOUNT=/bin/cat $COUNTFILE;
printf "update count is $UPDATECOUNT ";

If available updates contains * there are updates available

if ($AVAILABLEUPDATES=~/*/){

printf "there are updates available ";

if ($AVAILABLEUPDATES=~/(restart)|(shutsdown)/){

printf "updates need a restart ";

my $LOGGEDINUSER='';

$LOGGEDINUSER=/usr/bin/who | /usr/bin/grep console | /usr/bin/cut -d " " -f 1; chomp $LOGGEDINUSER;

printf "value of logged in user is $LOGGEDINUSER.. ";

if ($LOGGEDINUSER=~/[a-zA-Z]/) {

printf "as there is a logged in user checking count ";

my $CHANCESLEFT=$CHANCESTOUPDATE - $UPDATECOUNT; printf "Chances left is $CHANCESLEFT ";

if ($CHANCESLEFT <= 0) { printf "No chances left installing updates and will reboot.. "; system '/usr/sbin/jamf displayMessage -message "Your computer is installing updates and will reboot shortly."'; system "/usr/sbin/jamf policy -trigger runsoftwareupdate"; system "/bin/echo 0 > $COUNTFILE"; exit 0; }

else { printf "$CHANCESLEFT chances left... checking whether ok to restart "; my $ATTEMPT = $UPDATECOUNT 1; my $FINAL = $CHANCESTOUPDATE 1;

my $COMMAND= "'/Library/Application Support/JAMF/bin/jamfHelper.app/Contents/MacOS/jamfHelper' -startlaunchd -windowType utility -icon '/Library/Application Support/JAMF/bin/jamfHelper.app/Contents/Resources/Message.png' -heading "Software Updates are Available for Your Computer" -description "This is installation attempt $ATTEMPT of $CHANCESTOUPDATE. On attempt $FINAL, updates will install automatically and your machine will restart." -button1 "Yes" -button2 "Defer" -cancelButton "2"";

my $RESPONSE = ""; $RESPONSE=system $COMMAND;

if ($RESPONSE eq "0") { printf " User said YES to Updates "; system "/usr/sbin/jamf policy -trigger runsoftwareupdate"; system "/bin/echo 0 > $COUNTFILE"; exit 0; } else { printf " User said NO to Updates update count is $UPDATECOUNT "; $UPDATECOUNT=$UPDATECOUNT + 1; system "/bin/echo $UPDATECOUNT > $COUNTFILE"; printf " Update count is now $UPDATECOUNT "; exit 0; } } } else { printf "no logged in user so ok to run updates "; system "/usr/sbin/jamf policy -trigger runsoftwareupdate"; system "/bin/echo 0 > $COUNTFILE"; exit 0; } } else { printf "no restart required "; system "/usr/sbin/jamf policy -trigger runsoftwareupdate"; system "/bin/echo 0 > $COUNTFILE"; exit 0; }
}
else { printf "there are no updates available "; system "/bin/echo 0 > $COUNTFILE"; exit 0;
}

exit 0;

2.) I created two policies. One policy runs at trigger Reoccurring Check-in, Execution Frequency Once per Month, and with a scope of smart group that contains all Macs that need updates.

3.) The second policy is called RunSoftwareUpdate and has the below set with a trigger of custom runsoftwareupate

def52291505945aa92cb2de36118e8ba

lisacherie
Contributor II

You can choose the icon to use with the -icon option when calling jamfHelper, or you can replace the icon included with jamf helper.

jamfHelper options can be viewed with this command:

/Library/Application Support/JAMF/bin/jamfHelper.app/Contents/MacOS/jamfHelper -help

aaronbwcollins
New Contributor III

Not to bring alive an old thread, but since it was so helpful, I thought I would add something I added to make it work in an environment that has an internal SUS.

function check_corp {

  CORP_URL="internalcompanyurl.com"
  check_corp="False"
  ping=`host -W .5 $CORP_URL`

  # If the ping fails - check_corp="False"
  [[ ! -z $ping ]] && check_corp="True"

  # Check if we are using a test
  [[ -n "$1" ]] && check_corp="$1"
}

check_corp

function reset_sus_url {
    ## Reset the SUS URL to whatever was being used prior to the script running.
    if [ "$SUS_URL_SET" == "1" ]; then
        /usr/sbin/softwareupdate --set-catalog $SUS_URL
    else
        /usr/sbin/softwareupdate --clear-catalog
    fi
}

internal_sus_url="http://yoursus.company.com:8088/index.sucatalog"

## Check to see what SUS URL is currently being used and set a variable to call later.

SUS_URL=$(defaults read /Library/Preferences/com.apple.SoftwareUpdate CatalogURL | grep $internal_sus_url)

if [ -z "$SUS_URL" ]; then
    echo "Using Apple Server"
    SUS_URL_SET=""
else
    echo "Using Mac Server"
    SUS_URL_SET="1"
fi

## If connected ensure that VPN is not active

if [ $check_corp == "True" ]; then
    vpn_status="$(ifconfig -lu | grep tun0 )"
    if [ ! -z "$vpn_status" ]; then
        echo "Currently Connected to VPN"
        reset_sus_url
        exit 100
    else
        /usr/sbin/softwareupdate --set-catalog $internal_sus_url
    fi
else
    /usr/sbin/softwareupdate --clear-catalog
fi

MrP
Contributor III

Late to the party, but here is what I've written to handle this. It downloads the updates before it does anything to interact with the user. It then notifies the user updates are about to be installed and waits for acknowledgement before proceeding, so the user doesn't reboot in the middle of it. Once installed, if no reboot is required it notifies the user that no reboot is required and exits, so that they may reboot as desired without causing problems. If a reboot is required, it counts down 15 minutes before doing so,giving three prompts along the way, every 5 minutes. Each one says "reboot required, will happen automatically in N minutes" and has two buttons. "Reboot now" and "OK". If the user is logged in and the screensaver is active, indicating they are AFK, the script holds in a loop until the user is available to acknowledge. This prevents users screaming because they didn't save a document they had been working on all morning. If no user is logged in, the updates install and reboot automatically if needed.

The only thing I cannot get to work is the authenticated restart for encrypted volumes. I've tried the fde* command directly and tried to kick off a policy which has that configured. Neither ever boots back into the OS, which leaves the system at the filevault login with no way to remotely access it for further updates/inventory. Suggestions on that front welcome.

# Written by Paul Dickson

## Note: JAMF policy #350 is a policy that reboots with filevault authentication


touch /tmp/su.log
chmod 777 /tmp/su.log


b=softwareupdate -al | grep "found the following" | cut -d " " -f 3-5

# If updates were found, notify the user and begin install.
if [ "$b" == "found the following" ];
then

    # Marking updates as found for later script processing actions
    found="yes"

    # Caching/downloading updates before installation attempt
    softwareupdate -ad

    # Figure out if any of the updates require a restart
    softwareupdate -al | grep "[restart]"
    restart=$?


osascript << eof
-- Look to see if finder is running.  If it is then a user is logged in, so we will notify them about what is going on.
if application "Finder" is running and application "ScreenSaverEngine" is not running then 
    tell application "Finder"
        activate
        with timeout of 301 seconds
         display dialog "System updates are now being installed.  Please DO NOT shutdown or reboot until notified that the update process is complete." buttons {"Understood"} giving up after 300
        end timeout
    end tell
end if
eof

    # Installing all updates
    softwareupdate -ai

fi



# If updates were found this will run the jamf policy to update inventory on the server so that the server info on the computer reflects the new updates.
if [ "$found" == "yes" ]; then /usr/local/bin/jamf recon; fi


if [ "$restart" == "0" ]; 
then
# A restart is required to finish updates.  Notify user.

osascript << eof
-- Look to see if finder is running.  If it is then a user is logged in, so we will notify them about what is going on.
set a to 1
repeat until a = 0
    if application "Finder" is running and application "ScreenSaverEngine" is running then
        set a to 1
        --display dialog "1"
        do shell script "echo 'repeat 1 waiting' >> /tmp/su.log"
        delay 5
    else
        set a to 0
        --display dialog "0"
        do shell script "echo 'repeat 1 proceeding' >> /tmp/su.log"
    end if
end repeat
if application "Finder" is running then
do shell script "echo 'Finder running 1' >> /tmp/su.log"
    tell application "Finder"

    activate
    with timeout of 86400 seconds
        set question to display dialog "A reboot will be performed in 15 minutes to complete system updates.  Please save your work and exit all applications" buttons {"OK", "Reboot now!"} default button 1 giving up after 70000
        set answer to button returned of question

        if answer is equal to "Reboot Now!" then
            --tell application "Finder" to restart
            do shell script "jamf policy -id 350"
        else
            --delay 5
            delay 600
            if application "Finder" is running and application "ScreenSaverEngine" is not running then 
            do shell script "-n echo 'Finder running 2' >> /tmp/su.log"
                tell application "Finder"

                activate
                with timeout of 86399 seconds
                    -- This dialog will only wait for user confirmation for 60 seconds before continuing.
                    set question to display dialog "A reboot will be performed in 5 minutes to complete system updates.  Please save your work and exit all applications" buttons {"OK", "Reboot now!"} default button 1  giving up after 60
                    set answer to button returned of question

                    if answer is equal to "Reboot Now!" then
                            --tell application "Finder" to restart
                            do shell script "jamf policy -id 350"
                    else
                        --delay 5
                        delay 240
                        if application "Finder" is running and application "ScreenSaverEngine" is not running then 
                        do shell script "-n  echo 'Finder running 3' >> /tmp/su.log"
                            tell application "Finder"

                            activate
                            with timeout of 86398 seconds
                                -- This dialog will only wait for user confirmation for 60 seconds before continuing.
                                set question to display dialog "A reboot will be performed in 1 minute to complete system updates." buttons {"OK", "Reboot now!"} default button 1  giving up after 60
                                set answer to button returned of question
                            end timeout
                            end tell

                            if answer is equal to "Reboot Now!" then
                                --tell application "Finder" to restart
                                do shell script "jamf policy -id 350"
                            else
                                --delay 5
                                delay 60
                                if application "Finder" is running and application "ScreenSaverEngine" is not running then 
                                    do shell script "-n echo 'Tell Finder Restart 1' >> /tmp/su.log"
                                    --tell application "Finder" to restart
                                    do shell script "jamf policy -id 350"
                                end if
                            end if
                        end if
                    end if
                end timeout
                end tell
            end if
        end if
    end timeout
    end tell
end if
eof

echo 'jamf policy -id 350' >> /tmp/su.log
jamf policy -id 350

# Checking to see if updates were found.  As dictated by being an "elif", This only happens if a reboot isn't required but updates were found.  This lets them know a reboot is not required.
else
    if [ "$b" == "found the following" ];
    then
osascript << eof
if application "Finder" is running then
    tell application "Finder"
        activate
        with timeout of 3601 seconds
          display dialog "The software update process is now complete.  No reboot is required" buttons {"OK"} default button 1 giving up after 3600
        end timeout
    end tell
end if
eof
    fi
fi

exit 0

hkabik
Valued Contributor

@MrP Have you tried

do shell script "jamf policy -id 350 &"

to kick off your authenticated restart? I've had luck in the past shifting policy trigger to the background when they are problematic in scripts.

dmw3
Contributor III

@mm2270 running your Selectable_SoftwareUpdate.sh script on 10.11.5 comes back with syntax errors:

selectable_SoftwareUpdate.sh: line 461: syntax error near unexpected token `<'
selectable_SoftwareUpdate.sh: line 461: `done < <(echo "${readSWUs}")'

Any updates on this script?

mm2270
Legendary Contributor III

Hi @dmw3 Can i ask how you're running the script? From the error you posted, I think you may be calling the script in Terminal by doing something like sh /path/to/Selectable_SoftwareUpdate.sh If so, the problem with doing that is by specifying sh up front, you're overriding the shell interpreter for the script, which is /bin/bash, not /bin/sh. This script uses some bash specific items in it, namely process substitution, which is what the error you posted is showing.

If you were running it that way, try by making the script executable first and then running it by just putting in the path to the script. It should work that way I think. That being said, I'm not sure if I've tried running this script on 10.11.5, so I'll work on that later today to see if I see any similar errors or other issues with it.

mbezzo
Contributor III

crazy questions @mm2270 How much work would it be to change your script to not include check boxes? Make more of a "here's what's available to install" in a list. I've taken a quick look at the code, but curious on your thoughts.

Thanks!
Matt

mm2270
Legendary Contributor III

@mbezzo Not crazy. Instead of removing the checkboxes, how about having them all checked but greyed out/disabled instead? Something like this?

06034ff67bf849e9a929e663965e2579

Another option I thought of just now would be to have all the non-reboot updates checked and disabled, so the user can't refuse installs of those, but have the Reboot Required ones optional. It can be either not checked but check-able, like so:

178ec9c9834d4b0d861123a8ce78294f

Or, checked by default, but enabled for the user to uncheck it if they needed or wanted to.

568f935773b44952828690dce9d1f942

All these options are possible, because cocoaDialog supports checked and disabled arrays that you can pass to it to tell it which checkbox items in the index should be set those ways. (Its why I continue to love the product and wish it would get some love to keep it viable)

Note that although I just whipped this up in about 5 minutes, I haven't actually tested an install run just yet. I will do that and see if it works as designed. I assume it should, but you know what they say about assuming. :)

mbezzo
Contributor III

Looks good! Will definitely be playing with this next week. :)

Thanks as always @mm2270!

Enjoy the weekend,
Matt

Xopher
New Contributor III

@mm2270 thanks for the great script! Can you give a more advice on what items to change to have all updates checked but greyed out/disabled? We pretty much force all the updates to machines because they have gone through a couple rounds of test deployments before being sent out to all. Management also want the users to be able to defer updates that cause a reboot for a bit (15, 30, 1 hour or install now). Right now I have the reboot time parameter set to 30 min (if needed). So any advice on adding deferment like in your reboot_scheduler.sh (another great script)? Unfortunately my script-fu is not near good enough to meld to two scripts together successfully.

mm2270
Legendary Contributor III

Hi @Xopher I hadn't thought of merging functionality from the 2 scripts together, but I suppose its something that could be done. Of course, you could just run one script after another in some way to make it all happen, but I know that's a bit more work and could end up with weird results.
I'll look into adding some deferment functionality into the Selectable Software Update script.

As for making the checkboxes greyed out and checked, I'll post that back here a little later. The dialog text needs to be adjusted as well since it doesn't make any sense to say something like 'select the updates to install' if they are all already selected for you.
I have to say though that after looking over the screenshots the other day, I'm not too crazy on this after all, only because the grey text (for the checked but enabled items) is so light its hard to read them clearly, so its not the best user experience in my opinion. Unfortunately, when items are disabled in a dialog, there's no way to control the coloring. That comes from the OS, not something built into cocoaDialog, not that I"d be able to change it there either.

Given this, I may see about adding an extra flag in the script that will simply list the updates that are going to be installed in plain (black) text rather than the checkboxes. It shouldn't be too hard to incorporate.

mbezzo
Contributor III

@mm2270

Given this, I may see about adding an extra flag in the script that will simply list the updates that are going to be installed in plain (black) text rather than the checkboxes. It shouldn't be too hard to incorporate.

+1 for this - that's basically exactly what I want. :) That and a selectable deferral and i think this script is basically all anyone could ever hope for!

Thanks,
Matt

mm2270
Legendary Contributor III

Well, for the moment, I'm posting a version below that still uses the checkboxes, but greys them out like shown above, but with some additional modifications to the dialog. I will work on the version that just displays the text in black text like I mentioned above and then post back. Here's the first revision below.

#!/bin/bash

##  Script Name:        Selectable_SoftwareUpdate.sh    (v005)
##  Script Author:      Mike Morales, @mm2270 on JAMFNation
##  Last Update:        2016-07-19

##  Path to cocoaDialog (customize to your own location)
cdPath="/Library/Application Support/JAMF/bin/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.

forceEnableUpdates="~[upload](3744a7a8fa4e45b183e2d26ae2f58167)
"

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

◀  =  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.

◀  =  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".

"

else
    swuTextNoReboots="Select the Apple Software Update items you would like to install now from the list below.

"

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
    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
    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=""
else
    button3Label="Install No Reboot Updates"
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 " Cancel " --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 "Cancel" 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 Cancel. Exiting..."
        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 " Cancel " --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 exit, click "Cancel" 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 Cancel from the selection dialog."
        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

Essentially, the force option can be enabled with a flag by entering any value on line 29 in the script where it shows

forceEnableUpdates=""

When that's on, it checks and grays out the checkboxes and changes the dialog text and buttons accordingly. A couple of quick screenshots to show the difference. Without the forceEnableUpdates option enabled, we see the original behavior:

17ddae8fa08d49b485f37fa4cb95c355

With that option enabled with any text in the variable, it changes the behavior to:

c6f99c3af0164ac396b1d91572f8ee9c

As shown, the dialog text changes, and the "Install No Reboot Updates" button disappears in the 2 instance.

As soon as I have something to show for simply making the text show up in a list with no checkboxes, I'll post back with that.

mbezzo
Contributor III

thank you, sir! appreciate all your work on this.

Xopher
New Contributor III

Yes, thank you for all your contributions (and everyone's) to this community!

dmw3
Contributor III

@mm2270 In the modified script above the section between lines 524 to 634 are not valid as there is a syntax error around line 524.

Thanks for all the help, you were correct we were trying the script using sh, changed that behaviour and the unmodified script works well.

Also would like to have a deferral section added to this to make it complete.

m6jamf
New Contributor II

@mm2270 fantastic work! How do you feel about putting the script on github so others can contribute, if desired?

dmw3
Contributor III

@mm2270 Found that if you change "didn't" to "did not" the syntax is corrected

dmw3
Contributor III

From the BASH scripting guide:
escape [backslash]. A quoting mechanism for single characters.

X escapes the character X. This has the effect of "quoting" X, equivalent to 'X'. The may be used to quote " and ', so they are expressed literally.

So if "didn't" is changed to "didn't" the syntax is also correct.