代码之家  ›  专栏  ›  技术社区  ›  si618

如何从MSI“文件”表中提取数据(文件计数)

  •  10
  • si618  · 技术社区  · 15 年前

    在我们的构建过程中,目前有可能将非基于代码的文件(如图像文件)添加到我们的web项目中,但不包括在WiX构建的MSI安装程序中。

    为了帮助防止这种情况,我想在WiX项目的后构建目标中执行以下操作:

    • 获取生成的所有文件的计数(web部署项目的输出)
    • 获取MSI中内置的所有文件的计数(来自MSI中的“文件”表)

    如果我启动Orca,我可以很容易地看到文件表和计数,但我不知道如何从MSBuild自动执行此操作。是否有某种API或其他机制可以从MSI获取此信息?

    我不介意编写自定义MSBuild任务来提取MSI文件表计数。

    4 回复  |  直到 15 年前
        1
  •  9
  •   Stein Åsmul    10 年前

    创建新的visual studio项目,添加对的引用 c:\windows\system32\msi.dll

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

    此代码使用 WindowsInstaller.Installer complete reference documentation .

    编辑 :显然,wix附带托管程序集(在 C:\program files\Windows Installer XML v3\sdk )它包装了msi.dll。我猜这就是罗布在回答中所说的“DTF”。使用Microsoft.Deployment.WindowsInstaller程序集和命名空间中的类型可以将代码示例简化为:

    var database = new Database(@"\path\to\some\file.msi");
    var list = database.ExecuteQuery("SELECT FileName FROM File");
    Console.WriteLine(list.Count);
    
        2
  •  5
  •   Rob Mensching    15 年前

    SELECT `File` FROM `File` 
    

    并计算返回的行数。集成到MSBuild任务中最简单的方法可能是使用WiX的DTF,它为所有MSIAPI提供托管包装器。

    一旦你把所有的工具都准备好了,这个解决方案就会非常简单。

        3
  •  4
  •   Stein Åsmul    10 年前

    这是自定义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;
        }
    }
    

    • MSBuildHelper.Log只是ITask.BuildEngine.LogMessageEvent的一个简单包装,用于捕获运行单元测试的NullReferenceException。
    • 还有改进的余地,例如使用ITaskItem代替字符串作为属性,使用正则表达式作为比较。

    下面是相应的单元测试,假设您 在复制到输出目录的测试项目中。

    [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);
            }
        }
    }
    
        4
  •  0
  •   cdonner    15 年前

    WinRAR将MSI标识为自解压CAB归档(在为其提供.rar扩展名后)。我想你可以把文件复制到某个地方,重命名它,用WinRAR解包,然后数一数文件。不过,这些文件将不会有其原始名称。

    This 似乎有点过时了,我不知道这是否有帮助。