├── 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 | Screenshot 2024-10-02 at 3 55 52 AM 26 | 27 | 28 | ### Remediation Progress 29 | Screenshot 2024-10-02 at 3 55 59 AM 30 | 31 | 32 | ### Remediation Complete 33 | Screenshot 2024-10-02 at 3 56 17 AM 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 --------------------------------------------------------------------------------