├── LICENSE
├── README.md
└── sdJamfProtectRemediation.sh
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 chrisgzim
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # swiftDialog-Jamf-Protect-Template
2 | ## A simple way to put all of your remediations in one script!
3 |
4 | One of the coolest features of Jamf Protect is the ability to partner with Jamf Pro for remediation. The way this is accomplished is through smart groups and policies that are scoped to machines that have a Jamf Protect EA tied to them.
5 |
6 | A cool project out there is from @bartreardon: swiftDialog. The goal of this script was to make a simple template that would notify end-users of potential threats and remediation progress.
7 |
8 | This is my first experience using swiftDialog and it is a really sweet project. Hope this is a helpful resource for Mac Admins!
9 |
10 | (The script will check to see if swiftDialog is installed, if not it will install it for you.)
11 |
12 | ## How to Use
13 |
14 | For the most part, everything that you need to change is all at the beginning of the script. My goal was to make this as simple as possible for admins. Just copy your remediation scripts and paste them into the template.
15 |
16 | By default, there are only three remediations spots. However, I have added templates for up to 7 as well as a way to simply add your script to the remediation function. (There is also a template you can use to add as many as you would like!)
17 |
18 | Just want to test the swiftDialog prompts? No problem! There is now a Debug Mode that you can use to help see what the different prompts look like. Each instance of "remediation" in debug will go ahead and run a sleep command for 5 seconds. (This way you can see the progress bar move as well.)
19 |
20 | Hopefully you find this helpful and as always, please give some feedback!
21 |
22 | ## Screenshots of default behavior:
23 |
24 | ### Initial Warning
25 |
26 |
27 |
28 | ### Remediation Progress
29 |
30 |
31 |
32 | ### Remediation Complete
33 |
34 |
35 |
--------------------------------------------------------------------------------
/sdJamfProtectRemediation.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | #### JAMF PROTECT REMEDIATION WITH SWIFTDIALOG TEMPLATE ####
4 | #
5 | # A simple way to put in your Jamf Protect / Pro Remediations
6 | # in a single script
7 | #
8 | # First a huge shoutout to @robjschroeder for the idea for this script
9 | #
10 | # This project would not be possible without the amazing work of @bartreardon
11 | # and the awesomeness of swiftDialog
12 | #
13 | # Definitely have to give a shoutout to @dan-snelson for some help with putting this together
14 | # You should absolutely check out Setup Your Mac
15 | #
16 | #
17 | #
18 | # This is a script I wrote for fun, it has no affliation with Jamf
19 | # and is not a part of their support.
20 | #############################################################
21 |
22 |
23 | ########### WHAT NEEDS TO BE FILLED OUT #################
24 | #### Parameters for Jamf #####
25 |
26 | # File Path / URL to Icon -- Defaults to the Jamf Protect Logo
27 | icon="${4:-"/Applications/JamfProtect.app/Contents/Resources/AppIcon.icns"}"
28 | #Have the Script do the cleanup of the smart groups (/Library/Application Support/JamfProtect/Groups/(group)
29 | #Will keep groups that do not have a remediation
30 | SmartGroupCleanup="${5:-"true"}" # set to true by default. To turn off, use any value you want other than true.
31 | # Debug Mode -- will add the groups as necessary, give you a feel for the dialogs / progress bar and run all remediations as sleep commands
32 | debugMode="$6" # Set Debug to true to enable #To test out errors / non-matching variables use the fe+=() variable
33 | # File path for Log File
34 | logfile="${7:-"/var/log/protectremediation.txt"}"
35 |
36 |
37 | ###### Paramaters for swiftDialog ###############
38 | #Headline for the Warning
39 | titletext="Warning"
40 | #Main Text for Warning
41 | message="Your computer has detected a potential threat. Please click OK so that we can make sure that your computer is safe to use."
42 | #Title Text for Remediation
43 | title="Protecting your Mac"
44 | #Main Text for Remediation
45 | remediatemessage="Please wait. Remediation in Progress. . ."
46 | #Headline for Remediation
47 | completiontitle="Remediation Complete"
48 | #Main Text for Completion
49 | completiontext="Your Mac is now safe"
50 | ############# SwiftDialog Paramaters End ########
51 |
52 | ################ LOG FUNCTION ###################
53 | function updatelog() {
54 | echo -e "$( date +%Y-%m-%d\ %H:%M:%S ) - ${1}" | tee -a "${logfile}"
55 | }
56 | # you can use this with your remediation scripts too #
57 | ################################################
58 |
59 | ########## Remediation Workflow(s) ############
60 | ########## VARIABLES TO EDIT HERE #######
61 | ########## Instead of an exit 1, you can use
62 | ########## ((err++)) to track failed remediations
63 |
64 | ##### Three Remediations by default ###########
65 | ####
66 |
67 | # Name of a Group
68 | sg[1]=""
69 | function remediate1() {
70 | sleep 5 # Put your Remdiation Script Here
71 | }
72 | # Name of a Group
73 | sg[2]=""
74 | function remediate2() {
75 | sleep 5 #Put your Remediation Script Here
76 | }
77 | # Name of a Group
78 | sg[3]=""
79 | function remediate3() {
80 | sleep 5 #Put your Remediation Script Here
81 | }
82 |
83 | ####### Extra Remediation Spots ##############
84 | ###### Includes instructions of things to add
85 | ###### to the remediationwork function as well
86 | ###### If you need more, a solid template
87 | ###### is provided for you.
88 | #############################################
89 |
90 | # Name of a Group
91 | #sg[4]=""
92 | #function remediate4() {
93 | # sleep 5 #Put your Remediation Script Here
94 | #}
95 |
96 | ### If using add this to the script ###
97 | ### Without comments of course ########
98 |
99 | #${sg[4]})
100 | # #dialogupdateProtectRemediation "progresstext: $result or message here"
101 | # remediate4
102 | # ((rc++))
103 | # ;;
104 |
105 | # Name of a Group
106 | #sg[5]=""
107 | #function remediate5() {
108 | # sleep 5 #Put your Remediation Script Here
109 | #}
110 |
111 | ### If using add this to the script ###
112 | ### Without comments of course ########
113 |
114 | #${sg[5]})
115 | # #dialogupdateProtectRemediation "progresstext: $result or message here"
116 | # remediate5
117 | # ((rc++))
118 | # ;;
119 |
120 |
121 | ## Name of a Group
122 | #sg[6]=""
123 | #function remediate6() {
124 | # sleep 5 #Put your Remediation Script Here
125 | #}
126 |
127 | ### If using add this to the script ###
128 | ### Without comments of course ########
129 |
130 | #${sg[6]})
131 | # #dialogupdateProtectRemediation "progresstext: $result or message here"
132 | # remediate6
133 | # ((rc++))
134 | # ;;
135 |
136 | # Name of a Group
137 | #sg[7]=""
138 | #function remediate7() {
139 | # sleep 5 #Put your Remediation Script Here
140 | #}
141 |
142 | ### If using add this to the script ###
143 | ### Without comments of course ########
144 |
145 | #${sg[7]})
146 | # #dialogupdateProtectRemediation "progresstext: $result or message here"
147 | # remediate7
148 | # ((rc++))
149 | # ;;
150 |
151 | ##### TEMPLATE if going over 7 ############# (copy and make changes)
152 | # Name of a Group
153 | #sg[#]=""
154 | #function remediate#() {
155 | # sleep 5 #Put your Remediation Script Here
156 | #}
157 |
158 | ### If using add this to the script ###
159 | ### Without comments of course ########
160 |
161 | #${sg[#]})
162 | # #dialogupdateProtectRemediation "progresstext: $result or message here"
163 | # remediate#
164 | # ((rc++))
165 | # ;;
166 |
167 |
168 | ############### WHAT NEEDS TO BE FILLED OUT END ############################
169 |
170 | ##############################################################################
171 | #
172 | # If you need to add more remediations, please follow the instructions listed
173 | # in the remediation category of things to fill out. #
174 | ##############################################################################
175 | ########## You can add "progress text" using the first line that is commented out in the
176 | ########## cased remediation.
177 | ##############################################################################
178 |
179 | function remediationwork() {
180 | if [[ $SmartGroupCleanup == "true" ]]; then updatelog "Smart Group Cleanup Enabled, will clean up after each remediation"; fi
181 | for result in ${gtr[@]}; do
182 | #Variable needed for the Progress Bar
183 | piv=$(( 100 / er ))
184 | case $result in
185 |
186 | #replace "group" variable with Smart Group Tag, replace remediate with workflow for remediation
187 | #leave the arithemetic variables as those help with tracking progress
188 | ${sg[1]})
189 | #dialogupdateProtectRemediation "progresstext: $result or message here"
190 | remediate1
191 | ((rc++))
192 | ;;
193 | ${sg[2]})
194 | #dialogupdateProtectRemediation "progresstext: $result or message here"
195 | remediate2
196 | ((rc++))
197 | ;;
198 | ${sg[3]})
199 | #dialogupdateProtectRemediation "progresstext: $result or message here"
200 | remediate3
201 | ((rc++))
202 | ;;
203 | *)
204 | #dialogupdateProtectRemediation "progresstext: Unknown Remediation"
205 | updatelog "remediation not found for $result"
206 | ((rc++))
207 | ((err++))
208 | ;;
209 | esac
210 | #If Clean up is enabled, clean up
211 | if [[ $SmartGroupCleanup == "true" ]]; then
212 | # Makes sure your remediation didn't already delete the file ;)
213 | if [[ -e "$jpgd"/${result} ]] && [[ $errcheck = $err ]]; then
214 | updatelog "Removing $result from the $jpgd folder"
215 | rm -r "$jpgd/${result}"
216 | else
217 | updatelog "Could Not Delete $result (No Remediation Present or Remediation Failed)"
218 | #resets the error check
219 | errcheck=$((err))
220 | fi
221 |
222 | fi
223 | # Increment the progress bar
224 | dialogupdateProtectRemediation "progress: increment ${piv}"
225 | done
226 | dialogupdateProtectRemediation "button1: enable"
227 | }
228 |
229 |
230 |
231 | ####### DEBUG FUNCTIONS ###############
232 |
233 |
234 | #### DEBUG Variable #######
235 | ##### Fake Errors Array ###
236 | ##### Use this to add "Unknown Remediation Errors in Debug Mode
237 | ##### Simply type in values like so ( Fake Errors Go Here )
238 | fe+=()
239 | ###########################
240 |
241 |
242 | function debugsetup() {
243 | # Write Group Files if not already there to maximize the prompts
244 | for groupstomake in ${sg[@]}; do
245 | if [[ ! -e "$jpgd"/$groupstomake ]]; then
246 | updatelog "DEBUG: Creating the $groupstomake file now"
247 | touch "$jpgd"/$groupstomake
248 | else
249 | updatelog "DEBUG: There is already a $groupstomake here"
250 | fi
251 | done
252 | # Creating Fake Groups if added in the fe array
253 | if [[ -z ${fe[@]} ]]; then
254 | updatelog "DEBUG: No fake groups added to the fe array"
255 | else
256 | for fakegroups in ${fe[@]}; do
257 | if [[ -e $fakegroups ]]; then
258 | updatelog "DEBUG: $fakegroups already detected, skipping for now"
259 | else
260 | updatelog "DEBUG: Adding $fakegroups to Protect Groups"
261 | touch "$jpgd"/$fakegroups
262 | fi
263 | done
264 | fi
265 | }
266 |
267 | function debugremediation() {
268 | updatelog "DEBUG: Creating a test for ${#gtr[@]} remediations"
269 | for result in ${gtr[@]}; do
270 | #Variable needed for the Progress Bar
271 | piv=$(( 100 / er ))
272 | case ${sg[@]} in
273 |
274 | #leave the arithemetic variables as those help with tracking progress
275 | (*"$result"*)
276 | debugupdate "progresstext: DEBUG: Doing Fake Remediation for $result"
277 | sleep 5
278 | updatelog "DEBUG: Faking the remediation for $result"
279 | ((rc++))
280 | ;;
281 | *)
282 | debugupdate "progresstext: DEBUG Unknown Remediation for $result"
283 | sleep 5
284 | updatelog "DEBUG: There does not appear to be a remediation for $result"
285 | ((rc++))
286 | ((err++))
287 | ;;
288 | esac
289 | #If Clean up is enabled, clean up
290 | if [[ $SmartGroupCleanup -eq 1 ]]; then
291 | # Makes sure your remediation didn't already delete the file ;)
292 | if [[ -e "$jpgd"/${result} ]] && [[ $errcheck = $err ]]; then
293 | updatelog "DEBUG: SMARTGROUPCLEANUP ENABLED Deleting the $result from Protect Groups"
294 | rm -r "$jpgd"/${result}
295 |
296 | else
297 | updatelog "DEBUG: SMARTGROUPCLEANUP ENABLED but Could Not Delete $result (No Remediation Present or Remediation Failed)"
298 | #resets the error check
299 | updatelog "DEBUG: Resetting Error Check to Continue Calculations"
300 | updatelog "DEBUG: $errcheck does not equal $err"
301 | errcheck=$((err))
302 | updatelog "DEBUG: $errcheck equals $err"
303 | fi
304 |
305 | fi
306 | # Increment the progress bar
307 | debugupdate "progress: increment ${piv}"
308 | done
309 | debugupdate "button1: enable"
310 |
311 | }
312 |
313 | function debugcomplete() {
314 |
315 | debugupdate "title: DEBUG MODE $completiontitle"
316 | debugUpdate "message: $completiontext"
317 | debugupdate "progresstext: Your Mac is Safe Now"
318 | debugupdate "progress: complete"
319 | debugupdate "button1: enable"
320 | debugupdate "button1text: Continue"
321 |
322 | sleep 10
323 | }
324 | ######## Scripting Logic for the workflow ################
325 | ### This is where the magic happens, please do not touch ####
326 | #######################################################
327 | #Check for log file and create one if it doesn't exist
328 |
329 | if [[ ! -e $logfile ]]; then
330 | touch $logfile
331 | echo "No log file found, creating now"
332 | fi
333 |
334 | updatelog "Jamf Protect Template: Start of Log"
335 |
336 |
337 | # Validate swiftDialog is installed
338 |
339 | if [ ! -e "/Library/Application Support/Dialog/Dialog.app" ]; then
340 | updatelog "Dialog not found, installing..."
341 | dialogURL=$(curl -L --silent --fail "https://api.github.com/repos/swiftDialog/swiftDialog/releases/latest" | awk -F '"' "/browser_download_url/ && /pkg\"/ { print \$4; exit }")
342 | expectedDialogTeamID="PWA5E9TQ59"
343 | # Create a temp directory
344 | workDir=$(/usr/bin/basename "$0")
345 | tempDir=$(/usr/bin/mktemp -d "/private/tmp/$workDir.XXXXXX")
346 | # Download latest version of swiftDialog
347 | /usr/bin/curl --location --silent "$dialogURL" -o "$tempDir/Dialog.pkg"
348 | # Verify download
349 | teamID=$(/usr/sbin/spctl -a -vv -t install "$tempDir/Dialog.pkg" 2>&1 | awk '/origin=/ {print $NF }' | tr -d '()')
350 | if [ "$expectedDialogTeamID" = "$teamID" ] || [ "$expectedDialogTeamID" = "" ]; then
351 | /usr/sbin/installer -pkg "$tempDir/Dialog.pkg" -target /
352 | else
353 | updatelog "Team ID verification failed, could not continue..."
354 | exit 6
355 | fi
356 | /bin/rm -Rf "$tempDir"
357 | else
358 | updatelog "Dialog v$(dialog --version) installed, continuing..."
359 | fi
360 |
361 | ### Swift Dialog Binary and Application Location ####
362 | dialogBinary="/usr/local/bin/dialog"
363 | dialogApp="/Library/Application\ Support/Dialog/Dialog.app/Contents/MacOS/Dialog"
364 |
365 | #jamfprotect groups directory
366 | jpgd="/Library/Application Support/JamfProtect/groups"
367 |
368 | # Create files if they don't exist if Debug is Enabled
369 | if [[ $debugMode = "true" ]]; then
370 | updatelog "DEBUG MODE: Debug Mode Enabled adding all of the Groups"
371 | debugsetup
372 | fi
373 |
374 | #Make sure directory exists and grab the groups that need to be resolved
375 | if [[ -d "$jpgd" ]]; then
376 | #groups to remediate
377 | updatelog "Groups Identified. . . adding to array for remediation"
378 | gtr+=($(/bin/ls "$jpgd"))
379 | else
380 | updatelog "Directory Not found, unable to remediate"
381 | exit 1
382 | fi
383 |
384 | #expected remediations
385 | er=$(echo ${#gtr[@]})
386 | #variables to track progress (remediation complete)
387 | rc=0
388 | # Errors
389 | err=0
390 | errcheck=0
391 |
392 |
393 | ######### SWIFT DIALOG FUNCTIONS ########
394 |
395 | ### Command File for swiftDialog ###
396 |
397 | commandfile=$( mktemp /var/tmp/protectremediation.XXX )
398 | welcomecommandfile=$( mktemp /var/tmp/protectremediation.XXX )
399 |
400 | ## Welcome / Beginning Prompt Dialog
401 | beginningprompt="$dialogBinary \
402 | --title \"$titletext\" \
403 | --message \"$message\" \
404 | --icon \"$icon\" \
405 | --button1text \"OK\" \
406 | --blurscreen \
407 | --ontop \ "
408 |
409 | ## Remediation Prompt
410 | remediationtime="$dialogBinary \
411 | --title \"$title\" \
412 | --message \"$remediatemessage\" \
413 | --icon \"$icon\" \
414 | --progress \
415 | --progresstext \"Remediating Vulnerabilities\" \
416 | --button1text \"Wait\" \
417 | --blurscreen \
418 | --button1disabled \
419 | --quitkey k \
420 | --ontop \
421 | --commandfile \"$commandfile\" "
422 |
423 | ######## DEBUG Resources #############
424 |
425 | ### Command File for swiftDialog ###
426 |
427 | debugcommandfile=$( mktemp /var/tmp/dbprotectremediation.XXX )
428 | debugwelcomecommandfile=$( mktemp /var/tmp/dbprotectremediation.XXX )
429 |
430 | ## Welcome / Beginning Prompt Dialog
431 | debugbeginningprompt="$dialogBinary \
432 | --title \"DEBUG MODE $titletext\" \
433 | --message \"$message\" \
434 | --icon \"$icon\" \
435 | --button1text \"OK\" \
436 | --ontop \ "
437 |
438 | ## Remediation Prompt
439 | debugremediationtime="$dialogBinary \
440 | --title \"DEBUG MODE $title\" \
441 | --message \"$remediatemessage\" \
442 | --icon \"$icon\" \
443 | --progress \
444 | --progresstext \"Remediating Vulnerabilities\" \
445 | --button1text \"Wait\" \
446 | --button1disabled \
447 | --quitkey k \
448 | --ontop \
449 | --commandfile \"$debugcommandfile\" "
450 |
451 |
452 |
453 | #### Update Progress on Command File
454 | function dialogupdateProtectRemediation() {
455 | echo "$1" >> "$commandfile"
456 | }
457 |
458 | function debugupdate() {
459 | echo "$1" >> "$debugcommandfile"
460 | }
461 |
462 | ### Clean up your Mess ###
463 |
464 | function fin() {
465 | if [[ $debugMode = "true" ]]; then
466 | updatelog "DEBUG MODE: Deleting tmp files"
467 | rm -r $debugcommandfile
468 | rm -r $debugwelcomecommandfile
469 | else
470 | updatelog "Removing tmp files"
471 | rm -r $commandfile
472 | rm -r $welcomecommandfile
473 | fi
474 |
475 | updatelog "Clean up complete"
476 | updatelog "Jamf Protect Template: End of Log"
477 | }
478 |
479 | #Creates Working Files
480 | if [[ $debugMode = "true" ]]; then
481 | updatelog "DEBUG MODE: Creating Debug Command File"
482 | echo "$debugremediationtime" >> $debugcommandfile
483 | else
484 | echo "$remediatetime" >> $commandfile
485 | fi
486 |
487 | #starts progress bar
488 | if [[ $debugMode = "true" ]]; then
489 | debugupdate "progress: 1"
490 | else
491 | dialogupdateProtectRemediation "progress: 1"
492 | fi
493 |
494 | ########## SWIFT DIALOG FOR COMPLETION MESSAGE ############
495 | function completion() {
496 |
497 | dialogupdateProtectRemediation "title: $completiontitle"
498 | dialogupdateProtectRemediation "message: $completiontext"
499 | dialogupdateProtectRemediation "progresstext: Your Mac is Safe Now"
500 | dialogupdateProtectRemediation "progress: complete"
501 | dialogupdateProtectRemediation "button1: enable"
502 | dialogupdateProtectRemediation "button1text: Continue"
503 |
504 | sleep 10
505 | }
506 | ###########################################################
507 |
508 | #### Do the Work #################
509 | if [[ $debugMode = "True" ]]; then
510 | eval ${debugbeginningprompt}
511 | eval ${debugremediationtime[*]} & sleep 0.3
512 | debugremediation
513 | debugcomplete
514 | else
515 | eval ${beginningprompt}
516 | eval ${remediationtime[*]} & sleep 0.3
517 | remediationwork
518 | completion
519 | fi
520 | ###################################
521 |
522 | fin
--------------------------------------------------------------------------------