Examples/PlannerImportExport/Import-Planner-From-Xlsx.ps1
#requires -modules Microsoft.Graph.PlusPlus, importExcel <# .synopsis Import tasks from a .xlsx file into a teams plan. #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', Justification="False positives when initializing variable in begin block")] param ( #File to import from $excelPath = '.\planner-Import.xlsx', #The team which owns the planner. The signed in user must be a member of the team. Being an owner but not a member will fail $TeamName = 'Consultants' , #The name of the plan to import to $PlanName = 'Team Planner' ) Write-Progress -Activity 'Importing plan' -Status 'Getting information about the plan, its team, and the team members' $teamplanner = Get-GraphTeam $TeamName -Plans | Where-Object title -eq $PlanName #region ensure team members in the sheet are really in the team #Get the members of the team and create two hash tables, one to get Mail from ID and one to get ID from mail $existingteamMembers = Get-GraphTeam $TeamName -Members | Where-Object {$_.mail} $existingteamMembers | ForEach-Object -Begin {$memberMailHash = @{}; $memberIDHash = @{} } -Process { $memberMailHash[$_.mail] = $_.id if ($_.id) {$memberIDHash[$_.id] = $_.mail } } $importedTeamMembers = Import-Excel -Path $excelPath -WorksheetName values -StartColumn 12 #If any team members have no ID, and mail is not in the hash of existing users ... # ...Look them up (assume for this demo mail = upn) and add them to the team. $importedTeamMembers.Where({$mail -and -not $_.id -and -not $memberMailHash[$_.Mail]}) | ForEach-Object { Write-Progress -Activity 'Importing plan' -Status "Processing new team member '$($_.mail)'" $user = $null $user = Get-GraphUser -UserID $_.mail -ErrorAction SilentlyContinue if ($user) { $_.id = $user.id Add-GraphGroupMember -Group $TeamName -Member $user } else {Write-Warning "($_.mail) Doesn't Seem to be a valid user"} } #endregion #region ensure the plan's 6 category labels match the ones in the sheet #6 category lables are at I1:N1 in the Plan sheet; Import with no header so they will be P1..P6 as properties on an object. Make that into 6 objects with a name and value Write-Progress -Activity 'Importing plan' -Status 'Checking categories' $importedCategories = (Import-Excel -path $excelPath -WorksheetName 'Plan' -NoHeader -StartColumn 10 -EndRow 1 -EndColumn 14).psobject.Properties | Sort-Object name #Transform categories returned by the server into hash table of p1..P6 --> name; to compare with the imported ones $existingCategories = Get-GraphPlan $teamplanner -Details | Select-Object -ExpandProperty categorydescriptions $existingCategories.psobject.Properties | ForEach-Object -Begin {$catHash = @{} } -Process {$catHash[($_.name -replace 'category','p')] = $_.value} #for our 6 imported categories check them against the corresponding entry in catHash; if different pop in a hash table that can be splatted into Set-GraphplanDetails $importedCategories.where({$catHash[($_.name)] -ne $_.value}) | ForEach-Object -Begin {$newCategories= @{} } -Process { $newCategories[($_.name -replace 'p','Category')] = $_.value} if ($newCategories.Count -gt 0) { Write-Progress -Activity 'Importing plan' -Status 'Updating categories' Set-GraphPlanDetails $teamplanner @newCategories } #endregion #region ensure buckets in the the sheet are in the plan #Get buckets from the server and make a hash of name--> ID , and import the bucket list from the Values sheet Write-Progress -Activity 'Importing plan' -Status 'Checking Buckets' $existingBuckets = Get-GraphPlan $teamplanner -buckets $existingBuckets | foreach-object -Begin {$bucketHash = @{}} -Process {$bucketHash[$_.Name] = $_.id} $importedBuckets = Import-Excel -path $excelPath -WorksheetName values -StartColumn 1 -EndColumn 3 #NewBuckets here is new or changed buckets. We don't cope with bucket A being renamed, and then bucket B being changed to "A" $newbuckets = $importedBuckets.where({-not $bucketHash[$_.bucketName]}) #Buckets with an ID but no match in the hash table must have been reanmed, and those without an ID are new... foreach ($bucket in $newbuckets.where({$_.id}) ) { Write-Progress -Activity 'Importing plan' -Status 'Renaming bucket to ' -CurrentOperation $bucket.bucketName Rename-GraphPlanBucket -Bucket $bucket.id -NewName $bucket.bucketName $bucketHash[$bucket.bucketName] = $bucket.id } foreach ($bucket in $newbuckets.where({-not $_.id}) ) { Write-Progress -Activity 'Importing plan' -Status 'Adding new bucket ' -CurrentOperation $bucket.bucketName $newbucket = New-GraphPlanBucket -Plan $teamPlanner -Name $bucket.bucketName $bucketHash[$newbucket.Name] = $newbucket.id } #endregion Write-Progress -Activity 'Importing plan' -Status 'Checking tasks' #region get existing tasks - fiddle the results so they look like what we export and we are going to import next. $existingTasks = Get-GraphPlan $teamplanner -FullTasks | Sort-Object -Property ID| Select-Object -Property @{n='Title' ; e={ $_.title }}, @{n='Bucket' ; e={ $_.BucketName }}, @{n='StartDate' ; e={ [datetime]$_.StartDateTime }}, @{n='DueDate' ; e={ [datetime]$_.dueDatetime }}, @{n='PercentComplete'; e={ $_.percentComplete }}, @{n='AssignTo' ; e={ ($_.assignments.psobject.properties.name) -join "; " }}, @{n="Checklist" ; e={ ($_.checklist.psobject.Properties.value | sort-object orderHint | Select-Object -expand title) -join "; "} }, @{n='Description' ; e={ $_.description }}, @{n="Links" ; e={ ($_.references.psobject.Properties.name -replace "%2E","." -replace "%3A",":" -replace "%25","%") -join "; "}}, @{n="Category1" ; e={if($_.appliedCategories.Category1) {'Yes'} else {$null} } }, @{n="Category2" ; e={if($_.appliedCategories.Category2) {'Yes'} else {$null} } }, @{n="Category3" ; e={if($_.appliedCategories.Category3) {'Yes'} else {$null} } }, @{n="Category4" ; e={if($_.appliedCategories.Category4) {'Yes'} else {$null} } }, @{n="Category5" ; e={if($_.appliedCategories.Category5) {'Yes'} else {$null} } } , @{n="Category6" ; e={if($_.appliedCategories.Category6) {'Yes'} else {$null} } } , @{n="Task" ; e={$_.id } } Write-Progress -Activity 'Importing plan' -Completed # it isn't from the progress message point of view it is. $existingTasks | foreach-object -Begin {$taskHash = @{}} -Process {$taskHash[$_.task] = $true } #endregion # Import the tasks from the sheet. The Category names will be customized so use our own header names, so make them usable, And pull other columns to match add-GraphPlanTask , Set-GraphPlanTask commands $importedTasks = Import-Excel -Path $excelPath -WorksheetName 'Plan' -HeaderName Title, Bucket, StartDate, DueDate, PercentComplete, AssigneeMail, Checklist, Description, Links, Category1, Category2, Category3, Category4, Category5, Category6,Task | ForEach-Object { if ($_.AssigneeMail) {Add-Member -InputObject $_ -MemberType NoteProperty -Name AssignTo -Value $memberMailHash[$_.AssigneeMail]} else {Add-Member -InputObject $_ -MemberType NoteProperty -Name AssignTo -Value ""} Add-Member -InputObject $_ -MemberType NoteProperty -Name CategoryNumbers -value $(foreach ($n in (1..6)) {if ($_."Category$n" -eq 'Yes') {$n} }) -PassThru } | Sort-Object -Property Task #If a task has no ID it is new, so add it. Check it has a title so if blank rows were imported, we don't try to process them $importedTasks.Where({$_.title -and -not $_.task}) | Add-GraphPlanTask -Plan $teamplanner #$existingTasks and Imported tasks can be compared - so compare them, show the results in Gridview to allow the user to see any which should not be modified $propsToCompare= @('Task', 'Title', 'Bucket', 'StartDate', 'DueDate', 'PercentComplete', 'assignto', 'Checklist', 'Description', 'Links', 'Category1', 'Category2', 'Category3', 'Category4', 'Category5', 'Category6') $comparison = Compare-Object $existingTasks $importedTasks.Where({$_.Task -and $taskhash[$_.Task]}) -Property $propsToCompare | Sort-Object Task,sideindicator $comparison | Select-Object @{n='Side';e={if ($_.sideindicator -eq "<=") {'Existing'} else {'Import'}}}, 'Title', 'Bucket', 'StartDate', 'DueDate', 'PercentComplete', @{n='Asignee';e={$memberIDHash[$_.assignto]}}, 'Checklist', 'Description', 'Links', 'Category1', 'Category2', 'Category3', 'Category4', 'Category5', 'Category6' | Out-GridView -Title 'Review the changes - import may overwrite newer data on the server' $Comparison.Where({$_.sideindicator -eq '=>'}) | Set-GraphPlanTask -Confirm |