Migrate Microsoft Teams Meetings from One Room Mailbox to Another

This solution was born out of a situation where a customer had removed their last on-premises Exchange Server from their hybrid topology and wanted to manage room mailboxes natively in the Microsoft 365 Cloud.

The problem is that adding and editing room mailboxes becomes very difficult when you have migrated from Exchange On-Premises to Online and removed the On-Prem Exchange Organization. In fact, Microsoft strongly advise you never to do this.

In order for the customer to break out of this limitation the decision was made to add a new accepted domain to the customer tenant that would be cloud-only and not federated with On-Prem Active Directory.

The solution meant that room mailboxes could be created in the cloud and could also benefit from modern authentication without the need for password sync as required by Teams Meeting Room devices with accounts with AzureAD single sign-on via ADFS.

The problem though was how do we protect those future meetings that have already been booked with the old federated mailbox room account?

Hybrid Workplace Conference 2022

June 29th & 30th 2022 | Mercedes-Benz World | UK

It would not be very user friendly to request them to rebook their meetings and this would be subject to room gazumping. Similarly, it is not that easy to detach a mailbox and reattach to a new account in Exchange Online (although it is possible).

The solution was to use the Microsoft Graph API to interrogate the old room mailbox account and collect all future meetings from it. From that, find the meeting organiser of each meeting and then update the meeting invite from the organizer’s mailbox with the details of the new room account in the new domain.

This is an elegant solution because it can be done programmatically via Application API permission that does not resend invites to participants. Instead, just sends the invite to the new room account. This means that the participants never know of the change, although the meeting organizer will get an acceptance from the new room mailbox.

Step 1 – Create New Room Account

Before running the script, be sure that the new room account exists in AzureAD and licensed for Microsoft Teams Rooms.

This can be done in the M365 Admin Centre by creating a new Room Mailbox and then applying the license to the AzureAD account.

For a more detailed guide, please read this blog https://docs.microsoft.com/en-us/microsoftteams/rooms/with-exchange-online

Step 2 – Create AzureAD App Registration

Go to AzureAD >> App Registrations >> New Registration

Give the app registration a name and accept the defaults and press Register

Copy the ClientID and TenantID to notepad for the next steps

Then click on “Add a certificate or Secret

Add a new client secret and press Add

Copy the secret value to notepad (you will only be able to do this here)

Now go to the API Permissions option and Add an API Permission

From the side loaded blade, choose Microsoft Graph from the available APIs and then select Application Permissions

Using the Search box, search for “Calendars” and select the following options

  • Calendars.Read
  • Calendars.ReadWrite

Press Add Permission. Now you need to Grant Admin Consent to the permission

This completes the App Registration.

Step 3 – Modifying the Script

Copy the values from the ClientID, TenantID and Secret into the relevant variables at the top of the script

Next, enter the Old Room Mailbox UPN into the $OldRoomMailbox variable. Be sure to add this in the required case as it is CaSe SeNsItIvE. Enter the New Room Mailbox UPN into the $NewRoomMailbox variable.

Step 4 – Run the Script

Now all you need to do is run the script

Demo

Here is a short demo of the script working

Script

Here is a copy of the script. Please use at your own risk

#Enter client ID, tenant ID, Secret from the Azure AD Application created
$clientId = ""
$tenantId = ""
$clientSecret = ''

#Enter the UPNs of the old and new room accounts (assumes UPN and Primary Email Address Match)

$OldRoomMailbox = 'OldRoom@domain.com.com'
$NewRoomMailbox = 'newroom@domain.com'


function Get-MSGraphAppToken{

<#  .SYNOPSIS
        Get an app based authentication token required for interacting with Microsoft Graph API
    .NOTES
        Author: Jan Ketil Skanke
        Contact: @JankeSkanke
        Created: 2020-15-03
        Updated: 2020-15-03
 
        Version history:
        1.0.0 – (2020-03-15) Function created      
#>

[CmdletBinding()]
    param (
        [parameter(Mandatory = $true, HelpMessage = "Your Azure AD Directory ID should be provided")]
        [ValidateNotNullOrEmpty()]
        [string]$TenantID,
        [parameter(Mandatory = $true, HelpMessage = "Application ID for an Azure AD application")]
        [ValidateNotNullOrEmpty()]
        [string]$ClientID,
        [parameter(Mandatory = $true, HelpMessage = "Azure AD Application Client Secret.")]
        [ValidateNotNullOrEmpty()]
        [string]$ClientSecret
        )
Process {
    $ErrorActionPreference = "Stop"
       
    # Construct URI
    $uri = "https://login.microsoftonline.com/$tenantId/oauth2/v2.0/token"
    # Construct Body
    $body = @{
        client_id     = $clientId
        scope         = "https://graph.microsoft.com/.default"
        client_secret = $clientSecret
        grant_type    = "client_credentials"
        }
    
    try {
        $MyTokenRequest = Invoke-WebRequest –Method Post –Uri $uri –ContentType "application/x-www-form-urlencoded" –Body $body –UseBasicParsing
        $MyToken =($MyTokenRequest.Content | ConvertFrom-Json).access_token
            If(!$MyToken){
                Write-Warning "Failed to get Graph API access token!"
                Exit 1
            }
        $MyHeader = @{"Authorization" = "Bearer $MyToken" }
       }
    catch [System.Exception] {
        Write-Warning "Failed to get Access Token, Error message: $($_.Exception.Message)"; break
    }
    return $MyHeader
    }
}


