Teams Admin Center API

Achtung:
Diese Seite beschreibt eine inoffizielle REST-API, die von vom Teams Admin Web genutzt wird. Sie kann von Microsoft jederzeit geändert oder abgeschaltet werden. Einsatz auf eigenes Risiko!

Ich habe hier nur das Vorgehen beschrieben aber das Skript selbst nicht zum Download bereitgestellt. Ich möchte nicht den Missbrauch gegen eine API fördern.

Als Admin von Microsoft Teams mit Voice muss man auch die ein oder andere Auswertung erstellen. Es gibt diverse Schnittstellen, die Microsoft hier bereitstellt (Siehe auch Teams Reporting). Allerdings erlaubt Graph keine Abfrage aller Verbindungen, sondern nur eine Liste PSTN-Calls. Ich kann aber Details zu allen CallIDs erhalten, wenn ich die CallID habe. Auf der Seite Graph CallReport Webhook habe ich beschrieben, wie ich per Graph einen Webhook einrichte und damit über neue Verbindungen mit der dazu passenden "CallID" informiert werde. Es funktioniert aber ist nicht perfekt. Die PowerBI-Schnittstellen oder CQD liefern nur aggregierte Daten und sind auch nicht direkt per API mit eigenen Lösungen ansprechbar. Das Teams Admin Portal aber hat all diese Informationen. Also habe ich mich auf die Suche gemacht, diese Daten vielleicht per API zu erhalten.

Heutige "moderne" Webseiten funktionieren mittlerweile derart, dass JavaScript auf dem Client die Daten von einem Backend holt und aufbereitet. Es ist also nicht mehr der Webserver, der die Daten zusammensucht, formatiert und mir dann eine Tabelle zur Anzeige sendet. Das entlastet den Webserver und erlaubt "agile" Webseiten. Auf der anderen Seite kann der Entwickler seinen Code nicht mehr richtig "verstecken". Ich kann also mit "F12" den Debugger im Browser starten und z.B. einen Netzwerkmitschnitt machen. Einmal das Fenster neu aufrufen und dann nach der CallID suchen liefert sehr schnell den passenden "Request".

Teams Admin Portal

Wenn ich im Teams Admin Portal auf "Users" gehe, liefert mir die Webseite eine Liste der Benutzer.

Ein Blick mit dem Chromium Debugger zeigt einen REST-Request gegen folgende URL.

https://api.interfaces.records.teams.microsoft.com/Teams.User/users
   ?$select=OwnerUrn,EnterpriseVoiceEnabled,DirectoryStatus
   &pageSize=20

Zuerst sehe ich einen OPTIONS-Request. Die aktuellen Debugging-Tools erlauben direkt eine Übernahme in eine PowerShell oder andere Skript-Sprache:

Der erste Request ist noch ein "OPTIONS"-Request.

