Skip to main content

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)

this looks pretty slick mac@ibm-notification-tool


Whoa, nice. Playing with it a little and it seems like the notification appears and then hides pretty quickly where our goal would be to have the notification stay on screen. The -timeout option only applies to the Pop-Up option and not the notification banner. I'll definitely keep an eye on this project to see where it goes, thanks for the reference!


cool


Has anyone played around with making this script be able to close multiple apps? For example for Acrobat Reader update you need Reader and Safari to be closed I believe.


Funny - I am having the exact same issue. I guess the key is this one, if there can be build in more applications



Look if app is open via process name



appOpen="$(pgrep -ix "$6" | wc -l)"


Anyone else running into this notarization issue and have a fix? I tried creating a new package for version .004 of alerter and signing the package with our Developer ID and it's still throwing this prompt on 11.4 😞 Thanks!


Hi @kendalljjohnson this looks very useful, going to test it out. Can you post the most recent version of the script you are using with JAMF Helper?

nice work!


Hi @kendalljjohnson this looks very useful, going to test it out. Can you post the most recent version of the script you are using with JAMF Helper?

nice work!


Yeah @tender, no problem. Good opportunity to make sure my comments are up to date!

#!/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

################################DEFINE VARIABLES################################

# $4 = Title
# Example: "Google Chrome"
# $5 = App ID
# Example: "com.google.chrome"
# $6 = Process Name
# Example: "Google Chrome"
# $7 = App Icon Path
# Example: "/Applications/Google Chrome.app/Contents/Resources/app.icns"
# $8 = Jamf Policy Event
# Example: "install_googleChromeUpdate"
# $9 = Quit App Path
# Example: "/Applications/Google Chrome.app"
# $10 = Open App Path
# Example: "/Applications/Google Chrome.app"

#Jamf parameters can't be passed into a function, redefining the app path to be used within the funciton
iconPath="$7"
quitPath="$9"
openPath="${10}"

################################SETUP FUNCTIONS TO CALL################################

fGetCurrenUser (){
currentUser=`/bin/ls -l /dev/console | /usr/bin/awk '{ print $3 }'`

# Identify the UID of the logged-in user
currentUserUID=`id -u "$currentUser"`
}

fCheckAppIconPath (){
#Check if iconPath is still valid, use App Store logo if not found
if [ -f "$iconPath" ]; then
echo "App Icon Path still valid"
else
echo "App Icon not found, use generic logo"
iconPath="/Applications/App Store.app/Contents/Resources/AppIcon.icns"
fi
}

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 ]; then
echo "2" > /Library/Application\\ Support/JAMF/.$5.timer
fi

## Get the timer value
timer=`cat /Library/Application\\ Support/JAMF/.$5.timer`

################################ALERT MESSAGE OPTIONS################################

saveQuitMSG="must be quit in order to update. Save all data before quitting."
updatedMSG="has been updated. Thank you."

################################START 'UPDATE WITH JAMFHELPER' 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
fCheckAppIconPath
updateAnswer="$(/Library/Application\\ Support/JAMF/bin/jamfHelper.app/Contents/MacOS/jamfHelper -windowType utility -windowPosition ur -icon "$iconPath" -iconSize 50 -heading "Update Available" -description "$4 must be quit in order to update. Save all data before quitting." -button1 "Quit & Update" -button2 "Defer ($timer)" -timeout 3600)"
if [[ $updateAnswer == "0" ]]; 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 "$8"
reopenAnswer="$(/Library/Application\\ Support/JAMF/bin/jamfHelper.app/Contents/MacOS/jamfHelper -windowType utility -windowPosition ur -icon "$iconPath" -iconSize 50 -description "$4 has been updated" -button1 "Reopen" -button2 "Ok" -timeout 60)"
if [[ $reopenAnswer == "0" ]]; then
fOpenApp
fi
#reset timer after updating
echo "2" > /Library/Application\\ Support/JAMF/.$5.timer

else
let CurrTimer=$timer-1
echo "User chose to defer"
echo "$CurrTimer" > /Library/Application\\ Support/JAMF/.$5.timer
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
fCheckAppIconPath
/Library/Application\\ Support/JAMF/bin/jamfHelper.app/Contents/MacOS/jamfHelper -windowType utility -windowPosition ur -icon "$iconPath" -iconSize 50 -heading "Update Available" -description "$4 must be quit in order to update. Save all data before quitting." -button1 "Quit & Update" -button2 "No Deferrals Left" -timeout 3600
fQuitApp
/usr/local/bin/jamf policy -event "$8"
reopenAnswer="$(/Library/Application\\ Support/JAMF/bin/jamfHelper.app/Contents/MacOS/jamfHelper -windowType utility -windowPosition ur -icon "$iconPath" -iconSize 50 -description "$4 has been updated" -button1 "Reopen" -button2 "Ok" -timeout 60)"
if [[ $reopenAnswer == "0" ]]; then
fOpenApp
fi
#reset timer after updating
echo "2" > /Library/Application\\ Support/JAMF/.$5.timer

else
# app is not open, reset timer and run updates
echo "2" > /Library/Application\\ Support/JAMF/.$5.timer
/usr/local/bin/jamf policy -event "$8"
fi

 


Yeah @tender, no problem. Good opportunity to make sure my comments are up to date!