#Get Authorization header


$global:Header = Get-MSGraphAppToken –TenantID $tenantId –ClientID $clientId –ClientSecret $clientSecret

#Get Calendar events from the Old Mailbox Account

Write-Host "Getting calendar Events from $($OldRoomMailbox)..." -ForegroundColor Yellow

$uri = "https://graph.microsoft.com/beta/users/$($OldRoomMailbox)/calendar/events"

$OldRoom = Invoke-RestMethod –Uri $uri –Method GET –Headers $global:Header -ContentType "application/json"

#Loop through the events and get the organiser

Foreach($MeetingEvent in $OldRoom.Value){
            
            #Find the organiser of the meeting

            Write-Host "Found Meeting Belonging to $($MeetingEvent.organizer.emailAddress.name) with Subject $($MeetingEvent.subject)" -ForegroundColor Yellow
            Write-Host ""
            Write-Host ""
            
            $MeetingOrganiser = $MeetingEvent.organizer.emailAddress.address

            Write-Host "Finding the meeting in $($MeetingOrganiser)'s Mailbox" -ForegroundColor Yellow
            Write-Host ""
            Write-Host ""

            #Get a list of all their meetings in their calendar

            $uri = "https://graph.microsoft.com/beta/users/$($MeetingOrganiser)/calendar/events"

            $Events = Invoke-RestMethod –Uri $uri –Method GET –Headers $global:Header -ContentType "application/json"

            #Find the meeting invite relating to the old room mailbox
            
            $Event = $Events.Value | Where {$_.uid -eq "$($MeetingEvent.uid)"}

            if($Event){

                    Write-Host "Found the meeting in $($MeetingOrganiser)'s Mailbox. Updating Participants..." -ForegroundColor Yellow
                    Write-Host ""
                    Write-Host ""

                    #Find the attendee list
            
                    $attendees = $Event.attendees | Convertto-Json

                    #Find the OldMailbox address in the attendee list and replace with the new mailbox address
            
                    $invite = $attendees.Replace($OldRoomMailbox,$NewRoomMailbox)

                    #Build payload ready to send back to API for Event Update

                    $invite = @"
                    {
                    
                            "attendees":$invite
            
                    }

"@
           
                    $uri = "https://graph.microsoft.com/v1.0/users/$($MeetingOrganiser)/calendar/events/$($Event.id)"

                    #Update meeting invite with new room mailbox account

                    try{

                        $write = Invoke-RestMethod –Uri $uri –Method PATCH –Headers $global:Header -ContentType "application/json" -Body $invite

                        Write-Host "Successfully Updated the meeting in $($MeetingOrganiser)'s Mailbox" -ForegroundColor Green
                        Write-Host ""
                        Write-Host ""

                    }catch{

                        Write-Host $_ -ForegroundColor Red

                    }
            }else{
                    
                        Write-Host "Meeting Not Found in $($MeetingOrganiser)'s Mailbox" -ForegroundColor Orange
                        Write-Host ""
                        Write-Host ""


            }

}

Credits

Jan Ketil Skankehttps://jankesblog.com/about/ for his PS Function to get Authorization Token

Alexander Holmesethttps://alexholmeset.blog/ for his help on testing and validating the script

Share this Article on

Still Haven't Found What I'm Looking For

If you haven't found an article that has helped you solve your problem, let us know and we will try and create one for you.

Please read our privacy policy about how we store and use your data.

Leave a Comment

Your email address will not be published.

Hybrid Work Conference

JUNE 29 & 30th - Mercedes-Benz World, United Kingdom

Join us at our Hybrid Work Conference focussed on enabling people to work better using Microsoft Teams in the Hybrid Workplace

Scroll to Top