.txt report and a .zip to the user's Desktop, then notifies them via dialog โ ready to send to IT support.
The script runs 14 diagnostic sections automatically and flags warnings and critical issues as it goes. Here's a quick summary of each section:
pmset settings, sleep/wake history, assertions preventing sleep, power adapter info, and flags for problematic hibernate modes known to trigger AP watchdog..txt report and a .zip are both saved to the user's Desktop automatically. A dialog box notifies the user when complete with an option to open the Desktop folder. The user can email or upload the ZIP straight to IT support.
Copy the full script (see the Full Script section below) and save it as a .sh file.
mac-advanced-diagnostic.shchmod +x mac-advanced-diagnostic.shLF not CRLF). If editing on Windows, use VS Code or Notepad++ and set line ending to LF before saving.Upload the script into Jamf Pro's script library so it can be attached to a policy.
Advanced Mac Diagnostic v2.0Category: Diagnostics (create this category first if it doesn't exist)
Info: Full system diagnostic โ AP watchdog, hardware, network, security, Jamf MDM. Saves report to Desktop.
Notes: Run as root via Self Service. Output saved to
/var/tmp/mac_diagnostics/ and user Desktop.
#!/bin/bash must be the very first line.Parameter Labels: Leave blank โ this script uses no parameters.
Create a policy that runs the script on demand from the Jamf Self Service app โ so users or helpdesk can trigger it themselves without needing admin credentials.
Run Advanced Mac DiagnosticEnabled: โ Yes
Trigger: โ Self Service (uncheck all others)
Execution Frequency: Ongoing โ allows users to run it multiple times
Category: Diagnostics
jamf recon internally, so this is optional but adds a safety net.
Button Name (Before): Run Diagnostic
Button Name (After): Run Again
Description: Runs a full system diagnostic and saves a report to your Desktop. The report covers hardware, crashes, network, security, and more. Takes 2โ4 minutes. Send the ZIP file to IT Support when done.
Category in Self Service: IT Support (or whichever category suits your environment)
Feature on main page: Optional โ useful if this is a key helpdesk tool
Control which computers the policy appears on in Self Service.
Best practice: Start with a small pilot group โ your own Mac or a test device โ before rolling out to all users.
Test the policy on a scoped device to confirm the script runs correctly and the report appears on the Desktop.
MacDiagnostic_<hostname>_<timestamp>.txt โ full readable reportMacDiagnostic_<hostname>_<timestamp>.zip โ compressed copy ready to send
.txt file and scroll to Section 14 โ Diagnostic Summary & Recommendations. This is where all flagged issues are listed with colour-coded severity and a numbered action plan./var/tmp/mac_diagnostics/ on the device.
bputil and Recovery-mode tools will return limited output when not in Recovery โ this is expected and does not indicate a script error.
Copy the entire script below. The shebang #!/bin/bash must remain as the very first line with no blank line before it.
#!/bin/bash # ============================================================================= # ADVANCED MAC DIAGNOSTIC SCRIPT โ Jamf Pro Self Service # Version: 2.0 # Purpose: Full system diagnostics, AP watchdog, hardware & software analysis # Run As: Root (via Jamf) # ============================================================================= # โโ CONFIG โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ TIMESTAMP=$(date '+%Y%m%d_%H%M%S') CURRENT_USER=$(stat -f '%Su' /dev/console) HOSTNAME=$(hostname -s) REPORT_DIR="/var/tmp/mac_diagnostics" REPORT_FILE="$REPORT_DIR/diagnostic_${HOSTNAME}_${TIMESTAMP}.txt" DESKTOP_COPY="/Users/$CURRENT_USER/Desktop/MacDiagnostic_${HOSTNAME}_${TIMESTAMP}.txt" LOG_DIR="/var/tmp/mac_diagnostics/logs" mkdir -p "$REPORT_DIR" "$LOG_DIR" # โโ HELPER FUNCTIONS โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ section() { echo ""; echo "โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ"; echo " โถ $1"; echo "โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ"; } subsection() { echo ""; echo " โโ $1 โโ"; } flag_warn() { echo " โ ๏ธ WARNING : $1"; } flag_ok() { echo " โ OK : $1"; } flag_crit() { echo " ๐ด CRITICAL: $1"; } flag_info() { echo " โน๏ธ INFO : $1"; } # โโ BEGIN REPORT โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ { # SECTION 1 โ SYSTEM OVERVIEW section "1. SYSTEM OVERVIEW" subsection "Hardware Identity" system_profiler SPHardwareDataType 2>/dev/null | grep -E \ "Model Name|Model Identifier|Chip|Total Number of Cores|Memory|Serial Number|Hardware UUID" subsection "macOS Version" sw_vers; echo "Kernel: $(uname -r)"; echo "Architecture: $(uname -m)" subsection "Uptime & Load" uptime; sysctl -n vm.loadavg subsection "Current Logged-in User" echo "User: $CURRENT_USER"; echo "UID: $(id -u $CURRENT_USER 2>/dev/null)" echo "Groups: $(id -Gn $CURRENT_USER 2>/dev/null)" # SECTION 2 โ AP WATCHDOG & CRASH ANALYSIS section "2. AP WATCHDOG & CRASH ANALYSIS" subsection "Panic Log Summary" PANIC_COUNT=$(find /Library/Logs/DiagnosticReports -name "*panic*" 2>/dev/null | wc -l | tr -d ' ') echo "Total kernel panic logs: $PANIC_COUNT" if [ "$PANIC_COUNT" -gt 5 ]; then flag_crit "High kernel panics ($PANIC_COUNT)" elif [ "$PANIC_COUNT" -gt 2 ]; then flag_warn "Multiple panics ($PANIC_COUNT)" elif [ "$PANIC_COUNT" -gt 0 ]; then flag_warn "Some panics ($PANIC_COUNT)" else flag_ok "No kernel panics"; fi subsection "SOCD / AP Watchdog Events (Last 30 Days)" SOCD_COUNT=$(log show --predicate 'eventMessage contains "SOCD" OR eventMessage contains "AP watchdog"' \ --last 30d 2>/dev/null | grep -c "SOCD\|watchdog" || echo 0) echo "SOCD/Watchdog events: $SOCD_COUNT" if [ "$SOCD_COUNT" -gt 3 ]; then flag_crit "Frequent SOCD events โ hardware issue likely" elif [ "$SOCD_COUNT" -gt 0 ]; then flag_warn "SOCD events detected โ monitor closely" else flag_ok "No SOCD events in last 30 days"; fi subsection "SOCD Event Detail" log show --predicate 'eventMessage contains "SOCD" OR eventMessage contains "AP watchdog" OR eventMessage contains "watchdog expired"' \ --last 30d 2>/dev/null | tail -20 subsection "Last Kernel Panic Content" LAST_PANIC=$(find /Library/Logs/DiagnosticReports -name "*panic*" 2>/dev/null \ -exec ls -t {} + 2>/dev/null | head -1) if [ -n "$LAST_PANIC" ]; then echo "Most recent: $LAST_PANIC"; head -50 "$LAST_PANIC" 2>/dev/null else echo "No panic logs found."; fi # SECTION 3 โ FILEVAULT & ENCRYPTION section "3. FILEVAULT & ENCRYPTION" subsection "FileVault Status" FV_STATUS=$(fdesetup status 2>/dev/null); echo "$FV_STATUS" if echo "$FV_STATUS" | grep -q "On"; then flag_warn "FileVault ENABLED โ known AP watchdog trigger on M1 Macs" flag_info "Consider disabling FileVault to test if crashes stop" else flag_ok "FileVault is OFF"; fi subsection "APFS Encryption Detail" diskutil apfs list 2>/dev/null | grep -E "Container|Volume|Encrypted|FileVault|Role|Capacity" subsection "Secure Token Status" sysadminctl -secureTokenStatus "$CURRENT_USER" 2>/dev/null # SECTION 4 โ HARDWARE DIAGNOSTICS section "4. HARDWARE DIAGNOSTICS" subsection "CPU Information" sysctl -n machdep.cpu.brand_string 2>/dev/null || system_profiler SPHardwareDataType | grep "Chip" echo "Physical CPUs: $(sysctl -n hw.physicalcpu)"; echo "Logical CPUs: $(sysctl -n hw.logicalcpu)" subsection "Memory (RAM)" TOTAL_RAM=$(sysctl -n hw.memsize | awk '{print $1/1073741824 " GB"}'); echo "Total RAM: $TOTAL_RAM" vm_stat 2>/dev/null | awk '/Pages free/{free=$3}/Pages active/{active=$3}/Pages inactive/{inactive=$3}/Pages wired/{wired=$4}/Pages occupied/{compressed=$5}END{p=4096;printf "Free: %.2f GB\nActive: %.2f GB\nInactive: %.2f GB\nWired: %.2f GB\nCompressed: %.2f GB\n",free*p/1073741824,active*p/1073741824,inactive*p/1073741824,wired*p/1073741824,compressed*p/1073741824}' subsection "Memory Pressure" MEM_PRESSURE=$(memory_pressure 2>/dev/null | head -5); echo "$MEM_PRESSURE" if echo "$MEM_PRESSURE" | grep -q "critical\|WARNING"; then flag_crit "Memory pressure HIGH" elif echo "$MEM_PRESSURE" | grep -q "warn"; then flag_warn "Memory pressure elevated" else flag_ok "Memory pressure normal"; fi subsection "Storage โ Capacity" df -h 2>/dev/null | grep -v "devfs\|map\|/private" DISK_USAGE=$(df / | tail -1 | awk '{print $5}' | tr -d '%') if [ "$DISK_USAGE" -gt 90 ]; then flag_crit "Disk at ${DISK_USAGE}% โ critically low!" elif [ "$DISK_USAGE" -gt 80 ]; then flag_warn "Disk at ${DISK_USAGE}% โ low space" else flag_ok "Disk at ${DISK_USAGE}%"; fi subsection "Battery Health" BATTERY=$(system_profiler SPPowerDataType 2>/dev/null) if echo "$BATTERY" | grep -q "Battery"; then echo "$BATTERY" | grep -E "Condition|Cycle Count|Full Charge|Maximum Capacity|Charging" else echo "No battery detected (Desktop Mac)"; fi subsection "Thermal Sensors & Fan" powermetrics --samplers smc -n 1 -i 500 2>/dev/null | grep -E "CPU die|GPU die|Fan|thermal|temp" | head -15 # SECTION 5 โ CONNECTED DEVICES & PERIPHERALS section "5. CONNECTED DEVICES & PERIPHERALS" subsection "USB Devices" system_profiler SPUSBDataType 2>/dev/null | grep -E "Product ID|Vendor ID|Manufacturer|Speed|^\s+[A-Z]" USB_DRIVES=$(system_profiler SPUSBDataType 2>/dev/null | grep -i "storage\|disk\|drive" | wc -l) if [ "$USB_DRIVES" -gt 0 ]; then flag_warn "$USB_DRIVES external storage device(s) โ known AP watchdog trigger" flag_info "Try disconnecting external drives and monitor for crashes"; fi subsection "Bluetooth Devices" system_profiler SPBluetoothDataType 2>/dev/null | grep -E "Address|Connected|^\s+[A-Z][a-z]" subsection "Network Interfaces" ifconfig 2>/dev/null | grep -E "^[a-z]|inet |status" # SECTION 6 โ NETWORK & SHARES section "6. NETWORK & SHARES" subsection "Current Network" echo "WiFi: $(networksetup -getairportnetwork en0 2>/dev/null)" echo "IP: $(ipconfig getifaddr en0 2>/dev/null || ipconfig getifaddr en1 2>/dev/null)" echo "DNS: $(scutil --dns 2>/dev/null | grep nameserver | head -3)" subsection "Mounted Network Shares" SHARES=$(mount 2>/dev/null | grep -E "smb|afp|nfs") if [ -n "$SHARES" ]; then echo "$SHARES"; flag_warn "Network shares mounted โ SMB is a known AP watchdog trigger" else flag_ok "No network shares mounted"; fi # SECTION 7 โ SOFTWARE & PROCESSES section "7. SOFTWARE & PROCESSES" subsection "Known Trigger Applications" TRIGGERS_FOUND=0 for APP in "Docker" "Xcode" "Parallels Desktop" "VMware Fusion" "VirtualBox"; do if [ -d "/Applications/${APP}.app" ]; then flag_warn "$APP INSTALLED โ potential AP watchdog trigger"; TRIGGERS_FOUND=$((TRIGGERS_FOUND+1)) else flag_ok "$APP: Not installed"; fi done for AV in "McAfee" "Symantec" "Norton" "Kaspersky" "Avast"; do if find /Applications -maxdepth 1 -iname "*$AV*" 2>/dev/null | grep -q .; then flag_warn "$AV Antivirus INSTALLED โ can conflict with macOS kernel"; TRIGGERS_FOUND=$((TRIGGERS_FOUND+1)); fi done echo "Total trigger apps found: $TRIGGERS_FOUND" subsection "Top CPU Processes" ps aux 2>/dev/null | sort -rk 3 | head -10 | awk '{printf "%-35s CPU:%-6s MEM:%-6s\n", $11, $3, $4}' subsection "Third-Party Kernel Extensions" THIRD_PARTY_KEXTS=$(kextstat 2>/dev/null | grep -v "com.apple" | wc -l | tr -d ' ') kextstat 2>/dev/null | grep -v "com.apple" | head -15 if [ "$THIRD_PARTY_KEXTS" -gt 0 ]; then flag_warn "$THIRD_PARTY_KEXTS third-party kexts loaded โ potential instability" else flag_ok "No third-party kernel extensions"; fi subsection "macOS Updates Available" softwareupdate -l 2>/dev/null | head -15 # SECTION 8 โ POWER & SLEEP MANAGEMENT section "8. POWER & SLEEP MANAGEMENT" subsection "Power Management Settings" pmset -g 2>/dev/null subsection "Sleep/Wake History (Last 20)" pmset -g log 2>/dev/null | grep -E "Sleep|Wake|DarkWake" | tail -20 subsection "Assertions Preventing Sleep" pmset -g assertions 2>/dev/null HIBERNATE=$(pmset -g | grep "hibernatemode" | awk '{print $2}') if [ "$HIBERNATE" = "3" ]; then flag_warn "Hibernate mode 3 โ can trigger watchdog on M1. Try: sudo pmset -a hibernatemode 0"; fi # SECTION 9 โ SECURITY & SYSTEM INTEGRITY section "9. SECURITY & SYSTEM INTEGRITY" subsection "System Integrity Protection" SIP_STATUS=$(csrutil status 2>/dev/null); echo "$SIP_STATUS" if echo "$SIP_STATUS" | grep -q "disabled"; then flag_crit "SIP is DISABLED โ system vulnerable!" else flag_ok "SIP is enabled"; fi subsection "Gatekeeper" GATE=$(spctl --status 2>/dev/null); echo "$GATE" if echo "$GATE" | grep -q "disabled"; then flag_warn "Gatekeeper disabled" else flag_ok "Gatekeeper enabled"; fi subsection "MDM Enrollment" profiles status -type enrollment 2>/dev/null subsection "Firewall" /usr/libexec/ApplicationFirewall/socketfilterfw --getglobalstate 2>/dev/null # SECTION 10 โ DISK HEALTH & FILESYSTEM section "10. DISK HEALTH & FILESYSTEM" subsection "APFS Health" diskutil apfs list 2>/dev/null subsection "Filesystem Usage" df -hi 2>/dev/null | grep -v "devfs\|map\|/private" subsection "Large Files (over 1GB)" find / -maxdepth 8 -size +1g -not -path "*/proc/*" -not -path "*/System/*" 2>/dev/null | \ xargs ls -lh 2>/dev/null | sort -rk5 | head -15 subsection "Cache & Temp Size" du -sh /private/tmp 2>/dev/null; du -sh ~/Library/Caches 2>/dev/null # SECTION 11 โ NETWORK DIAGNOSTICS section "11. NETWORK DIAGNOSTICS" subsection "DNS Test" dig apple.com +short 2>/dev/null | head -3 || host apple.com 2>/dev/null | head -3 subsection "Ping Test" ping -c 3 apple.com 2>/dev/null | tail -3; ping -c 3 8.8.8.8 2>/dev/null | tail -3 subsection "Wi-Fi Details" /System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport \ -I 2>/dev/null | grep -E "SSID|BSSID|channel|RSSI|lastTxRate" subsection "VPN Status" scutil --nc list 2>/dev/null | head -10 # SECTION 12 โ JAMF & MDM STATUS section "12. JAMF & MDM STATUS" subsection "Jamf Version" jamf version 2>/dev/null || echo "Jamf binary not found" subsection "Last Jamf Check-in" jamf checkJSSConnection 2>/dev/null || \ defaults read /Library/Preferences/com.jamfsoftware.jamf.plist jss_url 2>/dev/null subsection "MDM Profiles" profiles list -output stdout 2>/dev/null | head -30 # SECTION 13 โ EVENT LOG ANALYSIS section "13. EVENT LOG ANALYSIS" subsection "Critical System Errors (7 Days)" log show --predicate 'messageType == 16' --last 7d --style syslog 2>/dev/null | grep -v "^Filtering" | tail -20 subsection "Application Crashes (7 Days)" log show --predicate 'eventMessage contains "crashed" OR eventMessage contains "segfault"' \ --last 7d 2>/dev/null | grep -v "^Filtering" | tail -15 subsection "Wake/Sleep Issues (7 Days)" log show --predicate 'eventMessage contains "Wake" OR eventMessage contains "sleep"' \ --last 7d 2>/dev/null | grep -v "^Filtering" | grep -i "error\|fail\|timeout" | tail -15 subsection "Memory Events (7 Days)" log show --predicate 'eventMessage contains "memory pressure" OR eventMessage contains "jettisoned"' \ --last 7d 2>/dev/null | grep -v "^Filtering" | tail -15 # SECTION 14 โ DIAGNOSTIC SUMMARY & RECOMMENDATIONS section "14. DIAGNOSTIC SUMMARY & RECOMMENDATIONS" echo ""; echo "โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ" echo "โ FINDINGS SUMMARY โ" echo "โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ"; echo "" ISSUES=0 if fdesetup status 2>/dev/null | grep -q "On"; then echo "๐ด [HIGH] FileVault ENABLED"; echo " โ sudo fdesetup disable"; ISSUES=$((ISSUES+1)); fi if [ "$PANIC_COUNT" -gt 2 ]; then echo "๐ด [HIGH] $PANIC_COUNT kernel panics"; echo " โ Book Apple Genius Bar"; ISSUES=$((ISSUES+1)); fi if [ "$SOCD_COUNT" -gt 0 ] 2>/dev/null; then echo "๐ด [HIGH] SOCD/AP Watchdog events ($SOCD_COUNT)"; echo " โ Apple hardware repair likely needed"; ISSUES=$((ISSUES+1)); fi if [ -d "/Applications/Docker.app" ]; then echo "๐ก [MED] Docker Desktop installed"; echo " โ Quit Docker, test for 48hrs"; ISSUES=$((ISSUES+1)); fi if [ "$USB_DRIVES" -gt 0 ] 2>/dev/null; then echo "๐ก [MED] External USB storage connected"; echo " โ Disconnect and monitor for 1 week"; ISSUES=$((ISSUES+1)); fi if mount 2>/dev/null | grep -qE "smb|afp|nfs"; then echo "๐ก [MED] SMB/NFS shares mounted"; echo " โ sudo umount -A"; ISSUES=$((ISSUES+1)); fi if csrutil status 2>/dev/null | grep -q "disabled"; then echo "๐ด [HIGH] SIP DISABLED"; echo " โ Re-enable in Recovery โ csrutil enable"; ISSUES=$((ISSUES+1)); fi if [ "$DISK_USAGE" -gt 90 ] 2>/dev/null; then echo "๐ด [HIGH] Disk usage critical (${DISK_USAGE}%)"; echo " โ Free up space immediately"; ISSUES=$((ISSUES+1)); fi echo "" if [ "$ISSUES" -eq 0 ]; then echo "โ No major issues detected. If crashes persist, book Apple repair." else echo "๐ Issues found: $ISSUES"; echo "" echo "๐ง ACTION PLAN:" echo " 1. Disable FileVault if enabled" echo " 2. Disconnect all external devices" echo " 3. Quit/uninstall trigger apps (Docker, Xcode, VMs)" echo " 4. Unmount network shares" echo " 5. Monitor 1 week after each change" echo " 6. Still crashing โ Apple Store with this report"; fi echo ""; echo "๐ Report: $REPORT_FILE"; echo "๐ Desktop: $DESKTOP_COPY" echo ""; echo "Report: $(date) | macOS: $(sw_vers -productVersion) | Host: $HOSTNAME | User: $CURRENT_USER" } 2>&1 | tee "$REPORT_FILE" # โโ COPY TO DESKTOP โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ cp "$REPORT_FILE" "$DESKTOP_COPY" chown "$CURRENT_USER" "$DESKTOP_COPY" # โโ COMPRESS REPORT โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ ZIP_FILE="/Users/$CURRENT_USER/Desktop/MacDiagnostic_${HOSTNAME}_${TIMESTAMP}.zip" zip -j "$ZIP_FILE" "$REPORT_FILE" 2>/dev/null chown "$CURRENT_USER" "$ZIP_FILE" # โโ JAMF RECON โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ jamf recon 2>/dev/null # โโ NOTIFY USER โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ osascript <<EOF display dialog "โ Advanced Diagnostic Complete! ๐ Report saved to your Desktop: $(basename $DESKTOP_COPY) ๐ฆ Compressed ZIP also saved: $(basename $ZIP_FILE) Please send the ZIP file to your IT Support team." \ buttons {"Open Desktop", "OK"} default button "Open Desktop" \ with title "Mac Diagnostic Report" with icon note if result = {button returned:"Open Desktop"} then tell application "Finder" to open folder "Desktop" of home end if EOF exit 0