Enterprise Setup Guide

Deploy MDE Toolkit across your fleet with centralized telemetry collection, Azure Function App ingestion, and Power BI-ready Table Storage.

1. Architecture Overview

The MDE Toolkit enterprise collector runs silently on each endpoint, collecting security telemetry and uploading it to a central Azure Function App for storage in Azure Table Storage.

┌──────────────────────────────────────┐
│         Endpoint (Windows PC)        │
│                                      │
│  ┌────────────────────────────────┐  │
│  │  Scheduled Task: Collect       │  │      HTTPS/Bearer       ┌──────────────────────┐      Table API       ┌─────────────────────┐
│  │  Runs as: SYSTEM               │  │                         │  Azure Function App  │                      │  Azure Table        │
│  │  Every 8 hours                 │  │                         │  (PowerShell, MI)    │                      │  Storage            │
│  │  --collect --no-upload         │──┼── writes to cache ──┐   │                      │                      │                     │
│  └────────────────────────────────┘  │                     │   │  • Validates JSON    │                      │  • HealthReports    │
│                                      │                     │   │  • Flattens fields   │                      │  • One row/device   │
│  ┌────────────────────────────────┐  │                     │   │  • Writes to Table   │                      │  • Power BI ready   │
│  │  Scheduled Task: Upload        │  │  reads cache ───────┘   │                      │                      │                     │
│  │  Runs as: Interactive User     │  │ ────────────────────►   │                      │ ──────────────────►  │                     │
│  │  (BUILTIN\Users group)         │  │    POST /api/ingest     │                      │    Upsert entity     │                     │
│  │  Every 8 hours                 │  │   + Entra bearer token  │                      │                      │                     │
│  │  --upload                      │  │                         │                      │                      │                     │
│  └────────────────────────────────┘  │                         └──────────────────────┘                      └─────────────────────┘
│                                      │                                 │                                            │
│  Registry config                     │                                 │  Managed Identity                          │  Power BI
│  HKLM\SOFTWARE\                      │                                 │  (Storage Table                            │  or custom
│  Policies\MDE-Toolkit                │                                 │   Data Contributor)                        │  dashboard
└──────────────────────────────────────┘
No secrets on endpoints. Collection runs as SYSTEM (full local access) while upload runs as the interactive user (has an Entra ID PRT / WAM token). The Function App uses Managed Identity to write to Table Storage. No keys, connection strings, or certificates are deployed to endpoints.
Why two tasks? SYSTEM can read all local Defender configuration but cannot acquire an Entra ID user token. The logged-on user has a Primary Refresh Token (PRT) that DefaultAzureCredential can use to authenticate to Azure. Splitting collect and upload gives you the best of both worlds.

2. Prerequisites

Azure Resources

  • Azure Subscription (Commercial or Government)
  • Azure Function App (PowerShell runtime, Consumption or App Service plan)
  • Azure Storage Account with Table Storage enabled
  • Entra ID App Registration (for Function App audience)

Endpoints

  • Windows 10 1809+ or Windows 11
  • .NET 8 Desktop Runtime
  • Entra ID joined or Hybrid joined (for token-based auth)
  • Network access to Function App URL

3. Azure Resource Setup

3.1 Create Storage Account

Azure CLI
# Create resource group
az group create --name rg-mde-toolkit --location eastus

# Create storage account
az storage account create \
  --name stmdetoolkit \
  --resource-group rg-mde-toolkit \
  --sku Standard_LRS \
  --kind StorageV2

# Create the table (optional — the Function App auto-creates it)
az storage table create \
  --name HealthReports \
  --account-name stmdetoolkit

3.2 Create Function App

Azure CLI
# Create Function App (PowerShell 7.4 runtime)
az functionapp create \
  --name func-mde-toolkit \
  --resource-group rg-mde-toolkit \
  --storage-account stmdetoolkit \
  --consumption-plan-location eastus \
  --runtime powershell \
  --runtime-version 7.4 \
  --functions-version 4 \
  --os-type Windows

