public/Invoke-DeviceCleanup.ps1
function Invoke-DeviceCleanup { <# .SYNOPSIS Orchestrates stale device cleanup across Intune, Entra ID, and optionally AD. .PARAMETER InactiveDays Days since last sync to consider devices stale. Default 30. .PARAMETER TenantId .PARAMETER ClientId .PARAMETER CertificateThumbprint Supply these for certificate-based auth; otherwise delegated sign-in is used. .PARAMETER Scopes Custom delegated scopes for Connect-MgGraph. .PARAMETER RemoveFromActiveDirectory Also remove matching AD computer objects. .PARAMETER AdOuPaths .PARAMETER AdBaseDn .PARAMETER AdOuNames OU targeting options for AD removal. .PARAMETER SkipGraphConnect Assume Graph is already connected. .EXAMPLE Invoke-DeviceCleanup -InactiveDays 45 -RemoveFromActiveDirectory -AdBaseDn "DC=contoso,DC=com" -AdOuNames "Clients","Admins" -WhatIf -Verbose #> [CmdletBinding(SupportsShouldProcess, ConfirmImpact='High')] param( [ValidateRange(1,3650)] [int]$InactiveDays = 30, [ValidatePattern('^[0-9a-fA-F-]{36}$')] [string]$TenantId, [ValidatePattern('^[0-9a-fA-F-]{36}$')] [string]$ClientId, [ValidatePattern('^[0-9A-Fa-f]{40}$')] [string]$CertificateThumbprint, [string[]]$Scopes, [switch]$RemoveFromActiveDirectory, [string[]]$AdOuPaths, [string]$AdBaseDn, [string[]]$AdOuNames, [switch]$SkipGraphConnect ) if (-not $SkipGraphConnect) { # Build Connect-DcGraph parameters only for provided values to avoid ValidatePattern triggering on empty strings $connectParams = @{ SkipIfConnected = $true } if ($TenantId) { $connectParams.TenantId = $TenantId } if ($ClientId) { $connectParams.ClientId = $ClientId } if ($CertificateThumbprint) { $connectParams.CertificateThumbprint = $CertificateThumbprint } if ($Scopes) { $connectParams.Scopes = $Scopes } Connect-DcGraph @connectParams } Write-Verbose "Finding stale Intune devices (InactiveDays=$InactiveDays)..." $stale = Get-StaleIntuneDevice -InactiveDays $InactiveDays Write-Verbose ("Stale devices: {0}" -f ($stale | Measure-Object | Select-Object -ExpandProperty Count)) # Capture common parameters to forward downstream without leaking unrelated parameters $commonParams = @{} foreach ($k in 'Verbose','WhatIf','Confirm') { if ($PSBoundParameters.ContainsKey($k)) { $commonParams[$k] = $PSBoundParameters[$k] } } # Intune removal if ($stale) { $stale | Remove-IntuneDevice @commonParams } # Entra removal if ($stale) { Remove-EntraDevice -FromManagedDevice $stale @commonParams } # AD removal (optional) if ($RemoveFromActiveDirectory -and $stale) { $names = $stale | Where-Object DeviceName | Select-Object -ExpandProperty DeviceName -Unique if ($names) { Remove-AdComputer -ComputerNames $names -AdOuPaths $AdOuPaths -AdBaseDn $AdBaseDn -AdOuNames $AdOuNames @commonParams } else { Write-Verbose "No device names available from Intune set for AD deletion." } } } |