Public/Get-StaleGuestUsers.ps1
|
function Get-StaleGuestUsers { <# .SYNOPSIS Finds guest users with no sign-in within a given number of days and exports them to CSV. .DESCRIPTION Requires Connect-MgGraph with User.Read.All and AuditLog.Read.All (signInActivity needs Entra ID P1). Read-only: review the CSV before removing any account. .PARAMETER DaysInactive Guests whose last sign-in (interactive or non-interactive) is older than this are stale. Default 90. .PARAMETER OutputFolder Folder for CSV output. Defaults to ./output. .EXAMPLE Get-StaleGuestUsers -DaysInactive 120 #> [CmdletBinding()] param( [ValidateRange(1, 3650)][int]$DaysInactive = 90, [string]$OutputFolder = (Join-Path (Get-Location) 'output') ) $ErrorActionPreference = 'Stop' if (-not (Get-MgContext)) { throw 'Not connected. Run Connect-MgGraph first.' } New-Item -ItemType Directory -Path $OutputFolder -Force | Out-Null $cutoff = (Get-Date).ToUniversalTime().AddDays(-$DaysInactive) $guests = Get-MgUser -All -Filter "userType eq 'Guest'" ` -Property Id, DisplayName, Mail, UserPrincipalName, CreatedDateTime, AccountEnabled, ExternalUserState, SignInActivity $rows = foreach ($g in $guests) { $last = @($g.SignInActivity.LastSignInDateTime, $g.SignInActivity.LastNonInteractiveSignInDateTime) | Where-Object { $_ } | Sort-Object -Descending | Select-Object -First 1 # Never signed in: judge by creation date so brand-new invites are not flagged. $reference = $last ?? $g.CreatedDateTime if ($reference -lt $cutoff) { [pscustomobject]@{ DisplayName = $g.DisplayName Mail = $g.Mail UserPrincipalName = $g.UserPrincipalName Id = $g.Id AccountEnabled = $g.AccountEnabled InvitationState = $g.ExternalUserState Created = $g.CreatedDateTime LastSignIn = $last DaysSinceActivity = [int]((Get-Date).ToUniversalTime() - $reference).TotalDays } } } $rows = $rows | Sort-Object DaysSinceActivity -Descending $file = Join-Path $OutputFolder "StaleGuests-$(Get-Date -Format 'yyyyMMdd-HHmm').csv" $rows | Export-Csv -Path $file -NoTypeInformation Write-Information "Guests: $(@($guests).Count) Stale (>$DaysInactive days): $(@($rows).Count) -> $file" -InformationAction Continue } |