# Enable system-assigned Managed Identity
az functionapp identity assign \
  --name func-mde-toolkit \
  --resource-group rg-mde-toolkit

# Set app settings
az functionapp config appsettings set \
  --name func-mde-toolkit \
  --resource-group rg-mde-toolkit \
  --settings \
    StorageAccountName=stmdetoolkit \
    StorageTableName=HealthReports \
    AzureEnvironment=Public

3.3 Grant Table Storage Access

The Function App's Managed Identity needs Storage Table Data Contributor on the storage account:

Azure CLI
# Get the Function App's principal ID
PRINCIPAL_ID=$(az functionapp identity show \
  --name func-mde-toolkit \
  --resource-group rg-mde-toolkit \
  --query principalId -o tsv)

# Get the storage account resource ID
STORAGE_ID=$(az storage account show \
  --name stmdetoolkit \
  --resource-group rg-mde-toolkit \
  --query id -o tsv)

# Assign Storage Table Data Contributor
az role assignment create \
  --assignee $PRINCIPAL_ID \
  --role "Storage Table Data Contributor" \
  --scope $STORAGE_ID

3.4 Register Entra ID App (Function App Audience)

Create an App Registration so endpoints can acquire a bearer token scoped to the Function App:

Azure CLI
# Create app registration
az ad app create \
  --display-name "MDE Toolkit Function" \
  --identifier-uris "api://mde-toolkit-func" \
  --sign-in-audience AzureADMyOrg

# Note the Application (client) ID — this becomes the FunctionAppAudience
# registry value: api://mde-toolkit-func

4. Function App Deployment

The Function App is a single PowerShell HTTP trigger that receives the JSON snapshot from each endpoint, flattens it into table-friendly columns, and upserts it into Azure Table Storage.

4.1 Project Structure

File Layout
FunctionApp/
├── host.json
├── requirements.psd1
├── profile.ps1
└── HealthReportIngestion/
    ├── function.json          # HTTP POST trigger binding
    └── run.ps1                # Ingestion logic

4.2 function.json

FunctionApp/HealthReportIngestion/function.json
{
  "bindings": [
    {
      "authLevel": "anonymous",
      "type": "httpTrigger",
      "direction": "in",
      "name": "Request",
      "methods": ["post"]
    },
    {
      "type": "http",
      "direction": "out",
      "name": "Response"
    }
  ]
}
Auth level is "anonymous" because authentication is handled by the Entra ID bearer token in the request header, not by a Function App key. The Function App validates the token via the Entra ID middleware configured on the Function App in the Azure Portal.

4.3 run.ps1 — How It Works

The PowerShell script performs these steps on each incoming request:

  1. Parse the JSON body — handles both string and pre-deserialized objects
  2. Extract identity fields — hostname, organizationId from headers or JSON
  3. Build a flat entity — maps ~80 fields for Azure Table using coalesce helpers that try flat root-level fields first (from MDE Toolkit), then nested object paths (for other senders)
  4. Acquire a Managed Identity token for https://storage.azure.com
  5. Upsert to Table Storage — creates the table if needed, then PUTs the entity
  6. Return success — JSON response with reportId, hostname, healthScore

4.4 Table Columns

Every row in the HealthReports table contains these categories of flat columns, plus the full JSON in ReportJson:

CategoryColumns
IdentityHostname, OsVersion, OrganizationId, DeviceGroup
Defender AVDefenderEnabled, RealTimeProtectionEnabled, BehaviorMonitorEnabled, TamperProtectionEnabled, SignatureAgeHours, DefenderAvStatus
MDE / SenseIsOnboarded, SenseIsRunning, MdeStatus
ASRAsrIsConfigured, AsrTotalRules, AsrBlockModeRules, AsrAuditModeRules, AsrStatus
FirewallFirewallDomainEnabled, FirewallPrivateEnabled, FirewallPublicEnabled, FirewallStatus
Network ProtectionNetworkProtectionMode, NetworkProtectionStatus
Device ControlDeviceControlEnabled, DeviceControlRuleCount, DeviceControlStatus
App ControlAppControlIsConfigured, AppControlIsEnabled, AppControlStatus
VBS / HVCIVbsEnabled, VbsStatus, HvciConfigured, HvciRunning, CredentialGuardConfigured, CredentialGuardRunning
Entra IDIsAzureAdJoined, IsHybridJoined
HealthHealthScore (0-100), OverallStatus, CriticalEventCount
Full ReportReportJson — complete snapshot for drill-down in Power BI

