65.9K
CodeProject 正在变化。 阅读更多。
Home

检查 Visual Studio 安装项目

starIconstarIconstarIconstarIconemptyStarIcon

4.00/5 (3投票s)

2015年1月8日

CPOL

6分钟阅读

viewsIcon

21084

downloadIcon

185

本文提出了一种检查安装项目正确性的方法。

引言

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 文件,该文件按字母顺序列出文件夹和文件。

项目

所提供的源包含两个文件夹

  1. VDProjXML:将 .vdproj 文件转换为两个 .xml 文件的工具
    1. .xml.vdproj 的一对一表示
    2. .p.xml:一个 XML 文件,其中包含按字母顺序排列的文件夹和文件
  2. 演示
    1. ClassLibrary1:放入 GAC 的程序集,由 ConsoleApplication1 使用
    2. ClassLibrary2:放入 GAC 的程序集,由 ConsoleApplication2 使用
    3. ConsoleApplication1:使用 ClassLibrary1 的控制台应用程序,打印“hello”消息
    4. ConsoleApplication2:使用 ClassLibrary1 的控制台应用程序,打印“hello”消息
    5. Setup1v1:包含 ClassLibrary1ConsoleApplication1 的安装项目版本 1
    6. Setup1v2:前一个安装项目的版本 2,额外包含 ClassLibrary2ConsoleApplication2

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\Setup1Folder 元素包含一个 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 方法递归遍历 FolderFolders 元素。对于找到的每个 Folder 元素,在新 XML 文档中构建一个 Folder 元素。通过截取最后一个下划线和名称末尾之间的部分,从元素名称中提取 folderIdFolder 元素的“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。更高版本不支持安装项目。

编译和启动

  1. 在 Visual Studio 中,打开项目 VDProjXml\VDProjXml.vbproj
  2. 右键单击项目并点击“生成”。
  3. 验证 WinDiff 是否存在于“C:\Program Files\Microsoft SDKs\Windows\v7.0A\bin\WinDiff.Exe”位置。如果 WinDiff 存在于其他位置,请更改 CompareVDProjSetups.batCompareXmlSetups.bat 中的路径。
  4. 双击 Demo\CompareVDProjSetups.bat。这将比较安装项目 Setup1v1/Setup1.vdprojSetup1v2/Setup1.vdproj 的两个版本。您将看到很难找出两个版本之间的区别。

  5. 双击 Demo\CompareXmlSetups.bat。这将把 Setup1v1Setup1v2 中的 vdproj 文件转换为 XML 文件,并比较生成的 Setup1v1\Setup1.p.xmlSetup1v2\Setup1.p.xml 文件。您将看到差异仅是两个附加文件和版本号。

  6. 为了确信安装项目文件是正确的
    1. 在 Visual Studio 中,打开解决方案 Demo\Demo.sln
    2. 右键单击解决方案并点击“生成解决方案”。
    3. 在 Visual Studio 中,打开解决方案 Demo\Setup1v1\Setup1.sln
    4. 右键单击解决方案并点击“生成”。
    5. 在 Visual Studio 中,打开项目 Demo\Setup1v2\Setup1.sln
    6. 右键单击解决方案并点击“生成”。
    7. 双击 Demo\Setup1v1\Release\Setup1.exe
    8. 这将安装 Setup1 版本 1。
    9. 双击 C:\Program Files\Manufacturer1\Setup1\ConsoleApplication1.exe
    10. 这将打开一个控制台窗口,其中包含以下文本
      ConsoleApplication1 Hello
      Before ClassLibrary1.Class1.Sub1
      After ClassLibrary1.Class1.Sub1
      Press any key to exit
    11. 双击 Demo\Setup1v1\Release\Setup1.exe,然后选择移除
    12. 双击 Demo\Setup1v2\Release\Setup1.exe。
    13. 这将安装 Setup1 版本 2。
    14. 双击 C:\Program Files\Manufacturer1\Setup1\ConsoleApplication2.exe
    15. 这将打开一个控制台窗口,其中包含以下文本
      ConsoleApplication2 Hello
      Before ClassLibrary2.Class1.Sub1
      After ClassLibrary2.Class1.Sub1
      Press any key to exit
    16. 双击 Demo\Setup1v2\Release\Setup1.exe,然后选择移除

结论

每当对 .vdproj 文件进行更改时,可以运行工具 VDProjXML 来生成相应的 .p.xml 文件。这两个文件(.vdproj.p.xml)都会被签入。下次对 .vdproj 文件进行更改时,可以使用 VDProjXML 工具生成新的 .p.xml 文件。比较旧的和新的 .p.xml 文件比比较旧的和新的 .vdproj 文件要容易得多。这可以揭示 Visual Studio 奇怪行为所造成的错误。

历史

  1. .vdproj 转换为 .xml,反之亦然。将 .vdproj 转换为 .p.xml
© . All rights reserved.