Workflow: __SharePoint 2010 Output Variablen löschen

Wenn man in einem SharePoint Designer 2013 Workflow die Aktion zum Starten eines (2010) Workflows verwendet, wird im Hintergrund eine völlig überflüssige Variable angelegt. Ebenso jedes Mal wenn man an der Aktion etwas ändert. Diese Variablen sind über die normale SharePoint Designer Oberfläche nicht zugänglich, außer in den Nachschlagedialogen. Dort findet man sie mit solchen Namen:
__SharePoint 2010 Output: <Name des zu startenden Workflows>.

Die Variablen an sich sind ja kein Problem, aber ein Workflow darf nur maximal 50 Variablen haben und selbstverständlich zählen diese „unsichtbaren“ Variablen da mit. Auch wenn man sie auf normalem Weg nicht wieder entfernen kann. Danke Microsoft für diesen Blödsinn!

Man muß also schon etwas tiefer in die Trickkiste greifen, wenn man nicht den ganzen Workflow neu machen möchte:

  • Man öffnet die Website mit dem Windows Explorer. Am einfachsten geht das, wenn man in eine Dokumentbibliothek navigiert und dort den Button „Mit Explorer öffnen“ klickt.
  • Im Root der Website findet man einen Ordner wfsvc, den man öffnet.
  • Darin findet man für jeden 2013 Workflow der Website einen Ordner mit einer GUID als Namen. Zum Identifizieren des richtigen Ordners bleibt also nur das Änderungsdatum. Im Zweifel macht man in SharePoint Designer eine Änderung am Workflow und schaut dann nach dem Ordner mit aktuellem Datum.
  • Im Ordner findet man eine Datei workflow.xaml, die man mit einem geeigneten Editor öffnet. Es ist eine XML-Datei, aber Vorsicht: der Editor darf keinerlei Formatierungen, Whitespace, Zeilenumbrüche oder ähnliches einfügen. Microsoft nimmt einem das übel.
  • In der Datei sucht man jetzt nach „__SharePoint 2010 Output“ und löscht alle
    <x:Property Name=“__SharePoint 2010 Output: …“. Dabei darauf achten, daß immer das ganze XML-Element gelöscht wird. Und natürlich nur das und nicht mehr.
  • Der Workflow muß danach noch in SharePoint Designer geöffnet werden und alle Aktionen zum Starten eines Workflows müssen neu konfiguriert werden.

Für den Fall des Falles macht man sich natürlich vorher ein Backup dieser Datei…

SharePoint 2013 Workflows Teil 2: Listenelemente iterieren

Im ersten Teil habe ich gezeigt, wie man in einem SharePoint 2013 Workflow mit Hilfe der Aktion Call HTTP Web Service auf Listenelemente zugreifen kann. Dort ging es erstmal nur darum, die Anzahl verbundener Elemente zu ermitteln. In diesem Teil soll jetzt gezeigt werden, wie man mit den in 2013 neuen Mitteln eine Schleife zum Durchlaufen der gefundenen Elemente realisieren kann.

Testumgebung

Als Testumgebung werden die beiden Listen aus dem ersten Teil verwendet. Die Child List wurde um ein Zahlenfeld Value erweitert. Die Parent List bekam ein zusätzliches Feld Item Sum. Per Workflow sollen jetzt die Werte aus Value aller verbundenen Child-Elemente aufsummiert und in Item Sum des Parent-Elementes geschrieben werden.

Es geht hier nur um das Prinzip und natürlich sind auch viele andere Aktionen denkbar. Man könnte innerhalb der Schleife z.B. auch alle verbundenen Child-Elemente aktualisieren.

Schleifenaufbau

Wir erweitern also unseren bestehenden Workflow um eine neue Stufe. Damit wir die Schleife realisieren können, benötigen wir eine Schleifenvariable. Wir legen also eine Integer-Variable LoopCounter an und initialisieren sie mit 0 (Null). Außerdem benötigen wir eine Variable, in der wir die Werte der einzelnen Child-Elemente aufsummieren können. Wir legen also noch eine Number-Variable ItemSum an und initialisieren sie ebenfalls mit 0. Jetzt fügen wir eine Schleife vom Typ Schleife n-mal ein und konfigurieren sie so, daß sie so oft ausgeführt wird, wie unsere Variable ItemCount aus dem ersten Teil vorgibt. Die Variable enthält die Anzahl der gefundenen Elemente. Bis jetzt siehst es also so aus:

Zugriff auf ein Element

