Last Updated on March 4, 2025 by Oktay Sari
In the ever-evolving battleground of endpoint security, Microsoft Defender for Endpoint stands as a vigilant guardian against the rising tide of cyber threats. But even the best guardians can sometimes be a bit… forgetful. What happens when your Microsoft Defender PUA Policy settings start changing mysteriously on your macOS devices? In my previous post we looked at Advanced macOS Protection with Microsoft Intune and I think this post is all about that principle.
The Mystery of the Changing PUA Policies
🚨 Before you sound the security alarm—don’t worry! We’ve tested in multiple tenants, and this only occurs in one specific tenant, strongly suggesting a unique configuration issue rather than a widespread bug or security risk. So no need to cancel your weekend plans or dig up that end-of-the-world playlist from 2012. The investigation is still ongoing, but this approach might help others facing similar configuration mysteries! And on a personal note; I keep on learning…
Shout-out to my partner in crime (and my personal Inspector Gadget) Melvin Luijten for helping with the investigation!
The Problem
Picture this scenario: You’ve configured and deployed your Microsoft Defender for Endpoint PUA Policy (Potentially Unwanted Application) across your macOS fleet. Everything is running smoothly until one day, by sheer luck, you notice that some devices have different settings than what you deployed through Intune. No policy changes were made on your end, yet somehow, the settings drifted on their own. Like finding your furniture rearranged overnight!
After some investigation, we discovered that on certain macOS devices, the Microsoft Defender PUA Policy would occasionally flip between “audit” and “off” modes without any administrator intervention. This ghost in the machine wasn’t just a curiosity but represented a significant security concern. Were our policies being compromised? Was there an issue with Defender for Endpoint itself? Or perhaps an Intune configuration profile problem?
The Challenge
The problem was clear, but the solution not so much. Intune doesn’t natively provide detailed historical tracking of policy changes at the device level, so we needed a way to:
- Monitor the current PUA policy state on each device
- Track when configuration changes occurred
- Report this info back to Intune for visibility
In other words, we needed a lightweight, digital private investigator like Sherlock Holmes, but without the pipe-smoking. From the terminal, you can fire the command âžś ~ mdatp threat policy list to see the current status.
I’ll update this blog with more information about what logfiles to check while investigating this behavior.
Who doesn’t love command line adventures?
I know what you’re thinking: “There are probably a dozen solutions that could tackle this problem with fancy UIs and colorful dashboards.” And you’d be right! But where’s the fun in that?
I’m on this quest to improve my bash scripting skills (a journey that oscillates between moments of “I am a command line wizard!” and “Why isn’t this working? I hate computers.“). So, when faced with mysteriously changing PUA configurations, obviously my first thought was “this sounds like a perfect excuse to write some more bash!“
Before you start climbing to your rooftop to shout about better alternatives, I’ll acknowledge there are certainly other approaches. Scroll down to the end of this post to read about some alternatives I considered. I also invite you to share your insights on how you would tackle this peculiar problem.
And yes, we’re working with Microsoft to investigate this odd Microsoft Defender PUA Policy changes further. In the mean time, I got carried away…
The Two-Script Solution
To tackle this challenge, I’ve developed a two-script system that works together to provide comprehensive tracking of PUA policy changes:
- Enhanced MDATP Policy Checker: Monitors Defender’s PUA policy and logs changes. The Watson to your Holmes.
- Custom Attribute Script: Reports these changes to Intune for centralized visibility. The Inspector Lestrade who actually listens (for those who didn’t watched Sherlock Holmes—It might be an age thingy…).
Let’s break down how each component works and how they collaborate to solve our mystery.
Enhanced MDATP Policy Checker: The Detective
The first script in our duo is responsible for checking the current state of Microsoft Defender PUA policy and maintaining a history of changes. Think of it as the detective who’s constantly gathering clues, minus the caffeine addiction and troubled personal life common to TV detectives.
What It Does
This script performs several functions:
- Verifies that Microsoft Defender processes are running
- Locates and validates the MDATP command-line tool
- Retrieves the current Microsoft Defender PUA policy configuration
- Logs both the current state and a history of changes
The Heart of the Script
The core functionality revolves around running the mdatp threat policy list command and analyzing its output like a digital lie detector test:
#-------------------------------------------------------------------------------
# Function: analyze_pua_configuration
# Purpose: Analyze the PUA configuration from the threat policy
# Uses global variable PUACHECK
#-------------------------------------------------------------------------------
analyze_pua_configuration() {
log "INFO" "Analyzing PUA configuration"
if echo "$PUACHECK" | grep -q "Threat type: potentially_unwanted_application"; then
# PUA policy exists, determine the action setting
if echo "$PUACHECK" | grep -q "Action: audit"; then
log "INFO" "PUA status: Audit (PUAs are detected but not blocked)"
# Store for historical tracking
echo "$(date +"%Y-%m-%d %H:%M:%S") - PUA_ACTION=audit" >> "$POLICY_FILE"
echo "audit" > "$CURRENT_POLICY_FILE"
if [ "$DEBUG" -eq 1 ]; then
echo "=== Enhanced MDATP Policy & Health Checker Completed Successfully ==="
fi
echo "PASSED: PUA in Audit Mode"
return 0
elif echo "$PUACHECK" | grep -q "Action: block"; then
log "INFO" "PUA status: Block (PUAs are actively blocked)"
# Store for historical tracking
echo "$(date +"%Y-%m-%d %H:%M:%S") - PUA_ACTION=block" >> "$POLICY_FILE"
echo "block" > "$CURRENT_POLICY_FILE"
if [ "$DEBUG" -eq 1 ]; then
echo "=== Enhanced MDATP Policy & Health Checker Completed Successfully ==="
fi
echo "PASSED: PUA in Block Mode"
return 0
else
log "WARNING" "PUA status: Unknown (policy exists but action is undefined)"
# Store for historical tracking
echo "$(date +"%Y-%m-%d %H:%M:%S") - PUA_ACTION=unknown" >> "$POLICY_FILE"
echo "unknown" > "$CURRENT_POLICY_FILE"
if [ "$DEBUG" -eq 1 ]; then
echo "=== Enhanced MDATP Policy & Health Checker Completed with Warnings ==="
fi
echo "FAILED: PUA status Unknown"
return 1
fi
else
log "WARNING" "No PUA configuration found"
# Store for historical tracking
echo "$(date +"%Y-%m-%d %H:%M:%S") - [WARNING] No PUA configuration found" >> "$POLICY_FILE"
echo "NOT_CONFIGURED" > "$CURRENT_POLICY_FILE"
if [ "$DEBUG" -eq 1 ]; then
echo "=== Enhanced MDATP Policy & Health Checker Completed with Warnings ==="
fi
echo "FAILED: PUA Not Configured"
return 1
fi
}
This snippet shows how the script extracts the PUA policy action (audit/block/of) and stores it in two different ways:
- In a historical log that maintains a record of all checks
- In a current state file that contains only the latest value
You can download the full scripts from my Github Repository.
The Log Files
The script maintains several log files, each with a specific purpose. It’s like having separate containers for recycling. a place for everything, and everything in its place:
- Main Log (${SCRIPT_NAME%.*}.log): Contains all general script execution information. The day-to-day diary.
- Error Log (${SCRIPT_NAME%.*}_error.log): Just the errors for easier troubleshooting. The “things that went wrong” highlight reel.
- Policy History Log (pua_policy.log): A historical record of all policy states and changes.
- Current Policy File (current_pua_policy.txt): Contains only the latest policy state.
To prevent these logs from growing indefinitely, the script implements log rotation, moving files that exceed 1MB to a .old extension.
Note: For now, it will only keep 1 .old file for historical purposes. These scripts were meant to be used as a troubleshooting mechanism, but could run for as long as you want.
Custom Attribute Script: The Reporter
The second script is our reporter, taking the detective’s findings and turning them into actionable intelligence for Intune admins.
What It Does
This Custom Attribute script:
- Reads the current policy state from log files created by the first script
- Compares it with the previous state to detect changes
- Formats the info for Intune’s Custom Attribute system
- Maintains a record of when changes occurred
The Heart of the Script
The most crucial part of this script is its change detection and reporting logic:
# Format output for Intune Custom Attribute
debug "Formatting output for Intune Custom Attribute"
if [ "$PREVIOUS_STATE" = "INITIAL_CHECK" ]; then
debug "Initial check - reporting current state only"
echo "PUA_Policy=$CURRENT_STATE"
elif [ "$CURRENT_STATE" != "$PREVIOUS_STATE" ]; then
debug "State change detected - reporting change"
echo "PUA_Policy=Changed from $PREVIOUS_STATE to $CURRENT_STATE on $(date '+%Y-%m-%d')"
else
debug "No change in state - reporting with last change date"
echo "PUA_Policy=$CURRENT_STATE (unchanged since $(date -r "$PREVIOUS_STATE_FILE" '+%Y-%m-%d'))"
fi
if [ "$DEBUG" -eq 1 ]; then
echo "=== MDATP Custom Attribute Script Completed ===" >&2
fi
This elegant yet simple logic handles three different scenarios:
- First run: Simply report the current state. “Hello, World!”
- When a change is detected: Report what changed and when. “It’s Complicated.”
- No change: Report the current state and when it was last modified. “Still married after all these years.”
- Report any errors that come along
Fallback Mechanism for Resiliency
What happens if something goes wrong with our primary data source? The Custom Attribute script includes a smart fallback mechanism:
# First check if the current policy file exists and has content
if [ -f "$CURRENT_POLICY_FILE" ] && [ -s "$CURRENT_POLICY_FILE" ]; then
local current_state=$(cat "$CURRENT_POLICY_FILE")
debug "Found current policy file with state: $current_state"
echo "$current_state"
return
fi
debug "Current policy file not found, checking policy log file"
# Fallback to parsing the log file if current policy file is unavailable
if [ ! -f "$POLICY_FILE" ]; then
debug "Policy log file not found, returning NOT_CONFIGURED"
echo "NOT_CONFIGURED"
return
fi
This ensures the scripts remains resilient even if one of the log files is unavailable or corrupted.
How the Scripts Work Together
The beauty of this solution lies in how these two scripts cooperate like a well synchronized dance, but with fewer sequins and more bash commands (I don’t dance anyway):
- The Enhanced MDATP Script runs periodically (e.g., every 15 minutes/1 hour/3 hours), checking Defender’s PUA configuration and logging the results
- The Custom Attribute Script is executed by Intune when collecting custom attributes, reading the logs created by the first script and reporting changes. Normally this is every 8 hours or so.
This separation of tasks creates a system where:
- The heavy lifting is done locally on the device. (checking Defender PUA status is not that hard but hey, just because we can…)
- Only the summarized results are reported back to Intune
- A complete history is maintained locally on the macOS device for troubleshooting
- These logs can also be collected using Intune.
If only all our monitoring systems were so considerate…
What You See in Intune
When the scripts are deployed, here’s what administrators will see in the Intune console under Devices > macOS > Custom Attributes > [pua_custom_attribute_script] :
This provides visibility into:
- Current policy state on each device
- When policies have changed
- If the script itself is working correctly
Remote macOS Log Collection with Intune: No Access Required!
Did you know you can remotely collect logs from macOS devices using Microsoft Intune? This powerful functionality is ideal for troubleshooting issues on devices when you don’t have physical access, or when you’re just too comfortable in your chair to walk across the office.
Sign in to the Microsoft Intune admin center
- Navigate to Devices > macOS > Scripts and select a macOS shell script
- In the Device status report, select your target device
- Select Collect logs
- Enter the paths you want to gather
Example: /Library/Logs/Microsoft/IntuneScripts/mdatp/ pua_policy.log
- Click OK to initiate the collection
The logs will be collected during the next check-in of the Intune management agent, which typically happens every 8 hours.
When the logs are available in Intune, you can collect them by clicking on “Download logs”
Bonus Information
When you collect logs, Intune automatically includes its own agent logs from:
- /Library/Logs/Microsoft/Intune
- ~/Library/Logs/Microsoft/Intune
These additional logs (named IntuneMDMDaemon date–time.log and IntuneMDMAgent date–time.log) can be used for troubleshooting the management agent itself.
If any files you specified cannot be found or have incorrect extensions, you’ll find them listed in a file called LogCollectionInfo.txt in the download. Happy hunting!
Read more on the Microsoft Learn pages
Debug Mode: Peeking Under the Hood
Both scripts include a helpful debug mode that can be activated with a simple environment variable.
sudo DEBUG=1 bash ./enhanced_mdatp_pua.sh
In debug mode, you’ll see a wealth of information printed to the console:
=== Enhanced MDATP Policy & Health Checker Started (Debug Mode) ===
[INFO] Enhanced MDATP Policy & Health Checker started
[INFO] Checking if Microsoft Defender for Endpoint processes are running
[INFO] Process [wdavdaemon_enterprise] is running
[INFO] Process [wdavdaemon_unprivileged] is running
[INFO] Process [wdavdaemon] is running
[INFO] Microsoft Defender is running properly (all processes verified)
[INFO] Current user: root
[INFO] PATH environment: /usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
[INFO] Searching for mdatp location...
[INFO] mdatp location from which: /usr/local/bin/mdatp
[INFO] Found mdatp at: /usr/local/bin/mdatp
[INFO] Checking for Microsoft Defender command-line tool (mdatp)
[INFO] Microsoft Defender command-line tool (mdatp) is available at /usr/local/bin/mdatp
[INFO] Retrieving threat policy information
[INFO] Successfully retrieved threat policy information
[INFO] Analyzing PUA configuration
[INFO] PUA status: Audit (PUAs are detected but not blocked)
=== Enhanced MDATP Policy & Health Checker Completed Successfully ===
PASSED: PUA in Audit Mode
This verbose output is great for troubleshooting and understanding exactly what’s happening at each step of the process when you’re working physically on the device.
What Happens If…?
the Enhanced Script Isn’t Deployed
If only the Custom Attribute script is deployed, it will report:
PUA_Policy=No information available yet – MDATP checker has not run
This makes it easy to identify devices where the scripts aren’t fully deployed (yet). It could also mean that the first script did not run first, and the custom attribute script did run. We don’t use a lock-file check with these scripts.
the Custom Attribute Script Isn’t Deployed
if only the Enhanced script is deployed, you won’t see any information in Intune’s Custom Attributes, but the local logs will still be maintained on the device for manual inspection if needed.
Defender Has an Issue
The Enhanced script includes error checking. If Defender processes aren’t running or the MDATP tool isn’t available, this will be reflected in both the logs and the Custom Attribute:
PUA_Policy=DEFENDER_ERROR
Alternatives to Consider
Are there other ways to monitor Defender policies? Yes, but each has limitations, much like diet options – technically there are alternatives to chocolate cake, but are they really as satisfying?
- Microsoft Defender for Endpoint (Advanced Hunting) Portal: Shows current compliance status but lacks detailed (historical) change tracking.
- Microsoft Sentinel: Can collect and analyze Defender logs but requires additional licensing and setup complexity.
- Third-party monitoring tools: Often come with high costs and may not integrate as seamlessly with Intune.
This solution bridges the gap by providing targeted monitoring with minimal overhead, using technologies you already have deployed.
Putting It All Together
By implementing this two-script solution, we went from a state of uncertainty about our Defender PUA policies to having complete visibility and change tracking. When policies changed unexpectedly, we could see:
- Which devices were affected
- When the change occurred
- What the change was (from/to)
Suspicion or Suspect: The Plot Thickens
As our investigation continues, we’ve narrowed down some potential suspects in our policy-changing mystery. The primary person of interest? Perhaps an old (unassigned) policy configuration that might be playing poorly with newer settings?
We’re looking at old configurations that might be haunting our current setup like digital ghosts. Although we can’t find a trace of these older policies on the devices. What makes this particularly puzzling is that these mysterious changes only manifest on macOS devices, while Windows devices remains blissfully unaffected.
We’ve thoroughly examined the evidence and found no signs of intrusion or malware. No fingerprints at the digital crime scene, if you will. This strongly suggests we’re dealing with a system configuration issue rather than a malicious actor.
What further confirms our suspicion is that we’ve tested in multiple tenants and found this phenomenon only occurs in one specific tenant, strongly suggesting a unique configuration issue rather than a widespread bug or security concern. So no need to sound the security alarm, cancel your weekend plans, or prepare that end-of-the-world playlist you’ve been curating since 2012
The case remains open, and our detective work continues. The monitoring scripts described in this article have become our vigilant deputies, keeping watch and documenting any further unexpected changes while we dig deeper into macOS specific policy behaviors and potential configuration conflicts.
Conclusion
Like any good detective story, sometimes the investigation takes unexpected turns. I’ll update this post as we uncover more clues and hopefully, eventually, crack the case. Until then, our scripts stand guard, ensuring that no policy change goes unnoticed or undocumented.
Get the Scripts
The scripts discussed in this article are available in my GitHub repository. Feel free to adapt them to your environment and specific monitoring needs.