A script that will prompt a user to close all apps before running a policy

yr_joelbruner
New Contributor III

Here's a script that will pop up an Applescript window asking a user to close apps before running a policy, it will attempt to close them via Applescript Quit calls, failing that, loops back and asks again, letting the user close them manually, in addition if the user clicks Cancel, this script will kill the policy so that it will not run.

To use the script, upload it to the JSS, have it run Before, supply the names of the apps that must be closed. You can put in "Microsoft" and ALL processes with that in the title will be matched, so all partial strings match. The downside might be for things like iTunes, which has an iTunesHelper that is not easily Quit-able by the user, so you might want to tweak the pattern matching. Me I just make them logout to run Apple Updates. For the most part it's a pretty useful script, for things like plugins where you want to ensure the user has quit the app first, so the plugin is loaded.

I've found great use in this script and I hope you do to. I only ask in return that you look at the Feature Requests I've added ("Receipt versions" and "SelfService URLs") and vote them up if you think they sound useful ;)

Thanks,
Joel
brunerd.com/blog (some useful scripts there too)

########## closeApps.command ###########
#!/bin/bash
# closeApps.command
# Author: Joel Bruner

###################
# Functions
###################
function sendlog
{
#system.log
logger "[$$] $@"
#seperate update log
echo "$@"
}

function runOSA
{
osascript 2>/dev/null<<-EOF
with timeout of 1000000 seconds
$@
end timeout
EOF
}

