Skript Adapter Erweiterung

Einleitung

Die RDS-Daten-Erweiterung sammelt Daten von den Windows Remote Desktop Systemen. Alle verfügbaren Session- und Prozessdaten werden von den angegebenen Hosts abgerufen und in den Datenspeicher integriert.

Fakten

Name

Script Adapter

Bezeichner

ScriptAdapter

Version

1.1

Enthaltene Module

Source

Abschnitte

  • <konfigurierbar>

Informationsblöcke

  • SA_<konfigurierbar>

Konfiguration

Das Modul benötigt keine Konfigurationsblock in der Serverkonfiguration. Es erwartet ein Unterverzeichnis namens adapter_scripts im Datenverzeichnis des Servers. Jede Datei mit einer .xml-Endung in diesem Verzeichnis wird als Skriptdatei gelesen. Andere Dateien, die dieser Endung nicht entsprechen, werden ignoriert.

Das folgende Beispiel zeigt, wie das Skriptverzeichnis aussehen könnte.

c:\Program Files\EducateIT\Raptor\
^- assumed server data directory
                                  adapter_scripts\
                                                  script1.xml
                                                  script2.xml
                                                  script3.xml

Jede Skriptdatei in diesem Verzeichnis muss im gültigen XML-Format vorliegen. Dabei solltest du die Datei immer in der UTF-8 Kodierung speichern.

Das folgende Beispiel zeigt die Struktur der Skriptdatei.

 1<?xml version="1.0" encoding="UTF-8" ?>
 2<AdapterScript version="1">
 3    <Id>...</Id>
 4    <Name>...</Name>
 5    <Required>
 6        ...
 7    </Required>
 8    <Schema>
 9        ...
10    </Schema>
11    <DataScript><![CDATA[
12        ...
13    ]]></DataScript>
14</AdapterScript>

Die folgenden Abschnitte erklären jeden Wert dieser Struktur im Detail.

Ersetze die Auslassungspunkte (...) mit den tatsächlichen Werten und Inhalten, die für dein Skript erforderlich sind. Die Werte <Id>, <Name>, <Required>, <Schema> und <DataScript> sollten durch die entsprechenden Informationen ersetzt werden, die das Skript beschreiben und definieren.

Das Tag Id

Eine eindeutige ID für das Adapter-Skript. Die ID darf ausschließlich Buchstaben, Zahlen und den Unterstrich enthalten. Die Länge der ID darf 200 Zeichen nicht überschreiten.

Das Tag Name

Ein für Menschen lesbarer Name, der in Fehlermeldungen innerhalb der Log-Datei angezeigt wird.

Das Tag Required

Der Required-Block enthält die Anforderungen, die erfüllt sein müssen, bevor das Skript ausgeführt werden kann. Jeder Eintrag in dieser Liste stellt eine zwingende Bedingung dar, die erfüllt sein muss.

Es kann einen ObjectType-Eintrag geben. Der ObjectType-Eintrag schränkt das Skript auf einen bestimmten Objekttyp ein. Der Inhalt dieses Tags muss der Name eines Objekttyps sein. Im folgenden Beispiel ist das Skript ausschließlich für Benutzerobjekte vorgesehen:

<Required>
    <ObjectType>User</ObjectType>
</Required>

Du kannst beliebig viele InformationBlock-Einträge hinzufügen. Der Inhalt des InformationBlock-Eintrags muss ein gültiger Informationsblock-Identifier sein. Nur wenn alle angegebenen Informationsblöcke vorhanden sind, wird dem Objekt ein Block für die Adapterdaten hinzugefügt.

Wenn Daten vom Adapterblock angefordert werden, wird die Ausführung des Skripts verzögert, bis alle erforderlichen Blöcke Daten enthalten. Im Spezialfall, dass die erforderlichen Blöcke nicht bereits automatisch vom Client ausgelöst wurden, löst der Adapter eine Datenanforderung für alle diese erforderlichen Blöcke aus.

Das folgende Beispiel verlangt, dass beide Informationsblöcke ActiveDirectory und ActiveDirectoryOR vorhanden sind und Daten enthalten, bevor das Skript ausgeführt wird.

<Required>
    <InformationBlock>ActiveDirectory</InformationBlock>
    <InformationBlock>ActiveDirectoryOR</InformationBlock>
</Required>

Die Reihenfolge der Werte spielt keine Rolle. Du kannst die verschiedenen Anforderungen kombinieren, wie im nächsten Beispiel gezeigt.