$session = New-Object Microsoft.PowerShell.Commands.WebRequestSession
$session.UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36"
Invoke-WebRequest `
   -UseBasicParsing `
   -Uri "https://api.interfaces.records.teams.microsoft.com/Teams.User/users?%24select=OwnerUrn%2CEnterpriseVoiceEnabled%2CDirectoryStatus&pageSize=20" `
   -Method "OPTIONS" `
   -WebSession $session `
   -Headers @{
   "method"="OPTIONS"
     "authority"="api.interfaces.records.teams.microsoft.com"
     "scheme"="https"
     "path"="/Teams.User/users?%24select=OwnerUrn%2CEnterpriseVoiceEnabled%2CDirectoryStatus&pageSize=20"
    "accept"="*/*"
     "access-control-request-method"="GET"
     "access-control-request-headers"="authorization,content-type,x-ms-correlation-id,x-requested-with"
     "origin"="https://admin.teams.microsoft.com"
     "sec-fetch-mode"="cors"
     "sec-fetch-site"="same-site"
     "sec-fetch-dest"="empty"
     "referer"="https://admin.teams.microsoft.com/"
     "accept-encoding"="gzip, deflate, br"
     "accept-language"="de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7"
   }

Sie sehen hier aber keine Authentifizierung und keinen Tenant-Namen oder GUID o.ä. Ich kann aber den Code problemlos als PS1-Datei speichern und dort ausführen. Allerdings ist die Rückgabe nur ein 200OK mit leeren Daten.

StatusCode        : 200
StatusDescription : OK
Content           : {}
RawContent        : HTTP/1.1 200 OK
                    Server: Microsoft-HTTPAPI/2.0
                    Access-Control-Allow-Origin: https://admin.teams.microsoft.com
                    Access-Control-Allow-Credentials: true
                    Access-Control-Allow-Headers: authorization,cont…
Headers           : {[Server, System.String[]], [Access-Control-Allow-Origin, System.String[]],
                    [Access-Control-Allow-Credentials, System.String[]], [Access-Control-Allow-Headers,
                    System.String[]]…}
RawContentLength  : 0
RelationLink      : {}

Interessanter ist der zweite Request, bei dem mittels "GET" und der Übermittlung eines Bearer-Token die gewünschten Nutzdaten kommen.

Der PowerShell-Code des Debuggers hilft hier aber nicht direkt weiter, denn er enthält auch das Bearer-Token, welches aber zu lang ist, um über eine Kommandozeile eingegeben zu werden.

Die Rückgabe ist dann eine JSON-Struktur:

Ich komme So an die Liste der Benutzer und einen "@nextlLink" um die nächsten 20 Benutzer abzurufen. Zudem wissen wir ja nun, dass wir JSON zurück erwarten und daher ist ein "Invoke-Restmethod" sinnvoller, als erst eine "Webseite" abzurufen und dann den Payload zu JSON zu konvertieren.

Achtung:
Die API liefert nicht nur die eigenen Voice-User sondern alle User im Tenant, d.h. auch alle Gäste und onmicrosoft.com-Benutzer. Das sehen Sie so auch im AdminCenter. Das lässt sich auch nicht sinnvoll ausschließen, denn auch diese Benutzer können ja als Gast z.B.: andere Gäste oder Mitarbeiter anrufen.

Anscheinend hat Microsoft hier auch en Throttling" oder Timeout aktiv, denn ich hatte schon einen Fehler bei exzessiven Abfragen:

Unable to get UserList. Error {
   "error":{
      "code":"InternalServerError",
      "message":"An error occurred while processing this request.",
      "target":"InternalServerError",
      "innerError":{
         "code":"InternalServerError",
         "message":"The underlying connection was closed: A connection that was expected to be kept alive was closed by the server.",
         "target":"InternalServerError","innerError":{
            ...<gekürzt>
         }
      }
   }

Es bietet sich daher an z.B. den Dialplan als Filter zu nutzen, auch wenn man damit dann nur eine Teilmenge der Anwender und damit auch der Calls bekommt.

https://api.interfaces.records.teams.microsoft.com/Teams.User/users
   ?$select=OwnerUrn,EnterpriseVoiceEnabled,DirectoryStatus
   &$filter=TenantDialPlan+eq+'Dialplanname'
   &pageSize=20

Details pro Benutzer

Ausgehend von der Benutzerliste kann ich einen Benutzer gezielt ansprechen und sehe im Bereich "Meetings & Calls" alle vergangenen Anruf des Benutzers, in denen er involviert war.

Hier sehe ich alle Verbindungen und nicht nur PSTN/DirectRouting-Anrufe. Auch Federation-Calls und Meetings sind protokolliert. Genau diese Daten hätte ich in der Form auch gerne per Graph oder eine andere API.

Auch hier nutze ich den Chromium Debugger und finde sehr schnell den entsprechenden Request der Webapplikation gegen das Backend:

Es ist gut zu erkennen, dass der Browser hier einen REST-Aufruf gegen das Backend "https://api.interfaces.records.teams.microsoft.com" macht.

GET https://api.interfaces.records.teams.microsoft.com/Skype.Analytics/Users('userid')/Communications?%24top=500

Allerdings ist hier auch wieder eine Anmeldung per Bearer vorhanden. Die Rückgabe der Calls ist wieder eine JSON-Struktur:

Das wichtige Element hierbei ist die "id", welche auch die CallID darstellt. Mit dieser CallID kann ich mir dann wieder über die "Graph Get-Callrecord"-Funktion von Graph die eigentlichen Detaildaten abrufen ohne über einen Graph CallReport Webhook gehen zu müssen. Wobei auch hier schon Details zu dem Callltyp, Start und Ende dem Status und der Teilnehmer vorliegen.

Call Details

Wenn ich schon mal so weit gekommen bin, dann interessiert mich natürlich auch noch der "Klick" auf den Call selbst. Es war ja nicht anders zu erwarten. Auch hier finde ich direkt einen REST-Aufruf mit Bearer Authentication.

https://api.interfaces.records.teams.microsoft.com/Skype.Analytics/Communications('<guid>')/Sessions('<guid>')

Die Rückgabe enthält in diesem Fall folgende Informationen:

Über die URL api.interfaces.records.teams.microsoft.com wurden aber noch andere URLs angefragt, die ebenfalls JSON-Daten geliefert haben.

Hier nur ein paar Auszüge, welche Daten per JSON damit erreichbar sind.

  • FeedbackReports
    Ich gebe eigentlich zu jedem Anruf ein Feedback. Hier war es aber noch nicht sichtbar.
  • QualityReports
    Sie sehe ich die verschiedenen genutzten Dienste. Aber die Felder "ThreasholdViolation" oder "traceroute" waren bislang immer leer.
  • DiagnosticsReports
    Hier liefert Teams weitere Details zu dem jeweiligen Call

Für einen Data Analysten tun sich hier sowohl Chancen als auch Abgründe auf. Natürlich könnte eine Firma aus solchen Daten umfangreichere Auswertungen generieren, als diese in Teams Admin Portal mühsam möglich sind. Auf der anderen kann es erschrecken, wie umfangreich welche Daten für ca. 30 Tage gesammelt werden.

Realtime

Seit Ende 2021 gibt es in Teams auch einen "Bereich "Realtime", in dem Sie für den ausgewählten Benutzer die (fast) aktuellen aktiven Sitzungen sehen und sogar erweiterte Diagnosen aktivieren können:

Auch diese Seite nutzt im Hintergrund die JSON-API

Ich kenne z.B. keinen Weg, um eine Liste der aktuell aktiven Calls in einem Tenant zu erhalten. Allerdings dürfte es auch nicht zielführend sein, nun dauerhaft jeden User abzufragen. Aber vielleicht gibt es Anwendungsfälle, Einzeldaten zu ermitteln. Daher habe ich mir diese Aufrufe auf angeschaut und habe folgende URL gefunden:

https://api.interfaces.records.teams.microsoft.com/Realtime.Analytics/GetUserDetailsInProgress/v1/users/<userGUID>?enableAdHoc=true

Danach erhalten ich genau die aktiven Meetings:

Bei einem Klick auf den Eintrag startet dann die eigentliche Überwachung. Der Request dazu wird über folgende verkürzte URL gesendet:

wss://realtimetelemetryapiprodcentralus.azurewebsites.net/signalr/connect?
   transport=webSockets
   &clientProtocol=2.1
   &instanceId=9
   &access_token=xxxxxx
   &connectionData=[{
      "name":"realtimemessaginghub"
   }]
   &tid=5

Auf die von der Cloud dann folgende Meldungen, die immer wieder erneuert werden.

Das ist eine WebSocket-Schnittstelle und oben sehen sie, dass die Daten immer wieder gesendet werden, ohne dass der Client diese neu anfordern muss. Die grafische Aufbereitung übernimmt dann der Browser.

Bearer Token (Pending)

Es ist nun höchste Zeit sich das Token etwas genauer anzuschauen. Eine REST-API mit einem Bearer-Token aufzurufen ist ja erst mal kein Hexenwert. Sicher ist die API von Microsoft nicht öffentlich dokumentiert und daher auch nicht supportet. Und natürlich brauche ich ein passendes Token. Ich habe mir daher das Token einmal decodieren lassen und sehe folgende Inhalte (gekürzt):

                                      {
Audience                           =    "aud": "48ac35b8-xxxx-xxxx-xxxx-xxxxxxxxxxx",
Issuer                             =    "iss": "https://sts.windows.net/<guid des tenant>/",
IssuedAt                           =    "iat": 1636932280,
notbefore                          =    "nbf": 1636932280,
expireat                                "exp": 1636937523,
AuthenticationClassReference       =    "acr": "1",
                                        "aio": "AVQxxxMCE=",
Authentiation                      =    "amr": [
Password                           =      "pwd",
MultiFactor                        =      "mfa"
                                        ],
ApplicationID                      =    "appid": "2ddfbe71-ed12-4123-b99b-d5fc8a062a79",
Appid AuthenticationClassReference =    "appidacr": "0",
DeviceID                           =    "deviceid": "7e2f367a-xxxx-xxxx-xxxx-xxxxxxxxxxx",
Nachname                           =    "family_name": "Carius (O365Admin)",
Vorname                            =    "given_name": "Frank",
Client IP Adresse                  =    "ipaddr": "94.31.83.223",
Voller Name                        =    "name": "Carius, Frank (O365Admin)",
OID des Anwender                   =    "oid": "8a77045b-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
                                   =    "puid": "1003000080AC45FA",
                                   =    "rh": "0.xxxxx-xxxxxx.",
Scope                              =    "scp": "user_impersonation",
Subject                            =    "sub": "xxxxxxx-xxxxxxx",
TenantCountry                      =    "tenant_ctry": "DE",
TenantRegion                       =    "tenant_region_scope": "EU",
TeanntID                           =    "tid": "de21c301-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
Eindeutiger Name                   =    "unique_name": "Frank.Carius@msxfaq.onmicrosoft.com",
UserPrincipalName                  =    "upn": "Frank.Carius@msxfaq.onmicrosoft.com",
                                   =    "uti": "RBSqyuuqtk-XE3QuG11kAA",
Version                            =    "ver": "1.0",
                                   =    "wids": [
                                   =      "194ae4cb-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
                                   =      "e8611ab8-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
                                   =      "62e90394-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
                                   =      "b79fbf4d-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
                                   =    ]
                                   =  }

Achtung: Das "Bearer Token ist nur 3939 Sekunden (ca. 65 Min) gültig.

Wenn es mir gelänge, so ein Ticket per Skript zu erhalten, dann könnte ich vermutlich ähnliche Daten ermitteln. Ich kann aber nur hoffen, dass das Backend nicht die AppID "2ddfbe71-ed12-4123-b99b-d5fc8a062a79" im Token überprüft, wenn ich mit einer anderen Lösung ein Token anfordere. Auf der anderen Seite muss der Browser ja auch ein Token anfordern und JavaScript ist ja nicht wirklich verschlüsselt.

Ich habe unterschiedliche Ansätze versucht, um mir so ein Token zu beschaffen, z.B.:

$token = Get-MsalToken `
            -ClientId "2ddfbe71-ed12-4123-b99b-d5fc8a062a79"  `
            -TenantId msxfaqdev.onmicrosoft.com  `
            -Scope "user_impersonation" `
            -RedirectUri "https://admin.teams.microsoft.com/signin-oidc"

AADSTS65002: Consent between first party application '2ddfbe71-ed12-4123-b99b-d5fc8a062a79' 
and first party resource '00000003-0000-0000-c000-000000000000' must be configured via 
preauthorization - applications owned and operated by Microsoft must get approval from 
the API owner before requesting tokens for that API.

Aber keiner der Wege hat aktuell zum Erfolg geführt. Anscheinend macht Microsoft da etwas mehr, um die Authentizität des Clients nachzuweisen.

Für die weitere Skriptlösung habe ich mich damit abgefunden, dass ich mich per Browser anmelden und das Token abfischen muss, um für maximal 65 Minuten Zugriff auf die API zu haben

PowerBI api.interfaces.records.teams.microsoft.com

Die URL "api.interfaces.records.teams.microsoft.com" sehen wir immer wieder und es scheint eine REST-API zu sein, die aber nicht weitergehend dokumentiert ist. Sie wird z.B. auch bei PowerBI genutzt und dort sogar öffentlich zur Auswertung von CQD-Reports beschrieben. Der Zugriff dort ist aber etwas verzögert.

The data is typically available within 30 minutes of the call being completed, but there are cases where it can take several hours for the data to appear.
Quelle: Auto attendant and Call queue historical reports https://learn.microsoft.com/en-us/microsoftteams/aa-cq-cqd-historical-reports#v315-published-on-january-29-2024

Allerdings habe ich bei PowerBI noch nicht nachgeschaut, welche URLs angesprochen werden

Für PowerBI gibt es ein Report-Paket, welches auch Code zum Zugriff enthält:

Power BI Query Template For Teams Voice Applications
https://www.microsoft.com/en-us/download/details.aspx?id=104623

Der Beispielcode für POSTMAN verrät eine andere ClientID. Also habe ich mir diese Werte geschnappt und versucht mich anzumelden.

$token = Get-MsalToken `
            -ClientId "a672d62c-fc7b-4e81-a576-e60dc46e951d" `
            -TenantId netatwork.onmicrosoft.com  `
            -Scope "user_impersonation"

