Get detailed data from your Teams meetings.

Today I will teach you how to setup a Graph API subscription to get a Microsoft Teams meeting id, which you again can use to get detailed diagnostics data from your meetings.

First we need to create a subscription to the Call Records endpoint in the Graph API change notifications system (more info here). To do this we need a incoming web hook of some sort that can give a response to the subscription request. Power Automate can be used for this.

Go to Power Automate and create a new ‘Instant cloud flow’.

Give it a name, select ‘When an HTTP request is received’ and click Create.

Click ‘New step’.

Search for ‘Data Operation’.

Select ‘Compose’.

Here you need to create an expression as the input:

trigger()?['outputs']?['queries']?['validationToken']


Click OK.

Click ‘+’ and ‘New Action’.
Search for and select ‘Condition’.

Set ‘Outputs’ from ‘Compose’ to ‘is not equal to’ ‘null’.

Under ‘If yes’ search for and select ‘Response’,

Set ‘Status Code’ to be ‘200’.
Headers need to be ‘content-type’ and ‘text/plain’.
Body should be the ‘Outputs’ from ‘Compose’.

Under ‘If no’ search for and select ‘Response’.

Set ‘Status Code’ to ‘202’

Save the flow and copy the ‘HTTP POST URL’ you get from ‘When a HTTP request is received’ connector at the top.

What you need to do now is to create a Azure Application with the CallRecords.Read.All Application rights to be able to authenticate against Graph API. You will also create an application secret. How to do these two things, you can read about in this article: Trigger Azure Automation with a Teams team request Form!

Then you need to add your details (client id, tenant id, application secret and notifications url) and run this script. You also need to change the expirationDateTime in $Body to a suitable value. A subscription can last for up to 72 hours, so remeber to set this code up as a scheduled task in Azure Automation or something similar to keep the subscription alive.

function Get-MSGraphAppToken{
    <#  .SYNOPSIS
        Get an app based authentication token required for interacting with Microsoft Graph API
    .PARAMETER TenantID
        A tenant ID should be provided.
 
    .PARAMETER ClientID
        Application ID for an Azure AD application. Uses by default the Microsoft Intune PowerShell application ID.
 
    .PARAMETER ClientSecret
        Web application client secret.
        
    .EXAMPLE
        # Manually specify username and password to acquire an authentication token:
        Get-MSGraphAppToken -TenantID $TenantID -ClientID $ClientID -ClientSecert = $ClientSecret 
    .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
    }
}
$tenantId = 'Enter tenant ID here'
$ClientID = 'Enter client ID here'
$ClientSecret = "Enter client secret here"
$global:Header = Get-MSGraphAppToken -TenantID $tenantId -ClientID $ClientID -ClientSecret $ClientSecret 


$body = @'
{
   "changeType": "created",
   "notificationUrl": "Enter your HTTP Post URL here",
   "resource": "/communications/callRecords",
   "expirationDateTime":"2021-01-19T08:28:45.9356913Z",
   "clientState": "secretClientValue",
   "latestSupportedTlsVersion": "v1_2"
}
'@

Invoke-RestMethod -Body $body -Method POST  -Headers $global:Header -ContentType 'application/json' -Uri 'https://graph.microsoft.com/v1.0/subscriptions'

Here you see a confirmation that the subscription is setup.

If you go to your Flow in PowerAutomate you can also see that the flow responded to the subscription request.

Now you can do a test meeting in Teams, and after 10-20 minutes you should see another run in your Flow.

If you open that run, you can see that there is some data in the output. What you need is the resource. This is the ID of the meeting. With this ID you can do a Graph API call to ‘…/communications/callRecords/3eb26387-6b47…..’ to get the detailed data from your meeting.

Now we are going to setup a Runbook in Azure Automation that will get the meeting ID from Power Automate and do a Graph API call to get the detailed data from the meeting.

Edit your Runbook and create a required parameter and output the value of that parameter. Save and ‘Publish’.

param(

    [parameter (Mandatory = $true)]

    [object] $callrecorddata
)

$callrecorddata.value

Go back to your Flow in Power Automate and click ‘Add an action’ between the HTTP and Compose action.

Search for ‘variable’ and select ‘Initialize variable’.

Give it a name, set type to ‘Object’ and value to ‘Body’ of the HTTP request.

Click ‘Add an action’ after the variable.

Search for ‘create job’ and select it.

What we are doing now is to send the variable we created into Azure Automation. So every time a new meeting from the subscription triggers the incoming webhook, it passes the meeting id on to our runbook in Azure Automation. Select your Azure subscription, resource group, automation account and runbook. As the Runbook Parameter you sett the variable we created.

Now test your flow with data from a previous run so you dont have to create a new test meeting.

Go back to your runbook in Azure. You can see that a job have completed. Open it and take a look at the Output.

You can see that we have gotten the meeting ID information we need.

Edit your runbook, replace the script with the one below and save/publish. Remember to add your client ID, tenant ID and application secret to the script.


param ( 

    [Parameter (Mandatory = $true)] 

    [object] $callrecorddata 

) 

$resource = ($callrecorddata.value).resource


function Get-MSGraphAppToken{
    <#  .SYNOPSIS
        Get an app based authentication token required for interacting with Microsoft Graph API
    .PARAMETER TenantID
        A tenant ID should be provided.
 
    .PARAMETER ClientID
        Application ID for an Azure AD application. Uses by default the Microsoft Intune PowerShell application ID.
 
    .PARAMETER ClientSecret
        Web application client secret.
        
    .EXAMPLE
        # Manually specify username and password to acquire an authentication token:
        Get-MSGraphAppToken -TenantID $TenantID -ClientID $ClientID -ClientSecert = $ClientSecret 
    .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
    }
}
$tenantId = 'xxxxxxx'
$ClientID = 'xxxxx'
$ClientSecret = "xxxx"
$global:Header = Get-MSGraphAppToken -TenantID $tenantId -ClientID $ClientID -ClientSecret $ClientSecret 




Invoke-RestMethod -Method GET -Headers $global:Header -ContentType 'application/json' -Uri ("https://graph.microsoft.com/v1.0/$resource"+'?$expand=sessions($expand=segments)')

Do a test run of your Power Automate Flow again. If you now take a look at the automation job, you see a lot of interesting meeting details.

If you dig deeper in you find many cool things you can report on. What would be the next step, that i wont be covering in this blog article is where to store the information. You could store it in a Azure SQL or maybe a SharePoint list. For those of you familiar with the Skype for Business CDR SQL database might have seen many of the same data types before. Its cool to have the same data for Microsoft Teams, and this can be very helpful in creating your own custom reports.

6 thoughts on “Get detailed data from your Teams meetings.

    • Thanks.
      I wanted to have a fully automated unnatended service that could process the subscription updates from Graph and pass it on to Azure SQL for storage.

      Like

  1. This is a very good guide! Do you think is it possibile to download the log of a meeting (connections and disconnections) using the GraphApi in an Android app? Tanks!

    Like

    • Thanks! That would most probbably be possible in some way since it all is graph. Probably would be easier to create a Power app, so you don’t need to do coding 🙂 If i remember correctly, you can see connect/disconnect in this report also.

      Like

  2. Would this solution provide the participant duration ? I’m looking for a automated way to obtain participations join time and leave time for teams meetings. Also need to account for re-joiners.

    Like

Leave a reply to Alexander Holmeset Cancel reply