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

部署,因为 WPF 桌面应用程序并未过时

starIconstarIconstarIconstarIconstarIcon

5.00/5 (17投票s)

2015 年 12 月 18 日

CPOL

50分钟阅读

viewsIcon

73984

downloadIcon

649

本文介绍如何使用 ClickOnce 和 Windows Installer 技术部署您的应用程序。

可用下载

目录

引言

最近我创建了一个 PDF 分割和合并工具,您可以在此处找到它。在完成 PDF 工具的制作后,我想通过部署包使其易于获取。在本文中,我将使用 ClickOnce 技术和经典的 Windows Installer 为这个 PDF 工具创建两个部署包。生成的 ClickOnce 部署包可以通过此链接从我的 Github 仓库启动。经典的 Windows Installer 包可以在本文的顶部找到。

背景

在您完成 WPF 应用程序的开发后,它必须到达您的客户手中。有几种策略可以用于实现这一点。

  • ClickOnce 部署: ClickOnce 技术使您能够创建易于通过 Web 浏览器安装的自更新应用程序。这项技术的一个缺点是它不允许对安装向导进行太多配置。
  • Web 浏览器部署(XAML 浏览器应用程序,XBAP):这项技术使您无需在客户端计算机上安装即可在 Web 浏览器中运行应用程序。这项技术的一个缺点是 XBAP 通常以非常有限的权限运行。但是,这是可配置的。
  • 传统安装程序:对于更详细的部署,Windows Installer 技术提供了一个高度可配置的部署环境。目前有两种广泛使用的安装程序技术。第一个是 InstallShield,您可以在此处下载。第二个是 Visual Studio 中的经典安装程序项目模板,您可以在此处下载。

在本文中,我将重点介绍两种部署策略,即 ClickOnce 部署和经典的 Windows Installer。

ClickOnce 部署

引言

正如引言中提到的,应用程序可以通过 Windows Installer 或 ClickOnce 技术进行部署。对于 WPF 应用程序,ClickOnce 被认为是首选技术。它是一种强大且易于使用的部署技术,在配置得当的情况下,为最终用户提供了简便的安装体验。

ClickOnce 技术的工作原理如下:开发人员使用 Visual Studio 将 ClickOnce 应用程序发布到 Web 服务器,然后应用程序用户可以浏览到生成的名为 publish.htm 的网页。此网页提供安装应用程序的链接。当用户单击此链接时,应用程序将被下载并安装,如图 1 所示。从安全角度来看,ClickOnce 应用程序使用证书来验证应用程序发布者的身份。

图 1: Clickonce 部署概述。

您可以使用 Visual Studio 中的“发布”页面来配置 ClickOnce 部署。要打开“发布”页面,请在“解决方案资源管理器”中右键单击项目名称,选择“属性”,然后选择“发布”页面。在“发布”页面中,您可以在“发布文件夹位置”组合框中指定应用程序的发布位置。“发布位置”是用户安装应用程序的位置。除了此位置之外,您还可以指定“安装文件夹 URL”。指定“安装文件夹 URL”通常是在您希望先将应用程序部署到“发布文件夹位置”,然后手动将文件复制到最终安装位置,用户可以从中下载应用程序时完成的。

图 2: 用于配置 ClickOnce 部署属性的发布页面。

对于 PDF 工具,应用程序使用默认的发布目录来发布应用程序,该目录是项目文件夹的一个子文件夹。通过使用 Github Windows 桌面版,PDF 工具的本地代码与 Github 仓库同步。此步骤会将本地发布目录复制到用户可以下载应用程序的安装位置。

https://raw.githubusercontent.com/Mohamed1976/EasySplitAndMergePdf/master/EasySplitAndMergePdf/publish/ 

代码片段 1 PDF 分割和合并工具的安装 URL。

正如您在图 2 的“发布”页面中看到的,您可以将应用程序配置为仅在线可用,或者也离线可用。通过选择“应用程序仅在线可用”,必须从 ClickOnce 网页启动应用程序。应用程序将被缓存到本地以获得最佳性能,但除非用户能够连接到发布应用程序的站点,否则他们将无法运行该应用程序。这确保了应用程序用户始终运行最新版本的应用程序。或者,您可以选择“应用程序也可离线使用”,以便应用程序在线和离线均可用。在这种情况下,应用程序将被复制到本地计算机并添加到“开始”菜单。可以使用“控制面板”中的“添加/删除程序”来删除应用程序或将其恢复到以前的版本。

此外,您可以在“发布”页面中指定“发布版本”。应注意的是,“发布版本”指定了应用程序的发布版本,这不应与应用程序的生成版本混淆。当选中“每次发布时自动递增修订号”时,“发布版本”将在您发布应用程序时自动递增。

配置发布选项

您可以通过单击“发布”页面中的“选项”按钮来指定有关 ClickOnce 应用程序的信息。这将打开“发布选项”对话框,其中包含四个页面,如图 3 所示。

图 3: 用于配置 ClickOnce 应用程序部署设置的发布选项对话框。

“发布选项”对话框中的最顶层页面是“描述”页面。此页面中的大多数设置不言自明,您可以设置“发布者名称”、“产品名称”和支持 URL。当 ClickOnce 应用程序安装用于脱机使用时,将在“开始”菜单的“所有程序”文件夹中创建一个条目。该条目出现在“产品名称”下,而“产品名称”又出现在“发布者名称”下,因此这些值必须在“描述”页面中设置。

图 4: PDF 工具快捷方式会出现在“开始”菜单中,位于发布者名称下。

在“部署”页面中,您可以指定“部署网页”,该网页在发布应用程序时生成。当您创建自定义部署网页时,应取消选中“每次发布后自动生成部署网页”,以避免您的自定义网页在每次发布应用程序时都被覆盖。您需要修改自定义部署网页,使其指向新发布的应用程序,而不是生成新的部署网页。此外,如果您打算手动使用 Mage.exe 签名应用程序,可以取消选中“使用 .deploy”文件扩展名,以避免 Mage.exe 生成错误。如果您使用 IIS Web 服务器发布 ClickOnce 应用程序,IIS Web 服务器会使用 .deploy 文件扩展名来锁定文件,以防止他人修改它们。

图 5: 用于配置 ClickOnce 应用程序部署设置的部署页面。

配置应用程序文件和必备组件

在发布 ClickOnce 应用程序时,Visual Studio 项目中的所有数据文件都会随应用程序文件一起部署。在某些情况下,您可能不希望发布 Visual Studio 项目中包含的所有文件,或者您可能希望根据条件安装某些文件。Visual Studio 提供了排除文件、将文件标记为数据文件或必备组件以及创建文件组以进行条件安装的功能。有关如何执行此操作的详细信息,请参阅此处。文件组的使用在您希望根据客户端计算机的区域设置部署卫星程序集时尤其有用,有关如何使用文件组部署特定区域设置的卫星程序集,请参阅本节

图 6: 用于配置安装所包含文件的应用程序文件对话框。

通过单击“发布”页面中的“必备组件”按钮,您可以配置必备组件。在“必备组件”对话框中,您可以选择在您的项目部署之前必须在客户端计算机上安装的包。所有使用 Visual Studio 2015 创建的应用程序都需要 .NET Framework 4.5 才能运行。正如在下面的必备组件列表中所见,.NET Framework 已默认配置为安装。在安装 ClickOnce 应用程序时,ClickOnce 引导程序会检查 .NET Framework 4.5 是否已安装,如果未安装,则会安装它,从而消除了在客户端计算机上单独下载和安装 .NET Framework 的额外步骤。

图 7: 用于配置运行 ClickOnce 应用程序所需的必备组件的必备组件对话框。

配置更新

ClickOnce 技术的主要优点之一是它支持更新的方式。ClickOnce 应用程序能够检查应用程序更新是否可用。如果可用,将下载并使用新版本的应用程序。为了提高效率,仅下载已更改的文件。