$Admingraph = Invoke-RestMethod `
                 -Uri 'https://api.interfaces.records.teams.microsoft.com/Skype.Analytics/Users(''b39bb717-ea64-46cd-ab57-00186effe82c'')/Communications?%24top=500' `
                 -Method GET `
                 -ContentType "application/json" `
                 -Header @{ 'Authorization' = "Bearer $($token.accesstoken)"}

Invoke-RestMethod:
{
"code": "Unauthorized",
"message": "Invalid credential",
"action": "Provide valid credential."
}

Leider hat auch das nicht funktioniert. Wenn ich das Token eines Administrators verwende, dann werden die Daten aber geliefert. Anscheinend prüft das Backend wirklich die ClientID.

Um diesen Weg weiter zu gehen, muss ich irgendwie ein "AccessToken" mit "user_impersonation" für die AppID "2ddfbe71-ed12-4123-b99b-d5fc8a062a79" erhalten

Proof of Concept Get-TeamsCallIDs

Ich habe zwar noch keinen Weg gefunden, mir selbst ein Token ohne Hilfe des Administrators zu besorgen, aber er kann ja sich per Browser anmelden und das Bearer-Token als "Datei abspeichern. Es ist zu lange, um es direkt in einer PowerShell-Zeile einzugeben, so dass der Umweg als Datei mir als praktikabel erscheint. Das Skript kann das Token dann einlesen und.

  1. Per REST die Liste der Benutzer ermitteln
    Das könnte an zwar auch per offizielle per Teams PowerShell und Get-CSUser oder AzureAD-Powershell. Ich brauche ja nur die ObjectID des Benutzers aber wenn ich schon das Bearer-Token habe, kann ich auch diese API nutzen.
  2. Für jeden Benutzer die CallIDs ermitteln
    Wenn ich nicht die Graph CallReport Webhook) nutzen will, dann muss ich meinen eigenen Weg gehen.
  3. Optional für jede CallID die Details einsammeln
    Das geht auch über die Admincenter-API wobei ich auch per Graph (Siehe Graph Get-Callrecord) die Daten offiziell erhalten kann.
  4. Alle Daten ausgeben
    Fürs erste reicht eine CSV-Datei mit den CallIDs, Start- und Ende-Zeit u.a.

