Checks/EntraID/ARG.ENT.004.ps1

Register-ArgusCheck @{
    Id          = 'ARG.ENT.004'
    Title       = 'Guests that never accepted the invitation (more than {PendingDays} days)'
    Area        = 'EntraID'
    Category    = 'External identities'
    Severity    = 'Medium'
    Description = 'Guest accounts whose `externalUserState` is still **PendingAcceptance** more than **{PendingDays}** days after they were invited (`externalUserStateChangeDateTime`, or `createdDateTime` when not set).'
    Rationale   = 'Unredeemed invitations clutter the directory, are often typos or abandoned collaborations, and can be redeemed later by whoever controls the mailbox.'
    Remediation = @{
        Steps      = "1. Check with the person who invited the guest whether the collaboration is still needed.`n2. Resend the invitation if it is, otherwise delete the guest account."
        PowerShell = "# Sample only - review before running. Deletes one pending guest.`nConnect-MgGraph -Scopes 'User.ReadWrite.All'`nRemove-MgUser -UserId '<ObjectId>'"
        PortalUrl  = 'https://entra.microsoft.com/'
    }
    References  = @(
        @{ Title = 'Microsoft Entra B2B collaboration invitation redemption'; Url = 'https://learn.microsoft.com/en-us/entra/external-id/redemption-experience' }
    )
    Requires    = @{
        Services    = @('Graph')
        GraphScopes = @('User.Read.All')
        DataSets    = @('Users')
    }
    Parameters  = @{ PendingDays = 30 }
    Tags        = @('Guests', 'Lifecycle')
    Test        = {
        param($Context)
        $threshold = $Context.Now.AddDays(-[int]$Context.Parameters.PendingDays)
        $guests = @($Context.Data['Users'] | Where-Object { (Get-ArgusProperty $_ 'userType') -eq 'Guest' })
        $findings = foreach ($guest in $guests) {
            if ((Get-ArgusProperty $guest 'externalUserState') -ne 'PendingAcceptance') { continue }
            $since = ConvertTo-ArgusUtcDate (Get-ArgusProperty $guest 'externalUserStateChangeDateTime')
            if (-not $since) { $since = ConvertTo-ArgusUtcDate (Get-ArgusProperty $guest 'createdDateTime') }
            if (-not $since -or $since -gt $threshold) { continue }
            [pscustomobject]@{
                DisplayName = Get-ArgusProperty $guest 'displayName' ''
                Mail        = Get-ArgusProperty $guest 'mail' ''
                InvitedOn   = Format-ArgusDate $since
                DaysPending = [int]($Context.Now - $since).TotalDays
                ObjectId    = Get-ArgusProperty $guest 'id' ''
            }
        }
        $findings = @($findings | Sort-Object DaysPending -Descending)
        $status = if ($findings.Count -gt 0) { 'Failed' } else { 'Passed' }
        New-ArgusResult -Status $status -Findings $findings -EvaluatedCount $guests.Count `
            -Summary '{AffectedCount} of {EvaluatedCount} guests have not accepted their invitation for {PendingDays}+ days.'
    }
}