ClickOnce 使您能够配置应用程序如何检查更新。有三种策略可用于检查更新:

  • 在应用程序启动时检查更新。
  • 在应用程序启动后使用后台线程检查更新。
  • 使用 ApplicationDeployment 类以编程方式检查更新。

以编程方式检查更新将在下一节中讨论。在本节中,我将讨论如何使用“应用程序更新”对话框配置更新检查。当您单击“发布”页面中的“更新”按钮(如图图 2所示)时,将显示此对话框。要启用更新检查,请在“应用程序更新”对话框(如图图 8所示)中选择标记为“应用程序应检查更新”的复选框。这样做会启用对话框中的其他选项。

图 8: 用于配置应用程序如何检查更新的应用程序更新对话框。

您可以指定 ClickOnce 应用程序是在启动前还是启动后检查更新。如果您选择“在应用程序启动前”,则应用程序会在每次启动时检查新更新。这确保了用户始终运行最新版本的应用程序,但此设置的一个缺点是它可能会降低应用程序在启动时的性能。

如果您选择“在应用程序启动后”,则应用程序将在应用程序运行时在后台尝试读取部署清单文件。如果存在更新,用户下次运行应用程序时,将提示用户下载并安装更新。这种策略最适合需要长时间下载或网络连接缓慢的大型更新。

如图 8 的“应用程序更新”对话框所示,您可以指定应用程序的最低必需版本。此设置强制安装更新。当应用程序检测到存在更新并且已设置为最低必需版本时,将安装该更新,用户无法跳过。此外,用户无法使用“控制面板”中的“添加/删除程序”菜单回滚到以前的版本。最低必需版本对于确保不同用户使用的应用程序保持最新非常有用,特别是如果以前版本的应用程序包含严重错误。

此外,正如在“应用程序更新”对话框(图 8)中所示,如果您的更新托管在不同于安装位置的位置,您可以指定一个更新位置。当 ClickOnce 应用程序最初从 CD/DVD 或文件共享安装,但应用程序必须在 Web 上检查更新时,这一点尤其有用。通过指定的更新位置,您的应用程序可以在初始安装后自行从 Web 更新。

以编程方式检查更新

除了安排更新外,您还可以以编程方式检查更新。通过使用 ApplicationDeployment 类,您可以从代码中检查更新。在获得当前部署的引用后,您可以使用 CheckForUpdate 方法检查更新;如果更新可用,您可以使用 Update 方法下载并安装更新。

System.Deployment.Application.ApplicationDeployment deployment = 
    System.Deployment.Application.ApplicationDeployment.CurrentDeployment;
if (deployment.CheckForUpdate())
{
    deployment.Update();
}

代码片段 2 使用同步调用以编程方式检查更新。

CheckForUpdateUpdate 方法也有异步对应项,分别称为 CheckForUpdateAsyncUpdateAsync,它们可以异步执行更新。当 CheckForUpdateAsync 返回时,它会引发 CheckForUpdateCompleted 事件。通过处理此事件,您可以在事件处理程序中检查 CheckForUpdateCompletedEventArgs 参数,以确定更新是否可用。如果更新可用,您可以调用 UpdateAsync 以异步下载和安装更新。同步和异步更新都将在应用程序重新启动后应用于应用程序。

System.Deployment.Application.ApplicationDeployment deployment;

public UpdateApplication
{
    InitializeComponent();
    deployment = 
        System.Deployment.Application.ApplicationDeployment.CurrentDeployment;
    deployment.CheckForUpdateCompleted += UpdateCheckCompleted;
}

public void UpdateApp()
{
    deployment.CheckForUpdateAsync();
}

private void UpdateCheckCompleted(object sender, 
    System.Deployment.Application.
    CheckForUpdateCompletedEventArgs e)
{
    if (e.UpdateAvailable)
    {
        deployment.UpdateAsync();
    }
}    

代码片段 3 使用异步调用以编程方式检查更新。

发布向导

如“发布”页面(图 2)所示,您还可以使用“发布向导”配置 ClickOnce 部署设置。此向导允许您配置以下属性:

  • 发布文件夹位置: Visual Studio 将文件复制到的位置(本地计算机、网络文件共享或 FTP 服务器)。
  • 安装文件夹位置: 用户将从中安装的位置(网络文件共享、网站或 CD/DVD)。
  • 在线或离线可用性:此设置决定用户只能在线访问应用程序,还是用户也可以离线访问应用程序。

有关如何使用发布向导的详细信息,请参阅此处

签名应用程序

您可以使用 Visual Studio 对您的应用程序进行数字签名。当您的应用程序被数字签名后,它就能确保签名后对应用程序文件的任何修改都会被接收者检测到。要对应用程序进行签名,请选择“签名”页面,如图 9 所示。为了在 Visual Studio 中启用证书签名,请选中“签名 ClickOnce 清单”复选框。然后,您可以从本地证书存储或特定文件中选择证书;在我的例子中,我选择了一个为应用程序签名创建的私钥(*.pfx 文件)。请注意,通常大多数公司都会从受信任的第三方证书颁发机构(如 Verisign)购买应用程序签名证书,该机构在大多数计算机上已经是根证书颁发机构。

图 9: 用于对您的应用程序进行签名的签名页面。

如前所述,为了对 PDF 分割和合并工具进行签名,我创建了一个自签名根证书和一个应用程序签名证书。证书是使用 Windows Makecert.exe 工具创建的,如代码片段 4 所示。有关如何创建自己的证书的详细信息,请参阅此处

#Creating a self signed root CA certificate
makecert.exe -n "CN=Moccasoft,OU=(c) 2015 Moccasoft - The name you can trust,O=Moccasoft,C=NL"
-r -pe -a sha512 -len 4096 -cy authority -sv CARootMoccasoft.pvk CARootMoccasoft.cer

#Copy the public(.cer) and private(.pvk) key into a .pfx (personal information exchange) file.
pvk2pfx.exe -pvk CARootMoccasoft.pvk -spc CARootMoccasoft.cer -pfx CARootMoccasoft.pfx -po pfxPassword

#Create certificate to sign applications.
makecert.exe -n "CN=Moccasoft" -iv CARootMoccasoft.pvk  -ic CARootMoccasoft.cer -pe -a sha512 -len 4096 -b 01/11/2015 -e 01/11/2017 -sky exchange -sv ApplicationSigning.pvk ApplicationSigning.cer 

#Copy the public(.cer) and private key(.pvk) into a .pfx (personal information exchange) file.
pvk2pfx.exe -pvk ApplicationSigning.pvk -spc ApplicationSigning.cer -pfx ApplicationSigning.pfx -po pfxPassword

*) Note that the absolute paths to the files are omitted for clarity.
*) pfxPassword is your password to protect the pfx file.
*) During the creation of the private key (.pvk), you will be prompted to enter a password to protect this file. 

代码片段 4 使用 Makecert.exe 工具创建证书。

此自签名根证书可在此下载,您可以通过检查其指纹来验证其真实性,指纹为 sha1 = ‎43 7c 1c 86 e0 d1 16 21 1a 2e cb 6e bd 42 ed 09 56 38 a0 ed。安装此根证书后,您可以使用它来验证 PDF 分割和合并工具的发布者。此外,您可以将应用程序签名证书安装为受信任的发布者。此应用程序签名证书可在此下载,您可以通过检查其指纹来验证其真实性,指纹为 sha1 = ‎‎9f 16 d5 f3 b9 5b 0a 01 55 59 a2 8c 7c da 3d 72 3b 48 bc af。将应用程序签名证书安装为受信任的发布者后,您可以安装 ClickOnce PDF 工具,而无需被提示。

