2008-09-24 23 views

Respuesta

17

Debe extraer la secuencia de archivos CAB de su msi usando MsiDB.exe (suministrado con Windows Installer SDK). Ejecútelo desde la línea de comando con la opción -x y especifique el nombre del archivo cab - esto se detalla en la tabla Medios en la base de datos msi.

Como alternativa, puede omitir esta parte si especifica la opción "Archivos de paquete como:" en las opciones de VSI en "Comprime en archivos de gabinete" para dejar el archivo cab fuera del msi cuando se haya creado (se creará en el mismo directorio que el msi).

Una vez extraído, puede cambiar el archivo especificado en la carpeta de la cabina; su nombre ha sido destrozado, por lo que debe averiguar qué nombre de msi está en la tabla de archivos y luego renombrarlo.

Una vez hecho esto, puede volver a abrirlo con la utilidad MsiDB con la opción -a.

Antes de agregar con -a necesita usar msidb -k a remove the cab from the MSI.

+0

VSI = Visual Studio Installer. http://www.devx.com/vb2themax/Article/19893 – splattne

+0

'msidb -d test.msi -x Data1.cab' ¿No está funcionando? Después de ejecutar este comando, no hay error, no hay mensaje de éxito. No sé cómo proceder. – PawanS

-1

La forma más sencilla de hacerlo es volver a empaquetar MSI:

  1. Abrir el archivo MSI en Wise para Windows Installer. Elija una opción para extraer archivos.
  2. Busque el archivo en el disco y reemplácelo.
  3. Construir MSI.

Estos pasos también deberían funcionar para InstallShield.

+1

Supongo que es una solución válida para alguien que tiene acceso a herramientas de reempaquetado comercial ... pero si ese fuera el caso, dudo que se hiciera la pregunta;) – saschabeaumont

1

Código de ejemplo muy simple para reemplazar un archivo dentro de una MSI. Esto no vuelve a transmitir el archivo/CAB nuevo al MSI, pero requiere que el CAB esté en el mismo directorio que el MSI para que la instalación se realice correctamente. Estoy seguro de que con un poco más de esfuerzo que podría alterar el código para transmitir los CAB de nuevo.

Const MSI_SOURCE = "application.msi" 
Const FILE_REPLACE = "config.xml" 

Dim filesys, installer, database, view 
Dim objFile, size, result, objCab 

Set filesys=CreateObject("Scripting.FileSystemObject") 
Set installer = CreateObject("WindowsInstaller.Installer") 
Set database = installer.OpenDatabase (MSI_SOURCE, 1) 

Set objFile = filesys.GetFile(FILE_REPLACE) 
size = objFile.Size 

Set objCab = CreateObject("MakeCab.MakeCab.1") 
objCab.CreateCab "config.cab", False, False, False 
objCab.AddFile FILE_REPLACE, filesys.GetFileName(FILE_REPLACE) 
objCab.CloseCab 

Set view = database.OpenView ("SELECT LastSequence FROM Media WHERE DiskId = 1") 
view.Execute 

Set result = view.Fetch 
seq = result.StringData(1) + 1 ' Sequence for new configuration file 

Set view = database.OpenView ("INSERT INTO Media (DiskId, LastSequence, Cabinet) VALUES ('2', '" & seq & "', 'config.cab')") 
view.Execute 

Set view = database.OpenView ("UPDATE File SET FileSize = " & size & ", Sequence = " & seq & " WHERE File = '" & LCase(FILE_REPLACE) & "'") 
view.Execute 
+0

¿Podría elaborar sobre la transmisión del archivo cab al msi? Además, si estoy entendiendo correctamente, no estás * reemplazando * un archivo en el msi, lo estás agregando. ¿Correcto? IOW, si 'config.xml' ya existe, entonces habrá dos de ellos. –

+0

objCab se crea como tal, establece objCab = CreateObject ("MakeCab.MakeCab.1"). ¿Correcto? –

+0

Correcto, estoy agregando un nuevo archivo y luego actualizando la tabla de archivos para hacer referencia al archivo recién agregado en lugar del original. En cuanto a la transmisión, una MSI puede contener archivos adicionales, como archivos CAB, incrustados en (creo) la tabla binaria. Aunque si vuelves a enviar el CAB al MSI, destruirás todas las firmas digitales, por lo que dejo el CAB por separado. – saschabeaumont

