Public/Get-M365PUserWorkload.ps1
|
function Get-M365PUserWorkload { <# .SYNOPSIS Generates a workload report showing pending tasks per user across all accessible Planner plans. .DESCRIPTION This function retrieves all Planner plans accessible to the current user and generates a comprehensive workload report showing the number of incomplete tasks assigned to each user. The report includes task counts by user and can be filtered by group or plan. .PARAMETER GroupId Optional. Filter the report to only include plans from a specific Microsoft 365 Group. .PARAMETER PlanId Optional. Filter the report to only include a specific plan. .PARAMETER IncludeCompletedTasks Optional. Include completed tasks in the workload calculation. By default, only incomplete tasks are counted. .PARAMETER Top Optional. Limit the number of plans to analyze. Useful for large tenants. Default is all plans. .EXAMPLE Get-M365PUserWorkload Generates a workload report for all users across all accessible plans. .EXAMPLE Get-M365PUserWorkload -GroupId "group123" Generates a workload report for all plans in the specified group. .EXAMPLE Get-M365PUserWorkload -PlanId "plan456" | Sort-Object TaskCount -Descending Gets workload for a specific plan and sorts by task count. .EXAMPLE Get-M365PUserWorkload -IncludeCompletedTasks | Export-Csv -Path "workload-report.csv" Generates a full workload report including completed tasks and exports to CSV. .NOTES Requires Microsoft.Graph.Planner module and appropriate permissions: - Group.Read.All - Tasks.Read - User.Read.All #> [CmdletBinding(DefaultParameterSetName = 'All')] param( [Parameter(Mandatory = $false, ParameterSetName = 'ByGroup')] [string]$GroupId, [Parameter(Mandatory = $false, ParameterSetName = 'ByPlan')] [string]$PlanId, [Parameter(Mandatory = $false)] [switch]$IncludeCompletedTasks, [Parameter(Mandatory = $false)] [int]$Top ) begin { Write-Verbose "Starting workload analysis" Write-Verbose "Include completed tasks: $IncludeCompletedTasks" # Initialize workload tracking $userWorkload = @{} $userDetails = @{} } process { try { # Get plans based on parameter set $plans = @() switch ($PSCmdlet.ParameterSetName) { 'ByPlan' { Write-Verbose "Retrieving specific plan: $PlanId" $plan = Get-MgPlannerPlan -PlannerPlanId $PlanId if ($plan) { $plans += $plan } } 'ByGroup' { Write-Verbose "Retrieving plans for group: $GroupId" if ($Top) { $plans = Get-MgGroupPlannerPlan -GroupId $GroupId -Top $Top } else { $plans = Get-MgGroupPlannerPlan -GroupId $GroupId -All } } 'All' { Write-Verbose "Retrieving all accessible plans..." # Note: This requires enumerating all groups the user has access to Write-Verbose "Enumerating all groups..." $allGroups = if ($Top) { Get-MgGroup -Filter "groupTypes/any(c:c eq 'Unified')" -Top $Top } else { Get-MgGroup -Filter "groupTypes/any(c:c eq 'Unified')" -All } Write-Verbose "Found $($allGroups.Count) Microsoft 365 Groups" foreach ($group in $allGroups) { try { $groupPlans = Get-MgGroupPlannerPlan -GroupId $group.Id -All -ErrorAction SilentlyContinue if ($groupPlans) { $plans += $groupPlans } } catch { Write-Verbose "No access to plans in group: $($group.DisplayName)" } } } } Write-Verbose "Analyzing $($plans.Count) plan(s)" if ($plans.Count -eq 0) { Write-Warning "No plans found to analyze" return } # Process each plan $planCount = 0 foreach ($plan in $plans) { $planCount++ Write-Verbose "[$planCount/$($plans.Count)] Analyzing plan: $($plan.Title)" try { # Get all tasks in the plan $tasks = Get-MgPlannerPlanTask -PlannerPlanId $plan.Id -All # Filter tasks based on completion status $tasksToAnalyze = if ($IncludeCompletedTasks) { $tasks } else { $tasks | Where-Object { $_.PercentComplete -lt 100 } } Write-Verbose "Found $($tasksToAnalyze.Count) task(s) to analyze in this plan" # Process each task foreach ($task in $tasksToAnalyze) { # Check if task has assignments if ($task.Assignments -and $task.Assignments.AdditionalProperties) { foreach ($assignment in $task.Assignments.AdditionalProperties.Keys) { # The key is the user ID $userId = $assignment # Initialize user workload if not exists if (-not $userWorkload.ContainsKey($userId)) { $userWorkload[$userId] = @{ TotalTasks = 0 PlanTasks = @{} } } # Increment total task count $userWorkload[$userId].TotalTasks++ # Increment plan-specific task count if (-not $userWorkload[$userId].PlanTasks.ContainsKey($plan.Id)) { $userWorkload[$userId].PlanTasks[$plan.Id] = @{ PlanTitle = $plan.Title TaskCount = 0 } } $userWorkload[$userId].PlanTasks[$plan.Id].TaskCount++ # Cache user details if (-not $userDetails.ContainsKey($userId)) { try { Write-Verbose "Fetching details for user: $userId" $user = Get-MgUser -UserId $userId -Property "DisplayName,Mail,UserPrincipalName" -ErrorAction Stop $userDetails[$userId] = @{ DisplayName = $user.DisplayName Email = if ($user.Mail) { $user.Mail } else { $user.UserPrincipalName } } } catch { Write-Verbose "Could not retrieve user details for: $userId" $userDetails[$userId] = @{ DisplayName = "Unknown User" Email = $userId } } } } } } } catch { Write-Warning "Failed to analyze plan '$($plan.Title)': $_" } } # Generate report Write-Verbose "Generating workload report..." $report = foreach ($userId in $userWorkload.Keys) { $workload = $userWorkload[$userId] $details = $userDetails[$userId] [PSCustomObject]@{ UserId = $userId DisplayName = $details.DisplayName Email = $details.Email TotalTasks = $workload.TotalTasks PlansCount = $workload.PlanTasks.Count PlanDetails = ($workload.PlanTasks.Values | ForEach-Object { "$($_.PlanTitle): $($_.TaskCount)" }) -join "; " } } Write-Verbose "Report generated for $($report.Count) user(s)" return $report | Sort-Object TotalTasks -Descending } catch { Write-Error "Failed to generate workload report: $_" throw } } end { Write-Verbose "Get-M365PUserWorkload completed" } } |