从自定义操作

时间:2017-01-21 13:52:27

标签: windows-installer custom-action

我想在安装时在我的msi包中插入cab文件。这可能吗? 立即采取自定义行动:

UINT __stdcall CreateDataCab(MSIHANDLE hInstall) {
    MSIHANDLE hRec = MsiCreateRecord(1);
    UINT status = MsiRecordSetStream(hRec, 1, T("C:\\work\\Data2.cab"));

    MSIHANDLE hDb = MsiGetActiveDatabase(hInstall);
    MSIHANDLE hView = 0;
    status = MsiDatabaseOpenView(hDb, _T("INSERT INTO `_Streams` (`Name`, `Data`) VALUES ('Data2.cab', ?)"), &hView);
    status = MsiViewExecute(hView, hRec);
    status = MsiViewClose(hView);
    status = MsiCloseHandle(hView);
    status = MsiCloseHandle(hRec);
    status = MsiCloseHandle(hDb);
}

它在INSTALL操作的最开始执行。 status变量的值始终为ERROR_SUCCESS。所以一切似乎都很好。但是在msi日志中我可以看到错误触发:

Action start 14:29:27: INSTALL.
MSI (s) (4C:14) [14:29:27:049]: Running ExecuteSequence
MSI (s) (4C:14) [14:29:27:049]: Doing action: SetSilentInstall
Action start 14:29:27: SetSilentInstall.
MSI (s) (4C:14) [14:29:27:051]: PROPERTY CHANGE: Adding SILENT property. Its value is '/s'.
Action ended 14:29:27: SetSilentInstall. Return value 1.
MSI (s) (4C:14) [14:29:27:052]: Doing action: CreateDataCab
Action start 14:29:27: CreateDataCab.
MSI (s) (4C:1C) [14:29:27:072]: Invoking remote custom action. DLL: C:\windows\Installer\MSI3EC2.tmp, Entrypoint: CreateDataCab
MSI (s) (4C:B4) [14:29:27:074]: Generating random cookie.
MSI (s) (4C:B4) [14:29:27:079]: Created Custom Action Server with PID 32604 (0x7F5C).
MSI (s) (4C:14) [14:29:27:147]: Running as a service.
MSI (s) (4C:14) [14:29:27:151]: Hello, I'm your 64bit Impersonated custom action server.
MSI (s) (4C!50) [14:32:22:069]: Note: 1: 2263 2: Data2.cab 3: -2147287035 

Note: 1: 2263 2: Data2.cab 3: -2147287035日志记录表示自定义操作中出现MSI错误2263 Could not open stream [2].-2147287035的十六进制值为FFFFFFFF80030005,可能为STG_E_ACCESSDENIED HRESULT - Access Denied。 我对此错误的第一个猜测是,当尝试将其提取到记录中时,MSI服务无法访问源文件。我放宽了C:\work\Data2.cab的权限,但没有运气。 然后我决定在MsiRecordSetStream()调用中使用不存在文件的路径。 通过此更改MsiRecordSetStream()调用返回161错误。由于CreateDataCab()中没有分支,因此它会在MsiRecordSetStream()成功后执行到最后并且所有MSI函数都会执行。 msi日志:

Action start 16:11:02: CreateDataCab.
MSI (s) (4C:D8) [16:11:02:596]: Invoking remote custom action. DLL: C:\windows\Installer\MSI4154.tmp, Entrypoint: JreCreateDataCab
MSI (s) (4C:54) [16:11:02:597]: Generating random cookie.
MSI (s) (4C:54) [16:11:02:605]: Created Custom Action Server with PID 34668 (0x876C).
MSI (s) (4C:6C) [16:11:02:682]: Running as a service.
MSI (s) (4C:6C) [16:11:02:684]: Hello, I'm your 64bit Impersonated custom action server.
MSI (s) (4C!74) [16:11:18:565]: Note: 1: 1101 2: C:\work\foo.cab 3: 2 
MSI (s) (4C!74) [16:15:08:291]: Note: 1: 2263 2: Data2.cab 3: -2147287035

Note: 1: 1101 2: C:\work\Data2.cab 3: 2表示发生了MSI错误1101 Could not open file stream: [2]。这是msi日志中的额外错误,Note: 1: 2263 2: Data2.cab 3: -2147287035仍在那里。