39

Uso msi2xml.

  1. Este comando extrae los archivos MSI:

    msi2xml -c OutputDir TestMSI.MSI

  2. abierto OutputDir y modificar el archivo.

  3. para reconstruir el MSI ejecutar:

    xml2msi.exe -m TestMSI.xml

Es necesario el -m hacer caso omiso de la 'prueba de suma de control MD5' que falla cuando un archivo MSI (s) son modificados.

+0

Gracias. Usé esto y funcionó como un encanto. –

+0

+1 esto también funcionó para mí, gracias. – yantaq

+0

También utilicé http://blogs.technet.com/b/sateesh-arveti/archive/2010/11/21/msi-explorer.aspx para encontrar el nombre real del archivo que quería reemplazar –

3

Pruebe InstEd - un editor de instalador en http://www.instedit.com/. Tiene una prueba de 30 días, y funciona para mí. Extrae los archivos en una carpeta, edita, reconstruye la cabina y luego guarda la MSI.Todo menos la edición de tus archivos se hace en la GUI.

No es un gran programa, pero pagué $ 30 para poder editar rápidamente los archivos en el MSI.

No trabajo para InstEd o relacionado de ninguna manera que no sea el pago y el uso de la aplicación.

+2

Mi requisito es el mismo. Pero no puedo hacer que este Instedit funcione. Sería genial si puedes decirme qué pasos seguir. Necesito reemplazar un archivo pdf dentro del paquete msi que creé usando Visual Studio 2010. –

+1

@ShaktiPrakashSingh Esto es dos años tarde, pero acabo de usar InstEdit para reemplazar el elemento en mi MSI usando InstEdit. Lo escribí en: http://stackoverflow.com/questions/4398042/replace-a-file-from-msi/37148282#37148282 – raddevus

0

Este código sólo se ha probado en 1 archivo, donde el nombre es excactly el mismo que el archivo que está siendo reemplazado ..

pero debe aplicar respuesta Christopher pintores en C#, la DTF (de WIX)

/** 
* this is a bastard class, as it is not really a part of building an installer package, 
* however, we need to be able to modify a prebuild package, and add user specific files, post build, to save memory on server, and have a fast execution time. 
* 
* \author Henrik Dalsager 
*/ 

//I'm using everything... 
using System; 
using System.IO; 
using System.Linq; 
using System.Text; 
using System.Collections; 
using System.Collections.Generic; 
using System.Diagnostics.CodeAnalysis; 
using System.Globalization; 
using System.Text.RegularExpressions; 
using Microsoft.Deployment.Compression.Cab; 
using Microsoft.Deployment.WindowsInstaller; 
using Microsoft.Deployment.WindowsInstaller.Package; 

namespace MSIFileManipulator 
{ 
/** 
* \brief updates an existing MSI, I.E. add new files 
* 
*/ 
class updateMSI 
{ 
    //everything revolves around this package.. 
    InstallPackage pkg = null; 

    //the destruction should close connection with the database, just in case we forgot.. 
    ~updateMSI() 
    { 
     if (pkg != null) 
     { 
      try 
      { 
       pkg.Close(); 
      } 
      catch (Exception ex) 
      { 
       //rollback? 

       //do nothing.. we just don't want to break anything if database was already closed, but not dereffered. 
      } 
     } 
    } 

    /** 
    * \brief compresses a list of files, in a workdir, to a cabinet file, in the same workdir. 
    * \param workdir path to the workdir 
    * \param filesToArchive a list of filenames, of the files to include in the cabinet file. 
    * \return filename of the created cab file 
    */ 
    public string createCabinetFileForMSI(string workdir, List<string> filesToArchive) 
    { 
     //create temporary cabinet file at this path: 
     string GUID = Guid.NewGuid().ToString(); 
     string cabFile = GUID + ".cab"; 
     string cabFilePath = Path.Combine(workdir, cabFile); 

     //create a instance of Microsoft.Deployment.Compression.Cab.CabInfo 
     //which provides file-based operations on the cabinet file 
     CabInfo cab = new CabInfo(cabFilePath); 

     //create a list with files and add them to a cab file 
     //now an argument, but previously this was used as test: 
     //List<string> filesToArchive = new List<string>() { @"C:\file1", @"C:\file2" }; 
     cab.PackFiles(workdir, filesToArchive, filesToArchive); 

     //we will ned the path for this file, when adding it to an msi.. 
     return cabFile; 
    } 

