Graph Get-Callrecord
Wenn Sie eine CallID zu einem Teams Call oder Konferenz haben, dann können Sie per Graph die weiteren Details dazu auslesen und auswerten.
Beachten Sie dazu auch die Seite Teams Verbindungsdaten, Teams Call Auswertung, Graph CallReport Webhook und Anrufliste mit Graph
Vorbedingungen
Diese Seite beschreibt die Abfrage von Details zu einem bekannten Call, der über eine CallID spezifiziert wird. Sie müssen dazu allerdings zuerst die CallID ermitteln. Das ist je nach Einsatzzweck nicht immer einfach. PSTN-Anrufe können Sie noch recht einfach per Graph abfragen (Siehe Anrufliste mit Graph) oder Teams Verbindungsdaten abfragen. Kniffliger wird es, wenn es um Federation-calls oder Meetings geht, die (Stand Jul 2023) noch nicht per Graph abgefragt werden können.
Um die auf dieser Seite beschriebenen Schnittstellen zu nutzen, sind natürlich wieder entsprechende Berechtigungen erforderlich. Sie müssen in ihrem Tenant eine eigene App oder von einem Dienstleister bereitgestellten App die erforderlichen Berechtigungen gewähren. Siehe auch Anrufliste mit Graph
Get-CallRecord
Mit der CallID und einer berechtigten App habe ich die Bausteine zusammen, damit ich die Details zu einem Call ermitteln kann.
PS C:\> $calldetails = Invoke-RestMethod ` -Uri "https://graph.microsoft.com/v1.0/communications/callRecords/1234568-1234-1234-1234-1234567890ab" ` -Method GET ` -Header @{ 'Authorization' = "Bearer $($accesstoken)"}
- Graph: Get callRecord
https://docs.microsoft.com/en-us/graph/api/callrecords-callrecord-get
Die geschweiften Klammern aus der API-Beschreibung werden nicht mit angegeben. Die Rückmeldungen liefern aktuell (Stand Nov 2021) nur grundlegende Verbindungsdaten aber keine Details zu den RTP-Streams.
CallTyp | Daten |
---|---|
1:1 Direct Routing |
PS C:\> $calldetails odata.context : https://graph.microsoft.com/v1.0/$metadata#communications/callRecords/$entity id : 1234568-1234-1234-1234-1234567890ab version : 2 type : peerToPeer modalities : {audio} lastModifiedDateTime : 13.11.2021 19:42:51 startDateTime : 13.11.2021 19:24:40 endDateTime : 13.11.2021 19:24:44 joinWebUrl : organizer : @{user=; acsUser=; spoolUser=; phone=; guest=; encrypted=; On-Premises=; acsApplicationInstance=; spoolApplicationInstance=; applicationInstance=; application=; device=} participants : {@{user=; acsUser=; spoolUser=; phone=; guest=; encrypted=; On-Premises=; acsApplicationInstance=; spoolApplicationInstance=; applicationInstance=; application=; device=}, @{acsUser=; spoolUser=; phone=; guest=; encrypted=; On-Premises=; acsApplicationInstance=; spoolApplicationInstance=; applicationInstance=; application=; device=; user=}} PS C:\> $calldetails.organizer user : acsUser : spoolUser : phone : @{id=+49160xxxxxxxx; displayName=; tenantId=} guest : encrypted : On-Premises : acsApplicationInstance : spoolApplicationInstance : applicationInstance : application : device : PS C:\> $calldetails.participants[0] user : acsUser : spoolUser : phone : @{id=+49160xxxxxxx; displayName=; tenantId=} guest : encrypted : On-Premises : acsApplicationInstance : spoolApplicationInstance : applicationInstance : application : device : PS C:\> $calldetails.participants[1] acsUser : spoolUser : phone : guest : encrypted : On-Premises : acsApplicationInstance : spoolApplicationInstance : applicationInstance : application : device : user : @{id=1234568-1234-1234-1234-1234567890ab; displayName=Carius, Frank (NAW); tenantId=1234568-1234-1234-1234-1234567890ab} |
MeetNow |
PS C:\> $calldetails.organizer @odata.context : https://graph.microsoft.com/v1.0/$metadata#communications/callRecords/$entity id : 1234568-1234-1234-1234-1234567890ab version : 2 type : groupCall modalities : {audio, video, screenSharing} lastModifiedDateTime : 13.11.2021 21:01:11 startDateTime : 13.11.2021 20:24:43 endDateTime : 13.11.2021 20:39:42 joinWebUrl : https://teams.microsoft.com/l/meetup-join/19%3ameeting_ZTg0ZDI0NTEtMWYyOS00xxxxxxxxxYzFhY 2JiM2I2%40thread.v2/0?context=%7b%22Tid%22%3a%22d<guid>%22%2c%22Oid %22%3a%22<guid>%22%7d organizer : @{acsUser=; spoolUser=; phone=; guest=; encrypted=; On-Premises=; acsApplicationInstance=; spoolApplicationInstance=; applicationInstance=; application=; device=; user=} participants : {@{acsUser=; spoolUser=; phone=; guest=; encrypted=; On-Premises=; acsApplicationInstance=; spoolApplicationInstance=; applicationInstance=; application=; device=; user=}} PS C:\> $calldetails.participants[0] acsUser : spoolUser : phone : guest : encrypted : On-Premises : acsApplicationInstance : spoolApplicationInstance : applicationInstance : application : device : user : @{id=1234568-1234-1234-1234-1234567890ab; displayName=Carius, Frank (NAW); tenantId=1234568-1234-1234-1234-1234567890ab} PS C:\> $calldetails.participants[1] user : acsUser : spoolUser : phone : guest : @{id=ec96fafc5e744cc0951fb5e5efcb3aa8; displayName=Guest user; tenantId=} encrypted : On-Premises : acsApplicationInstance : spoolApplicationInstance : applicationInstance : application : device : |
Federation call (Voicemail) |
PS C:\> $calldetails.participants[1] acsUser : spoolUser : phone : guest : encrypted : On-Premises : acsApplicationInstance : spoolApplicationInstance : applicationInstance : application : device : user : @{id=b13fed33-b3ab-42b5-83a9-feb2c7665f52; displayName=External user; tenantId=eef62a09-7718-4063-82db-d7582dc8916f} |
Sie sehen aber, dass da noch nicht so viel "Details" zu finden sind.
- Graph: Get callRecord
https://docs.microsoft.com/en-us/graph/api/callrecords-callrecord-get
Get-Callrecord Details
Die Anzeige, wann ein Call gestartet und beendet wurde, ist zwar nett, aber im Teams Admin Portal kann ich viel mehr Details zu einem Anruf sehen. Teams zeigt mir z.B. das Endgeräte, das Headset und viele andere Daten an, die ich auch gerne wüsste aber in dem einfachen CallReport nicht ersichtlich sind. Aber Microsoft beschreibt selbst, wie man die "Full Details" bekommt, indem ich ein "?$expand=sessions($expand=segments)" an die URL anhänge.
Hinweis:
Das "$"-Zeichen ist normal das Kennzeichen für eine
Variable. Hier muss aber ein $-Zeichen in der URL übergeben
werden, damit die Sessiondaten erhalten werden können. Es
macht einen Unterschied, ob sie die URL mit einfachen oder
doppelten Anführungsstrichen einfassen
- Graph: Get callRecord
https://docs.microsoft.com/en-us/graph/api/callrecords-callrecord-get?view=graph-rest-1.0&tabs=http#example-2-get-full-details
$calldetails = Invoke-RestMethod ` -Uri 'https://graph.microsoft.com/v1.0/communications/callRecords/xxxxxguidxxxxxx?$expand=sessions($expand=segments)' ` -Method GET ` -Header @{ 'Authorization' = "Bearer $($accesstoken)"}
Bei einen Meeting wird dann auch das Property "sessions" gefüllt.
PS C:\> $calldetails7 @odata.context : https://graph.microsoft.com/v1.0/$metadata#communications/callRecords(sessions(segments()))/$entity id : f32197cb-e848-45ab-9c26-54bbff5ed77a version : 1 type : groupCall modalities : {audio, video, videoBasedScreenSharing} lastModifiedDateTime : 12.11.2021 15:04:32 startDateTime : 12.11.2021 12:29:29 endDateTime : 12.11.2021 14:52:55 joinWebUrl : https://teams.microsoft.com/l/meetup-join/19%3ameeting_xxxxxxxxxczMDk 4YjZlOWFk%40thread.v2/0?context=%7b%22Tid%22%3a%22xxxxxxxxxx6%22%2c%2 2Oid%22%3a%22xxxxxxxxxxxf7%22%7d organizer : @{acsUser=; spoolUser=; phone=; guest=; encrypted=; On-Premises=; acsApplicationInstance=; spoolApplicationInstance=; applicationInstance=; application=; device=; user=} participants : {@{acsUser=; spoolUser=; phone=; guest=; encrypted=; On-Premises=; acsApplicationInstance=; spoolApplicationInstance=; applicationInstance=; application=; device=; user=}, @{acsUser=; spoolUser=; phone=; guest=; encrypted=; On-Premises=; acsApplicationInstance=; spoolApplicationInstance=; applicationInstance=; application=; device=; user=}…} sessions@odata.context : https://graph.microsoft.com/v1.0/$metadata#communications/callRecords('xxxxx -xxxxx')/sessions(segments()) sessions : {@{id=xxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxx; modalities=System.Object[]; startDateTime=12.11.2021 13:59:12; endDateTime=12.11.2021 14:47:39; failureInfo=; caller=; callee=; segments@odata.context=https://graph.microsoft.com/v1.0/$metadata#communications/call Records('xxxxxxxxxx')/sessions('xxxxxx')/segments; segments=System.Object[]}, @{id=xxxx; modalities=System.Object[]; startDateTime=12.11.2021 12:29:41; endDateTime=12.11.2021 13:33:17; failureInfo=; caller=; callee=; segments@odata.context=https://graph.microsoft.com/v 1.0/$metadata#communications/callRecords('xxxxxxx')/sessions('xxxxxxx')/segments; segments=System.Object[]}, @{id=xxxxx; modalities=System.Object[]; startDateTime=12.11.2021 12:29:34; endDateTime=12.11.2021 14:52:52; failureInfo=; caller=; callee=; segments@odata.context=https://graph.microsoft.com/v 1.0/$metadata#communications/callRecords('xxxxx')/sessions('xxxxxxxx')/segments; segments=System.Object[]}…}
Und jede Session enthält umfangreiche Daten über den Teilnehmer (Useragent, sein Feedback etc.
$calldetails.sessions.caller.useragent.headervalue $calldetails.sessions.caller.feedback $calldetails8.sessions.Segments.media.streams.maxjitter $calldetails8.sessions.Segments.media.streams.maxPacketLossRate $calldetails8.sessions.Segments.media.calleenetwork $calldetails8.sessions.Segments.media.callernetwork
Die JSON-Struktur ist sehr verschachtelt und Microsoft hat sie recht gut dokumentiert.
Die Blattknoten haben folgende Properties. Nicht immer sind alle Felder gefüllt:
Blatt | Properties |
---|---|
Verzeigungsknoten |
Unterschiedliche Zusatzproperties. Siehe Informationen im vorigen Bild. |
Organizer, Participants
Identity |
user :@{id=userGUID; displayName=Carius, Frank; tenantId=<tenantguid>} acsUser : spoolUser : phone : @{id=+49160xxxxxxxx; displayName=; tenantId=} guest : encrypted : On-Premises : acsApplicationInstance : spoolApplicationInstance : applicationInstance : application : device : |
userAgent |
@odata.type : #microsoft.graph.callRecords.clientUserAgent headerValue :CallSignalingAgent (27/1.4.00.30068//;release_petrusp/2558553_mac_nr_multi_compositor_ui_api.2021.34.01.22;releases/CL2021.R34) applicationVersion : platform : windows productFamily : teams |
callerNetwork,
calleeNetwork |
ipAddress : 192.168.178.91 subnet : 192.168.178.0 linkSpeed : 1000000000 connectionType : wired port : 50006 reflexiveIPAddress : 94.31.83.223 relayIPAddress : 52.112.172.67 relayPort : 50488 macAddress : f2:20:7a:12:34:45 wifiMicrosoftDriver : Virtueller Microsoft Wi-Fi Direct-Adapter wifiMicrosoftDriverVersion : Microsoft:10.0.19041.1 wifiVendorDriver : Intel(R) Dual Band Wireless-AC 8265 wifiVendorDriverVersion : Intel:20.70.24.1 wifiChannel : wifiBand : unknown basicServiceSetIdentifier : wifiRadioType : unknown wifiSignalStrength : wifiBatteryCharge : dnsSuffix : sentQualityEventRatio : 0 receivedQualityEventRatio : 0 delayEventRatio : 0 bandwidthLowEventRatio : |
callerDevice,
calleeDevice |
captureDeviceName : 2- Jabra Evolve 65 captureDeviceDriver : Microsoft: 10.0.19041.1202 renderDeviceName : 2- Jabra Evolve 65 renderDeviceDriver : Microsoft: 10.0.19041.1202 sentSignalLevel : receivedSignalLevel : -35 sentNoiseLevel : receivedNoiseLevel : -68 initialSignalLevelRootMeanSquare : cpuInsufficentEventRatio : 0 renderNotFunctioningEventRatio : 0 captureNotFunctioningEventRatio : 0 deviceGlitchEventRatio : 0 lowSpeechToNoiseEventRatio : 0 lowSpeechLevelEventRatio : 0 deviceClippingEventRatio : 0 howlingEventCount : 0 renderZeroVolumeEventRatio : 0 renderMuteEventRatio : 0 micGlitchRate : speakerGlitchRate : |
Streams |
streamId : 2874020951 startDateTime : endDateTime : streamDirection : callerToCallee averageAudioDegradation : averageJitter : PT0S maxJitter : PT0.001S averagePacketLossRate : 0 maxPacketLossRate : 0 averageRatioOfConcealedSamples : 0 maxRatioOfConcealedSamples : 0 averageRoundTripTime : maxRoundTripTime : packetUtilization : 3725 averageBandwidthEstimate : wasMediaBypassed : postForwardErrorCorrectionPacketLossRate : averageVideoFrameLossPercentage : averageReceivedFrameRate : lowFrameRateRatio : averageVideoPacketLossRate : averageVideoFrameRate : lowVideoProcessingCapabilityRatio : averageAudioNetworkJitter : PT0.002S maxAudioNetworkJitter : PT0.004S |
Die Daten sind schon sehr umfangreich, auch wenn es durchaus Lücken gibt. Insbesondere bei Gegenstellen, die z.B. ein Telefon sind oder per Federation in einem anderen Tenant unterwegs sind. Allerdings kann ich auch sehen, wann welche Sessions gestartet und geendet haben. Hier müssen Sie auch damit rechnen, dass ein Client das Meeting verlässt und wiederkehrt oder Video Ein/Abschaltet. All das sehen Sie z.B. auch im Teams Admin Center:
Entsprechend sind in der JSON-Antwort durchaus mehrere Streams pro Benutzer verzeichnet. Der "Attendance Report" könnte ich so auch generieren. Die CallID zu Meetings habe ich nun ja auch.
- Microsoft Teams – Anwesenheitsbericht zu
einer Besprechung
https://docs.microsoft.com/de-de/microsoftteams/teams-analytics-and-reports/meeting-attendance-report - Working with the call records API in
Microsoft Graph
https://docs.microsoft.com/en-us/graph/api/resources/callrecords-api-overview - Graph: Get callRecord - Full Details
https://docs.microsoft.com/en-us/graph/api/callrecords-callrecord-get?view=graph-rest-1.0&tabs=http#example-2-get-full-details
Weitere Links
- Teams Verbindungsdaten
- Anrufliste mit Graph
- Azure Functions
- Graph CallReport Webhook
- Teams Call Auswertung
- Teams Graph und CallIDs
-
Teams Call Manager
https://www.cu-solutions.de/teams-call-manager/
Kenne die Funktionsweise nicht genau aber dürfte alle 10 Minuten die PSTN Call Records abfragen und in einen Kanal publishen.