<Required>
    <ObjectType>User</ObjectType>
    <InformationBlock>ActiveDirectory</InformationBlock>
    <InformationBlock>ActiveDirectoryOR</InformationBlock>
</Required>

Das Tag Schema

Mit dem Schema-Block legst du das Schema für die Anzeige im Client fest. Du kannst beliebig viele SchemaValue-Einträge hinzufügen. Jeder Eintrag hat das folgende Format:

<SchemaValue name="..." label="..." type="..." section="..." orderIndex="..." />

Die Bedeutung der einzelnen Attribute wird in den nächsten Abschnitten beschrieben.

Das Attribut name

Dies ist der Bezeichner für den Wert. Er kann Buchstaben, Zahlen und den Unterstrich enthalten. Du verwendest diesen Bezeichner im Skript, um die Daten für diesen Wert bereitzustellen.

Das Attribut label

Das für Menschen lesbare Label, das im Client angezeigt wird.

Das Attribut type

Der Typ des Wertes. Der Adapter unterstützt nicht alle Raptor-Typen. Die unterstützten Typen sind String, StringList, Table, Integer und Date.

Das Attribut section

Der Bezeichner des Abschnitts, in dem der Wert platziert werden soll. Dies kann ein vorhandener Abschnitt oder ein neuer sein.

Das Attribut orderIndex

Eine Ganzzahl zwischen 1 und 1000, die die Position des Wertes im Abschnitt bestimmt.

Das Tag DataScript

In dieses Tag schreibst du den eigentlichen Skript welcher ausgeführt wird. Wie du Skripte für den Skript-Adapter schreibst findest du im Kapitel Skript-Adapter Skripte.

Um Probleme mit der Formatierung deiner Skripte zu vermeiden, solltest du diese immer in eine CDATA-Sektion platzieren, wie im folgenden Beispiel gezeigt:

 1<?xml version="1.0" encoding="UTF-8" ?>
 2<AdapterScript version="1">
 3    <Id>...</Id>
 4    <Name>...</Name>
 5    <Required>
 6        ...
 7    </Required>
 8    <Schema>
 9        ...
10    </Schema>
11    <DataScript><![CDATA[
12        // Dein Skript....
13    ]]></DataScript>
14</AdapterScript>

Das Konfigurationsschema

Module

ScriptAdapter

Value

Directory

Optional

String

  • Must not be empty.

Der Wert Directory

Mit dem optionalen Wert Directory kannst du das Unterverzeichnis wählen, worin nach Scripten für die Erweiterung gesucht wird. Lässt du diesen Wert weg, wird im Unterverzeichnis adapter_scripts nach Scripten gesucht.

Falls du den Pfad änderst, passe die Berechtigungen so an, dass keine unbefugten Personen die Scripte anpassen können.

Skript-Adapter Skripte

Kurze Übersicht

Verfasse die Skripte, die Adapterdaten erzeugen, in JavaScript oder dem standardisierten ECMAScript. Eine Übersicht der Sprache findest du im Kapitel Qt-Script/JavaScript Übersicht.

Sofern die von dir im Tag Required konfigurierten Bedingungen erfüllt sind, wird dein Skript ausgeführt. Da dies für jedes angeforderte Objekt bei jeder Änderung von Daten geschieht, sollte dein Skript schnell ausgeführt werden und keinesfalls den Prozess blockieren.

Innerhalb deines Skripts hast du Zugriff auf zwei globale Objekte: object und result. Nutze diese beiden Objekte, um alle aktuellen Daten vom Objekt zu erhalten und das Ergebnis für deinen Adapter bereitzustellen. Zudem existiert ein helper Objekt, das weitere Funktionen zur Verfügung stellt.

Um das Ergebnis bereitzustellen, schreibst du es als Instanzvariablen in das result-Objekt. Zum Beispiel hast du in deinem Schema den „String“-Wert example definiert.

1<?xml version="1.0" encoding="UTF-8" ?>
2<AdapterScript version="1">
3    ...
4    <Schema>
5        <SchemaValue name="example" label="Example Script" type="String" section="exampleScript" orderIndex="10" />
6    </Schema>
7    ...
8</AdapterScript>

