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"
    }
}