2009-03-20 21 views
10

En nuestro proceso de compilación actualmente existe la posibilidad de que los archivos que no están basados ​​en código (como archivos de imagen) se agreguen a nuestro proyecto web, pero no se incluyen en el instalador MSI creado por WiX.Cómo extraer datos (conteo de archivos) de MSI "Archivo" Tabla

Para ayudar a prevenir esto, quiero llevar a cabo lo siguiente en el objetivo AfterBuild para nuestro proyecto de WiX:

  • obtener un conteo de todos los archivos construidos (salida del proyecto de implementación web)
  • Obtener un recuento de todos los archivos integrados en MSI (de la tabla "archivo" en MSI)
  • comparar los recuentos y fallan a construir si no coinciden

Si el fuego de la orca que pueda ver fácilmente la tabla de archivos y contar, pero No sé cómo automatizar esto desde MSBuild. ¿Hay alguna API u otro mecanismo para obtener esta información de una MSI?

No me importa escribir una tarea personalizada de MSBuild para extraer el conteo de la tabla de archivos MSI.

Respuesta

8

Crear un nuevo proyecto de Visual Studio, añadir una referencia a c:\windows\system32\msi.dll y utilizar el siguiente código para leer el número de archivos en un archivo MSI:

Type installerType = Type.GetTypeFromProgID("WindowsInstaller.Installer"); 
var installer = 
    (WindowsInstaller.Installer)Activator.CreateInstance(installerType); 
var msi = installer.OpenDatabase(@"path\to\some\file.msi", 0); 
var fileView = msi.OpenView("SELECT FileName FROM File"); 
fileView.Execute(null); 
int fileCount = 0; 
while (fileView.Fetch() != null) 
{ 
    fileCount++; 
} 
Console.WriteLine(fileCount); 

Este código utiliza el objeto WindowsInstaller.Installer COM, que es el punto de entrada para la API de automatización del instalador de Windows. Eche un vistazo al complete reference documentation.

edit: aparentemente wix viene con ensamblados administrados (en C:\program files\Windows Installer XML v3\sdk) que envuelven msi.dll. Creo que esto es a lo que Rob se refiere con "DTF" en su respuesta. El uso de los tipos en el montaje y espacio de nombres Microsoft.Deployment.WindowsInstaller simplificaría el ejemplo de código para esto:

var database = new Database(@"\path\to\some\file.msi"); 
var list = database.ExecuteQuery("SELECT FileName FROM File"); 
Console.WriteLine(list.Count); 
0

WinRAR identifica el MSI como un archivo CAB autoextraíble (después de darle una extensión .rar). Supongo que podría copiar el archivo en algún lugar, cambiarle el nombre, descomprimirlo con WinRAR y luego contar los archivos. Sin embargo, los archivos no tendrán sus nombres originales.

This parece un poco obsoleto y no sé si podría ser de alguna ayuda.

+0

no votaré esto abajo, ya que en realidad podría funcionar si WinRAR puede leer el archivo de almacenamiento estructurado COM (que es lo que es un archivo MSI), pero definitivamente no es la manera de tomar un número de archivos, mira en la respuesta de Rob Mensching en mi opinión. Si todo lo que desea es extraer los archivos, puede hacer una instalación administrativa desde un símbolo del sistema: setup.exe/a para un archivo exe, o msiexec/a YourMsiName.msi para un archivo MSI. –

4

Los archivos MSI son pequeñas bases de datos para bebés con un motor SQL personalizado. Solo necesita ejecutar la consulta:

SELECT `File` FROM `File` 

y contar el número de filas que regresan. La forma más sencilla de integrarse en una tarea de MSBuild probablemente sea usar el DTF de WiX, que proporciona envoltorios administrados para todas las API de MSI.

La solución será realmente simple una vez que obtenga todas las herramientas en su lugar.

+0

+1 Soy fan de wix pero nunca noté la bondad en la carpeta "C: \ Archivos de programa \ Windows Installer XML v3 \ sdk", agregué una muestra en mi propia respuesta –

+0

Gracias Rob! Marqué la respuesta de wcoenen como correcta simplemente porque agregó la muestra del código. Sería bueno marcar ambos como correctos, pero obtuviste mi +1. – si618

4

Puesto que hay múltiples maneras en que podría poner en práctica esto, estoy respondiendo a mi propia pregunta con los resultados que' Ahora estoy usando gracias a las respuestas de wcoenen y Rob.

Esta es la costumbre tarea MSBuild:

public class VerifyMsiFileCount : Task 
{ 
    [Required] 
    public string MsiFile { get; set; } 

    [Required] 
    public string Directory { get; set; } 

    public override bool Execute() 
    { 
     Database database = new Database(MsiFile, DatabaseOpenMode.ReadOnly); 
     IList msiFiles = database.ExecuteQuery("SELECT FileName FROM File", new Record(0)); 
     IList<string> files = new List<string>(
      System.IO.Directory.GetFiles(Directory, "*", SearchOption.AllDirectories)); 
     return compareContents(msiFiles, files); 
    } 