Am Ende deines Skripts weist du der Variable result.example dein Resultat zu, wie im folgenden Beispiel gezeigt:

 1<?xml version="1.0" encoding="UTF-8" ?>
 2<AdapterScript version="1">
 3    ...
 4    <Schema>
 5        <SchemaValue name="example" label="Example Script" type="String" section="exampleScript" orderIndex="10" />
 6    </Schema>
 7    <DataScript><![CDATA[
 8        // Deine Berechnung des Resultats...
 9        result.example = "Hello World!"
10    ]]></DataScript>
11</AdapterScript>

Für das Ergebnis kannst du native Datentypen verwenden, aber es gibt auch Klassen, die du instanziieren kannst: StringValue und TableValue, die eine einfache Schnittstelle bieten, um die beiden Raptor-Werttypen zu erstellen.

Lies die nächsten Kapitel für eine detaillierte Beschreibung aller oben erwähnten Variablen und Klassen.

Die Variable object

Die Schnittstelle für die Variable object wird im Kapitel Skript Objekt Interface beschrieben. Die Instanz dieser Variable ist bereits zu Beginn des Skripts mit einer Momentaufnahme der Daten des Objekts initialisiert. Auch wenn mehr Informationsblöcke verfügbar sind, als im Abschnitt „Required“ angegeben, solltest du nur auf Daten zugreifen, die du dort spezifiziert hast.

Die Variable result

Die Variable result wird zu Beginn des Skripts als leeres Objekt initialisiert. Du musst diesem Objekt Eigenschaften hinzufügen, um den Datenblock als Ergebnis aufzubauen. Die hinzugefügten Eigenschaften und die Namen der Eigenschaften werden am Ende deines Skripts in einen Datenblock konvertiert.

Du kannst verschiedene native JavaScript-Typen verwenden, um die Werte zu erstellen. Sie werden automatisch in die richtigen Werttypen konvertiert.

Siehe das folgende Beispiel für automatische Konvertierungen von nativen Werten. Einige Zieltypen unterstützen mehrere Möglichkeiten einen Wert bereitzustellen, wie Strings und String-Arrays.

result.value1 = "A text"; // Native string literal
result.value2 = new String("A text"); // String object
result.value3 = 12; // Number
result.value4 = true; // Boolean
result.value5 = ["1", "2", "3"]; // Array literal.
result.value6 = new Array("1", "2", "3"); // Array object.

Die Konvertierung von nativen Werten erfolgt wie in der folgenden Tabelle beschrieben:

JavaScript Typ

Raptor Typ

Notes

Native String Literal

String

Sonderzeichen im Text werden automatisch maskiert.

String object

Native Number

Integer

Der Wert wird in eine Ganzzahl konvertiert. Stellen nach dem Dezimalpunkt werden abgeschnitten.

Native Boolean

Boolean

Array Literal

StringList

Das Array wird in eine StringList konvertiert, aber nur, wenn alle Werte in der Liste Strings sind oder in Strings konvertiert werden können.

Array Object

Es gibt spezielle Objekttypen für spezielle Formatierungen in Strings, wie Aktionen oder Links, und für Tabellen. Siehe das folgende Code-Beispiel, wie man Instanzen dieser Klassen erstellt.

result.value1 = new StringValue();
result.value2 = new TableValue();

Siehe Die StringValue Klasse und Die TableValue Klasse für Details zu den beiden speziellen Klassen und ihren Schnittstellen.

Das globale helper Objekt

Das globale helper Objekt stellt Methoden zur Verfügung, um zusätzliche Prüfungen ohne das Benutzerobjekt durchzuführen. Siehe die folgende Abbildung für die Schnittstelle dieser Klasse.

../../../_images/helper-class-interface.jpg

Das Interface des helper Objekts.

Derzeit definiert die Schnittstelle nur zwei Methoden, die in den folgenden Abschnitten erklärt werden.

Methode doesFileExist

Diese Methode überprüft, ob eine Datei an dem angegebenen Ort vorhanden ist. Die Methode gibt true zurück, wenn der Pfad vorhanden ist und auf eine Datei verweist.

Methode doesDirectoryExist

Diese Methode überprüft, ob ein Verzeichnis an dem angegebenen Ort vorhanden ist. Die Methode gibt true zurück, wenn der Pfad vorhanden ist und auf ein Verzeichnis verweist.

Anwendungsbeispiel

var filePath = "\\\\example\\path\\path\\file.exe";
if (helper.doesFileExist(filePath)) {
    // ...
}
var directoryPath = "C:\\exampleDirectory";
if (helper.doesDirectoryExist(directoryPath)) {
    // ...
}

Die StringValue Klasse

