Custom Patch Management Workflow

kendalljjohnson
Contributor II

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.

28a985a9c48144409183df00b389e93b

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)

1 ACCEPTED SOLUTION

kendalljjohnson
Contributor II

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

View solution in original post

112 REPLIES 112

kendalljjohnson
Contributor II

@mconners

I guess I'm not sure which you mean by second script.

The script I marked as solved from 10/18/18 is the one I'm currently using for this process, replacing the original script posted when I first started this post.

mconners
Valued Contributor

Sorry @kendalljjohnson got it. Sorry, I am slightly off today.

A few posts up, you had a script for the alerter, posted on 2/5/19. That is throwing me off, but I think you intended this to be a stand alone item for working with the alerter function. Maybe I am totally off base and the way my day has gone, I am not surprised.

kendalljjohnson
Contributor II

@mconners Ah, yes. That specific script is a quick stand-alone script that will prompt the user to either open the website or defer in response to a specific question about launching a website using Alerter.

mconners
Valued Contributor

Thank you @kendalljjohnson I should be good to go for testing. Thanks for your help, I sincerely appreciate it.

mconners
Valued Contributor

Hey @kendalljjohnson I hate to bother you again on this. Please pardon my interruption. I am new to the entire scripting thing and I am just now working through many different workflows where I will be using them often. This gives me a chance to learn how to use them and be more efficient in my processes.

215a01c78e3243ca95f571e62077603b

I see in your example above, your script parameters in the policy are showing up as Title, App ID and so forth. When I load the script into my policy, I am not seeing this. Is there something special I need to do for the display to show up this way? All I am seeing is Parameter 1, Parameter 2 and so on.

a43e0c7f04954630af1eda74c48667f6

Again, thank you for helping me understand this better.

kendalljjohnson
Contributor II

@mconners No prob. In your Jamf Pro Settings, go to the script you uploaded and navigate to the "Options" section. That is where you can put in your own custom labels for the parameters 4-11 you use in the script.

mm2270
Legendary Contributor III

@mconners You need to go back to the script itself, go into Edit mode, and then on the Options tab add custom labels for each parameter. Once you do that and save it, when you go back into your policy that uses that script it will show those custom labels for the parameters instead of the default ones.

mconners
Valued Contributor

Thank you both, @kendalljjohnson and @mm2270 that was almost too easy!! Sorry I didn't even look there, this is very helpful. Enjoy your afternoon's.

myronjoffe
Contributor III

