Automating D365 F&O SOX Compliance Audits with Microsoft Graph API and PowerShell

Discover how to bridge Entra ID and D365 F&O using Microsoft Graph API and certificate based PowerShell automation to eliminate audit gaps.

Share
Automating D365 F&O SOX Compliance Audits with Microsoft Graph API and PowerShell

When an employee leaves your organization — disabling their Microsoft Entra ID account gives management a false sense of security.

If their provisioned profile inside Microsoft Dynamics 365 Finance and Operations remains active — your next Sarbanes-Oxley IT General Controls audit will flag a material weakness in access management — triggering severe financial penalties, failed internal controls, and executive scrutiny.

Manual reconciliation spreadsheets cannot keep pace with dynamic enterprise lifecycle changes. In this guide we examine how to bridge the identity governance gap between Microsoft Entra ID and D365 F&O using an automated PowerShell script powered by Microsoft Graph API and certificate based authentication.

The Core Problem: Why Entra ID Deactivation Leaves an ERP Blind Spot

Enterprise security architectures often treat Entra ID as the single source of truth for user lifecycles. When an HR event triggers an account disablement — administrators assume downstream applications automatically revoke permissions.

In Microsoft Dynamics 365 Finance and Operations — user records tied to local security identifiers or custom providers do not always undergo instantaneous bi-directional lifecycle purging unless an explicit orchestration layer exists.

A terminated employee can retain active security roles, license allocations, and transaction posting rights inside the ERP weeks after their corporate login is cut.

For a CFO or IT Audit Director — this unmonitored window represents an open vector for Segregation of Duties breaches and fraudulent activity.

IT Audit Director Question:
How do we prove to external auditors that this sync gap does not exist across our entire global user base without conducting manual quarterly spot-checks?

By moving away from point-in-time spreadsheets and implementing automated API-driven cross-tenant validation that executes continuously.

The Architecture: Microsoft Graph API vs F&O OData

To build a true governance engine — you must query the identity provider directly rather than relying solely on internal ERP tables.

While OData endpoints within D365 F&O such as the SystemUsers data entity can show you who is currently enabled inside the ERP — they cannot tell you the authoritative lifecycle intent originating from Microsoft Entra ID.

By integrating Microsoft Graph API — our automation script pulls real-time account enablement states straight from the tenant's directory service.

We combine this with MSAL Microsoft Authentication Library and certificate based authentication — eliminating hardcoded service account secrets and adhering to modern Zero Trust principles.

Security Architect Question:
Does this certificate based app-only authentication method comply with modern enterprise token and credential rotation policies?

Absolutely. It utilizes asymmetric cryptographic validation — removing interactive user prompts and eliminating the risk of plain-text password leakage in automation scripts.


Live Demonstration: The Four-Step Execution

During our live audit run the script executes across four distinct technical phases:

Step 1 — Certificate Initialization:
Locates and loads the enterprise authentication certificate from the local machine certificate store using its unique thumbprint.

Step 2 — Graph API Extraction:
Connects securely to Microsoft Graph API using app-only permissions User.Read.All to query all disabled user accounts.

Step 3 — ERP OData Extraction:
Authenticates via MSAL to the D365 F&O OData endpoint to pull the active user inventory.

Step 4 — Cross-Reference Audit:
Matches Entra ID User Principal Names against D365 F&O email attributes to identify synchronization anomalies.

powershell
==============================================================================

ENTERPRISE IDENTITY GOVERNANCE AUDIT: MICROSOFT GRAPH API + D365 F&O SOX COMPLIANCE

==============================================================================

Clear-Host
Write-Host "" -ForegroundColor Cyan
Write-Host " MICROSOFT GRAPH & D365 F&O IDENTITY GOVERNANCE ENGINE" -ForegroundColor Cyan
Write-Host "`n" -ForegroundColor Cyan

$tenantId = "YOUR-TENANT-ID"
$clientId = "YOUR-APP-REGISTRATION-CLIENT-ID"
$fnoUrl = "https://YOUR-ENVIRONMENT.operations.dynamics.com"
$thumbprint = "YOUR-CERTIFICATE-THUMBPRINT"

Write-Host "[1/4] Locating Enterprise Certificate..." -ForegroundColor Yellow
$cert = Get-ChildItem -Path Cert:\CurrentUser\My | Where-Object { $_.Thumbprint -eq $thumbprint } | Select-Object -First 1
if (-not $cert) {
Write-Host " [CRITICAL ERROR] Certificate thumbprint not found in store!" -ForegroundColor Red
return
}
Write-Host " [SUCCESS] Certificate loaded: $($cert.Subject)" -ForegroundColor Green

Write-Host "`n[2/4] Authenticating to Microsoft Graph API (App-Only)..." -ForegroundColor Yellow
try {
Connect-MgGraph -ClientId $clientId -TenantId $tenantId -Certificate $cert -NoWelcome | Out-Null
Write-Host " [SUCCESS] Connected to Microsoft Graph." -ForegroundColor Green
} catch {
Write-Host " [CRITICAL ERROR] Graph Auth Failed: $_" -ForegroundColor Red
return
}

Write-Host " Querying Entra ID for disabled user accounts..." -ForegroundColor Yellow
$disabledEntraUsers = Get-MgUser -Filter "accountEnabled eq false" -Property "Id,UserPrincipalName,DisplayName"
Write-Host " [SUCCESS] Found $($disabledEntraUsers.Count) disabled user(s) in Entra ID." -ForegroundColor Cyan

