MSI-Custom Actions in C++ Teil 4

Antworten
Andreas Kapust
Administrator
Beiträge: 1092
Registriert: 29.02.2004, 15:51
Wohnort: Hamburg
Kontaktdaten:

MSI-Custom Actions in C++ Teil 4

Beitrag von Andreas Kapust »

In diesem Teil geht es um die sofortige Ausführung und die Sammlung von Daten, um diese an eine Custom Action mit verzögerter Ausführung (InScript) weiterzugeben.

Die Klasse wurde in der Zwischenzeit hierfür erweitert und noch fehlende Logfileausgaben sowie kleinere Bugfixes eingepflegt.

Vorweg zur Erinnerung: MSI arbeitet in drei Phasen, welche in zwei Sequenzen aufgeteilt sind.
Die UI- Sequenz und die Execute- Sequenz. Letztere ist in sofortige Ausführung und verzögerte Ausführung (InScript) unterteilt.

Die sofortige Ausführung in der Client-Schicht erstellten in einem bestimmten Abschnitt ein Ablaufskript, das alle Systemänderungen bzw. Funktionsaufrufe vermerkt.

Ist die Skripterstellung fehlerfrei durchlaufen, wird dieses Skript an die Ausführungsschicht übergeben und ausgeführt. Im Logfile werden die Schichten mit (c) und (s) kenntlich gemacht. Es sind also zwei unterschiedliche Prozesse, die im Allgemeinen mit unterschiedlichen Rechten laufen.

Die Ausführungsschicht hat keinen Zugriff auf die Daten des Clients, diese betrifft die MSI-Datenbank aber auch alle Eigenschaften, die der Client-Schicht bekannt sind.

Tritt im Laufe der Abarbeitung des Skriptes ein Fehler auf, werden alle Schritte vom Zeitpunkt des Fehlers bis zurück zum Anfang zurückgenommen (Rollback).


Im letzten Teil wurde gezeigt, wie Daten aus den MSI-Tabellen gelesen werden können, allerdings betraf dieses im Grunde nur Zahlen und Strings. MSI bietet aber auch die Möglichkeit binäre Daten in den Tabellen zu speichern, ein Beispiel dafür ist die Tabelle Binary.

Dieses wird im AKInstallerMSI durch die Seite Ressourcen abgebildet.

Binary hat die Spalten Name und Data, wobei Data den Stream der binären Daten enthält.

Bild

Es wird geprüft ob Binary vorhanden ist und im Anschluss wie bereits aus Teil 3 bekannt ein OpenExecuteView und FetchRecord aufgerufen. GetRecordStream ermittelt wie üblich die Länge, besorgt Speicher und liest die Daten – im Prinzip der gleiche Aufbau wie wir ihn schon vorher bei der Funktion GetProperty kennengelernt haben.

Im Test-Code finden Sie ein Beispiel (MSI_CA_Teil3()), wie „BigPicture“ aus der Tabelle Binary gelesen wird.

Kommen wir nun zu der Übertragung der Daten.

Egal wie und welche Daten zur Verarbeitung gesammelt werden, diese haben alle gemein, dass diese als String (Zeichenkette) übergeben werden muss.

Doch wie geschieht dieses, wenn ein CA-Funktionsaufruf so aussieht?
UINT __stdcall CA_Funtion1(MSIHANDLE hInstall)
Die Antwort lautet: Über eine Eigenschaft namens CustomActionData.

Doch wurde nicht gesagt, dass in InScript-Aufrufen kein direkter Zugriff auf Eigenschaften besteht?
Dem ist auch so, wie oben erwähnt hat die Ausführungsschicht keinen Zugriff auf die MSI-Datenbank und auch nicht auf die im Speicher des Clients gehaltenen Eigenschaften. Nur auf die, die explizit transferiert werden. Allerdings wird CustomActionData auch gar nicht direkt gesetzt, sondern es wird der Funktionsname verwendet und alles in das Ablaufskript eingetragen.

Man kann sich dieses etwa so vorstellen:

SetProperty(„CustomActionData“, Zeichenkette) // DataSetter
DoAction(„Funktionsname“)

Damit steht der Ausführungsschicht diese Eigenschaft zur Verfügung, sie wird ja unmittelbar vor dem Funktionsaufruf gesetzt.

Bild

Dazu ein Beispiel:

Unsere Funktion heißt z. B. FILE_BackUp.
Wir ermitteln den Pfad einer Datei die geändert, und von der vorher ein Backup erzeugt werden soll. Dazu benötigen wir den Pfad der bestehenden Datei und, wenn man es nicht in der Ausführung machen will, den Pfad des Backups und natürlich die Daten, die geändert werden sollen.

Das alles muss wie oben erwähnt in einer Zeichenkette wzCustomActionData gesammelt und der verzögert aufzurufenden Funktion FILE_BackUp mitgeteilt werden.

In Zeile 791 wird die Eigenschaft FILE_BackUp (wzAction) per MsiSetPropertyW() mit dem Wert aus wzCustomActionData gesetzt.

In Zeile 808 wird die Funktion und der DataSetter für CustomActionData eingetragen, da es sich um eine verzögerte Ausführung handelt.

