Public/Get-IOStaleGuests.ps1

function Get-IOStaleGuests {
    <#
    .SYNOPSIS
        Lists guest users who haven't signed in for a specified number of days.
    .EXAMPLE
        Get-IOStaleGuests -InactiveDays 90
    .EXAMPLE
        Get-IOStaleGuests -InactiveDays 180 -ToCsv "stale-guests.csv"
    #>

    [CmdletBinding()]
    param(
        [ValidateRange(1, 3650)]
        [int]$InactiveDays = 90,

        [switch]$IncludeNeverSignedIn,

        [string]$ToCsv
    )

    $cmdName = $MyInvocation.MyCommand.Name
    Write-IOLog "Scanning for guest users inactive for $InactiveDays+ days..." -Level Info -Component $cmdName

    $cutoff  = [datetime]::UtcNow.AddDays(-$InactiveDays)
    $results = [System.Collections.Generic.List[PSCustomObject]]::new()

    $guests = Invoke-IOGraphRequest -Uri "v1.0/users?`$filter=userType eq 'Guest'&`$select=id,displayName,userPrincipalName,mail,createdDateTime,accountEnabled,signInActivity"

    foreach ($guest in $guests) {
        $lastSignIn = $null
        $neverSignedIn = $true

        $sia = if ($guest.PSObject.Properties['signInActivity']) { $guest.signInActivity } else { $null }
        if ($sia -and $sia.lastSignInDateTime) {
            $lastSignIn = [datetime]::Parse($sia.lastSignInDateTime, [System.Globalization.CultureInfo]::InvariantCulture, [System.Globalization.DateTimeStyles]::AssumeUniversal)
            $neverSignedIn = $false
        }

        $isStale = $false
        if ($neverSignedIn -and $IncludeNeverSignedIn) {
            $isStale = $true
        }
        elseif (-not $neverSignedIn -and $lastSignIn -lt $cutoff) {
            $isStale = $true
        }

        if ($isStale) {
            $created = if ($guest.createdDateTime) { [datetime]::Parse($guest.createdDateTime, [System.Globalization.CultureInfo]::InvariantCulture, [System.Globalization.DateTimeStyles]::AssumeUniversal).ToString('yyyy-MM-dd') } else { 'Unknown' }

            $results.Add([PSCustomObject]@{
                DisplayName       = $guest.displayName
                UserPrincipalName = $guest.userPrincipalName
                Mail              = $guest.mail
                ObjectId          = $guest.id
                AccountEnabled    = $guest.accountEnabled
                CreatedDate       = $created
                LastSignIn        = if ($lastSignIn) { $lastSignIn.ToString('yyyy-MM-dd') } else { 'Never' }
                InactiveDays      = if ($lastSignIn) { [math]::Floor(([datetime]::UtcNow - $lastSignIn).TotalDays) } else { 'N/A' }
                Status            = if ($neverSignedIn) { 'NEVER_SIGNED_IN' } else { 'INACTIVE' }
            })
        }
    }

    $sorted = $results | Sort-Object Status, LastSignIn
    Export-IOResult -Data $sorted -ToCsv $ToCsv -CommandName $cmdName
}