:(

Apple Seed 10.15 Alerter post

If anyone has some feedback would be much appreciated...

kendalljjohnson
Contributor II

@myronjoffe From what I can tell, Alerter won't launch when calling it from a non-Apple BundleID. When you do call it from an Apple BundleID, for example com.apple.package, it will launch but just show the generic exec icon. Definitely not as visually appealing but will need to play and see what options we have.

Unfortunately, this is the downside of using a 3rd party tool like this (specially one that hasn't been touched in 4 years), Apple can easily cut the legs out on it at any time. Hoping to find a solution based on how much we utilize it!

myronjoffe
Contributor III

@kendalljjohnson Thanks for the tip. I have also raised an issue with the dev on Github.

tlarkin
Honored Contributor

FWIW, I have built similar flows in the past and have a static set of Python code that does this. It leverages NSRunningApplication to check if apps are running by Bundle ID, and quits or force quits them in the same fashion. I stopped using PIDs and things like pgrep due to false positives from things like helpers and user agents, example below with Safari:

bash-3.2$ pgrep Safari
1017
5181
5192
5193
13812
bash-3.2$ ps -ef | grep Safari
  502  1017     1   0 Fri09AM ??         0:18.25 /System/Library/CoreServices/SafariSupport.bundle/Contents/MacOS/SafariBookmarksSyncAgent
  502  5181     1   0 Fri03PM ??         0:00.17 /System/Library/PrivateFrameworks/SafariShared.framework/Versions/A/XPCServices/com.apple.Safari.History.xpc/Contents/MacOS/com.apple.Safari.History
  502  5192     1   0 Fri03PM ??         0:00.24 /System/Library/Frameworks/SafariServices.framework/Versions/A/XPCServices/com.apple.SafariServices.ExtensionHelper.xpc/Contents/MacOS/com.apple.SafariServices.ExtensionHelper
  502  5193     1   0 Fri03PM ??         0:24.93 /System/Library/PrivateFrameworks/SafariSafeBrowsing.framework/com.apple.Safari.SafeBrowsing.Service
  502 11307   603   0 11:50AM ??        17:26.88 /Applications/Slack.app/Contents/Frameworks/Slack Helper.app/Contents/MacOS/Slack Helper --type=renderer --autoplay-policy=no-user-gesture-required --force-color-profile=srgb --enable-features=SharedArrayBuffer --disable-features=MacV2Sandbox --service-pipe-token=4643921532856445533 --lang=en-US --standard-schemes=slack-resources,slack-sounds,slack-webapp-dev --secure-schemes=slack-resources,slack-sounds,slack-webapp-dev --app-path=/Applications/Slack.app/Contents/Resources/app.asar --user-agent=Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Slack/4.0.2 Chrome/69.0.3497.128 Electron/4.2.8 Safari/537.36 Sonic Slack_SSB/4.0.2 --node-integration=false --webview-tag=false --no-sandbox --preload=/Applications/Slack.app/Contents/Resources/app.asar/dist/ssb-interop.bundle.js --background-color=#FFFFFF --num-raster-threads=4 --enable-zero-copy --enable-gpu-memory-buffer-compositor-resources --enable-main-frame-before-activation --service-request-channel-token=4643921532856445533 --renderer-client-id=9 {"preloadEnvironment":{"resourcePath":"/Applications/Slack.app/Contents/Resources/app.asar","isSonic":true,"appVersion":"4.0.2","teamId":"app","windowType":"main"},"crashReporterOpts":{"productName":"Slack","companyName":"Slack Technologies","uploadToServer":true,"submitURL":"https://slack.com/apps/breakpad?instanceUid=5f45913c-5ad7-51ff-9f92-3d6112838729","extra":{"instanceUid":"5f45913c-5ad7-51ff-9f92-3d6112838729","sessionId":"NWY0NTkxM2MtNWFkNy01MWZmLTlmOTItM2Q2MTEyODM4NzI5XzE1NjgzOTI0MDkyMDQ="}},"perfTimer":{"mainPid":603,"BOOT":[28,892285882],"numTeamsAtLaunch":3,"SHELL":[30,106792404],"APP_READY":[30,185617956],"APP_CREATED":[30,496675498],"MAIN_WINDOW_CREATING":[30,529137517]},"identifier":"slack_preload_metadata_arguments"}
  502 13812     1   0  3:32PM ??         0:00.08 /System/Library/Frameworks/SafariServices.framework/Versions/A/XPCServices/com.apple.SafariServices.xpc/Contents/MacOS/com.apple.SafariServices
  502 19396  7741   0  9:50AM ttys000    0:00.00 grep Safari

Note, I do not have Safari running at all, and Slack also lists Mozilla as a user agent, so it would give you false positives on Firefox, and I just basically bolted messaging on top of jamf helper, example:

dd21e64fb0bd416dacfc260423bad9d5
5244c68019894faa85928c638925905b

Auto Update Repo is where my code is stored, maybe it will be useful to some of you.

myronjoffe
Contributor III

@tlarkin Love your work and the jamf helper looks great in dark mode but prefer the native notification feel from Alerter.

tlarkin
Honored Contributor

@myronjoffe yup many ways to do it, I have found that dialog boxes tend to get actually used more than notification center pops. Most of the people at my Org respond best to Slack, average to pop ups, and they ignore NC pretty much.

So, we are actually looking at getting rid of desktop notifications and go into Slackbot DMs and responses instead.

myronjoffe
Contributor III

That sounds awesome and very original. Would love to see a write up or presentation on that one day 😃

tlarkin
Honored Contributor

we are just now staring the planning process of getting data pipelines into our product, build an Ops platform on top of it, and one of the integrations will be Slackbot and we also have a help desk AI product, that we are integrating into Slack as well. If all goes well maybe in 2020 we might be looking to share/present some of this stuff. :-)

federico_joly
New Contributor II

@kendalljjohnson Thanks for the script and workflow, first of all!

Now, I've done small changes basically in displaying texts and been testing this locally from my coding app or from command line. It works perfectly. Of course I had to create local variables with static values to replace jamf script variables ($4, $5, etc).

However, when I put back jamf variables, put it in jamf and run the policy, it doesn't work. I calls the update policy regardless of Chrome being open or not without any alert.

I also made a version with 'osascript' instead of 'alerter' to see if that was the issue but still no luck. (In my company having to use an external piece of software has to run through a long security process which is basically a pain in the a...)

I might be missing something when translating variables...
I'll post my scripts in different posts for clear reading I guess.
Thank you!

federico_joly
New Contributor II

-Erased-

federico_joly
New Contributor II

LOCAL VERSION:

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

title="Critical update"
subTitle="Google Chrome"
appID="com.google.com"
procName="Google Chrome"
policyID="install_GoogleChromeUpdate"
quitAppPath="/Applications/Google Chrome.app"
openAppPath="/Applications/Google Chrome.app"

#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="$quitAppPath"
openPath="$openAppPath"

################################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/$appID.timer.txt ]; then
  echo "3" > /Library/Application Support/JAMF/$appID.timer.txt
fi

## Get the timer value
timer=`cat /Library/Application Support/JAMF/$appID.timer.txt`

################################ALERTER MESSAGE OPTIONS################################

saveQuitMSG="It must quit to be updated. Save all data before quitting."
updatedMSG="It has been updated. Thank you."
lastMSG="Please save your work and close $subTitle."

################################START 'UPDATE WITH ALERTER' PROCESS################################

# Look if app is open via process name
appOpen="$(pgrep -ix "$procName" | 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 -sender "$sender" -title "$title: $subTitle" -message "$saveQuitMSG" -closeLabel "Defer ($timer)" -actions "Close & Update" -timeout 3600)"
    if [[ $updateAnswer == "Close & 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 "$policyID"
        reopenAnswer="$(/bin/launchctl asuser "$currentUserUID" /Library/Application Support/JAMF/alerter -sender "$sender" -title "$title: $subTitle" -message "$updatedMSG" -closeLabel Ok -actions Reopen -timeout 60)"
        if [[ $reopenAnswer == Reopen ]]; then
            fOpenApp
        fi
        #reset timer after updating
        echo "3" > /Library/Application Support/JAMF/$appID.timer.txt

    else
        let CurrTimer=$timer-1
        echo "User chose to defer"
        echo "$CurrTimer" > /Library/Application Support/JAMF/$appID.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 -sender "$sender" -title "$title" -message "$lastMSG" -actions "Close & Update" -closeLabel "No Deferrals Left " -timeout 3600
    fQuitApp

    /usr/local/bin/jamf policy -event "$policyID"
    reopenAnswer="$(/bin/launchctl asuser "$currentUserUID" /Library/Application Support/JAMF/alerter -sender "$sender" -title "$title: $subTitle" -message "$updatedMSG" -closeLabel Ok -actions Reopen -timeout 60)"
    if [[ $reopenAnswer == Reopen ]]; then
        fOpenApp
    fi
    #reset timer after updating
    echo "3" > /Library/Application Support/JAMF/$appID.timer.txt

else
    # app is not open, reset timer and run updates
    echo "3" > /Library/Application Support/JAMF/$appID.timer.txt
    /usr/local/bin/jamf policy -event "$policyID"
fi

federico_joly
New Contributor II

JAMF VERSION:

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

#title="Critical update"
#subTitle="Google Chrome"
#appID="com.google.com"
#procName="Google Chrome"
#policyID="install_GoogleChromeUpdate"
#quitAppPath="/Applications/Google Chrome.app"
#openAppPath="/Applications/Google Chrome.app"

#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/$appID.timer.txt ]; then
  echo "3" > /Library/Application Support/JAMF/$appID.timer.txt
fi

## Get the timer value
timer=`cat /Library/Application Support/JAMF/$appID.timer.txt`

################################ALERTER MESSAGE OPTIONS################################

saveQuitMSG="It must quit to be updated. Save all data before quitting."
updatedMSG="It has been updated. Thank you."
lastMSG="Please save your work and close $5."

################################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 -sender "$sender" -title "$4: $5" -message "$saveQuitMSG" -closeLabel "Defer ($timer)" -actions "Close & Update" -timeout 3600)"
    if [[ $updateAnswer == "Close & 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 -sender "$sender" -title "$4: $5" -message "$updatedMSG" -closeLabel Ok -actions Reopen -timeout 60)"
        if [[ $reopenAnswer == Reopen ]]; then
            fOpenApp
        fi
        #reset timer after updating
        echo "3" > /Library/Application Support/JAMF/$appID.timer.txt

    else
        let CurrTimer=$timer-1
        echo "User chose to defer"
        echo "$CurrTimer" > /Library/Application Support/JAMF/$appID.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 -sender "$sender" -title "$4" -message "$lastMSG" -actions "Close & 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 -sender "$sender" -title "$4: $5" -message "$updatedMSG" -closeLabel Ok -actions Reopen -timeout 60)"
    if [[ $reopenAnswer == Reopen ]]; then
        fOpenApp
    fi
    #reset timer after updating
    echo "3" > /Library/Application Support/JAMF/$appID.timer.txt

else
    # app is not open, reset timer and run updates
    echo "3" > /Library/Application Support/JAMF/$appID.timer.txt
    /usr/local/bin/jamf policy -event "$7"
fi

kendalljjohnson
Contributor II

@federico.joly Hmm, not sure exactly where the issue would fall.

Without being able to test your exact process or changes, what I might suggest is adding in lines to echo out comments of each step in the process, including echoing out the variables. This might help see if it is not properly interpreting the variables, where things are getting stuck, or what actions might be getting skipped.

For example, quick lines such as echo "Process Name: $procName" right before it is supposed to check what process is running and echo "App open: $appOpen" to see if it is properly detecting the app. After running this you can then look in your Jamf logs to see what isn't translating correctly.

Hope that helps!

federico_joly
New Contributor II

@kendalljjohnson thanks again!

I've been going around this for about a week and I had "Google Chorme" instead of "Google Chrome" in my policy, on the script parameters...

Sure, you all can call me names...

:'D

rqomsiya
Contributor III

Has anyone had any luck with Alterer and Catalina?

kendalljjohnson
Contributor II

@rqomsiya Yes, you have to change the App calling alerter (sender) to be an Apple App, not 3rd party like Self Service.

I am using the App Store, com.apple.AppStore, just so it shows the generic App Store logo, seemed the most generic that could apply to a broad spectrum of apps that might be notified for.

rqomsiya
Contributor III

Thanks @kendalljjohnson ill give that a shot!

HeyWhosTheMacGu
New Contributor II

Comment Removed (because I'm an idiot and now realize it)

Captainamerica
Contributor II

Can someone try and show an working example for Catalina (with jamf)

I have the following that does not work and gives this error

pgrep: Cannot compile regular expression `' (empty (sub)expression)

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


title="Critical update"
subTitle="Illustator"
appID="com.adobe.photoshop"
procName="Adobe Photoshop"
policyID="photoshopupdate"
quitAppPath="/Applications/Adobe Photoshop 2020/Adobe Photoshop 2020.app"
openAppPath="/Applications/Adobe Photoshop 2020/Adobe Photoshop 2020.app"

#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/$appID.timer.txt ]; then
    echo "3" > /Library/Application Support/JAMF/$appID.timer.txt
fi

## Get the timer value
timer=`cat /Library/Application Support/JAMF/$appID.timer.txt`

################################ALERTER MESSAGE OPTIONS################################

saveQuitMSG="It must quit to be updated. Save all data before quitting."
updatedMSG="It has been updated. Thank you."
lastMSG="Please save your work and close $5."

################################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 -sender "$sender" -title "$4: $5" -message "$saveQuitMSG" -closeLabel "Defer ($timer)" -actions "Close & Update" -timeout 3600)"
        if [[ $updateAnswer == "Close & 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 -sender "$sender" -title "$4: $5" -message "$updatedMSG" -closeLabel Ok -actions Reopen -timeout 60)"
                if [[ $reopenAnswer == Reopen ]]; then
                        fOpenApp
                fi
                #reset timer after updating
                echo "3" > /Library/Application Support/JAMF/$appID.timer.txt

        else
                let CurrTimer=$timer-1
                echo "User chose to defer"
                echo "$CurrTimer" > /Library/Application Support/JAMF/$appID.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 -sender "$sender" -title "$4" -message "$lastMSG" -actions "Close & 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 -sender "$sender" -title "$4: $5" -message "$updatedMSG" -closeLabel Ok -actions Reopen -timeout 60)"
        if [[ $reopenAnswer == Reopen ]]; then
                fOpenApp
        fi
        #reset timer after updating
        echo "3" > /Library/Application Support/JAMF/$appID.timer.txt

else
        # app is not open, reset timer and run updates
        echo "3" > /Library/Application Support/JAMF/$appID.timer.txt
        /usr/local/bin/jamf policy -event "$7"
fi

sdagley
Esteemed Contributor II

@Captainamerica Don't quote the appOpen assignment. You should just have

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

And to be pedantic, using backticks (`) for command substitution is considered a legacy usage, the recommended method is to use $(...).

e.g.

timer=`cat /Library/Application Support/JAMF/$appID.timer.txt`

should be

timer=$(cat /Library/Application Support/JAMF/$appID.timer.txt)

See SC2006 for more detail.

Captainamerica
Contributor II

I managed to get it working

However, have 2 additional questions

If no more deferrals left the message appear. Is it possible to set a time limit, as it seems users can just ignore the message and nothing then will happen(of course this notification will stay there, but some users may just ignore this)

And 2nd question. As I can see it is only possible to have 1 policy running at a time (as far I have testet). So if I would push out a policy to photoshop, illustrator, indesign, I cannot do it in once, but I have to wait for each to finish before I can relaunch a new policy. Is it possible in the paramters to add more application so like $6 have photoshop; illustrator; indesign somehow ?

kendalljjohnson
Contributor II

@Captainamerica

-Alerter has a timeout option, -timeout, that can be used. Then you just use the if/else logic of if the alerter notification ends and the displayed options are selected then it performs the action you want.

-Never played with multiple instances at once. You could try adding & at the end of a command to tell the script to keep going, but I don't believe it would then be able to call an action back to Jamf after the script has been ended. Alternatively, you could potentially setup an array logic to cycle through multiple app titles, but that would probably take some reworking of the overall workflow and process within the policies.

Captainamerica
Contributor II

Thanks.

Is there any possibility to have 2 policies running at same time but with different applications to close down ? Would also solve my problem and maybe more easy to set up ?

kendalljjohnson
Contributor II

@Captainamerica If you're referring to 2 policies from Jamf running at once on a machine, I don't believe that is possible. Multiple actions can happen within a single policy, but not simultaneously. Only method I can think of would be to locally store the script on the machine, but you might have to have a separate one for each app and fill in the variables of app titles. Then the policy could call on multiple of the local scripts, allowing Jamf to not be dependent upon the completion. You would still run into the "limitation" that only one policy can run at once, so if you have 2 different scripts attempting to run a jamf policy -event simultaneously one would fail. Maybe I'm missing another work around but that's what comes to mind right now.

Captainamerica
Contributor II

I used the script and it works fine for adobe photoshop.
I used nearly 2 days now and I cannot get this working for Indesign and Illustrator for some reason, even I compare them the notification does not simply show up, even the application is open - so for some reason it does not work. The policy executes as was the application not running, but it is

It sounds really crazy as I can get both photoshop and also chrome working without issues

4fe0348e53784593b1ca97eafac4d34a

7ca241f37c55408db0358bf92e037aba

Rikky
New Contributor II

Adobe has dropped the 'CC' in the folder for Illustrator. The correct path would be '/Applications/Adobe Illustrator 2020/Adobe Illustrator.app' I think it's the same for InDesign.

Captainamerica
Contributor II

@kendalljjohnson Can you try and post the current working scipt for Catalina

It is really driving me nuts right now. It worked so well for me to start with and even I tried start from scratch again it only shows the below picture when the policy is running - so there is no defer or quit option anymore, no matter which application I try with. So strange

531cbec071574e568986643f3fe7dc1d

Rikky
New Contributor II

Have you created a notification profile for Alerter (I used ProfileCreator) App bundle identifier is fr.vjeantet.alerter. I think it needs to be alert style banner

Captainamerica
Contributor II

Well if it can show the banner it should work I think ?
Do you have a running config that works for Catalina ? - somewhere there must be a mistake in my since it does not show quit/defer option anymore, but just the picture in my post before

Rikky
New Contributor II

Make sure the 'Later -actions "Quit & Update"' is correct in your script.

    updateAnswer="$(/bin/launchctl asuser "$currentUserUID" /usr/local/bin/alerter -title "$4" -sender com.jamfsoftware.selfservice.mac -message "Update Required. Please save your work and close the application." -closeLabel Later -actions "Quit & Update" )"

Captainamerica
Contributor II

This is the script

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

kendalljjohnson
Contributor II

@Captainamerica

Not sure what is going on, I just took your exact script and ran it locally while hard coding the $4-$9 variables and it worked as expected. Are you running it via Jamf or have you tried running it locally and seen if any errors are called out? If it has only been from within Jamf, perhaps check your variable usage? Here's what I used for mine in the test just now, with Chrome:

title="Google Chrome"
appID="com.google.chrome"
process="Google Chrome"
policy="install_googleChromeUpdate"
openpath1="/Applications/Google Chrome.app"
quitpath1="/Applications/Google Chrome.app"

b25be6fb76844b6080e8e81e835cf1f0