4.5 Deploy

Azure CLI — Deploy from local folder
# From the repo root, zip and deploy the Function App
cd FunctionApp
func azure functionapp publish func-mde-toolkit --powershell

4.6 Government Cloud

For Azure Government, set AzureEnvironment=Government in app settings. The script auto-detects the storage endpoint suffix (core.usgovcloudapi.net) and uses the correct OAuth resource URI.

5. Registry Configuration

All MDE Toolkit enterprise settings are read from a single registry key. Deploy these via GPO Preferences, Intune Settings Catalog (custom OMA-URI), SCCM, or any registry management tool.

Registry Path
HKLM\SOFTWARE\Policies\MDE-Toolkit

5.1 Background Collector

Value NameTypeDefaultDescription
EnableBackgroundCollectorDWORD01 = enable background collection, 0 = off
CollectionIntervalMinutesDWORD480Minutes between collections (minimum 5, default 8 hours)
CacheFolderSZ%ProgramData%\MDE-Toolkit\cacheLocal folder for JSON snapshot cache
MaxCacheFilesDWORD48Rolling file count — oldest deleted first
CacheRetentionDaysDWORD30Delete files older than N days (0 = no age limit)

5.2 Upload: Function App

Value NameTypeDefaultDescription
EnableUploadDWORD01 = enable upload to Function App
FunctionAppUrlSZFull URL, e.g. https://func-mde-toolkit.azurewebsites.net/api/HealthReportIngestion
FunctionAppAudienceSZEntra app audience, e.g. api://mde-toolkit-func
UploadIntervalMinutesDWORD480How often to upload (minimum 5, default 8 hours)
UploadTimeoutSecondsDWORD30HTTP POST timeout (5–300)
UploadRetryCountDWORD2Retries on transient failure (0–10)

5.3 Upload: Direct Blob Storage (Alternative)

Value NameTypeDefaultDescription
EnableBlobUploadDWORD01 = enable direct blob upload
BlobStorageAccountNameSZStorage account name
BlobContainerNameSZmde-telemetryBlob container name
BlobEndpointSuffixSZe.g. blob.core.usgovcloudapi.net for Gov Cloud

5.4 Identity / Authentication

Value NameTypeDefaultDescription
TenantIdSZ(auto)Entra tenant ID — auto-detected from PRT if blank
PreferredAuthMethodSZDefaultDefault | WAM | DeviceCode | ManagedIdentity
AzureEnvironmentSZPublicPublic | USGov | China | Germany
AuthorityHostOverrideSZCustom authority URI for sovereign/air-gapped clouds

5.5 Data Scope

Control which telemetry categories are collected. All DWORD, 1 = on, 0 = off.

Value NameDefaultDescription
CollectDefenderStatus1MpComputerStatus — RTP, Tamper, Signatures, etc.
CollectDefenderPolicies1Policy Manager — ASR, NP, CFA, Device Control
CollectFirewallStatus1Firewall profile status (Domain/Private/Public)
CollectFirewallRules0Individual firewall rules (can be large)
CollectWfpFilters0WFP filter summaries
CollectDeviceControl1Device Control policies and groups
CollectAppControl1WDAC / App Control status
CollectIntuneEnrollment1Intune / ConfigMgr enrollment info
CollectDeviceGuard1VBS, HVCI, Credential Guard status
CollectNetworkInfo0Network adapter and IP information
CollectSecurityScore0Computed security score

5.6 Tagging / Organization

