Posted on 07-09-2019 08:14 PM
Sometimes when you're deploying software updates and upgrades, you need to identify if the application in question that is being updated and/or upgraded is running and if it is, sometimes you need to quit it.
Sometimes, some applications that you are trying to update and/or upgrade are just being a little too persnickety and the "gentle" method of "quitting cleanly" either isn't an option, or it's just too much of a hassle.
With that, allow me to introduce my latest pre-flight script I affectionately call the "App Process Killer".
#!/bin/bash
##############################################################################
#
# SCRIPT FILENAME:
# AppProcessKiller.sh
#
#
# SCRIPT SYNTAX AND EXAMPLE:
# Syntax: sudo AppProcessKiller.sh [AppName]
#
# Examples
# sudo AppProcessKiller.sh "Microsoft Office"
# sudo AppProcessKiller.sh zoom.us
# sudo AppProcessKiller.sh calculator
#
#
# DESCRIPTION:
# Use for killing an app or process with extreme prejudice.
# Works on command line via $1 or within Jamf Pro via $4.
#
# Jamf Variables
# https://www.jamf.com/jamf-nation/articles/146/script-parameters
# $1 - Predefined Variable - Mount point of the target drive
# $2 - Predefined Variable - Computer name
# $3 - Predefined Variable - Username
# $4 - Custom Variable - Okta Device Trust Version Number
#
# USE CASE:
# Use as a pre-flight script prior to software updates and upgrades, etc.
#
#
# CHANGE LOG:
# v1.0 - 2019-07-09
# Written by Caine Hörr.
# * Initial Script Creation
#
##############################################################################
##############################################################################
#
# CHOOSE YOUR RUN MODE
appName="$(/bin/echo ${1})" # Run Mode: Command Line
# appName="$(/bin/echo ${4})" # Run Mode: Jamf Pro
#
##############################################################################
##############################################################################
#
# THERE ARE NO USER SERVICEABLE PARTS HERE...
# DO NOT EDIT BELOW THIS LINE!
#
##############################################################################
# Confirm the existence of the command line argument
if [ -z "${appName}" ]; then
echo ""
echo "ERROR: Missing Argument"
echo ""
echo "SYNTAX: sudo ${0} [appName]"
echo ""
echo " EXAMPLES:"
echo " sudo ${0} "Microsoft Office""
echo " sudo ${0} zoom.us"
echo ""
exit 1
fi
main(){
Run_as_Root
Process_Killer
}
Run_as_Root(){
# Check for admin/root permissions
if [ "$(id -u)" != "0" ]; then
echo ""
echo "ERROR: Script must be run as root or with sudo."
echo ""
exit 1
fi
}
Process_Killer(){
processCheck=$(pgrep -i ${appName})
if [ "${processCheck}" = "" ]; then
echo "${appName} is not running..."
else
echo "${appName} is running..."
echo "${appName} Process ID: ${processCheck}"
echo "Quitting ${appName}..."
sudo kill -9 ${processCheck}
fi
}
main
exit
As you can see, the script is fairly straight forward.
The syntax is as simple as this...
sudo .AppProcessKiller.sh AppName
You can run it on the command line or from within Jamf. Just edit the "run mode" portion of the script and you're good to go.
When running from command line, the script reads in $1 - That's the app or process name you want to kill.
When running from Jamf, you need to set up your $4 variable in Settings > Computer Management > Scripts.
In your policy, you identify the name of the app or process name your want to kill.
Set your script to run "Before" and then your policy can install the update/upgrade you require.
Here's some sample output of the script in action:
$ sudo ./AppProcessKiller.sh Calculator
Password:
Calculator is running...
Calculator Process ID: 3369
Quitting Calculator...
Let's see that again, but this time, Calculator isn't running...
$ sudo ./AppProcessKiller.sh Calculator
Calculator is not running...
If the script is run without the correct syntax, you see the following:
$ ./AppProcessKiller.sh Calculator
ERROR: Script must be run as root or with sudo.
OR
$ sudo ./AppProcessKiller.sh
ERROR: Missing Argument
SYNTAX: sudo ./AppProcessKiller.sh [appName]
EXAMPLES:
sudo ./AppProcessKiller.sh "Microsoft Office"
sudo ./AppProcessKiller.sh zoom.us
These messages will also be reflected in your Jamf policy logs so as to assist you in troubleshooting any problems you may have.
Anyway - This script comes in handy for some of my needs - hopefully it will serve you well too!
Cheers!
Caine Hörr
A reboot a day keeps the admin away!
Posted on 07-25-2019 10:33 AM
Thanks to my peeps over at MacBrained, I'm now seeing the "bug" that was pointed out...
Might miss multiple instances of an app. Seems to only kill the first one it finds
Thomas Larkin
if apps spawn multiple PIDs or have helpers this can happen, Safari is a prime example $ pgrep Safari 376 18934…See More VPN clients will also sometimes spawn a helper process I have noticed, I have PyOjC code I use to kill apps by bundle ID that I am using. Sandboxing can also tribute to this if I recall to work around this you might want to use SIGTERM instead of SIGKILL, but my initial testing same results
I'm seeing it specifically with Google Chrome during updates on some systems.
When the script runs, it finds all the matching processes, but if some of the processes go MIA while the script is running, it spits out some errors and the script exits with "Error Code Status 1".
Here's a sample from my Jamf policy log...
Executing Policy PATCH - Google Chrome 75.0.3770.142
Running script AppProcessKiller.sh...
Script exit code: 1
Script result: Google Chrome is running...
Google Chrome Process ID: 444
997
1009
1013
1014
1015
1016
1017
1028
8234
9761
10214
10937
11034
11041
11302
11315
11330
11340
11341
11367
Quiting Google Chrome...
kill: 1016: No such process
kill: 1028: No such process
kill: 8234: No such process
kill: 10214: No such process
kill: 11034: No such process
kill: 11315: No such process
kill: 11330: No such process
kill: 11340: No such process
kill: 11341: No such process
kill: 11367: No such process
Error running script: return code was 1.
I'm looking into this now so we can make it more robust.
Caine Hörr
A reboot a day keeps the admin away!
Posted on 02-20-2020 11:41 AM
@caine.horr Any updated to this? Im looking for a method to quit all Microsoft Apps prior to a Office-wide app upgrade Im installing from cache. Would this work? Any feedback on the bug you mentioned?
Posted on 04-03-2020 07:13 PM
@cainehorr so if i use $4 for the JAMF Pro run mode to check for Zoom running it would be zoom.us? not just zoom?
i don't have a mac in front of me to check.
basically what i want to do is update the Zoom app if the user is not currently using it, if they are to just exit.
if running the script via JAMF policy is that Run_as_Root function even necessary? i would think not.
Posted on 04-04-2020 07:24 AM
This is one of the cleanest methods I've seen for closing apps.
Posted on 05-04-2020 11:13 AM
I am using this script to check to see if zoom.us process is running and if so perform a certain task. I am using $4 in the policy and entered zoom.us, it is working very good.
I now want to use this same script to check if Adobe Acrobat Reader DC is running and if so also perform a certain task. In $4 i entered Adobe Acrobat Reader DC (just like i see it in the Jamf Application Usage screen for computers.). When the script runs I am always getting that the process is running. Am i supposed to enclose the application in " " when putting it in the $4 section of the script? Is this the correct process name? or is it Acrobat Reader?
When i ran activity monitor to check, the process name said 'Acrobat Reader'. The inventory data for a computer listed Adobe Acrobat Reader DC in the application usage log.
I tried both ways, and they both show that the application is running. Here is a sample result from echo's in the script
Script result: Adobe Acrobat Reader DC is running...
Adobe Acrobat Reader DC Process ID: 165
212
214
429
Quitting; Adobe Acrobat Reader DC not upgraded.
Posted on 05-04-2020 03:15 PM
PIDs can be tricky, you can tell binaries like pgrep
to only match the exact process name, and it will typically return the parent process PID. Your results might vary, but here is an example:
% pgrep Safari
1503
3820
13406
13508
13516
13520
40951
42179
65882
65945
70150
% pgrep -x Safari
65882
You can also look at the ObjC bridge in Python and use something like NSRunningApplication
and the app's bundle ID, which is what I do in my auto update framework. So you have options to be more precise and exact.
So look at maybe using pgrep -x
or a different method since with all the OS changes, sandboxing, and apps having things like agents/helpers you can get false positives or return many PIDs. This is basically why I decided to use bundle ID instead
Posted on 05-04-2020 05:32 PM
If time isn't a factor, and you want to avoid the dreaded "IT made me lose my work, so I missed my deadline!" escalations, there's another approach. Check if the process is running, if so exit and try again tomorrow (once a day policy). Using OmniGraffle Pro as an example...
#!/bin/bash
APPLICATION="/Applications/OmniGraffle.app"
PROCESS=$(ps aux | grep -v grep | grep -ci "OmniGraffle")
if [ -e "$APPLICATION" ]
then
echo "$APPLICATION exists."
if [[ "$PROCESS" -ge "1" ]]
then
echo "$APPLICATION is running, exiting."
exit 1
else
echo "$APPLICATION is not running, proceeding with update."
fi
else
echo "$APPLICATION does not exist, exiting."
exit 1
fi
Posted on 05-08-2020 11:04 AM
I have two different pyobjc scripts I use for updates, one is a silent update one is a prompt, which use bundle ID to check if a process is running. You might find them useful, here is my github