Ich stieß auf das gleiche Problem beim Schreiben von WiX-Installern. Meine Herangehensweise an das Problem ist größtenteils wie das, was Mike vorgeschlagen hat, und ich habe einen Blog-Beitrag Implementing WiX custom actions part 2: using custom tables.
Kurz gesagt, können Sie eine benutzerdefinierte Tabelle für Ihre Daten definieren:
<CustomTable Id="LocalGroupPermissionTable">
<Column Id="GroupName" Category="Text" PrimaryKey="yes" Type="string"/>
<Column Id="ACL" Category="Text" PrimaryKey="no" Type="string"/>
<Row>
<Data Column="GroupName">GroupToCreate</Data>
<Data Column="ACL">SeIncreaseQuotaPrivilege</Data>
</Row>
</CustomTable>
Dann eine einzige unmittelbare benutzerdefinierte Aktion schreiben das latente, Zurückkehren zu planen, und Commit Aktionen:
extern "C" UINT __stdcall ScheduleLocalGroupCreation(MSIHANDLE hInstall)
{
try {
ScheduleAction(hInstall,L"SELECT * FROM CreateLocalGroupTable", L"CA.LocalGroupCustomAction.deferred", L"create");
ScheduleAction(hInstall,L"SELECT * FROM CreateLocalGroupTable", L"CA.LocalGroupCustomAction.rollback", L"create");
}
catch(CMsiException &) {
return ERROR_INSTALL_FAILURE;
}
return ERROR_SUCCESS;
}
Der folgende Code zeigt, wie eine einzelne benutzerdefinierte Aktion geplant wird. Im Grunde öffnen Sie einfach die benutzerdefinierte Tabelle, lesen Sie die gewünschte Eigenschaft (Sie können das Schema jeder benutzerdefinierten Tabelle abrufen MsiViewGetColumnInfo()), dann formatieren Sie die erforderlichen Eigenschaften in die CustomActionData Eigenschaft (Ich verwende das Formular /propname:value
, obwohl du alles verwenden kannst, was du willst).
void ScheduleAction(MSIHANDLE hInstall,
const wchar_t *szQueryString,
const wchar_t *szCustomActionName,
const wchar_t *szAction)
{
CTableView view(hInstall,szQueryString);
PMSIHANDLE record;
//For each record in the custom action table
while(view.Fetch(record)) {
//get the "GroupName" property
wchar_t recordBuf[2048] = {0};
DWORD dwBufSize(_countof(recordBuf));
MsiRecordGetString(record, view.GetPropIdx(L"GroupName"), recordBuf, &dwBufSize);
//Format two properties "GroupName" and "Operation" into
//the custom action data string.
CCustomActionDataUtil formatter;
formatter.addProp(L"GroupName", recordBuf);
formatter.addProp(L"Operation", szAction);
//Set the "CustomActionData" property".
MsiSetProperty(hInstall,szCustomActionName,formatter.GetCustomActionData());
//Add the custom action into installation script. Each
//MsiDoAction adds a distinct custom action into the
//script, so if we have multiple entries in the custom
//action table, the deferred custom action will be called
//multiple times.
nRet = MsiDoAction(hInstall,szCustomActionName);
}
}
wie für die latenten, Rollback-Implementierung und benutzerdefinierte Aktionen begehen, ziehe ich nur eine Funktion verwenden und MsiGetMode() zu unterscheiden, was getan werden sollte:
extern "C" UINT __stdcall LocalGroupCustomAction(MSIHANDLE hInstall)
{
try {
//Parse the properties from the "CustomActionData" property
std::map<std::wstring,std::wstring> mapProps;
{
wchar_t szBuf[2048]={0};
DWORD dwBufSize = _countof(szBuf); MsiGetProperty(hInstall,L"CustomActionData",szBuf,&dwBufSize);
CCustomActionDataUtil::ParseCustomActionData(szBuf,mapProps);
}
//Find the "GroupName" and "Operation" property
std::wstring sGroupName;
bool bCreate = false;
std::map<std::wstring,std::wstring>::const_iterator it;
it = mapProps.find(L"GroupName");
if(mapProps.end() != it) sGroupName = it->second;
it = mapProps.find(L"Operation");
if(mapProps.end() != it)
bCreate = wcscmp(it->second.c_str(),L"create") == 0 ? true : false ;
//Since we know what opeartion to perform, and we know whether it is
//running rollback, commit or deferred script by MsiGetMode, the
//implementation is straight forward
if(MsiGetMode(hInstall,MSIRUNMODE_SCHEDULED)) {
if(bCreate)
CreateLocalGroup(sGroupName.c_str());
else
DeleteLocalGroup(sGroupName.c_str());
}
else if(MsiGetMode(hInstall,MSIRUNMODE_ROLLBACK)) {
if(bCreate)
DeleteLocalGroup(sGroupName.c_str());
else
CreateLocalGroup(sGroupName.c_str());
}
}
catch(CMsiException &) {
return ERROR_INSTALL_FAILURE;
}
return ERROR_SUCCESS;
}
Durch die Verwendung von Über der Technik können Sie für eine typische benutzerdefinierte Aktion die benutzerdefinierte Aktionstabelle auf fünf Einträge reduzieren:
<CustomAction Id="CA.ScheduleLocalGroupCreation"
Return="check"
Execute="immediate"
BinaryKey="CustomActionDLL"
DllEntry="ScheduleLocalGroupCreation"
HideTarget="yes"/>
<CustomAction Id="CA.ScheduleLocalGroupDeletion"
Return="check"
Execute="immediate"
BinaryKey="CustomActionDLL"
DllEntry="ScheduleLocalGroupDeletion"
HideTarget="yes"/>
<CustomAction Id="CA.LocalGroupCustomAction.deferred"
Return="check"
Execute="deferred"
BinaryKey="CustomActionDLL"
DllEntry="LocalGroupCustomAction"
HideTarget="yes"/>
<CustomAction Id="CA.LocalGroupCustomAction.commit"
Return="check"
Execute="commit"
BinaryKey="CustomActionDLL"
DllEntry="LocalGroupCustomAction"
HideTarget="yes"/>
<CustomAction Id="CA.LocalGroupCustomAction.rollback"
Return="check"
Execute="rollback"
BinaryKey="CustomActionDLL"
DllEntry="LocalGroupCustomAction"
HideTarget="yes"/>
Und InstallSque nce Tabelle nur zwei Einträge:
<InstallExecuteSequence>
<Custom Action="CA.ScheduleLocalGroupCreation"
After="InstallFiles">
Not Installed
</Custom>
<Custom Action="CA.ScheduleLocalGroupDeletion"
After="InstallFiles">
Installed
</Custom>
</InstallExecuteSequence>
Zusätzlich mit einem wenig Mühe der meisten des Code geschrieben werden können wiederverwendet werden (wie zum Beispiel von benutzerdefinierter Tabelle zu lesen, um die Eigenschaften bekommen, die Formatierung der benötigten Eigenschaften und auf CustomActionData-Eigenschaften), und die Einträge in der benutzerdefinierten Aktionstabelle sind jetzt nicht anwendungsspezifisch (die anwendungsspezifischen Daten werden in die benutzerdefinierte Tabelle geschrieben). Wir können die benutzerdefinierte Aktionstabelle in eine eigene Datei einfügen und sie einfach in jedes WiX-Projekt einfügen .
Da die Anwendungsdaten aus der benutzerdefinierten Tabelle gelesen werden, können anwendungsspezifische Details aus der DLL-Implementierung für die DLL-Datei mit benutzerdefinierter Aktion beibehalten werden, sodass die benutzerdefinierte Aktionstabelle zu einer Bibliothek werden und somit einfacher wiederverwenden kann.
So schreibe ich derzeit meine WiX Custom Actions, wenn jemand weiß, wie ich mich weiter verbessern kann, würde ich es sehr zu schätzen wissen. :)
(Den vollständigen Quellcode finden Sie auch in meinem Blogpost, Implementing Wix custom actions part 2: using custom tables.).