2009-03-20 9 views
10

Nel nostro processo di generazione esiste attualmente il potenziale per file non basati su codice (come file immagine) da aggiungere al nostro progetto web, ma non inclusi in l'installer MSI costruito da WiX.Come estrarre i dati (numero di file) da MSI "File" Tabella

Per evitare questo, voglio eseguire le seguenti operazioni nel target AfterBuild per il nostro progetto WiX:

  • ottenere un conteggio di tutti i file incorporati (in uscita dal progetto di distribuzione web)
  • ottenere un conteggio di tutti i file incorporati MSI (dalla tabella "file" nel MSI)
  • Confronta i conteggi e non riescono a costruire se non corrispondono

Se apro l'Orca posso facilmente vedere la tabella file e contare, ma Non so come automatizzarlo da MSBuild. C'è qualche API o altro meccanismo per ottenere queste informazioni da un MSI?

Non mi interessa scrivere un'attività MSBuild personalizzata per estrarre il conteggio delle tabelle file MSI.

risposta

8

Creare un nuovo progetto di Visual Studio, aggiungere un riferimento a c:\windows\system32\msi.dll e utilizzare il seguente codice per leggere il numero di file in un file 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); 

Questo codice utilizza l'oggetto WindowsInstaller.Installer COM, che è il entry-point per l'API di automazione di Windows Installer. Dai uno sguardo allo complete reference documentation.

modifica: apparentemente wix viene fornito con assembly gestiti (in C:\program files\Windows Installer XML v3\sdk) che racchiudono msi.dll. Credo che questo sia ciò a cui Rob si riferisce con "DTF" nella sua risposta. Utilizzando i tipi nell'assemblea Microsoft.Deployment.WindowsInstaller e namespace semplificherebbe il codice di esempio per questo:

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

WinRAR identifica l'MSI come un archivio CAB autoestraente (dopo aver fornito un'estensione .rar). Suppongo che potresti copiare il file da qualche parte, rinominarlo, decomprimerlo con WinRAR, quindi contare i file. I file non avranno i loro nomi originali, però.

This sembra un po 'obsoleto e non so se potrebbe essere di alcun aiuto.

+0

Non voterò questo giù dal momento che potrebbe effettivamente funzionare se WinRAR in grado di leggere file di archiviazione strutturata COM (che è ciò che un file MSI è), ma non è sicuramente il modo di fare un conteggio dei file, guardo alla risposta di Rob Mensching secondo me. Se tutto ciò che si desidera è estrarre i file, è possibile eseguire un'installazione di amministrazione da un prompt dei comandi: setup.exe/a per un file exe o msiexec/a YourMsiName.msi per un file MSI. –

4

I file MSI sono piccoli database figlio con un motore SQL personalizzato. Hai solo bisogno di eseguire la query:

SELECT `File` FROM `File` 

e contare il numero di righe che ritorna. Il modo più semplice per integrare un'attività MSBuild è probabilmente quello di utilizzare il DTF di WiX che fornisce wrapper gestiti per tutte le API MSI.

La soluzione sarà davvero semplice una volta che tutti gli strumenti sono stati installati.

+0

+1 Sono un fan di wix ma non ho mai notato la bontà nella cartella "C: \ Programmi \ Windows Installer XML v3 \ sdk", ho aggiunto un esempio nella mia risposta –

+0

Grazie Rob! Ho contrassegnato la risposta di wcoenen come corretta semplicemente perché ha aggiunto il codice di esempio. Sarebbe bello contrassegnare entrambi come corretto, ma hai il mio +1. – si618

4

Dal momento che ci sono diversi modi si potrebbe implementare questo, sto rispondendo alla mia domanda con i risultati che ho' Ora sto usando grazie alle risposte di Wcoenen e Rob.

Questa è l'usanza compito 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; 
    } 
} 

paio di cose da notare:

  • ho lasciato fuori la documentazione per brevità.
  • MSBuildHelper.Log è solo un semplice wrapper per ITask.BuildEngine.LogMessageEvent per rilevare i test delle unità in esecuzione NullReferenceException.
  • Ancora margini di miglioramento, ad es. utilizzando ITaskItem anziché string per le proprietà, regex per il confronto.
  • La logica di confronto può sembrare un po 'strana, ma la tabella File fa alcuni elementi funky con il prefisso di directory, e volevo anche evitare il caso limite in cui un file può essere eliminato e un nuovo file aggiunto, quindi il conteggio dei file è contenuti corretti ma MSI sono sbagliato :)

Qui ci sono i test di unità corrispondenti, presupposto è che devi Test.msi nel vostro progetto di test che viene copiato nella directory di output.

[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); 
     } 
    } 
} 
Problemi correlati