Retrieving Teams meeting attendance report via the Microsoft Graph PowerShell module

Somewhere in the flurry of announcements from this Fall Ignite I run into a mention of the Get-MgCommunicationOnlineMeetingAttendanceReport cmdlet, and foolish me though we’d finally get some meaningful way of listing all meetings within a tenant, along with their corresponding “artifacts”, such as the attendance report. Alas, reality is often disappointing πŸ™‚

Regardless, I took this opportunity to check on the latest state of working with the corresponding Graph API endpoints and Microsoft Graph PowerShell cmdlets, just to make sure I haven’t missed anything interesting. Getting the meeting attendance report is indeed possible now via the Get-MgUserOnlineMeetingAttendanceReport cmdlet, but that one runs in the context of a user, so not exactly the thing I was looking for. Getting the same via application permissions is also possible, the big unsolved issue however remains the lack of any viable method to list all online meetings within the tenant. Nothing regarding such functionality has been published on the Graph API changelog, and latest comment over at GitHub is from June. So it seem we’re still waiting on this front.

Anyway, here are some code samples that you can use to retrieve the meeting attendance report programmatically. First, let’s start with the delegate permissions model, and the Graph explorer. Two things are needed here: most importantly the meeting ID, which we can obtain via some additional Graph API queries, but to facilitate that you’d need to grant some permissions (which is the second thing). Specifically, you’d need to grant OnlineMeetingArtifact.Read.All and OnlineMeetings.Read permissions to the Graph explorer tool. Once you have the permissions, you can copy the join URL for any meeting you’ve scheduled and use it to obtain the actual meeting ID. The URL looks something like this:

https://teams.microsoft.com/l/meetup-join/19%3ameeting_ZTg0MTQ4ZmEtNmVlYi00YzM1LThlOTktM2MxNzg0ZTYyOTA1%40thread.v2/0?context=%7b%22Tid%22%3a%22923722ba-352a-4eda-bece-09d0a84d0cfb%22%2c%22Oid%22%3a%22584bdb38-888c-4285-8871-c9766cb4791b%22%7d

Having the join URL, you can run the following GET query against the /onlineMeetings endpoint and use the $filter operator to find the matching instance:

https://graph.microsoft.com/beta/me/onlineMeetings?$filter=JoinWebUrl eq ‘https://teams.microsoft.com/l/meetup-join/19%3ameeting_ZTg0MTQ4ZmEtNmVlYi00YzM1LThlOTktM2MxNzg0ZTYyOTA1%40thread.v2/0?context=%7b%22Tid%22%3a%22923722ba-352a-4eda-bece-09d0a84d0cfb%22%2c%22Oid%22%3a%22584bdb38-888c-4285-8871-c9766cb4791b%22%7d’

You can also use the /users/{userId}/onlineMeetings endpoint, however the query does NOT work if you specify a userPrincipalName as the identifier, and expects you to provide an objectID GUID value. Really helpful πŸ™‚ Anyway, here’s how a success response would look like:

Obtaining MeetingID via the Graph explorerThe thing we care about here is the first property, id. Once we have the meeting ID, we can request the attendance report, by issuing another GET request, this time against the /onlineMeetings/{meetingId}/meetingAttendanceReport endpoint. To mix things up a bit, I’m using an example of querying against a given user here (not the /me endpoint). Here’s how successful response looks like in the Graph explorer:

Meeting attendance report via the Graph ExplorerThe resulting JSON should match the information presented in the CSV file which you can download from the Teams client. So yay, we do have a working way to obtain the meeting attendance report programmatically, at lest when it comes to “known” meetings and using delegate permissions. And since this whole thing started from the mention of a PowerShell cmdlet, let’s see how we can obtain the same report via PowerShell, shall we.

First, and this is important, make sure to use the beta profile, otherwise the corresponding Get-MgUserOnlineMeetingAttendanceReport cmdlet will not be loaded. Then, connect to the Graph and request the necessary permissions, you will be prompted to consent if needed. Next, you can use the Get-MgUserOnlineMeetingΒ  to obtain the meeting id, or just skip to the next step if you have it already. With the meeting id at hand, run the Get-MgUserOnlineMeetingAttendanceReport cmdlet to obtain the actual report. Lastly, parse it.

Select-MgProfile beta
Connect-MgGraph -Tenant michev.onmicrosoft.com -Scopes OnlineMeetingArtifact.Read.All,OnlineMeetings.Read

