Export Microsoft Intune data to Azure Log Analytics for compliance monitoring, endpoint analytics, and custom alerting using Azure Functions.
- Azure CLI
- Azure Functions Core Tools v4
- Az PowerShell Module (for permission scripts)
# Windows - Install prerequisites
winget install Microsoft.Azure.FunctionsCoreTools
Install-Module -Name Az -Scope CurrentUser -Force
Install-Module -Name Microsoft.Graph -Scope CurrentUser -Force
# macOS
brew tap azure/functions && brew install azure-functions-core-tools@4# Clone the repo
git clone https://github.com/JacobWLMS/IntuneReporting.git
cd IntuneReporting
# Login to Azure
az login
Connect-AzAccount
# Deploy everything (creates all Azure resources)
.\deployment\deploy.ps1 -Name "intune-reports" -Location "uksouth"The script creates:
- Resource Group
- Log Analytics Workspace with 10 custom tables
- Data Collection Endpoint & Rule (DCE/DCR)
- Function App (Flex Consumption, Python 3.11) with system-assigned managed identity
After deployment, grant the required permissions:
# Option 1: Use the permissions script (recommended)
.\deployment\scripts\Grant-GraphPermissions.ps1 -ServicePrincipalObjectId "<object-id-from-deploy-output>"
# Option 2: Quick script for Managed Identity
.\deployment\scripts\Configure-Permissions.ps1Required Graph API Permissions:
| Permission | Purpose |
|---|---|
DeviceManagementManagedDevices.Read.All |
Read device inventory & endpoint analytics |
DeviceManagementConfiguration.Read.All |
Read compliance policies & device status reports |
DeviceManagementServiceConfig.Read.All |
Read Autopilot devices & profiles |
User.Read.All |
Read Entra ID user profiles |
AuditLog.Read.All |
Read user sign-in activity (last sign-in dates) |
Required Azure Role:
| Role | Scope | Purpose |
|---|---|---|
Monitoring Metrics Publisher |
Data Collection Rule | Ingest data to Log Analytics |
Trigger exports on-demand without waiting for scheduled timers. Useful for testing and initial data population.
| Method | Endpoint | Description |
|---|---|---|
GET/POST |
/api/export |
List available export types |
GET/POST |
/api/export/devices |
Export device inventory |
GET/POST |
/api/export/compliance |
Export compliance policies & states |
GET/POST |
/api/export/analytics |
Export endpoint analytics scores |
GET/POST |
/api/export/autopilot |
Export Autopilot devices & profiles |
GET/POST |
/api/export/users |
Export Entra ID user profiles |
GET/POST |
/api/export/all |
Run all exports sequentially |
GET/POST |
/api/export/test |
Send a test record to Log Analytics |
GET/POST |
/api/export/health |
Check auth, Graph API, and ingestion connectivity |
All endpoints require a function key. Get it from Azure Portal or CLI:
# Get function key
az functionapp keys list -g <resource-group> -n <function-app> --query "functionKeys.default" -o tsvPowerShell:
$functionKey = "<your-function-key>"
$baseUrl = "https://<function-app>.azurewebsites.net/api/export"
# Run single export
$response = Invoke-RestMethod -Uri "$baseUrl/devices?code=$functionKey" -Method POST -TimeoutSec 300
$response | Format-List
# Run all exports
$response = Invoke-RestMethod -Uri "$baseUrl/all?code=$functionKey" -Method POST -TimeoutSec 600
$response.results | Format-Tablecurl:
# Run device export
curl -X POST "https://<function-app>.azurewebsites.net/api/export/devices?code=<function-key>"
# Run all exports
curl -X POST "https://<function-app>.azurewebsites.net/api/export/all?code=<function-key>"{
"message": "Export Devices completed",
"export_type": "devices",
"records": 8656,
"duration_seconds": 45.23
}For /api/export/all:
{
"message": "All exports completed",
"results": [
{"export_type": "devices", "records": 8656, "status": "success"},
{"export_type": "compliance", "records": 13070, "status": "success"},
{"export_type": "analytics", "records": 4046, "status": "success"},
{"export_type": "autopilot", "records": 0, "status": "error", "error": "..."}
],
"total_duration_seconds": 120.5
}| Function | Schedule | Tables | Data |
|---|---|---|---|
export_devices |
Every 4 hours | IntuneManagedDevices_CL |
Device inventory, hardware, compliance state |
export_compliance |
Every 6 hours | IntuneCompliancePolicies_CL, IntuneComplianceStates_CL |
Policies & per-device compliance |
export_endpoint_analytics |
Daily 8 AM | IntuneDeviceScores_CL, IntuneStartupPerformance_CL, IntuneAppReliability_CL |
Health scores, performance metrics |
export_autopilot |
Daily 6 AM | IntuneAutopilotDevices_CL, IntuneAutopilotProfiles_CL |
Autopilot enrollment status |
export_users |
Daily 2 AM | IntuneUsers_CL |
Entra ID user profiles, department, sign-in activity |
| Table | Key Fields |
|---|---|
IntuneManagedDevices_CL |
DeviceId, DeviceName, UserPrincipalName, ComplianceState, OperatingSystem, LastSyncDateTime, JoinType, ChassisType |
IntuneUsers_CL |
UserId, UserPrincipalName, Department, AccountEnabled, LastSignInDateTime |
IntuneCompliancePolicies_CL |
PolicyId, PolicyName, PolicyType, CreatedDateTime |
IntuneComplianceStates_CL |
DeviceId, PolicyId, Status, LastContact |
IntuneDeviceScores_CL |
DeviceId, EndpointAnalyticsScore, StartupPerformanceScore, AppReliabilityScore |
IntuneStartupPerformance_CL |
DeviceId, CoreBootTimeInMs, TotalBootTimeInMs |
IntuneAppReliability_CL |
AppName, AppCrashCount, AppHealthScore |
IntuneAutopilotDevices_CL |
SerialNumber, EnrollmentState, GroupTag |
IntuneAutopilotProfiles_CL |
ProfileId, DisplayName, DeviceNameTemplate |
IntuneSyncState_CL |
ExportType, RecordCount, DurationSeconds, Status |
┌──────────────────┐ ┌────────────────────┐ ┌──────────────────┐
│ Microsoft │ │ Azure Functions │ │ Log Analytics │
│ Graph API │────▶│ (Python 3.11) │────▶│ Workspace │
│ (Intune Data) │ │ Flex Consumption │ │ (DCE/DCR) │
└──────────────────┘ └────────────────────┘ └──────────────────┘
│ │ │
│ ▼ ▼
│ ┌────────────────────┐ ┌──────────────────┐
│ │ Timer Triggers │ │ Azure Workbooks │
│ │ HTTP Triggers │ │ + Alerts │
│ └────────────────────┘ └──────────────────┘
│
▼
┌──────────────────┐
│ App Registration │
│ (Graph Perms) │
└──────────────────┘
Cost: ~$0/month on Flex Consumption (pay per execution, scales to zero)
Import Azure Monitor workbooks for visualization. In Azure Portal:
- Go to Log Analytics workspace → Workbooks
- Click New → Advanced Editor
- Paste the JSON from any workbook file
- Click Apply then Done Editing
| Workbook | Purpose |
|---|---|
| device-inventory.workbook | Complete device fleet overview with filtering |
| compliance-overview.workbook | Compliance rates, policy analysis, non-compliant devices |
| device-health.workbook | Health scores, sync freshness, devices needing attention |
| autopilot-deployment.workbook | Autopilot enrollment tracking and failures |
| stale-devices.workbook | Guided stale device investigation and cleanup workflow |
Set automatically by deploy.ps1 (managed identity mode):
| Variable | Description |
|---|---|
USE_MANAGED_IDENTITY |
Set to true — uses the Function App's managed identity for all auth |
AZURE_TENANT_ID |
Entra ID tenant ID |
LOG_ANALYTICS_DCE |
Data Collection Endpoint URL |
LOG_ANALYTICS_DCR_ID |
Data Collection Rule immutable ID (starts with dcr-) |
For app registration / local development (set USE_MANAGED_IDENTITY=false):
| Variable | Description |
|---|---|
AZURE_CLIENT_ID |
App registration client ID |
AZURE_CLIENT_SECRET |
App registration client secret |
When all three of AZURE_TENANT_ID, AZURE_CLIENT_ID, and AZURE_CLIENT_SECRET are present and USE_MANAGED_IDENTITY is not true, ClientSecretCredential is used for both Graph API and Log Analytics ingestion. The app registration's service principal must have Monitoring Metrics Publisher on the DCR.
| Script | Purpose |
|---|---|
setup-local.ps1 |
Set up local development environment (venv, dependencies, settings file) |
deployment/deploy.ps1 |
Full deployment (resources + code) |
deployment/scripts/Grant-GraphPermissions.ps1 |
Grant Graph API permissions to service principal or managed identity |
deployment/scripts/Configure-Permissions.ps1 |
Quick permission setup for Managed Identity |
deployment/scripts/Update-DCRSchema.ps1 |
Update DCR schema when adding tables or columns |
- Check function executed: Azure Portal → Function App → Functions → Monitor
- Check for errors in logs:
func azure functionapp logstream <function-app> - Verify DCR permissions: Managed Identity needs
Monitoring Metrics Publisheron DCR - Data ingestion delay: Allow 5-10 minutes for data to appear
- Verify App Registration has correct Graph API permissions
- Ensure admin consent was granted (Enterprise Applications → Permissions)
- Check client secret hasn't expired
- Default timeout: 10 minutes
- For large tenants (10k+ devices), consider:
- Flex Consumption with higher timeout
- Split exports into smaller batches
- Built-in retry with exponential backoff handles this automatically
- Check logs for retry messages
- Reduce schedule frequency if persistent
- Add the field to the Python export dict in
functions/export_*/ - Add the column to
deployment/scripts/Update-DCRSchema.ps1 - Run the schema update, then deploy the code:
.\deployment\scripts\Update-DCRSchema.ps1
cd functions && func azure functionapp publish <function-app-name> --pythonSchema update must go first — if the code deploys before the column exists in the DCR, Log Analytics silently drops that field.
The Log Analytics table must exist before the DCR will accept a new stream referencing it. Create the table first, then update the schema:
# 1. Create the table (see New-CustomTables pattern in deploy.ps1)
# 2. Update DCR schema
.\deployment\scripts\Update-DCRSchema.ps1
# 3. Deploy code
cd functions && func azure functionapp publish <function-app-name> --pythoncd functions
func azure functionapp publish <function-app-name> --python- Python 3.11 (
winget install Python.Python.3.11) - Azure Functions Core Tools v4 (
winget install Microsoft.Azure.FunctionsCoreTools) - Azurite — local Azure Storage emulator (
npm install -g azurite)
.\setup-local.ps1This checks prerequisites, creates a Python virtual environment at functions/.venv/, installs dependencies, and copies local.settings.json.example → local.settings.json.
cd functions
python3.11 -m venv .venv
.\.venv\Scripts\Activate.ps1
pip install -r requirements.txt
cp local.settings.json.example local.settings.jsonEdit functions/local.settings.json and fill in:
| Setting | Value |
|---|---|
AZURE_TENANT_ID |
Your Entra tenant ID |
AZURE_CLIENT_ID |
App registration client ID |
AZURE_CLIENT_SECRET |
App registration client secret |
LOG_ANALYTICS_DCE |
Data Collection Endpoint URL |
LOG_ANALYTICS_DCR_ID |
DCR immutable ID (starts with dcr-) |
The app registration needs Monitoring Metrics Publisher on the DCR and the Graph API permissions listed above.
Start Azurite in one terminal (required for AzureWebJobsStorage):
azurite --location .azuriteStart the Functions host in another terminal:
cd functions
.\.venv\Scripts\Activate.ps1
func start# Check auth + Graph API connectivity
curl http://localhost:7071/api/export/health
# Send a test record to Log Analytics
curl -X POST http://localhost:7071/api/export/test
# Trigger a full export
curl -X POST http://localhost:7071/api/export/devicesNo function key is required locally — Core Tools accepts requests without one by default.
Install the Azure Functions extension, then press F5. This starts the Functions host and attaches the Python debugger so you can set breakpoints in any export function.
MIT