Value NameTypeDescription
DeviceTagSZFreeform label — e.g. Finance, LOB1
OrgUnitSZOrganizational unit — e.g. US-East
EnvironmentSZProduction | Staging | Dev

5.7 UI / Feature Flags

These control the interactive WPF application, not the background collector. All DWORD, default 0.

Value NameDescription
DisableAiFeaturesHide AI analysis panels
DisableExportButtonsHide PDF/JSON export
DisableAdvancedNetworkingHide advanced networking window
DisableRemoteTargetPrevent targeting remote machines
ForceReadOnlyModeDisable all write operations (remediation, policy push)

5.8 Logging

Value NameTypeDefaultDescription
LogLevelSZInfoNone | Error | Warning | Info | Verbose
LogFilePathSZcache\service.logPath to service log file
MaxLogSizeMbDWORD10Max log size before rotation (1–500 MB)

5.9 Example: GPO / Intune Deployment

PowerShell — Set registry for enterprise collection + upload
$regPath = "HKLM:\SOFTWARE\Policies\MDE-Toolkit"
New-Item -Path $regPath -Force | Out-Null

# Enable collector + upload
Set-ItemProperty $regPath -Name EnableBackgroundCollector -Value 1 -Type DWord
Set-ItemProperty $regPath -Name EnableUpload             -Value 1 -Type DWord

# Function App config
Set-ItemProperty $regPath -Name FunctionAppUrl      -Value "https://func-mde-toolkit.azurewebsites.net/api/HealthReportIngestion"
Set-ItemProperty $regPath -Name FunctionAppAudience -Value "api://mde-toolkit-func"

# Cloud environment (Public, USGov, China, Germany)
Set-ItemProperty $regPath -Name AzureEnvironment -Value "Public"

# Tagging
Set-ItemProperty $regPath -Name DeviceTag -Value "Finance"
Set-ItemProperty $regPath -Name OrgUnit   -Value "US-East"

# Cache retention
Set-ItemProperty $regPath -Name CacheRetentionDays -Value 30 -Type DWord

6. MSI Deployment

The MDE Toolkit ships as a standard WiX v5 MSI installer, suitable for deployment via SCCM, Intune, or GPO.

What the MSI Does

Silent Install

Command Line
# Silent install
msiexec /i MDE-Toolkit-3.0.2.msi /qn

# Silent install with logging
msiexec /i MDE-Toolkit-3.0.2.msi /qn /l*v install.log

# Intune detection rule: HKLM\SOFTWARE\MDE-Toolkit\Version exists

7. Scheduled Tasks

The MSI installer creates two scheduled tasks automatically. Collection and upload are split to leverage the right security context for each operation.

7.1 MDE Toolkit Collect (SYSTEM)

Runs as SYSTEM every 8 hours. SYSTEM has full access to read all Defender configuration, firewall rules, WFP filters, and registry settings — but cannot acquire a user Entra ID token.

What the MSI creates (schtasks equivalent)
schtasks /create /tn "MDE Toolkit Collect" ^
  /tr "\"C:\Program Files\MDE-Toolkit\MDE Monitoring App.exe\" --collect --no-upload" ^
  /sc HOURLY /mo 8 ^
  /ru SYSTEM ^
  /rl HIGHEST ^
  /f

The --collect --no-upload flags tell the app to:

  1. Read enterprise config from registry
  2. Collect all enabled telemetry categories
  3. Build a flat summary with health score
  4. Cache the snapshot to C:\ProgramData\MDE-Toolkit\cache\latest.json
  5. Exit with code 0 (upload is skipped — handled by the Upload task)

7.2 MDE Toolkit Upload (Interactive User)

Runs as the currently logged-on user every 8 hours. The user's Entra ID Primary Refresh Token (PRT) allows DefaultAzureCredential to acquire a bearer token for the Function App — no stored passwords or secrets needed.

