Automatischer Versand von Webinaraufzeichnungen

Eine vollautomatisierte Journey für die begleitende Kommunikation zu allen Events ist inzwischen Standard und viel besprochen. Aber wie sieht es aus, wenn die Webinaraufzeichnung eines Teams Webinars (oder Meeting) automatisch an die Teilnehmenden versandt werden soll? In diesem Blogartikel zeige ich, wie Sie mit Dynamics Customer Insights Schritt für Schritt die Webinaraufzeichnungen effizient und automatisch an die Teilnehmenden senden.“

Die Idee und wo es mit den Standardtools hapert

Eine Webinar- oder Meetingaufzeichnung in Microsoft Teams wird automatisch im OneDrive Business Account des Eventinhabers gespeichert. Auf diesen Vorgang kann man mit Power Automate reagieren, dort einen Sharing-Link erstellen und diesen dann im Event hinterlegen. Anschließend können alle beim Event registrierten Kontakte via Journey den Link zugesandt bekommen. Das ist grundsätzlich vollautomatisch möglich.

Klingt eigentlich ziemlich straight forward, doch der Teufel liegt im Detail. Zunächst mal: Der OneDrive Connector in Power Automate hat immer nur Zugriff auf einen persönlichen Bereich, nämlich den, über dessen Connection er läuft. Dann muss die Aufzeichnung anhand des Namens mit dem Event aus Customer Insights gemapped werden. Das funktioniert auch grundsätzlich, da Teams das Meeting immer mit dem öffentlichen Namen des Events im Dateinamen abspeichert. Allerdings hat OneDrive (wie auch Sharepoint) einige Restriktionen bei der Verwendung von Sonderzeichen. Ein „?“ würde z.B. durch einen „_“ ersetzt. Das heißt, ein einfacher Stringvergleich funktioniert nicht. Und zu guter Letzt muss ein Auslöser her, der Anstoßpunkt für den Versand des Links ist. Ein Dataverse Auslöser ist ungeeignet, da es keine direkte Beziehung zu Kontakten gibt. Das heißt, es muss mit einem Custom Trigger gearbeitet werden und dieser muss eben irgendwie ausgelöst werden, am besten durch einen weiteren Power Automate Flow.

Low Code vs. Pro Code Ansatz

In diesem Blog zeige ich einen Ansatz, der vollständig mit Low Code Tools funktioniert. Er wird für kleine- und mittlere Umgebungen super funktionieren. Für einen größeren Scope sollte man eventuell überlegen, einen Pro-Code Ansatz zu wählen. Ein möglicher Weg, auf neue Recordings zu reagieren – und zwar tenantweit – funktioniert mit sogenannten MS Graph Subscriptions. Einmal eingerichtet, sendet eine solche Subscription bei jedem neuen Recording im gesamten Tenant eine Notification an einen zuvor definierten Webhook. Hier findest Du mehr Informationen zu diesem Ansatz. Hier gibt es jedoch sehr vieles zu beachten und zu maintainen, die Daten sind verschlüsselt, die Subscription läuft immer nur wenige Tage, eine Verlängerung muss dann ebenfalls im Code umgesetzt werden, für die Verschlüsselung und Entschlüsselung wird ein SSL Zertifikat benötigt, es muss eine App registration erstellt und mit den richtigen Berechtigungen ausgestattet werden und und und. Außerdem kann Power Automate keine Decryption von Daten vornehmen, weshalb dann Azure Functions oder lokale Umgebungen benötigt werden.

Aus all diesen Gründen konzentriere ich mich hier auf den Low-Code Ansatz 🙂

Teil eins: Bekomme den Link in das Event

Das ist der eigentlich komplexe Teil des Prozesses. Zunächst müssen wir ein Feld anlegen, in dem der Recording Link gespeichert wird. Erstelle in Deiner Solution ein neues URL-Feld (String) mit möglichst etwas mehr Zeichen, ich habe bei mir 300 Zeichen als Obergrenze gewählt, damit sollte man denke ich immer auskommen, auch bei sehr langen Links. Lege das Feld auf das Ereignisformular und überlege, es read-only zu konfigurieren, um den gesamten Prozess in die Hände der Automatisierung zu legen.

