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.

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)"}

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.

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

$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.

Weitere Links