Als nächstes holen wir uns die ID eines Listenelementes aus den Ergebnissen der REST-Abfrage. Dazu verwenden wir wieder die Aktion Get an Item from a Dictionary. Für den Parameter item by name or path verwenden wir d/results(<Counter>)/ID, wobei der Platzhalter <Counter> für die nullbasierte Laufvariable steht, in unserem Fall also die Variable LoopCounter. Im Ganzen sieht das so aus:

Wie haben jetzt eine Variable ChildItemID, die pro Schleifendurchlauf immer die Element-ID eines Child-Elementes enthält und wir können damit auf das jeweilige Element zugreifen. Wir verwenden im Beispiel die Aktion Do Calculation, um den Wert des Feldes Value zu unserer Variablen ItemSum zu addieren. Der Zugriff auf das das Feld des Child-Elementes erfolgt dabei so:

Schleifenabschluß

Damit die Schleife auch korrekt funktioniert, müssen wir jetzt noch unsere Laufvariable LoopCounter hochzählen. Dazu verwenden wir wieder die Aktion Do Calculation und addieren einfach 1 (eins). Dabei ist zu beachten, daß Do Calculation immer einen Gleitkommawert zurückliefert, den wir nicht wieder direkt unserer Integer-Variablen zuweisen können. Wir verwenden hier also eine Zwischenvariable vom Typ Number und eine weitere Aktion Set Workflow Variable um diese unserer Laufvariablen zuzuweisen. Hier funktioniert die Konvertierung dann.

Ich habe versucht, als Laufvariable selbst eine Number-Variable zu verwenden, aber das hat bei meinen Versuchen nicht zuverlässig funktioniert. Deshalb der kleine Workaround.

Zum Abschluß benutzen wir noch eine Aktion Set Field in Current Item (nach der Schleife!) und schreiben damit die ermittelte Summe in das Feld Item Sum des aktuellen Elements. Zur Erinnerung: der Workflow läuft auf der Parent List und das aktuelle Element im Workflow ist daher ein Element dieser Liste.

Hier nochmal die gesamte Schleife in der Übersicht:

SharePoint 2013 Workflows Teil 1: Call HTTP Web Service

Nachdem ich mich jetzt endlich etwas mit den neuen SharePoint 2013 Workflows beschäftigen konnte, möchte ich hier meine Erfahrungen festhalten. Auch damit ich selbst etwas zum Nachschlagen habe 😉

Im ersten Teil soll es darum gehen, wie man mit der neuen Aktion Call HTTP Web Service Daten aus SharePoint abfragen kann. Selbstverständlich kann man damit jeden beliebigen Webservice abfragen, aber mir ging es ganz speziell um Daten aus SharePoint. Prinzipiell kommt man damit an die Daten aus jeder beliebigen SharePoint-Website, auch wenn sie sich in einer anderen Farm befinden. Voraussetzung dafür ist, daß der Benutzer dazu berechtigt ist (und natürlich daß der Webservice erreichbar ist).

Testumgebung

Für den Test habe ich ein einfaches Szenario aufgebaut: innerhalb einer Website gibt es eine Parent List und eine Child List, die über ein Nachschlagefeld mit der Parent List verbunden ist. Die Parent List hat ein zusätzliches Zahlenfeld Child Count. Ein Workflow auf der Parent List soll jetzt alle zugehörigen Elemente der Child List finden und deren Anzahl beim Parent in Child Count eintragen. Zur Verdeutlichung hier die Parent List:

Und die Child List:

Workflow erstellen

Wir öffnen jetzt die Website in SharePoint Designer 2013 und erstellen einen neuen Listenworkflow für die Parent List. Die Vorgehensweise dazu ist dieselbe wie in 2010, aber beim Anlegen muß darauf geachtet werden, daß SharePoint 2013 Workflow ausgewählt ist. Nur dann stehen die neuen Funktionen auch zur Verfügung:

Service-URL

Im Workflow brauchen wir zunächst die Adresse des Services, den wir für die Abfrage der verknüpften Elemente verwenden wollen. Wir nehmen dafür den in SharePoint 2013 neuen REST-Service, der unter der Adresse <Website>/_api zur Verfügung steht. Der Platzhalter <Website> steht hierbei für die absolute URL der Website, also ungefähr http://sharepoint.firma.tld/Sites/Site1. Wenn es sich wie in unserem Beispiel um die aktuelle Website handelt, kann man dafür die vordefinierte Variable Workflowkontext: Aktuelle Website-URL verwenden.