Get-MgUserOnlineMeeting -Filter {JoinWebUrl eq 'https://teams.microsoft.com/l/meetup-join/19%3ameeting_ODI5Njg5ATEtZGJiNS00YjFiL2kwNDQtZDYxNTg2NTEyOGQw%40thread.v2/0?context={"Tid"%3a"923212ba-352a-4eda-b1ce-09d0684d0cfb"%2c"Oid"%3a"584b2h38-888c-4b85-8871-c97a6cb4791b"}'} -UserId 584b2b38-888c-4b85-8871-c9766cb4791b | select -ExpandProperty Id

$report = Get-MgUserOnlineMeetingAttendanceReport -OnlineMeetingId 'MSo1ODRiMmIzOC04ODhjLTRiODUtODg3MS1jOTc2NmNiNDc5MWIqMCoqMTk6bWVldGluZ19aVGcwTVRRAFptRXRObVZsWWkwMFl6TTFMVGhsT1RrdE0yTXhOemc2WlRZeU9UQTFAdGhyZWFkLnYy' -UserId 58a32b38-888c-4b85-8871-c9366cf4791b

$report | ConvertTo-Json -Depth 5

{
"AttendanceRecords": [
{
"AttendanceIntervals": [
{
"DurationInSeconds": 156,
"JoinDateTime": "2021-11-05T08:00:32.3651334Z",
"LeaveDateTime": "2021-11-05T08:03:08.856403Z"
}
],
"EmailAddress": "vasil@michev.info",
"Identity": {
"DisplayName": "Vasil Michev",
"Id": "584b2b38-xxxx-xxxx-xxxx-c9766cb4791b"
},
"Role": "Organizer",
"TotalAttendanceInSeconds": 156
},
{
"AttendanceIntervals": [
{
"DurationInSeconds": 119,
"JoinDateTime": "2021-11-05T08:00:38.6778309Z",
"LeaveDateTime": "2021-11-05T08:02:38.5855885Z"
}
],
"EmailAddress": "pesho@michev.info",
"Identity": {
"DisplayName": "Pesho",
"Id": "421117a2-xxxx-xxxx-xxxx-927b04839c9b"
},
"Role": "Presenter",
"TotalAttendanceInSeconds": 119
}
],
"Id": "df48df70-7184-4598-af7e-c913d000ecb8",
"TotalParticipantCount": 2,
"AdditionalProperties": {}
}

Now, one thing I didn’t mention above is that in this case, I’m connected to the Graph via the credentials of the user who is the meeting organizer. To cover scenarios where you want to run this against other users, or even better, run in the context of an application, few additional steps will be needed. Most importantly, you will need to configure an Application access policy as detailed for example here. Make sure to scope this policy to any user you’d like to cover, or even to the whole tenant. Otherwise, you will run into errors.

You will of course have to take care of authenticating against the Graph too, with the latest versions of the MG PowerShell module supporting only certificate-based flows. Once connected, use the Get-MgUserOnlineMeeting and Get-MgUserOnlineMeetingAttendanceReport cmdlet, similar to the above examples:

Select-MgProfile beta
Connect-MgGraph -CertificateThumbprint "6AE56AE5E52DF081A868776F39AECF49FEE623BC" -TenantId michev.onmicrosoft.com -ClientId 9dd50c8b-xxxx-xxxx-xxxx-80d200b11505

Get-MgUserOnlineMeetingAttendanceReport -OnlineMeetingId 'MSo1ODRiMmIzOCA3ODhjLTRiODUtODg3MS1jOTc2NmNiNDc5MWIqMCoqMTk6bWVldGluZ19aVGcwTVRRNFptRXRObVZsWWkwMFl6TTFMVGhsT1RrdE0yTXhOemcwWlRZeU9UQTFAdGhyZWFkLnYy' -UserId 584b2b38-xxxx-xxxx-xxxx-c9766cb4791b -Verbose | fl *