“签名”页面(图 9)中,您可以指定一个“时间戳服务器 URL”来为应用程序版本打时间戳。为应用程序打时间戳的主要好处是它将应用程序信任的有效期延长到您的应用程序签名证书的有效期之外。如果您不为应用程序打时间戳,当您的应用程序签名证书过期时,应用程序签名将被视为无效。相比之下,带时间戳的签名应用程序将无限期有效,只要可验证的时间戳(由受信任的时间戳颁发机构颁发)表明应用程序是在签名证书有效期内签名的。如果由于泄露而必须撤销签名证书,则在撤销日期之前签名的应用程序将保持有效。

使用 Mage.exe 签名应用程序

通过 ClickOnce 部署的任何应用程序都将有一个部署清单和一个应用程序清单。如果您决定手动签名应用程序,则需要使用命令行工具 Manifest Generation and Editing Tool (Mage.exe) 来签名这两个清单文件。

根据应用程序的类型,部署清单可以具有以下文件扩展名:

  • .application: 应用程序,例如 Winforms 或 Wpf 应用程序
  • .vsto: Office 解决方案。
  • .xbap: Xaml 浏览器应用程序(xbap)

应用程序清单将始终具有 .manifest 扩展名。部署清单和应用程序清单都可以在发布目录中找到,发布目录的结构如下所示。如图 10 所示,最新的应用程序部署清单存储在两个位置:发布目录的根目录和最新的应用程序文件目录。应用程序清单仅存储在应用程序文件目录中。

图 10:布目录的结构。

如前一节所述,Visual Studio 能够签名部署清单和应用程序清单。那么问题来了,为什么要手动签名文件。手动签名是在应用程序发布后想要更改应用程序的某些内容时需要的。例如,您可能想要混淆您的可执行文件,或者您可能想要更改发布版本中的某个特定文件,如 .config 文件。

图 11:应用程序文件、应用程序清单和部署清单之间的安全链接

正如前面提到的,ClickOnce 应用程序签名涉及按特定顺序签名两个清单文件。这是因为部署清单包含应用程序清单的哈希值,如图 11 所示。因此,如果应用程序清单发生更改,部署清单中包含的哈希值将不再有效。类似地,应用程序清单包含应用程序文件的所有哈希值。因此,如果在应用程序发布后其中一个应用程序文件发生更改,您需要手动重新签名应用程序清单,然后是部署清单,如代码片段 5 所示。在发布目录中的部署清单签名后,您需要将此文件复制到最新的应用程序文件目录。

mage -update EasySplitAndMergePdf.exe.manifest -certfile ApplicationSigning.pfx -Password PfxPassword   -TimestampUri http://timestamp.digicert.com
EasySplitAndMergePdf.exe.manifest successfully signed

mage -update EasySplitAndMergePdf.application -appmanifest EasySplitAndMergePdf.exe.manifest -certfile ApplicationSigning.pfx" -Password PfxPassword -TimestampUri http://timestamp.digicert.com
EasySplitAndMergePdf.application successfully signed

*) Note that the absolute paths to the files are omitted for clarity'.

代码片段 5 使用 Mage.exe 签名应用程序。

在签名过程中,您可能会看到一些关于文件未找到的错误。如果您对应用程序文件使用 .deploy 扩展名,则可能会发生这种情况。要避免这种情况,您必须将 .deploy 扩展名重命名回其原始扩展名,然后运行带有 mage 的更新命令。一旦更新了应用程序清单和部署清单,就必须将应用程序文件重命名回 .deploy 扩展名。或者,您可以取消选中本节中描述的“部署”页面中的“使用 .deploy”文件扩展名。

安全

在安全讨论中,您需要区分代码访问安全(CAS)和基于用户的安全。按设计,ClickOnce 应用程序的权限仅限于其调用者的权限。因此,对于 ClickOnce 应用程序,requestedExecutionLevel 元素的唯一有效值为 asInvoker。所有这一切的结果是,ClickOnce 应用程序无法以管理员权限运行,除非应用程序调用者具有系统管理员权限。

<requestedExecutionLevel level="asInvoker" uiAccess="false" />

代码片段 6 ClickOnce 部署唯一有效的 requestedExecutionLevel 是 asInvoker。

ClickOnce 应用程序可以具有两种权限集之一:完全信任或部分信任。完全信任意味着您不希望在运行时对您的应用程序进行 CAS 限制。因此,完全信任的应用程序可以执行用户安全允许它执行的任何操作。相比之下,部分信任的应用程序获得的权限比其调用者少。部分信任原则意味着应用程序开发人员仅请求应用程序执行其任务所必需的权限。当部分信任的应用程序配置正确时,它不能用于利用客户端计算机的其他部分。

有两种具有预定义权限集的部分信任区域:内网区域和外网区域。如图 12 所示,外网区域的权限集相对比内网区域更严格。在本文中,您可以查看外网和内网区域请求了哪些权限。除了这两个预定义的权限集外,您还可以定义自己的自定义权限集。有关如何执行此操作的详细信息将在本节后面讨论。

图 12: 不同区域的 ClickOnce 应用程序权限集。

默认情况下,ClickOnce 应用程序在客户端计算机上安装或运行时会请求完全信任权限。原因是 ClickOnce 应用程序需要完全信任权限来创建窗口(非托管代码权限)。与基于窗口的 ClickOnce 应用程序相反,XBAP 可以在部分信任环境中运行,因为这些类型的应用程序托管在 Web 浏览器中并在其中运行。

如图 13 所示的“安全”页面可用于配置 ClickOnce 应用程序的 CAS 设置。如果您希望配置您的 XBAP 在部分信任下运行,您需要选中“这是一个部分信任应用程序”单选按钮。通过选择此单选按钮,“您的应用程序将从哪个区域安装”下拉列表将启用,允许您选择预定义的权限集或自定义权限集之一。如果您选择自定义,则可以通过单击“编辑权限 XML”按钮来配置自定义权限集,该按钮将打开应用程序清单。然后,您可以将 IPermission 元素添加到此清单以定义自定义权限集。

 

图 13: 安全页面可用于配置代码访问安全设置。

对于完全信任的应用程序,应用程序清单声明应用程序没有任何限制。应用程序清单通过 PermissionSet 元素的 Unrestricted 属性值设置为 True 来指示完全信任的应用程序,如以下所示。

<trustInfo>
    <security>
        <applicationRequestMinimum>
            <PermissionSet Unrestricted="true" ID="Custom" SameSite="site" />
            <defaultAssemblyRequest permissionSetReference="Custom" />
         </applicationRequestMinimum>
    </security>
</trustInfo>   

代码片段 7 完全信任应用程序的 Unrestricted 属性设置为 true。

对于部分信任的应用程序,不设置 Unrestricted 属性,而是分别声明每个请求的权限,如下所示。

<trustInfo>
    <security>
          <applicationRequestMinimum>
                <PermissionSet class="System.Security.NamedPermissionSet">
                  <IPermission class="System.Security.Permissions.EnvironmentPermission" />
                  <IPermission class="System.Security.Permissions.FileDialogPermission"/>
                  <IPermission class="System.Security.Permissions.IsolatedStorageFilePermission"/>
                  <IPermission class="System.Drawing.Printing.PrintingPermission"/>                  
                </PermissionSet>
            <defaultAssemblyRequest permissionSetReference="Custom" />
          </applicationRequestMinimum>
    </security>
</trustInfo>
*) Note that several IPermission attributes are omitted for clarity.

代码片段 8 对于部分信任的应用程序,每个请求的权限都是单独声明的。

在定义自定义权限集时,您可以使用 Permission 类的 ToXml 方法生成所需的 IPermission 元素,以填充应用程序清单的 PermissionSet。将 IPermission 元素导出到文件后,您可以将其复制到应用程序清单的 PermissionSet 中。下面的代码显示了使用 ToXml 方法生成 IPermission 元素。

FileIOPermission fileIOPermission = new FileIOPermission(PermissionState.Unrestricted);
SecurityElement securityElement = fileIOPermission.ToXml();
StreamWriter streamWriter = new StreamWriter("@C:\permissionXml.txt");
streamWriter.Write(securityElement.ToString());
streamWriter.Flush();
streamWriter.Close();

