-
Notifications
You must be signed in to change notification settings - Fork 4
/
app_quitter.py
266 lines (222 loc) · 8.86 KB
/
app_quitter.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
#!/usr/bin/python
"""
This script will prompt end users to quit apps, or force quit apps depending on the options passed
in the positional parameters
Since this will be ran by jamf remember the first 3 parameters are reserved by jamf, so we will start with parameter 4
APPLIST will be a comma separated list of bundle IDs of apps you want this code to quit, example:
com.apple.Safari,org.mozilla.firefox,com.google.Chrome
PROMPT will be the parameter you use to decide to prompt the user or not, use strings "true" or "false"
APPNAME will be the name of the application and how you want to present it in a dialog box, i.e. Safari or Safari.app
UPDATEPOLICY is the jamf event to trigger the policy to update the app
FORCEQUIT is set to true or false in jamf as a psoitional parameter, if you set this to true it does as advertised
and will force quit the apps by bundle ID and force an update
SYMBOL is the unicode string for the heart emoji, because we can
MESSAGE is the actual message you wish to display to the end user
COMPLETE is the message that will pop when the patch is complete
FORCEMSG = the template message to pop when doing a forced update for security reasons
"""
# import modules
from Cocoa import NSRunningApplication
import sys
import subprocess
import os
import time
# positional parameters and global variables
# list apps by bundle ID to quit
APPLIST = sys.argv[4].split(",")
# pass "true" or "false" to this if you want to prompt the user or not
PROMPT = sys.argv[5].lower()
# display name of the app in the dialog boxes, i.e. "Safari"
APPNAME = sys.argv[6]
# the event trigger of the jamf policy that will update the app
UPDATEPOLICY = sys.argv[7]
# option to force quit an app, just in case you need that big red button
FORCEQUIT = sys.argv[8].lower()
# heart emoji, because we love Snowflake!
SYMBOL = u"\u2764\ufe0f"
# message to prompt the user to quit and update an app
MESSAGE = """Greetings Employee:
I.T. would like to patch {0}. Please click on the "OK" button to continue, this will prompt you to quit your application and save your work.
You may click "Cancel" to delay this update.
{1} I.T.
""".format(
APPNAME, SYMBOL.encode("utf-8")
)
FORCEMSG = """Greetings Employee:
I.T. would like to patch {0}. This is an emergency patch and the application will be quit to deploy security patches.
{1} I.T.
""".format(
APPNAME, SYMBOL.encode("utf-8")
)
# message to notify the user upon completion
COMPLETE = """Thank You!
{0} has been patched on your system. You may relaunch it now if you wish
""".format(
APPNAME
)
# start functions
def check_if_running(bid):
"""Test to see if an app is running by bundle ID"""
# macOS API to check if an app bundle is running or not
app = NSRunningApplication.runningApplicationsWithBundleIdentifier_(bid)
# return True if running, False if not
if app:
return True
if not app:
return False
def user_prompt(prompt):
"""simple jamf helper dialog box"""
# set the path to your custom branding, it will default to the warning sign if your branding is not found
icon = ""
# test to see what icons are available on the file system
if not os.path.exists(icon):
# default fail over icon in case our custom one does not exist
icon = "/System/Library/CoreServices/Problem Reporter.app/Contents/Resources/ProblemReporter.icns"
# build the jamf helper unix command in a list
cmd = [
"/Library/Application Support/JAMF/bin/jamfHelper.app/Contents/MacOS/jamfHelper",
"-windowType",
"utility",
"-title",
"Quit Applications",
"-description",
prompt,
"-icon",
icon,
"-button1",
"OK",
"-button2",
"Cancel",
"-defaultbutton",
"1",
]
# call the command via subprocess
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
# get stdout and stderr
out, err = proc.communicate()
# check for exit status for button clicked, 0 = OK 2 = Cancel
if proc.returncode == 0:
# user clicked OK
return True
if proc.returncode == 2:
# user clicked cancel
return False
# if there is any other return print it
else:
print("Error: %s" % err)
def force_quit_prompt(prompt):
"""jamf helper dialog to inform of the force quit"""
# Custom branding icon path goes here for Force Quit work flows
icon = ""
# test to see what icons are available on the file system
if not os.path.exists(icon):
# default fail over icon in case our custom one does not exist
icon = "/System/Library/CoreServices/Problem Reporter.app/Contents/Resources/ProblemReporter.icns"
# build the jamf helper unix command in a list
cmd = [
"/Library/Application Support/JAMF/bin/jamfHelper.app/Contents/MacOS/jamfHelper",
"-windowType",
"utility",
"-title",
"Quit Applications",
"-description",
prompt,
"-icon",
icon,
"-button1",
"OK",
"-defaultbutton",
"1",
]
# call the command via subprocess
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
# get stdout and stderr
out, err = proc.communicate()
# check for exit status for button clicked, 0 = OK 2 = Cancel
if proc.returncode == 0:
# user clicked OK
return True
if proc.returncode == 2:
# user clicked cancel
return False
# if there is any other return print it
else:
print("Error: %s" % err)
def quit_application(bid):
"""quits apps using NSRunningApplication"""
# use API to assign a variable for the running API so we can terminate it
apps = NSRunningApplication.runningApplicationsWithBundleIdentifier_(bid)
# API returns an array always, must iterate through it
for app in apps:
# terminate the app
app.terminate()
# if the app does not terminate in 3 seconds gracefully force it
time.sleep(3)
if not app.isTerminated():
app.forceTerminate()
def force_quit_applicaiton(bid):
"""force an application to quit for emergency workflows"""
# use API to assign a variable for the running API so we can FORCE terminate it
apps = NSRunningApplication.runningApplicationsWithBundleIdentifier_(bid)
# API returns an array always, must iterate through it
for app in apps:
# terminate the app
app.forceTerminate()
def run_update_policy(event):
"""run the updater policy for the app"""
# if you don't need to run an update policy, set to "false" to skip this
if event == "false":
pass
# unix command list
cmd = ["/usr/local/bin/jamf", "policy", "-event", event]
# execute the policy to the binary
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
# grab stdout and stderr pipes and communicate them to the shell
out, err = proc.communicate()
# if we get a non zero response, print the error
if proc.returncode != 0:
print("Error: %s" % err)
def notify_on_completion():
"""notification that the patch is complete"""
# probably do not need this, can most likely reuse prior dialog box function
# this is just a place holder for now
pass
def run():
"""runs the workflow of the script"""
# check to see if the app is not running, if it is not we are in luck we can update now!
for app in APPLIST:
if not check_if_running(app):
run_update_policy(UPDATEPOLICY)
sys.exit(0)
# check to see if we are forcing the app to quit first, and take action
if FORCEQUIT == "true":
force_quit_prompt(FORCEMSG)
# loop through the bundle ID list
for bid in APPLIST:
# force quit the app and force the update via jamf policy
force_quit_applicaiton(bid)
run_update_policy(UPDATEPOLICY)
user_prompt(COMPLETE)
# if we are using the force we can exit here
sys.exit(0)
# use the bundle ID or IDs from parameter 4 and iterate through them
for bid in APPLIST:
# check if the app is running by bundle ID and we are choosing to prompt from parameter 5
if check_if_running(bid) and PROMPT == "true":
# prompt the user
answer = user_prompt(MESSAGE)
# if they click OK, will return True value
if answer:
# quit the app, run the update, prompt to notify when complete
quit_application(bid)
run_update_policy(UPDATEPOLICY)
user_prompt(COMPLETE)
if not answer:
# if they click "Cancel" we will exit
sys.exit(0)
# if we pass the option to not prompt, just quit the app
if check_if_running(bid) and PROMPT == "false":
quit_application(bid)
# gotta have a main
if __name__ == "__main__":
run()