Popup notification

jeffrey_ngo
New Contributor

Hello everyone!

We like to alert our users of new software deployments and patches via popup notification, through SCCM in Windows this is an easy task. Currently for our Mac environment when I go to deploy a new update or software push I have to :

-Create a pdf with what software is being deployed and the time of deployment
-Push the pdf to the shared folder -Setup a policy to run through remote to open the PDF at a certain time in the AM

It seems like an awful lot of work for something fairly simple. Anyone have any better practices for deploying a popup notification to end users with new software or patches, unfortunately we can't use notification center because it's not obvious enough.

Thanks in advance.

18 REPLIES 18

mm2270
Legendary Contributor III

Too much work you're doing there. Use some of the built in functionality in the JSS version 9.x, like the User Interaction tab in a policy. Or a script calling jamfHelper, AppleScript, cocoaDialog, etc. There are loads of ways of getting a message up in front of a user that don't involved creating and deploying a full document.

stevewood
Honored Contributor II
Honored Contributor II

@jeffrey_ngo jamfHelper would be the easiest way that I can think of to do it. If all you are doing is deploying some text about what software updates are being pushed, it would be relatively easy to setup a policy in the JSS to do this. The syntax of jamfHelper is:

banner=`/Library/Application Support/JAMF/bin/jamfHelper.app/Contents/MacOS/jamfHelper -windowType hud -title "<windowtitle>" -heading "<paragraphheading>" -description "<text of what you are doing>" -icon <iconfile> -button1 "Proceed" -button2 "Not Now" -defaultButton 1 -cancelButton 2 -timeout 60 -countdown`

That will place a HUD window in the middle of the screen, place two buttons and a timeout of 60 with a countdown.

You can simply issue the jamfHelper command with -help to get more info:

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

Hope that helps you out.

emily
Valued Contributor III
Valued Contributor III

What about a script that creates a pop-up notification?

#!/bin/bash

# This script displays a message that lets the user know that 
# a browser installation policy has finished. It is set 
# to the lowest priority to ensure that it runs last after all 
# other scripts and policy actions.

# Determine OS version
osvers=$(sw_vers -productVersion | awk -F. '{print $2}')

dialog="hello world."
description=`echo "$dialog"`
button1="OK"
jamfHelper="/Library/Application Support/JAMF/bin/jamfHelper.app/Contents/MacOS/jamfHelper"
icon="/path/to/icon"

if [[ ${osvers} -lt 7 ]]; then

  "$jamfHelper" -windowType utility -description "$description" -button1 "$button1" -icon "$icon"

fi

if [[ ${osvers} -ge 7 ]]; then

  jamf displayMessage -message "$dialog"

fi

exit 0

I think I got this from @rtrouton but I can't find the github path for whatever reason.

jeffrey_ngo
New Contributor

Thanks so much everyone!

This will be immensely helpful in saving time!

rtrouton
Release Candidate Programs Tester

I've got a number of message scripts like the one @emilykausalik referenced. They're available from here:

https://github.com/rtrouton/rtrouton_scripts/tree/master/rtrouton_scripts/Casper_Scripts/message_dis...

John_Wetter
Release Candidate Programs Tester

If you're using Self Service, you could also turn on notifications in the app via the JSS.

tkimpton
Valued Contributor II

@stevewood that looks great but do you go about scripting if someone clicks proceed then go and do XYZ?

thoule
Valued Contributor II

As long as we're sharing.. I have a script called "Notify User". Then in a policy I can call that script with parameters.

#!/bin/sh                                            
#thoule
#displays jamf help message.

JH="/Library/Application Support/JAMF/bin/jamfHelper.app/Contents/MacOS/jamfHelper"
JHaddon=" -windowType utility -title IS_Updates -icon /usr/local/CORP/Logos/Logo_Black.png -windowPosition $6"
"$JH" $JHaddon -description "$4" -button1 $5  -timeout $7

Then under Script Options, I name Parameter 4 "Notification Text (Required)"
Parameter 5 "ButtonText (Optional)"
Parameter 6 "Location: ctr, ul, ll, ur, lr (optional)"
Parameter 7 "Timeout (Optional)"

Location is upper left, lower left, etc. Don't leave both Timeout and ButtonText empty!

stevewood
Honored Contributor II
Honored Contributor II

@tkimpton The short answer is yes. I don't typically use notifications to get permission to continue, with the exception of using @mm2270's Select Software Update script for updates. WIth that script it utilizes cocoaDialog to throw up a notification that the user can select updates and install, and then stays in the foreground as it installs (giving a progress bar for the user).

I typically use jamfHelper for "in your face" HUD style notifications when doing background installs at login or logout. For example, I just had to push Adobe Acrobat updates out at login, and I throw up a jamfHelper HUD to let folks know that is what is happening and to not open Acrobat until that dialog disappears. There is no button for them to clear the dialog (although if they were smart they could click on it and CMD-Q to quit it).

And like @thoule, my jamfHelper call is done via a script that allows me to select the image that is displayed, heading, text displayed, and icon.

Hope that answers your question.

