All scripts
Microsoft 365 742

Planner Plan and Task Overview Report

Pulls every Microsoft Planner plan across your Microsoft 365 groups and reports task counts by bucket and by status, so you can see which plans are stale or overloaded without opening each one by hand.

Get-PlannerPlanOverviewReport.ps1
<#
.SYNOPSIS
    Reports Planner plans and task counts across Microsoft 365 groups.

.DESCRIPTION
    Connects to Microsoft Graph and finds every Microsoft 365 group the
    signed in account can see, then looks up the Planner plans attached to
    each group. For every plan it counts tasks by bucket and by status
    (not started, in progress, completed) and checks how long it has been
    since the plan's last task update. This is meant for admins who inherit
    a tenant with dozens of Planner plans and no idea which ones are still
    active. The output is one row per plan so you can sort by task count or
    by last activity and spot the plans nobody is using anymore.

.AUTHOR
    Shehryar Hassan

.EXAMPLE
    .\Get-PlannerPlanOverviewReport.ps1 -ExportPath C:\Reports\PlannerOverview.csv

    Connects to Graph, builds the report for every group with a Planner
    plan, and writes the results to the given CSV file.
#>

param(
    [Parameter(Mandatory = $false)]
    [string]$ExportPath = ".\PlannerPlanOverviewReport.csv"
)

$requiredScopes = @("Group.Read.All", "Tasks.Read")

Write-Host "Connecting to Microsoft Graph..." -ForegroundColor Cyan
Connect-MgGraph -Scopes $requiredScopes -NoWelcome

$results = New-Object System.Collections.Generic.List[Object]

Write-Host "Fetching Microsoft 365 groups..." -ForegroundColor Cyan
$groups = Get-MgGroup -All -Filter "groupTypes/any(c:c eq 'Unified')" -Property "Id,DisplayName"

$groupCount = $groups.Count
$current = 0

foreach ($group in $groups) {
    $current++
    Write-Progress -Activity "Scanning groups for Planner plans" -Status $group.DisplayName -PercentComplete (($current / $groupCount) * 100)

    $plans = $null
    try {
        $uri = "https://graph.microsoft.com/v1.0/groups/$($group.Id)/planner/plans"
        $plans = Invoke-MgGraphRequest -Method GET -Uri $uri -ErrorAction Stop
    }
    catch {
        continue
    }

    if (-not $plans.value -or $plans.value.Count -eq 0) {
        continue
    }

    foreach ($plan in $plans.value) {
        $tasksUri = "https://graph.microsoft.com/v1.0/planner/plans/$($plan.id)/tasks"
        try {
            $tasks = Invoke-MgGraphRequest -Method GET -Uri $tasksUri -ErrorAction Stop
        }
        catch {
            continue
        }

        $taskItems = $tasks.value
        $notStarted = ($taskItems | Where-Object { $_.percentComplete -eq 0 }).Count
        $inProgress = ($taskItems | Where-Object { $_.percentComplete -gt 0 -and $_.percentComplete -lt 100 }).Count
        $completed = ($taskItems | Where-Object { $_.percentComplete -eq 100 }).Count

        $lastActivity = $null
        if ($taskItems.Count -gt 0) {
            $lastActivity = ($taskItems | ForEach-Object { [datetime]$_.createdDateTime } | Sort-Object -Descending | Select-Object -First 1)
        }

        $daysSinceActivity = if ($lastActivity) { (New-TimeSpan -Start $lastActivity -End (Get-Date)).Days } else { "N/A" }

        $results.Add([PSCustomObject]@{
            GroupName         = $group.DisplayName
            PlanTitle         = $plan.title
            PlanId            = $plan.id
            TotalTasks        = $taskItems.Count
            NotStarted        = $notStarted
            InProgress        = $inProgress
            Completed         = $completed
            DaysSinceLastTask = $daysSinceActivity
        })
    }
}

Write-Progress -Activity "Scanning groups for Planner plans" -Completed

if ($results.Count -eq 0) {
    Write-Host "No Planner plans found across the groups this account can read." -ForegroundColor Yellow
    return
}

$results | Sort-Object DaysSinceLastTask -Descending | Export-Csv -Path $ExportPath -NoTypeInformation

Write-Host "Report written to $ExportPath" -ForegroundColor Green
Write-Host "Plans found: $($results.Count)" -ForegroundColor Green
$stalePlans = $results | Where-Object { $_.DaysSinceLastTask -ne "N/A" -and $_.DaysSinceLastTask -gt 90 }
if ($stalePlans.Count -gt 0) {
    Write-Host "Plans with no task activity in over 90 days: $($stalePlans.Count)" -ForegroundColor Yellow
}

Disconnect-MgGraph | Out-Null

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