Public/Get-GkDeletedItem.ps1

function Get-GkDeletedItem {
    <#
    .SYNOPSIS
        List soft-deleted directory objects still inside the 30-day restore window.

    .DESCRIPTION
        Reads GET /directory/deletedItems/microsoft.graph.{type} and reports what was deleted, when,
        and how long is left to restore it. Deleted users, groups and applications sit in a
        recoverable container for 30 days before Entra purges them permanently.

        This is the safety net behind the module's delete paths: Remove-GkStaleGuest -Delete and
        Disable-GkStaleDevice -Delete both soft-delete, so anything they removed by mistake shows up
        here until the window closes.

        Scopes depend on what you ask for and only the one in use is validated: User.Read.All for
        users, Group.Read.All for groups, Application.Read.All for applications and service
        principals, AdministrativeUnit.Read.All for administrative units.

    .PARAMETER Type
        The directory object type to list. Required — Graph has no "all deleted objects" endpoint.

    .PARAMETER DeletedWithinDays
        Only return objects deleted within this many days.

    .PARAMETER ExpiringInDays
        Only return objects whose restore window closes within this many days. Use it to catch
        anything about to be purged.

    .PARAMETER First
        Return at most this many objects.

    .PARAMETER AsReport
        Add a ReportGeneratedUtc column.

    .EXAMPLE
        Get-GkDeletedItem -Type User

        Every recoverable deleted user.

    .EXAMPLE
        Get-GkDeletedItem -Type Group -ExpiringInDays 5

        Deleted groups with fewer than five days left to restore.

    .EXAMPLE
        Get-GkDeletedItem -Type User -AsReport | Export-Csv .\deleted-users.csv -NoTypeInformation

    .OUTPUTS
        PSGraphKit.DeletedItem
    #>

    [CmdletBinding()]
    [OutputType('PSGraphKit.DeletedItem')]
    param(
        [Parameter(Mandatory)]
        [ValidateSet('User', 'Group', 'Application', 'ServicePrincipal', 'AdministrativeUnit')]
        [string] $Type,

        [int] $DeletedWithinDays,

        [int] $ExpiringInDays,

        [int] $First,

        [switch] $AsReport
    )

    begin {
        # Each type is a different Graph permission, so validate the one actually being read.
        Test-GkConnection -FunctionName 'Get-GkDeletedItem' -Variant $Type -Caller $PSCmdlet | Out-Null
        $now = [datetime]::UtcNow

        # Entra keeps soft-deleted directory objects for 30 days before permanent purge.
        $retentionDays = 30
    }

    process {
        # The type is a cast segment in the path, not a filter; Graph has no combined collection.
        $segment = 'microsoft.graph.' + $Type.Substring(0, 1).ToLower() + $Type.Substring(1)
        $params = @{
            Uri            = "/directory/deletedItems/$segment"
            CallerFunction = 'Get-GkDeletedItem'
        }
        if ($PSBoundParameters.ContainsKey('First')) { $params['MaxResult'] = $First }

        $items = Invoke-GkGraphRequest @params

        foreach ($i in $items) {
            $deleted = ConvertTo-GkDateTime (Get-GkDictValue $i 'deletedDateTime')
            $ageDays = if ($null -ne $deleted) { [int][math]::Floor(($now - $deleted).TotalDays) } else { $null }
            $daysLeft = if ($null -ne $ageDays) { $retentionDays - $ageDays } else { $null }

            if ($PSBoundParameters.ContainsKey('DeletedWithinDays')) {
                if ($null -eq $ageDays -or $ageDays -gt $DeletedWithinDays) { continue }
            }
            if ($PSBoundParameters.ContainsKey('ExpiringInDays')) {
                if ($null -eq $daysLeft -or $daysLeft -gt $ExpiringInDays) { continue }
            }

            $obj = [ordered]@{
                PSTypeName        = 'PSGraphKit.DeletedItem'
                ObjectType        = $Type
                DisplayName       = [string](Get-GkDictValue $i 'displayName')
                UserPrincipalName = [string](Get-GkDictValue $i 'userPrincipalName')
                DeletedDateTime   = $deleted
                DaysSinceDeleted  = $ageDays
                DaysUntilPurge    = $daysLeft
                Id                = [string](Get-GkDictValue $i 'id')
            }
            if ($AsReport) { $obj['ReportGeneratedUtc'] = $now }
            [pscustomobject]$obj
        }
    }
}