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."
        }
    }
}