代码片段 9 使用 ToXml 方法导出的权限集到文件。

<IPermission class="System.Security.Permissions.FileIOPermission, mscorlib, Version=4.0.0.0,                         Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Unrestricted="true"/>

代码片段 10 可以添加到应用程序清单的已导出权限集。

在编写部署在部分信任环境中的 XBAP 时,您需要避免使用本文中列出的 WPF 功能。对于可能在部分信任和完全信任环境中运行的 XBAP,您可以使用 CAS 在运行时检查是否允许特定操作。如果请求的操作不允许,您的应用程序可以以受控方式从该尝试中恢复,而不是崩溃。以下示例演示了创建 FileIOPermission 的要求,以确定写入文件是否安全。如果应用程序没有写入指定文件的权限,则会引发异常。

try
{
    FileIOPermission fileIOPermission =
        new FileIOPermission(FileIOPermissionAccess.AllAccess, @"C:\security.txt");
    fileIOPermission.Demand();
    MessageBox.Show("fileIOPermission.Demand() succeeded");
    // proceed with writing the file
}
catch(Exception ex)
{
    MessageBox.Show("fileIOPermission.Demand() failed: " + ex.Message);
    // recover from being unable to write the file
}

代码片段 11 在运行时检查是否允许特定操作。

最后我要说明的一点是,ClickOnce 安全设置也将应用于 Visual Studio 调试器。因此,您的调试运行时环境将具有与目标环境相同的安全限制。这有助于调试和修复在已部署环境中才会出现的问题。

本地化 ClickOnce 应用程序的部署

本地化是使您的应用程序为不同文化做好准备的过程。此过程涉及将应用程序资源翻译成其他文化。本地化应用程序是文化感知的,并根据客户端计算机的文化设置显示用户界面 (UI)。ClickOnce 应用程序的本地化会导致创建一个或多个卫星程序集。每个程序集包含特定于给定文化的 UI 字符串、图像和其他资源。当部署 ClickOnce 应用程序时,其相应的卫星程序集也需要随之部署。这可以通过三种方式实现:

  • 将所有卫星程序集包含在单个 ClickOnce 部署中:此选项允许您部署一个包含所有卫星程序集的 ClickOnce 部署。在本节的末尾,我将讨论一个说明此方法的示例应用程序。部署过程的第一步是在 Visual Studio 中将应用程序的文化设置为中性,然后您需要发布 ClickOnce 应用程序。接下来,将所有卫星程序集复制到发布目录的子文件夹中,即最新的应用程序文件目录。然后,您需要使用 MageUI.exe 手动将所有卫星程序集添加到应用程序清单中。在 MageUI.exe 中打开应用程序清单,选择“文件”页面,浏览到卫星程序集,然后使用“填充”按钮将其添加到应用程序清单。接下来,保存应用程序清单,然后重新签名应用程序清单,接着是部署清单。此部署方法的优点是它通过创建单个 ClickOnce 部署包简化了部署。当用户安装 ClickOnce 应用程序时,所有卫星程序集都将下载到客户端计算机。在运行时,将使用适当的卫星程序集,具体取决于客户端计算机的文化设置。此方法的缺点是它在每次客户端计算机上安装或更新应用程序时都会下载所有卫星程序集,这可能会影响应用程序更新期间的性能。
  • 按需下载卫星程序集:如果您决定将所有卫星程序集包含在单个部署中,则可以使用按需下载来提高性能,它允许您将程序集标记为可选。可选标记的程序集在安装或更新应用程序时不会被下载。您可以通过调用 ApplicationDeployment 类中的 DownloadFileGroup 方法来安装所需的程序集。要启用按需下载,您需要使用 MageUI.exe 手动将所有卫星程序集添加到部署中,方式与上一点类似。在通过 MageUI.exe 将所有卫星程序集添加到应用程序清单后,您需要将除中性语言程序集外的所有卫星程序集在 MageUI.exe 的“文件”页面中标记为可选。此外,您需要将卫星程序集组名设置为相应的区域设置,如下所示(图 14)。启用卫星程序集按需下载的应用程序代码如代码片段 12 所示。

图 14:使用 MageUI.exe 将可选卫星程序集添加到应用程序清单。

protected override void OnStartup(StartupEventArgs e)
{
    try
    {
        GetSatelliteAssemblies(Thread.CurrentThread.CurrentCulture.ToString());
        MainWindow mainView = new MainWindow();
        mainView.Show();
    }
    catch (Exception ex)
    {
        Debug.WriteLine(string.Format("OnStartup Exception: {0}", ex.ToString()));
    }
}

static void GetSatelliteAssemblies(string groupName)
{
    if (ApplicationDeployment.IsNetworkDeployed)
    {
        ApplicationDeployment deploy = ApplicationDeployment.CurrentDeployment;
        if (deploy.IsFirstRun)
        {
            try
            {
                deploy.DownloadFileGroup(groupName);
            }
            catch (DeploymentException ex)
            {
                //Logs that there is no satellite assembly for the user's default culture
                Debug.WriteLine(string.Format("DeploymentException: {0}", ex.ToString()));
            }
        }
    }
}

代码片段 12: 负责下载可选卫星程序集的代码。

  • 为每种文化生成一个部署(卫星程序集),每个部署位于单独的位置:在此部署策略中,您会生成多个部署,每种文化一个部署。在每个部署中,您仅包含该特定文化所需的卫星程序集和中性语言程序集,并将部署标记为特定于该文化。在发布应用程序后,您需要使用 MageUI.exe 将特定区域设置的程序集和中性语言程序集添加到应用程序清单中。为此,您需要使用 MageUI.exe 打开应用程序清单,选择“文件”页面,浏览到两个卫星程序集,然后使用“填充”按钮将它们添加到应用程序清单。完成此操作后,您需要设置部署清单的“名称”页面中可以找到的区域设置字段。最后,保存应用程序清单,然后重新签名应用程序清单,接着是部署清单。

为了说明 ClickOnce 应用程序的本地化过程,我创建了一个简单的 ClickOnce 应用程序,如图 15 所示。该应用程序的部署策略是如本节前面所述,包含所有卫星程序集。在示例应用程序中,用户可以选择三种文化之一。当用户单击“显示对话框”按钮时,所选文化将应用于登录窗口。

  

图 15 说明卫星程序集用法的示例应用程序。

ClickOnce 应用程序的本地化过程涉及几个步骤:

  1. 向项目文件添加 UICulture 属性。
  2. 在 XAML 文件中用 x:Uid 属性标记可本地化属性,以唯一标识它们。
  3. 使用 LocBaml.exe 等工具从您的应用程序中提取可本地化内容。
  4. 翻译可本地化内容。
  5. 创建子目录以保存新文化的卫星程序集。
  6. 使用 LocBaml.exe 等专用工具生成卫星程序集。

创建卫星程序集后,您需要使用 MageUI.exe 将它们添加到应用程序清单中。

图 16 使用 MageUI.exe 将卫星程序集添加到应用程序清单。

部署过程的最后一步是保存更改并重新签名应用程序清单,然后是部署清单。示例应用程序的源代码可以在本文的顶部找到。

ClickOnce 存储位置

ClickOnce 应用程序的一个优点是它们与文件系统的其余部分隔离,并且
因此不与安装在客户端计算机上的任何其他应用程序共享组件。由于这种隔离,ClickOnce 应用程序和数据存储在特殊位置。这些位置总结如下:

  • ClickOnce 应用程序目录
  • ClickOnce 数据目录
  • 可以在设置页面配置的应用程序和用户设置
  • 隔离存储
  • 远程位置,例如 Web 服务或数据库。

所有 ClickOnce 应用程序,无论是在本地安装还是在线托管,都存储在客户端计算机上的 ClickOnce 应用程序目录中。ClickOnce 应用程序目录位于遵循以下模式的位置:

