Seite 1 von 1

MSI-Custom Actions in C++ Teil 1

Verfasst: 22.04.2024, 13:57
von Andreas Kapust
Der Windows Installer stellt verschiedene Custom Action - Typen zur Verfügung. Wovon drei allerdings nicht mehr benutzt werden sollten bzw. können. JScript und VBScript sind abgekündigt, stehen auf neuen System also nicht mehr zur Verfügung und der Aufruf eines MSI über CA ist schon lange als problematisch eingestuft.

In diesem Teil geht es um den Aufruf einer DLL Typ 1, welcher im Stream der Binary Tabelle untergebracht ist.

Doch was sind Custom Actions und wofür benötigt man diese überhaupt?

Allgemein soll damit eine Aufgabe erlegt werden, die durch den Windows Installer nicht abgebildet werden kann (oder die der AKInstallerMSI nicht zur Verfügung stellt). Dieser Punkt ist wichtig! Lässt sich etwas mit Bordmitteln abdecken, sollte auf den Einsatz einer eigenen Custom Action verzichtet werden!

Die DLL kann in jeder beliebigen Sprache erstellt sein. Verbreitet sind C/C++, C# oder neuerdings auch Rust.

Eine DLL darf keine Abhängigkeiten haben, die auf dem Zielsystem zum Zeitpunkt der Ausführung möglicherweise nicht vorhanden sind, sonst scheitert die Installation oder Deinstallation. Wobei das Scheitern einer Deinstallation der kritischere Punkt ist.

Allgemein kann man die Custom Actions – Funktionen in drei Kategorien einteilen.

1: Sofort Ausführung per UI oder (Execute/)UI-Sequenz:
Diese sind gedacht um Werte zur ermitteln und Eigenschaften zu setzen, dürfen das System aber nicht verändern.

2: Sofortige per Execute-Sequenz:
Diese ermitteln Werte, lesen aus der MSI-Datenbank und erzeugen die Liste der Änderungen die bei der Systemänderung (verzögerte Ausführung) ausgeführt bzw. beim Rollback zurückgenommen werden müssen.

3; Verzögerte Ausführung (Execute-Sequenz):
In dieser Schicht besteht kein Zugriff auf die MSI-Datenbank, alle benötigten Werte müssen vorher von 2 ermittelt worden sein. Hier darf das System verändert werden bzw. muss das System bei Fehlern in Ursprungszustand zurückversetzt werden.

In diesem Teil befassen wir uns mit 1 und einer C/C++-DLL, dafür werden hier zwei Möglichkeiten vorgestellt. 2 und 3 wird Teil eines anderen Tutorials.


„C++ Standard Library“

Bild


Wie an diesem Beispiel erkennbar, nutzt die C++ Standard Library aber auch die MFC an jeder Ecke freiwillig Exceptions.
Das mag in einer Applikation, maximal mit dem Verlust der Änderungen am aktuellen Dokument zu kämpfen hat ok sein. Code der mit höhen Rechten (3) ausgeführt wird und an ungünstiger Stelle aussteigt, weil eine Ausnahme nicht abgefangen wird – aus welchen Gründen auch immer- und damit das Zielsystem in einem ungewollten Zustand zurücklässt, ist eine ganz andere Sache.

Ausnahmen müssen abgefangen werden, so dass die Funktion sauber zurückkehrt. Diese gilt ganz besonders für Rollback- und Deinstallation-Funktionen.

„C++ und fast schon C“

Bild

Auf den ersten Blick viel mehr an Code, da hier Speicher für Zeichenketten selbst besorgt und freigegeben werden muss. Auch hier kann es zu Exceptions kommen, wenn der Speicher durch Programmierfehler zerschossen wird. Allerdings kehren Funktionen mit einem Fehlercode oder false zurück anstatt einer Ausnahme.

Der Mehraufwand mag abschreckend sein, weil man alles von Hand machen muss und ein CString / std::wstring MeinString = L„Hallo “ + L„User“ + L„!\nWie geht es dir?“ geht doch viel schneller als ein Speicher holen, String kopieren, reicht Speicher für nächsten String, (ja) anfügen, (nein) mehr Speicher holen. Dennoch wird diese Reihe den steinigen „C++ und fast schon C“ beschreiten und die Klasse CustomAction nach und nach erweitern.

Befassen wir uns mit GetProperty, einer sehr häufig benötigten Routine welche uneingeschränkt nur in der sofortigen Ausführung zur Verfügung steht.

Der Aufruf würde so aussehen:

Code: Alles auswählen

UINT __stdcall CustomAction(MSIHANDLE hInstall)
{
CustomAction msi(hInstall);
LPWSTR pStr = nullptr;
if(msi.GetProperty(L"Eigenschaft", &pStr) == false)
    {
    // ...
    }
msi.FreeMem(pStr);
CustomAction wird mit dem MSIHANDLE initialisiert, der vom Windows Installer geliefert wird. Dieser wird aktuell nur gesichert. Man könnte aber auch direkt MsiGetActiveDatabase(hInstall); aufrufen, diesen Handle sichern um auf die MSI Datenbank zugreifen zu können. Das wird aber vielleicht gar nicht benötigen, darum verschieben wird das auf einen späteren Zeitpunkt (Teil) und andere Funktion.

In GetProperty wird die Größe des Werts ermittelt, Speicher besorgt und die Eigenschaft gelesen. Dabei wird berücksichtigt, das pStr bereits vorbelegt wurde (durch einen vorherigen GetProperty-Aufruf) oder das pStr = nullptr ist.

Man „kann“ aber auch folgendes verwenden:
WCHAR szStr[MAX_PATH+1];
DWORD dwSize = 0;
MsiGetProperty(hInstall, L"Eigenschaft", szStr, &dwSize);

Diese birgt den Nachteil der festen Größe, sowohl wenn der Wert kürzer ist, aber vor allem wenn der Wert länger als MAX_PATH ist.

Und eine Eigenschaft kann durchaus länger sein, speziell in der verzögerten Ausführung wie in folgen Teilen deutlich wird.

Haben wir alles ohne Fehler abgeschlossen, muss mit return ERROR_SUCCESS; zurück gekehrt werden, andernfalls mittels return ERROR_INSTALL_FAILURE; oder einem anderen entsprechendem Fehlercode z. B. ERROR_OUTOFMEMORY.


Im AKInstallerMSI wird die DLL in den Benutzerdefinierten Aktionen (Custom Actions) angeben und dort entschieden, wann die jeweilige Funktion aufgerufen wird.

Wie erwähnt kann dieses per Klick auf einen Schalter der GUI des Setups passieren oder aber per Sequenz. Also an einer bestimmten Position des „Ablaufplans“ des Setups. Dies wird Teil eines anderen Tutorials.

Bei Fragen oder Anregungen, wenden Sie sich gern per Mail an uns.