    /** 
    * \brief embeds a cabinet file into an MSI into the "stream" table, and adds it as a new media in the media table 
    * This does not install the files on a clients computer, if he runs the installer, 
    * as none of the files in the cabinet, is defined in the MSI File Table (that informs msiexec where to place mentioned files.) 
    * It simply allows cabinet files to piggypack within a package, so that they may be extracted again at clients computer. 
    * 
    * \param pathToCabFile full absolute path to the cabinet file 
    * \return media number of the new cabinet file wihtin the MSI 
    */ 
    public int insertCabFileAsNewMediaInMSI(string cabFilePath, int numberOfFilesInCabinet = -1) 
    { 
     if (pkg == null) 
     { 
      throw new Exception("Cannot insert cabinet file into non-existing MSI package. Please Supply a path to the MSI package"); 
     } 

     int numberOfFilesToAdd = numberOfFilesInCabinet; 
     if (numberOfFilesInCabinet < 0) 
     { 
      CabInfo cab = new CabInfo(cabFilePath); 
      numberOfFilesToAdd = cab.GetFiles().Count; 
     } 

     //create a cab file record as a stream (embeddable into an MSI) 
     Record cabRec = new Record(1); 
     cabRec.SetStream(1, cabFilePath); 

     /*The Media table describes the set of disks that make up the source media for the installation. 
      we want to add one, after all the others 
      DiskId - Determines the sort order for the table. This number must be equal to or greater than 1, 
      for out new cab file, it must be > than the existing ones... 
     */ 
     //the baby SQL service in the MSI does not support "ORDER BY `` DESC" but does support order by.. 
     IList<int> mediaIDs = pkg.ExecuteIntegerQuery("SELECT `DiskId` FROM `Media` ORDER BY `DiskId`"); 
     int lastIndex = mediaIDs.Count - 1; 
     int DiskId = mediaIDs.ElementAt(lastIndex) + 1; 

     //wix name conventions of embedded cab files is "#cab" + DiskId + ".cab" 
     string mediaCabinet = "cab" + DiskId.ToString() + ".cab"; 

     //The _Streams table lists embedded OLE data streams. 
     //This is a temporary table, created only when referenced by a SQL statement. 
     string query = "INSERT INTO `_Streams` (`Name`, `Data`) VALUES ('" + mediaCabinet + "', ?)"; 
     pkg.Execute(query, cabRec); 
     Console.WriteLine(query); 

     /*LastSequence - File sequence number for the last file for this new media. 
      The numbers in the LastSequence column specify which of the files in the File table 
      are found on a particular source disk. 

      Each source disk contains all files with sequence numbers (as shown in the Sequence column of the File table) 
      less than or equal to the value in the LastSequence column, and greater than the LastSequence value of the previous disk 
      (or greater than 0, for the first entry in the Media table). 
      This number must be non-negative; the maximum limit is 32767 files. 
      /MSDN 
     */ 
     IList<int> sequences = pkg.ExecuteIntegerQuery("SELECT `LastSequence` FROM `Media` ORDER BY `LastSequence`"); 
     lastIndex = sequences.Count - 1; 
     int LastSequence = sequences.ElementAt(lastIndex) + numberOfFilesToAdd; 

     query = "INSERT INTO `Media` (`DiskId`, `LastSequence`, `Cabinet`) VALUES (" + DiskId.ToString() + "," + LastSequence.ToString() + ",'#" + mediaCabinet + "')"; 
     Console.WriteLine(query); 
     pkg.Execute(query); 

     return DiskId; 

    } 