c:\users\username\AppData\Local\Apps\2.0\[...]\[...]\[...]\

代码片段 13 ClickOnce 应用程序目录的位置。

此路径的最后三个部分是自动生成的字符串,例如“\RRECT6Z4.EOT\CG8AWZZE.B19\easy..tion_7a9ade79bb74c459_0001.0000_3c138a878cc17ccc”。正如您可能从生成的路径名所期望的那样,不期望应用程序用户和开发人员直接访问或修改 ClickOnce 应用程序目录中的文件。因此,建议不要使用此目录存储数据。

应使用 ClickOnce 数据目录来存储应用程序设置,而不是使用 ClickOnce 应用程序目录。在安装应用程序时,标记为数据文件的 ClickOnce 应用程序中的任何文件都将被复制到此目录。数据文件可以是任何文件类型,例如 XML 和数据库文件。代码片段 14 显示了 ClickOnce 数据目录的路径。    

c:\users\username\AppData\Local\Apps\2.0\data\[...]\[...]\[...]

代码片段 14 ClickOnce 数据目录的位置模式,与应用程序目录类似。

每个版本的 ClickOnce 应用程序都有自己的隔离数据目录。您可以使用此数据目录来存储数据文件。图 17 显示了两个 ClickOnce 数据目录的存在,一个用于应用程序版本 1,一个用于版本 2。当安装新版本的 ClickOnce 应用程序时,ClickOnce 会将所有现有数据文件从以前的数据目录复制到新的数据目录。例如,如果您在应用程序中包含一个用户修改过的自述文件。只要 ClickOnce 部署包中包含的自述文件在较新版本中相对于以前的 ClickOnce 部署包未被更改,ClickOnce 就会将已更改的自述文件复制到新数据目录中。同样,如果早期版本的 ClickOnce 应用程序创建的数据文件与较新版本 ClickOnce 部署包中包含的文件同名,ClickOnce 将用新文件覆盖旧文件。在这两种情况下,以前版本中的旧数据文件都将移动到数据目录内的名为 .pre 的子目录中,以便应用程序仍然可以访问旧数据以进行迁移。最后我想说一点关于 ClickOnce 数据目录,那就是当 ClickOnce 应用程序被卸载时,其数据目录也会被删除。因此,您不应使用数据目录来存储永久数据,例如文档。

图 17 ClickOnce 部署目录结构。

假设您的 ClickOnce 应用程序具有完全信任权限,它可以使用 System.IO 类中的方法调用来访问数据目录。您可以使用 ApplicationDeployment 类上 CurrentDeployment 属性定义的 DataDirectory 属性来获取 ClickOnce 应用程序内的数据目录路径。这是访问数据的最方便和推荐的方式。此外,您还可以使用 Forms.Application 类的 LocalUserAppDataPath 属性来获取数据目录路径。代码片段 15 显示了如何读取一个名为 readme.txt 的文本文件,该文件被标记为数据文件并包含在 ClickOnce 应用程序的应用程序清单文件中。

private void ReadFileFromDataDir()
{
    if (ApplicationDeployment.IsNetworkDeployed)
    {
        try
        {
           using (StreamReader sr = 
              new StreamReader(ApplicationDeployment.CurrentDeployment.DataDirectory + @"\readme.txt"))
           {
                MessageBox.Show(sr.ReadToEnd());
           }
        }
        catch (Exception ex)
        {
            Debug.WriteLine(string.Format("ReadFileFromDataDir Exception: {0}", ex.ToString()));
        }
    }
}

代码片段 15 以编程方式从 ClickOnce 数据目录读取数据文件。

除了使用数据目录外,您还可以使用隔离存储来创建和访问应用程序的文件。使用隔离存储 API,您可以为应用程序存储和检索数据。隔离存储在部分信任的应用程序中也能正常工作,而无需额外的权限授予。因此,如果您的应用程序在部分信任环境中运行,但必须维护应用程序特定的数据,则应使用隔离存储。有关隔离存储 API 的详细信息,请参阅此处此处

WPF 应用程序还允许您创建和访问从应用程序会话持续到应用程序会话的值,称为设置。用户设置存储在 user.config 文件中,该文件位于 ClickOnce 数据目录的子目录中。当 ClickOnce 应用程序更新时,用户设置会自动迁移到新版本。应用程序设置存储在位于 ClickOnce 应用程序目录的子目录中的 DataFiles.exe.config 文件中。应用程序和用户设置都可以在设置视图中查看和配置,如图 18 所示。

图 18 可以在设置页面配置的用户和应用程序设置。

设置页面中的一个重要属性是范围属性,它可以设置为应用程序或用户。应用程序范围的设置代表整个应用程序使用的值,而与用户无关,而用户范围的设置更可能是用户特定的,并且对应用程序不那么重要。用户设置和应用程序设置之间的一个重要区别是用户设置是可读写的。它们可以在运行时读写,新写入的值可以由应用程序保存。相比之下,应用程序设置是只读的,并且只能在设计时或通过在应用程序会话之间编辑设置文件来更改值。我创建了一个说明用户和应用程序设置用法的简单应用程序。这个示例应用程序可以在本文的顶部找到。

  

图 19 示例应用程序说明了用户和应用程序设置的用法。

private void ColorChangedEvent(object sender, RoutedEventArgs e)
{
    RadioButton ColorChosen = sender as RadioButton;
    if ((ColorChosen != null) && (ColorChosen.Tag != null))
    {
        switch (ColorChosen.Tag.ToString())
        {
            case "Red":
                Properties.Settings.Default.BackgroundColor = new SolidColorBrush(Colors.Red);
                break;
            case "Green":
                Properties.Settings.Default.BackgroundColor = new SolidColorBrush(Colors.Green);
                break;
            case "Blue":
                Properties.Settings.Default.BackgroundColor = new SolidColorBrush(Colors.Blue);
                break;
        }
        Properties.Settings.Default.Save();
    }
}

代码片段 16 将选择的背景颜色保存为用户设置。

此外,您还可以在 ClickOnce 应用程序中使用来自 Web 服务或数据库的远程数据,有关详细信息,请参阅此处

安装和卸载应用程序

部署后,用户可以通过单击此链接来安装 PDF 分割和合并工具,该链接显示下面的网页。如果您没有安装 .Net 4.5 框架,则可以使用安装按钮进行安装。要安装应用程序,您可以单击启动链接。

图 20 Publish.htm 页面包含 PDF 工具的安装链接。

如果您已将自签名根证书安装为受信任的根证书颁发机构,并将应用程序签名证书安装为受信任的发布者,则 ClickOnce 应用程序将无需提示即可安装。如果应用程序签名证书未安装为受信任的发布者,用户将看到一个安装对话框,如下所示。

图 21 用户看到一个安装对话框。

正如上面安装对话框中所示,显示了一个发布者链接,单击此链接可以查看信任链证书,该证书将引导您找到前面讨论的已安装的自签名根证书。信任链证书如下所示,其中自签名根证书是根。

图 22 应用程序签名的信任链。

您可以通过“控制面板”卸载应用程序或将其恢复到以前的状态,方法是单击“程序”,然后单击“程序和功能”,最后单击“卸载程序”。接下来,双击 Easy Split & Merge PDF 条目,然后将显示一个对话框。在此对话框中,用户可以删除应用程序,或将其恢复到以前的版本,如下所示。如果用户选择“将应用程序恢复到其以前的状态”选项并单击“确定”,ClickOnce 将回滚应用程序到以前安装的版本。请注意,ClickOnce 仅保留一个用户可以还原的先前应用程序版本。如果用户希望完全卸载应用程序,他们会选择“从此计算机删除应用程序”选项并单击“确定”。这将从客户端计算机中删除应用程序。

图 23:您可以卸载 ClickOnce 应用程序,也可以将应用程序回滚到其以前的状态。

ClickOnce 限制