AttendanceRecords : {Microsoft.Graph.PowerShell.Models.MicrosoftGraphAttendanceRecord, Microsoft.Graph.PowerShell.Models.MicrosoftGraphAttendanceRecord}
Id : df48df70-7184-4598-af7e-c913d000ecb8
TotalParticipantCount : 2
AdditionalProperties : {[@odata.context, https://graph.microsoft.com/beta/$metadata#users('584b2b38-xxxx-xxxx-xxxx-c9766cb4791b')/onlineMeetings('MSo1ODRiMmIzOC04ODhjLTRiODUtODg3MS1jOTc2NmNiNDc5MWIq
MCoqMTk6bWVldGluZ19aVGcwA1RRNFptRXRObVZsWWkwMFl6TTFMVGhsT1RrdE0yTXhOemcwWlRZeU9UQTFAdGhyZWFkLnYy')/meetingAttendanceReport/$entity]}

All good, but this still doesn’t answer how we can use the Get-MgCommunicationOnlineMeetingAttendanceReport, as mentioned in that announcement, nor the Get-MgCommunicationOnlineMeeting cmdlet to retrieve all meetings. Documentation of course is nowhere to be found, apart from the automatically generated syntax, which is sadly the norm when it comes to MG PowerShell cmdlets. The corresponding Graph API endpoint also do not mention any ways to make this work without a user context, so unless I’ve missed something obvious here, that announcement was made ahead of its time…

 

UPDATE 12/11/2021: Microsoft just announced a change to the above, which you can read about here. Serves me well for publishing stuff on the beta endpoint πŸ™‚

5 thoughts on “Retrieving Teams meeting attendance report via the Microsoft Graph PowerShell module

  1. Joe says:

    Does this method still work? There seems to be no way for admins to gather users meeting attendance reports

    Reply
    1. Vasil Michev says:

      Works fine here, and has been available under the /v1.0 endpoint for a while now.

      Reply
      1. Mark says:

        This write up is really great Vasil, thanks so much I was getting nowhere without it. As you updated yourself there is an additional step in the way MS now do it, which leads to running an extra cmd. I seemed to hit the same problem as Joe and a couple of other issues too. As you so kindly shared this in the first place, I thought I’d share what I found too. Hope that’s ok, thanks again.

        $CertThumbPrint = “Your Thumbprint”
        $TenantID = “Your Tenant ID”
        $ClientAppId = “ID for App you Created in Azure with the right perms”

        #The link people click on to join meeting
        $JoinURL = “Your meeting link here”

        #Guid of Meeting Organizer
        $UserID = “1234abcd-1a12-12ab-12a1-1cac2164e633”

        Connect-MgGraph -ClientId $ClientAppId -TenantId $TenantID -CertificateThumbprint $CertThumbPrint

        #Three Steps are needed. The meeting, the attendance report, AND the attendance record.
        $MeetingInfo = Get-MgUserOnlineMeeting -Filter “JoinWebUrl eq ‘$JoinURL'” -UserId $UserID
        $AttendanceMeetingInfo = Get-MgUserOnlineMeetingAttendanceReport -OnlineMeetingId $MeetingInfo.ID -UserId $userid
        $MeetingAttendanceRecord = Get-MgUserOnlineMeetingAttendanceReportAttendanceRecord -OnlineMeetingId $MeetingInfo.ID -UserId $userid -MeetingAttendanceReportId $AttendanceMeetingInfo.ID

        #Store Results. # This might need some finessing if your meeting had people joining and leaving multiple times. Attendance intervals is actually it’s own array. Or you just rem those bits out if you don’t need them.
        $MeetingReport = @()
        $MeetingAttendanceRecord | %{
        $Object = New-Object PSObject -Property @{
        EmailAddress = $_.EmailAddress
        Role = $_.Role
        TotalAttendanceInSeconds = $_.TotalAttendanceInSeconds
        JoinDateTime = $_.AttendanceIntervals.JoinDateTime
        LeaveDateTime = $_.AttendanceIntervals.LeaveDateTime
        ID = $_.ID
        }
        $MeetingReport += $Object
        }

        #OutPut Results
        $MeetingReport | select EmailAddress,Role,TotalAttendanceInSeconds,JoinDateTime,LeaveDateTime

        ###

        #If you have problems getting the online meeting.

        # 1.There is a possibility it is dormant and you need to “wake it up” esp if 60 days since last activity. This process is not ideal as it can’t be easily used for multiple meetings. Do NOT enter the meeting just close “join” box down or press cancel. Otherwise you’ll impact what you are trying to do.
        Start-Job {Start-Process “msteams:$joinURL”}
        #From: https://learn.microsoft.com/en-us/answers/questions/730736/microsoft-graph-teams-meeting-links-dormant-for-ge.html. #You’ll need to crtl+c in POSH window

        # 2. You need to make sure your Azure App is allowed to get online meeting info in Graph. Either do it for the users you need to report on or create a global scope.
        #https://learn.microsoft.com/en-us/graph/cloud-communication-online-meeting-application-access-policy

        Reply
  2. Robin says:

    Thanks for this write up. It’s a real pain/limitation not being able to query for all meetings in a tenancy! Unfortunately the only way may be to subscribe to change notifications (which ultimately means an exposed external endpoint) to get the meeting IDs when created. The last time I looked at this though, it wasn’t a 5 minute job πŸ™‚

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.