All scripts
Automation 199

Disable-InactiveEntraGuestUsers

Finds Entra ID guest accounts that have not signed in for a set number of days and disables them, so you can clean up stale guest access without deleting anything outright. Writes a CSV report of everything it touches.

Disable-InactiveEntraGuestUsers.ps1
<#
.SYNOPSIS
    Disables Entra ID guest accounts that have not signed in for a set number of days.
.DESCRIPTION
    Connects to Microsoft Graph, pulls every guest account (userType eq 'Guest'), checks
    each one's last sign in date from the sign in activity, and disables any guest that
    has been inactive longer than the threshold you set. Accounts are disabled, not
    deleted, so you can review and re-enable anyone flagged by mistake. Writes a CSV
    report of everything the script found and touched.
.PARAMETER InactiveDays
    Number of days since last sign in before a guest is considered inactive. Defaults to 90.
.PARAMETER WhatIf
    Run the script without making any changes, just report what would be disabled.
.PARAMETER ReportPath
    Path to write the CSV report to. Defaults to the current folder.
.EXAMPLE
    .\Disable-InactiveEntraGuestUsers.ps1 -InactiveDays 120 -WhatIf
.EXAMPLE
    .\Disable-InactiveEntraGuestUsers.ps1 -InactiveDays 90 -ReportPath "C:\Reports\guests.csv"
.AUTHOR
    Shehryar Hassan
#>

[CmdletBinding()]
param(
    [int]$InactiveDays = 90,
    [switch]$WhatIf,
    [string]$ReportPath = ".\InactiveGuestUsers_$(Get-Date -Format 'yyyyMMdd').csv"
)

Import-Module Microsoft.Graph.Users -ErrorAction Stop

Connect-MgGraph -Scopes "User.ReadWrite.All", "AuditLog.Read.All" -NoWelcome

$cutoffDate = (Get-Date).AddDays(-$InactiveDays)
Write-Host "Looking for guest accounts inactive since before $($cutoffDate.ToString('yyyy-MM-dd'))" -ForegroundColor Cyan

$guests = Get-MgUser -Filter "userType eq 'Guest'" -All `
    -Property "Id,DisplayName,Mail,UserPrincipalName,AccountEnabled,SignInActivity,CreatedDateTime" `
    -ConsistencyLevel eventual -CountVariable guestCount

Write-Host "Found $($guests.Count) guest accounts to check." -ForegroundColor Cyan

$results = @()

foreach ($guest in $guests) {
    $lastSignIn = $guest.SignInActivity.LastSignInDateTime
    $createdDate = $guest.CreatedDateTime

    if (-not $lastSignIn) {
        # Never signed in, use account creation date as the reference point instead.
        $referenceDate = $createdDate
        $reason = "Never signed in"
    }
    else {
        $referenceDate = $lastSignIn
        $reason = "Last sign in $($lastSignIn.ToString('yyyy-MM-dd'))"
    }

    $isInactive = $referenceDate -lt $cutoffDate

    if ($isInactive -and $guest.AccountEnabled) {
        if ($WhatIf) {
            Write-Host "Would disable: $($guest.DisplayName) ($($guest.Mail)) - $reason" -ForegroundColor Yellow
        }
        else {
            try {
                Update-MgUser -UserId $guest.Id -AccountEnabled:$false
                Write-Host "Disabled: $($guest.DisplayName) ($($guest.Mail)) - $reason" -ForegroundColor Red
            }
            catch {
                Write-Warning "Failed to disable $($guest.DisplayName): $($_.Exception.Message)"
            }
        }

        $results += [PSCustomObject]@{
            DisplayName       = $guest.DisplayName
            Mail              = $guest.Mail
            UserPrincipalName = $guest.UserPrincipalName
            LastSignIn        = $lastSignIn
            CreatedDateTime   = $createdDate
            Reason            = $reason
            Action            = if ($WhatIf) { "Would disable" } else { "Disabled" }
        }
    }
}

if ($results.Count -gt 0) {
    $results | Export-Csv -Path $ReportPath -NoTypeInformation
    Write-Host "`n$($results.Count) inactive guest accounts found. Report saved to $ReportPath" -ForegroundColor Green
}
else {
    Write-Host "`nNo inactive guest accounts found past the $InactiveDays day threshold." -ForegroundColor Green
}

Disconnect-MgGraph | Out-Null

Read it before you run it, and test in a safe tenant first.