使用调试器单步执行CreateDataCab()会产生以下结果:

  1. Note: 1: 1101 2: C:\work\foo.cab 3: 2错误在错误MsiRecordSetStream()调用失败后立即添加到日志中,当它返回错误161时。
  2. 成功Note: 1: 2263 2: Data2.cab 3: -2147287035调用后,
  3. MsiCloseHandle(hView)错误会立即添加到日志中。
  4. _Streams的开头倾倒CreateDataCab()表的内容,最后表示新记录已成功添加到表中。
  5. 这就是我现在所拥有的。似乎发生以下情况:

    1. MsiRecordSetStream()新记录工作正常。如果指定了正确的路径,系统可以将文件提取到记录中;
    2. 将新记录插入_Streams表的SQL请求成功;
    3. Data新添加记录的字段不包含任何数据。
    4. 我很困惑在自定义操作中执行的MSI函数全部通过,但msi日志文件中有错误。对_Streams表的更改也部分成功:添加了新记录,但记录中不包含任何数据。看起来有可能在运行时更新_Streams表,但是在表中存储数据会出错。

      我在构建时更新msi文件中的_Streams没有问题。一切正常。但是,在自定义操作的运行时执行此操作对我来说是打破的。我在CA功能中遗漏了什么吗?在运行时更改_Streams是否合法?

      UPD :我对此问题进行了更多调查。我重写了我的msi,从两个CA拨打CreateDataCab()两次。还修改了CreateDataCab()本身。伪代码如下:

      CreateDataCab() {
          dumpStream();
      
          openDb;
          _Streams["Data1.cab"].Data = "c:/work/Data1.cab";
          commitDb;
          closeDb;
      
          dumpStream();
      }
      
      dumpStream() {
          openDb;
          if ("Data1.cab" in _Streams) {
              save _Streams["Data1.cab"].Data to "c:/work/saved.cab";
          }
          closeDb;
      }
      

      当为第一个CA调用CreateDataCab()时,第一个dumpStream()不输出任何内容,但第二个内容会向c:/work/saved.cab写入内容。此文件显示为等于c:/work/Data1.cab的二进制文件,该文件用作记录流的源。这表明可以在_Streams表中更新流数据。数据仍然存在并保留在数据库中,不会看到它是在两个不同的MsiGetActiveDatabase()/MsiDatabaseClose()范围中插入和检索的。非常好,但Note: 1: 2263 2: Data2.cab 3: -2147287035仍然在日志中。

      当为第二个CA调用CreateDataCab()时,事情并不那么好。第一个dumpStream()清空c:/work/saved.cab文件。这意味着Data1.cab记录仍在_Streams表中,但具有空流数据。

      正如我们注意到_Streams表是特别的,我试验了Binary表。尝试修改现有记录导致Note: 1: 2259 2: 3: 4:错误(数据库:[2]表更新失败),即"UPDATE Binary SET Data = ? WHERE Name='Data1.cab'" SQL查询不起作用。所以我在运行安装之前重新编写了CA代码以使用MsiViewModify(hView, MSIMODIFY_INSERT_TEMPORARY, hRec)并从Data1.cab表中删除了Binary记录。

      第一个CreateDataCab() CA工作正常:插入OK,msi日志中没有错误,第二个dumpStream()确定。但是,当为第二个CA调用CreateDataCab()时,第一个dumpStream()转储零字节和后续MsiViewModify(hView, MSIMODIFY_INSERT_TEMPORARY, hRec)失败。

      _Streams和Binary表的结果完全相同:

      1. 插入的记录会在CA调用中保留。
      2. 添加记录中的流数据不会在CA调用中保留,并在第二个CA中设置为零长度。
      3. 在二进制表中插入新的临时记录会导致msi日志中没有错误消息。 好消息是可以使用来自CA的流数据来更改记录。不是坏消息是,在Binary表中,不可能更改现有记录中的数据。 坏消息是添加/更改流数据会破坏db。

        如果有人可以质疑我的结论,我会很高兴。

1 个答案:

答案 0 :(得分:1)

在运行时,数据库是只读的。您必须使用INSERT ... TEMPORARY而不仅仅是INSERT,我不确定它是否适用于_Streams表。 (我通常也更幸运使用基于记录的API而不是基于SQL的API,即打开表的视图,构建记录,然后在这种情况下MsiViewModify(hView,MSIMODIFY_INSERT_TEMPORARY,hRec) );但我不认为这是问题所在。)

那就是说,这个用例对我来说听起来非常不寻常,所以我会非常谨慎。因为记录是临时的,所以每当MSI运行时你都必须这样做 - 而不仅仅是第一次安装。请务必测试您要支持的所有方案,包括卸载,修复和任何升级方法。特别是如果你可以提供小的升级补丁。