14

我有一个WiX安装程序和一个使用安装程序属性的自定义操作(加上撤消和回滚)。自定义操作必须在所有文件都在硬盘上之后发生。为此,您似乎需要 WXS 文件中的 16 个条目;根内有八个,如下所示:

<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"/>

中的八个InstallExecuteSequence,如下所示:

<Custom Action="SetForRollbackDo" After="InstallFiles">REMOVE&lt;>"ALL"</Custom>
<Custom Action="RollbackDo" After="SetForRollbackDo">REMOVE&lt;>"ALL"</Custom>
<Custom Action="SetForDo" After="RollbackDo">REMOVE&lt;>"ALL"</Custom>
<Custom Action="Do" After="SetForDo">REMOVE&lt;>"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>

有没有更好的办法?

4

3 回答 3

4

WiX 自定义操作是一个很好的模型。在这种情况下,您只需声明CustomAction立即操作、延迟操作和回滚操作。您只需使用 调度Custom立即操作,其中立即操作作为本机 DLL 中的代码实现。

然后,在立即操作的代码中,您调用MsiDoAction以安排回滚和延迟操作:由于它们是延迟的,它们会在您调用时写入脚本中,MsiDoAction而不是立即执行。您还需要调用MsiSetProperty以设置自定义操作数据。

例如,下载 WiX 源代码并研究其IISExtension工作原理。WiX 操作通常会解析自定义表并基于该表为延迟操作的属性生成数据。

于 2008-09-30T13:13:12.037 回答
3

如果您有需要支持回滚的复杂自定义操作,您可以考虑编写一个 Wix 扩展。扩展通常提供创作支持(即映射到 MSI 表条目的新 XML 标记),以及自定义操作的自动调度。

这不仅仅是编写自定义操作,但一旦您的 CA 达到一定程度的复杂性,扩展提供的易于创作是值得的。

于 2008-09-27T23:06:37.533 回答
3

我在编写 WiX 安装程序时遇到了同样的问题。我解决这个问题的方法很像 Mike 建议的,我有一篇博文实现 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属性(我使用 form /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 部分:使用自定义表格。)。

于 2011-06-16T12:27:07.213 回答