Public/Disable-GkStaleDevice.ps1

function Disable-GkStaleDevice {
    <#
    .SYNOPSIS
        Disable (default) or delete stale Entra devices.

    .DESCRIPTION
        By default blocks the device (PATCH /devices/{id} accountEnabled=false). With -Delete it
        deletes the device (DELETE /devices/{id}); deletion is a 30-day soft-delete. Typically fed
        from Get-GkDeviceInventory -StaleOnly.

        State-changing: supports -WhatIf / -Confirm and prompts by default. Accepts devices from the
        pipeline (by the Id property, so Get-GkDeviceInventory output pipes in) and yields a
        PSGraphKit.DeviceDisableResult per device; failures warn and continue.

        Delegated device writes use Directory.AccessAsUser.All (there is no delegated
        Device.ReadWrite.All) and require a supporting Entra role — Cloud Device Administrator to
        enable/disable, Intune Administrator to delete.

    .PARAMETER DeviceId
        One or more device OBJECT IDs (the Id from Get-GkDeviceInventory, not the deviceId GUID).
        Accepts pipeline input incl. by the Id property.

    .PARAMETER Delete
        Delete the device (soft-delete) instead of only disabling it.

    .EXAMPLE
        Get-GkDeviceInventory -StaleOnly -StaleDays 180 | Disable-GkStaleDevice -WhatIf

        Preview disabling devices with no activity for 180+ days.

    .EXAMPLE
        Get-GkDeviceInventory -StaleOnly -StaleDays 365 | Disable-GkStaleDevice -Confirm:$false

        Disable devices inactive 365+ days.

    .EXAMPLE
        Disable-GkStaleDevice -DeviceId 11111111-1111-1111-1111-111111111111 -Delete

        Delete one device by object id (prompts for confirmation).

    .OUTPUTS
        PSGraphKit.DeviceDisableResult
    #>

    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')]
    [OutputType('PSGraphKit.DeviceDisableResult')]
    param(
        [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [Alias('Id')]
        [string[]] $DeviceId,

        [switch] $Delete
    )

    begin {
        Test-GkConnection -FunctionName 'Disable-GkStaleDevice' | Out-Null
    }

    process {
        foreach ($did in $DeviceId) {
            if ([string]::IsNullOrWhiteSpace($did)) { continue }

            $action = if ($Delete) { 'Delete device (soft-delete)' } else { 'Disable device (accountEnabled = false)' }
            if (-not $PSCmdlet.ShouldProcess($did, $action)) { continue }

            $enc = [uri]::EscapeDataString($did)
            $outcome = if ($Delete) { 'Deleted' } else { 'Disabled' }
            $errMsg = $null
            try {
                if ($Delete) {
                    Invoke-GkGraphRequest -Method DELETE -Uri "/devices/$enc" -CallerFunction 'Disable-GkStaleDevice' | Out-Null
                }
                else {
                    Invoke-GkGraphRequest -Method PATCH -Uri "/devices/$enc" -Body @{ accountEnabled = $false } -CallerFunction 'Disable-GkStaleDevice' | Out-Null
                }
            }
            catch {
                $outcome = 'Failed'
                $errMsg = $_.Exception.Message
                Write-Warning "Failed to $(if ($Delete) { 'delete' } else { 'disable' }) device '$did': $errMsg"
            }

            [pscustomobject]@{
                PSTypeName = 'PSGraphKit.DeviceDisableResult'
                DeviceId   = $did
                Action     = if ($Delete) { 'DeleteDevice' } else { 'DisableDevice' }
                Outcome    = $outcome
                Error      = $errMsg
            }
        }
    }
}