Da die Daten bis zu 30 Tage in die Vergangenheit zurückreichen, müsste ein Administrator quasi nur alle vier Wochen einmal manuell das Bearer-Token abrufen oder immer dann, wenn er eine Auswertung machen möchte.

Ich hoffe ja, dass irgendwann Microsoft einen eleganteren Weg zur Ermittlung der CallIDs bereitstellt

Ich habe erst mal einen POC gestartet, indem ich das Bearer-Token aus dem Mitschnitt kopiert und in ein PowerShell-Script verpackt habe. Da musste ich schon etwas tricksen, da das Token deutlich länger als die PowerShell Eingabezeile ist. Ich habe es dann schnell einmal "zusammengebaut". Hier muss man mit den Anführungszeichen aufpassen, denn es funktioniert nicht mit doppelten Anführungszeichen:

$teamsadmintoken = get-content".\admintoken.txt"
$Admingraph = Invoke-RestMethod `
                 -Uri 'https://api.interfaces.records.teams.microsoft.com/Skype.Analytics/Users(''<guid>'')/Communications?%24top=500' `
                 -Method GET `
                 -ContentType "application/json" `
                 -Header @{ 'Authorization' = "Bearer $($teamsadmintoken)"}

$Admingraph.value.count
116

PS C:\> $Admingraph.value[0]