正如前面提到的,ClickOnce 部署在自定义安装向导方面缺乏灵活性。因此,您不能将 ClickOnce 部署用于需要详细安装以指导用户完成一系列配置步骤的复杂应用程序。在这些情况下,您可以使用 Windows Installer,这将在下一章讨论。最后,还有一点我想提一下关于 ClickOnce 部署,.NET 可以构建一个简单的自定义安装程序,该安装程序使用 ClickOnce。通过使用 InPlaceHostingManager 类,您可以创建一个使用 ClickOnce 的自定义安装程序,有关示例,请参阅此处此处此处

此外,ClickOnce 技术还有以下限制:

  • ClickOnce 应用程序是为单个用户安装的,您无法为所有用户安装应用程序。
  • ClickOnce 部署无法执行自定义操作,例如创建数据库、配置注册表设置、添加新字体、将文件放入全局程序集缓存 (GAC)。
  • ClickOnce 应用程序无法以管理员权限运行,除非应用程序调用者具有系统管理员权限。
  • ClickOnce 应用程序和更新通过将其托管在 Web 或文件共享上进行发布。无法控制谁可以下载更新或何时下载。当您发布一个新版本,而第二天办公室一开门就有大量用户同时下载该应用程序时,这尤其成问题。

Windows Installer (MSI)

正如上一节所述,ClickOnce技术提供了一种简单易用的方式来部署各种应用程序。对于更复杂的部署,您可以使用 Window Installer 技术。Window Installer 项目是高度可配置的,它允许您在客户端计算机上创建目录、复制文件、修改注册表以及在安装过程中执行自定义操作。编译时,Window Installer 项目会生成一个 MSI 包,该包包含应用程序的安装向导。当单击此包时,它会启动应用程序安装向导并安装应用程序。MSI 包可以通过磁盘分发,也可以托管在网站或文件共享上。
 
您可以将安装程序项目添加到您的解决方案中,从 Visual Studio 的“文件”菜单中,选择“添加”,然后单击“新建项目”以打开“添加新项目”对话框。在“项目类型”窗格中,展开“其他项目类型”,然后单击“Visual Studio Installer”。在“模板”窗格中,单击“Setup Project”,然后单击“确定”。
 
安装程序项目包含六个编辑器,可用于配置安装程序项目的内容和行为。您可以通过右键单击“解决方案资源管理器”中的安装程序项目并从“视图”菜单中选择相应的编辑器来打开这些编辑器中的任何一个。这六个编辑器是:
  • 文件系统编辑器:此编辑器允许您配置安装程序中包含哪些文件。
  • 注册表编辑器:此编辑器可用于在安装时向注册表添加条目。
  • 文件类型编辑器:此编辑器允许您设置应用程序和文件类型之间的文件关联。
  • 用户界面编辑器:此编辑器允许您为常规安装和管理安装配置安装过程中看到的界面。
  • 自定义操作编辑器:此编辑器允许您定义将在安装过程中执行的自定义操作。
  • 启动条件编辑器:此编辑器允许您设置启动安装所需的条件。

使用文件系统编辑器将文件添加到安装程序项目

文件系统编辑器表示客户端计算机的文件系统。您可以将输出文件添加到各种目录、创建新目录以及在客户端计算机上添加快捷方式。

图 24:文件系统编辑器表示客户端计算机上的文件系统。

如图上所示,文件系统编辑器分为两个窗格。左窗格表示客户端计算机的目录结构。左窗格中的每个文件夹都表示在安装时存在或将要创建的客户端计算机上的一个文件夹。右窗格显示左窗格中选定目录的内容。最初,文件系统编辑器包含三个文件夹:

  • 应用程序文件夹
  • 用户桌面
  • 用户程序菜单。

您可以通过选择右窗格中的文件并将其拖到相应的文件夹来更改特定文件的文件夹。您可以右键单击左窗格并选择“添加特殊文件夹”来向文件系统编辑器添加文件夹。使用此菜单,您可以向文件系统编辑器添加一个特殊文件夹或创建自己的自定义文件夹。如果您选择创建自定义文件夹,则在安装时会在客户端计算机上创建该文件夹。

要将 PDF 分割和合并项目中的文件添加到安装程序项目,我右键单击文件系统编辑器左窗格中的“应用程序文件夹”,然后选择“添加”,再选择“项目输出”。生成的对话框如下所示。

图 25:主输出包含您的应用程序的所有 .exe 和 .dll 文件。

我将“主输出”添加到了应用程序文件夹中,其中包含 PDF 分割和合并项目的所有 .exe 和 .dll 文件。您还可以将其他项目文件添加到安装程序项目中,例如本地化资源、内容文件、文档、调试符号、源文件或可扩展标记语言 (XML) 序列化程序集。选择要添加到应用程序文件夹的输出后,单击“确定”以确认您的选择。

为安装程序添加快捷方式图标

我创建了一个指向 PDF 分割和合并工具可执行文件的快捷方式,并将其添加到了目标计算机。要创建快捷方式,请转到文件系统编辑器右窗格,右键单击要为其创建快捷方式的文件,然后选择“创建快捷方式”。将创建一个指向该文件的快捷方式并添加到窗格中。将快捷方式从右侧窗格拖到左侧窗格中的相应文件夹,在我的例子中,我将其添加到了用户桌面文件夹。

此外,您可以使用文件系统编辑器将图标与应用程序的快捷方式关联。执行此操作时,应用程序的快捷方式将显示您指定的图标。要添加图标,请转到文件系统编辑器右窗格,右键单击一个文件夹(在我的例子中,我选择了应用程序文件夹),选择“添加”,然后选择“文件”。将打开“添加文件”对话框。接下来,浏览到要与应用程序快捷方式关联的 .ico 文件,然后单击“添加”将其添加到安装程序项目中。要将图标与本节开头创建的快捷方式关联,请在文件系统编辑器左窗格中选择包含快捷方式的文件夹。选择文件夹后,快捷方式将出现在文件系统编辑器右窗格中。右键单击文件系统编辑器右窗格中的快捷方式条目,选择“属性”窗口,然后选择“图标”属性,然后从下拉列表中选择(浏览…)。浏览到要与应用程序关联的图标,选择图标,然后单击“确定”。

用户界面编辑器

用户界面编辑器允许您配置安装向导界面。如下所示,用户界面编辑器包含两个部分:“安装”和“管理”部分。“安装”部分包含当用户运行安装程序包时将显示的对话框。管理安装是 Microsoft Windows Installer 的一项功能,允许您在网络共享上安装应用程序的源映像。有权访问网络共享的用户随后可以从源映像安装应用程序。可以使用 /a 命令行选项(msiexec /a setup)通过命令行启动管理安装。有关 msiexec 命令行选项的详细信息,请参阅此处

图 26:用户界面编辑器允许您配置安装向导界面。

使用用户界面编辑器,您可以通过更改应用程序安装过程中显示的邮件和图片来定制安装向导。可以通过从安装向导中添加或删除对话框来进一步定制安装向导。要删除对话框,请右键单击用户界面编辑器中的对话框并选择删除。可以通过右键单击根节点(例如“开始”)并选择“添加对话框”来向安装向导添加对话框。有关如何向安装向导添加新对话框的示例,请参阅此处

有关定制安装向导的另一个示例,请参阅此处。在此示例中,安装文件夹在代码中设置,并且从安装向导中删除了“文件夹”对话框。硬编码安装文件夹的替代方法是在管理安装期间设置安装文件夹,以便管理员选择一个应用程序用户在从网络共享安装时无法覆盖的安装文件夹。

基于操作系统版本的条件安装

您可以使用 VersionNT 系统属性在安装时确定操作系统,并创建安装条件,允许安装根据操作系统继续或中止。VersionNT 属性是一个整数,由以下公式计算:MajorVersion * 100 + MinorVersion。因此,Microsoft Windows 7 将报告 VersionNT 值为 601(MajorVersion = 6,MinorVersion = 1),如此处所述。要配置基于操作系统版本的条件安装,请转到文件系统编辑器左窗格并选择应用程序文件夹。接下来,转到文件系统编辑器右窗格并选择包含应用程序主输出的文件。在属性窗口中,选择“条件”属性,然后键入一个根据前面描述的公式评估操作系统的条件。因为我希望将安装限制在 Windows 7 或更高版本,所以我使用了条件 VersionNT >= 601。

