A paste into Intune that lost the first "{" line produced an unreadable
mdatp_onboard.json, and Defender reported "No license found". Check that
the pasted JSON parses and has onboardingInfo before writing it, and log
what to fix otherwise.
The placeholder also appeared twice, so a find-and-replace of it broke
the script; match it by prefix so it appears only once.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
111 lines
4.2 KiB
Bash
Executable File
111 lines
4.2 KiB
Bash
Executable File
#!/bin/bash
|
|
# Intune custom configuration script for Linux: bring Microsoft Defender for
|
|
# Endpoint (mdatp) back to the state defender-compliance.sh checks for.
|
|
#
|
|
# Intune has no "run only when non-compliant" trigger for Linux, so this runs
|
|
# on the policy's schedule and only acts on what is wrong; on a healthy device
|
|
# it only reads `mdatp health`. Himmelblau installs it as an /etc/cron.d job.
|
|
#
|
|
# Intune > Devices > Configuration > Create > Linux > Settings catalog >
|
|
# Custom Configuration:
|
|
# Execution context: root
|
|
# Execution frequency: every 1 hour (or 15 minutes while rolling out)
|
|
# Execution retries: 1
|
|
# Script: this file, with ONBOARDING_JSON filled in
|
|
#
|
|
# Exit status is non-zero when Defender is still not healthy afterwards.
|
|
|
|
# Contents of mdatp_onboard.json from the Defender portal: Settings > Endpoints
|
|
# > Onboarding > Linux Server > "Local Script (Python)". The downloaded
|
|
# MicrosoftDefenderATPOnboardingLinuxServer.py writes this file to
|
|
# /etc/opt/microsoft/mdatp/ on the machine it runs on; paste that file's
|
|
# contents here. Leave the placeholder to skip onboarding.
|
|
ONBOARDING_JSON=$(cat <<'JSON'
|
|
REPLACE_WITH_MDATP_ONBOARD_JSON
|
|
JSON
|
|
)
|
|
|
|
MDATP=/usr/bin/mdatp
|
|
ONBOARD_FILE=/etc/opt/microsoft/mdatp/mdatp_onboard.json
|
|
|
|
log() {
|
|
logger -t defender-remediation -- "$*"
|
|
echo "$*"
|
|
}
|
|
|
|
# Print one `mdatp health` field without quotes, a trailing " [managed]" or
|
|
# the "ATTENTION: ..." lines mdatp prints while unlicensed.
|
|
field() {
|
|
"${MDATP}" health --field "$1" 2>/dev/null | grep -v '^ATTENTION' | tail -n1 \
|
|
| sed -e 's/ \[managed\]$//' -e 's/^"//' -e 's/"$//'
|
|
}
|
|
|
|
# Wait up to 2 minutes for the daemon to answer.
|
|
wait_for_daemon() {
|
|
local i
|
|
for i in $(seq 1 24); do
|
|
[[ "$(field healthy)" =~ ^(true|false)$ ]] && return 0
|
|
sleep 5
|
|
done
|
|
return 1
|
|
}
|
|
|
|
if [[ ! -x "${MDATP}" ]]; then
|
|
log "mdatp is not installed; it ships in the image, so update or rebase the device."
|
|
exit 1
|
|
fi
|
|
|
|
# The image finishes the Defender install on first boot (mdatp-firstboot).
|
|
if [[ ! -f /var/lib/mdatp-firstboot.done ]]; then
|
|
log "Defender first-boot setup has not completed; starting it."
|
|
systemctl start mdatp-firstboot.service
|
|
fi
|
|
|
|
if ! systemctl is-active --quiet mdatp.service; then
|
|
log "mdatp.service is not running; restarting it."
|
|
systemctl restart mdatp.service
|
|
fi
|
|
if ! wait_for_daemon; then
|
|
log "The Defender daemon does not respond."
|
|
exit 1
|
|
fi
|
|
|
|
if [[ -z "$(field org_id)" ]]; then
|
|
# Prefix match, so the placeholder appears only once in this file and a
|
|
# find-and-replace of it can't hit this line.
|
|
if [[ "${ONBOARDING_JSON}" == REPLACE_WITH_* ]]; then
|
|
log "Defender is not onboarded and this script has no onboarding JSON."
|
|
elif ! printf '%s\n' "${ONBOARDING_JSON}" \
|
|
| python3 -c 'import json, sys; assert "onboardingInfo" in json.load(sys.stdin)' 2>/dev/null; then
|
|
# A paste that lost the first "{" or last "}" line would otherwise leave
|
|
# Defender with an unreadable onboarding file ("No license found").
|
|
log "The onboarding JSON in this script is not valid; paste mdatp_onboard.json again, including its first '{' and last '}' lines."
|
|
exit 1
|
|
else
|
|
log "Defender is not onboarded; writing ${ONBOARD_FILE}."
|
|
install -d -m 0755 "$(dirname "${ONBOARD_FILE}")"
|
|
printf '%s\n' "${ONBOARDING_JSON}" > "${ONBOARD_FILE}.tmp"
|
|
chown root:mdatp "${ONBOARD_FILE}.tmp"
|
|
chmod 0600 "${ONBOARD_FILE}.tmp"
|
|
mv -f "${ONBOARD_FILE}.tmp" "${ONBOARD_FILE}"
|
|
systemctl restart mdatp.service
|
|
wait_for_daemon || { log "The Defender daemon does not respond after onboarding."; exit 1; }
|
|
fi
|
|
fi
|
|
|
|
# Settings Defender's own policy manages show " [managed]" and can't be
|
|
# changed locally; this only fixes a locally disabled real-time protection.
|
|
if [[ "$(field real_time_protection_enabled)" != true ]]; then
|
|
log "Real-time protection is off; enabling it."
|
|
"${MDATP}" config real-time-protection --value enabled
|
|
fi
|
|
|
|
if [[ "$(field definitions_status)" != up_to_date ]]; then
|
|
log "Definitions are not up to date; updating them."
|
|
"${MDATP}" definitions update
|
|
fi
|
|
|
|
healthy="$(field healthy)"
|
|
log "healthy=${healthy} licensed=$(field licensed) org_id=$(field org_id) rtp=$(field real_time_protection_enabled) definitions=$(field definitions_status)"
|
|
[[ "${healthy}" == true ]]
|