SharePointVault ~/scripts/lists-and-libraries ⌕ Search 404 pages, error messages, scripts…Ctrl K ★ Star
M↓ README.mdPS Get-ListIndexAdvice.ps1
sharepoint-vault›scripts›lists-and-libraries›Get-ListIndexAdvice.ps1historyedit on GitHub
● SCRIPTlists-and-libraries

Get-ListIndexAdvice.ps1

Reports which large lists have no useful indexed columns - and how many of the 20 allowed indexes each list has left.

⬇ downloadview on GitHub ↗146 lines · 5.7 KB
<#
.SYNOPSIS
    Reports which large lists have no useful indexed columns - and how many
    of the 20 allowed indexes each list has left.

.DESCRIPTION
    The 5,000-item list view threshold is about *scanned* rows, not returned
    rows: a filter on an unindexed column has to walk the whole list, so it
    fails the moment the list crosses the limit. An index on the filtered
    column turns the same query into a seek and it works again.

    This script pairs item counts with the actual index situation per list:
    which columns are indexed, how many index slots remain (SharePoint allows
    20 per list), and which lists are big enough to be at risk while carrying
    no index at all.

    READ-ONLY: this script creates no indexes. Adding one is a write against
    a live list and belongs in a change window, not in a report.

    Module note: uses PnP.PowerShell - the official SharePoint Online
    Management Shell cannot enumerate lists or read their fields and indexes.

    Related gotcha in this repo:
    gotchas/lists/list-view-threshold-and-indexes.md

.PARAMETER SiteUrl
    One or more full site URLs to scan.

.PARAMETER ClientId
    Client ID of your own Entra ID app registration used by PnP.PowerShell.
    https://pnp.github.io/powershell/articles/registerapplication.html

.PARAMETER MinItemCount
    Only examine lists with at least this many items. Default 2000.

.PARAMETER OutputPath
    CSV file to create. Defaults to a timestamped file in the current directory.

.EXAMPLE
    .\Get-ListIndexAdvice.ps1 -SiteUrl https://contoso.sharepoint.com/sites/projects -ClientId 00000000-0000-0000-0000-000000000000

    Scanning https://contoso.sharepoint.com/sites/projects ...
      22 list(s) at or above 2000 items.
      Contracts: 12 480 items, NO indexed column
      Archive: index limit reached (20/20)

    Done. 22 list(s) written to .\ListIndexAdvice_20260729-143912.csv
    1 list(s) are past 5,000 items with no index at all - those are already
    breaking views.

.EXAMPLE
    .\Get-ListIndexAdvice.ps1 -SiteUrl https://contoso.sharepoint.com/sites/archive -ClientId 00000000-0000-0000-0000-000000000000 -MinItemCount 500

.NOTES
    Requires : PnP.PowerShell 2.x or newer (Install-Module PnP.PowerShell)
    Auth     : Interactive (browser) sign-in per site.
    Caveat   : Creating an index on a list that is already over the threshold
               can itself fail during the day; SharePoint runs index creation
               as a background job on large lists. Schedule it off-hours.
    Samples  : scripts/sample-outputs.md - what this prints, from a real run
    Source   : https://github.com/EasyPortal365/sharepoint-vault
#>
#Requires -Modules PnP.PowerShell

[CmdletBinding()]
param(
    [Parameter(Mandatory = $true)]
    [string[]]$SiteUrl,

    [Parameter(Mandatory = $true)]
    [string]$ClientId,

    [ValidateRange(1, [int]::MaxValue)]
    [int]$MinItemCount = 2000,

    [string]$OutputPath = ".\ListIndexAdvice_$(Get-Date -Format 'yyyyMMdd-HHmmss').csv"
)

$ErrorActionPreference = 'Stop'
$report = New-Object System.Collections.Generic.List[object]

$maxIndexesPerList = 20

foreach ($url in $SiteUrl) {
    Write-Host "Scanning $url ..." -ForegroundColor Cyan

    try {
        Connect-PnPOnline -Url $url -Interactive -ClientId $ClientId

        $lists = @(Get-PnPList -Includes Hidden, ItemCount |
            Where-Object { -not $_.Hidden -and $_.ItemCount -ge $MinItemCount })

        Write-Host ("  {0} list(s) at or above {1:N0} items." -f $lists.Count, $MinItemCount)

        foreach ($list in $lists) {
            $fields  = @(Get-PnPField -List $list)
            $indexed = @($fields | Where-Object { $_.Indexed })

            # Columns people habitually filter or group by.
            $candidates = @($fields | Where-Object {
                -not $_.Indexed -and -not $_.Hidden -and
                $_.InternalName -in @('Modified', 'Created', 'Editor', 'Author', 'Status', 'Category', 'FileLeafRef')
            })

            $report.Add([pscustomobject]@{
                SiteUrl          = $url
                ListTitle        = $list.Title
                ItemCount        = $list.ItemCount
                OverHardLimit    = ($list.ItemCount -ge 5000)
                IndexedColumns   = (($indexed | Select-Object -ExpandProperty InternalName) -join ', ')
                IndexCount       = $indexed.Count
                IndexSlotsLeft   = ($maxIndexesPerList - $indexed.Count)
                AtIndexLimit     = ($indexed.Count -ge $maxIndexesPerList)
                NoIndexAtAll     = ($indexed.Count -eq 0)
                SuggestedColumns = (($candidates | Select-Object -ExpandProperty InternalName) -join ', ')
            })

            if ($indexed.Count -eq 0) {
                Write-Host ("  {0}: {1:N0} items, NO indexed column" -f $list.Title, $list.ItemCount) -ForegroundColor Yellow
            }
            elseif ($indexed.Count -ge $maxIndexesPerList) {
                Write-Host ("  {0}: index limit reached ({1}/{1})" -f $list.Title, $maxIndexesPerList) -ForegroundColor Yellow
            }
        }
    }
    catch {
        Write-Warning "Failed to scan ${url}: $($_.Exception.Message)"
    }
}

Write-Host ''
if ($report.Count -gt 0) {
    $report | Export-Csv -Path $OutputPath -NoTypeInformation -Encoding UTF8
    Write-Host ("Done. {0} list(s) written to {1}" -f $report.Count, (Resolve-Path $OutputPath)) -ForegroundColor Green

    $urgent = @($report | Where-Object { $_.OverHardLimit -and $_.NoIndexAtAll })
    if ($urgent.Count -gt 0) {
        Write-Host ("{0} list(s) are past 5,000 items with no index at all - those are already breaking views." -f $urgent.Count) -ForegroundColor Red
    }
}
else {
    Write-Host ("Done. No list reaches {0:N0} items." -f $MinItemCount) -ForegroundColor Green
}

try { Disconnect-PnPOnline } catch { }
Was this helpful?ask or add your experience on GitHub →
Saved you an hour? A star on GitHub helps other SharePoint people find the vault.★ Star on GitHub
last updated: 2026-09-23historyedit on GitHubview source