检查 Visual Studio 安装项目





4.00/5 (3投票s)
本文提出了一种检查安装项目正确性的方法。
引言
Visual Studio 2010 在“文件/新建项目/已安装的模板/其他项目类型/安装和部署/Visual Studio 安装程序”下提供了“安装项目”模板,用于将文件和程序集打包到 Windows 安装程序包(.msi 文件)中。安装项目定义了包的名称、制造商和版本,文件和程序集的来源,它们的安装位置,需要进行的注册表修改以及应运行的自定义操作。安装项目的定义保存在扩展名为 vdproj 的文件中。该文件是一个 ASCII 文件,使用类似于 JSON 的语法。问题在于,当在 Visual Studio 中对安装项目进行修改时,Visual Studio 可能会做一些奇怪的事情。当比较新旧版本时,很难确定是否只进行了所需的更改,或者 Visual Studio 是否进行了任意更改。所提供的工具将 .vdproj 文件转换为 .p.xml 文件,该文件按字母顺序列出文件夹和文件。当比较 .p.xml 文件的新旧版本时,很容易确定是否只进行了所需的更改。
代码描述
该项目使用了 GitHub 上的 VDProjectXML 的代码。该项目将 .vdproj 文件一对一地转换为 .xml 文件,反之亦然。在这个项目中,该代码从 C# 转换为 VB.NET,并生成了一个额外的 XML 文件,该文件按字母顺序列出文件夹和文件。
项目
所提供的源包含两个文件夹
- VDProjXML:将 .vdproj 文件转换为两个 .xml 文件的工具
- .xml:.vdproj 的一对一表示
- .p.xml:一个 XML 文件,其中包含按字母顺序排列的文件夹和文件
- 演示
ClassLibrary1
:放入 GAC 的程序集,由ConsoleApplication1
使用ClassLibrary2
:放入 GAC 的程序集,由ConsoleApplication2
使用ConsoleApplication1
:使用ClassLibrary1
的控制台应用程序,打印“hello”消息ConsoleApplication2
:使用ClassLibrary1
的控制台应用程序,打印“hello”消息Setup1v1
:包含ClassLibrary1
和ConsoleApplication1
的安装项目版本 1Setup1v2
:前一个安装项目的版本 2,额外包含ClassLibrary2
和ConsoleApplication2
VDProj 语法
将安装项目文件转换为 XML 后,解释其语法会更容易。为了理解本文,您可能需要打开文件 Setup1v1\Setup1.xml。安装项目文件具有元素 DeployProject/Deployable/File
<deployproject>
...
<Deployable>
...
<File>
此元素包含每个文件的元素。例如,对于 ConsoleApplication1.exe
<_x007b_9f6f8455-1ef1-4b85-886a-4223bcc8e7f7_x007d__x003a__1d474339abbb4af1b32224edb2841e26>
<AssemblyRegister valueType="3" value="1" />
<AssemblyIsInGAC valueType="11" value="FALSE" />
<AssemblyAsmDisplayName valueType="8" value="ConsoleApplication1,
Version=1.0.0.0, Culture=neutral, processorArchitecture=x86" />
<ScatterAssemblies>
<_1D474339ABBB4AF1B32224EDB2841E26>
<Name valueType="8" value="ConsoleApplication1.exe" />
<Attributes valueType="3" value="512" />
</_1D474339ABBB4AF1B32224EDB2841E26>
</ScatterAssemblies>
<SourcePath valueType="8"
value="..\ConsoleApplication1\bin\Release\ConsoleApplication1.exe" />
<TargetName valueType="8" value="" />
<Tag valueType="8" value="" />
<Folder valueType="8" value="_4150DEBE0E5B4CF79CB533E65291044A" />
<Condition valueType="8" value="" />
<Transitive valueType="11" value="FALSE" />
<Vital valueType="11" value="TRUE" />
<ReadOnly valueType="11" value="FALSE" />
<Hidden valueType="11" value="FALSE" />
<System valueType="11" value="FALSE" />
<Permanent valueType="11" value="FALSE" />
<SharedLegacy valueType="11" value="FALSE" />
<PackageAs valueType="3" value="1" />
<Register valueType="3" value="1" />
<Exclude valueType="11" value="FALSE" />
<IsDependency valueType="11" value="FALSE" />
<IsolateTo valueType="8" value="" />
</_x007b_9f6f8455-1ef1-4b85-886a-4223bcc8e7f7_x007d__x003a__1d474339abbb4af1b32224edb2841e26>
此元素包含一个 element 文件夹,其值为“_4150DEBE0E5B4CF79CB533E65291044A
”。要找到此文件夹的名称,必须在 DeployProject/Deployable/Folder 元素下搜索以该值结尾的元素
<_x007b_3c67513d-01dd-4637-8a68-80971eb9504f_x007d__x003a__4150debe0e5b4cf79cb533e65291044a>
<DefaultLocation valueType="8"
value="[ProgramFilesFolder][Manufacturer]\[ProductName]" />
<Name valueType="8" value="#1925" />
<AlwaysCreate valueType="11" value="FALSE" />
<Condition valueType="8" value="" />
<Transitive valueType="11" value="FALSE" />
<Property valueType="8" value="TARGETDIR" />
<Folders> </Folders>
</_x007b_3c67513d-01dd-4637-8a68-80971eb9504f_x007d__x003a__4150debe0e5b4cf79cb533e65291044a>
这意味着 ConsoleApplication1.exe 安装在 TARGETDIR 文件夹中,默认情况下为 C:\Program Files\Manufacturer1\Setup1。Folder 元素包含一个 element Folder,它可能包含包含非空 Folders 元素的元素。因此,必须搜索整个层次结构才能找到所需的文件夹。您可以对 ClassLibrary1 进行相同的练习,您会发现它安装在 _9EDD3CBEB70C42E0ABBF0D0D28BBBC27 文件夹中,即 GAC 文件夹。
VDProjXML
VDProjXML
是将 vdproj 文件转换为 XML 文件的工具。它是一个只有一个模块的控制台应用程序。这里解释了这些方法。
Main
第一个命令行参数包含输入文件名,第二个可选命令行参数包含输出文件名。如果未提供第二个参数,则通过更改扩展名从输入文件名构造输出文件名。当输入文件名扩展名为“.vdproj”时,调用 ConvertVDProjToPXml
方法;当输入文件名扩展名为“.xml”时,调用 ConvertXmlVDProj
方法。
Select Case Path.GetExtension(inputFile).Trim().ToLowerInvariant()
Case ".vdproj"
ConvertVDProjToPXml(inputFile, outputFile)
Case ".xml"
ConvertXmlVDProj(inputFile, outputFile)
Case Else
Throw New Exception("Unknown file type: " & inputFile)
End Select
ConvertVDProjToPXml
此方法通过调用 ConvertVDProjToXmlString
方法构建 vdproj 文件的一对一 XML 表示并将其保存到文件中。然后调用 ConvertVDProjNodeToPXml
方法,生成一个按字母顺序列出文件的 XML 文档。此 XML 文档以 .p.xml 扩展名保存。
Dim xmlString As String = ConvertVDProjToXmlString(vdrpojFile)
File.WriteAllText(xmlFile, xmlString)
Console.WriteLine(xmlFile & " created")
Dim xdoc1 As New XmlDocument()
xdoc1.LoadXml(xmlString)
Dim xdoc2 As New XmlDocument()
Dim node2 As XmlElement = xdoc2.CreateElement(xdoc1.DocumentElement.Name)
xdoc2.AppendChild(node2)
ConvertVDProjNodeToPXml(xdoc1.DocumentElement, node2)
xmlFile = Path.ChangeExtension(xmlFile, ".p.xml")
xdoc2.Save(xmlFile)
ConvertVDProjToXmlString
此方法将 vdproj 文件一对一地转换为 XML。它是 GitHub 上的 VDProjectXML 中代码的 VB.NET 翻译。
ConvertVDProjNodeToPXml
此方法递归遍历 vdproj 文件中的所有元素并将它们复制到新的 XML 文档中。排除“Hierarchy
”、“File
”和“Folder
”元素。对于“Folder
”元素,通过调用 ConvertFolderList
进行特殊映射。
Dim excludeNodes As String() = {"Hierarchy", "File", "Folder"}
Dim xdoc2 As XmlDocument = XmlNodeGetDocument(containerNode2)
For Each node1 As XmlNode In containerNode1.ChildNodes
If Array.IndexOf(excludeNodes, node1.Name) < 0 Then
Dim node2 As XmlElement = xdoc2.CreateElement(node1.Name)
For Each attr1 As XmlAttribute In node1.Attributes
node2.SetAttribute(attr1.Name, attr1.Value)
Next
containerNode2.AppendChild(node2)
ConvertVDProjNodeToPXml(node1, node2)
ElseIf node1.Name = "Folder" Then
ConvertFolderList(containerNode1, containerNode2)
End If
Next
ConvertFolderList
ConvertFolderList
方法递归遍历 Folder
和 Folders
元素。对于找到的每个 Folder
元素,在新 XML 文档中构建一个 Folder
元素。通过截取最后一个下划线和名称末尾之间的部分,从元素名称中提取 folderId
。Folder
元素的“Name
”、“Property
”、“AlwaysCreate
”、“DefaultLocation
”元素具有“value
”属性。这些元素在新 Folder
元素中成为属性。在“File
”元素下的元素中搜索 folderId
。找到的文件元素按其 SourcePath
元素排序。在新 XML 文档中创建一个 File
元素并放置在 Folder
元素下。File
元素具有“SourcePath
”、“Exclude
”、“IsDependency
”等元素,它们在新 File
元素中成为属性。
Private Sub ConvertFolderList(folderContainerNode1 As XmlNode, folderContainerNode2 As XmlNode)
Dim folderPattern As String = "Folders/*"
If folderContainerNode1.Name = "Deployable" Then
folderPattern = "Folder/*"
End If
Dim folderList1 As XmlNodeList = folderContainerNode1.SelectNodes(folderPattern)
If folderList1.Count > 0 Then
Dim xdoc1 As XmlDocument = folderContainerNode1.OwnerDocument
Dim xdoc2 As XmlDocument = XmlNodeGetDocument(folderContainerNode2)
Dim foldersNode2 As XmlNode = folderContainerNode2.AppendChild(xdoc2.CreateElement("Folders"))
Dim folderEnumerable1 As System.Linq.IOrderedEnumerable(Of XmlNode) =
folderList1.Cast(Of XmlNode)().OrderBy(Function(n) _
n.SelectSingleNode("Name/@value").Value)
For Each folderNode1 As XmlNode In folderEnumerable1
Dim folderId1 As String = ExtractId(folderNode1.Name)
Dim folderNode2 As XmlElement = xdoc2.CreateElement("Folder")
folderNode2.SetAttribute("Id", folderId1)
CopyAttributes(folderNode1, folderNode2, _
{"Name", "Property", "AlwaysCreate", "DefaultLocation"})
foldersNode2.AppendChild(folderNode2)
Dim filePattern As String = _
"DeployProject/Deployable/File/*[Folder _
[@value=""" & folderId1 & """]]"
Dim fileList1 As XmlNodeList = xdoc1.SelectNodes(filePattern)
Dim fileEnumerable1 As System.Linq.IOrderedEnumerable(Of XmlNode) =
fileList1.Cast(Of XmlNode)().OrderBy(Function(n) _
n.SelectSingleNode("SourcePath/@value").Value)
For Each fileNode1 As XmlNode In fileEnumerable1
Dim sourcePath1 As String = _
fileNode1.SelectSingleNode("SourcePath/@value").Value
Dim fileNode2 As XmlElement = xdoc2.CreateElement("File")
CopyAttributes(fileNode1, fileNode2, _
{"SourcePath", "Exclude", "IsDependency"})
folderNode2.AppendChild(fileNode2)
Next
ConvertFolderList(folderNode1, folderNode2)
Next
End If
End Sub
ConvertXmlVDProj
此方法将 XML 文件一对一地转换为 vdproj 文件。它是 GitHub 上的 VDProjectXML 中代码的 VB.NET 翻译。
必备组件
该项目需要 Visual Studio 2010。更高版本不支持安装项目。
编译和启动
- 在 Visual Studio 中,打开项目 VDProjXml\VDProjXml.vbproj。
- 右键单击项目并点击“生成”。
- 验证 WinDiff 是否存在于“C:\Program Files\Microsoft SDKs\Windows\v7.0A\bin\WinDiff.Exe”位置。如果 WinDiff 存在于其他位置,请更改 CompareVDProjSetups.bat 和 CompareXmlSetups.bat 中的路径。
- 双击 Demo\CompareVDProjSetups.bat。这将比较安装项目 Setup1v1/Setup1.vdproj 和 Setup1v2/Setup1.vdproj 的两个版本。您将看到很难找出两个版本之间的区别。
- 双击 Demo\CompareXmlSetups.bat。这将把
Setup1v1
和Setup1v2
中的 vdproj 文件转换为 XML 文件,并比较生成的 Setup1v1\Setup1.p.xml 和 Setup1v2\Setup1.p.xml 文件。您将看到差异仅是两个附加文件和版本号。 - 为了确信安装项目文件是正确的
- 在 Visual Studio 中,打开解决方案 Demo\Demo.sln。
- 右键单击解决方案并点击“生成解决方案”。
- 在 Visual Studio 中,打开解决方案 Demo\Setup1v1\Setup1.sln。
- 右键单击解决方案并点击“生成”。
- 在 Visual Studio 中,打开项目 Demo\Setup1v2\Setup1.sln。
- 右键单击解决方案并点击“生成”。
- 双击 Demo\Setup1v1\Release\Setup1.exe
- 这将安装
Setup1
版本 1。 - 双击 C:\Program Files\Manufacturer1\Setup1\ConsoleApplication1.exe。
- 这将打开一个控制台窗口,其中包含以下文本
ConsoleApplication1 Hello Before ClassLibrary1.Class1.Sub1 After ClassLibrary1.Class1.Sub1 Press any key to exit
- 双击 Demo\Setup1v1\Release\Setup1.exe,然后选择移除。
- 双击 Demo\Setup1v2\Release\Setup1.exe。
- 这将安装
Setup1
版本 2。 - 双击 C:\Program Files\Manufacturer1\Setup1\ConsoleApplication2.exe。
- 这将打开一个控制台窗口,其中包含以下文本
ConsoleApplication2 Hello Before ClassLibrary2.Class1.Sub1 After ClassLibrary2.Class1.Sub1 Press any key to exit
- 双击 Demo\Setup1v2\Release\Setup1.exe,然后选择移除。
结论
每当对 .vdproj 文件进行更改时,可以运行工具 VDProjXML
来生成相应的 .p.xml 文件。这两个文件(.vdproj 和 .p.xml)都会被签入。下次对 .vdproj 文件进行更改时,可以使用 VDProjXML
工具生成新的 .p.xml 文件。比较旧的和新的 .p.xml 文件比比较旧的和新的 .vdproj 文件要容易得多。这可以揭示 Visual Studio 奇怪行为所造成的错误。
历史
- 将 .vdproj 转换为 .xml,反之亦然。将 .vdproj 转换为 .p.xml