Posted on 03-21-2012 11:14 AM
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
#############
Posted on 07-05-2012 12:23 AM
Excellent article. Very helpful.
Posted on 07-05-2012 09:39 AM
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.
Posted on 11-05-2012 08:47 PM
@ 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?
Posted on 11-06-2012 06:41 AM
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
Posted on 11-06-2012 06:43 AM
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
Posted on 11-06-2012 06:52 AM
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.
Posted on 03-07-2013 06:32 PM
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) )
Posted on 03-13-2013 07:32 AM
I would like to see a cocoa dialog version of this script!
Wi11
Posted on 09-13-2013 09:58 AM
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.
Posted on 09-17-2013 10:52 AM
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
Posted on 09-23-2013 11:15 PM
Thanks. Is it ok to use "killall jamf" command?
What happens if you have another policy running at the same time?
Thanks
Posted on 09-24-2013 03:56 PM
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.
Posted on 12-17-2016 12:09 AM
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?