userId               : b3xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
id                   : 5fxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
communicationType    : Call
communicationSubtype : Complete
participantCount     : 2
modalities           : {}
responseCode         :
organizerId          : +49160xxxx****
organizerIdType      : PstnNumber
organizerTenantId    :
endDateTime          : 14.11.2021 17:46:01
startDateTime        : 14.11.2021 17:35:36
isComplete           : True
status               : good
statusIsPartial      : False
usersType            : Unknown
providerType         : Teams, PSTN
threadId             :
tenantIds            : {}
participantList      : {@{displayName=+49160xxxxxxxx;
                          userPrincipalName=; 
                          userId=+49160xxxx****; 
                          userIdType=PstnNumber; 
                          tenantId=; objectId=; 
                          sipId=; 
                          phone=+49160xxxx****; 
                          isOrganizer=False; 
                          tags=System.Object[];
                          providerType=Unknown
                         },
                        @{displayName=Carius, Frank; 
                          userPrincipalName=; 
                          userId=b3xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx; 
                          userIdType=ObjectId; 
                          tenantId=dexxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx;
                          objectId=b3xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx; 
                          sipId=; 
                          phone=; 
                          isOrganizer=False; 
                          tags=System.Object[]; 
                          providerType=Unknown
                         }
                       }

Da kommt schon ein kleiner Jubelschrei auf. Über diese API könnte ich mit der Liste der Benutzer schon einmal auch ohne Webhook und Graph Subscription die Daten von Teams extrahieren.  Hier kommen dann aber sehr viele und auch redundante Daten zurück. In einem Meeting sind ja mehrere Anwender als Teilnehmer enthalten und entsprechend erscheint eine CallID nicht nur bei genau einem Teilnehmer. Es macht aber für die Auswertung wenig Sinn, eine CallID mehrfacht zu erfassen. Auf der anderen Seite möchte ich aber auch die Teilnehmer nicht unterschlagen. Daher generiert das Skript etwas einmal mehrere Dateien zur weitere Untersuchung.