    bool compareContents(IList msiFiles, IList<string> files) 
    { 
     // Always false if count mismatch, but helpful to know which file(s) are missing 
     bool result = msiFiles.Count == files.Count; 

     StringBuilder sb = new StringBuilder(msiFiles.Count); 
     foreach (string msiFile in msiFiles) 
     { 
      sb.AppendLine(msiFile.ToUpper()); 
     } 
     string allMsiFiles = sb.ToString(); 

     // Could be optimized using regex - each non-matched line in allMsiFiles 
     string filename; 
     foreach (string file in files) 
     { 
      filename = file.ToUpper(); 
      // Strip directory as File table in MSI does funky things with directory prefixing 
      if (filename.Contains(Path.DirectorySeparatorChar.ToString())) 
      { 
       filename = filename.Substring(file.LastIndexOf(Path.DirectorySeparatorChar) + 1); 
      } 
      if (!allMsiFiles.Contains(filename)) 
      { 
       result = false; 
       MSBuildHelper.Log(this, file + " appears to be missing from MSI File table", 
        MessageImportance.High); 
      } 
     } 
     return result; 
    } 
} 

Un par de cosas a tener en cuenta:

  • He dejado de lado la documentación por razones de brevedad.
  • MSBuildHelper.Log es simplemente un contenedor simple para ITask.BuildEngine.LogMessageEvent para capturar pruebas de unidad de funcionamiento NullReferenceException.
  • Todavía se puede mejorar, como p. utilizando ITaskItem en lugar de cadena para propiedades, expresiones regulares para comparación.
  • La lógica de comparación puede parecer un poco extraña, pero la tabla de archivos hace algunas cosas funky con el prefijo de directorio, y también quería evitar el caso límite donde se puede eliminar un archivo y agregar un nuevo archivo, por lo que el conteo de archivos contenidos correctos pero el msi están equivocados :)

Estas son las pruebas unitarias correspondientes, suposición es que tienes Test.msi en su proyecto de prueba que se copia en el directorio de salida.

[TestFixture] 
public class VerifyMsiFileCountFixture 
{ 
    VerifyMsiFileCount verify; 

    [SetUp] 
    public void Setup() 
    { 
     verify = new VerifyMsiFileCount(); 
    } 

    [Test] 
    [ExpectedException(typeof(InstallerException))] 
    public void Execute_ThrowsInstallerException_InvalidMsiFilePath() 
    { 
     verify.Directory = Environment.CurrentDirectory; 
     verify.MsiFile = "Bogus"; 
     verify.Execute(); 
    } 

    [Test] 
    [ExpectedException(typeof(DirectoryNotFoundException))] 
    public void Execute_ThrowsDirectoryNotFoundException_InvalidDirectoryPath() 
    { 
     verify.Directory = "Bogus"; 
     verify.MsiFile = "Test.msi"; 
     verify.Execute(); 
    } 

    [Test] 
    public void Execute_ReturnsTrue_ValidDirectoryAndFile() 
    { 
     string directory = Path.Combine(Environment.CurrentDirectory, "Temp"); 
     string file = Path.Combine(directory, "Test.txt"); 
     Directory.CreateDirectory(directory); 
     File.WriteAllText(file, "Temp"); 
     try 
     { 
      verify.Directory = directory; 
      verify.MsiFile = "Test.msi"; 
      Assert.IsTrue(verify.Execute()); 
     } 
     finally 
     { 
      File.Delete(file); 
      Directory.Delete(directory); 
     } 
    } 

    [Test] 
    public void Execute_ReturnsFalse_NoFileDefined() 
    { 
     string directory = Path.Combine(Environment.CurrentDirectory, "Temp"); 
     Directory.CreateDirectory(directory); 
     try 
     { 
      verify.Directory = directory; 
      verify.MsiFile = "Test.msi"; 
      Assert.IsFalse(verify.Execute()); 
     } 
     finally 
     { 
      Directory.Delete(directory); 
     } 
    } 

    [Test] 
    public void Execute_ReturnsFalse_IncorrectFilename() 
    { 
     string directory = Path.Combine(Environment.CurrentDirectory, "Temp"); 
     string file = Path.Combine(directory, "Bogus.txt"); 
     Directory.CreateDirectory(directory); 
     File.WriteAllText(file, "Temp"); 
     try 
     { 
      verify.Directory = directory; 
      verify.MsiFile = "Test.msi"; 
      Assert.IsFalse(verify.Execute()); 
     } 
     finally 
     { 
      File.Delete(file); 
      Directory.Delete(directory); 
     } 
    } 

    [Test] 
    public void Execute_ReturnsFalse_ExtraFileDefined() 
    { 
     string directory = Path.Combine(Environment.CurrentDirectory, "Temp"); 
     string file1 = Path.Combine(directory, "Test.txt"); 
     string file2 = Path.Combine(directory, "Bogus.txt"); 
     Directory.CreateDirectory(directory); 
     File.WriteAllText(file1, "Temp"); 
     File.WriteAllText(file2, "Temp"); 
     try 
     { 
      verify.Directory = directory; 
      verify.MsiFile = "Test.msi"; 
      Assert.IsFalse(verify.Execute()); 
     } 
     finally 
     { 
      File.Delete(file1); 
      File.Delete(file2); 
      Directory.Delete(directory); 
     } 
    } 
} 
Cuestiones relacionadas