Kamil Zaripov
Kamil Zaripov

Reputation: 990

How to extract .msi features from command line?

I have an .msi that contains some development files (gstreamer development files) and I want to extract some features from .msi to some folder without installing from command line.

I know how to install some features using ADDLOCAL property of msiexec:

msiexec /i gstreamer.msi /qb TARGETDIR=some\folder ADDLOCAL=_gstreamer_1.0_system,_gstreamer_1.0_libav

But when I'm trying to extract files without installing using administrative installation, it seems ADDLOCAL property doesn't work and it extract all files in package:

msiexec /a gstreamer.msi /qb TARGETDIR=some\folder ADDLOCAL=_gstreamer_1.0_system,_gstreamer_1.0_libav

Does somebody know how to extract only selected features from .msi without installing it into system?

Upvotes: 2

Views: 5290

Answers (1)

Stein Åsmul
Stein Åsmul

Reputation: 42226

Short Answer: Make a transform, set Feature Table => Level Column to 0 for features you want to exclude from file extract. Run administrative installation as follows:

msiexec.exe /a MySetup.msi TRANSFORMS=MyTransform.mst TARGETDIR=C:\MyExtractPath\

Transform: There might be other ways that I can't think of at the moment, but one way you can try is to make a transform that you apply to the administrative installation. Depending on the number of features in the MSI this could be a lot of work, or it might not be much work at all (if there are few features you want to exclude).

Feature Level: There is a peculiarity in an MSI whereby a feature that has its Feature level set to 0 will not be extracted during an administrative installation. This seems like a bug to me (it is by design though), but you can use it to achieve what you want here - I think - but it isn't exactly pretty.

  1. Transform: Make a transform that sets the Level column in the Feature table to 0 for all the features you do not want to extract.
  2. msiexec.exe: Apply the transform to the MSI via the command line as follows:

    msiexec.exe /a MySetup.msi TRANSFORMS=MyTransform.mst TARGETDIR=C:\MyExtractPath\
    

Tools: You need a tool to help you make this transform. You probably have this already, but for others: I recommend Orca.exe - Microsoft's own SDK tool. However, there are a number of tools you can use that are free. The majority (I think) are described here: How can I compare the content of two (or more) MSI files? (scroll down to the list towards bottom - dark.exe is a decompiler and not an MSI viewer - the link describes comparing MSI files, not changing them).

Orca.exe will already be on disk (most likely) if you have Visual Studio installed. Try searching for Orca-x86_en-us.msi - under Program Files (x86). Just install it and find Orca in the start menu (or search for it).


Advanced: There is a VBScript (widiffdb.vbs) linked in the above "compare MSI" answer. It allows comparison of two MSI files. There is another VBScript which allows you to update an MSI via SQL statements. See here: WiRunSQL.vbs. These scripts you can find on disk if you have the SDK installed, or you can find them on github.com. See sample use of the script towards the bottom in this answer. Try this if you have a massive amount of Feature levels to set to 0. Obviously set all features to 0 and then manually switch on the ones you need by setting them back to normal (1 or higher - depends on the MSI).

Mockup: Sample VBScript code to set all Feature levels to 0:

Note! Do not run this on your main source MSI file. Make a copy!

No error handling in this script. To generate a transform see sample here (generate transform based on diff between original and modified MSI file).

Const msiOpenDatabaseModeTransact = 1
Const msiViewModifyReplace = 4

Set installer = CreateObject("WindowsInstaller.Installer")
Set database = installer.OpenDatabase("Test.msi", msiOpenDatabaseModeTransact)

' Allow user to cancel operation
If MsgBox ("Only run this on a COPY of your MSI!" & vbNewLine & vbNewLine & "Continue?", vbYesNo + vbInformation, "Warning!") = vbNo Then
   MsgBox "Update Aborted.", vbOKOnly + vbInformation, "Aborted" 
   WScript.Quit(0)
End If

sql = "SELECT * FROM `Feature`"
Set view = database.OpenView(sql)
view.Execute()

Do
   Set record = view.Fetch()
   If record Is Nothing Then Exit Do
   record.IntegerData(6) = 0
   view.Modify msiViewModifyReplace, record
Loop

view.Close()
database.Commit()

MsgBox "Update Complete.", vbOKOnly + vbInformation, "Completed"

Upvotes: 3

Related Questions