2009-03-20 19 views
10

In unserem Buildprozess besteht derzeit die Möglichkeit, dass nicht codebasierte Dateien (z. B. Bilddateien) zu unserem Webprojekt hinzugefügt, jedoch nicht darin enthalten sind das von WiX erstellte MSI-Installationsprogramm.So extrahieren Sie Daten (Dateianzahl) aus MSI "Datei" Tabelle

Um dies zu verhindern, habe ich folgendes in der Afterbuild Ziel für unsere WiX Projekt durchführen möchten:

  • Holen Sie sich eine Anzahl aller Dateien erstellt (Ausgabe von Web-Deployment-Projekt)
  • Erhalten Sie einen Zähler ich kann die Dateitabelle leicht erkennen und zählen, alle Dateien in MSI (aus „Datei“ Tabelle in MSI) gebaut
  • zählen vergleichen und nicht bauen, wenn sie

wenn ich Orca Feuer nicht übereinstimmen aber Ich weiß nicht, wie man das von MSBuild automatisiert. Gibt es eine API oder einen anderen Mechanismus, um diese Informationen aus einem MSI herauszuholen?

Es macht mir nichts aus, einen benutzerdefinierten MSBuild-Task zu schreiben, um die Anzahl der MSI-Dateitabellen zu extrahieren.

Antwort

8

Erstellen Sie ein neues Visual Studio-Projekt einen Verweis auf c:\windows\system32\msi.dll hinzufügen und verwenden Sie den folgenden Code, um die Anzahl der Dateien in einer msi-Datei zu lesen:

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); 

Dieser Code verwendet die WindowsInstaller.Installer COM-Objekt, das das ist Einstiegspunkt für die Windows Installer Automation API. Werfen Sie einen Blick auf die complete reference documentation.

bearbeiten: anscheinend wix kommt mit verwalteten Assembly (in C:\program files\Windows Installer XML v3\sdk), die Wickel msi.dll. Ich schätze, darauf bezieht sich Rob in seiner Antwort von "DTF". Mit den Typen in der Microsoft.Deployment.WindowsInstaller Montage und Namensraum vereinfachen würde das Codebeispiel dazu:

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

WinRAR identifiziert das MSI als selbstextrahierendes CAB-Archiv (nachdem es die Erweiterung .rar erhalten hat). Ich nehme an, Sie könnten die Datei irgendwo kopieren, umbenennen, mit WinRAR entpacken und dann die Dateien zählen. Die Dateien haben jedoch nicht ihre ursprünglichen Namen.

This scheint ein bisschen veraltet und ich weiß nicht, ob es eine Hilfe sein könnte.

+0

ich das nicht überstimmen werden, da es tatsächlich funktionieren kann, wenn WinRAR COM strukturierte Speicherdatei lesen kann (die eine MSI-Datei ist das, was), aber es ist definitiv nicht der Weg, um eine Dateianzahl zu nehmen, schauen bei Rob Menschings Antwort meiner Meinung nach. Wenn Sie nur die Dateien extrahieren möchten, können Sie eine Admin-Installation über eine Eingabeaufforderung ausführen: setup.exe/a für eine EXE-Datei oder msiexec/a YourMsiName.msi für eine MSI-Datei. –

4

MSI-Dateien sind kleine Baby-Datenbanken mit einer benutzerdefinierten SQL-Engine. Sie müssen nur die Abfrage ausführen:

SELECT `File` FROM `File` 

und zählen Sie die Anzahl der Zeilen, die zurückkommen. Der einfachste Weg zur Integration in einen MSBuild-Task wäre wahrscheinlich die Verwendung von WiXs DTF, das verwaltete Wrapper für alle MSI-APIs bereitstellt.

Die Lösung wird wirklich einfach sein, sobald Sie alle Werkzeuge an Ort und Stelle haben.

+0

+1 Ich bin ein Wix-Fan aber nie bemerkt, die Güte in der "C: \ Programme \ Windows Installer XML v3 \ sdk" Ordner, ich habe ein Beispiel in meiner eigenen Antwort hinzugefügt –

+0

Vielen Dank Rob! Ich habe wcoenens Antwort als korrekt markiert, weil er das Codebeispiel hinzugefügt hat. Es wäre schön, beide als richtig zu markieren, aber du hast meine +1. – si618

4

Da gibt es mehrere Möglichkeiten, dies implementieren könnte, ich bin meine eigene Frage mit den Ergebnissen der Beantwortung ich m jetzt dank den Antworten von Wcoenen und Rob.

Dies ist die benutzerdefinierte MSBuild-Aufgabe:

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; 
    } 
} 

paar Dinge zu beachten:

  • Ich habe Kürze Dokumentation weggelassen.
  • MSBuildHelper.Log ist nur ein einfacher Wrapper für ITask.BuildEngine.LogMessageEvent, um NullReferenceException laufende Unit-Tests abzufangen.
  • Noch Raum für Verbesserungen, z.B. Verwenden von ITaskItem anstelle von String für Eigenschaften, Regex zum Vergleich.
  • Die Vergleichslogik mag ein wenig komisch aussehen, aber die Dateitabelle macht funky Sachen mit Verzeichnispräfix, und ich wollte auch den Randfall vermeiden, wo eine Datei gelöscht und eine neue Datei hinzugefügt werden kann, also die Anzahl der Dateien richtig, aber die msi Inhalt falsch sind :)

Hier werden die entsprechenden Unit-Tests sind Voraussetzung ist, dass Sie Test.msi in Ihrem Testprojekt haben, die in das Ausgabeverzeichnis kopiert wird.

[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); 
     } 
    } 
} 
Verwandte Themen