安装程序项目属性

有一些项目属性可以提供有关您的应用程序的描述性信息。这些属性包括:

  • AddRemoveProgramsIcon:指定“控制面板”的“添加/删除程序”菜单的图标。
  • Author:包含有关程序作者的信息。
  • Description:包含应用程序的描述。
  • Keywords:包含要与应用程序关联的关键字。
  • Localization:提供应用程序的区域设置信息。
  • Manufacturer:包含有关应用程序制造商的信息,该信息用于
    定义“程序文件”文件夹内的默认安装文件夹。
  • ManufacturerURL:包含制造商网站的 URL。
  • ProductName:包含产品名称。
  • Subject:包含有关应用程序主题的信息。
  • SupportPhone:提供应用程序支持的电话号码。
  • SupportURL:包含应用程序支持的 URL。
  • TargetPlatform:指定安装程序的目標平台:x86、x64 或 Itanium。
  • Title:包含应用程序的标题。

安装程序项目的其他属性决定了安装程序项目在安装时的行为。这些属性包括:

  • InstallAllUsers:指定包是为所有用户安装还是仅为安装用户安装。
  • PostBuildEvent:指定生成结束后执行的命令行。
  • PreBuildEvent:指定生成开始前执行的命令行。
  • RunPostBuildEvent:指定后置生成事件运行的条件。
    值为“生成成功”或“始终”。
  • SearchPath:指定在开发计算机上用于查找程序集、文件或合并模块的路径。

安装程序的升级行为由下图所示的属性确定。使用以下设置,安装程序将删除应用程序的任何旧版本并安装新版本。    

  • RemovePreviousVersion:应设置为 true 以删除应用程序的先前版本。
  • DetectNewerInstalledVersion:应设置为 true 以查找目标计算机上应用程序的更新版本,如果找到,则中止安装。
  • Version:制作新版本时应递增版本号。系统将提示您更改产品代码,单击“是”进行确认。

ProductCodeUpgradeCode 属性由安装程序使用,不应手动更改。您可以通过单击浏览按钮,然后单击“新建代码”按钮来在设计时更改这些属性。

必备组件

所有使用 Visual Studio 2015 创建的应用程序都需要 .NET Framework 4.5 才能运行。您可以配置安装程序项目,以在安装过程中安装必备组件,例如 .NET Framework。正如在必备组件列表中所见,.NET Framework 已默认配置为安装。通过右键单击安装程序项目并选择“属性”,然后打开属性对话框并单击“必备组件”以打开“必备组件”对话框,可以显示必备组件列表。

自定义操作

虽然在大多数情况下,标准安装设置足以配置安装程序,但自定义操作允许您通过包含可执行文件、动态链接库 (DLL) 和脚本来扩展安装程序的功能。由于这种灵活性,自定义操作可用于多种任务,例如填充数据库重启后台服务、更改安装目录的权限。

自定义操作编辑器允许您配置可在安装应用程序的各个步骤中执行的自定义操作。自定义操作可以在四个 Installer 事件上执行:InstallCommitRollbackUninstallInstall 操作发生在文件安装完成后但安装提交之前。Commit 操作发生在安装在客户端计算机上提交时。Rollback 操作在安装失败并被回滚时执行,Uninstall 操作在应用程序被卸载时执行。您可以使用自定义操作编辑器将任意数量的自定义操作与这四个 Installer 事件关联起来。在图 27 中,OpenUrl.dll 作为自定义操作添加到了 InstallCommit 事件。此 DLL 的创建和配置将在本节后面讨论。

图 27:自定义操作编辑器允许您配置可在安装过程中执行的自定义操作。

如代码片段 19 所示,InstallerHelper 类继承自 Installer 抽象类,因此可以重写 InstallCommitRollbackUninstall 方法。当您在自定义操作编辑器中时,可以通过选择自定义操作然后在属性窗口中设置属性来配置自定义操作。可配置的自定义操作属性如下所示:

  • Name:所选自定义操作的名称。
  • Arguments:可以传递给自定义操作的命令行参数。此属性仅在自定义操作实现为可执行文件时适用。
  • Condition:一个 boolean 语句,将在执行自定义操作之前进行评估。如果语句为 True,则执行自定义操作。如果语句为 False,则不执行该操作。
  • CustomActionData:将任何其他必需的数据传递给自定义操作。
  • EntryPoint:指定要执行的方法的名称。如果留空,自定义操作将尝试执行与它关联的事件同名的函数。此属性仅适用于在动态链接库 (DLL) 中实现的自定义操作,当 InstallerClass 属性设置为 True 时将被忽略。
  • InstallerClass:一个boolean值,表示您的自定义操作是否在Installer类中实现。如果自定义操作是在Installer类中实现的,则此属性必须为True,否则为False
  • SourcePath:包含自定义操作实现文件在开发人员计算机上的路径。此属性是只读的。

您可以将安装向导中输入的参数传递给自定义操作。例如,安装目标目录存储在变量TARGETDIR中。此变量可以作为CustomActionData参数传递给Commit操作。在Commit操作中,可以通过上下文参数和选择的名称检索传递的参数,如下所示。

 

图 28:您可以将参数作为 CustomActionData 参数传递给 Commit 操作。

可以通过上下文参数和名称ESM_TargetDir来检索传递的CustomActionData参数。

[System.Security.Permissions.SecurityPermission(System.Security.Permissions.SecurityAction.Demand)]
public override void Commit(IDictionary savedState)
{
    base.Commit(savedState);
    string TargetDir = Context.Parameters["ESM_TargetDir"];
}

代码片段 17:可以使用名称 ESM_TargetDir 来检索 CustomActionData 参数。

正如您在下面所示的InstallerHelper类中可能注意到的,传递给install事件处理程序的参数名称与传递给其他三个事件处理程序的参数名称不同。传递给安装事件处理程序的stateSaver字典允许您保存可以在其他三个安装事件中共享的参数。例如,您可以将 [TARGETDIR] 参数保存在Install事件处理程序中,并在其他三个事件处理程序中检索它,如下所示。

//Save the installation directory in dictionary  
public override void Install(System.Collections.IDictionary stateSaver) 
{ 
   base.Install(stateSaver); 
   stateSaver.Add("SavedDir", Context.Parameters["ESM_TargetDir"].ToString()); 
}

//Retrieve the installation directory from dictionary. 
public override void Commit(System.Collections.IDictionary savedState) 
{ 
   base.Commit(savedState);
   string installDir = savedState["SavedDir"].ToString();   
}

代码片段 18:stateSaver 字典与其他三个事件处理程序共享。

使用此方法,[TARGETDIR] 参数将作为CustomActionData参数传递给Install事件处理程序,然后传递的参数保存在stateSaver字典中,该字典与其他三个事件处理程序共享。

为了说明自定义操作,我创建了一个自定义操作 DLL,以便在安装结束时将用户重定向到一个网页。要创建自定义操作 DLL,我执行了以下步骤。

  • 右键单击解决方案,选择添加->新建项目,然后选择 c# 类库。将项目命名为 OpenUrl,然后单击确定,之后项目将添加到解决方案资源管理器中。
  • 删除默认的Class1.cs,并向项目中添加一个新的Installer类。您可以通过右键单击 OpenUrl 项目,选择添加->类,然后选择Installer类来添加一个新的Installer类。将类命名为InstallerHelper,然后单击确定,之后该类将添加到 OpenUrl 项目中。
  • 将以下代码添加到InstallerHelper类中,然后构建库。
public partial class InstallerHelper : System.Configuration.Install.Installer
{
    public InstallerHelper()
    {
        InitializeComponent();
    }

