|
| 1 | +#!/bin/zsh |
| 2 | +# shellcheck shell=zsh |
| 3 | +# |
| 4 | +# Script Name: intune_agent_health_time_from_mdm.sh |
| 5 | +# Description : Returns the elapsed time in seconds between Microsoft MDM |
| 6 | +# profile installation and the first healthy Microsoft Intune |
| 7 | +# agent marker. |
| 8 | +# Usage : ./intune_agent_health_time_from_mdm.sh [--last <window>] [--verbose] |
| 9 | +# Default : Searches the last 30d of unified and Intune agent logs. |
| 10 | +# Dependencies: /usr/bin/log, /bin/date, /usr/bin/awk, /usr/bin/find, |
| 11 | +# /usr/bin/sort, /Library/Logs/Microsoft/Intune |
| 12 | +# Output : Prints elapsed seconds as a decimal value, or "unknown" when |
| 13 | +# the required markers are unavailable. |
| 14 | + |
| 15 | +setopt errexit nounset pipefail |
| 16 | + |
| 17 | +LOOKBACK="30d" |
| 18 | +VERBOSE=0 |
| 19 | +LOG_DIR="/Library/Logs/Microsoft/Intune" |
| 20 | +LOCAL_TZ_OFFSET="$(date +%z)" |
| 21 | +MDM_PREDICATE='process == "mdmclient" AND eventMessage CONTAINS[c] "Installed configuration profile: Management Profile (Microsoft.Profiles.MDM"' |
| 22 | + |
| 23 | +MDM_TS="" |
| 24 | +MDM_MS="" |
| 25 | +MDM_LINE="" |
| 26 | +HEALTH_TS="" |
| 27 | +HEALTH_MS="" |
| 28 | +HEALTH_LINE="" |
| 29 | +HEALTH_FILE="" |
| 30 | +HEALTH_LABEL="" |
| 31 | + |
| 32 | +typeset -A FALLBACK_TS FALLBACK_MS FALLBACK_LINE FALLBACK_FILE |
| 33 | + |
| 34 | +usage() { |
| 35 | + cat <<'EOF' |
| 36 | +Usage: intune_agent_health_time_from_mdm.sh [--last <window>] [--verbose] |
| 37 | +
|
| 38 | +Returns the time in seconds from the Microsoft MDM management profile install to |
| 39 | +Intune agent health. |
| 40 | +
|
| 41 | +If the required historical markers are no longer available, the script prints |
| 42 | +"unknown" instead of failing. |
| 43 | +
|
| 44 | +Primary healthy marker: |
| 45 | + HealthCheckWorkflow | Completed health check Domain: regular |
| 46 | +
|
| 47 | +Fallbacks: |
| 48 | + - VerifyEnrollmentStatus | Successfully verified enrollment status. |
| 49 | + - VerifyEnrollmentStatus | Successfully verified device status. |
| 50 | + - VerifyEnrollmentStatus | Retrieved enrollment info. |
| 51 | +EOF |
| 52 | +} |
| 53 | + |
| 54 | +parse_args() { |
| 55 | + while (( $# > 0 )); do |
| 56 | + case "$1" in |
| 57 | + --last) |
| 58 | + (( $# >= 2 )) || { print -u2 -- "Missing value for --last"; exit 2; } |
| 59 | + LOOKBACK="$2" |
| 60 | + shift 2 |
| 61 | + ;; |
| 62 | + -v|--verbose) |
| 63 | + VERBOSE=1 |
| 64 | + shift |
| 65 | + ;; |
| 66 | + -h|--help) |
| 67 | + usage |
| 68 | + exit 0 |
| 69 | + ;; |
| 70 | + *) |
| 71 | + print -u2 -- "Unknown option: $1" |
| 72 | + exit 2 |
| 73 | + ;; |
| 74 | + esac |
| 75 | + done |
| 76 | +} |
| 77 | + |
| 78 | +extract_unified_ts() { |
| 79 | + emulate -L zsh |
| 80 | + local line="$1" |
| 81 | + local -a fields |
| 82 | + fields=(${=line}) |
| 83 | + (( ${#fields} >= 2 )) || return 1 |
| 84 | + print -r -- "${fields[1]} ${fields[2]}" |
| 85 | +} |
| 86 | + |
| 87 | +extract_pipe_ts() { |
| 88 | + emulate -L zsh |
| 89 | + local line="$1" |
| 90 | + local ts="${line%% |*}" |
| 91 | + [[ "$ts" == "$line" ]] && return 1 |
| 92 | + print -r -- "$ts" |
| 93 | +} |
| 94 | + |
| 95 | +to_epoch_ms() { |
| 96 | + emulate -L zsh |
| 97 | + local ts="$1" |
| 98 | + local re='^([0-9]{4}-[0-9]{2}-[0-9]{2}) ([0-9]{2}:[0-9]{2}:[0-9]{2})([.:]([0-9]{1,6}))?([+-][0-9]{4})?$' |
| 99 | + local date_part time_part frac tz epoch |
| 100 | + |
| 101 | + if [[ ! "$ts" =~ $re ]]; then |
| 102 | + return 1 |
| 103 | + fi |
| 104 | + |
| 105 | + date_part="$match[1]" |
| 106 | + time_part="$match[2]" |
| 107 | + frac="${match[4]:-0}" |
| 108 | + tz="${match[5]:-$LOCAL_TZ_OFFSET}" |
| 109 | + frac="${frac}000" |
| 110 | + frac="${frac[1,3]}" |
| 111 | + |
| 112 | + if ! epoch="$(date -j -f "%Y-%m-%d %H:%M:%S %z" "$date_part $time_part $tz" +%s 2>/dev/null)"; then |
| 113 | + return 1 |
| 114 | + fi |
| 115 | + |
| 116 | + print -r -- $(( epoch * 1000 + 10#$frac )) |
| 117 | +} |
| 118 | + |
| 119 | +format_seconds_ms() { |
| 120 | + emulate -L zsh |
| 121 | + local value="$1" |
| 122 | + /usr/bin/awk -v ms="$value" 'BEGIN { printf "%.3f\n", ms / 1000 }' |
| 123 | +} |
| 124 | + |
| 125 | +emit_unknown() { |
| 126 | + emulate -L zsh |
| 127 | + local reason="$1" |
| 128 | + |
| 129 | + if (( VERBOSE )); then |
| 130 | + print "Result : unknown" |
| 131 | + print "Reason : $reason" |
| 132 | + if [[ -n "$MDM_TS" ]]; then |
| 133 | + print "MDM marker : $MDM_TS" |
| 134 | + print "$MDM_LINE" |
| 135 | + fi |
| 136 | + if [[ -n "$HEALTH_TS" ]]; then |
| 137 | + print |
| 138 | + print "Health marker: $HEALTH_TS" |
| 139 | + print "$HEALTH_LINE" |
| 140 | + print "Log file : $HEALTH_FILE" |
| 141 | + print "Marker used : $HEALTH_LABEL" |
| 142 | + fi |
| 143 | + else |
| 144 | + print "unknown" |
| 145 | + fi |
| 146 | +} |
| 147 | + |
| 148 | +find_mdm_marker() { |
| 149 | + emulate -L zsh |
| 150 | + local line ts |
| 151 | + |
| 152 | + line="$(/usr/bin/log show --style compact --info --last "$LOOKBACK" --predicate "$MDM_PREDICATE" 2>/dev/null | /usr/bin/grep 'Installed configuration profile: Management Profile (Microsoft.Profiles.MDM' | tail -1)" |
| 153 | + [[ -n "$line" ]] || return 1 |
| 154 | + ts="$(extract_unified_ts "$line")" || return 1 |
| 155 | + MDM_TS="$ts" |
| 156 | + MDM_MS="$(to_epoch_ms "$ts")" || return 1 |
| 157 | + MDM_LINE="$line" |
| 158 | +} |
| 159 | + |
| 160 | +record_fallback_once() { |
| 161 | + emulate -L zsh |
| 162 | + local key="$1" |
| 163 | + local ts="$2" |
| 164 | + local ms="$3" |
| 165 | + local file="$4" |
| 166 | + local line="$5" |
| 167 | + [[ -n ${FALLBACK_TS[$key]-} ]] && return 0 |
| 168 | + FALLBACK_TS[$key]="$ts" |
| 169 | + FALLBACK_MS[$key]="$ms" |
| 170 | + FALLBACK_FILE[$key]="$file" |
| 171 | + FALLBACK_LINE[$key]="$line" |
| 172 | +} |
| 173 | + |
| 174 | +find_health_marker() { |
| 175 | + emulate -L zsh |
| 176 | + local file line ts ms |
| 177 | + |
| 178 | + while IFS= read -r file; do |
| 179 | + while IFS= read -r line; do |
| 180 | + ts="$(extract_pipe_ts "$line")" || continue |
| 181 | + ms="$(to_epoch_ms "$ts")" || continue |
| 182 | + (( ms >= MDM_MS )) || continue |
| 183 | + |
| 184 | + if [[ "$line" == *"HealthCheckWorkflow | Completed health check Domain: regular"* ]]; then |
| 185 | + HEALTH_TS="$ts" |
| 186 | + HEALTH_MS="$ms" |
| 187 | + HEALTH_FILE="$file" |
| 188 | + HEALTH_LINE="$line" |
| 189 | + HEALTH_LABEL="Completed regular health check" |
| 190 | + return 0 |
| 191 | + fi |
| 192 | + |
| 193 | + if [[ "$line" == *"VerifyEnrollmentStatus | Successfully verified enrollment status."* ]]; then |
| 194 | + record_fallback_once verify_enrollment "$ts" "$ms" "$file" "$line" |
| 195 | + elif [[ "$line" == *"VerifyEnrollmentStatus | Successfully verified device status."* ]]; then |
| 196 | + record_fallback_once verify_device "$ts" "$ms" "$file" "$line" |
| 197 | + elif [[ "$line" == *"VerifyEnrollmentStatus | Retrieved enrollment info."* ]]; then |
| 198 | + record_fallback_once enrollment_info "$ts" "$ms" "$file" "$line" |
| 199 | + fi |
| 200 | + done < "$file" |
| 201 | + done < <(/usr/bin/find "$LOG_DIR" -maxdepth 1 -type f -name 'IntuneMDMDaemon *.log' -print | /usr/bin/sort) |
| 202 | + |
| 203 | + if [[ -n ${FALLBACK_TS[verify_enrollment]-} ]]; then |
| 204 | + HEALTH_TS="${FALLBACK_TS[verify_enrollment]}" |
| 205 | + HEALTH_MS="${FALLBACK_MS[verify_enrollment]}" |
| 206 | + HEALTH_FILE="${FALLBACK_FILE[verify_enrollment]}" |
| 207 | + HEALTH_LINE="${FALLBACK_LINE[verify_enrollment]}" |
| 208 | + HEALTH_LABEL="Successfully verified enrollment status" |
| 209 | + return 0 |
| 210 | + fi |
| 211 | + |
| 212 | + if [[ -n ${FALLBACK_TS[verify_device]-} ]]; then |
| 213 | + HEALTH_TS="${FALLBACK_TS[verify_device]}" |
| 214 | + HEALTH_MS="${FALLBACK_MS[verify_device]}" |
| 215 | + HEALTH_FILE="${FALLBACK_FILE[verify_device]}" |
| 216 | + HEALTH_LINE="${FALLBACK_LINE[verify_device]}" |
| 217 | + HEALTH_LABEL="Successfully verified device status" |
| 218 | + return 0 |
| 219 | + fi |
| 220 | + |
| 221 | + if [[ -n ${FALLBACK_TS[enrollment_info]-} ]]; then |
| 222 | + HEALTH_TS="${FALLBACK_TS[enrollment_info]}" |
| 223 | + HEALTH_MS="${FALLBACK_MS[enrollment_info]}" |
| 224 | + HEALTH_FILE="${FALLBACK_FILE[enrollment_info]}" |
| 225 | + HEALTH_LINE="${FALLBACK_LINE[enrollment_info]}" |
| 226 | + HEALTH_LABEL="Retrieved enrollment info" |
| 227 | + return 0 |
| 228 | + fi |
| 229 | + |
| 230 | + return 1 |
| 231 | +} |
| 232 | + |
| 233 | +main() { |
| 234 | + parse_args "$@" |
| 235 | + find_mdm_marker || { emit_unknown "Unable to find the MDM management profile install event in the requested log window."; return 0; } |
| 236 | + find_health_marker || { emit_unknown "Unable to find a health marker after the MDM profile install in the available Intune logs."; return 0; } |
| 237 | + |
| 238 | + local delta_ms=$(( HEALTH_MS - MDM_MS )) |
| 239 | + (( delta_ms >= 0 )) || { emit_unknown "Healthy marker occurred before the MDM profile install, so the elapsed time cannot be trusted."; return 0; } |
| 240 | + |
| 241 | + if (( VERBOSE )); then |
| 242 | + print "MDM marker : $MDM_TS" |
| 243 | + print "$MDM_LINE" |
| 244 | + print |
| 245 | + print "Health marker: $HEALTH_TS" |
| 246 | + print "$HEALTH_LINE" |
| 247 | + print "Log file : $HEALTH_FILE" |
| 248 | + print "Marker used : $HEALTH_LABEL" |
| 249 | + print |
| 250 | + print "Seconds : $(format_seconds_ms "$delta_ms")" |
| 251 | + return 0 |
| 252 | + fi |
| 253 | + |
| 254 | + format_seconds_ms "$delta_ms" |
| 255 | +} |
| 256 | + |
| 257 | +main "$@" |
0 commit comments