Public/get-inactiveusers.ps1

# get-inactiveusers.ps1
# Lists enabled users with no mailbox activity beyond a threshold (default 90 days).
# Accounts that have never logged in are also flagged.
# Requires: ActiveDirectory module (on-prem) OR Exchange Online + Graph for cloud-only tenants

$mode = Read-Host "Tenant type — (1) On-prem AD (2) Cloud-only M365"

$days = Read-Host "Inactivity threshold in days (default 90)"
if (-not $days) { $days = 90 }
$cutoff = (Get-Date).AddDays(-[int]$days)

if ($mode -eq "1") {
    # On-prem Active Directory path
    Import-Module ActiveDirectory

    $users = Get-ADUser -Filter { Enabled -eq $true } -Properties LastLogonDate, EmailAddress, Department |
        Where-Object { $_.LastLogonDate -lt $cutoff -or $_.LastLogonDate -eq $null } |
        Select-Object @{N="Display Name"; E={$_.Name}},
                      @{N="UPN"; E={$_.UserPrincipalName}},
                      @{N="Email"; E={$_.EmailAddress}},
                      @{N="Department"; E={$_.Department}},
                      @{N="Last Login"; E={$_.LastLogonDate}},
                      @{N="Notes"; E={ if (-not $_.LastLogonDate) { "Never Logged In" } else { "Inactive $days`d" } }} |
        Sort-Object "Last Login"

} else {
    # Cloud-only M365 path (Exchange mailbox stats)
    if (-not (Get-ConnectionInformation)) { Connect-ExchangeOnline }
    if (-not (Get-MgContext)) {
        Connect-MgGraph -Scopes "User.Read.All", "Directory.Read.All" -ContextScope Process
    }

    Write-Host "Fetching users and mailbox stats..."
    $mgUsers = Get-MgUser -All -Property "DisplayName,UserPrincipalName,AccountEnabled" |
        Where-Object { $_.AccountEnabled -eq $true }

    $stats = Get-MailboxStatistics -ResultSize Unlimited | Select-Object UserPrincipalName, LastLogonTime
    $statsIndex = @{}
    foreach ($s in $stats) {
        if ($s.UserPrincipalName) { $statsIndex[$s.UserPrincipalName.ToLower()] = $s.LastLogonTime }
    }

    $users = foreach ($u in $mgUsers) {
        $last = $statsIndex[$u.UserPrincipalName.ToLower()]
        if (-not $last -or $last -lt $cutoff) {
            [PSCustomObject]@{
                "Display Name" = $u.DisplayName
                "UPN"          = $u.UserPrincipalName
                "Last Login"   = $last
                "Notes"        = if (-not $last) { "No Mailbox Activity" } else { "Inactive $days`d" }
            }
        }
    } | Sort-Object "Last Login"
}

Write-Host "`nFound $($users.Count) inactive/never-logged-in accounts (threshold: $days days)`n"
$users | Format-Table -AutoSize

$export = (Read-Host "Export to CSV on Desktop? (y/n)") -eq "y"
if ($export) {
    $path = "$env:USERPROFILE\Desktop\InactiveUsers_$(Get-Date -Format 'yyyyMMdd').csv"
    $users | Export-Csv -Path $path -NoTypeInformation
    Write-Host "Exported to $path"
}