What the MSI creates (PowerShell equivalent)
# The MSI uses Register-ScheduledTask with BUILTIN\Users group principal
# so the task runs as whichever user is interactively logged on:
$action    = New-ScheduledTaskAction `
  -Execute '"C:\Program Files\MDE-Toolkit\MDE Monitoring App.exe"' `
  -Argument '--upload'

$trigger   = New-ScheduledTaskTrigger -Once -At (Get-Date) `
  -RepetitionInterval (New-TimeSpan -Hours 8)

$principal = New-ScheduledTaskPrincipal `
  -GroupId 'BUILTIN\Users' -RunLevel Highest

$settings  = New-ScheduledTaskSettingsSet `
  -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable

Register-ScheduledTask -TaskName 'MDE Toolkit Upload' `
  -Action $action -Trigger $trigger -Principal $principal `
  -Settings $settings -Force

The --upload flag tells the app to:

  1. Read the latest cached snapshot from latest.json
  2. Acquire an Entra ID bearer token using the logged-on user's PRT
  3. POST the snapshot to the configured Function App URL
  4. Exit with code 0 on success
No GUI is shown. Both --collect and --upload modes run as headless console processes. The WPF window is never created.
User must be logged on. The Upload task uses BUILTIN\Users with interactive logon, so it only fires when a user session is active. If no user is logged on, the upload is deferred until the next trigger with StartWhenAvailable.

8. Verification

8.1 Test Collection Locally

PowerShell (Admin)
# Run a single collect cycle (as admin, simulating the SYSTEM task)
& "C:\Program Files\MDE-Toolkit\MDE Monitoring App.exe" --collect --no-upload

# Check the cached snapshot
Get-Content "C:\ProgramData\MDE-Toolkit\cache\latest.json" | ConvertFrom-Json | Select hostname, healthScore, overallStatus

# Check the service log
Get-Content "C:\ProgramData\MDE-Toolkit\cache\service.log" -Tail 20

8.2 Test Upload Locally

PowerShell (as your logged-on user)
# Run a single upload cycle (uses your Entra PRT for auth)
& "C:\Program Files\MDE-Toolkit\MDE Monitoring App.exe" --upload

# Check the service log for upload result
Get-Content "C:\ProgramData\MDE-Toolkit\cache\service.log" -Tail 10

8.3 Verify Scheduled Tasks

PowerShell
# Both tasks should exist after MSI install
Get-ScheduledTask -TaskName "MDE Toolkit*" | Format-Table TaskName, State, @{N='RunAs';E={$_.Principal.GroupId ?? $_.Principal.UserId}}

8.2 Verify Table Row

Azure CLI
# Query the table for your machine
az storage entity query \
  --table-name HealthReports \
  --account-name stmdetoolkit \
  --filter "Hostname eq 'YOUR-PC-NAME'"

8.3 Expected Output

CheckExpected
Exit code0
latest.json existsYes, with hostname, summary, defenderPolicies
Service logFunction App upload succeeded (200)
Table rowRowKey = HOSTNAME_guid, HealthScore > 0
Registry detectionHKLM\SOFTWARE\MDE-Toolkit\Version = 3.0.2

9. Power BI Integration

Connect Power BI directly to the Azure Table Storage to build fleet-wide security dashboards.

  1. In Power BI Desktop, select Get Data → Azure → Azure Table Storage
  2. Enter the storage account name: stmdetoolkit
  3. Authenticate with your Entra ID account (needs Storage Table Data Reader)
  4. Select the HealthReports table
  5. Expand the Content column to access all flat fields

Suggested Visuals

🏥 Fleet Health

Gauge chart on HealthScore with card count by OverallStatus

🛡️ ASR Coverage

Bar chart: AsrBlockModeRules vs AsrAuditModeRules per device

🔥 Firewall Status

Matrix of Hostname × FirewallDomain/Private/PublicEnabled

🔒 VBS / HVCI

Pie chart: devices with HvciRunning=true vs false

📊 Device Tags

Slicer on DeviceTag / OrgUnit for filtering

⏱️ Freshness

Table of Hostname, GeneratedAtUtc sorted by staleness