    /** 
    * \brief embeds a cabinet file into an MSI into the "stream" table, and adds it as a new media in the media table 
    * This does not install the files on a clients computer, if he runs the installer, 
    * as none of the files in the cabinet, is defined in the MSI File Table (that informs msiexec where to place mentioned files.) 
    * It simply allows cabinet files to piggypack within a package, so that they may be extracted again at clients computer. 
    * 
    * \param pathToCabFile full absolute path to the cabinet file 
    * \param pathToMSIFile full absolute path to the msi file 
    * \return media number of the new cabinet file wihtin the MSI 
    */ 
    public int insertCabFileAsNewMediaInMSI(string cabFilePath, string pathToMSIFile, int numberOfFilesInCabinet = -1) 
    { 
     //open the MSI package for editing 
     pkg = new InstallPackage(pathToMSIFile, DatabaseOpenMode.Direct); //have also tried direct, while database was corrupted when writing. 
     return insertCabFileAsNewMediaInMSI(cabFilePath, numberOfFilesInCabinet); 
    } 

    /** 
    * \brief overloaded method, that embeds a cabinet file into an MSI into the "stream" table, and adds it as a new media in the media table 
    * This does not install the files on a clients computer, if he runs the installer, 
    * as none of the files in the cabinet, is defined in the MSI File Table (that informs msiexec where to place mentioned files.) 
    * It simply allows cabinet files to piggypack within a package, so that they may be extracted again at clients computer. 
    * 
    * \param workdir absolute path to the cabinet files location 
    * \param cabFile is the filename of the cabinet file 
    * \param pathToMSIFile full absolute path to the msi file 
    * \return media number of the new cabinet file wihtin the MSI 
    */ 
    public int insertCabFileAsNewMediaInMSI(string workdir, string cabFile, string pathToMSIFile, int numberOfFilesInCabinet = -1) 
    { 
     string absPathToCabFile = Path.Combine(workdir, cabFile); 
     string absPathToMSIFile = Path.Combine(workdir, pathToMSIFile); 
     return insertCabFileAsNewMediaInMSI(absPathToCabFile, absPathToMSIFile, numberOfFilesInCabinet); 
    } 

