Skip to content

Find guest (B2B) users who haven't signed in within a specified period.

Guest accounts that remain unused are a security risk — they can be targeted for lateral movement. This script identifies stale guests for review or cleanup.

Inspired by Find-InactiveGuestsWithAudit.ps1 and Find-OldGuestAccounts.PS1 from Office 365 for IT Pros.

Permissions

Required delegated permissions: User.Read.All Read user profiles AuditLog.Read.All Read sign-in activity User-Invite.All (optional) manage guests

Reference

View source

from datetime import datetime, timedelta, timezone

from office365.graph_client import GraphClient
from tests import test_client_id, test_client_secret, test_tenant


def find_inactive_guests(days_threshold: int = 90) -> list[dict]:
    """Find guest users without sign-in activity.

    Args:
        days_threshold: Days since last sign-in to flag a guest.

    Returns:
        List of guest dicts with user info and days since last activity.
    """
    client = GraphClient(tenant=test_tenant).with_client_secret(test_client_id, test_client_secret)

    cutoff = datetime.now(timezone.utc) - timedelta(days=days_threshold)
    inactive = []

    # Get all guest users (user_type == "Guest")
    guests = client.users.filter("userType eq 'Guest'").get_all().execute_query()

    for guest in guests:
        last_signin = getattr(guest, "sign_in_activity", None)
        if last_signin:
            last_success = getattr(last_signin, "last_successful_sign_in_date_time", None)
        else:
            last_success = None

        # Also check creation date as fallback
        created = getattr(guest, "created_date_time", None)

        last_activity = last_success or created
        if last_activity and last_activity < cutoff:
            days_since = (datetime.now(timezone.utc) - last_activity).days
            inactive.append(
                {
                    "upn": guest.user_principal_name,
                    "display_name": guest.display_name,
                    "created": created,
                    "last_signin": last_success,
                    "days_since_activity": days_since,
                }
            )
        elif not last_activity:
            # No sign-in history at all
            inactive.append(
                {
                    "upn": guest.user_principal_name,
                    "display_name": guest.display_name,
                    "created": created,
                    "last_signin": None,
                    "days_since_activity": days_threshold + 1,
                }
            )

    inactive.sort(key=lambda x: x["days_since_activity"], reverse=True)
    return inactive


def main():
    print("Finding inactive guest accounts (90+ days)...")
    guests = find_inactive_guests(days_threshold=90)

    if not guests:
        print("No inactive guest accounts found.")
        return

    print(f"\nFound {len(guests)} inactive guests:\n")
    for g in guests:
        last = g["last_signin"].strftime("%Y-%m-%d") if g["last_signin"] else "Never"
        print(f"  {g['upn']:45s}  {g['display_name'][:25]:25s}  Last: {last}  ({g['days_since_activity']} days)")


if __name__ == "__main__":
    main()

← Back to Users