#!/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 ]]