    /** 
    * \brief reconfigures the MSI, so that a file pointer is "replaced" by a file pointer to another cabinets version of said file... 
    * The original file will not be removed from the MSI, but simply orphaned (no component refers to it). It will not be installed, but will remain in the package. 
    * 
    * \param OriginalFileName (this is the files target name at the clients computer after installation. It is our only way to locate the file in the file table. If two or more files have the same target name, we cannot reorient the pointer to that file!) 
    * \param FileNameInCabinet (In case you did not have the excact same filename for the new file, as the original file, you can specify the name of the file, as it is known in the cabinet, here.) 
    * \param DiskIdOfCabinetFile - Very important information. This is the Id of the new cabinet file, it is the only way to know where the new source data is within the MSI cabinet stream. This function extracts the data it needs from there, like sequence numbers 
    */ 
    public void PointAPreviouslyConfiguredComponentsFileToBeFetchedFromAnotherCabinet(string OriginalFileName, string FileNameInCabinet, string newFileSizeInBytes, int DiskIdOfCabinetFile) 
    { 
     //retrieve the range of sequence numbers for this cabinet file. 
     string query = "SELECT `DiskId` FROM `Media` ORDER BY `LastSequence`"; 
     Console.WriteLine(query); 
     IList<int> medias = pkg.ExecuteIntegerQuery("SELECT `DiskId` FROM `Media` ORDER BY `LastSequence`"); 

     query = "SELECT `LastSequence` FROM `Media` ORDER BY `LastSequence`"; 
     Console.WriteLine(query); 
     IList<int> mediaLastSequences = pkg.ExecuteIntegerQuery("SELECT `LastSequence` FROM `Media` ORDER BY `LastSequence`"); 

     if(medias.Count != mediaLastSequences.Count) 
     { 
      throw new Exception("there is something wrong with the Media Table, There is a different number of DiskId and LastSequence rows"); 
     } 

     if(medias.Count <= 0) 
     { 
      throw new Exception("there is something wrong with the Media Table, There are no rows with medias available.."); 
     } 

     int FirstSequence = -1; 
     int LastSequence = -1; 
     int lastIndex = medias.Count - 1; 

     for (int index = lastIndex; index >= 0; index--) 
     { 
      int rowLastSequence = mediaLastSequences.ElementAt(index); 
      int rowDiskId = medias.ElementAt(index); 

      if (rowDiskId == DiskIdOfCabinetFile) 
      { 
       LastSequence = rowLastSequence; 
       if (index < lastIndex) 
       { 
        //the next cabinet files last sequence number + 1, is this ones first.. 
        FirstSequence = mediaLastSequences.ElementAt(index + 1) + 1; 
        break; 
       } 
       else 
       { 
        //all files from the first, to this last sequence number, are found in this cabinet 
        FirstSequence = mediaLastSequences.ElementAt(lastIndex); 
        break; 
       } 
      } 
     } 

     //now we will look in the file table to get a vacant sequence number in the new cabinet (if available - first run will return empty, and thus default to FirstSequence) 
     int Sequence = FirstSequence; 
     query = "SELECT `Sequence` FROM `File` WHERE `Sequence` >= " + FirstSequence.ToString() + " AND `Sequence` <= " + LastSequence.ToString() + " ORDER BY `Sequence`"; 
     Console.WriteLine(query); 

     IList<int> SequencesInRange = pkg.ExecuteIntegerQuery(query); 
     for (int index = 0; index < SequencesInRange.Count; index++) 
     { 
      if (FirstSequence + index != SequencesInRange.ElementAt(index)) 
      { 
       Sequence = FirstSequence + index; 
       break; 
      } 
     } 

     //now we set this in the file table, to re-point this file to the new media.. 
     //File.FileName = FileNameInCabinet; 
     //File.FileSize = newFileSizeInBytes; 
     //File.Sequence = sequence; 
     query = "UPDATE `File` SET `File`.`FileName`='" + FileNameInCabinet + "' WHERE `File`='" + OriginalFileName + "'"; 
     Console.WriteLine(query); 
     pkg.Execute(query); 
     query = "UPDATE `File` SET `File`.`FileSize`=" + newFileSizeInBytes + " WHERE `File`='" + OriginalFileName + "'"; 
     Console.WriteLine(query); 
     pkg.Execute(query); 
     query = "UPDATE `File` SET `File`.`Sequence`=" + Sequence.ToString() + " WHERE `File`='" + OriginalFileName + "'"; 
     Console.WriteLine(query); 
     pkg.Execute(query); 
    }   
} 
} 

uso de demostración:

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 

namespace MSIFileManipulator 
{ 
class Program 
{ 
    static void Main(string[] args) 
    { 
     string workdir = @"C:\Users\Me\MyDevFolder\tests"; 
     string msiFile = "replace_test_copy.msi"; 
     string fileName = "REPLACE_THIS_IMAGE.png"; 

     List<string> filesToInclude = new List<string>(); 
     System.IO.FileInfo fileInfo = new System.IO.FileInfo(System.IO.Path.Combine(workdir, fileName)); 
     if (fileInfo.Exists) 
     { 
      Console.WriteLine("now adding: " + fileName + " to cabinet"); 
      filesToInclude.Add(fileName); 

      updateMSI myMSI = new updateMSI(); 
      string cabfileName = myMSI.createCabinetFileForMSI(workdir, filesToInclude); 
      Console.WriteLine("cabinet file saved as: " + cabfileName); 

      int diskID = myMSI.insertCabFileAsNewMediaInMSI(workdir, cabfileName, msiFile); 
      Console.WriteLine("new media added with disk ID: " + diskID.ToString()); 
      myMSI.PointAPreviouslyConfiguredComponentsFileToBeFetchedFromAnotherCabinet(fileName, fileName, fileInfo.Length.ToString(), diskID); 
      Console.WriteLine("Done"); 

     } 
     else 
     { 
      Console.WriteLine("Could not locate the replacement file:" + fileName); 
     } 
     Console.WriteLine("press any key to exit"); 
     Console.ReadKey(); 
    } 
} 
} 

soy consciente de que mi prueba no limpia después de que uno mismo ..

Cuestiones relacionadas