Redundante Flows notwendig – arbeiten mit Childflow

Da der OneDrive Connector immer nur auf den persönlichen Bereich des in der Connection hinterlegten Accounts zugreifen kann, muss zunächst identifiziert werden, wer im Unternehmen alles Customer Insights Events anlegt. Denn der User, der die Events in Dynamics 365 Customer Insights – Journeys anlegt, wird gleichzeitig auch Owner des Teams Webinars bzw. Teams Meetings. Daraus folgt, dass im zugehörigen OneDrive die Aufzeichnung abgelegt wird und zwar im Ordner „Recordings“, der im Rootverzeichnis des persönlichen OneDrive for Business Accounts liegt.

Damit wir nicht die gesamte Logik des Flows für jeden Benutzer duplizieren müssen, werden wir mit einem Child-Flow arbeiten und nur den wirklich notwendigen Teil mit den OneDrive Actions redundant für jeden Benutzer erstellen. Das heißt, die unten sichtbare Flowlogik muss für jeden User erstellt werden, der Webinare über Customer Insights anlegt.

Connection references

Zunächst sollten wir alle benötigten Connections für alle relevanten Benutzer erstellen. Dazu erstelle innerhalb Ihrer Lösung eine neue „Connection reference“. Nenne sie z.B. „OneDrive Johannes“ und wähle den „OneDrive for Business“ Connector. Unter „Verbindung“ muss nun eine neue Connection hergestellt werden. Es öffnet sich ein Loginfeld und eine Anmeldung muss über den Account des relevanten Users erfolgen.

Wichtiger Hinweis: Durch das Hinzufügen dieser Connection ist es möglich, auf den Inhalt des entsprechenden OneDrive des Users über Power Automate Flows zuzugreifen. Das sollte sorgfältig überlegt sein und in Abstimmung mit Verantwortlichen und dem User erfolgen.

Nachdem die Connection erstellt wurde, kehre zurück zur Erstellung der Connection Reference, klicke auf „Refresh“ und wähle nun die neue Connection aus.

Erstelle den Parent Flow pro User

Nun können wir den ersten Flow erstellen und wählen als Einstiegspunkt den OneDrive for Business Trigger „When a file is created“ aus. Im Trigger und allen folgenden OneDrive for Business Actions muss die Connection Reference des Users hinterlegt sein, für den dieser Flow angelegt wird. Wähle im Trigger den Ordner „Recordings“ aus.

Beachte dabei, dass später, wenn der Flow für weitere User kopiert wird, der Recording Ordner neu ausgewählt wird! Dahinter verbirgt sich die Path ID und die ist natürlich jedes Mal anders, auch wenn der Ordner immer „Recordings“ heißt.

Anschließend benötigen wir die Metadaten der Datei, um den Mediatyp und den Dateinamen verarbeiten zu können. Dafür wähle die Action „Get file metadata“ ein und wähle als Parameter den „File identifier“ aus dem Trigger.

Die ganze Logik für das Mapping zum Event geben wir nun an einen Child Flow ab. Dadurch bleibt diese Logik zentral verwaltet. Bevor wir „Run a Child Flow“ auswählen können, müssen wir den zunächst anlegen. Später können wir hierher zurückkehren. Den Child Flow definieren wir so, dass er entweder eine Event GUID mitsamt dem Status 200 zurückgibt oder einen 404 Status.

Reagiere auf das Ergebnis aus dem Childflow

Wird eine Event GUID identifiziert, können wir anschließend einen Sharing Link erstellen und diesen dann im Event speichern. Wir überprüfen also den Response vom Child Flow in einer Condition

body('Run_a_Child_Flow')?['status'] eq string(200)

Ergibt die Prüfung „true“, können wir die Action „Create share link“ verwenden, um einen öffentlichen View-Link zu erstellen. Diesen Link speichern wir abschließend im Ereignis in dem Feld, welches wir zu Beginn erstellt haben.

