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

WIX SSRS 自定义程序集安装程序

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.71/5 (4投票s)

2009 年 7 月 9 日

CPOL

11分钟阅读

viewsIcon

43549

downloadIcon

385

使用 Windows Installer XML (WIX) 项目将自定义程序集安装到 Microsoft SQL Server Reporting Services (SSRS)。

引言

本文将讨论我在构建 Windows Installer XML (WIX) 项目以将自定义程序集安装到 Microsoft SQL Server Reporting Services (SSRS) 时遇到的危险和陷阱。此 WIX 安装程序包含安装、修复、删除和升级 SSRS 自定义程序集多个实例到 SSRS 实例的功能。安装程序将检测计算机上找到的所有 SSRS 实例,并提供一个对话框以允许选择一个 SSRS 实例。此选择将导致安装程序将所需的自定义程序集放置到 SSRS 实例的应用程序 bin 目录中,并修改 SSRS 的安全配置文件以允许自定义程序集的正确执行权限。

背景

自定义程序集是可以从 SSRS 报表调用的 DLL。由于一台计算机可能有多个 SSRS 实例,因此需要一个安装程序来定位每个 SSRS 实例。此外,可能需要在两个不同的 SSRS 实例(生产/测试)中使用两个不同版本的自定义程序集。

在此示例中,要安装的自定义程序集恰好是一个互操作程序集(一个 .NET 程序集,它是 ActiveX / COM DLL 的包装器)。这会带来一些独特的安全问题,因为自定义程序集需要在 rssrvpolicy.config 中配置适当的安全权限才能正常执行。

参考资料

DummyInterop 项目

这是一个 makefile 项目,它创建一个版本化的 .NET 互操作程序集,该程序集包装 SSRS 报表将使用的 ActiveX / COM 对象。

.NET 互操作程序集 DLL 是通过在 ActiveX 或 COM DLL 上使用类型库导入 (tlbimp) 命令创建的。这不会对 DLL 进行版本控制,因此需要将 DLL 反汇编为中间语言,并用版本化的资源文件重新汇编。为了演示目的,我使用了 IEFrame COM / ActiveX 控件。

tlbimp ieframe.dll /out:%1\DummyInterop.dll
ildasm /nobar %1\DummyInterop.dll /out=%1\DummyInterop.il
rc.exe /r /fo %1\DummyInterop.res DummyInterop.rc
ilasm %1\DummyInterop.il /OUTPUT=%1\DummyInterop.dll /RESOURCE=%1\DummyInterop.res /DLL

CA_RSInstances 项目

一个 C# DLL - 添加为新项目(WIX / C# 自定义操作项目)。该项目将创建两个 DLL。CA_RSInstances.dll 是一个 .NET 程序集,CA_RSInstances.CA.dll 是一个 C DLL,它封装了 .NET DLL,从而可以通过 WIX 自定义操作使用它。

Custom Action Project 必须引用 Microsoft.Deployment.WindowsInstaller

对于每个要从 MSI 调用的自定义操作,在函数声明之前必须存在声明性语句 [CustomAction]。自定义操作函数将传递一个 Session 对象,该对象包含安装数据库信息。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Deployment.WindowsInstaller;
using Microsoft.Win32;

namespace CA_RSInstances
{
  public class CustomActions
  {
    [CustomAction]
    public static ActionResult CAFillRSInstanceListbox(Session session)
    {
      ......
    }

CA_RSInstances DLL 包含两个自定义操作

  • CAFillRSInstanceListbox 将填充一个 SSRS 实例列表框(包括已安装的版本号),并在 MSI 数据库中创建一个自定义表,该表将在选择一个实例进行安装时使用。该函数枚举注册表的 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\RS 来查找所有已安装的 SSRS 实例。它还查询注册表以获取版本和安装 GUID,以确定此 SSRS 实例是否已安装自定义程序集版本。
  • 可以使用 Session 对象访问或更新安装属性