#!/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

################################DEFINE VARIABLES################################

# $4 = Title
# Example: "Google Chrome"
# $5 = App ID
# Example: "com.google.chrome"
# $6 = Process Name
# Example: "Google Chrome"
# $7 = App Icon Path
# Example: "/Applications/Google Chrome.app/Contents/Resources/app.icns"
# $8 = Jamf Policy Event
# Example: "install_googleChromeUpdate"
# $9 = Quit App Path
# Example: "/Applications/Google Chrome.app"
# $10 = Open App Path
# Example: "/Applications/Google Chrome.app"

#Jamf parameters can't be passed into a function, redefining the app path to be used within the funciton
iconPath="$7"
quitPath="$9"
openPath="${10}"

################################SETUP FUNCTIONS TO CALL################################

fGetCurrenUser (){
currentUser=`/bin/ls -l /dev/console | /usr/bin/awk '{ print $3 }'`

# Identify the UID of the logged-in user
currentUserUID=`id -u "$currentUser"`
}

fCheckAppIconPath (){
#Check if iconPath is still valid, use App Store logo if not found
if [ -f "$iconPath" ]; then
echo "App Icon Path still valid"
else
echo "App Icon not found, use generic logo"
iconPath="/Applications/App Store.app/Contents/Resources/AppIcon.icns"
fi
}

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 ]; then
echo "2" > /Library/Application\\ Support/JAMF/.$5.timer
fi

## Get the timer value
timer=`cat /Library/Application\\ Support/JAMF/.$5.timer`

################################ALERT MESSAGE OPTIONS################################

saveQuitMSG="must be quit in order to update. Save all data before quitting."
updatedMSG="has been updated. Thank you."

################################START 'UPDATE WITH JAMFHELPER' 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
fCheckAppIconPath
updateAnswer="$(/Library/Application\\ Support/JAMF/bin/jamfHelper.app/Contents/MacOS/jamfHelper -windowType utility -windowPosition ur -icon "$iconPath" -iconSize 50 -heading "Update Available" -description "$4 must be quit in order to update. Save all data before quitting." -button1 "Quit & Update" -button2 "Defer ($timer)" -timeout 3600)"
if [[ $updateAnswer == "0" ]]; 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 "$8"
reopenAnswer="$(/Library/Application\\ Support/JAMF/bin/jamfHelper.app/Contents/MacOS/jamfHelper -windowType utility -windowPosition ur -icon "$iconPath" -iconSize 50 -description "$4 has been updated" -button1 "Reopen" -button2 "Ok" -timeout 60)"
if [[ $reopenAnswer == "0" ]]; then
fOpenApp
fi
#reset timer after updating
echo "2" > /Library/Application\\ Support/JAMF/.$5.timer

else
let CurrTimer=$timer-1
echo "User chose to defer"
echo "$CurrTimer" > /Library/Application\\ Support/JAMF/.$5.timer
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
fCheckAppIconPath
/Library/Application\\ Support/JAMF/bin/jamfHelper.app/Contents/MacOS/jamfHelper -windowType utility -windowPosition ur -icon "$iconPath" -iconSize 50 -heading "Update Available" -description "$4 must be quit in order to update. Save all data before quitting." -button1 "Quit & Update" -button2 "No Deferrals Left" -timeout 3600
fQuitApp
/usr/local/bin/jamf policy -event "$8"
reopenAnswer="$(/Library/Application\\ Support/JAMF/bin/jamfHelper.app/Contents/MacOS/jamfHelper -windowType utility -windowPosition ur -icon "$iconPath" -iconSize 50 -description "$4 has been updated" -button1 "Reopen" -button2 "Ok" -timeout 60)"
if [[ $reopenAnswer == "0" ]]; then
fOpenApp
fi
#reset timer after updating
echo "2" > /Library/Application\\ Support/JAMF/.$5.timer

else
# app is not open, reset timer and run updates
echo "2" > /Library/Application\\ Support/JAMF/.$5.timer
/usr/local/bin/jamf policy -event "$8"
fi

 


Thanks @kendalljjohnson, I am working on adapting this to my needs. Great stuff you put together in your script.

-steven


Hey @Kristopher,



There are 2 different policies:




  1. The Alerter policy
    -This is scoped to a smart group that looks for all computers that have the software installed but not on the desired version
    -If the user chooses to update and not defer, it calls on the other policy that installs the update via a custom policy trigger

  2. The actual updater policy



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!



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

 


Am I correct in understanding that using this alternative workflow would mean that a pending patch would not appear in Self Service.app? For example:

  1. There's a policy to upgrade Chrome, set to run daily, running the script.
  2. Chrome Update Policy runs while Chrome is running.
  3. User gets notification to close Chrome and proceed or defer.
  4. They choose to defer
  5. A couple hours pass, it's lunch time, the user decides they are free to run updates now.
  6. They go to Self Service but since there's no actual patch policy in play, there won't be pending patch installs in Self Service.

Is that correct or am I missing something? The user would only ever have the opportunity to trigger the update when the script runs and asks them?


@allpurposeben Correct, I never built in a logic for Self Service. It would need to be a separate policy created since the alerting policy is only available to be run once a day. Theoretically you could clone the daily alerting policy but change it to be ongoing within Self Service, using the same scope of only available to those not on the current version.


Reply