Automated solution for Microsoft Intune that detects Windows devices with low free disk space using Proactive Remediations and performs bidirectional sync with an Entra ID security group: devices with low disk space are added, and devices that recover are automatically removed.
| Component | Path | Description |
|---|---|---|
| Proactive Remediation Scripts | RemediationScripts/ |
Detection script (checks free space, exit 0/1) + optional remediation script (cleans temp files) |
| Azure Logic App (Bicep) | main.bicep |
Serverless Logic App that reads Intune inventory and adds low-disk devices to an Entra group |
| .NET 10 Worker Service | Automation/IntuneDiskGuardian/ |
Long-running background service β reads remediation results via Graph API, bidirectional group sync |
| PowerShell Automation | Automation/ |
Standalone scripts for the same bidirectional sync + Windows Scheduled Task setup |
| App Registration | Automation/Register-App.ps1 |
Creates the Entra app with least-privilege Graph permissions |
| Graph Permissions | grant-graph-permissions.ps1 |
Grants Graph API permissions to the Logic App's Managed Identity |
ββββββββββββββββββββββ ββββββββββββββββββββββββ ββββββββββββββββββββββββββ
β Intune Proactive β β .NET Worker Service β β Entra ID Group β
β Remediation β β (or PowerShell / β β "Devices-LowDisk" β
β β β Logic App) β β β
β Detect-LowDisk β β β β Targeted policies: β
β runs on device βββββββΊβ Reads health script βββββββΊβ - Cleanup scripts β
β < 25 GB = exit 1 β β run states via Graph β β - Conditional Access β
β β β Adds/removes devices β β - Notifications β
β Remediate-LowDisk β β bidirectionally β β β
β cleans temp files β β β β β
ββββββββββββββββββββββ ββββββββββββββββββββββββ ββββββββββββββββββββββββββ
Three deployment options β pick the one that fits your environment:
| Option | Best for | Runs on |
|---|---|---|
| A β .NET Worker Service | Enterprises needing a robust, long-running service | Windows Server / Container / Azure Container Apps |
| B β PowerShell Scheduled Task | Simple on-prem deployments | Windows Server (Task Scheduler) |
| C β Azure Logic App | Fully serverless, zero infrastructure | Azure (Consumption plan) |
- Azure subscription (Contributor on target Resource Group β for Logic App option)
- Microsoft Intune with enrolled Windows devices
- Entra ID permissions to create security groups and app registrations
- .NET 10 SDK β for the Worker Service (download)
- Azure CLI + Bicep CLI β for the Logic App option
- Microsoft Graph PowerShell SDK:
Install-Module Microsoft.Graph -Scope CurrentUser
- Intune admin center β Devices β Remediations β Create script package
- Name:
IntuneDiskGuardian - Low Disk Space - Upload
RemediationScripts/Detect-LowDiskSpace.ps1as the Detection script - Upload
RemediationScripts/Remediate-LowDiskSpace.ps1as the Remediation script (optional β cleans temp files) - Set Run this script using the logged-on credentials: No (run as SYSTEM)
- Assign to your device groups and configure the Schedule (e.g., every 1 hour, daily)
- After deploying, note the Health Script ID from the Intune portal URL or via Graph:
GET https://graph.microsoft.com/beta/deviceManagement/deviceHealthScripts
Devices with less than 25 GB free on the system drive will report exit 1 (issue detected). When a device frees up space, the next run reports exit 0 and it will be automatically removed from the group at the next sync cycle.
.\Automation\Register-App.ps1Save the output (TenantId, ClientId, ClientSecret) securely β ideally in Azure Key Vault.
cd Automation/IntuneDiskGuardian
# Store secrets with .NET User Secrets (never in appsettings.json)
dotnet user-secrets set "IntuneDiskGuardian:TenantId" "<YOUR-TENANT-ID>"
dotnet user-secrets set "IntuneDiskGuardian:ClientId" "<YOUR-CLIENT-ID>"
dotnet user-secrets set "IntuneDiskGuardian:ClientSecret" "<YOUR-CLIENT-SECRET>"
dotnet user-secrets set "IntuneDiskGuardian:EntraGroupId" "<YOUR-GROUP-OBJECT-ID>"
dotnet user-secrets set "IntuneDiskGuardian:HealthScriptId" "<YOUR-HEALTH-SCRIPT-ID>"
dotnet runConfiguration in appsettings.json:
.\Automation\Setup-ScheduledTask.ps1 `
-TenantId "<TENANT-ID>" `
-ClientId "<CLIENT-ID>" `
-ClientSecret "<CLIENT-SECRET>" `
-EntraGroupId "<GROUP-ID>" `
-HealthScriptId "<HEALTH-SCRIPT-ID>" `
-ScriptPath "C:\Scripts\Sync-NonCompliantDevices.ps1"az group create --name rg-intune-disk-guardian --location westeurope
az deployment group create \
--resource-group rg-intune-disk-guardian \
--template-file main.bicep \
--parameters main.bicepparam
# Grant Graph permissions to Managed Identity (from deployment output)
.\grant-graph-permissions.ps1 -ManagedIdentityObjectId "<MI-OBJECT-ID>"| Permission | Type | Purpose |
|---|---|---|
DeviceManagementConfiguration.Read.All |
Application | Read Proactive Remediation (health script) run states |
GroupMember.ReadWrite.All |
Application | Add/remove devices in the Entra group |
Device.Read.All |
Application | Resolve device objects in Entra ID |
All permissions follow the least-privilege principle. The Register-App.ps1 script assigns them automatically with admin consent.
Edit RemediationScripts/Detect-LowDiskSpace.ps1 β change the $thresholdGB variable:
$thresholdGB = 25 # Change this valueAlso update ThresholdGB in the Worker Service config / PowerShell parameter to match.
Change 25 to your desired threshold in GB.
In appsettings.json or via environment variable:
"SyncInterval": "00:30:00"Format: HH:mm:ss (e.g., 00:30:00 = every 30 minutes).
| Symptom | Cause | Fix |
|---|---|---|
| 401 Unauthorized | Graph permissions not yet propagated | Wait 15β30 min after granting permissions |
| 403 Forbidden | Missing application permissions | Re-run Register-App.ps1 or grant-graph-permissions.ps1 |
| Device not flagged | freeStorageSpaceInBytes is 0 or null |
Verify device is enrolled and syncing with Intune |
| 400 Bad Request on group add | Device already in group | Expected behavior β silently skipped |
| No devices processed | All devices are compliant | Check compliance state in Intune admin center |
IntuneDiskGuardian/
βββ RemediationScripts/
β βββ Detect-LowDiskSpace.ps1 # Proactive Remediation detection (runs on device)
β βββ Remediate-LowDiskSpace.ps1 # Optional: cleans temp files to free space
βββ Automation/
β βββ Register-App.ps1 # App Registration setup
β βββ Sync-NonCompliantDevices.ps1 # PowerShell sync script
β βββ Setup-ScheduledTask.ps1 # Scheduled Task installer
β βββ IntuneDiskGuardian/ # .NET 10 Worker Service
β βββ Program.cs
β βββ Worker.cs
β βββ Configuration/
β β βββ IntuneDiskGuardianOptions.cs
β βββ Services/
β β βββ DeviceSyncService.cs
β βββ appsettings.json
βββ main.bicep # Logic App infrastructure (Bicep)
βββ main.bicepparam # Deployment parameters
βββ main.json # Compiled ARM template
βββ grant-graph-permissions.ps1 # Graph permissions for Managed Identity
- Never commit secrets to source control β use
.NET User SecretsorAzure Key Vault - For production, prefer certificate-based authentication over client secrets
- The App Registration uses least-privilege application permissions
- Rotate client secrets before expiry (default: 1 year)
- The Logic App uses a System-assigned Managed Identity (no secrets to manage)
This project is licensed under the MIT License.
Contributions are welcome! Please open an issue or submit a pull request.
{ "IntuneDiskGuardian": { "TenantId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "ClientId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "ClientSecret": "use-user-secrets-or-keyvault", "EntraGroupId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "HealthScriptId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "ThresholdGB": 25, "SyncInterval": "01:00:00" } }