Write-Host "`n[3/4] Authenticating to D365 F&O via MSAL..." -ForegroundColor Yellow
try {
$cert2 = Get-ChildItem -Path Cert:\CurrentUser\My | Where-Object Thumbprint -eq $thumbprint | Select-Object -First 1
$token = Get-MsalToken -ClientId $clientId -TenantId $tenantId -ClientCertificate $cert2 -Scopes "$fnoUrl/.default"
$headers = @{ Authorization = "Bearer $($token.AccessToken)" }
$odataUrl = "$fnoUrl/data/SystemUsers"
$response = Invoke-RestMethod -Uri $odataUrl -Headers $headers -Method Get
$activeFnoUsers = $response.value
Write-Host " [SUCCESS] Extracted $($activeFnoUsers.Count) active ERP user(s) from F&O." -ForegroundColor Green
} catch {
Write-Host " [CRITICAL ERROR] F&O Extraction Failed: $_" -ForegroundColor Red
return
}

Write-Host "`n[4/4] Cross-referencing Entra ID disabled profiles against F&O active records..." -ForegroundColor Yellow
Start-Sleep -Seconds 1

Write-Host "`n--------------------------------------------------------"
Write-Host " SOX COMPLIANCE AUDIT REPORT " -ForegroundColor Green
Write-Host "--------------------------------------------------------"

$violationCount = 0

foreach ($entraUser in $disabledEntraUsers) {
$match = $activeFnoUsers | Where-Object { $_.Alias -eq $entraUser.UserPrincipalName }
if ($match) {
Write-Host "[VIOLATION DETECTED] Terminated User '$($entraUser.DisplayName)' is DISABLED in Entra ID but ACTIVE in D365 F&O!" -ForegroundColor Red
$violationCount++
}
}

if ($violationCount -eq 0) {
Write-Host "[PASS] Zero identity governance violations found. ERP and Entra ID are synchronized." -ForegroundColor Green
} else {
Write-Host "[FAILED] $violationCount SOX Compliance Violation(s) Detected requiring immediate remediation." -ForegroundColor Red
}
Write-Host "========================================================`n" -ForegroundColor Cyan

Microsoft Partner Question:
Why utilize Microsoft Graph API directly within custom PowerShell rather than relying entirely on pre-built connectors in Identity Governance tools?

Custom scripts offer immediate agility, zero licensing overhead for mid-market implementations, and precise control over raw OData payload mapping for specialized ERP instances.


The Kill Shot: Detecting the SOX Compliance Violation

When executed against our test environment — the script processes 117 active ERP profiles and cross-references them against directory-level disablements.

MICROSOFT GRAPH & D365 F&O IDENTITY GOVERNANCE ENGINE

[1/4] Locating Enterprise Certificate...
[SUCCESS] Certificate loaded: CN=D365FOServiceApp2

[2/4] Authenticating to Microsoft Graph API (App-Only)...
[SUCCESS] Connected to Microsoft Graph.
Querying Entra ID for disabled user accounts...
[SUCCESS] Found 1 disabled user(s) in Entra ID.

[3/4] Authenticating to D365 F&O via MSAL...
[SUCCESS] Extracted 117 active ERP user(s) from F&O.

[4/4] Cross-referencing Entra ID disabled profiles against F&O active records...


           SOX COMPLIANCE AUDIT REPORT

[VIOLATION DETECTED] Terminated User 'Peter Procurement' is DISABLED
in Entra ID but remains ACTIVE in D365 F&O!
[FAILED] 1 SOX Compliance Violation(s) Detected requiring immediate remediation

CFO Question:
If a terminated user still holds active ERP access during an audit period — does this directly expose us to material weaknesses and financial penalties?

Yes. Auditors view active access for departed staff as an unmitigated control failure — which can lead to adverse audit opinions and mandatory remediation testing.

Implementation Team Question:
Can this automated script be scheduled as a nightly Azure Automation runbook to proactively revoke access before auditors arrive?

Exactly. Wrapping this core logic inside an Azure Automation webhook allows your team to shift from reactive compliance firefighting to automated continuous prevention.


Frequently Asked Questions

What specific Graph API permissions are required to run this identity audit script?
The app registration must be granted the User.Read.All Application permission within Microsoft Entra ID. Administrative consent must be explicitly applied so the certificate can authenticate securely without user intervention.

How does certificate based authentication improve security over standard client secrets?
Client secrets expire and can be exposed in plain text configuration files. Certificate based authentication uses asymmetric cryptography — public and private key pairs stored in secure machine stores — fulfilling modern enterprise Zero Trust mandates.

Can this script be extended to automatically disable the user inside D365 F&O?
Yes. Once the violation match is confirmed in Step 4 — an additional HTTP PATCH request can be sent to the D365 F&O SystemUsers OData entity to toggle the Enabled property to false automatically.

What Microsoft Graph API version should be used for production deployments?
Always target the stable v1.0 endpoint rather than beta. The v1.0 Graph API for user queries is fully supported and carries Microsoft's production SLA guarantees.

How frequently should this audit script be scheduled in a production environment?
For SOX compliant environments — daily execution is the minimum recommended cadence. High risk enterprises with frequent onboarding and offboarding activity should schedule it every four to six hours via Azure Automation.

Visit sajeedmullaji.com

Read more