Public/Copy-M365PPlan.ps1
|
function Copy-M365PPlan { <# .SYNOPSIS Clones a Microsoft Planner plan from one Microsoft 365 Group to another. .DESCRIPTION This function creates a complete copy of a Planner plan including all buckets and task titles. The new plan is created in the specified destination group. Task details like assignments, descriptions, and checklists are preserved as titles only in this MVP version. .PARAMETER SourceGroupId The ID of the source Microsoft 365 Group containing the plan to copy. .PARAMETER SourcePlanId The ID of the plan to copy. .PARAMETER DestinationGroupId The ID of the destination Microsoft 365 Group where the plan will be copied. .PARAMETER NewPlanTitle Optional. The title for the new plan. If not specified, uses the original plan title with " (Copy)" appended. .EXAMPLE Copy-M365PPlan -SourceGroupId "abc123" -SourcePlanId "plan456" -DestinationGroupId "xyz789" Copies the specified plan to the destination group with the original title plus " (Copy)". .EXAMPLE Copy-M365PPlan -SourceGroupId "abc123" -SourcePlanId "plan456" -DestinationGroupId "xyz789" -NewPlanTitle "Q1 Project Plan" Copies the plan with a custom title. .NOTES Requires Microsoft.Graph.Planner module and appropriate permissions: - Group.Read.All - Tasks.ReadWrite #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string]$SourceGroupId, [Parameter(Mandatory = $true)] [string]$SourcePlanId, [Parameter(Mandatory = $true)] [string]$DestinationGroupId, [Parameter(Mandatory = $false)] [string]$NewPlanTitle ) begin { Write-Verbose "Starting plan copy operation" Write-Verbose "Source Group: $SourceGroupId" Write-Verbose "Source Plan: $SourcePlanId" Write-Verbose "Destination Group: $DestinationGroupId" } process { try { # Get source plan details Write-Verbose "Retrieving source plan details..." $sourcePlan = Get-MgGroupPlannerPlan -GroupId $SourceGroupId -PlannerPlanId $SourcePlanId if (-not $sourcePlan) { throw "Source plan not found" } # Determine new plan title if (-not $NewPlanTitle) { $NewPlanTitle = "$($sourcePlan.Title) (Copy)" } Write-Verbose "Creating new plan: $NewPlanTitle" # Create new plan in destination group $newPlan = New-MgGroupPlannerPlan -GroupId $DestinationGroupId -Title $NewPlanTitle Write-Verbose "New plan created with ID: $($newPlan.Id)" # Get all buckets from source plan Write-Verbose "Retrieving buckets from source plan..." $sourceBuckets = Get-MgPlannerPlanBucket -PlannerPlanId $SourcePlanId -All Write-Verbose "Found $($sourceBuckets.Count) buckets" # Create bucket mapping (old ID -> new ID) $bucketMapping = @{} foreach ($bucket in $sourceBuckets) { Write-Verbose "Creating bucket: $($bucket.Name)" $newBucket = New-MgPlannerBucket -Name $bucket.Name -PlanId $newPlan.Id $bucketMapping[$bucket.Id] = $newBucket.Id Write-Verbose "Bucket created with ID: $($newBucket.Id)" } # Get all tasks from source plan Write-Verbose "Retrieving tasks from source plan..." $sourceTasks = Get-MgPlannerPlanTask -PlannerPlanId $SourcePlanId -All Write-Verbose "Found $($sourceTasks.Count) tasks" # Copy tasks to new buckets $taskCount = 0 foreach ($task in $sourceTasks) { $taskCount++ Write-Verbose "[$taskCount/$($sourceTasks.Count)] Copying task: $($task.Title)" $newBucketId = $bucketMapping[$task.BucketId] $taskParams = @{ PlanId = $newPlan.Id BucketId = $newBucketId Title = $task.Title } # Preserve optional properties if they exist if ($task.StartDateTime) { $taskParams['StartDateTime'] = $task.StartDateTime } if ($task.DueDateTime) { $taskParams['DueDateTime'] = $task.DueDateTime } if ($task.PercentComplete) { $taskParams['PercentComplete'] = 0 # Reset progress for copied tasks } $newTask = New-MgPlannerTask @taskParams Write-Verbose "Task created with ID: $($newTask.Id)" } Write-Verbose "Plan copy completed successfully" # Return the new plan object return $newPlan } catch { Write-Error "Failed to copy plan: $_" throw } } end { Write-Verbose "Copy-M365PPlan completed" } } |