I've spent the past year building automated Microsoft 365 posture assessments, and the biggest challenge has been identifying controls that cannot be read programmatically. Here are the gaps I've found:
- Entra Password Protection: the custom banned-password list and on-premises agent status appear to be portal-only.
- Defender for Cloud Apps: alerts are available, but policy configuration and Cloud Discovery settings are not. Through the API, a licensed tenant with no policies can look identical to one that is fully configured.
- Entra diagnostic settings: there doesn't seem to be a Graph API path for checking whether sign-in and audit logs are being exported to Log Analytics or storage. That makes it difficult to provide evidence for requirements such as PCI DSS 10.1, SOC 2 CC7.2, and ISO 27001 A.8.15.
- Defender for Office 365 Tenant Allow/Block List: I haven't found a documented app-only read path.
- Information barriers: reading policies requires a highly privileged role, with no obvious read-only alternative.
I also ran into an Exchange app-only authentication issue. The documentation points to `Exchange.ManageAsAppV2`, but granting and consenting that permission produced tokens containing the role while every call to `POST https://outlook.office365.com/adminapi/beta/{tenantId}/InvokeCommand` returned an empty 403 response. The Exchange Online PowerShell module failed the same way, which made the problem especially confusing.
The working setup used `Exchange.ManageAsApp` instead of V2. The app role also needs to be assigned to the service principal in each tenant; admin consent for the app registration alone is not enough. The role ID I used was `dc50a0fb-09a3-484d-be87-e023b12c6440`.
Has anyone found a reliable way to verify Entra diagnostic export settings directly? I'm especially interested in approaches that can provide defensible log-retention evidence.
1 Answer
For diagnostic settings, I’d verify the destination rather than trying to read the configuration switch. For a Log Analytics workspace, query for recent `AuditLogs` and `SigninLogs`, for example:
`AuditLogs | where TimeGenerated > ago(6h) | summarize count()`
`SigninLogs | where TimeGenerated > ago(6h) | summarize count()`
Recent rows show that data is actually flowing, which is usually stronger evidence than simply proving that a setting is enabled. It also catches real operational failures such as an expired or incorrect workspace ID, insufficient retention, or a destination that has stopped receiving data.
The same pattern works elsewhere: check for recent blobs in a storage account or recent ingestion in an Event Hub. A posture tool can run these checks on a schedule and alert when no data arrives within the expected window. Graph audit endpoints can confirm that Entra logs exist, but they don’t prove that the export pipeline is working.

That’s a much better approach than checking a static flag. Verifying the sink also catches the failures that a configuration-only check would miss.