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.