Skip to main content
Solved

Custom Patch Management Workflow


Forum|alt.badge.img+16

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)

Best answer by kendalljjohnson

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 original
Did this topic help you find an answer to your question?

112 replies

Forum|alt.badge.img+1
  • New Contributor
  • 5 replies
  • April 16, 2018

Hi,

nice workflow. But can you explain $5 = ParameterID more in detail? Where do you get the ParameterID?

Thanks, Philipp


Forum|alt.badge.img+7
  • Contributor
  • 52 replies
  • April 16, 2018

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.

Forum|alt.badge.img+11

@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!


Forum|alt.badge.img+7
  • Contributor
  • 52 replies
  • April 17, 2018

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

Forum|alt.badge.img+11

@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

Forum|alt.badge.img+7
  • Contributor
  • 52 replies
  • April 17, 2018

@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!


Forum|alt.badge.img+3

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.


Forum|alt.badge.img+16
  • Author
  • Valued Contributor
  • 105 replies
  • April 17, 2018

@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.


Forum|alt.badge.img+3

@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!


Forum|alt.badge.img+7
  • Contributor
  • 52 replies
  • April 18, 2018

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.


Forum|alt.badge.img+11

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.


Forum|alt.badge.img+7
  • Contributor
  • 52 replies
  • April 18, 2018

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.


Forum|alt.badge.img+16
  • Author
  • Valued Contributor
  • 105 replies
  • April 18, 2018

@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!


mm2270
Forum|alt.badge.img+16
  • Legendary Contributor
  • 7880 replies
  • April 18, 2018

@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.


Forum|alt.badge.img+16
  • Author
  • Valued Contributor
  • 105 replies
  • April 18, 2018

@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

Forum|alt.badge.img+11

@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!


Forum|alt.badge.img+11
  • Valued Contributor
  • 133 replies
  • April 23, 2018

@kendalljjohnson

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

Forum|alt.badge.img+16
  • Author
  • Valued Contributor
  • 105 replies
  • April 24, 2018

@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

Forum|alt.badge.img+7
  • Contributor
  • 52 replies
  • May 4, 2018

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

Forum|alt.badge.img+4
  • Contributor
  • 17 replies
  • August 13, 2018

@kendalljjohnson on the google management account how did you create the pkg for alerter? im having trouble creating that.


Forum|alt.badge.img+16
  • Author
  • Valued Contributor
  • 105 replies
  • August 13, 2018

@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?


Forum|alt.badge.img+4
  • Contributor
  • 17 replies
  • August 14, 2018

@kendalljjohnson sorry, the installation of Alerter.


Forum|alt.badge.img+16
  • Author
  • Valued Contributor
  • 105 replies
  • August 14, 2018

@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.


Forum|alt.badge.img+16
  • Author
  • Valued Contributor
  • 105 replies
  • Answer
  • October 18, 2018

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

Forum|alt.badge.img+11

@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! :)


Cookie policy

We use cookies to enhance and personalize your experience. If you accept you agree to our full cookie policy. Learn more about our cookies.

 
Cookie settings