Entra / Microsoft 365 ยท Groups
Update Planner plan from message center
Reads Microsoft 365 message center notifications and creates or updates tasks in a Planner plan owned by a group.
Connect & set up
Run these once per session. All scopes are read-only unless the script makes changes.
Connect-MgGraph -NoWelcome -Scopes Group.Read.All, Tasks.ReadWrite, ServiceMessage.Read.All, User.Read.All
Run it
The main script. Copy it, or download the .ps1 and run it from your console.
param([int] $LookbackDays = 7)Connect-MgGraph -NoWelcome -Scopes Group.Read.All, Tasks.ReadWrite, ServiceMessage.Read.All, User.Read.All$ProcessedDataFile = "c:\temp\Announcements.csv"$GroupId = '78b47932-b35f-4b26-94c2-3228cb234b07'$TargetPlanName = 'Admin Task Assignment'$Now = Get-DateWrite-Host ("Staring synchronization run at {0}" -f $Now)# Attempt to import the file of previously processed data. If we have a file, set the window to query for new# announcements to 7 days. If not, use 60 days[array]$ProcessedData = Import-CSV $ProcessedDataFile -ErrorAction SilentlyContinueIf ($ProcessedData) {$CheckDate = [datetime]::UtcNow.AddDays(-$LookbackDays).ToString("s") + "Z"} Else {$CheckDate = [datetime]::UtcNow.AddDays(-$LookbackDays).ToString("s") + "Z"}Write-Host "Finding message center announcement posts"# Fetch all available announcements to create a new data file of previously processed data. Then trim# the data to find the announcements created in the window we want to process[array]$AllAnnouncements = Get-MgServiceAnnouncementMessage -Sort 'LastmodifiedDateTime desc' -All[array]$AnnouncementsForPeriod = $AllAnnouncements | Where-Object {$_.StartDateTime -as [datetime] -gt $CheckDate}If ($AnnouncementsForPeriod.Count -eq 0) {Write-Host ("No new message center posts found since {0} - exiting" -f $CheckDate)Break} Else {Write-Host ("{0} message center posts found to process..." -f $AnnouncementsForPeriod.count)}# Now find what announcements we need to process (not seen before or not changed since we processed)[array]$Announcements = $nullForEach ($Announcement in $AnnouncementsForPeriod) {If ($Announcement.Id -notin $ProcessedData.Id) {$Announcements += $Announcement}}Write-Host "Checking for target plan and buckets..."[array]$Plans = Get-MgGroupPlannerPlan -GroupId $GroupId$TargetPlan = $Plans | Where-Object Title -Match $TargetPlanNameIf (!$TargetPlan) {Write-Host ("Unable to find the target plan ({0}) - exiting" -f $TargetPlanName)Break}[array]$Buckets = Get-MgPlannerPlanBucket -PlannerPlanId $TargetPlan.IdIf (!$Buckets) {Write-Host "No buckets found in the target plan - exiting"Break}$ExchangeBucket = $Buckets | Where-Object Name -match 'Exchange Online'$SharePointBucket = $Buckets | Where-Object Name -match 'SharePoint Online'$TeamsBucket = $Buckets | Where-Object Name -match 'Teams'$PlannerBucket = $Buckets | Where-Object Name -match 'Planner'$GeneralBucket = $Buckets | Where-Object Name -match 'For assignment'$PowerBucket = $Buckets | Where-Object Name -match 'Power Platform'$AdminBucket = $Buckets | Where-Object Name -match 'Administration'$EntraBucket = $Buckets | Where-Object Name -match 'Entra'$OfficeAppsBucket = $Buckets | Where-Object Name -match 'Office Apps'$PurviewBucket = $Buckets | Where-Object Name -match 'Purview'$VivaBucket = $Buckets | Where-Object Name -match 'Viva'Write-Host "Finding plan members and creating assignee details..."[array]$PlanMembers = Get-MgGroupMember -GroupId $GroupId# Populate the assignees from the group members$ExchangeAssignee = $PlanMembers.additionalProperties | Where-Object mail -match 'lotte.vetler@office365itpros.com'$TeamsAssignee = $PlanMembers.additionalProperties | Where-Object mail -match 'michelle.dubois@office365itpros.com'$AdminAssignee = $PlanMembers.additionalProperties | Where-Object mail -match 'Marty.King@office365itpros.com'$SharePointAssignee = $PlanMembers.additionalProperties | Where-Object mail -match 'Rene.artois@office365itpros.com'$PowerAssignee = $PlanMembers.additionalProperties | Where-Object mail -match 'hans.flick@office365itpros.com'$PlannerAssignee = $PlanMembers.additionalProperties | Where-Object mail -match 'mimie@office365exchangebook.com'$EntraAssignee = $PlanMembers.additionalProperties | Where-Object mail -match 'brian.weakliam@office365itpros.com'$OfficeAppsAssignee = $PlanMembers.additionalProperties | Where-Object mail -match 'hans.geering@office365itpros.com'$GeneralAssignee = $PlanMembers.additionalProperties | Where-Object mail -match 'kim.Akers@office365itpros.com'$PurviewAssignee = $PlanMembers.additionalProperties | Where-Object mail -match 'James.A.Abrahams@office365itpros.com'$VivaAssignee = $PlanMembers.additionalProperties | Where-Object mail -match 'mimie@office365exchangebook.com'# Planner likes to assign people via user id, so we make sure that we have it# The select statement is there because of the spurious output bug in SDK V2.13.1 and V2.14$PowerAssignee = (Get-MgUser -UserId $PowerAssignee.userPrincipalName) | Select-Object -First 1$TeamsAssignee = (Get-MgUser -UserId $TeamsAssignee.userPrincipalName) | Select-Object -First 1$SharePointAssignee = (Get-MgUser -UserId $SharePointAssignee.userPrincipalName) | Select-Object -First 1$AdminAssignee = (Get-MgUser -UserId $AdminAssignee.userPrincipalName) | Select-Object -First 1$ExchangeAssignee = (Get-MgUser -UserId $ExchangeAssignee.userPrincipalName) | Select-Object -First 1$PlannerAssignee = (Get-MgUser -UserId $PlannerAssignee.userPrincipalName) | Select-Object -First 1$EntraAssignee = (Get-MgUser -UserId $EntraAssignee.userPrincipalName) | Select-Object -First 1$OfficeAppsAssignee = (Get-MgUser -UserId $OfficeAppsAssignee.userPrincipalName) | Select-Object -First 1$GeneralAssignee = (Get-MgUser -UserId $GeneralAssignee.userPrincipalName) | Select-Object -First 1$PurviewAssignee = (Get-MgUser -UserId $PurviewAssignee.userPrincipalName) | Select-Object -First 1$VivaAssignee = (Get-MgUser -UserId $VivaAssignee.userPrincipalName) | Select-Object -First 1# Some generic settings needed for an assignment$GenericTaskData = @{}$GenericTaskData.Add("@odata.type", "#microsoft.graph.plannerAssignment")$GenericTaskData.Add("orderHint"," !")$GenericCategoryData = @{}$GenericCategoryData.Add("@odata.type", "#microsoft.graph.plannerAppliedCategories")Write-Host "Examining message center posts..."$Report = [System.Collections.Generic.List[Object]]::new()ForEach ($Announcement in $Announcements) {[array]$Services = $Announcement.Services$TaskTitle = ("[{0}] {1} [{2}]" -f ($Services -join ","), $Announcement.Title, $Announcement.Id)Write-Host ("Processing task {0}" -f $Announcement.Title)$AssignedUserId = $AdminAssignee.Id# Figure out who should be assignedSwitch -wildcard ($Services) {"Microsoft Teams*" {$AssignedUserId = $TeamsAssignee.Id$AssignedName = $TeamsAssignee.DisplayName$TargetBucket = $TeamsBucket.Id$BucketName = "Teams"$Category = "category4"}"*Power*" {$AssignedUserId = $PowerAssignee.Id$AssignedName = $PowerAssignee.DisplayName$TargetBucket = $PowerBucket.Id$BucketName = "Power Platform"$Category = "category1"}"Exchange*" {$AssignedUserId = $ExchangeAssignee.Id$AssignedName = $ExchangeAssignee.DisplayName$TargetBucket = $ExchangeBucket.Id$BucketName = "Exchange Online"$Category = "category2"}"SharePoint*"{$AssignedUserId = $SharePointAssignee.Id$AssignedName = $SharePointAssignee.DisplayName$TargetBucket = $SharePointBucket.Id$BucketName = "SharePoint Online"$Category = "category7"}"*Plan*" {$AssignedUserId = $PlannerAssignee.Id$AssignedName = $PlannerAssignee.DisplayName$TargetBucket = $PlannerBucket.Id$BucketName = "Planner"$Category = "category8"}"Microsoft 365*" {$AssignedUserId = $AdminAssignee.Id$AssignedName = $AdminAssignee.DisplayName$TargetBucket = $AdminBucket.Id$BucketName = "Administration"$Category = "category3"}"Entra*" {$AssignedUserId = $EntraAssignee.Id$AssignedName = $EntraAssignee.DisplayName$TargetBucket = $EntraBucket.Id$BucketName = "Entra"$Category = "category5"}"*Office*" {$AssignedUserId = $OfficeAppsAssignee.Id$AssignedName = $OfficeAppsAssignee.DisplayName$TargetBucket = $OfficeAppsBucket.Id$BucketName = "Office Apps"$Category = "category6"}"*Viva*" {$AssignedUserId = $VivaAssignee.Id$AssignedName = $VivaAssignee.DisplayName$TargetBucket = $VivaBucket.Id$BucketName = "Viva"$Category = "category9"}"*Purview*" {$AssignedUserId = $PurviewAssignee.Id$AssignedName = $PurviewAssignee.DisplayName$TargetBucket = $PurviewBucket.Id$BucketName = "Purview"$Category = "category11"}default {$AssignedUserId = $GeneralAssignee.Id$AssignedName = $GeneralAssignee.DisplayName$TargetBucket = $GeneralBucket.Id$BucketName = "For assignment"$Category = "category10"}}$TaskAssignments = @{}$TaskAssignments.Add($AssignedUserId, $GenericTaskData)# Create a text only version of the notes$Body = $Announcement | Select-Object -ExpandProperty Body$HTML = New-Object -Com "HTMLFile"$HTML.write([ref]$body.content)$TextOnly = $HTML.body.innerText# Add announcement properties to the top of the notes$AnnouncementStartDate = Get-Date $Announcement.StartDateTime -format "dd-MMM-yyyy"$AnnouncementLastModifiedDate = Get-Date $Announcement.LastmodifiedDateTime -format "dd-MMM-yyyy"$TextHeading = ("Message ID: {0} `nPublished date: {1} `nLast updated date: {2} `nCategory: {3} `n" -f `$Announcement.Id, $AnnouncementStartDate, $AnnouncementLastModifiedDate, $Announcement.Category)[array]$Tags = $Announcement.Tags | Sort-Object$TextBody = ("{0}Tags: {1} `n`n{2}" -f $TextHeading, ($Tags -join ", "), $TextOnly)$TaskDescription = @{}$TaskDescription.Add('description', $TextBody)$TaskLabels = @{}$TaskLabels.Add($Category,$true)$TaskLabels$TaskParameters = @{}$TaskParameters.Add('planId',$TargetPlan.Id)$TaskParameters.Add('bucketid',$TargetBucket)$TaskParameters.Add('title', $TaskTitle)$TaskParameters.Add('assignments', $TaskAssignments)$TaskParameters.Add('priority', '5')$TaskParameters.Add('startDateTime', $Announcement.LastModifiedDateTime)$TaskParameters.Add('details',$TaskDescription)$TaskParameters.Add('appliedCategories', $TaskLabels)# If an end date is given, use it as the due date for the taskIf ($Announcement.EndDateTime) {$TaskParameters.Add('dueDateTime', $Announcement.EndDateTime)}$NewTask = New-MgPlannerTask -BodyParameter $TaskParametersIf ($NewTask) {Write-Host ("Task {0} assigned to {1}" -f $TaskTitle, $AssignedName) -ForegroundColor Red$DataLine = [PSCustomObject][Ordered]@{TaskId = $Announcement.IDTitle = $TaskTitleAssignedto = $AssignedNameBucket = $BucketNameType = "New assignment"}$Report.Add($DataLine)}}# Now we need to check if any changes have happened to tasks in our plan# Start off by getting the set of known tasks in the plan# We're only interested in incomplete tasks, so that's why we filter out any that are marked as complete[array]$CurrentTasks = Get-MgPlannerPlanTask -PlannerPlanId $TargetPlan.Id -All -PageSize 999 | Where-Object {$_.null -eq $_.CompletedDateTime}ForEach ($Task in $CurrentTasks) {# For each task, get its MC identifier$Start = $Task.Title.IndexOf("MC")$MCId = $Task.Title.SubString($Start,8)$OnlineTask = $AllAnnouncements | Where-Object Id -match $MCId# If the Last modified date for the online task has changed, update our taskIf ($Task.StartDateTime -lt $OnlineTask.LastModifiedDateTime) {Write-Host "Updating" $MCId# Update task$UpdateParams = @{}$UpdateParams.Add('startdatetime', $Task.LastModifiedDateTime)Update-MgPlannerTask -PlannerTaskId $Task.Id -BodyParameter $UpdateParams -IfMatch $Task.additionalProperties.'@odata.etag'# Update task details with whatever the body now is (normally has some details about a rescheduled date)$TaskDetails = Get-MgPlannerTaskDetail -PlannerTaskId $Task.Id$Body = $Task | Select-Object -ExpandProperty Body$HTML = New-Object -Com "HTMLFile"$HTML.write([ref]$body.content)$TextOnly = $HTML.body.innerText$TaskDescription = @{}$TaskDescription.Add('description', $TextOnly)Update-MgPlannerTaskDetail -PlannerTaskId $TaskDetails.Id -BodyParameter $TaskDescription -IfMatch $TaskDetails.additionalProperties.'@odata.etag'# Get current assignments$Uri = ("https://graph.microsoft.com/v1.0/planner/tasks/{0}" -f $Task.Id)$Data = Invoke-MgGraphRequest -Uri $Uri -Method GET[hashtable]$Assignments = $Data.Assignments[array]$AssignmentNames = $nullForEach ($Assignment in $Assignments.GetEnumerator()) {$Name = (Get-MgUser -UserId $Assignment.Name).DisplayName$AssignmentNames += $Name}# Report what we've done$DataLine = [PSCustomObject][Ordered]@{TaskId = $Task.IDTitle = $Task.TitleAssignedto = ($AssignmentNames -join ",")Bucket = (($Buckets | Where-Object Id -match $Task.BucketId).Name)Type = "Updated task"}$Report.Add($DataLine)}}$Report | Out-GridView[array]$ReportNewAssignments = $Report | Where-Object Type -match 'New assignment'[array]$ReportUpdatedTasks = $Report | Where-Object Type -match 'Updated task'# Export the data that we've processed so it doesn't get processed again$AllAnnouncements | Export-CSV -NoTypeInformation $ProcessedDataFileWrite-Host ("All done. {0} tasks assigned and {1} updated tasks" -f $ReportNewAssignments.count, $ReportUpdatedTasks.count)
Parameters
ParameterDefaultNotes
-LookbackDays7How many days back to search for newly created mailboxes or recent activity.Attribution
Author
Office365itpros