Mit dieser Klasse erstellst du einen String-Wert, der spezielle Formatierungen oder Links enthält. Sieh dir die folgende Abbildung für ein UML-Diagramm der StringValue-Klassen-Schnittstelle an.

../../../_images/StringValue-class-interface.jpg

Die Schnittstelle der StringValue Klasse.

Die folgenden Abschnitte enthalten Beschreibungen für jede Methode dieser Klasse. Siehe das folgende Beispiel für Anwendungsbeispiele dieser Klasse.

var text = new StringValue();

// Setze den String auf einen gegebenen Text
text.setText( "Beispieltext." ); // Lösche den String.
text.clear();

// Konvertiere den String in einen nativen String.
var nativeString = text.toString(); // Füge weiteren Text hinzu.
text.appendText( "Mehr Text." ); // Füge einen benutzerdefinierten Link hinzu.
text.appendLink( "http://www.example.com", "Gehe zur Beispiel-Website" );

// Füge einen Programmlink (Aktion) hinzu
text.appendProgramLink( "\"c:/my software/example.exe\" -a=xxx", "Beispiel starten" );

// Füge einen Dateilink hinzu
text.appendFileLink( "c:/documents/file.txt", "Datei öffnen" );

Methode clear

Mit der clear-Methode setzt du die String-Wert-Instanz zurück und entfernst jeglichen zuvor hinzugefügten oder gesetzten Text.

Methode toString

Mit dieser Methode konvertierst du den aktuellen Inhalt der String-Wert-Instanz in einen nativen Skript-String, der alle Escape- und Formatierungstags enthält, die bereits hinzugefügt wurden.

Methode setText

Mit der Methode setText legst du den Text der Instanz fest. Escaping wird durchgeführt, um eine Formatierungsinterpretation des Textes zu verhindern.

Methode appendText

Mit dieser Methode fügst du am Ende des aktuellen Strings zusätzlichen Text hinzu. Escaping wird durchgeführt, um eine Formatierungsinterpretation des angehängten Textes zu verhindern.

Die TableValue Klasse

Diese Klasse repräsentiert einen Tabellenwert. Siehe die folgende Abbildung für einen Tabellenwert, der im Raptor-Client angezeigt wird.

../../../_images/table.jpg

Ein Tabellenwert im Raptor-Client.

Die Schnittstelle der TableValue Klasse ist in der Abbildung unten dargestellt.

../../../_images/TableValue-class-interface.jpg

Die Schnittstelle der TableValue Klasse.

Um die Schnittstelle zu vereinfachen, muss die Tabelle in der folgenden Reihenfolge erstellt werden:

  • Erstelle eine neue Instanz der Klasse.

  • Füge alle Spalten mit der Methode addColumn hinzu.

  • Setze alle Zellen mit der Methode setCellValue.

Am Ende des Skripts werden leere oder unvollständige Zeilen entfernt. Es gibt eine Begrenzung von maximal 16 Spalten und 1000 Zeilen.

Die nächsten Abschnitte beschreiben die Methoden dieser Schnittstelle.

Methode addColumn

Diese Methode fügt der Tabelle eine neue Spalte hinzu. Jede Spalte benötigt ein Label, das im Client angezeigt wird, und einen festen Typ. Der Typ muss ein String mit dem case-sensitiven Typnamen sein. Die unterstützten Typen sind String, StringList, Table, Integer und Date.

Der Spaltenindex beginnt bei Null.

Methode getColumnCount

Diese Methode gibt die aktuelle Anzahl der Spalten zurück.

Methode getRowCount

Diese Methode gibt die aktuelle Anzahl der Zeilen zurück. Beim Erstellen der Tabelle werden leere Zeilen gezählt und du erhältst nur die höchste Anzahl der aktuell gesetzten Zeilen. Wenn du beispielsweise eine Zelle in Zeile 100 setzt, gibt diese Methode 101 Zeilen zurück. Am Ende des Skripts werden leere oder unvollständige Zeilen entfernt. In diesem Fall kann die Zeilenzahl niedriger sein.

Methode setCellValue

Mit dieser Methode setzt du die Zellen der Tabelle. Du kannst die Zellen in beliebiger Reihenfolge setzen, indem du sie über einen Zeilen- und Spaltenindex ansprichst.

Um gültige Zeilen zu erstellen, müssen alle Zellen einer Zeile mit einem Wert des richtigen Typs gesetzt werden, den du in der Methode addColumn angegeben hast.

