Tuesday, May 3, 2011

Javascript MSI automation - closing the database

Hello,

I'm maintaining some build scripts that munge MSI databases from Javascript files (.js). The code is basically:

{
    var oTargetDB = g_oInstaller.openDatabase("mymsi.msi", msiOpenDatabaseModeReadOnly);
    var oView = oTargetDB.openView(...);
    oView.execute();
    oView.close();
}
// Later...
{
    var oTargetDB = g_oInstaller.openDatabase("mymsi.msi", msiOpenDatabaseModeTransact);
}

The second openDatabase fails with the ever-helpful 0x80004005 (E_FAIL), which I assume is because the first one is not closed. However, the Database object has no close method. I tried oTargetDB=null - didn't change anything.

What can I do to close the database, so I can open it again?

Edit:

  • I see the handle open using handle.exe from sysinternals, so it's definately the problem
  • Delay didn't help (I tried waiting for a really long time, and it didn't help)
  • Short-lived script isn't an option due to the existing script structure
  • Uber-hacking is beyond the scope of this fix
  • I'll have to uglify my code and go with the (pseudo-singleton) solution. Blergh.
From stackoverflow
  • Setting the handle to null works with VB Script, so I'm not sure why it wouldn't with JS. My best guess would possibly be that you've discovered a bug in the JS implimentation - especially if you're opening it as read-only the first time, there isn't any need to call the Commit method to flush any changes.

    The documentation seems to indicate that simply closing the handle is all that is needed to close the database. So I'm guessing even after setting oTargetDB=null Javascript isn't releasing the handle by the time you go to open it again. You could try inserting a delay after releasing the handle to see if this is the case.

  • In this case, MS forgot to implement a close method. Really. Lame huh? In general, setting obj=null does not perform clean up such as close operations.

    When I ran into specific this problem, it was calling the MSIDatabase objects from C#. The database needs to be closed (see MsiOpenDatabase and MsiCloseHandle). Unfortunately, as you have observed, MS forgot to implement Database.Close in the COM object you see. From C#, I was able to call Marshal.FinalReleaseComObject to unlock the file.

    We eventially gave up on the COM object as unreliable and switched to p/invoke.

    If jscript is your only option, you can keep the scripts short lived - when they die they release their handles. Or, you create a singleton to represent the object. Potentially, some uberhacker will know how to invoke MsiCloseHandle from jScript, and identify the handle used by that COM object.

0 comments:

Post a Comment