installer - modificar - ¿Cuál es la mejor manera de definir una acción personalizada en WiX?
metaetiqueta wix (3)
Tengo un instalador de WiX y una única acción personalizada (además de deshacer y deshacer) para ella que utiliza una propiedad del instalador. La acción personalizada tiene que suceder después de que todos los archivos estén en el disco duro. Parece que necesita 16 entradas en el archivo WXS para esto; ocho dentro de la raíz, así:
<CustomAction Id="SetForRollbackDo" Execute="immediate" Property="RollbackDo" Value="[MYPROP]"/>
<CustomAction Id="RollbackDo" Execute="rollback" BinaryKey="MyDLL" DllEntry="UndoThing" Return="ignore"/>
<CustomAction Id="SetForDo" Execute="immediate" Property="Do" Value="[MYPROP]"/>
<CustomAction Id="Do" Execute="deferred" BinaryKey="MyDLL" DllEntry="DoThing" Return="check"/>
<CustomAction Id="SetForRollbackUndo" Execute="immediate" Property="RollbackUndo" Value="[MYPROP]"/>
<CustomAction Id="RollbackUndo" Execute="rollback" BinaryKey="MyDLL" DllEntry="DoThing" Return="ignore"/>
<CustomAction Id="SetForUndo" Execute="immediate" Property="Undo" Value="[MYPROP]"/>
<CustomAction Id="Undo" Execute="deferred" BinaryKey="MyDLL" DllEntry="UndoThing" Return="check"/>
Y ocho dentro de InstallExecuteSequence
, así:
<Custom Action="SetForRollbackDo" After="InstallFiles">REMOVE<>"ALL"</Custom>
<Custom Action="RollbackDo" After="SetForRollbackDo">REMOVE<>"ALL"</Custom>
<Custom Action="SetForDo" After="RollbackDo">REMOVE<>"ALL"</Custom>
<Custom Action="Do" After="SetForDo">REMOVE<>"ALL"</Custom>
<Custom Action="SetForRollbackUndo" After="InstallInitialize">REMOVE="ALL"</Custom>
<Custom Action="RollbackUndo" After="SetForRollbackUndo">REMOVE="ALL"</Custom>
<Custom Action="SetForUndo" After="RollbackUndo">REMOVE="ALL"</Custom>
<Custom Action="Undo" After="SetForUndo">REMOVE="ALL"</Custom>
¿Hay una mejor manera?
Encontré el mismo problema al escribir instaladores de WiX. Mi enfoque del problema es más o menos como lo sugirió Mike y tengo una publicación de blog. Implementación de acciones personalizadas de WiX, parte 2: uso de tablas personalizadas .
En resumen, puede definir una tabla personalizada para sus datos:
<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>
Luego, escriba una única acción personalizada inmediata para programar las acciones personalizadas diferidas, revertidas y confirmadas:
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;
}
El siguiente código muestra cómo programar una única acción personalizada. Básicamente, acaba de abrir la tabla personalizada, leer la propiedad que desea (puede obtener el esquema de cualquier tabla personalizada llamando a MsiViewGetColumnInfo () ), luego formatee las propiedades necesarias en la propiedad CustomActionData (yo uso el form /propname:value
, aunque puedes usar lo que quieras).
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);
}
}
En cuanto a implementar las acciones personalizadas diferidas, retrotraer y confirmar, prefiero usar solo una función y usar MsiGetMode () para distinguir lo que se debe hacer:
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;
}
Al usar la técnica anterior, para un conjunto de acciones personalizado típico puede reducir la tabla de acciones personalizadas a cinco entradas:
<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"/>
Y la tabla InstallSquence solo para dos entradas:
<InstallExecuteSequence>
<Custom Action="CA.ScheduleLocalGroupCreation"
After="InstallFiles">
Not Installed
</Custom>
<Custom Action="CA.ScheduleLocalGroupDeletion"
After="InstallFiles">
Installed
</Custom>
</InstallExecuteSequence>
Además, con un poco de esfuerzo, la mayor parte del código se puede escribir para reutilizar (como leer en la tabla personalizada, obtener las propiedades, formatear las propiedades necesarias y establecerlas en las propiedades de CustomActionData), y ahora las entradas en la tabla de acciones personalizadas son no específico de la aplicación (los datos específicos de la aplicación están escritos en la tabla personalizada), podemos poner la tabla de acción personalizada en un archivo propio y solo incluirla en cada proyecto de WiX.
Para el archivo DLL de acción personalizada, dado que los datos de la aplicación se leen desde la tabla personalizada, podemos mantener los detalles específicos de la aplicación fuera de la implementación de DLL, por lo que la tabla de acción personalizada puede convertirse en una biblioteca y, por lo tanto, más fácil de reutilizar.
Así es como actualmente escribo mis acciones personalizadas de WiX, si alguien sabe cómo mejorar aún más, lo agradecería muchísimo. :)
(También puede encontrar el código fuente completo en mi publicación de blog, Implementación de acciones personalizadas de Wix, parte 2: uso de tablas personalizadas ).
Las acciones personalizadas de WiX son un excelente modelo a seguir. En este caso, solo declara, con CustomAction
, la acción inmediata, la acción diferida y la acción de reversión. Solo programa, con Custom
, la acción inmediata, donde la acción inmediata se implementa como código en una DLL nativa.
Luego, en el código de la acción inmediata, usted llama a MsiDoAction
para programar la reversión y las acciones diferidas: a medida que se difieren, se escriben en la secuencia de comandos en el punto en que llama a MsiDoAction
lugar de ejecutarse inmediatamente. Deberá llamar también a MsiSetProperty
para establecer los datos de acción personalizados.
Descargue el código fuente de WiX y estudie cómo funciona IISExtension
, por ejemplo. Las acciones de WiX generalmente analizan una tabla personalizada y generan los datos para la propiedad de la acción diferida basada en esa tabla.
Si tiene acciones personalizadas complejas que necesitan admitir la reversión, puede considerar escribir una extensión Wix. Las extensiones suelen proporcionar soporte de autoría (es decir, nuevas etiquetas XML que se asignan a las entradas de la tabla MSI), además de la programación automática de acciones personalizadas.
Es más trabajo que simplemente escribir una acción personalizada, pero una vez que sus CA alcanzan un cierto nivel de complejidad, la facilidad de creación que brindan las extensiones puede valer la pena.