Das folgende Skriptbeispiel zeigt, wie man eine Tabelle erstellt.

// Erstelle eine neue Instanz von TableValue
var tabelle = new TableValue();

// Füge Spalten hinzu
tabelle.addColumn("Name", "String");
tabelle.addColumn("Alter", "Integer");

// Setze Zellenwerte
tabelle.setCellValue(0, 0, "Alice");
tabelle.setCellValue(0, 1, 30);
tabelle.setCellValue(1, 0, "Bob");
tabelle.setCellValue(1, 1, 25);

Beispiele

Konfigurationsbeispiel

 1<?xml version="1.0" encoding="UTF-8" ?>
 2<AdapterScript version="1">
 3    <Id>profilePaths</Id>
 4    <Name>Group Based Profile Paths</Name>
 5    <Required>
 6        <InformationBlock>ActiveDirectory</InformationBlock>
 7        <InformationBlock>ActiveDirectoryOR</InformationBlock>
 8    </Required>
 9    <Schema>
10        <SchemaValue name="profilePaths" label="Profile Paths" type="Table" section="profilePaths" orderIndex="10"/>
11    </Schema>
12    <DataScript><![CDATA[
13        ...script, see below...
14    ]]></DataScript>
15</AdapterScript>

Skript-Beispiel

 1// Configuration
 2// ---------------------------------------------------------------------------
 3// config:
 4// This configures the paths, labels and the group who has to match to
 5// display this path.
 6// column 1: The label for the path.
 7// column 2: The name of the group who has to match.
 8// column 3: The path, which can contain the placeholder %USERNAME% which
 9// is replaced by the name of the object.
10// Note! Don't forget to escape the backslash characters!
11//
12var config = [
13    ["CTX Farm Prod", "CitrixFarmProd", "\\\\ctx\\profile$\\%USERNAME%\\Prod"],
14    ["CTX Farm Int",  "CitrixFarmInt",  "\\\\ctx\\profile$\\%USERNAME%\\Int"],
15    ["CTX Farm Text", "CitrixFarmTest", "\\\\ctx\\profile$\\%USERNAME%\\Test"],
16    ["Domain Admin",  "Domain Admins",  "\\\\ctx\\profile$\\%USERNAME%\\Test"]
17];
18
19// actionCommand:
20// This is the action command which is displayed in the last column of the
21// table. You can use the placeholder %PATH% which is replaced with the
22// path in the table.
23var actionCommand =
24    "\"%RaptorPath%/EducateIT/AppleTreeClient/AppleTreeClient.exe\" " +
25    "-profile=profilePath " +
26    "-path=\"%PATH%\"";
27
28// actionLabel:
29// The action label is displayed as button for the action above.
30var actionLabel = "Reset Profile";
31
32// Script
33// ---------------------------------------------------------------------------
34//
35// Get the groups for this object (array of strings)
36var groups = object.AD_User_Delayed.groups;
37var user = object.name;
38
39// Prepare the table as result.
40var paths = new TableValue();
41paths.addColumn("Name", "String");
42paths.addColumn("Path", "String");
43paths.addColumn("Action", "String");
44
45// Check which of the paths matches.
46var row = 0;
47for (var i = 0; i < config.length; ++i) {
48    var configRow = config[i];
49    var name = configRow[0];
50    var group = configRow[1];
51    var path = configRow[2];
52
53    var hasMatch = false;
54    for (var x = 0; x < groups.length; ++x) {
55        if (groups[x] === group) {
56            hasMatch = true;
57            break;
58        }
59    }
60
61    if (!hasMatch) {
62        continue;
63    }
64
65    // Success, found a match prepare the final path.
66    var finalPath = path.replace("%USERNAME%", user);
67
68    // Check if the path exists
69    if (!helper.doesDirectoryExist(finalPath)) {
70        continue; // Skip this profile
71    }
72
73    // Prepare the other values
74    var finalAction = actionCommand.replace("%PATH%", finalPath);
75    var pathValue = new StringValue();
76    pathValue.appendFileLink(finalPath);
77    var actionValue = new StringValue();
78    actionValue.appendProgramLink(finalAction, actionLabel);
79
80    // Add the values to the table.
81    paths.setCellValue(row, 0, name);
82    paths.setCellValue(row, 1, pathValue);
83    paths.setCellValue(row, 2, actionValue);
84    row++;
85}
86
87// Add the table to the result.
88result.profilePaths = paths;