Dateiname Inhalt

allcallids.csv

Eine Textdatei, in der jede gefundene CallID genau einmal vorkommt und je Zeile eine CallID steht. Diese Liste kann ich z.B. einfach mit Get-Content einlesen und dann per Graph die Details dazu holen.

allcallspairs.csv

Ein Call kann ja mehrere Teilnehmer haben, z.B.: wenn ein Anruf "weitergeleitet" wird, über eine Call-Queue geht oder eine Konferenz genutzt wird. In der JSON-Antwort ist das alles "verschachtelt" enthalten. Ich generiere hier eine Liste der "Paarungen". Ein Meeting mit 4 Teilnehmern gibt dann 4 Zeilen mit der CallID und der UserID. Quasi eine "Index-Datei" um in den CallIDs zu suchen oder erste Auswertungen nach dem Menge zu machen.

callist.json

In der größeren JSON-Datei sind alle Details zu allen erhaltenen Calls. Je nach Umgebung können das einige Megabytes werden. Ich nutze diese Datei, um in Testfeldern über kurze Zeit zu ermitteln, wie bestimmte Situationen in den Datenstrukturen abgebildet werden.

userlist.csv

Eine einfache Zuordnung der UserID zu dem Benutzer. In den Call-Reports stehen ja oft nur GUIDs und damit kann man diese auf den Benutzer zurückführen. Die Spalten sind:

"objectId","userPrincipalName","displayName","givenName","surname","lineUri","interpretedUserType","enterpriseVoiceEnabled"

Zum Einsatz müssen Sie erst einmal das Bearer-Token abgreifen:

  1. Starten Sie ihren Browser und starten Sie den Debugger mit F12
  2. Gehen Sie dann ins Teams Admin Portal auf einen Benutzer
    d.h. https://admin.teams.microsoft.com ansurfen und unter "Users" auf einen Benutzer gehen.
  3. Aktivieren Sie dann im Debugger auf dem Reiter "Netzwerk" die Checkbox bei "Preserve Log"
  4. Gehen Sie nun auf die Karteikarte "Meetings und Calls" beim Benutzer
    Wenn der Browser sehr klein ist, ist es vielleicht eine Auswahlbox. Der Browser lädt nun die Details und die Requests sollten im Debugger zu sehen sein.
  5. Suche nach "Bearer"
    Um die Information schnell zu finden, suchen wir nach "Bearer" und sollten ein paar Requests auf "api.interfaces.records.teams.microsoft.com" finden. Im Headers-Bereich ist das Bearer-Token schon markiert und über die rechte Maustaste können wie den Wert direkt kopieren.
  6. Wert in "bearer.txt" speichern
    Den String speichern wir einfach in der Datei "Bearer.txt", welches vom Skript per Default eingelesen wird. Beachten Sie, dass das Token mit "eyj" anfängt und der String "Bearer " davor entfernt werden muss.

So vorbereitet rufen Sie dann einfach das Skript auf, welches sich per HTTPS mit der inoffiziellen API verbindet und die Daten extrahiert. Eine besondere "AppPermission" o.ä. müssen Sie nicht einrichten, denn das Bearer-Token enthält schon alle erforderlichen Informationen und Berechtigungen zum Auslesen dieser Daten.

Achtung: Das Bearer-Token kann sicher noch mehr und sollte daher wie eine gültige Kombination aus Anmeldekonto und Kennwort zumindest solange geschützt sein, solange es gültig ist.

Auswertung der Ausgaben

Mit den CSV und JSON-Dateien kann man mit den Daten viel einfacher spielen und weitere Informationen ermitteln z.B. welche Werte in verschiedenen Feldern erscheinen, indem ich diese Dateien per "Import-CSV" wieder einlese und mit "Group" nach Feldern gruppiere.

Feld gefundene Werte Beschreibung

communicationtype

Call
Conference

Ich habe aktuell nur diese beiden Stati gefunden aber z.B.: keinen Hinweis auf Anrufe zu einem Auto Attendant oder eine Call Queue.

communicationSubtype

Complete
Missed
None
Voicemail

Alle Konferenzen hatten den SubType "none". Die anderen drei Subtypes waren alles Verbindungen mit Communicationtype="Call". Allerdings gibt es auch einige Calls, die ebenfalls ein "None" haben.

Es wären auch weitergehende Auswertungen möglich, z. B: die Verteilung von "ParticipantCount" oder die Verteilung der Dauer von Gesprächen. Sogar Aussagen über die Leitungsbelegung (Siehe Erlang) wären möglich, indem man einfach je Minute die aktiven Calls aufsummiert.

Wer sich mit "Data Mining" beschäftigt, kann selbst mit anonymisierten UserIDs und CallIDs schon statistische Aussagen treffen. Allerdings sind die Datensätze auch dann noch z.B. über die IP-Adresse, die CPU, den Gerätetyp nicht als "anonym" anzusehen und die geltenden Vorschriften zu beachten.

Wie weiter?

Hier habe ich meine Versuche zur Erlangung von CallIDs erst einmal gestoppt. Der Weg über "api.interfaces.records.teams.microsoft.com" ist ja eh nicht offiziell beschrieben und vielleicht gibt es zukünftig auch einen Weg über den Graph CallReport Webhook komme ich mittlerweile auch an die CallIDs heran.

Sobald es neue Informationen gibt, werde ich die Seite weiter schreiben. Bis dahin habe ich aber einen Einblick in die Funktion der Webseite selbst, JavaScript und eine nicht öffentliche API erhalten. Wer weiß, wozu es nochmal gut ist

Weitere Links