Repository navigation
Guard staged Entra and Intune device cleanup #62
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
PrzemyslawKlys
merged 4 commits into
fix/large-graph-inventory
from
feature/cloud-device-cleanup
Sep 22, 2026
Merged
Changes from 3 commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
2560658
Prepare staged Entra and Intune device cleanup
PrzemyslawKlys ccec1d4
Require safe Graph inventory version for cloud cleanup
PrzemyslawKlys 3f7ba81
Block ambiguous Intune links and non-Windows Autopilot removal
PrzemyslawKlys 77b8311
Ignore empty Entra IDs in Intune link matching
PrzemyslawKlys File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| # Entra and Intune device cleanup | ||
|
|
||
| Use [CleanupEntraDevices.ps1](CleanupEntraDevices.ps1) as a separate daily job beside the existing AD/Jamf computer cleanup. It follows the same pattern: connect to Microsoft Graph, set one splat, and call CleanupMonster. The operating-system scope includes Windows, Android, iOS, iPadOS, and macOS. The existing AD/Jamf job does not need to change. | ||
|
|
||
| ## Reuse the existing Graph app | ||
|
|
||
| Keep the existing job's `Connect-MgGraph -ClientSecretCredential` block in the private scheduled script. Set `$TenantId` in the example to that job's tenant GUID. Do not copy its encrypted secret into this repository. The example checks the connected tenant before reading inventory or taking action. There is no need for a new app registration or Azure subscription resource if the existing app can be used. | ||
|
|
||
| Check the existing app's Microsoft Graph **application permissions** and tenant admin consent: | ||
|
|
||
| | Permission | Needed for | | ||
| | --- | --- | | ||
| | `Device.ReadWrite.All` | Entra device disable and delete | | ||
| | `DeviceManagementManagedDevices.ReadWrite.All` | Intune managed-device inventory and final record removal | | ||
| | `DeviceManagementServiceConfig.ReadWrite.All` | Autopilot identity inventory and final removal | | ||
|
|
||
| Release GraphEssentials **0.0.63** first, then CleanupMonster **3.1.15**. Install both on the scheduled host before running this example. The example requires these versions because they stop ambiguous Autopilot matches, stop Entra deletion after an Intune deletion failure, and protect recently synced Intune devices. Earlier releases do not contain these changes. | ||
|
|
||
| ## Run it like the old job | ||
|
|
||
| 1. Keep the existing Graph connection and log/transcript setup. Create the example's report, log, and script folders if they do not exist. Use its **new cloud datastore path**, never the AD job's `CleanupComputers_ListProcessed.xml`. | ||
| 2. The old job used inventory minimums of `20,000` Entra and `19,000` Intune. The cloud job counts selected join types, so these numbers may not fit. Check its reported counts and adjust the two limits to stable, current cloud baselines before actions. Check the operating-system values in the report, especially for Macs and iPads, and adjust the patterns if this tenant uses different labels. Add approved cloud exclusions and protected Autopilot group tags to the splat. | ||
| 3. Leave `ReportOnly = $true` for the first 30 daily runs and review the reports. The supplied category counts add up to **37,600**, while the headline says **38,600**; reconcile this before enabling actions. Unknown activity and hybrid joined devices remain outside action scope. | ||
| 4. To preview actions, set `ReportOnly = $false`, `WhatIfDisable = $true`, and `WhatIfDelete = $true`. Then start disabling with `WhatIfDisable = $false` while leaving `WhatIfDelete = $true`. Enable deletion only after reviewing recovery, duplicates, Autopilot associations, and the first disabled cohort. | ||
|
|
||
| The policy disables Entra joined or registered devices on the selected platforms after more than 90 days of known Entra inactivity and registration age. It deletes only after more than 180 days of both, and after a successful CleanupMonster disable has remained in the new datastore for more than 90 days. The old already-disabled backlog is not automatically promoted. Keep the datastore across runs; do not run overlapping instances. | ||
|
|
||
| This cloud cmdlet uses Entra and Intune inventory. In this example, **both** Entra activity and, when an Intune record exists, Intune last sync must be older than 90 days for disable and 180 days for delete. A matching Intune record with unknown last sync is excluded. Entra-only records still use the Entra threshold. The cmdlet does not query Jamf or delete Jamf records. A recent Jamf check-in will not block cloud actions for a Mac. The old AD cleanup job continues to use Jamf for its AD computer decisions. The Autopilot identity option applies only to Windows or records whose operating system is unknown. | ||
|
|
||
| If an Intune device cleanup rule is set to 90 days, remember that Microsoft describes it as **hiding** stale devices from the portal and reports. It does not replace this Entra cleanup policy or prove an Intune object was deleted. See [Intune device cleanup rules](https://learn.microsoft.com/en-us/intune/governance/configure-cleanup-rules) and [Microsoft Entra stale-device guidance](https://learn.microsoft.com/en-us/entra/identity/devices/manage-stale-devices). |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,55 @@ | ||
| # Run the existing scheduled task's Connect-MgGraph block before this script. | ||
| # Keep its client secret outside the repository. No Jamf connection is needed. | ||
|
|
||
| $TenantId = '<tenant-guid>' # Use the tenant ID from the existing computer cleanup job. | ||
| $Today = Get-Date -Format 'yyyy-MM-dd_HH_mm_ss' | ||
|
|
||
| Import-Module GraphEssentials -MinimumVersion '0.0.63' -ErrorAction Stop | ||
| Import-Module CleanupMonster -MinimumVersion '3.1.15' -ErrorAction Stop | ||
|
|
||
| if ([string] (Get-MgContext).TenantId -ne $TenantId) { | ||
| throw "Connect-MgGraph to tenant $TenantId before running cloud cleanup." | ||
| } | ||
|
|
||
| $invokeCloudDevicesCleanupSplat = @{ | ||
| # Starting values from the old job; cloud-scoped counts may differ. | ||
| SafetyEntraLimit = 20000 | ||
| SafetyIntuneLimit = 19000 | ||
|
|
||
| Disable = $true | ||
| DisableLimit = 10 | ||
| DisableLastSeenEntraMoreThan = 90 | ||
| DisableRegisteredMoreThan = 90 | ||
| DisableListProcessedMoreThan = $null | ||
| DisableIncludeEntraOnly = $true | ||
|
|
||
| Delete = $true | ||
| DeleteLimit = 5 | ||
| DeleteLastSeenEntraMoreThan = 180 | ||
| DeleteRegisteredMoreThan = 180 | ||
| DeleteListProcessedMoreThan = 90 | ||
| DeleteIncludeEntraOnly = $true | ||
| DeleteRemoveIntuneRecord = $true | ||
| DeleteAutopilotIdentity = $true | ||
|
|
||
| IncludeJoinType = @('AzureAD joined', 'AzureAD registered') | ||
| IncludeOperatingSystem = @('Windows*', 'Android*', 'iOS*', 'iPadOS*', 'macOS*', 'Mac OS*') | ||
| IncludeCompanyOwned = $true | ||
| ProtectRecentIntuneActivity = $true | ||
| PreserveDuplicateDeviceNames = $true | ||
| Exclusions = @() # Add approved cloud device names or IDs. | ||
| ExcludeAutopilotGroupTag = @() # Add protected group tags if needed. | ||
|
|
||
| # Use a new, persistent datastore for this cloud policy. Keep it across runs. | ||
| DataStorePath = 'E:\Support\Scripts\CleanupCloudDevices_ListProcessed.xml' | ||
| ReportPath = "E:\Support\Reporting\Custom\CleanupCloudDevices_$Today.html" | ||
| LogPath = "E:\Support\Logs\CleanupComputersCloud\CleanupCloudDevices_$Today.log" | ||
|
|
||
| ReportOnly = $true | ||
| WhatIfDisable = $false | ||
| WhatIfDelete = $false | ||
| ShowHTML = $false | ||
| } | ||
|
|
||
| $Output = Invoke-CloudDevicesCleanup @invokeCloudDevicesCleanupSplat | ||
| $Output |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.