Я сталкивался с той же проблемой при написании установщиков WiX. Мой подход к проблеме в основном похож на то, что предложил Майк, и у меня есть запись в блоге Реализация пользовательских действий WiX, часть 2: использование пользовательских таблиц .
Короче говоря, вы можете определить пользовательскую таблицу для ваших данных:
<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>
Затем напишите одно немедленное настраиваемое действие, чтобы запланировать отложенные, откатные и зафиксированные настраиваемые действия:
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;
}
Следующий код показывает, как запланировать одно настраиваемое действие. По сути, вы просто открываете пользовательскую таблицу, читаете требуемое свойство (вы можете получить схему любой пользовательской таблицы, вызвав MsiViewGetColumnInfo () ), а затем отформатируйте необходимые свойства в CustomActionData свойство (я использую форму /propname:value
, хотя вы можете использовать все, что вы хотите).
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);
}
}
Что касается реализации отложенных, откатных и фиксированных пользовательских действий, я предпочитаю использовать только одну функцию и использовать MsiGetMode () , чтобы различать, что должно быть сделано:
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;
}
Используя описанную выше технику, для типичного набора настраиваемых действий вы можете уменьшить таблицу настраиваемых действий до пяти записей:
<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"/>
И в таблице InstallSquence только две записи:
<InstallExecuteSequence>
<Custom Action="CA.ScheduleLocalGroupCreation"
After="InstallFiles">
Not Installed
</Custom>
<Custom Action="CA.ScheduleLocalGroupDeletion"
After="InstallFiles">
Installed
</Custom>
</InstallExecuteSequence>
Кроме того, при небольшом усилии большая часть кода может быть написана для повторного использования (например, чтение из пользовательской таблицы, получение свойств, форматирование необходимых свойств и установка свойств CustomActionData), а также записей в настраиваемом действии. Теперь таблица не привязана к конкретному приложению (данные, относящиеся к конкретному приложению, записаны в пользовательской таблице), мы можем поместить таблицу пользовательских действий в отдельный файл и просто включить ее в каждый проект WiX.
Для файла DLL настраиваемых действий, поскольку данные приложения считываются из настраиваемой таблицы, мы можем не допускать сведений о приложении из реализации DLL, поэтому таблица настраиваемых действий может стать библиотекой и, следовательно, ее будет проще использовать повторно.
Вот как я сейчас пишу свои пользовательские действия WiX, если кто-то знает, как улучшить дальше, я был бы очень признателен. :)
(Вы также можете найти полный исходный код в моем сообщении в блоге, Реализация пользовательских действий Wix, часть 2: использование пользовательских таблиц .).