Da wir hier die Daten einer ganz bestimmten Liste möchten, hängen wir an die URL noch folgendes an: /_api/lists/GetByTitle(‚Child List‘)/Items. Da wir uns im ersten Schritt nur für die ID und den Titel der verknüpften Elemente interessieren, teilen wir der API das mit: ?$select=Id,Title.

Das wichtigste ist aber der Filter, weil wir nur die Elemente haben möchten, die zu unserem aktuellen Element der Parent List gehören. Prinzipiell geht das so: $filter=<LookupField> eq <ID>. <LookupField> ist der interne Name des Nachschlagefelds, <ID> ist die ID des aktuellen Parent-Elements und eq steht für equals also zu deutsch ist gleich. Achtung: damit der Filter funktioniert, muß man das Feld auch beim $select-Parameter angeben und dort mit dem Suffix Id (also ParentId). Zum Ausprobieren wie die Abfrage genau aussehen muß und wie die Felder heißen, empfehle ich einen Test im Browser. Wie das geht, wird weiter unten unter Exkurs erklärt.

Hier nochmal zusammengefaßt die gesamte aufzurufende URL, die wir einer Variablen REST-URL zuweisen. Da man den Text wegen der Nachschlageinformationen ohnehin nicht kopieren kann hier nur als Screenshot:

Request Header

Damit der REST-Service die Daten in einem Format liefert, das vom Workflow verarbeitet werden kann, müssen für die Abfrage sogenannte Request Header definiert werden. Die Header Accept und Content-Type müssen beide auf den Wert application/json;odata=verbose gesetzt werden. Im Workflow geschieht das durch den ebenfalls neuen Variablentyp Dictionary.

Wir legen also eine neue Dictionary-Variable RequestHeaders an und verwenden die Aktion Build Dictionary um die beiden Header einzutragen:

Exkurs: SharePoint 2013 REST API

Man kann den REST-Service zum Ausprobieren auch jederzeit einfach im Browser aufrufen. Der Service liefert XML zurück, das der Internet Explorer als Feed interpretiert. Wie man ihn dazu bringen kann die rohen XML-Daten anzuzeigen, habe ich hier beschrieben.

Wenn man sich die vom Service gelieferten Json-Daten im Browser anschauen möchte, wird es schon schwieriger, weil man wie oben für den Workflow beschrieben, die an den Server gesendeten Header modifizieren muß. Beim Internet Explorer habe ich keinen Weg gefunden das zu erreichen. Ich habe dann einige Plugins für Firefox getestet, aber leider auch ohne Erfolg. Fündig geworden bin ich dann mit Google Chrome und dem Plugin Change HTTP Request Header. Wenn man damit die Header wie oben gezeigt modifiziert, kann man sich das Ergebnis einer REST-Abfrage auch im Json-Format anzeigen lassen. Wer es etwas schöner formatiert haben möchte, kopiert es in den Json Parser Online.

Ein guter Einstiegspunkt in die offizielle Dokumentation von Microsoft findet sich hier: Get startet with the SharePoint 2013 REST service.

Call http Web Service

Jetzt aber weiter im Workflow. Nachdem wir jetzt die URL des Services und die notwendigen Header haben, fügen wir eine Call HTTP Web Service Aktion ein. Für den ersten Parameter verwenden wir einfach unsere vorher festgelegte Variable und belassen die HTTP-Methode bei GET:

Von den restlichen Parametern der Aktion setzen wir nur den ResponseContent (das Ergebnis der Abfrage) auf eine gleichnamige Dictionary-Variable und den ResponseCode auf eine gleichnamige String-Variable. Der Response Code gibt Auskunft über den Erfolg der Abfrage und kann zur weiteren Steuerung des Workflows verwendet werden. Er lautet OK, wenn die Abfrage syntaktisch in Ordnung war. Natürlich kann die Abfrage trotzdem logische Fehler enthalten, so daß z.B. der Filter nicht wie erwartet funktioniert, aber das muß separat festgestellt werden. Alle anderen Parameter interessieren uns hier nicht.

Achtung: der aufmerksame Leser fragt sich jetzt wahrscheinlich was mit unserer Variablen für die Request Header passiert. Diese muß der Workflowaktion auf anderem Wege zugewiesen werden und zwar über das Eigenschaften-Fenster, das man bei jeder Workflowaktion über ihr Menü erreichen kann. Das Menü findet man, wenn man mit der Maus über die Aktion fährt und dann auf den DropDown-Pfeil ganz rechts klickt. Innerhalb des Fensters kann man jetzt auch die Variable für die Request Header setzen (3. Punkt von oben):