Zusammenfassend:
MsiSetPropertyW(„Funktionsname“,…) wirkt also als DataSetter.
Die Eigenschaft CustomActionData wird direkt vor dem Aufruf der Funktion gesetzt und kann von FILE_BackUp abgerufen werden.
Eine nachfolgende Änderung von CustomActionData hat auf die Eigenschaft der Funktion FILE_BackUp somit auch keine Auswirkung.

Das FILE_BackUp-Beispiel ist jetzt sehr einfach gehalten. Nicht berücksichtigt sind z. B. der Fehlerfall oder temporäre Daten, die beim Erfolg entfernt werden müssen.

Wir könnten also drei Aufrufe von CAExecuteAction haben:
1) Rollback
2) Execute
3) Commit

Wichtig: Das Setzen von Rollback muss vor dem Execute erfolgen, da beide Aufrufe im Ablaufskript stehen und im Fehlerfall rückwärts abgearbeitet werden. Stünde Rollback hinter Execute würde es im Fehlerfall nicht aufgerufen.

Beim Rollback soll, wenn das Backup existiert, das Original durch das Backup ersetzt werden.
Beim Execute wird erst das Backup erzeugt und dann das Original geändert.
Beim Commit, wird das Backup entfernt.

Oder

Beim Rollback soll, wenn das Backup existiert, das Backup entfernt werden.
Beim Execute wird erst das Backup erzeugt und dann das Backup geändert.
Beim Commit, wird das Original durch das Backup ersetzt.

Wobei die letzte Vorgehensweise die Bessere ist.

Erzeugung von wzCustomActionData.

Wie Sie durch die Funktion GetDataFromBinary() schon vermuten, haben wir in den meisten Fällen natürlich nicht nur Strings, die irgendwie in einer großen Zeichenkette kombiniert werden müssen. Es kann sich dabei auch um Zahlen oder Binär (mit Nullen) handeln, wobei 0 bekanntlich eine C-Zeichenkette abschließt und damit der Teil rechts davon unter den Tisch fallen würde.
Wir müssen also Zahlen in String umwandeln und Binär Decoden.

Eine Konvertierung von Binär zu Hex-String ist hierbei die schlechteste Lösung, weil wir für jedes Byte zwei (wenn man Kommas nutzt, drei Zeichen) benötigen, das Ganze natürlich * sizeof(WCHAR), kurz um - das ist zu lang.

Eine bessere Möglichkeit, und in der Klasse umgesetzt, ist die Konvertierung in Base64, was durch die Crypt32.dll in wenigen Zeilen Code umgesetzt werden kann. Damit sinkt der Overhead von 100% ((Byte * 2 ohne Komma) auf 33%, mit Base85 sogar auf 25% mit Base91 auf 23%.

In unserem einfachen Beispiel mag dieses noch keine Rolle spielen, doch stellen Sie sich kurz vor, dieses Beispiel wird auf 1000 Dateien angewendet und alles in einer Zeichenkette übertragen.

Die Funktionen Encode() und Decode() ermöglichen die Konvertierung nach und von Base64 und können um Base85 oder Base91 ergänzt werden.

Da diese Zeichenkette möglichst kompakt sein soll, fällt ein Wrapper wie XML oder JSON weg und wir nutzen ein Token-System, ähnlich der Funktion wcstok(). Da diese Funktion aber keine überlappenden Aufrufe gestattet, können wir wcstok() selbst nicht verwenden.

Das Erzeugen der Zeichenkette wird über einen Satz von CASetXXXX-Funktionen bereitgestellt.
Das Abholen der Werte über einen Satz von CAGetXXXX-Funktionen.

Der Aufbau der Zeichenkette ist folgender: Wert1<ZEICHEN>Wert2<ZEICHEN>Wert3.

ZEICHEN kann jedes Zeichen außer 0 sein, sollte aber nicht in einem der Werte vorkommen. Das Zeichen 128 bietet sich Trenner an, Sie können aber auch ein beliebiges anders Zeichen verwenden oder mehrere für unterschiedliche Abschnitte.

Beispiel für mehrere: Wert1<ZEICHEN1>Wert1a<ZEICHEN2>Wert1b<ZEICHEN2>Wert2b<ZEICHEN1>Wert2a

Beim Abholen wird die Zeichenkette in Token aufgeteilt, Speicher dafür besorgt und ein String zurück geliefert, der am Ende freigegeben werden muss.

Im Code finden Sie unter MSI_CA_Teil4() ein ausführliches Beispiel für diese Funktionen.

Das war in diesem Teil leider wieder sehr viel Theorie, die allerdings nötig ist, um den Ablauf zu verstehen. Im nächsten Teil werden wir das oben erwähnte FILE_BackUp-Beispiel umsetzen.

Ich hoffen es hat Ihnen gefallen und einen tieferen Einblick ermöglicht, bei Fragen wenden Sie sich gern per Mail an uns.

Die Konsolenapplikation und die CustomAction-Klasse finden Sie hier.
Mit freundlichen Grüßen,
AKApplications, Andreas Kapust
Antworten