Dieser Parent Flow muss nun für jeden relevanten User (also jeden User, der im Unternehmen Dynamics Customer Insights Webevents erstellt und somit Owner der entsprechenden Teams Events ist) mithilfe der „Save as“ Funktion in Flows kopiert werden. Nach dem Kopieren müssen in allen OneDrive Actions die Connection references ausgetauscht werden. Zusätzlich muss im Trigger der Recording Pfad neu ausgewählt werden, damit er auf die richtige path id zeigt.

Child Flow zur Identifikation eines Events

Für den Child Flow wählen wir den Trigger „Manually trigger a flow“ und erstellen zwei Input Parameter des Typs Text: „mediaType“ und „fileName“. Diese Inputparameter übergeben wir dann aus dem Parent Flow, wenn wir den Child Flow aufrufen.

Sollte der mediaTyp nicht „video/mp4“ entsprechen, wird der Flow abgebrochen, um so unvorhergesehenem Verhalten vorzubeugen.

Überprüfen der Struktur des Dateinamen

Ereignisaufzeichnungen werden im Format [Name des Ereignis]-[Datum]_[Uhrzeit]-Meeting Recording.mp4 (Beispiel: „An event of another user-20250526_223801-Meeting Recording.mp4“) gespeichert. Eine Besonderheit dabei gibt es, denn einige Sonderzeichen sind nicht erlaubt in Dateinamen innerhalb von OneDrive oder auch Sharepoint. Diese Zeichen werden automatisch durch „_“ ersetzt. Das wird wichtig, wenn wir später den Ereignisnamen im CRM finden wollen.

Um den Ereignisnamen extrahieren zu können, nutzen wir die „Find text position“ Action und suchen im fileName Parameter nach dem Substring mit dem heutigen Datum, also z.B. „-20250527“. Alles, was sich davor befindet, ist der Name des Ereignisses.

Format für den zu suchenden Substring

-@{formatDateTime(utcNow(), 'yyyyMMdd')}

Wird ein solcher Substring nicht gefunden, sollten wir den Flow auch an dieser Stelle abbrechen, denn dann können wir das Format nicht korrekt auswerten.

Extrahieren des Ereignisname und auflisten aller Ereignisse im CRM

Wurde ein entsprechender Substring gefunden, können wir alles davor nehmen und als unseren Ereignisnamen verwenden. Dafür verwende die Action „Substring“, wähle den fileName Parameter als Textinput, beginne bei Position 0 und gehe bis zur vorher herausgefunden Position aus der vorigen Action @{body('Find_current_day_in_filename')}.

Wir benötigen eine Variable für die Event ID, da wir nun im nächsten Schritt alle relevanten Ereignisse im CRM auflisten und einzeln überprüfen müssen. Aufgrund der verbotenen Zeichen kann es sehr gut sein, dass der Ereignisname im CRM nicht eindeutig mit unserem extrahierten Wert aus der Aufzeichnung übereinstimmt. Deshalb listen wir alle Ereignisse auf, die derzeit aktiv im CRM sind und die nicht in der Zukunft liegen und werden uns jeden einzelnen Ereignisnamen vornehmen, anpassen und den angepassten Namen dann mit unserem extrahierten Wert vergleichen.

Werte für „List all active events in the past

Select columnsmsevtmgt_name,msevtmgt_eventid
Filter rowsstatecode eq 0 and msevtmgt_eventenddate le @{utcNow()}

Überprüfe und vergleiche die Ereignisnamen

Jetzt wo wir alle Ereignisnamen geholt haben, müssen wir jeden einzelnen überprüfen. Damit wir den Ereignisnamen vergleichen können, müssen wir verbotenen Zeichen im Ereignisnamen durch „_“ ersetzen. Dafür verwenden wir die replace() in einer Compose Action.

Verbotene Zeichen im Dateinamen

ZeichenBeschreibung
/Slash
\Backslash
:Doppelpunkt
*Stern
?Fragezeichen
Anführungszeichen
<Kleiner als
>Größer al
|Senkrechter Strich

Die einfachste Möglichkeit für die Ersetzung ist eine Verschachtelung von replace() ineinander, die nacheinander alle verbotenen Zeichen durch „_“ ersetzt. Die sieht furchtbar aus, kann aber einfach von hier kopiert werden.

replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(
items('Apply_to_each_active_event')?['msevtmgt_name'], '"', '_'), '*', '_'), ':', '_'), '<', '_'), '>', '_'), '?', '_'), '/', '_'), '\', '_'), '|', '_'), '#', '_')

Nun können wir den angepassten Namen mit unserem extrahierten Namen in einer Condition vergleichen:

outputs('Replace_all_forbidden_characters_from_eventname') eq body('Extract_event_name')

Ergibt die Prüfung „true“, setzen wir die guid des aktuellen Ereignisses in unsere Variable. Da wir nur ein Ereignis erwarten, können wir trotz Verwendung der Variable die Concurrency der Apply to each Schleife ruhig hochschrauben, damit sie nicht ewig läuft.

Auf diese Weise werden wir immer genau ein Event identifizieren. Sollte es (aus welchen Gründen auch immer) mehrere Ereignisse mit demselben Namen geben, wäre das Ergebnis nicht eindeutig.

Antworten an den Parent Flow

Hat unser Vergleich ein Ereignis identifiziert, übergeben wir die GUID an den Parent Flow, ansonsten übergeben wir einen 404.

length(variables('Event ID')) gt 0

Wenn das Ergebnis dieses Vergleichs „true“ ist, übergeben wir mit der „Respond to a Power App or flow“ Action die GUID aus der Variable an den Parent Flow.

Teil zwei: Verschicke den Link an die Teilnehmenden

Ab hier wird es wieder ziemlich straight forward. Wir müssen einen Custom Trigger erstellen, auf den wir später in einer Journey reagieren können. Das kann entweder eine bereits bestehende Event-Journey (z.B. mit dem Startpunkt „When a event registration gets created“) eingebaut werden, indem wir etwa nach Ablauf des Events für einen gewissen Zeitraum auf unseren Custom Trigger für den Versand des Recording Links warten oder aber auch eine völlig eigenständige Journey sein, die als Startpunkt unseren neuen Trigger enthält und dann einfach nur die eine E-Mail aussendet.

Custom Trigger erstellen

Es wäre so schön einfach, wenn wir einen Dataverse Trigger verwenden könnten, denn der würde einfach auf Veränderung im Feld „Recording Link“ reagieren. Allerdings besteht keine direkte Beziehung zwischen Ereignissen und Kontakten, eine solche ist aber zwingende Voraussetzung für einen Dataverse Trigger. Daher bedienen wir uns eines Custom Triggers, den wir per Power Automate Flow aktivieren werden.

Erstelle einen neuen Trigger, wähle „Custom Trigger“ und gib dem Trigger neben der Zielgruppe „Kontakt“ einen weiteren Parameter „Event“ und verwende dafür den Typ „Entity reference“ auf die Tabelle „Event“.

Ich habe den Trigger „New recording link for event“ genannt und in der Beschreibung „This trigger will be triggered for all registered contacts of an event, if a new recording link has been pasted into the event.“ hinterlegt, sodass er auch für andere nachvollziehbar wird.

Journey für den Versand des Links