function getProcs
{
for (( i=0; i<${#myArgs[@]}; i++ )); do
runOSA 'tell application "System Events" to get name of every process whose name contains "'${myArgs[$i]}'"'
done
}

function checkSystem
{
#sendlog "Running $scriptName, parameters: "$1"";
# check to make sure we are running as root
if [ ! $(id -u) -eq 0 ]; then sendlog "Must be run as root, exiting." exit 1;
fi

#is this running in a policy?
#if so the argument list will need to be shifted 3
echo "$(ps auxww)" | grep -q "[j]amf policy -id"
result=$?

[ $result -eq 0 ] && jamfScript=1
[ $result -eq 1 ] && jamfScript=0
}

#for troubleshooting
function printProcArray
{
for (( i=0; i<${#procArray[@]}; i++ )); do
sendlog Process $i: ${procArray[$i]}
done
}

function parseArgs
{
#Shift forward 3 so to skip first 3 JAMF arguments
if [ $jamfScript -eq 1 ]; then
shift 3
fi

if [ -z "$1" ]; then
sendlog "No arguments, exiting"
exit 1;
fi

#make myArgs array
for arg in ${@}; do myArgs[${i:=0}]=$arg
let i++;
done
}

function makeProcList
{
#make array of processes running
procArray=( $(for proc in $(getProcs); do echo $proc | sed 's/^ //g'; done) )

procString=""; ##reset procString
#make display formatted string with commas
for (( i=0; i<${#procArray[@]}; i++ )); do if [ -z "$procString" ]; then procString=${procArray[$i]} else procString="$procString, ${procArray[$i]}" fi
done

procList='' ## reset procList
#make applescript formatted list to loop through
#format is {"This", "That", "Other"}, do quotes fist curly braces at end
for (( i=0; i<${#procArray[@]}; i++ )); do if [ -z "$procList" ]; then procList="${procArray[$i]}" else procList="$procList, "${procArray[$i]}"" fi
done
#add curly braces
procList="{ $procList }"

#sendlog $procList
}

function alertUser
{
#if you don't single quote you must escape double quotes for osascript
runOSA 'tell app "Finder" to activate'
runOSA 'tell app "Finder" to display dialog "Please close the following: '"${procString}"' Click Continue to try auto closing (otherwise Quit by hand) Unsaved work will prompt to be saved " buttons {"Continue", "Cancel"} with icon 0'
}

function waitForClose
{
makeProcList;

if [ ${#procArray[@]} -ne 0 ]; then while [ ${#procArray[@]} -ne 0 ]; do

#run Applescript to alert user myResult=$(alertUser); #sendlog MY RESULT: $myResult

#if [ "$myResult" == "button returned:OK" ]; then #makeProcList; #fi

if [ "$myResult" == "button returned:Continue" ]; then #printProcArray; quitAll; makeProcList; fi

if [ -z "$myResult" ]; then Cancel; exit 0; fi done
fi
}

function quitAll
{
osascript <<-EOF
repeat with proc in $procList try tell application proc quit end tell end try
end repeat
EOF

#sometimes too fast for an app to close, so have it wait
sleep 2
}

function Cancel
{
#if we are not in JAMF so just exit non zero
if [ $jamfScript -eq 0 ]; then
exit 0
fi

#else we are in JAMF

#get parent policy string
policyNames=$(ps auxww | grep "/usr/sbin/[j]amf policy -id")
policyPIDS=$(ps auxww | grep "/usr/sbin/[j]amf policy -id" | awk {'print $2'})

if [ -z "$policyPIDS" ]; then
sendlog "Could not find processes to kill $policyNames"
exit 1
fi

sendlog "Cancelled/Timeout, killing $policyPIDS"
kill -9 $policyPIDS
}

###################
# MAIN
###################
checkSystem;

#applescript uses commas between commands
IFS=$', '
#get the arguments of the apps we want to close
parseArgs ${@};
#process contain spaces so don't use those in IFS
IFS=$' '

#creates myArgs array
waitForClose;

exit 0
#############

13 REPLIES 13

Kumarasinghe
Valued Contributor

Excellent article. Very helpful.

jhalvorson
Valued Contributor

Here's an example of how I used it to for Office 2011 install via Self Service.

As noted above, created the command file and dropped it into Casper Admin. Within the Information > Options tab, I entered "App2Close" in all of the label fields. This helps remind me what I should enter when adding the script to a policy.

When I created the policy for Office 2011 via Self Service:
- Add the script called "closeApps.command"
- Set to Run Before
- Within each "App2Close" field I entered the following, each in it's own field:
Word
Excel
PowerPoint
Outlook
Entourage
Safari
(Left the two remaining parameter fields blank.)

This script worked well for me! Thanks for sharing.

Kumarasinghe
Valued Contributor

@ yr_joelbruner

I found that "Cancel" button will install the app if you use this script in a non-Self Service policy.
(e.g- you can't grep "/usr/sbin/[j]amf policy -id" for every15 trigger )

Any solution?

acdesigntech
Contributor II

You should be able to Grep it, but you can't have the AS kill the policy since the policy is run outside the user space. Self service allows it because it runs with elevated privileges.

You should be able to have the AS pass the result of the cancel button back to a shell script that will then kill the policy.

Something like TestVar=$(osascript<<EndAS
AS CODE THAT RETURNS THE VALUE OF THE CANCEL BUTTON
EndAS
)
If [ $TestVar -eq 2 ]; then
Kill all jamf
Fi

acdesigntech
Contributor II

Alternatively you could first use jamfHelper to throw up a utility window to proceed or cancel. That way the user could cancel the elevated process.

If proceeding, by all means use AS

nessts
Valued Contributor II

look into cocoa dialog, i have found it to me much more reliable than AS in actually appearing, and staying as long as you need it to stay and not randomly timing out. and you don't run into the -10080 interaction not allowed errors if you need to actually get input back from the users.

groupboard
New Contributor

Very useful script, thanks. I changed the 'Continue' button to 'Close Apps' (so as user doesn't get a surprise).

Also there is a bug in the script: it doesn't work if multiple processes contain any of the args. For example: try using "chrome" and you'll see it fails to close chrome.

Solution is to change this line:

procArray=( $(for proc in $(getProcs); do echo $proc | sed 's/^ //g'; done) )

to this:

procArray=( $(for proc in $(getProcs); do echo $proc | sed 's/^ //g' | perl -p -e 's/, / /g'; done) )

quedayone
Contributor

I would like to see a cocoa dialog version of this script!

Wi11

andyinindy
Contributor II

Joel:

Thanks for the great script. I'd really like to try it out, but I am getting the following when I try to run it:

line 201: syntax error: unexpected end of file

Thinking that maybe I somehow fouled it up when I copied/pasted it from this post, I went to your site to try to find it, but couldn't locate it.

Any chance that you could post a link to it?

Thanks,

--Andy

EDIT - Never mind, got it sorted. Copied/pasted into a new document and it worked. Weird.

andyinindy
Contributor II

FYI, I was able to solve the issue with the cancel button not working when the script was run via a policy. By collecting the result of the button click, we can either proceed or killall jamf.

myResult=`runOSA 'tell app "Finder" to activate
tell app "Finder" to display dialog "Please close the following:
'"${procString}"'

Click Continue to try auto closing 
(otherwise Quit by hand)

Unsaved work will prompt to be saved
" buttons {"Continue", "Cancel"} with icon 0
set result to button returned of result'`

function waitForClose
{
makeProcList;

if [ ${#procArray[@]} -ne 0 ]; then
while [ ${#procArray[@]} -ne 0 ]; do

if [ "$myResult" == "Continue" ]; then
quitAll;
makeProcList;
else
killall jamf;
exit 0;
fi
done
fi
}

Hopefully this helps someone!

--Andy

Kumarasinghe
Valued Contributor

@andyinindy

Thanks. Is it ok to use "killall jamf" command?
What happens if you have another policy running at the same time?

Thanks

andyinindy
Contributor II

@Kumarasinghe

You should be able to get a process listing and grep for the policy ID of the policy that ran the closeapps.command and quit it specifically.

robby_c137
New Contributor III

Hi Joel, though it's four years old, I'm really looking forward to making use of this great script to ask my users to quit apps before updating. However, after copying/pasting it I get a bunch of "command not found" errors. I'm new to scripting so I'm a bit lost here. Is there an updated version anyone's using I could make use of?