1

I need help in closing an MSI file.

I am making an MSI file editor, where i can open and update an msi file. but the problem is when i try to reopen the same file (just to cross check the updated msi), it throws an error

OpenDatabase,DatabasePath,OpenMode

on this line

database = installer.OpenDatabase(msiFile.FullName, MsiOpenDatabaseMode.msiOpenDatabaseModeTransact);

So, i believe its because the msi file is already open. can anyone help me out in closing an already opened msi.

Bravo
  • 3,341
  • 4
  • 28
  • 43

4 Answers4

3

I'm guessing you are using a interop library generated from the WindowsInstaller.Installer COM object. You don't want to do that. Trust me on this one. Instead download Windows Installer XML and look for the Microsoft.Deployment.WindodsInstaller assembly found in the SDK folder. This library is part of Deployment Tools Foundation (DTF) and it is the gold standard for interoping with Windows Installer.

In DTF, the Database class implements the IDisposable inteface therefore the most elegant way is to put it inside of a using statement.

using(Database database = new Database(...))
{
 // Do Stuff Here
}

.NET will automatically call the Dispose() method once the block is complete and this in turn close the database and free the unmanaged handles. The garbage collector will then be notified to clean up the managed class at a later non-important time. If you fail to call Dispose (either implicitly or explicitly) then the unmanged resources won't be released until the garbage collector gets around to cleaning up and that would not be a good thing as you are seeing.

Christopher Painter
  • 54,556
  • 6
  • 63
  • 100
  • Thanks Chris, i got what you are trying to explain. But right now, i have already implemented it using interop library. I guess i will be using your method, once i plan to update this application. Thanks again for your help. – Bravo Feb 12 '13 at 07:13
  • I've got a boat load of experience in this area. Trust me, it won't be that difficult to refactor and you'll be in such better shape for it. The longer you wait the more rework you'll have. DTF uses P/Invoke which is cleaner then COM Interop and also supports WPF Databinding and LINQ to MSI queries. – Christopher Painter Feb 12 '13 at 12:07
  • Part of this issue is that the only answer I found to find the product version from an msi file used the COM library (like everyone else). The `Microsoft.Deployment.WindowsInstaller` library which I ultimately used was available in the wixtoolset SDK. – Ben Adams Jun 29 '21 at 22:34
  • The MSI SDK shipped with a COM automation interface and Win32 Installer functions. DTF creates a set of classes for .NET that ultimately P/Invoke the installer functions. https://learn.microsoft.com/en-us/windows/win32/msi/installer-functions – Christopher Painter Jun 29 '21 at 23:53
2

Hey Stephen and Christopher, this is how i resolved my problem using your invaluable suggestions.

I am using database as a global variable, since i need it for many different transactions throughout the application, so added these lines before opening an msi file.

if (view != null)
   {
      System.Runtime.InteropServices.Marshal.FinalReleaseComObject(view);
      view = null;
   }
if (database != null)
   {
      System.Runtime.InteropServices.Marshal.FinalReleaseComObject(database);
      database = null;
   }
GC.Collect();

hope my answer also help people stuck in the same scenario.

Bravo
  • 3,341
  • 4
  • 28
  • 43
1

Set the database to variable to null or just force the variable go out of scope. This should release the open handle.

Alternatively you may need to force the garbage collector to run to relinquish the handle. Best Practice for Forcing Garbage Collection in C#

Community
  • 1
  • 1
Stephen Connolly
  • 1,639
  • 10
  • 15
0

You will have to release every handle, for example:

Database database = _installer.OpenDatabase(...);
View view = database.OpenView(query);
view.Execute();
Record record = view.Fetch();

Marshal.FinalReleaseComObject(record);
view.Close();
Marshal.FinalReleaseComObject(record);
Marshal.FinalReleaseComObject(database);
record = null;
view = null;
database = null;
GC.Collect();

Do that in a try... finally and you should be good.

Wernight
  • 36,122
  • 25
  • 118
  • 131