Ich habe mich aus Demonstrationszwecken dafür entschieden, eine eigene Journey nur für den Versand eines Recording Links aufzusetzen. Bei Erstellung der Journey sollte festgelegt werden, in welchen Fällen ein solcher Link verschickt werden soll (z.B. nur für bestimmte Ereignistypen. Ich habe die Journey hier sehr simpel gehalten und verschicke einfach immer eine entsprechende E-Mail, wenn der Trigger für einen Kontakt ausgelöst wird.

Die E-Mail muss dann den Link aus dem „Recording Link“ Feld auf dem Ereignis enthalten, z.B. in einem Button. Wichtig: Je nachdem, in welchem Kontext die E-Mail verschickt werden soll, muss der richtige Trigger ausgewählt sein. Eine E-Mail kann immer nur den Trigger zur Personalisierung verwenden, der als Startpunkt der Journey gesetzt ist, in der die E-Mail verwendet wird. Setzen wir also unsere eigene Journey auf, die auf unseren Custom Trigger als Startpunkt reagiert, sollte der Ereignislink über den Trigger „New recording link is created“ ausgewählt werden. Soll dies aber als Teil einer größeren Event-Kommunikations-Journey sein, die schon seit der Registrierung läuft, müsste der Recording Link dann aus dem Trigger „When a event registration is created“ gezogen werden.

Custom Trigger via Power Automate auslösen

In Power Automate erstellen wir einen neuen Flow mit dem Trigger „When a row is added, modified, or deleted“ und konfigurieren diesen so, dass er auf Änderungen im Feld „Recording link“ auf Ereignisse reagiert. Anschließend holen wir uns einmal das Ereignis in einer eigenen Action, eigentlich nur, um bei späteren möglichen Veränderungen am Trigger nicht versehentlich alle Referenzen auf das Ereignis in den noch folgenden Actions zu löschen. So können wir den Trigger löschen, neu erstellen und die Flowlogik bleibt dennoch vollständig erhalten.

Durch die „Filter rows“ Zeile legen wir fest, dass der Flow nur startet, wenn auch tatsächlich ein Wert im Feld vorhanden ist (und die Änderung nicht etwa durch Löschen des Wertes zustande kam).

Select columnsnew_recording_link
Filter rowsnew_recording_link ne null

Auflisten der Kontakte

Nun suchen wir uns alle Kontakte, die für das Ereignis registriert sind. Alternativ könnte man auch nur die suchen, für die es einen Check-In gibt. Dafür nutzen wir einen FetchXML Filter, den wir z.B. vorher auf einer Kontaktview im CRM erstellt und dann exportiert haben. Wichtig ist, dass wir die Event ID dynamisch einbeziehen (fett markiert). Hier mein FetchXML:

<fetch version="1.0" output-format="xml-platform" mapping="logical" no-lock="false" distinct="true">
  <entity name="contact">
    <filter type="and">
      <condition attribute="statecode" operator="eq" value="0"/>
    </filter>
    <link-entity name="msevtmgt_eventregistration" alias="aa" link-type="inner" from="msevtmgt_contactid" to="contactid">
      <link-entity name="msevtmgt_event" alias="ab" link-type="inner" from="msevtmgt_eventid" to="msevtmgt_eventid">
        <filter type="and">
          <condition attribute="msevtmgt_eventid" operator="eq" value="@{outputs('Get_event')?['body/msevtmgt_eventid']}"/>
        </filter>
      </link-entity>
    </link-entity>
  </entity>
</fetch>

Auslösen des Triggers

Anschließend müssen wir nur noch den Trigger für jeden gefundenen Kontakt auslösen. Das erreichen wir mit der „Perform an unbound action“ Action. Unseren Custom Trigger finden wir hier unter dem logical name, der im Hintergrund von Dynamics 365 erstellt wurde. Das Schema dafür ist „msdynmkt_[Name des Triggers ohne Leerzeichen und Lowercase]_id„. Suchen wir also z.B. nach „recording“, werden wir den Trigger finden können.

Aufgrund der Entitätsreferenz hat diese Action jede Menge optionale Parameter. Wichtig sind eigentlich nur die folgenden:

msevtmgt_event msevmgt_eventid@{outputs(‚Get_event‘)?[‚body/msevtmgt_eventid‘]}
msdynmkt_signalingestiontimestamp@{utcNow()}
msdynmkt_signaltimestamp@{utcNow()}
msdynmkt_signaluserauthid@{items(‚Apply_to_each_contact‘)?[‚contactid‘]}
msdynmkt_profileid@{items(‚Apply_to_each_contact‘)?[‚contactid‘]}

Geschafft! Mit diesem Flow haben wir den letzten Schritt in der Automatisierung ausgeführt. Sobald ein Link im Ereignis hinterlegt wird, sucht der Flow alle relevanten Kontakte und triggert „New recording link created“ und die Journey schickt den Recording Link an die Teilnehmenden!


Bleibe informiert

Verpasse keinen neuen Beitrag und abonniere einfach den Newsletter.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert