Posted on 04-13-2018 02:16 PM
With the goal of having better user interaction options that what is provided within Jamf's Patch Management Policies (not having the app quit 5 minutes later without warning) I have been testing different options with my existing update workflow and open source tools like Yo. I ended up using Alerter which will wait for user interaction within a script and allows the calling of a command with sudo permissions in order to execute a policy trigger.
I haven't deployed it to my fleet yet but figured I would see what feedback the community might have. It takes a 2 policies and 1 smart group per software title, which is a bit cumbersome, and currently never forces the update as the user could infinitely defer the installation if the app is never quit. I use a deferral counter for macOS software updates that could be integrated but I have not taken the time to do so yet.
I'm open to any and all feedback, thanks!
Setup Prior to Workflow
Script
By setting up the Script Parameters it can be adapted to any title
#!/bin/bash
# If app is open, alert user with the option to quit the app or defer for later. If user chooses to install it will quit the app, trigger the installation,
# then alert the user the policy is complete with the option to reopen the app. If the app is not open it will trigger the installation without alerting
# $4 = Title
# $5 = ParameterID
# $6 = Process Name
# $7 = Jamf Policy Event
# $8 = App Path
# Define currently logged in user
currentUser="$(ls -la /dev/console | cut -d " " -f 4)"
# Look if app is open via process name
appOpen="$(pgrep -ix "$6" | wc -l)"
if [[ $appOpen -gt 0 ]]; then
updateAnswer="$(/Library/Application Support/JAMF/alerter -title "$4" -sender $5 -message "must be quit in order to update. Save all data before quitting." -closeLabel Later -actions "Quit & Update")"
if [[ $updateAnswer == "Quit & Update" ]]; then
sudo -u $currentUser killall "$6"
jamf policy -event "$7"
reopenAnswer="$(/Library/Application Support/JAMF/alerter -title "$4" -sender "$5" -message "has been updated. Thank you." -closeLabel Ok -actions Reopen -timeout 60)"
if [[ $reopenAnswer == Reopen ]]; then
sudo -u $currentUser open "$8"
fi
else
echo "User deferred"
fi
else
jamf policy -event "$7"
fi
Policy
1. Create package that installs the alerter
executable to /Library/Application Support/JAMF/
2. Push pkg via policy to install Alerter pkg to All Manage Clients (or whatever scope will utilize this workflow)
Patch Management Title
Create a Patch Management Title for 'Google Chrome'. If the app you are looking to update is not available as a patch Management Title setup your smart group to have Application Version
instead of Patch Reporting
.
Testing Rings
To allow the update to be pushed out in rings for testing, pre-defined Static/Smart Groups are setup with the following logic:
Ring 1 - Smart Group based IT computers
Ring 2 - Static Group of manually selected group with various roles and use cases across organization, around 15-20% of computer count
Ring 3 - all computers not in Ring 1 or Ring 2
Workflow Setup
Smart Computer Group
Create a Smart Computer Group to scope to any computer with the app installed and not running the desired version
1) Smart Group name: *Google Chrome Smart Update
(Asterisk to keep Smart Update groups grouped at the top of your Smart Computer Group list)
2) Application Title
is
Google Chrome.app
3) Patch Reporting: Google Chrome
is not
65.0.3325.181
Policies
Create a Policy used to trigger the installation of the pkg
1) Policy name: Install Latest Google Chrome Trigger
2) Custom Trigger: install_googleChromeUpdate
3) Execution Frequency: Ongoing
4) Packages: GoogleChrome-65.0.3325.181.pkg
5) Scope: *Google Chrome Smart Update
6) Maintenance: Update Inventory
Create policy that will prompt user if app is open, install if it is not open
1) Policy Name: Install Latest Google Chrome Alerter
2) Scripts: Alerter App Updates
3) Parameter Values
a. Title: Google Chrome
b. Sender ID: com.google.chrome
c. Process Name: Google Chrome
d. Jamf Policy Event: install_googleChromeUpdate
e. App Path: /Applications/Google Chrome.app
4) Scope:
a. Target: *Google Chrome Smart Update
b. Exclusions: Ring 1
, Ring 2
, Ring 3
Ongoing Maintenance
When a new update is release the following maintenance would be required
1) Upload new pkg release for Google Chrome
2) Change Smart Computer Group the Patch Reporting version to new release number
3) Update the pkg within Install Latest Google Chrome Trigger
policy
4) Reset Ring Exclusions - Based on testing schedule remove a group from the exclusions as needed (start with Ring 2
and Ring 3
as excluded, one week later remove Ring 2
, one week after that remove Ring 3
)
Solved! Go to Solution.
Posted on 10-18-2018 11:33 AM
Realized I never posted what I finally settled on and put in production in case others want to give it a try. Been running with it for a while and seems to be working pretty well! Thanks for all the input and testing from others in the thread.
#!/bin/bash
# If app is open, alert user with the option to quit the app or defer for later. If user chooses to install it will quit the app, trigger the installation,
# then alert the user the policy is complete with the option to reopen the app. If the app is not open it will trigger the installation without alerting
# Quit and Open path have 2 entries for the times you are quiting/uninstalling an old version of an app that is replaced by a new name (for example quiting Adobe Acrobat Pro, which is replaced by Adobe Acorbat.app)
################################DEFINE VARIABLES################################
# $4 = Title
# $5 = App ID
# $6 = Process Name
# $7 = Jamf Policy Event
# $8 = Quit App Path
# $9 = Open App Path
#Defining the Sender ID as self service due to setting the Sender ID as the actual app being updated would often cause the app to crash
sender="com.jamfsoftware.selfservice.mac"
#Jamf parameters can't be passed into a function, redefining the app path to be used within the funciton
quitPath="$8"
openPath="$9"
################################SETUP FUNCTIONS TO CALL################################
fGetCurrenUser (){
currentUser=`python -c 'from SystemConfiguration import SCDynamicStoreCopyConsoleUser; import sys; username = (SCDynamicStoreCopyConsoleUser(None, None, None) or [None])[0]; username = [username,""][username in [u"loginwindow", None, u""]]; sys.stdout.write(username + "
");'`
# Identify the UID of the logged-in user
currentUserUID=`id -u "$currentUser"`
}
fQuitApp (){
cat > /private/tmp/quit_application.sh <<EOF
#!/bin/bash
/bin/launchctl asuser "$currentUserUID" /usr/bin/osascript -e 'tell application "$quitPath" to quit'
EOF
/bin/chmod +x /private/tmp/quit_application.sh
/bin/launchctl asuser "$currentUserUID" sudo -iu "$currentUser" "/private/tmp/quit_application.sh"
/bin/rm -f "/private/tmp/quit_application.sh"
}
fOpenApp (){
cat > /private/tmp/open_application.sh <<EOF
#!/bin/bash
/usr/bin/open "$openPath"
EOF
/bin/chmod +x /private/tmp/open_application.sh
/bin/launchctl asuser "$currentUserUID" sudo -iu "$currentUser" "/private/tmp/open_application.sh"
/bin/rm -f "/private/tmp/open_application.sh"
}
################################SETUP TIMER FILE################################
## Set up the software update time if it does not exist already
if [ ! -e /Library/Application Support/JAMF/.$5.timer.txt ]; then
echo "2" > /Library/Application Support/JAMF/.$5.timer.txt
fi
## Get the timer value
timer=`cat /Library/Application Support/JAMF/.$5.timer.txt`
################################ALERTER MESSAGE OPTIONS################################
saveQuitMSG="must be quit in order to update. Save all data before quitting."
updatedMSG="has been updated. Thank you."
################################START 'UPDATE WITH ALERTER' PROCESS################################
# Look if app is open via process name
appOpen="$(pgrep -ix "$6" | wc -l)"
# if the app is open and the defer timer is not zero
if [[ $appOpen -gt 0 && $timer -gt 0 ]]; then
fGetCurrenUser
updateAnswer="$(/bin/launchctl asuser "$currentUserUID" /Library/Application Support/JAMF/alerter -title "$4" -sender "$sender" -message "$saveQuitMSG" -closeLabel "Defer ($timer)" -actions "Quit & Update" -timeout 3600)"
if [[ $updateAnswer == "Quit & Update" ]]; then
#quit app, install the update, then prompt the user when complete and ask if they want to reopen the app. Message will time out after 60 secs.
fQuitApp
/usr/local/bin/jamf policy -event "$7"
reopenAnswer="$(/bin/launchctl asuser "$currentUserUID" /Library/Application Support/JAMF/alerter -title "$4" -sender "$sender" -message "$updatedMSG" -closeLabel Ok -actions Reopen -timeout 60)"
if [[ $reopenAnswer == Reopen ]]; then
fOpenApp
fi
#reset timer after updating
echo "2" > /Library/Application Support/JAMF/.$5.timer.txt
else
let CurrTimer=$timer-1
echo "User chose to defer"
echo "$CurrTimer" > /Library/Application Support/JAMF/.$5.timer.txt
echo "Defer count is now $CurrTimer"
exit 0
fi
# if app is open and defer timer has run out
elif [[ $appOpen -gt 0 && $timer == 0 ]]; then
fGetCurrenUser
/bin/launchctl asuser "$currentUserUID" /Library/Application Support/JAMF/alerter -title "$4" -sender "$sender" -message "$saveQuitMSG" -actions "Quit & Update" -closeLabel "No Deferrals Left " -timeout 3600
fQuitApp
/usr/local/bin/jamf policy -event "$7"
reopenAnswer="$(/bin/launchctl asuser "$currentUserUID" /Library/Application Support/JAMF/alerter -title "$4" -sender "$sender" -message "$updatedMSG" -closeLabel Ok -actions Reopen -timeout 60)"
if [[ $reopenAnswer == Reopen ]]; then
fOpenApp
fi
#reset timer after updating
echo "2" > /Library/Application Support/JAMF/.$5.timer.txt
else
# app is not open, reset timer and run updates
echo "2" > /Library/Application Support/JAMF/.$5.timer.txt
/usr/local/bin/jamf policy -event "$7"
fi
Posted on 04-16-2018 03:01 AM
Hi,
nice workflow. But can you explain $5 = ParameterID more in detail? Where do you get the ParameterID?
Thanks, Philipp
Posted on 04-16-2018 03:10 AM
This is very interesting, I have a similar workflow for patching, that just checks if the application is running and doesn't do anything if it it. I might replace that part of the workflow with this!
@preinheimer The ParamaterID will be the '-sender' argument for Alerter
-sender ID The bundle identifier of the application that should be shown as the sender, including its icon.
Posted on 04-16-2018 01:03 PM
@kendalljjohnson This is awesome! Thanks so much for your hard work on this!
This is very close to what we've been doing with @kitzy's Better Software Updates with End User Interaction script that we're currently using in our environment.
But I really like the idea of using notifications for this instead, especially since folks tend to trust and respond to those a lot more these days. What's nice about the Better Software Updates... script is that also allows for multiple processes (comma separated) to be closed if needed. I've modified the script you've shared to behave similarly and thought I'd post if anyone else might find it useful.
#!/bin/bash
# If app is open, alert user with the option to quit the app or defer for later. If user chooses to install it will quit the app, trigger the installation,
# then alert the user the policy is complete with the option to reopen the app. If the app is not open it will trigger the installation without alerting
# $4 = Title
# $5 = ParameterID
processNames="${6}"
# $7 = Jamf Policy Event
# $8 = App Path
IFS="," # Set internal field separator to comma to saparate process names
# Identify the username of the logged-in user
currentUser=`python -c 'from SystemConfiguration import SCDynamicStoreCopyConsoleUser; import sys; username = (SCDynamicStoreCopyConsoleUser(None, None, None) or [None])[0]; username = [username,""][username in [u"loginwindow", None, u""]]; sys.stdout.write(username + "
");'`
# Identify the UID of the logged-in user
currentUserUID=`id -u "$currentUser"`
for process in $processNames
do
PID="" # Clear PID to elimnate false positives
PID=`pgrep "$process"` # Get application PID
if [ ! -z "$PID" ] # Detect if application is running
then
# Prompt user to quit the running application
echo "$process is running, prompting user to quit"
updateAnswer="$(/bin/launchctl asuser "$currentUserUID" /Library/Application Support/JAMF/alerter -title "$4" -sender "$5" -message "$process must close to install an update." -closeLabel Later -actions "OK" -timeout 300)"
if [[ $updateAnswer == "OK" ]]
then
echo "User clicked OK"
# Ask application to quit
osascript -e "tell application "$process" to quit"
else
echo "User clicked Later. Exiting..."
exit 0
fi
else
echo "$process not running, moving on..."
fi
done
jamf policy -event "$7"
jamf recon
reopenAnswer="$(/bin/launchctl asuser "$currentUserUID" /Library/Application Support/JAMF/alerter -title "$4" -sender "$5" -message "has been updated." -closeLabel OK -actions Open -timeout 60)"
if [[ $reopenAnswer == Open ]]; then
sudo -u $currentUser open "$8"
echo "Opening $8 for $currentUser..."
fi
P.S. If anyone catches any mistakes or has any suggestions for improvements please let me know. Thanks!
Posted on 04-17-2018 12:28 AM
Has anyone tested this in a policy so far? Running alerter as normal user seems to work, but if it runs in a script that runs as root, it fails to connect:
2018-04-17 09:27:53.661 alerter[2252:16472] NSNotificationCenter connection invalid
2018-04-17 09:27:53.661 alerter[2252:16472] Connection to notification center invalid. ServerConnectionFailure: 1 invalidated: 0
Posted on 04-17-2018 06:30 AM
@remyb, hmmm I've tested both scripts using a policy and didn't have any issues running as root. Are you able to run Alerter in terminal as root?
/Library/Application Support/JAMF/alerter -message 'Start now ?' -closeLabel No -actions YES,MAYBE,'one more action' -timeout 10
Posted on 04-17-2018 06:35 AM
@mottertektura I believe I found the issue; I'm changing to root using: sudo su -, this discards the current environment. Changing to root using: sudo su (without the -) does work and now I have working notifications. Sorry for the confusion!
Posted on 04-17-2018 01:35 PM
Hi there @kendalljjohnson, thanks for creating this! It looks like it would work very elegantly and I've begun experimenting with it.
I noticed in your writeup that there isn't a policy trigger specified for the "Install Latest Google Chrome Alerter." Would it be recurring check-in?
Also, @mottertektura , I think I had a similar issue running the policy(?)... the script attempted to run but just got hung up somewhere until I cancelled it. Not sure yet where I'm running into problems yet.
Posted on 04-17-2018 01:52 PM
@crogersgrazado You're right, looks like I totally skipped that. I've been testing it with recurring. The frequency would probably need to be set to your environment's urgency. It would need to be either an ongoing or once per day/week/month, depending on how much you want to bug your user. The alerts would not build up, since the script awaits user interaction. But if you have it set to ongoing, with the default every 15 minutes for check-in, then if your user is at their computer and says "Later" every time, they will repeatedly get peppered with the alert throughout the day. Since there's isn't a force install feature built in (yet?), if they are only asked once a day or week it could be an easy enough process that they click "Later" every time and never actually run the update. Whatever the time frame you set, they would not stop getting the alert until they actually quit the app, which allows the install to happen, which would include the recon after so they get moved out of the smart group saying they weren't on the desired version.
As far as the error, I'm not sure as I haven't run into that specific issue. You can always play with it as a local script that you can call from an elevated account, that might give more feedback or flexibility in testing. Even try each line manually, run within a terminal window with sudo permissions while logged into a non-admin account.
Hope that helps, glad ya'll are getting some use out of this.
Posted on 04-17-2018 01:56 PM
@kendalljjohnson Thanks so much for the thorough response - I figured that was the case.
As for the issue I'm having, I probably missed a small detail somewhere. I'll do more testing and report back, and do keep us posted if you roll it out! Thanks again!
Posted on 04-18-2018 06:33 AM
It might be necessary to add the timeout option to alerter if you use this in production. If the user never presses any button, the policy will be stuck waiting for user input. And the machine will stop checking in until the user actually provides that input.
Posted on 04-18-2018 10:05 AM
Found out that if I manually run jamf policy on the client, the policy works as expected and I receive the notification to update, but if I let the policy run with a normal check in, in hangs and never sends the notification. @crogersgrazado @remyb Is this what you were running into as well? I also agree with @remyb , it would probably be a good idea to add a timeout to the update notification so it doesn't hang there and cause the client to stop checking in.
Posted on 04-18-2018 10:08 AM
It wasn't the issue I was originally facing, but after some further testing today I ran into the same issue you are now describing. Running a manual sudo jamf policy results in a notification, letting the check-in handle it doesn't produce a notification and 'ps -ef|grep jamf' shows that it hangs.
Posted on 04-18-2018 01:27 PM
@remyb @mottertektura @crogersgrazado
Great catch by you all! I had only tested by manually triggering the policy check, not with the recurring checkin. I am getting the same results.
I started a support case, as there is some weird difference between how a policy is called when running "sudo jamf policy" and a recurring check-in. My initial thoughts is there would be a difference in permissions between the elevated terminal command with sudo and our jamf management account using the launch agents/daemons for check ins.
I'll let you know if I get any news from support on how to address this difference!
Posted on 04-18-2018 02:11 PM
@kendalljjohnson what you're experiencing is normal, sort of. The difference is that when you do sudo jamf policy
, you're actually running the process as you, but with elevated privileges. So when something like Alerter for example, requires running as the user to work, it works ok when the policy is manually called, because again, it's really you running it with elevated rights.
When the recurring trigger runs, it gets called by the Jamf LaunchDaemon, which runs completely as root, meaning tools like Alerter won't work. This is partly Apple to blame as they keep tightening the screws on security around what can display messages to the user, especially interactive stuff. I've run into the same issue with Yo, for example.
The way I've gotten around it is to use the /bin/launchctl asuser
syntax. If you do searches on that you'll find examples on how to use it to get this to work. Again this only typically applies when the LaunchDaemon calls the policy. It may not apply with manually called policies, Self Service policies and so on.
Posted on 04-18-2018 03:09 PM
@remyb @mottertektura @crogersgrazado
Once again, @mm2270 saves the day with scripting. Found the right method of getting the correct user info from a Rich Trouton blog post and was able to get the policy to run correctly from a recurring check-in. Try the following script, which calls Alerter with launchctl asuser. I also moved the part of getting the currentUser info inside the then statement since that information is only needed if the app is open, rather than getting the info every time the script is run.
#!/bin/sh
# If app is open, alert user with the option to quit the app or defer for later. If user chooses to install it will quit the app, trigger the installation,
# then alert the user the policy is complete with the option to reopen the app. If the app is not open it will trigger the installation without alerting
# $4 = Title
# $5 = ParameterID
# $6 = Process Name
# $7 = Jamf Policy Event
# $8 = App Path
# Look if app is open via process name
appOpen="$(pgrep -ix "$6" | wc -l)"
if [[ $appOpen -gt 0 ]]; then
# Identify the username of the logged-in user
currentUser=`python -c 'from SystemConfiguration import SCDynamicStoreCopyConsoleUser; import sys; username = (SCDynamicStoreCopyConsoleUser(None, None, None) or [None])[0]; username = [username,""][username in [u"loginwindow", None, u""]]; sys.stdout.write(username + "
");'`
# Identify the UID of the logged-in user
currentUserUID=`id -u "$currentUser"`
updateAnswer="$(/bin/launchctl asuser "$currentUserUID" /Library/Application Support/JAMF/alerter -title "$4" -sender $5 -message "must be quit in order to update. Save all data before quitting." -closeLabel Later -actions "Quit & Update")"
if [[ $updateAnswer == "Quit & Update" ]]; then
sudo -u $currentUser killall "$6"
jamf policy -event "$7"
reopenAnswer="$(/Library/Application Support/JAMF/alerter -title "$4" -sender "$5" -message "has been updated. Thank you." -closeLabel Ok -actions Reopen -timeout 60)"
if [[ $reopenAnswer == Reopen ]]; then
sudo -u $currentUser open "$8"
fi
else
echo "User deferred"
fi
else
jamf policy -event "$7"
fi
Posted on 04-19-2018 09:16 AM
@kendalljjohnson , Thanks! I updated the version I posted above as well with this change. I thought about trying to adjust as well to only re-open the app if it was running, but because I'm checking for multiple processes I couldn't add it inside that then statement or it could potentially open multiple times. Thanks again!
Posted on 04-23-2018 02:06 PM
Thanks for the script! I took a very crude stab at adding some minimal deferral functionality. I added a few options to explicitly define the max number of deferrals as well as where to put the deferral text file. Additionally, I use the app name for the deferral text file in the event there are multiple apps that need to be updated.
I was thinking about adding a timer to the alert so in the event a user is logged on but doesn't respond within a certain time-frame it will automatically close with the Defer option. When the user runs out of deferrals I was looking at adding a warning notification 5 minutes before the app is updated.
#!/bin/bash
# If app is open, alert user with the option to quit the app or defer for later. If user chooses to install it will quit the app, trigger the installation,
# then alert the user the policy is complete with the option to reopen the app. If the app is not open it will trigger the installation without alerting
# $4 = Title
# $5 = ParameterID
# $6 = Process Name
# $7 = Jamf Policy Event
# $8 = App Path
# $9 = Deferral Count
# $10 = Deferral Timer Path
######### Set variables for the script ############
## Set the Patch Update Timer if it does not exist already
appTimer="${10}.${4}PatchTimer.txt"
if [ ! -e "$appTimer" ]; then
echo "$9" >"$appTimer"
fi
## Get the timer value
Timer=`cat "$appTimer"`
# Alerter Messages
appMsg="Mandatory $4 Update"
deferralMsg="Save all data before continuing. You have $Timer deferalls remaining."
# Identify the username of the logged-in user
currentUser=`who | grep console | awk '{print $1}'`
# Identify the UID of the logged-in user
currentUserUID=`id -u "$currentUser"`
################ End Variable Set ################
## if there is no one logged in, just run the update
if [ "$currentUser" == "" ]; then
echo "No one is logged in. Continuing Install"
jamf policy -event "$7"
echo "The install was successfull, removing deferral Timer"
rm -rf $appTimer
exit 0
else
echo "A user is logged in"
fi
# Look if app is open via process name
appOpen="$(pgrep -ix "$6" | wc -l)"
if [[ $appOpen -gt 0 ]]; then
echo "The application is open. Checking Deferral count"
if [ $Timer -gt 0 ]; then
updateAnswer="$(/bin/launchctl asuser "$currentUserUID" /Library/Application Support/JAMF/alerter -title "$appMsg" -sender $5 -message "$deferralMsg" -closeLabel Later -actions "Quit & Update")"
if [[ $updateAnswer == "Quit & Update" ]]; then
sudo -u $currentUser killall "$6"
jamf policy -event "$7"
reopenAnswer="$(/Library/Application Support/JAMF/alerter -title "$4" -sender "$5" -message "has been updated. Thank you." -closeLabel Ok -actions Reopen -timeout 60)"
if [[ $reopenAnswer == Reopen ]]; then
sudo -u $currentUser open "$8"
fi
else
let CurrTimer=$Timer-1
echo "User chose to defer."
echo "$CurrTimer" > $appTimer
exit 0
fi
else
echo "No more Deferals Allowed"
sudo -u $currentUser killall "$6"
jamf policy -event "$7"
echo "The install was successfull, removing deferral Timer"
rm -rf $appTimer
reopenAnswer="$(/Library/Application Support/JAMF/alerter -title "$4" -sender "$5" -message "has been updated. Thank you." -closeLabel Ok -actions Reopen -timeout 60)"
if [[ $reopenAnswer == Reopen ]]; then
sudo -u $currentUser open "$8"
fi
fi
else
echo "App is not in use. Continuing with the update."
jamf policy -event "$7"
echo "The install was successfull, removing deferral Timer"
rm -rf $appTimer
exit 0
fi
exit 0
Posted on 04-24-2018 08:07 AM
@FritzsCorner Very cool! Oddly enough, I was working on the same thing yesterday and we came to pretty similar results. One difference I added was a warning once they are out of deferrals so the app doesn't quit on them out of nowhere. I also added a 4 hour timeout on each alert so that other policies would eventually continue if the user never acknowledges.
#!/bin/sh
# If app is open, alert user with the option to quit the app or defer for later. If user chooses to install it will quit the app, trigger the installation,
# then alert the user the policy is complete with the option to reopen the app. If the app is not open it will trigger the installation without alerting
# $4 = Title
# $5 = Sender ID
# $6 = Process Name
# $7 = Jamf Policy Event
# $8 = App Path
## Set up the software update time if it does not exist already
if [ ! -e /Library/Application Support/JAMF/.$5.timer.txt ]; then
echo "2" > /Library/Application Support/JAMF/.$5.timer.txt
fi
## Get the timer value
timer=`cat /Library/Application Support/JAMF/.$5.timer.txt`
# Look if app is open via process name
appOpen="$(pgrep -ix "$6" | wc -l)"
# if the app is open and the defer timer is not zero
if [[ $appOpen -gt 0 && $timer -gt 0 ]]; then
# Identify the username of the logged-in user
currentUser=`python -c 'from SystemConfiguration import SCDynamicStoreCopyConsoleUser; import sys; username = (SCDynamicStoreCopyConsoleUser(None, None, None) or [None])[0]; username = [username,""][username in [u"loginwindow", None, u""]]; sys.stdout.write(username + "
");'`
# Identify the UID of the logged-in user
currentUserUID=`id -u "$currentUser"`
updateAnswer="$(/bin/launchctl asuser "$currentUserUID" /Library/Application Support/JAMF/alerter -title "$4" -sender $5 -message "must be quit in order to update. Save all data before quitting." -closeLabel "Defer ($timer)" -actions "Quit & Update" -timeout 14400)"
if [[ $updateAnswer == "Quit & Update" ]]; then
#quit app, install the update, then prompt the user when complete and ask if they want to reopen the app. Message will time out after 60 secs.
sudo -u $currentUser killall "$6"
jamf policy -event "$7"
reopenAnswer="$(/Library/Application Support/JAMF/alerter -title "$4" -sender "$5" -message "has been updated. Thank you." -closeLabel Ok -actions Reopen -timeout 60)"
if [[ $reopenAnswer == Reopen ]]; then
sudo -u $currentUser open "$8"
fi
#reset timer after updating
echo "2" > /Library/Application Support/JAMF/.$5.timer.txt
else
let CurrTimer=$timer-1
echo "User chose to defer"
echo "$CurrTimer" > /Library/Application Support/JAMF/.$5.timer.txt
echo "$CurrTimer"
exit 0
fi
# if app is open and defer timer has run out
elif [[ $appOpen -gt 0 && $timer == 0 ]]; then
currentUser=`python -c 'from SystemConfiguration import SCDynamicStoreCopyConsoleUser; import sys; username = (SCDynamicStoreCopyConsoleUser(None, None, None) or [None])[0]; username = [username,""][username in [u"loginwindow", None, u""]]; sys.stdout.write(username + "
");'`
# Identify the UID of the logged-in user
currentUserUID=`id -u "$currentUser"`
/bin/launchctl asuser "$currentUserUID" /Library/Application Support/JAMF/alerter -title "$4" -sender $5 -message "must be quit in order to update. Save all data." -actions "Quit & Update" -closeLabel "No Deferrals Left " -timeout 14400
# whether user quits, clicks on no deferrals, or the message times out: quit and update
# quit app, install the update, then prompt the user when complete and ask if they want to reopen the app. Message will time out after 60 secs.
sudo -u $currentUser killall "$6"
jamf policy -event "$7"
reopenAnswer="$(/Library/Application Support/JAMF/alerter -title "$4" -sender "$5" -message "has been updated. Thank you." -closeLabel Ok -actions Reopen -timeout 60)"
if [[ $reopenAnswer == Reopen ]]; then
sudo -u $currentUser open "$8"
fi
#reset timer after updating
echo "2" > /Library/Application Support/JAMF/.$5.timer.txt
else
# app is not open, reset timer and run updates
echo "2" > /Library/Application Support/JAMF/.$5.timer.txt
jamf policy -event "$7"
fi
Posted on 05-04-2018 05:27 AM
I went a bit further in testing and adapting this into a more universal usable script that you can re-use in multiple policies
#!/bin/bash
# $4 = Sender bundle id
# $5 = Notification Title
# $6 = Notification message
# $7 = Cancel button
# $8 = Run event button
# $9 = Custom event to run
currentUser=`python -c 'from SystemConfiguration import SCDynamicStoreCopyConsoleUser; import sys; username = (SCDynamicStoreCopyConsoleUser(None, None, None) or [None])[0]; username = [username,""][username in [u"loginwindow", None, u""]]; sys.stdout.write(username + "
");'`
currentUserUID=`id -u "$currentUser"`
ANSWER="$(/bin/launchctl asuser "$currentUserUID" /Library/Application Support/JAMF/alerter -sender "$4" -title "$5" -message "$6" -closeLabel "$7" -actions "$8" -timeout 600)"
echo "The user answered: $ANSWER"
case $ANSWER in
"@TIMEOUT")
echo "Timeout reached"
;;
"@CONTENTCLICKED")
echo "You clicked the alert's content !"
;;
"$7")
echo "Action cancelled"
;;
"$8")
echo "Action running"
jamf policy -event $9
;;
**)
echo "? --> $ANSWER"
exit 1
;;
esac
exit 0
Creating a policy like this:
Will result in a notification for the user like this:
The custom event mentioned uses this script in my case:
#!/bin/sh
echo "Fullscreen notify"
fsicon="$(mktemp)"
/usr/bin/curl -s -o $fsicon <LINK TO ICON>
fstitle="Installing updates"
fsmessage="Please wait! Do not shut down your computer, this can take up to 10 minutes."
"/Library/Application Support/JAMF/bin/jamfHelper.app/Contents/MacOS/jamfHelper" -windowType fs -heading "${fstitle}" -description "${fsmessage}" -icon ${fsicon} -lockHUD > /dev/null 2>&1 &
UPDATES=$(/usr/sbin/softwareupdate -l)
RestartRequired=$(echo $UPDATES|grep restart)
if [ "$RestartRequired" ];
then
echo "Reboot required"
echo "Updating"
/usr/sbin/softwareupdate -ir
echo "Reconning"
/usr/local/bin/jamf recon
echo "Rebooting"
/usr/local/bin/jamf reboot -background -immediately
exit 0
else
echo "Reboot not required"
echo "Updating"
NoRestartUpdates=$(echo $UPDATES | /usr/bin/grep -v restart | /usr/bin/grep -B1 recommended | /usr/bin/grep -v recommended | /usr/bin/awk '{print $2}' | /usr/bin/awk '{printf "%s ", $0}')
/usr/sbin/softwareupdate -i $NoRestartUpdates
/usr/local/bin/jamf recon
killall jamfHelper > /dev/null 2>&1
fi
Posted on 08-13-2018 02:39 PM
@kendalljjohnson on the google management account how did you create the pkg for alerter? im having trouble creating that.
Posted on 08-13-2018 02:53 PM
@diegogut90 I'm not sure what part your asking about, sorry. Are you referring to the policy that installs the new version of Google or what? Or are you asking about the actual installation of Alerter?
Posted on 08-14-2018 06:24 AM
@kendalljjohnson sorry, the installation of Alerter.
Posted on 08-14-2018 08:05 AM
@diegogut90 Oh, gotcha. Grab the zip from the Alerter Github and then use Composer to package it where you want it. I personally dropped it in /Library/Application Support/JAMF/
but the location only really matters for your script to know where to call it from.
Posted on 10-18-2018 11:33 AM
Realized I never posted what I finally settled on and put in production in case others want to give it a try. Been running with it for a while and seems to be working pretty well! Thanks for all the input and testing from others in the thread.
#!/bin/bash
# If app is open, alert user with the option to quit the app or defer for later. If user chooses to install it will quit the app, trigger the installation,
# then alert the user the policy is complete with the option to reopen the app. If the app is not open it will trigger the installation without alerting
# Quit and Open path have 2 entries for the times you are quiting/uninstalling an old version of an app that is replaced by a new name (for example quiting Adobe Acrobat Pro, which is replaced by Adobe Acorbat.app)
################################DEFINE VARIABLES################################
# $4 = Title
# $5 = App ID
# $6 = Process Name
# $7 = Jamf Policy Event
# $8 = Quit App Path
# $9 = Open App Path
#Defining the Sender ID as self service due to setting the Sender ID as the actual app being updated would often cause the app to crash
sender="com.jamfsoftware.selfservice.mac"
#Jamf parameters can't be passed into a function, redefining the app path to be used within the funciton
quitPath="$8"
openPath="$9"
################################SETUP FUNCTIONS TO CALL################################
fGetCurrenUser (){
currentUser=`python -c 'from SystemConfiguration import SCDynamicStoreCopyConsoleUser; import sys; username = (SCDynamicStoreCopyConsoleUser(None, None, None) or [None])[0]; username = [username,""][username in [u"loginwindow", None, u""]]; sys.stdout.write(username + "
");'`
# Identify the UID of the logged-in user
currentUserUID=`id -u "$currentUser"`
}
fQuitApp (){
cat > /private/tmp/quit_application.sh <<EOF
#!/bin/bash
/bin/launchctl asuser "$currentUserUID" /usr/bin/osascript -e 'tell application "$quitPath" to quit'
EOF
/bin/chmod +x /private/tmp/quit_application.sh
/bin/launchctl asuser "$currentUserUID" sudo -iu "$currentUser" "/private/tmp/quit_application.sh"
/bin/rm -f "/private/tmp/quit_application.sh"
}
fOpenApp (){
cat > /private/tmp/open_application.sh <<EOF
#!/bin/bash
/usr/bin/open "$openPath"
EOF
/bin/chmod +x /private/tmp/open_application.sh
/bin/launchctl asuser "$currentUserUID" sudo -iu "$currentUser" "/private/tmp/open_application.sh"
/bin/rm -f "/private/tmp/open_application.sh"
}
################################SETUP TIMER FILE################################
## Set up the software update time if it does not exist already
if [ ! -e /Library/Application Support/JAMF/.$5.timer.txt ]; then
echo "2" > /Library/Application Support/JAMF/.$5.timer.txt
fi
## Get the timer value
timer=`cat /Library/Application Support/JAMF/.$5.timer.txt`
################################ALERTER MESSAGE OPTIONS################################
saveQuitMSG="must be quit in order to update. Save all data before quitting."
updatedMSG="has been updated. Thank you."
################################START 'UPDATE WITH ALERTER' PROCESS################################
# Look if app is open via process name
appOpen="$(pgrep -ix "$6" | wc -l)"
# if the app is open and the defer timer is not zero
if [[ $appOpen -gt 0 && $timer -gt 0 ]]; then
fGetCurrenUser
updateAnswer="$(/bin/launchctl asuser "$currentUserUID" /Library/Application Support/JAMF/alerter -title "$4" -sender "$sender" -message "$saveQuitMSG" -closeLabel "Defer ($timer)" -actions "Quit & Update" -timeout 3600)"
if [[ $updateAnswer == "Quit & Update" ]]; then
#quit app, install the update, then prompt the user when complete and ask if they want to reopen the app. Message will time out after 60 secs.
fQuitApp
/usr/local/bin/jamf policy -event "$7"
reopenAnswer="$(/bin/launchctl asuser "$currentUserUID" /Library/Application Support/JAMF/alerter -title "$4" -sender "$sender" -message "$updatedMSG" -closeLabel Ok -actions Reopen -timeout 60)"
if [[ $reopenAnswer == Reopen ]]; then
fOpenApp
fi
#reset timer after updating
echo "2" > /Library/Application Support/JAMF/.$5.timer.txt
else
let CurrTimer=$timer-1
echo "User chose to defer"
echo "$CurrTimer" > /Library/Application Support/JAMF/.$5.timer.txt
echo "Defer count is now $CurrTimer"
exit 0
fi
# if app is open and defer timer has run out
elif [[ $appOpen -gt 0 && $timer == 0 ]]; then
fGetCurrenUser
/bin/launchctl asuser "$currentUserUID" /Library/Application Support/JAMF/alerter -title "$4" -sender "$sender" -message "$saveQuitMSG" -actions "Quit & Update" -closeLabel "No Deferrals Left " -timeout 3600
fQuitApp
/usr/local/bin/jamf policy -event "$7"
reopenAnswer="$(/bin/launchctl asuser "$currentUserUID" /Library/Application Support/JAMF/alerter -title "$4" -sender "$sender" -message "$updatedMSG" -closeLabel Ok -actions Reopen -timeout 60)"
if [[ $reopenAnswer == Reopen ]]; then
fOpenApp
fi
#reset timer after updating
echo "2" > /Library/Application Support/JAMF/.$5.timer.txt
else
# app is not open, reset timer and run updates
echo "2" > /Library/Application Support/JAMF/.$5.timer.txt
/usr/local/bin/jamf policy -event "$7"
fi
Posted on 10-18-2018 11:48 AM
@kendalljjohnson Yes! Thank you for sharing this! I've been using this as well and have even adapted this for OS upgrades and it works great! :)
Posted on 10-26-2018 04:23 PM
@kendalljjohnson Thank you for this. I've been giving your script a try using Google Chrome and it seems to work great, but I'm running into an issue when I click on the "Reopen" button after Chrome is updated. Instead of relaunching Chrome, it is opening up the user's home folder in Finder. Any ideas why it is doing that? I copied your final version of the script and the only change I made was updating the path to the Alerter executable.
Posted on 10-26-2018 04:40 PM
Disregard. I guess I needed to define the Open App Path. Thanks again for the script!
Posted on 10-26-2018 06:32 PM
@kendalljjohnson this is excellent and very well thought out and thanks to those who have shared their mods. I can't believe i missed this! i'll be adding this to my workflow for sure and looking to make my own adjustments. this would be great if jamf could adopt something similar into their product
Posted on 11-19-2018 04:54 AM
Can someone explain how the process is suppose to work for this? I tried setting up a test machine to test this out and all I got was a loop of a patch installing. How do you tell the scrip to popup the notification first? How can you test that? I am a bit confused on the workflow aspect.
There is a policy that triggers the install of the package and then the policy that triggers if the app is open. However... that doesn't seem to be working.
Posted on 11-19-2018 09:26 AM
Hey @Kristopher,
There are 2 different policies:
Here are an example of the 2 policies (in reverse order).
And what script variables I use for Google Chrome.
Hope that helps, happy to answer any questions you might have!
12-15-2021 04:34 AM - edited 12-15-2021 05:12 AM
Hi
It’s not working for me. I think I did every step you said, but get an error
Script result: /Library/Application Support/JAMF/tmp/Alerter App Updates: line 60: [: /Library/Application: binary operator expected
cat: Support/JAMF/.com.google.chrome.timer.txt: No such file or directory
Checking for policies triggered by "install_googleChromeUpdate" for user "ursare1"...
Executing Policy Install Latest GoogleChrome
Downloading Chrome_96.0.4664.110.pkg...
Downloading https://repository.example.com/Packages/Chrome_96.0.4664.110.pkg...
Installing Chrome_96.0.4664.110.pkg...
Successfully installed Chrome_96.0.4664.110.pkg.
Running Recon...
Retrieving inventory preferences from https://xxxxxx/...
Finding extension attributes...
Locating accounts...
Locating applications...
Locating package receipts...
Searching path: /System/Applications
Locating hard drive information...
Locating software updates...
Locating printers...
Searching path: /Applications
Locating hardware information (macOS 11.6.1)...
The only difference I have: my alerter is in /usr/local/bin
But I think, that’s not the point.
Any help?
Thanks
PS I found it: a typo from copy paste. Sorry for that
Posted on 02-01-2019 04:49 PM
This is fantastic! Just what I needed for my OS updates. Can you tell me how to get rid of the little tiny terminal icon?
Posted on 02-04-2019 09:11 AM
@bergmire In that example, -appIcon
is set as the black/white circle logo and -sender
is not set so it provides the generic exec icon. If you were to use -sender "com.jamfsoftware.selfservice.mac"
in that same command, the App Icon would remain the same and the exec icon would now be the self service icon. You could not set -appIcon
and just use -sender
and there would be only 1 icon on the left.
Here are some examples that should help clarify:
alerter -title "Test Alert" -sender "com.jamfsoftware.selfservice.mac" -message "Do something." -closeLabel "Later" -actions "Ok"
alerter -title "Test Alert" -sender "com.jamfsoftware.selfservice.mac" -appIcon "/System/Library/CoreServices/Dock.app/Contents/Resources/dashboard.png" -message "Do something." -closeLabel "Later" -actions "Ok"
alerter -title "Test Alert" -appIcon "/System/Library/CoreServices/Dock.app/Contents/Resources/dashboard.png" -message "Do something." -closeLabel "Later" -actions "Ok"
Hope that helps!
Posted on 02-05-2019 03:05 AM
Interesting scipts. Can this somehow be changed to prompts that appear central on the page, as those notifications user simply don´t take care about in my company, as so much information appear in these notifications
Posted on 02-05-2019 04:29 AM
@kendalljjohnson Can you use Alerter to open a URL?
I can get it to work in terminal notifier but not Alerter...
Posted on 02-05-2019 09:47 AM
@Captainamerica Not that I am aware of. It taps into notification center which is always in the top right corner. For alerts in the middle of the screen I would imaged JamfHelper is your best bet.
@myronjoffe Yes, this is definitely possible. The only trick is getting it to run as the logged in user, not the sudo user sent via Jamf. Here's a quick adaptation I did from an alert I was using. My original was used to alert users that Mojave was available in Self Service and if they wanted to go there the open button would launch Self Service. Here's a to open a website:
#!/bin/bash
################################DEFINE USER################################
currentUser=`python -c 'from SystemConfiguration import SCDynamicStoreCopyConsoleUser; import sys; username = (SCDynamicStoreCopyConsoleUser(None, None, None) or [None])[0]; username = [username,""][username in [u"loginwindow", None, u""]]; sys.stdout.write(username + "
");'`
# Identify the UID of the logged-in user
currentUserUID=`id -u "$currentUser"`
################################ALERTER FUNCTION################################
fAlerter ()
{
updateAnswer="$(/bin/launchctl asuser "$currentUserUID" /Library/Application Support/JAMF/alerter -title "Website" -sender "com.jamfsoftware.selfservice.mac" -message "Check out this website!" -closeLabel "Later" -actions "Open Now")"
if [[ "$updateAnswer" == "Open Now" ]]; then
sudo su - $currentUser -c "open https://www.jamf.com"
else
echo "Try again later"
fi
}
fAlerter &
Posted on 03-29-2019 10:00 AM
Hello @kendalljjohnson thank you so much for your effort and time to help us out.
I am trying to understand the workflow so please excuse me if this has been hashed out and I simply missed it.
You have a policy that begins the update process and your larger script above will automatically launch the alerter portion if needed? If not needed, your script will begin the process. I see a your main script listed above but also your alerter script. I am trying to figure out if I need both. Thank you for your patience with me.
Posted on 03-29-2019 10:19 AM
The first policy has a script to check if the app that needs to be updated is running. If it is running, it will prompt for the user to defer or quit the app now to perform the update. If they choose now, it would trigger another policy that actually performs the install. If they defer, it changes the deferral count until the count runs out, so the only option is to update.
If the app is not running, it should call upon the trigger to have the install since the app is not in use.
Hopefully that makes sense, happy to chat through the process if not.
Posted on 03-29-2019 10:22 AM
Thank you @kendalljjohnson, so in a sense, the second script is only for using as an alerter for other purposes. The script you gave us really is all self contained, triggering the actual policy to do the install.