jameson
Contributor II

@mm2270 Script is really great. I cannot imagine how long this script has taken to create

It is nearly 100% of what we need, but hope some scripts experts can help with a single change

Default it shows which available software there is to install. Can it be changed so there is no choice (checkmark greyed out or similar) - but popup just appear and name of software listed that needs to be updated and users then can use the "install now" button.
software Update information should appear on the screen, but users should not have an option check which software they want to install - some are even afraid of installing something.

mm2270
Legendary Contributor III

@jameson I actually have a version of the script where you can set a variable up near top to do exactly what you're asking. I should just say though that I really haven't used this script in a long time now, and I have heard that it may not work anymore on more recent OSes, like High Sierra and Mojave, but again, I haven't had the chance to test it to see. If you find it's working for you, then that's awesome and I'm glad to hear it!

I'll dig thru my script repo to locate the version that includes that option and post it here when I find it. It's just a flag you can set to enable that function. I believe it still allows for canceling the dialog however, so I don't know if you just want an "Install Now" button and nothing else. If so, I will tell you which line in the script to modify to make that change.

Captainamerica
Contributor II

Thank you very much - would be great and would love to have Those script skills but still quite new to Jamf.

The Best would be if users just Can click install now or a countdown shows when it is going to auto start the Update

Captainamerica
Contributor II

Sorry wrote it wrong. Actually updates should show up and countdown start fx x minutes before it installed - or user Can initiate install on their own.
Before rebooting (if needed) popup should appear to inform users and again countdown x minutes before auto restart

mm2270
Legendary Contributor III

OK, so here is that version of the script I mentioned. Use at your own risk, since I haven't tested it against current OS versions as I said earlier. (As you can see, it was July 2016 when I last touched it)

#!/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="yes"

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


##  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[@]}" 
    --label "$swuTextReboots" --button1 " Install " --button2 " Cancel " --cancel "button2" --button3 "Install No Reboot Updates" 
    --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

}


## This function is called when "forceEnableUpdates" is enabled above

startDialog2 ()
{

##  Generate array of SWUs for dialog
z=0
while read SWU; do
    SWUList+=( "• $SWU" )
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}")

dialogText="${swuTextReboots}
$(printf '%s
' "${SWUList[@]}")
"

SWUDiag=$( "$cdPath" msgbox --title "$orgName Software Update" --text "" 
--informative-text "${dialogText}" --button1 " Install " --button2 " Cancel " 
--cancel "button2" --icon-file "$swuIcon" --icon-height 80 --icon-width 80 --width 500 --posY top )

Button=$( echo "$SWUDiag" | awk 'NR==1{print $0}' )

if [[ "$Button" == "1" ]]; then
    ##  "Install" button was clicked. Run the assess checkbox function
    echo "User clicked the "Install" button"
    installUpdates
else
    echo "User chose to Cancel from the install dialog."
    echo "Cleaning up SWU list file. Exiting..."
    rm /tmp/SWULIST
    exit 0
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
    if [[ -z "$forceEnableUpdates" ]]; then
        echo "Software Updates are available, and a user is logged in. Moving to initial dialog..."
        startDialog
    else
        startDialog2
    fi
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

If you copy paste this into a text editor like TextWrangler or BBEdit, etc. the relevant lines are on line 29, which has forceEnableUpdates="yes" With the "yes" in there, it forces the updates to be selected by removing the checkboxes altogether. It will just show a list of the updates about to be installed.

If you want to remove the Cancel button from the dialog, then edit line 591, which is part of the startDialog2 function. Remove the --button2 " Cancel " text from that, which will remove the cancel button. You can also remove --cancel "button2" from line 592, just for cleanliness, but it's not strictly necessary.

Lastly, the if/then/else block that comes right after those lines includes an else part for if the user clicks Cancel, but it won't have any effect since there is only an "Install Now" button in the dialog. You could remove the else portion though if you wanted to.

Hope that helps and it works for you. Feel free to post back if you see any issues, but I can't guarantee how much time I will have to look it over for adjustments.

Captainamerica
Contributor II

Thank you - i Will try with mojave :)

Captainamerica
Contributor II

Your corrections work very good. However, when I click install (on the update 10.14.2 for Mojave), the policy just finish without anything is installed. So seems like the installation process has some issues
Don´t know if there is a "quick fix" for this or you maybe know some different script that can be used for software update that inform the users

Thanks for your support

Captainamerica
Contributor II

@mm2270 Just checked the first script again called Selectable_SoftwareUpdate.sh works fine and is updating - but the 005 version seems to have somekind of "bug" since nothing happens what clicking install. So guess it must be somekind of difference. Have tried to compare the 2 scripts but quite difficult for me to find the area where the problem is. So if you have any time to take a look it would be great

aqnguyen87
New Contributor

Is there a way I can get a step by step 'how-to' on sending a simple popup message with an OK button to a students computer? I am some what familiar with Terminal, but I am not seeing a place in Jamf to send commands. Is this something I can administer from an admin pc on our network? or would I use a the jamf webapp? Any help would be much appreciated. Thank you.