    [System.Security.Permissions.SecurityPermission(System.Security.Permissions.SecurityAction.Demand)]
    public override void Install(IDictionary stateSaver)
    {
        base.Install(stateSaver);
    }

    [System.Security.Permissions.SecurityPermission(System.Security.Permissions.SecurityAction.Demand)]
    public override void Commit(IDictionary savedState)
    {
        base.Commit(savedState);
        System.Diagnostics.Process.Start("https://codeproject.org.cn/Articles/1040410/
                                          PDF-Split-and-Merge-Tool-using-itextsharp");
    }

    [System.Security.Permissions.SecurityPermission(System.Security.Permissions.SecurityAction.Demand)]
    public override void Rollback(IDictionary savedState)
    {
        base.Rollback(savedState);
    }

    [System.Security.Permissions.SecurityPermission(System.Security.Permissions.SecurityAction.Demand)]
    public override void Uninstall(IDictionary savedState)
    {
        base.Uninstall(savedState);
    }
}

代码片段 19:InstallerHelper 类的实现。

成功构建自定义操作 DLL 后,需要将其添加到installer项目中。应以与向项目添加应用程序文件相同的方式将自定义操作 DLL 添加到installer项目中。要将自定义操作 DLL 添加到设置项目,请右键单击文件系统编辑器左侧窗格中的应用程序文件夹,然后选择添加,再选择项目输出,选择 OpenUrl 项目的主输出,然后单击确定。所示的是结果项目输出对话框。

图 29:将自定义操作 DLL 添加到安装程序项目。

要将自定义操作添加到installer项目,我执行了以下步骤。

  • 右键单击安装程序项目,然后选择视图->自定义操作。然后将显示自定义操作编辑器。
  • 在自定义操作编辑器中,右键单击 Commit 节点,然后选择添加自定义操作,并选择 OpenUrl 的主输出,如下所示。

图 30:将自定义操作 DLL 添加为自定义操作。

  • 在属性窗口中,确保InstallerClass属性设置为True(这是默认值)。
  • 最后,右键单击设置项目并选择生成以创建设置包。

在执行设置包时,我遇到了以下错误消息:自定义操作 - 错误 1001:找不到文件 OpenUrl.InstallState。此错误是由于缺少通常在安装程序安装事件中创建的安装状态文件引起的。解决方案是将自定义操作添加到InstallCommitInstaller事件中,尽管在Install事件中不执行任何操作。

由于自定义操作是执行代码,因此您还需要处理自定义操作中的错误。您可以使用 try/catch 块来捕获和纠正任何可以纠正的错误。如果发生无法纠正的错误(例如,文件丢失),则引发新的InstallException异常。引发InstallException异常会导致安装回滚,而不会对客户端计算机产生任何持久影响。以下示例演示了如何检查文件是否存在,并在找不到文件时引发新的InstallException异常。代码需要添加到Installer类中。

System.IO.FileInfo fileInfo = new System.IO.FileInfo("Readme.txt");
if(!(fileInfo.Exists))
    throw new System.Configuration.Install.InstallException("Readme file not found");

代码片段 20:引发 InstallException 异常会导致安装回滚。

或者,您可以使用 try/catch,如下所示。

try
{
   //Code
}
catch (Exception ex)
{
   throw new System.Configuration.Install.InstallException("unexpected error occurred", ex);
}

代码片段 21:用于处理所有发生异常的通用 try/catch。

如前所述,如果在installer中引发了InstallException,则安装过程将回滚,并向用户显示一个弹出框,其中包含错误的描述。此外,错误会记录在计算机的事件日志中,您可以通过控制面板的管理工具打开该日志。

本地化 MSI 软件包的部署

有几种策略可用于使用 Windows Installer 部署本地化应用程序。例如,您可以使用SystemLanguageID属性进行本地化资源的条件安装。有关此方法的详细信息,请参阅本文。Microsoft 在本文中详细解释了另一种本地化策略。

使用 signtool.exe 进行 MSI 签名

您可以使用 SignTool.exe 对生成的 MSI 包进行签名。要对 MSI 包进行签名,我使用了之前在 ClickOnce 部分创建的私钥。使用 ApplicationSigning.pfx 私钥,可以按如下方式对 MSI 包进行签名。

signtool sign /fd SHA512 /t http://timestamp.digicert.com /f ApplicationSigning.pfx /p PfxPassword Setup.msi
Done Adding Additional Store
Successfully signed: Setup.msi
*) Note that the absolute paths to the files are omitted for clarity.

代码片段 22:用于签名 MSI 包的签名命令。

通过右键单击 MSI 包并单击属性,可以验证 MSI 包是否已正确签名。数字签名选项卡显示 MSI 包的签名和时间戳。

图 31:MSI 包的签名属性。

安全

安装和卸载 MSI 包都需要提升的权限。要以提升的权限运行,MSI 包必须由本地管理员部署,或者由系统管理员通过组策略进行广告。

应用程序位置

TARGETDIR属性指定安装目录。在管理员安装(msiexec /a setup)期间,TARGETDIR属性指定安装包复制到的位置。在普通安装期间,此属性指定安装目录。

   

图 32:Windows Installer 应用程序的默认安装位置。

安装和卸载应用程序

所有 Windows Installer 操作都是事务性的。这意味着 Windows Installer 会创建一个文件,其中详细说明了安装期间执行的所有操作。对于 Windows Installer 执行的每个操作,它都会生成一个等效的撤销操作,该操作将回滚对客户端计算机所做的更改。如果安装失败或用户取消了安装,则在当时执行的所有操作都将被回滚,将客户端计算机恢复到其原始状态。应注意,当您创建更改客户端计算机状态的自定义操作时,还应创建相应的回滚操作以及卸载操作。

ClickOnce 和 Windows Installer 的比较

对于没有复杂安装要求的简单应用程序,ClickOnce 是一项出色的部署技术,它提供了有价值的优势,例如自动更新和易用性。对于具有更复杂要求的应用程序,Windows Installer 仍然是首选的部署技术,因为它高度可配置。下表说明了这两种技术之间的区别。

表 1:ClickOnce 和 Windows Installer 的比较。

关注点

如果您无法使用 Visual Studio 部署您的 ClickOnce 应用程序,您可以使用 Mage.exe 或 MageUi.exe 来部署您的应用程序,有关如何执行此操作的详细信息可以在此处找到。

您可以通过创建具有管理员权限的单独进程来提升 ClickOnce 应用程序的安装权限。详细信息可以在本文本文中找到。

您还可以使用自定义代码自动更新 Windows Installer 应用程序,详细信息可以在此处找到。

从 Internet Explorer 安装 ClickOnce 应用程序非常简单。只需浏览到 publish.htm 页面并单击启动链接。这将立即启动安装过程。如果您想从其他浏览器(如 Chrome)安装 ClickOnce 应用程序,则需要至少多一个步骤。下载完成后,您需要显式启动文件以开始安装过程。Chrome 提供了一个插件,可以实现与 Internet Explorer 类似的 ClickOnce 安装行为,您可以在此处下载。

托管在 Github 上的 publish.htm 页面通过网站显示给用户。

使用自签名证书的一个缺点是,它们无法通过 CRL 进行吊销。

可以用于配置 ClickOnce 部署的 Mage.exe 和 MageUi.exe 工具可以在以下目录中找到:“C:\Program Files\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.6 Tools\”。

证书签名工具(如 signtool.exe 和 makecert.exe)可以在以下目录中找到:“C:\Program Files\Windows Kits\10\bin\x86\”。

参考文献列表

  1. MCTS 自学培训套件考试 70-511
  2. Apress Pro WPF 4.5 in Csharp 第 4 版
  3. 本地化 ClickOnce 应用程序的示例。

历史

  • 2015 年 12 月 19 日:版本 1.0.0.0 - 创建了文章
© . All rights reserved.