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.