Warum das an dieser Stelle so umständlich zu handhaben ist, kann ich nicht sagen, halte es aber für einen Bug. Oder zumindest für ein Versäumnis.

Ergebnisverarbeitung

Das Ergebnis der Abfrage haben wir jetzt in einer Dictionary-Variablen ResponseContent. Hier soll nur gezeigt werden, wie man die Anzahl der gefundenen Elemente ermitteln und in ein Feld des Parentelements schreiben kann. Zunächst holen wir uns ein Subset des Ergebnisses in eine weitere Dictionary-Variable ResultItems. Dazu verwenden wir die Aktion Get an Item from a Dictionary und geben für den Parameter item by name or path den Wert d/results an. Mit der Aktion Count Items in a Dictionary bekommen wir jetzt die Anzahl der gefundenen Elemente in eine Integer-Variable ItemCount. Mit der Aktion Set Field in Current Item lassen wir uns diesen Wert jetzt noch ins aktuelle Parentelement ausgeben.

Hier nochmal der gesamte Workflow zusammengefaßt:

Im zweiten Teil wird gezeigt, was man mit dem Ergebnis der Abfrage noch anstellen kann. Dort geht es dann um eine Schleife, die alle gefundenen Elemente durchläuft.

Per Workflow feststellen, ob ein Nachschlagefeld leer ist

Man sollte meinen, daß man in einem SharePoint Designer 2010 Workflow mit Hilfe der Bedingung Feld ist leer sehr einfach feststellen kann, ob ein bestimmtes Feld leer ist oder nicht. Bei Nachschlagefeldern geht das aber leider nicht ganz so einfach.

Die Bedingung Feld ist leer ist nur dann wahr, wenn das Nachschlagefeld noch nie Daten enthalten hatte. Wenn ein Nachschlagefeld zu einem früheren Zeitpunkt Daten enthielt und dann wieder geleert wurde, ist es nicht leer sondern enthält eine Null. Um das zu prüfen, muß man sich den Wert in eine Variable holen und diese dann auf Null prüfen. Das wiederum funktioniert aber nur, wenn das Nachschlagefeld auch eine Null (oder einen echten Wert) enthält. Wenn das Nachschlagefeld leer ist, bekommt man dabei einen Fehler. Für eine vollständige Prüfung müssen also beide Bedingungen kombiniert geprüft werden.

Der Ablauf ist also folgender:

Man legt eine neue Variable an, indem man im Workflowdesigner auf Lokale Variablen klickt und im Dialog dann auf Hinzufügen. Man vergibt einen sinnvollen Namen und wählt als Typ Ganze Zahl.

Wenn man die verwendete Variable weiter oben im Workflow bereits benutzt hat – oder wenn man einfach auf Nummer sicher gehen möchte – setzt man sie zuerst explizit auf Null. Das geht mit der Aktion Workflowvariable festlegen.

Jetzt prüft man mit der Bedingung Wenn das aktuelle Elementfeld gleich Wert ist, ob das Nachschlagefeld leer ist. Wenn man ein Nachschlagefeld einer anderen Liste prüfen möchte, verwendet man die Bedingung Wenn ein beliebiger Wert gleich Wert ist. Für den ersten Wert wählt man das Nachschlagefeld aus. Als Vergleichsoperator ist gleich eingestellt, das man durch Klicken in ist nicht leer ändert. Der zweite Wert, der sonst als Vergleich herangezogen wird, verschwindet dadurch und muß nicht angegeben werden.

Innerhalb der Bedingung verwendet man jetzt wieder die Aktion Workflowvariable festlegen und weist damit der Variablen den Wert des Nachschlagefelds zu (von dem man jetzt annimmt, daß es nicht leer ist). Dabei ist ganz wichtig, daß bei Feld zurückgeben als Als ganze Zahl ausgewählt wird. Man verwendet hier normalerweise Nachschlage-ID (als ganze Zahl), aber das erzeugt einen Fehler, wenn das Nachschlagefeld nur eine Null und keinen gültigen Wert enthält.

Damit hat man jetzt in der Variablen entweder die Zahl Null, falls das Nachschlagefeld leer ist, oder die ID des nachgeschlagenen Elements. Durch eine weitere Bedingung kann man jetzt die Variable prüfen und entsprechend reagieren. Zusammengefaßt sieht das dann so aus: