Ответ 1
Я столкнулся с той же проблемой при написании установщиков 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: использование пользовательских таблиц.).