Applications / Process Killer Script

cainehorr
Contributor III

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!

Kind regards,

Caine Hörr

A reboot a day keeps the admin away!

8 REPLIES 8

cainehorr
Contributor III

Thanks to my peeps over at MacBrained, I'm now seeing the "bug" that was pointed out...

@niklas.blomdalen

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.

Kind regards,

Caine Hörr

A reboot a day keeps the admin away!

sanbornc
New Contributor III

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

tcandela
Valued Contributor II

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

shaquir
Contributor III

tcandela
Valued Contributor II

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.

tlarkin
Honored Contributor

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

donmontalvo
Esteemed Contributor III

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
--
https://donmontalvo.com

tlarkin
Honored Contributor

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