I wanted to monitor if our Macs are doing their Microsoft Defender Full and Quick Scans properly in Jamf.
As I couldn't find any solution out-of-the-box I came up with a custom script, in case anybody got the same need.
The problem I faced, was that the Mac only saves the state of the last 7 scans (Quick and Full Scans are combined). So let's say you schedule your Quick Scans once per day and your full scans once per week and the user is on vacation only on the "Full-Scan-Day", you have no chance to see, if a full scan never ran or just the week before, but was overwritten by Quick Scans.
That's why I came up with two different timestamps for the full scans, that one timestamp will always be in Jamf, to see when the last full scan ran, the second one might not really be needed, as it only checks the Full Scan within the last 7 scans.
The script might be a bit fragile as it relies on Microsoft to keep the same format for the "mdatp scan list" command, but for now it works :)
You will also need extension attributes in Jamf to collect your results. I collect the following results:
MS Defender - Full Scan - Last Scan Timestamp Overall
"scan_last_timestemp" from "com.puma.DefenderFullScanMonitoring.plist"
MS Defender - Full Scan - State
"scan_state" from "com.puma.DefenderFullScanMonitoring.plist"
MS Defender - Full Scan - Timestamp (within last 7 scans)
"scan_time" from "com.puma.DefenderFullScanMonitoring.plist"
MS Defender - Quick Scan - State
"scan_state" from "com.puma.DefenderQuickScanMonitoring.plist"
MS Defender - Quick Scan - Timestamp (within last 7 scans)
"scan_time" from "com.puma.DefenderQuickScanMonitoring.plist"
Feel free to share your comments or improvements on this. Maybe there's even a out-of-the-box solution, which you can point me to.
#!/bin/bash
#####################################################################
#Created by RaGL
#Modified by
#
#Subject: Defender Scan Monitoring Script
#####################################################################
#
#
#
#VARIABLES###########################################################
scan_results=( $(mdatp scan list) )
quick_scan="quick"
full_scan="full"
quick_scan_plist_path="<PATH>/com.company.DefenderQuickScanMonitoring.plist"
full_scan_plist_path="<PATH>/com.company.DefenderFullScanMonitoring.plist"
#Scan State Positions in the Array
type_pos1="197"
type_pos2="166"
type_pos3="135"
type_pos4="104"
type_pos5="73"
type_pos6="42"
type_pos7="11"
#Scan Start Time Positions in the Array
time_pos1=( "189" "190" "191" "192" "193" "194" )
time_pos2=( "158" "159" "160" "161" "162" "163" )
time_pos3=( "127" "128" "129" "130" "131" "132" )
time_pos4=( "96" "97" "98" "99" "100" "101" )
time_pos5=( "65" "66" "67" "68" "69" "70" )
time_pos6=( "34" "35" "36" "37" "38" "39" )
time_pos7=( "3" "4" "5" "6" "7" "8" )
#Scan State Positions in the Array
state_pos1="202"
state_pos2="171"
state_pos3="140"
state_pos4="109"
state_pos5="78"
state_pos6="47"
state_pos7="16"
#
#SCRIPT CONTENT######################################################
#last quick scan
if [[ ${scan_results[$type_pos1]} = $quick_scan ]]; then
scan_time_quick=$(echo ${scan_results[$(echo ${time_pos1[0]})]})
scan_time_quick+=" $(echo ${scan_results[$(echo ${time_pos1[1]})]})"
scan_time_quick+=" $(echo ${scan_results[$(echo ${time_pos1[2]})]})"
scan_time_quick+=" $(echo ${scan_results[$(echo ${time_pos1[3]})]})"
scan_time_quick+=" $(echo ${scan_results[$(echo ${time_pos1[4]})]})"
scan_time_quick+=" $(echo ${scan_results[$(echo ${time_pos1[5]})]})"
scan_state_quick=$(echo ${scan_results[$(echo ${state_pos1})]})
elif [[ ${scan_results[$type_pos2]} = $quick_scan ]]; then
scan_time_quick=$(echo ${scan_results[$(echo ${time_pos2[0]})]})
scan_time_quick+=" $(echo ${scan_results[$(echo ${time_pos2[1]})]})"
scan_time_quick+=" $(echo ${scan_results[$(echo ${time_pos2[2]})]})"
scan_time_quick+=" $(echo ${scan_results[$(echo ${time_pos2[3]})]})"
scan_time_quick+=" $(echo ${scan_results[$(echo ${time_pos2[4]})]})"
scan_time_quick+=" $(echo ${scan_results[$(echo ${time_pos2[5]})]})"
scan_state_quick=$(echo ${scan_results[$(echo ${state_pos2})]})
elif [[ ${scan_results[$type_pos3]} = $quick_scan ]]; then
scan_time_quick=$(echo ${scan_results[$(echo ${time_pos3[0]})]})
scan_time_quick+=" $(echo ${scan_results[$(echo ${time_pos3[1]})]})"
scan_time_quick+=" $(echo ${scan_results[$(echo ${time_pos3[2]})]})"
scan_time_quick+=" $(echo ${scan_results[$(echo ${time_pos3[3]})]})"
scan_time_quick+=" $(echo ${scan_results[$(echo ${time_pos3[4]})]})"
scan_time_quick+=" $(echo ${scan_results[$(echo ${time_pos3[5]})]})"
scan_state_quick=$(echo ${scan_results[$(echo ${state_pos3})]})
elif [[ ${scan_results[$type_pos4]} = $quick_scan ]]; then
scan_time_quick=$(echo ${scan_results[$(echo ${time_pos4[0]})]})
scan_time_quick+=" $(echo ${scan_results[$(echo ${time_pos4[1]})]})"
scan_time_quick+=" $(echo ${scan_results[$(echo ${time_pos4[2]})]})"
scan_time_quick+=" $(echo ${scan_results[$(echo ${time_pos4[3]})]})"
scan_time_quick+=" $(echo ${scan_results[$(echo ${time_pos4[4]})]})"
scan_time_quick+=" $(echo ${scan_results[$(echo ${time_pos4[5]})]})"
scan_state_quick=$(echo ${scan_results[$(echo ${state_pos4})]})
elif [[ ${scan_results[$type_pos5]} = $quick_scan ]]; then
scan_time_quick=$(echo ${scan_results[$(echo ${time_pos5[0]})]})
scan_time_quick+=" $(echo ${scan_results[$(echo ${time_pos5[1]})]})"
scan_time_quick+=" $(echo ${scan_results[$(echo ${time_pos5[2]})]})"
scan_time_quick+=" $(echo ${scan_results[$(echo ${time_pos5[3]})]})"
scan_time_quick+=" $(echo ${scan_results[$(echo ${time_pos5[4]})]})"
scan_time_quick+=" $(echo ${scan_results[$(echo ${time_pos5[5]})]})"
scan_state_quick=$(echo ${scan_results[$(echo ${state_pos5})]})
elif [[ ${scan_results[$type_pos6]} = $quick_scan ]]; then
scan_time_quick=$(echo ${scan_results[$(echo ${time_pos6[0]})]})
scan_time_quick+=" $(echo ${scan_results[$(echo ${time_pos6[1]})]})"
scan_time_quick+=" $(echo ${scan_results[$(echo ${time_pos6[2]})]})"
scan_time_quick+=" $(echo ${scan_results[$(echo ${time_pos6[3]})]})"
scan_time_quick+=" $(echo ${scan_results[$(echo ${time_pos6[4]})]})"
scan_time_quick+=" $(echo ${scan_results[$(echo ${time_pos6[5]})]})"
scan_state_quick=$(echo ${scan_results[$(echo ${state_pos6})]})
elif [[ ${scan_results[$type_pos7]} = $quick_scan ]]; then
scan_time_quick=$(echo ${scan_results[$(echo ${time_pos7[0]})]})
scan_time_quick+=" $(echo ${scan_results[$(echo ${time_pos7[1]})]})"
scan_time_quick+=" $(echo ${scan_results[$(echo ${time_pos7[2]})]})"
scan_time_quick+=" $(echo ${scan_results[$(echo ${time_pos7[3]})]})"
scan_time_quick+=" $(echo ${scan_results[$(echo ${time_pos7[4]})]})"
scan_time_quick+=" $(echo ${scan_results[$(echo ${time_pos7[5]})]})"
scan_state_quick=$(echo ${scan_results[$(echo ${state_pos7})]})
fi
#check if there are no quick scans found
if [[ -z "$scan_state_quick" ]]; then
scan_state_quick="No Quick Scans detected."
scan_time_quick="No Quick Scans detected."
fi
#write quickscan output to a plist
defaults write "$quick_scan_plist_path" scan_time -string "$scan_time_quick"
defaults write "$quick_scan_plist_path" scan_state -string "$scan_state_quick"
#last full scan
if [[ ${scan_results[$type_pos1]} = $full_scan ]]; then
scan_time_full=$(echo ${scan_results[$(echo ${time_pos1[0]})]})
scan_time_full+=" $(echo ${scan_results[$(echo ${time_pos1[1]})]})"
scan_time_full+=" $(echo ${scan_results[$(echo ${time_pos1[2]})]})"
scan_time_full+=" $(echo ${scan_results[$(echo ${time_pos1[3]})]})"
scan_time_full+=" $(echo ${scan_results[$(echo ${time_pos1[4]})]})"
scan_time_full+=" $(echo ${scan_results[$(echo ${time_pos1[5]})]})"
scan_state_full=$(echo ${scan_results[$(echo ${state_pos1})]})
elif [[ ${scan_results[$type_pos2]} = $full_scan ]]; then
scan_time_full=$(echo ${scan_results[$(echo ${time_pos2[0]})]})
scan_time_full+=" $(echo ${scan_results[$(echo ${time_pos2[1]})]})"
scan_time_full+=" $(echo ${scan_results[$(echo ${time_pos2[2]})]})"
scan_time_full+=" $(echo ${scan_results[$(echo ${time_pos2[3]})]})"
scan_time_full+=" $(echo ${scan_results[$(echo ${time_pos2[4]})]})"
scan_time_full+=" $(echo ${scan_results[$(echo ${time_pos2[5]})]})"
scan_state_full=$(echo ${scan_results[$(echo ${state_pos2})]})
elif [[ ${scan_results[$type_pos3]} = $full_scan ]]; then
scan_time_full=$(echo ${scan_results[$(echo ${time_pos3[0]})]})
scan_time_full+=" $(echo ${scan_results[$(echo ${time_pos3[1]})]})"
scan_time_full+=" $(echo ${scan_results[$(echo ${time_pos3[2]})]})"
scan_time_full+=" $(echo ${scan_results[$(echo ${time_pos3[3]})]})"
scan_time_full+=" $(echo ${scan_results[$(echo ${time_pos3[4]})]})"
scan_time_full+=" $(echo ${scan_results[$(echo ${time_pos3[5]})]})"
scan_state_full=$(echo ${scan_results[$(echo ${state_pos3})]})
elif [[ ${scan_results[$type_pos4]} = $full_scan ]]; then
scan_time_full=$(echo ${scan_results[$(echo ${time_pos4[0]})]})
scan_time_full+=" $(echo ${scan_results[$(echo ${time_pos4[1]})]})"
scan_time_full+=" $(echo ${scan_results[$(echo ${time_pos4[2]})]})"
scan_time_full+=" $(echo ${scan_results[$(echo ${time_pos4[3]})]})"
scan_time_full+=" $(echo ${scan_results[$(echo ${time_pos4[4]})]})"
scan_time_full+=" $(echo ${scan_results[$(echo ${time_pos4[5]})]})"
scan_state_full=$(echo ${scan_results[$(echo ${state_pos4})]})
elif [[ ${scan_results[$type_pos5]} = $full_scan ]]; then
scan_time_full=$(echo ${scan_results[$(echo ${time_pos5[0]})]})
scan_time_full+=" $(echo ${scan_results[$(echo ${time_pos5[1]})]})"
scan_time_full+=" $(echo ${scan_results[$(echo ${time_pos5[2]})]})"
scan_time_full+=" $(echo ${scan_results[$(echo ${time_pos5[3]})]})"
scan_time_full+=" $(echo ${scan_results[$(echo ${time_pos5[4]})]})"
scan_time_full+=" $(echo ${scan_results[$(echo ${time_pos5[5]})]})"
scan_state_full=$(echo ${scan_results[$(echo ${state_pos5})]})
elif [[ ${scan_results[$type_pos6]} = $full_scan ]]; then
scan_time_full=$(echo ${scan_results[$(echo ${time_pos6[0]})]})
scan_time_full+=" $(echo ${scan_results[$(echo ${time_pos6[1]})]})"
scan_time_full+=" $(echo ${scan_results[$(echo ${time_pos6[2]})]})"
scan_time_full+=" $(echo ${scan_results[$(echo ${time_pos6[3]})]})"
scan_time_full+=" $(echo ${scan_results[$(echo ${time_pos6[4]})]})"
scan_time_full+=" $(echo ${scan_results[$(echo ${time_pos6[5]})]})"
scan_state_full=$(echo ${scan_results[$(echo ${state_pos6})]})
elif [[ ${scan_results[$type_pos7]} = $full_scan ]]; then
scan_time_full=$(echo ${scan_results[$(echo ${time_pos7[0]})]})
scan_time_full+=" $(echo ${scan_results[$(echo ${time_pos7[1]})]})"
scan_time_full+=" $(echo ${scan_results[$(echo ${time_pos7[2]})]})"
scan_time_full+=" $(echo ${scan_results[$(echo ${time_pos7[3]})]})"
scan_time_full+=" $(echo ${scan_results[$(echo ${time_pos7[4]})]})"
scan_time_full+=" $(echo ${scan_results[$(echo ${time_pos7[5]})]})"
scan_state_full=$(echo ${scan_results[$(echo ${state_pos7})]})
fi
#check if there are no full scans found
if [[ -z "$scan_state_full" ]]; then
scan_state_full="No Full Scans detected within last 7 scans."
scan_time_full="No Full Scans detected within last 7 scans."
fi
#set a timestamp for the last full scan, to always see in Jamf when the last full scan was done; even if there was no timestamp within the last 7 scans
year_only=$(echo $scan_time_full | awk '{print $3}')
month_only=$(echo $scan_time_full | awk '{print $1}')
day_only=$(echo $scan_time_full | awk '{print $2}' | cut -c 1,2)
time_only=$(echo $scan_time_full | awk '{print $5}')
if [[ $month_only == "Jan" ]]; then
month_only="01"
elif [[ $month_only == "Feb" ]]; then
month_only="02"
elif [[ $month_only == "Mar" ]]; then
month_only="03"
elif [[ $month_only == "Apr" ]]; then
month_only="04"
elif [[ $month_only == "May" ]]; then
month_only="05"
elif [[ $month_only == "Jun" ]]; then
month_only="06"
elif [[ $month_only == "Jul" ]]; then
month_only="07"
elif [[ $month_only == "Aug" ]]; then
month_only="08"
elif [[ $month_only == "Sep" ]]; then
month_only="09"
elif [[ $month_only == "Oct" ]]; then
month_only="10"
elif [[ $month_only == "Nov" ]]; then
month_only="11"
elif [[ $month_only == "Dec" ]]; then
month_only="12"
fi
#combine the variables to match the date format for Jamf: YYYY-MM-DD hh
ss
scan_timestamp_last_full=$(echo "$year_only-$month_only-$day_only $time_only")
#write quickscan output to a plist
defaults write "$full_scan_plist_path" scan_time -string "$scan_time_full"
defaults write "$full_scan_plist_path" scan_state -string "$scan_state_full"
defaults write "$full_scan_plist_path" scan_last_timestemp -string "$scan_timestamp_last_full"