A collection of macOS scripts for CrowdStrike Falcon Real Time Response
Vendor Overview
Real Time Response is a feature of CrowdStrike Falcon® Insight [that] empowers incident responders with deep access to systems across the distributed enterprise, [providing] enhanced visibility … to fully understand emerging threats and the power to directly remediate.
macOS Scripts
The following macOS scripts — presented in their typical execution order — are tuned for computers enrolled in Jamf Pro.
Template
Description
The template below includes the following features:
- Interpreter Directive: Unless you’re leveraging specific “bash-isms,” you should probably use zsh as the default interpreter directive
- Script Name: Including the script’s name and purpose as part of the script’s output has proved invaluable when assembling copy-pasta audit logs
- Estimated Duration: As with nearly all remotely executed commands, you’re “flying blind” and having a ballpark estimate of how long the command typically takes to execute can be stress-reducing — in what tends to be stressful situations
#!/bin/zsh # macOS Script_Name_Goes_Here (0.0.1) # (Estimated Duration: 0h:0m:01s) echo -e "\n\n\n###\n# macOS Script_Name_Goes_Here (0.0.1)\n# (Estimated Duration: 0h:0m:01s)\n###" # INITIALIZE SECONDS SECONDS="0" # COMMAND # Replace # these # lines # with # the # command # to # execute # OUTPUT EXECUTION DURATION echo -e "\nExecution Duration: $(printf '%dh:%dm:%ds\n' $((SECONDS/3600)) $((SECONDS%3600/60)) $((SECONDS%60)))"
macOS Clock Skew Correction (0.0.1)
Description
Set Simple Network Time Protocol to time.apple.com
#!/bin/zsh # macOS Clock Skew Correction (0.0.1) # (Estimated Duration: 0h:0m:01s) echo -e "\n\n\n###\n# macOS Clock Skew Correction (0.0.1)\n# (Estimated Duration: 0h:0m:01s)\n###" # Initialize SECONDS SECONDS="0" echo "• Set Simple Network Time Protocol to 'time.apple.com'" /usr/bin/sntp -sS time.apple.com echo -e "\nExecution Duration: $(printf '%dh:%dm:%ds\n' $((SECONDS/3600)) $((SECONDS%3600/60)) $((SECONDS%60)))"
Sample Output
### # macOS Clock Skew Correction (0.0.1) # (Estimated Duration: 0h:0m:01s) ### • Set Simple Network Time Protocol to 'time.apple.com' +0.129035 +/- 0.112248 time.apple.com 17.254.0.26 Execution Duration: 0h:0m:0s
macOS Network Quality Test (0.0.2)
Description
Leverage macOS Monterey’s (and later) networkQuality
binary to better understand your users’ Internet connection
#!/bin/zsh # macOS Network Quality Test (0.0.2) # (Estimated Duration: 0h:0m:41s) echo -e "\n\n\n###\n# macOS Network Quality Test (0.0.2)\n# (Estimated Duration: 0h:0m:41s)\n###" # Initialize SECONDS SECONDS="0" osProductVersion=$( /usr/bin/sw_vers -productVersion ) case "${osProductVersion}" in 10* | 11* ) dlCapacity="N/A; macOS ${osProductVersion}" dlResponsiveness="N/A; macOS ${osProductVersion}" ;; 12* ) networkqualityTest=$( /usr/bin/networkquality -s -v ) dlCapacity=$( echo "${networkqualityTest}" | /usr/bin/awk '/Download capacity:/{print $3, $4}' ) dlResponsiveness=$( echo "${networkqualityTest}" | /usr/bin/awk '/Download Responsiveness:/{print $3, $4, $5}' ) ;; 13* | 14* ) networkqualityTest=$( /usr/bin/networkquality -s -v ) dlCapacity=$( echo "${networkqualityTest}" | /usr/bin/awk '/Downlink capacity:/{print $3, $4}' ) dlResponsiveness=$( echo "${networkqualityTest}" | /usr/bin/awk '/Downlink Responsiveness:/{print $3, $4, $5}' ) ;; 15* ) networkqualityTest=$( /usr/bin/networkquality -s -v ) dlCapacity=$( echo "${networkqualityTest}" | /usr/bin/awk '/Downlink capacity:/{print $3, $4}' | head -n 1 ) dlResponsiveness=$( echo "${networkqualityTest}" | /usr/bin/awk '/Downlink Responsiveness:/{print $3, $4, $5}' | head -n 1 ) ;; esac echo -e "\n• Download Capacity: ${dlCapacity}" echo "• Download Responsiveness: ${dlResponsiveness}" echo -e "\nExecution Duration: $(printf '%dh:%dm:%ds\n' $((SECONDS/3600)) $((SECONDS%3600/60)) $((SECONDS%60)))"
Sample Output
### # macOS Network Quality Test (0.0.2) # (Estimated Duration: 0h:0m:41s) ### • Download Capacity: 458.487 Mbps • Download Responsiveness: Medium Execution Duration: 0h:0m:39s
macOS Computer Information (0.0.4) 🆕
Description
Outputs Computer Name, User Name, macOS version, Serial Number, Uptime, Pending Updates and jamf
binary status
#!/bin/zsh # macOS Computer Information (0.0.4) # Outputs Computer Name, User Name, macOS version, Serial Number, Uptime, Pending Updates, `jamf` binary status and the Mac's connection to the Jamf Pro server # (Estimated Duration: 0h:0m:5s) echo -e "\n\n\n###\n# macOS Computer Information (0.0.4)\n# (Estimated Duration: 0h:0m:5s)\n###\n" # Initialize SECONDS SECONDS="0" # Last Logged-in User lastUser=$( defaults read /Library/Preferences/com.apple.loginwindow.plist lastUserName ) # General Computer Information Variables macOSproductVersion="$( sw_vers -productVersion )" macOSbuildVersion="$( sw_vers -buildVersion )" serialNumber=$( ioreg -rd1 -c IOPlatformExpertDevice | awk -F'"' '/IOPlatformSerialNumber/{print $4}' ) computerName=$( scutil --get LocalHostName ) # Uptime lastBootTime=$( sysctl kern.boottime | awk -F'[ |,]' '{print $5}' ) currentTime=$( date +"%s" ) upTimeRaw=$((currentTime-lastBootTime)) upTimeMin=$((upTimeRaw/60)) upTimeHours=$((upTimeMin/60)) uptimeDays=$( uptime | awk '{ print $4 }' | sed 's/,//g' ) uptimeNumber=$( uptime | awk '{ print $3 }' | sed 's/,//g' ) if [[ "${uptimeDays}" = "day"* ]]; then if [[ "${uptimeNumber}" -gt 1 ]]; then uptimeHumanReadable="${uptimeNumber} (days)" else uptimeHumanReadable="${uptimeNumber} (day)" fi elif [[ "${uptimeDays}" == "mins"* ]]; then uptimeHumanReadable="${uptimeNumber} (mins)" else uptimeHumanReadable="${uptimeNumber} (HH:MM)" fi # General Computer Information Output echo "• Computer Name: ${computerName}" echo "• User Name: ${lastUser}" echo "• Serial Number: ${serialNumber}" echo "• Operating System: macOS ${macOSproductVersion} (${macOSbuildVersion})" echo "• Uptime: ${uptimeHumanReadable}" # Pending Software Updates availableUpdates=$( defaults read /Library/Preferences/com.apple.SoftwareUpdate.plist LastRecommendedUpdatesAvailable ) if [ "$availableUpdates" != "0" ]; then echo "• Pending Software Updates: $( /usr/libexec/mdmclient AvailableOSUpdates )" else echo "• Pending Software Updates: ${availableUpdates}" fi # Jamf binary-related Variables jamfVersion=$( /usr/local/bin/jamf -version | cut -d "=" -f2 ) jamfPid=$( pgrep -a "jamf" | head -n 1 ) if [[ -n "${jamfPid}" ]]; then jamfPidDuration=$( ps -ao etime= "${jamfPid}" ) if [[ "${jamfPidDuration}" == *"-"* ]]; then jamfPidDays=$( ps -ao etime= "${jamfPid}" | cut -d "-" -f1 ) echo "• Jamf ${jamfVersion} binary PID [${jamfPid}] running for ${jamfPidDays} days" ps -p "$jamfPid" echo " Please run the 'macOS jamf Binary Kick-start' script" else echo "• jamf ${jamfVersion} binary PID [${jamfPid}] hasn't been running more than a day" ps -p "$jamfPid" fi else echo "• jamf ${jamfVersion} binary not running" fi echo "• Jamf Pro server connection: $( /usr/local/bin/jamf checkJSSConnection )" echo -e "\nExecution Duration: $(printf '%dh:%dm:%ds\n' $((SECONDS/3600)) $((SECONDS%3600/60)) $((SECONDS%60)))"
Sample Output
### # macOS Computer Information (0.0.4) # (Estimated Duration: 0h:0m:5s) ### • Computer Name: X*****-**** • User Name: dan • Serial Number: X********7 • Operating System: macOS 14.7 (23H124) • Uptime: 9:41 (HH:MM) • Pending Software Updates: === OS Update Item === Product Key: 062-78429 Title: macOS Sequoia Version: 15.0 <Build: (null)> <PMV: (null)> Deferred: no (Date: ) Tags: SUBUNDLE:com.apple.InstallAssistant.macOSSequoia; CUSTOMER MacOSUpdate: no MSU: no (Major: no Full: no DL: no label: (null)) Splat: no <(null)> (Revoked: no) IsMacOSUpdate(): no Major Version: 15.0 Major BundleID: com.apple.InstallAssistant.macOSSequoia Major Title: macOS Sequoia Ignoring IA-based installer === OS Update Item === Product Key: MSU_UPDATE_24A335_patch_15.0_major Title: macOS Sequoia 15.0 Version: 15.0 <Build: 24A335> <PMV: 15.0> Deferred: no (Date: ) Tags: (null) MacOSUpdate: YES MSU: YES (Major: YES Full: no DL: no label: macOS Sequoia 15.0-24A335) Splat: no <(null)> (Revoked: no) IsMacOSUpdate(): YES Major Version: 15.0 Major BundleID: (null) Major Title: macOS Sequoia 15.0 Available updates (install debug profile for more details): ( { AllowsInstallLater = 0; AppIdentifiersToClose = ( ); Build = 24A335; DownloadSize = 6624852495; HumanReadableName = "macOS Sequoia 15.0"; HumanReadableNameLocale = "en-US"; IsConfigDataUpdate = 0; IsCritical = 0; IsFirmwareUpdate = 0; IsMajorOSUpdate = 1; IsSecurityResponse = 0; ProductKey = "MSU_UPDATE_24A335_patch_15.0_major"; RequiresBootstrapToken = 1; RestartRequired = 1; SupplementalBuildVersion = 24A335; Version = "15.0"; } ) • Jamf 11.9.1-t1726060704 binary not running • Jamf Pro server connection: Checking availability The JSS is available. Execution Duration: 0h:0m:6s
macOS LaunchDaemons & LaunchAgents (0.0.4)
Description
Lists LaunchDaemons and LaunchAgents both system-wide and for the last logged-in user
#!/bin/zsh # macOS LaunchDaemons & LaunchAgents (0.0.4) # (Estimated Duration: 0h:0m:0s) echo -e "\n\n\n###\n# macOS LaunchDaemons & LaunchAgents (0.0.4)\n# (Estimated Duration: 0h:0m:0s)\n###" # Initialize SECONDS SECONDS="0" echo -e "\nSystem LaunchDaemons:" ls -ld /Library/LaunchDaemons/* echo -e "\n\n\nSystem LaunchAgents:" ls -ld /Library/LaunchAgents/* lastUser=$( defaults read /Library/Preferences/com.apple.loginwindow.plist lastUserName ) echo -e "\n\n\nUser's LaunchAgents:" ls -ld /Users/$lastUser/Library/LaunchAgents/* if [[ -f "/Users/$lastUser/Library/LaunchAgents/com.mmsprot.agent.plist" ]]; then /usr/bin/plutil -p "/Users/$lastUser/Library/LaunchAgents/com.mmsprot.agent.plist" fi echo -e "\nExecution Duration: $(printf '%dh:%dm:%ds\n' $((SECONDS/3600)) $((SECONDS%3600/60)) $((SECONDS%60)))"
Sample Output
### # macOS LaunchDaemons & LaunchAgents (0.0.4) # (Estimated Duration: 0h:0m:0s) ### System LaunchDaemons: -rw-r--r-- 1 root wheel 474 Feb 26 2024 /Library/LaunchDaemons/com.a***.plist -rw-r--r-- 1 root wheel 486 Feb 26 2024 /Library/LaunchDaemons/com.b***.plist -rw-r--r-- 1 root wheel 564 Aug 30 14:08 /Library/LaunchDaemons/com.c***.plist System LaunchAgents: -rw-r--r-- 1 root wheel 577 Aug 30 13:42 /Library/LaunchAgents/com.a***.plist -rw-r--r-- 1 root wheel 562 Jun 5 15:49 /Library/LaunchAgents/com.crowdstrike.falcon.UserAgent.plist -rw-r--r-- 1 root wheel 523 Aug 30 13:37 /Library/LaunchAgents/com.m***.plist User's LaunchAgents: -rw-r--r-- 1 dan staff 640 Aug 31 22:04 /Users/dan/Library/LaunchAgents/com.a***.plist Execution Duration: 0h:0m:0s
macOS Update Inventory via Daily Update Inventory policy (0.0.1)
Description
Updates the Jamf Pro server with this Mac’s current inventory data via the “Daily Update Inventory” policy. (Observation of No policies were found for the ID 1.
indicates the policy has executed as expected in the last 24 hours.)
#!/bin/zsh # macOS Update Inventory via "Daily Update Inventory" policy (0.0.1) # (Estimated Duration: 0h:0m:37s) echo -e "\n\n\n###\n# macOS Update Inventory via "Daily Update Inventory" policy (0.0.1)\n# (Estimated Duration: 0h:0m:37s)\n###\n" # Initialize SECONDS SECONDS="0" # Jamf binary-related Variables jamfVersion=$( /usr/local/bin/jamf -version | cut -d "=" -f2 ) jamfPid=$( pgrep -a "jamf" | head -n 1 ) if [[ -n "${jamfPid}" ]]; then echo "• jamf ${jamfVersion} binary already running …" ps -p "${jamfPid}" echo "• Killing ${jamfPid} …" kill "${jamfPid}" fi echo "• Update Inventory via 'Daily Update Inventory' policy …" /usr/local/bin/jamf policy -id 1 -verbose echo -e "\nExecution Duration: $(printf '%dh:%dm:%ds\n' $((SECONDS/3600)) $((SECONDS%3600/60)) $((SECONDS%60)))"
Sample Output
### # macOS Update Inventory via Daily Update Inventory policy (0.0.1) # (Estimated Duration: 0h:0m:37s) ### • Update Inventory via 'Daily Update Inventory' policy … verbose: JAMF binary already symlinked verbose: Checking for an existing instance of this application... Checking for policy ID 1... verbose: Checking for active ethernet connection... verbose: No active ethernet connection found... verbose: Removing any cached policies for this trigger. verbose: Parsing servers... verbose: The Management Framework Settings are up to date. No policies were found for the ID 1. Execution Duration: 0h:0m:2s
macOS Force-update Inventory via jamf binary (0.0.1)
Description
Force-updates the Jamf Pro server with this Mac’s current inventory data via the jamf
binary
#!/bin/zsh # macOS Force-update Inventory via jamf binary (0.0.1) # (Estimated Duration: 0h:0m:37s) echo -e "\n\n\n###\n# macOS Force-update Inventory via jamf binary (0.0.1)\n# (Estimated Duration: 0h:0m:37s)\n###\n" # Initialize SECONDS SECONDS="0" # Jamf binary-related Variables jamfVersion=$( /usr/local/bin/jamf -version | cut -d "=" -f2 ) jamfPid=$( pgrep -a "jamf" | head -n 1 ) if [[ -n "${jamfPid}" ]]; then echo "• jamf ${jamfVersion} binary already running …" ps -p "${jamfPid}" echo "• Killing ${jamfPid} …" kill "${jamfPid}" fi echo "• Update Inventory via jamf binary …" /usr/local/bin/jamf recon -verbose echo -e "\nExecution Duration: $(printf '%dh:%dm:%ds\n' $((SECONDS/3600)) $((SECONDS%3600/60)) $((SECONDS%60)))"
Sample Output
### # macOS Force-update Inventory via jamf binary (0.0.1) # (Estimated Duration: 0h:0m:37s) ### • Update Inventory via jamf binary … verbose: Timeout: 10 verbose: Checking availability of https://jamfpro.company.com/... verbose: The JSS is available. Retrieving inventory preferences from https://jamfpro.company.com/... Finding extension attributes... Locating accounts... Locating applications... Locating hard drive information... Locating package receipts... Locating plugins... Gathering application usage information from the JamfDaemon... Searching path: /Applications Locating hardware information (macOS 15.1.1)... Submitting data to https://jamfpro.company.com/... <computer_id>007</computer_id> verbose: Calling JamfDaemonremoveOldAppUsageLogs... verbose: Removing existing launchd task /Library/LaunchDaemons/com.jamfsoftware.task.bgrecon.plist... Execution Duration: 0h:0m:27s
macOS jamf
binary Kickstart (0.0.5)
Description
Kick-starts the jamf
binary
#!/bin/zsh # macOS jamf binary Kickstart (0.0.5) # (Estimated Duration: 0h:3m:0s) echo -e "\n\n\n###\n# macOS jamf binary Kickstart (0.0.5)\n# (Estimated Duration: 0h:3m:0s)\n###" # Initialize SECONDS SECONDS="0" # Functions restartJamfLaunchDaemon() { launchctl bootout system /Library/LaunchDaemons/com.jamfsoftware.task.1.plist launchctl bootstrap system /Library/LaunchDaemons/com.jamfsoftware.task.1.plist launchctl bootout system /Library/LaunchDaemons/com.jamf.management.daemon.plist launchctl bootstrap system /Library/LaunchDaemons/com.jamf.management.daemon.plist } startJamfLaunchDaemon() { launchctl bootout system /Library/LaunchDaemons/com.jamfsoftware.task.1.plist launchctl bootstrap system /Library/LaunchDaemons/com.jamfsoftware.task.1.plist launchctl bootout system /Library/LaunchDaemons/com.jamf.management.daemon.plist launchctl bootstrap system /Library/LaunchDaemons/com.jamf.management.daemon.plist } # Uptime lastBootTime=$( sysctl kern.boottime | awk -F'[ |,]' '{print $5}' ) currentTime=$( date +"%s" ) upTimeRaw=$((currentTime-lastBootTime)) upTimeMin=$((upTimeRaw/60)) upTimeHours=$((upTimeMin/60)) uptimeDays=$( uptime | awk '{ print $4 }' | sed 's/,//g' ) uptimeNumber=$( uptime | awk '{ print $3 }' | sed 's/,//g' ) if [[ "${uptimeDays}" = "day"• ]]; then if [[ "${uptimeNumber}" -gt 1 ]]; then uptimeHumanReadable="${uptimeNumber} (days)" else uptimeHumanReadable="${uptimeNumber} (day)" fi elif [[ "${uptimeDays}" == "mins"• ]]; then uptimeHumanReadable="${uptimeNumber} (mins)" else uptimeHumanReadable="${uptimeNumber} (HH:MM)" fi echo "• Uptime: ${uptimeHumanReadable}" # Jamf binary-related Variables jamfVersion=$( /usr/local/bin/jamf -version | cut -d "=" -f2 ) jamfPid=$( pgrep -a "jamf" | head -n 1 ) if [[ -n "${jamfPid}" ]]; then jamfPidDuration=$( ps -ao etime= "${jamfPid}" ) echo "• Jamf ${jamfVersion} binary PID [${jamfPid}] running for: ${jamfPidDuration}" ps -p "$jamfPid" if [[ -e /Library/Application\ Support/JAMF/Remote\ Assist/Uninstall ]]; then echo "• Removing Jamf Remote Assist …" sh /Library/Application\ Support/JAMF/Remote\ Assist/Uninstall fi jamfRemoteAssistVersion=$( ls /Library/Application\ Support/JAMF/JamfRemoteAssist/ 2>/dev/null ) if [[ -n "${jamfRemoteAssistVersion}" ]]; then echo "• Removing Jamf Remote Assist version ${jamfRemoteAssistVersion} …" sh "/Library/Application Support/JAMF/JamfRemoteAssist/${jamfRemoteAssistVersion}/uninstall.sh" fi echo "• Killing 'jamf' binary …" /usr/bin/killall jamf echo "• Restarting Jamf-related LaunchDaemons …" restartJamfLaunchDaemon startJamfLaunchDaemon echo "• Updating jamf management …" /usr/local/bin/jamf manage -rebootIfNeeded -deleteLaunchdTask -verbose 2>&1 # echo "• Updating inventory …" # /usr/local/bin/jamf recon -verbose 2>&1 & # echo "• Executing pending policies …" # /usr/local/bin/jamf policy -verbose 2>&1 & echo -e "\nPlease run the 'macOS Update Inventory via Policy' script" else echo "• jamf ${jamfVersion} binary not running" fi echo -e "\nExecution Duration: $(printf '%dh:%dm:%ds\n' $((SECONDS/3600)) $((SECONDS%3600/60)) $((SECONDS%60)))"
Sample Output
### # macOS jamf binary Kickstart (0.0.5) # (Estimated Duration: 0h:3m:0s) ### • Uptime: 1:17 (HH:MM) • jamf 11.7.1-t1721056075 binary not running Execution Duration: 0h:0m:0s
macOS User-friendly Reboot Prompt (0.0.4)
Description
Edit rebootDays
to specify the allowed number of days before a forced reboot. The user will be prompted with the following reboot options:
- 15 minutes
- 30 minutes
- 1 hour
#!/bin/bash # macOS User-friendly Reboot Prompt (0.0.4) # (Estimated Duration: 0h:5m:0s) # Inspired by: https://community.jamf.com/t5/jamf-pro/jamf-helper-reboot-script-with-deferral/td-p/188511#responseChild164611 echo -e "\n\n\n###\n# macOS User-friendly Reboot Prompt (0.0.4)\n# (Estimated Duration: 0h:5m:0s)\n###" # Number of days before reboot rebootDays="${1:-"30"}" # Reverse Domain Name reverseDomainName="com.company.division" # Initialize SECONDS SECONDS="0" # Date & Time Variables lastBootRaw=$( sysctl kern.boottime | awk -F'[= |,]' '{print $6}' ) lastBootFormat=$( date -jf "%s" "$lastBootRaw" +"%d-%b-%Y" ) today=$( date +%s ) uptimeDays=$(( (today - lastBootRaw) / 86400 )) icon="/usr/local/company/icons/Restart.png" echo -e "\n• Days since last reboot: ${uptimeDays}" # If days of uptime is less than specified reboot days, exit. if [[ ${uptimeDays} -le ${rebootDays} ]] ; then echo -e "\nExiting: ${uptimeDays} Uptime (days) is less than or equal to ${rebootDays}" echo -e "\nExecution Duration: $(printf '%dh:%dm:%ds\n' $((SECONDS/3600)) $((SECONDS%3600/60)) $((SECONDS%60)))" exit 0 fi function cleanUp(){ # Remove reboot-related files /bin/echo "#!/bin/bash # Remove reboot-related files /bin/rm -f /Library/LaunchDaemons/${reverseDomainName}.rebootDelay.plist /bin/rm -f /Library/LaunchDaemons/${reverseDomainName}.rebootDelayWarning.plist /bin/rm -f /usr/local/company/scripts/rebootWarning.sh /bin/launchctl remove ${reverseDomainName}.rebootDelay /bin/launchctl remove ${reverseDomainName}.rebootDelayWarning /bin/sleep 2 ## Update Device Inventory /usr/local/jamf/bin/jamf recon ## Remove LaunchDaemon /bin/rm -f /Library/LaunchDaemons/${reverseDomainName}.rebootCleanup.plist ## Remove Script /bin/rm -f /usr/local/company/scripts/rebootCleanup.sh exit 0" > /usr/local/company/scripts/rebootCleanup.sh # Set permission on the file just created /usr/sbin/chown root:admin /usr/local/company/scripts/rebootCleanup.sh /bin/chmod 755 /usr/local/company/scripts/rebootCleanup.sh # rebootCleanup LaunchDaemon cat << EOF > /Library/LaunchDaemons/${reverseDomainName}.rebootCleanup.plist <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>Label</key> <string>${reverseDomainName}.rebootCleanup</string> <key>ProgramArguments</key> <array> <string>/bin/bash</string> <string>-c</string> <string>/usr/local/company/scripts/rebootCleanup.sh</string> </array> <key>RunAtLoad</key> <true/> </dict> </plist> EOF # Set permission on the file just created /usr/sbin/chown root:wheel /Library/LaunchDaemons/${reverseDomainName}.rebootCleanup.plist /bin/chmod 644 /Library/LaunchDaemons/${reverseDomainName}.rebootCleanup.plist } function jamfhelper(){ /Library/Application\ Support/JAMF/bin/jamfHelper.app/Contents/MacOS/jamfHelper \ -windowType utility \ -title "Church Security Required Reboot [KB0117342]" \ -description "This computer was last restarted ${lastBootFormat}. Church Security requires computers to be rebooted every ${rebootDays} days. (See KB0117342.) Click \"Restart Now\" or choose a delay option within the time specified below. (You will receive a five-minute warning with any delay option.)" \ -icon "${icon}" \ -timeout "60" \ -countdown \ -button1 "Delay" \ -button2 "Restart Now" \ -showDelayOptions "900, 1800, 3600" # 15 minutes, 30 minutes, 1 hour } # variables result=$( jamfhelper ) delayint=$( echo "${result}" | /usr/bin/sed 's/.$//' ) warndelayint=$( expr ${delayint} - 300 ) defercal=$(($(/bin/date +%s) + delayint)) hour=$( /bin/date -j -f "%s" "${defercal}" "+%H" ) minute=$( /bin/date -j -f "%s" "${defercal}" "+%M" ) warndefercal=$(($(/bin/date +%s) + warndelayint)) warnhour=$( /bin/date -j -f "%s" "${warndefercal}" "+%H" ) warnminute=$( /bin/date -j -f "%s" "${warndefercal}" "+%M" ) # Write LaunchDaemon populated with variables from jamfHelper output function delay(){ /bin/cat <<EOF > /Library/LaunchDaemons/${reverseDomainName}.rebootDelay.plist <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>Label</key> <string>${reverseDomainName}.rebootDelay</string> <key>ProgramArguments</key> <array> <string>reboot</string> </array> <key>StartCalendarInterval</key> <dict> <key>Hour</key> <integer>$hour</integer> <key>Minute</key> <integer>$minute</integer> </dict> </dict> </plist> EOF } function warndelay(){ /bin/cat <<EOF > /Library/LaunchDaemons/${reverseDomainName}.rebootDelayWarning.plist <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>Label</key> <string>${reverseDomainName}.rebootDelayWarning</string> <key>ProgramArguments</key> <array> <string>sh</string> <string>/usr/local/company/scripts/rebootWarning.sh</string> </array> <key>StartCalendarInterval</key> <dict> <key>Hour</key> <integer>$warnhour</integer> <key>Minute</key> <integer>$warnminute</integer> </dict> </dict> </plist> EOF } function warnScript(){ /bin/cat <<EOF > /usr/local/company/scripts/rebootWarning.sh #!/bin/bash /Library/Application\ Support/JAMF/bin/jamfHelper.app/Contents/MacOS/jamfHelper \ -windowType utility \ -title "Church Security Required Reboot [KB0117342]" \ -description "This computer was last restarted ${lastBootFormat}. Church Security requires computers to be rebooted every ${rebootDays} days. (See KB0117342.) This computer will restart in the time indicated below. " \ -timeout 300 \ -countdown \ -lockHUD \ -icon "${icon}" \ -button1 "OK" EOF } function finalPrep(){ # ScriptLog "Calling the finalPrep function ..." # Unload LaunchDaemons # shellcheck disable=SC2143 if [[ $( /bin/launchctl list | grep ${reverseDomainName}.reboot ) ]]; then /bin/launchctl bootout system /Library/LaunchDaemons/${reverseDomainName}.rebootDelay.plist /bin/launchctl bootout system /Library/LaunchDaemons/${reverseDomainName}.rebootDelayWarning.plist fi # Set ownership on delay LaunchDaemon chown root:wheel /Library/LaunchDaemons/${reverseDomainName}.rebootDelay.plist chmod 644 /Library/LaunchDaemons/${reverseDomainName}.rebootDelay.plist # Set ownership on delaywarning LaunchDaemon chown root:wheel /Library/LaunchDaemons/${reverseDomainName}.rebootDelayWarning.plist chmod 644 /Library/LaunchDaemons/${reverseDomainName}.rebootDelayWarning.plist # Load LaunchDaemons /bin/launchctl bootstrap system /Library/LaunchDaemons/${reverseDomainName}.rebootDelay.plist /bin/launchctl bootstrap system /Library/LaunchDaemons/${reverseDomainName}.rebootDelayWarning.plist # Start LaunchDaemons /bin/launchctl start /Library/LaunchDaemons/${reverseDomainName}.rebootDelay.plist /bin/launchctl start /Library/LaunchDaemons/${reverseDomainName}.rebootDelayWarning.plist } # select action based on user input case "$result" in *1 ) echo "${uptimeDays} Uptime (days); User delayed reboot until: ${hour}:${minute} local time" delay warndelay warnScript finalPrep cleanUp echo -e "\nExecution Duration: $(printf '%dh:%dm:%ds\n' $((SECONDS/3600)) $((SECONDS%3600/60)) $((SECONDS%60)))" ;; *2 ) reboot echo "User clicked \"Restart Now\"" echo -e "\nExecution Duration: $(printf '%dh:%dm:%ds\n' $((SECONDS/3600)) $((SECONDS%3600/60)) $((SECONDS%60)))" ;; esac
Sample Output
### # macOS User-friendly Reboot Prompt (0.0.3) # (Estimated Duration: 0h:5m:0s) ### • Days since last reboot: 47 47 Uptime (days); User delayed reboot until: 07:31 local time Execution Duration: 0h:0m:42s
macOS Forensically Sound Workstation Lockout 🆕
macOS Forensically Sound* Workstation Lockout with CrowdStrike Falcon and Jamf Pro
Designed as a possible last step before a MDM “Lock Computer” command,
FSWL.bash
*may aid in keeping a Mac computer online for investigation, while discouraging end-user tampering
Background
When a macOS computer is lost, stolen or involved in a security breach, the Mobile Device Management (MDM) Lock Computer command can be used as an “atomic” option to quickly bring some peace of mind to what are typically stressful situations, while the MDM Wipe Computer command can be used as the “nuclear” option.
For occasions where first forensically securing a macOS computer are preferred, the following approach may aid in keeping a device online for investigation, while discouraging end-user tampering.