如何从 MSI“文件”中提取数据(文件计数) 桌子
在我们的构建过程中,当前有可能将基于非代码的文件(例如图像文件)添加到我们的 Web 项目中,但不包含在 WiX 构建的 MSI 安装程序中。
为了帮助防止这种情况,我想在 WiX 项目的 AfterBuild 目标中执行以下操作:
- 获取构建的所有文件的计数(来自 Web 部署项目的输出)
- 获取构建到 MSI 中的所有文件的计数(来自“文件”表) MSI)
- 比较计数,如果不匹配则构建失败
如果我启动 Orca,我可以轻松查看文件表和计数,但我不知道如何从 MSBuild 自动化此操作。 是否有一些 API 或其他机制可以从 MSI 中获取此信息?
我不介意编写自定义 MSBuild 任务来提取 MSI 文件表计数。
In our build process there is currently the potential for non-code based files (such as image files) to be added to our web project, but not included in the MSI installer built by WiX.
To help prevent this, I want to perform the following in the AfterBuild target for our WiX project:
- Get a count of all files built (output from web deployment project)
- Get a count of all files built into MSI (from "File" table in MSI)
- Compare counts and fail build if they don't match
If I fire up Orca I can easily see the File table and count, but I don't know how to automate this from MSBuild. Is there some API or other mechanism to get this information out of an MSI?
I don't mind writing a custom MSBuild task to extract the MSI File table count.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
发布评论
评论(4)
由于有多种方法可以实现此目的,因此我正在用我现在使用的结果来回答我自己的问题,这要感谢 wcoenen 和 Rob 的回答。
这是自定义 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 而不是字符串作为属性,使用正则表达式进行比较。
- 比较逻辑可能看起来有点奇怪,但是文件表在目录前缀方面做了一些有趣的事情,而且我还想避免可能删除文件并添加新文件的边缘情况,因此文件计数是正确的,但msi 内容错误:)
以下是相应的单元测试,假设您的测试项目中有 Test.msi ,并将其复制到输出目录。
[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);
}
}
}
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
创建一个新的 Visual Studio 项目,添加对
c:\windows\system32\msi.dll
的引用,并使用以下代码读取 msi 文件中的文件数量:此代码使用
WindowsInstaller.Installer
COM 对象,它是 Windows 安装程序自动化 API 的入口点。 请查看完整参考文档。编辑:显然wix附带了托管程序集(在
C:\program files\Windows Installer XML v3\sdk
中),它包装了msi.dll。 我想这就是 Rob 在他的回答中所说的“DTF”。 使用 Microsoft.Deployment.WindowsInstaller 程序集和命名空间中的类型会将代码示例简化为:Create a new visual studio project, add a reference to
c:\windows\system32\msi.dll
and use the following code to read the number of files in a msi file:This code uses the
WindowsInstaller.Installer
COM object, which is the entry-point for the windows installer automation API. Take a look at the complete reference documentation.edit: apparently wix comes with managed assemblies (in
C:\program files\Windows Installer XML v3\sdk
) which wrap msi.dll. I guess this is what Rob is referring to by "DTF" in his answer. Using the types in the Microsoft.Deployment.WindowsInstaller assembly and namespace would simplify the code sample to this: