Skip to Content
Microsoft 365Entra IDDisabled & Stale Accounts

Disabled & Stale Accounts

Identifies user accounts that haven’t signed in within a configurable number of days. Optionally disables them.

Requirements

Install-Module Microsoft.Graph.Authentication -Scope CurrentUser Install-Module Microsoft.Graph.Users -Scope CurrentUser

Requires PowerShell 7.0+.

Usage

# Report accounts inactive for 90+ days (default) .\disabled-stale-accounts.ps1 # Custom inactivity threshold .\disabled-stale-accounts.ps1 -InactiveDays 60 # Actually disable the stale accounts (with WhatIf support) .\disabled-stale-accounts.ps1 -Disable # Preview what would be disabled .\disabled-stale-accounts.ps1 -Disable -WhatIf

Script

#requires -Version 7.0 [CmdletBinding(SupportsShouldProcess)] param( [int]$InactiveDays = 90, [switch]$Disable, [string]$OutputPath = (Join-Path $PSScriptRoot 'stale-accounts.csv') ) Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' function Ensure-Module { param([Parameter(Mandatory=$true)][string]$Name) if (-not (Get-Module -ListAvailable -Name $Name)) { Write-Error "Required module '$Name' not found. Install with: Install-Module $Name -Scope CurrentUser" exit 1 } Import-Module $Name -ErrorAction Stop | Out-Null } Ensure-Module -Name Microsoft.Graph.Authentication Ensure-Module -Name Microsoft.Graph.Users $scopes = @('User.Read.All', 'AuditLog.Read.All', 'Directory.ReadWrite.All') Connect-MgGraph -Scopes $scopes | Out-Null $cutoff = (Get-Date).AddDays(-[math]::Abs($InactiveDays)) # signInActivity requires specific permissions and may not be available to all tenants $users = Get-MgUser -All -Property 'id,displayName,userPrincipalName,accountEnabled,signInActivity' $stale = foreach ($u in $users) { $last = $null if ($u.AdditionalProperties.ContainsKey('signInActivity')) { $sia = $u.AdditionalProperties['signInActivity'] $last = if ($sia['lastSignInDateTime']) { [datetime]$sia['lastSignInDateTime'] } else { $null } } if (-not $last -or $last -lt $cutoff) { [pscustomobject]@{ DisplayName = $u.DisplayName UserPrincipalName = $u.UserPrincipalName AccountEnabled = $u.AccountEnabled LastSignInDateTime = $last CandidateDisable = $true UserId = $u.Id } } } $stale | Export-Csv -NoTypeInformation -Path $OutputPath Write-Host "Identified $($stale.Count) stale accounts; saved to $OutputPath" -ForegroundColor Yellow if ($Disable) { foreach ($row in $stale) { if ($PSCmdlet.ShouldProcess($row.UserPrincipalName, 'Disable account')) { try { Update-MgUser -UserId $row.UserId -AccountEnabled:$false Write-Host "Disabled: $($row.UserPrincipalName)" -ForegroundColor Green } catch { Write-Warning "Failed to disable $($row.UserPrincipalName): $($_.Exception.Message)" } } } }

Output

Exports a CSV (stale-accounts.csv) with:

  • DisplayName / UserPrincipalName
  • AccountEnabled
  • LastSignInDateTime
  • CandidateDisable

Graph Permissions

  • User.Read.All
  • AuditLog.Read.All
  • Directory.ReadWrite.All (only needed with -Disable)

Note: The signInActivity property requires an Entra ID P1/P2 licence on the tenant.

Last updated on