    string ProductVersion=session["ProductVersion"];

    可以使用 View 对象访问或更新安装表(列表框表和自定义表)

    View listBoxView = session.Database.OpenView("select * from ListBox");
    Record newListBoxRecord = new Record(4);
    newListBoxRecord[1] = "RSINSTANCE";
    newListBoxRecord[2] = order++;
    newListBoxRecord[3] = Instance.Instance;
    newListBoxRecord[4] = Instance.Name;
    listBoxView.Modify(ViewModifyMode.InsertTemporary, newListBoxRecord);
     
    View availableRSView = session.Database.OpenView(
         "select * from AvailableRSInstances");
    Record newRSRecord = new Record(5);
    newRSRecord[1] = Instance.Instance;
    newRSRecord[2] = Instance.Name;
    newRSRecord[3] = Instance.FullPath;
    newRSRecord[4] = Instance.Version;
    newRSRecord[5] = Instance.InstalledGUID;
    availableRSView.Modify(ViewModifyMode.InsertTemporary, newRSRecord);
  • UpdatePropsWithSelectedRSInstance 将根据所选实例查询自定义表以检索路径、版本和已安装 GUID。基于这些信息,需要设置各种 MSI 属性,以支持安装多个实例/升级现有已安装实例/防止降级到旧版本。
  • View availableRSView = session.Database.OpenView(
      "Select * from AvailableRSInstances where Instance='" + RSInstance+"'");
    availableRSView.Execute();
    Record record = availableRSView.Fetch();
    if ((record[1].ToString()) == RSInstance)
    {
      RSInstance RS = new RSInstance();
      RS.Instance = RSInstance;
      RS.Name = (string)record[2];
      RS.FullPath = (string)record[3];
      RS.Version = (string)record[4];
      RS.InstalledGUID = (string)record[5];

    要分配/清除的 MSI 属性

    • TRANSFORMS -- 这会告知 MSI 的 ExecuteSequence 应用指定的转换实例。
    • INSTALLDIR -- 用于放置自定义程序集的目录。
    • ProductName -- 在选择实例后用于显示产品的属性。
    • Installed - 用于确定自定义程序集是否已在此实例上安装的属性。如果已安装且不是升级或降级,MSI 将进入维护对话框以修复/卸载此实例。
    • MSINEWINSTANCE - 用于强制 MSI 创建新的安装/升级实例的属性。
    • NEWERPRODUCTFOUND - 当所选实例已安装较新版本时使用的属性。设置此值将防止降级。
    • UPGRADEFOUND - 当所选实例已安装但版本较旧需要升级时使用的属性。
    • MIGRATE - 该属性与 UPGRADEFOUND 结合使用,以将当前已安装的实例迁移到新版本。

InstallReportingServicesExtensions 项目

WIX 项目将利用 CA_RSInstances 自定义操作,并将 DummyInterop.dll 安装到所选的 SSRS 实例位置。

WIX 使用 XML 标签来定义 MSI 安装程序内的所有功能。我将回顾其中一些标签及其含义

  • <? ?> 用于定义一个预编译器宏,该宏随后可以使用 $(var.xxx) 语法进行展开
  • <?define MainProductCode = "{d4e3d961-60db-11de-8a39-0800200c9a66}"?>
    <Product Id="$(var.MainProductCode)" ...>
  • <Product 是定义 MSI 整体内容的最顶层标签。它使用以下属性
    • Id - 一个唯一的 GUID。升级版本时,应更改 GUID 以强制进行主要升级。
    • Language - 产品的十进制语言 ID (LCID)。
    • Version - 产品版本字符串(###.###.###.###)格式。
    • UpgradeCode - 一个唯一的 GUID。对于所有版本,此 GUID 必须保持不变。它用于确定是否已安装另一个产品且版本不同。
  • <Package 设置有关安装包的属性。
    • Id - 一个唯一的 GUID。升级版本时,应更改 GUID 以强制进行主要升级。
    • InstallerVersion - 安装此包所需的最低 Windows Installer 版本。
    • Compressed - 确定包中的文件是否被压缩。
    • InstallScope - perMachineperUser 安装。
  • <Upgrade 描述了当找到现有安装时安装的行为。它定义了 FindRelatedProducts 安装步骤将设置哪些属性。
  • <Upgrade Id="$(var.UpgradeCode_GUID)">
    
      <!-- Populate NEWERPRODUCTFOUND if there is an installed 
           package with the same upgrade code
           and version is > the version being installed -->
    
      <UpgradeVersion 
        Minimum="$(var.ProductVersion)" 
        IncludeMinimum="no" 
        OnlyDetect="yes" Language="1033" 
        Property="NEWERPRODUCTFOUND" />
    
      <!-- Populate UPGRADEFOUND if there is an installed 
           package with the same upgrade code
           and the version is between the earliest version defined
           and the version being installed -->
    
      <UpgradeVersion 
        Minimum="$(var.FirstProductVersion)" 
        IncludeMinimum="yes" 
        Maximum="$(var.ProductVersion)" 
        IncludeMaximum="no" 
        Language="1033" 
        Property="UPGRADEFOUND" />
    
    </Upgrade>
  • <Condition - 用于强制在安装前必须满足的条件。还定义了 UI 控件的条件和要安装的组件的条件。
  • <!-- Condition will fail with message if the user is not 
         a Privileged (admin) user-->
    <Condition Message="You need to be an administrator to install this product.">
      Privileged
    </Condition>
  • <CustomTable - 定义一个可供自定义操作使用的自定义表。
  • <!-- Custom Table is used by the RSInstanceCustomAction to populate 
         information regarding each Reporting Services Instance on the machine, 
         and it's versions of installed extensions -->
    
    <CustomTable Id="AvailableRSInstances">
      <Column Id="Instance" Category="Text" PrimaryKey="yes" Type="string" />
      <Column Id="Name" Category="Text" PrimaryKey="no" Type="string" />
      <Column Id="Path" Category="Text" PrimaryKey="no" Type="string" />
      <Column Id="Version" Category="Text" Nullable="yes" 
        PrimaryKey="no" Type="string" />
      <Column Id="InstalledGUID" Category="Text" Nullable="yes" 
        PrimaryKey="no" Type="string" />
    </CustomTable>
    
    <EnsureTable Id="AvailableRSInstances" />
  • <Property - 定义一个可以在条件/UI/自定义操作中使用的属性。可以在 WIX 文件中的其他位置引用为 [Property]
  • <!-- RSINSTANCE will be used to Identify the MSSQL.# that has been 
         chosen by the user-->
    <Property Id="RSINSTANCE"
      Value="NotSelected" />
  • <InstanceTransforms - 定义如何通过将一个或多个转换应用于数据库来转换 MSI 数据库。在我们的示例中,应用转换时,它会将通用的 ProductCode GUID 替换为每个实例特定的 GUID。此外,ProductName 将被替换为实例特定的名称(这将显示在“添加/删除程序”列表中)。支持多个安装实例需要为每个允许的实例定义一个实例。
  • <!-- An instancetransform is necessary for each instance you 
         plan on supporting. The Instance Id will be used when 
         property TRANSFORMS=:XXXXXX -->
    <InstanceTransforms Property="RSINSTANCE">
      <Instance Id="MSSQL.1" ProductCode="$(var.Instance1ProductCode)" 
        ProductName="$(var.ApplicationSpecificProductName) MSSQL.1" />
      <Instance Id="MSSQL.2" ProductCode="$(var.Instance2ProductCode)" 
        ProductName="$(var.ApplicationSpecificProductName) MSSQL.2" />
    ...
      <Instance Id="MSSQL.10" ProductCode="$(var.Instance10ProductCode)" 
        ProductName="$(var.ApplicationSpecificProductName) MSSQL.10" />
    </InstanceTransforms>
  • <Directory - 指定目录布局以及源目录和目标目录之间的映射。每个 WIX 文件都有一个 Directory Id="TARGETDIR",这是强制性的。在此 TARGETDIR 目录内,其他 Directory 元素将描述目录位置和组件的结构。
  • <Directory Id="TARGETDIR" Name="SourceDir">
    
      <!-- INSTALLDIR will be set by the UpdatePropsWithSelectedRSInstance 
           Once an instance is selected -->
      <Directory Id="INSTALLDIR" >
    ...
      </Directory>
    </Directory>
  • <Component - 您希望安装程序在安装或卸载期间放置、更改或删除的项目(文件、注册表更改、XML 更改、SQL 更改)。
  • <Component Id="ProductComponent" Guid="$(var.ProductComponent_GUID)">
      <!-- File(s) to be place in INSTALLDIR-->
      <File Id="DummyInterop" 
            Name=" DummyInterop.dll" 
            src="$(var.SolutionDir)\DummyInterop\$(var.Configuration)\
    DummyInterop.dll"
            Vital="yes" 
            DiskId="1" />
    </Component>
  • <RegistryKeyRegistryValue - 用于描述要应用的注册表更改。注册表标签是组件的子项。MSI 将非文件识别为共享项,因此在安装期间添加的注册表项不会被删除,除非只有一个实例被安装和卸载。为了克服此 MSI 限制,每个非文件组件都必须有一个唯一的组件来处理非文件数据。因此,在此示例中,对于我们支持的每个实例,我们都必须创建一组独立的组件,其中包含相同的注册表项来应用/删除。
  • 请注意,此组件中有一个条件,该条件仅当 RSINSTANCE 与我们为其定义组件的实例匹配时,才会安装此组件。这样,我们可以定义多个组件,并且根据 RSINSTANCE 属性,只有一个会被安装/卸载。

    <Component Id="Registry_Instance1" Guid="$(var.Registry_Instance1_GUID)">
      <Condition> <![CDATA[RSINSTANCE = "MSSQL.1"]]> </Condition>
      <RegistryKey Root="HKLM" 
       Key="SOFTWARE\[Manufacturer]\[ApplicationSpecificProductName]\[RSINSTANCE]">
        <RegistryValue Id="RSEVersion.1" Action="write" Name="Version" 
           Value="[ProductVersion]" Type="string" KeyPath="yes" />
        <RegistryValue Id="RSEGUID.1" Action="write" Name="InstalledGUID" 
           Value="[ProductCode]" Type="string" />
      </RegistryKey>
    ...
    </Component>
  • <XmlConfig 是一个 WIX 实用程序扩展标签,它允许在组件安装/卸载过程中操作 XML 文件。为了使用 WIX 实用程序扩展,描述整个 WIX 文件的 <WIX 标签必须包含 xmlns:util="http://schemas.microsoft.com/wix/UtilExtension">
  • 此外,您必须将一个项目引用添加到 WIX 安装时创建的 Windows Installer XML bin 目录中的 WixUtilExtension.dll

    注意:在此示例中,我们放置了一个没有内置 .NET 安全性的互操作 DLL 自定义程序集。由于没有安全性,并且我们无法确定所需的安全性级别,因此必须修改 SSRS 安全文件(rssrvpolicy.config)以允许所有通过报表表达式主机运行的程序集获得完全信任。要更深入地了解 SSRS 自定义程序集和 .NET 安全性,请参考以下链接:https://codeproject.org.cn/KB/reporting-services/CustomAssemblies.aspx。此示例在安装时将 PermissionSetName 设置为 FullTrust,在卸载时将其设置回 Execution

    <util:XmlConfig Id="rssrvpolicy.config.1.install" 
        On="install" 
        File="[INSTALLDIR]\..\rssrvpolicy.config" 
        ElementPath="//configuration/mscorlib/security/policy/PolicyLevel/
    CodeGroup/CodeGroup[\[]@Name='Report_Expressions_Default_Permissions'[\]]"
        Action="create" 
        Node="value" 
        Name="PermissionSetName" 
        Value="FullTrust" />
    
    <util:XmlConfig Id="rssrvpolicy.config.1.uninstall" 
        On="uninstall" 
        File="[INSTALLDIR]\..\rssrvpolicy.config"
        ElementPath="//configuration/mscorlib/security/policy/PolicyLevel/
    CodeGroup/CodeGroup[\[]@Name='Report_Expressions_Default_Permissions'[\]]"
        Action="create" 
        Node="value" 
        Name="PermissionSetName" 
        Value="Execution" />;
  • <Binary 用于定义包中需要但未安装的文件。
  • <CustomAction 用于定义自定义操作入口点,并引用二进制文件 ID。
  • <!-- Define a binary file that will be used in the package and 
         referenced via custom actions 
         Must add the Project as a Reference in order to do 
         $(var preprocessing on project name varables -->
      <Binary Id="CA_RSInstances" 
        SourceFile="$(var.CA_RSInstances.TargetDir)\
    $(var.CA_RSInstances.TargetName).CA.dll" />
    
      <!-- Define Custom Action to Fill List of RSINSTANCE with 
           each Report Server Instance that is installed -->
      <CustomAction Id="GetRSInstances" BinaryKey="CA_RSInstances" 
         DllEntry="CAFillRSInstanceListbox" 
         Execute="immediate"  
         Return="check" />
    
    <!-- Define Custom Action to assign all necessary properties 
         based upon selected RSINSTANCE -->
      <CustomAction Id="UpdatePropsWithSelectedRSInstance" 
         BinaryKey="CA_RSInstances" 
         DllEntry="UpdatePropsWithSelectedRSInstance" 
         Execute="immediate" Return="check" />
  • <UIRef 用于引入 UI 对话框。它们可以是标准的 UI 配置之一,也可以是您自己的 UI 定义。
    • WixUI_Mondo 提供了完整的界面,欢迎页面,许可协议,设置类型(典型,自定义,完整),允许功能定制,浏览目标目录,并提供磁盘成本计算。还包括维护模式对话框。当您的产品某些功能默认不应安装时(换句话说,“典型”和“完整”安装之间存在显著且有意义的区别)应该使用此选项。
    • WixUI_FeatureTree 类似于完整集,但不允许用户在设置类型之间进行选择。它始终假定为 Custom,并在用户接受许可协议后直接进入功能定制对话框。
    • WixUI_InstallDir 允许用户选择目标目录,但不显示通常的自定义功能页面。选择目录后,安装将自动进行。
    • WixUI_Minimal 提供了一个简化的用户界面,其中一个对话框结合了欢迎和许可协议页面。之后,安装会自动进行,而不允许用户自定义任何内容。当您的应用程序没有可选功能可安装时使用它。

    在此示例中,我构建了以下 UI 流程

    • PrepareDlg
    • SelectRSInstance
    • WixUI_MaintenanceTypeDlg(如果已安装且未升级)
    • ProgressDlg
  • <dialog 用于定义 UI 对话框屏幕。
  • <!-- define UI screen to display the list of instances and allow selection -->
    <UI>
      <Dialog Id="SelectRSInstance" Width="370" Height="270" 
         Title="Reporting Services Extensions - Version [ProductVersion]">
    ...
    </Dialog>

    可以使用 SharpDevelop 工具使用 GUI 工具查看和操作 WIX 对话框。http://sourceforge.net/projects/sharpdevelop/

  • <Control 定义一个 GUI 小部件:可以对小部件设置条件;Publish 用于定义控件激活时发生的动作或其他对话框。在下面的示例中,如果 RSINSTANCE 为空,则“下一步”按钮将被禁用,如果非空则启用。按下“下一步”按钮时,将触发自定义操作 UpdatePropsWithSelectedRSInstance,然后关闭此对话框。请注意,在 Publish 定义中,有一个条件检查(在下面的示例中,由于 </Publish> 前面的 1,两个 Publish 定义将始终发生)。这可以是任何条件表达式,用于根据设置限制发生的事件。
  • <Control Id="Next" 
       Type="PushButton" 
       X="236" Y="243" Width="56" Height="17" 
       Default="yes" 
       Text="!(loc.WixUINext)">
       <Condition Action="disable">RSINSTANCE = ""</Condition>
       <Condition Action="enable"><![CDATA[RSINSTANCE <> ""]]></Condition>
       <Publish Event="DoAction" 
          Value="UpdatePropsWithSelectedRSInstance">1</Publish>
       <Publish Event="EndDialog" 
          Value="Return">1</Publish>
    </Control>
  • <InstallUISequence 用于定义在安装过程中的 UI 事件序列中触发的对话框或自定义操作。在下面的示例中,在显示 PrepareDlg 之后执行 GetRSInstances 自定义操作(它调用 DLL 的 CAFillRSInstanceListbox)。在 GetRSInstances 自定义操作执行之后,显示 SelectRSInstance 对话框。根据定义的条件,MaintenanceTypeDlg 被定义为在 ProgressDlg 之前显示。自定义操作 SetRemove 用于将 REMOVE 属性设置为 ALL 值。如果用户选择从 MaintenanceTypeDlg 卸载,则会发生这种情况。自定义操作 PreventDowngrading 将阻止安装,如果已有较新版本安装。
  • <InstallUISequence>
      <Custom Action="GetRSInstances" 
         After="PrepareDlg" Overridable="yes"></Custom>
      <Show Dialog="SelectRSInstance" 
         After="GetRSInstances">1</Show>
      <Show Dialog="MaintenanceTypeDlg" 
         Before="ProgressDlg">
    Installed AND NOT RESUME AND NOT Preselected AND NOT UPGRADEFOUND
      </Show>
      <Custom Action="SetRemove" 
         After="MaintenanceTypeDlg" 
         Overridable="yes">WixUI_InstallMode="Remove"</Custom>
      <Custom Action="PreventDowngrading" 
         Before="MaintenanceTypeDlg">NEWERPRODUCTFOUND</Custom>
    </InstallUISequence>
  • <InstallExecuteSequence 用于定义 UI 完成后、安装开始执行后触发的自定义操作。如果已有较新版本安装,PreventDowngrading 自定义操作将阻止安装。如果使用了 <Upgrade 标签,则必须在 ExecuteSequence 中指定 RemoveExistingProducts 标签。主要升级实际上会安装新包,并卸载原始安装的包。
  • <InstallExecuteSequence>
      <Custom Action="PreventDowngrading" 
         After="FindRelatedProducts">NEWERPRODUCTFOUND</Custom>
      <RemoveExistingProducts After="InstallFinalize" />
    </InstallExecuteSequence>

通过 ORCA 查看完成的 MSI

创建 MSI 包后,可以通过 ORCA 工具查看 MSI 数据库。这有助于确定 UI 和执行序列是否按预期启动。

orca.jpg

调试 MSI

您可以使用命令行执行 MSI 来调试您的 MSI,并使用以下命令创建一个调试文件:

Msiexec -I NameofyourInstaller.msi -l*vc debug.log

这将创建一个调试文件,其中包含有用的信息,例如参数值何时更改以及传递给执行序列的参数。

示例如下输出

结论

由于我是 MSI 和 WIX 的新手,因此构建此项目对我来说是一次学习经历。我希望本文能弥合 WIX 手册和完整的安装程序之间的差距。非常感谢参考资料中列出的文章的作者。没有他们的贡献,我就无法完成这项工作。随着 Microsoft Reporting Services 被更广泛地接受,我预见到在许多报表服务器上安装自定义程序集的需求会很大。此安装程序足够通用,应该可以成为任何应用程序安装套件的绝佳插件。

© . All rights reserved.