内容简介:本文主要说的是 .NET 客户端应用,可以是只能在 Windows 端运行的基于 .NET Framework 或基于 .NET Core 的 WPF / Windows Forms 应用,也可以是其他基于 .NET Core 的跨平台应用。但是不是那些更新权限受到严格控制的 UWP / iOS / Android 应用。本文将编写一个简单的程序,这个程序初次运行的时候会安装自己,如果已安装旧版本会更新自己,如果已安装最新则直接运行。简单的安装过程实际上是
本文主要说的是 .NET 客户端应用,可以是只能在 Windows 端运行的基于 .NET Framework 或基于 .NET Core 的 WPF / Windows Forms 应用,也可以是其他基于 .NET Core 的跨平台应用。但是不是那些更新权限受到严格控制的 UWP / iOS / Android 应用。
本文将编写一个简单的程序,这个程序初次运行的时候会安装自己,如果已安装旧版本会更新自己,如果已安装最新则直接运行。
自安装或自更新的思路
简单的安装过程实际上是 解压 + 复制 + 配置 + 外部命令
。这里,我只做 复制 + 配置 + 外部命令
,并且把 配置 + 外部命令
合为一个步骤。
于是:
- 启动后,检查安装路径下是否有已经安装的程序;
- 如果没有,则直接复制自己过去;
- 如果有,则比较版本号,更新则复制过去。
本文用到的知识
- 在 Windows 系统上降低 UAC 权限运行程序(从管理员权限降权到普通用户权限) - walterlv
- Windows 上的应用程序在运行期间可以给自己改名(可以做 OTA 自我更新) - walterlv
- 仅反射加载(ReflectionOnlyLoadFrom)的 .NET 程序集,如何反射获取它的 Attribute 元数据呢? - walterlv
使用
于是我写了一个简单的类型用来做自安装。创建完 SelfInstaller
的实例后,根据安装完的结果做不同的行为:
- 显示安装成功的窗口
- 显示正常的窗口
- 关闭自己
using System.IO; using System.Windows; using Walterlv.Installing; namespace Walterlv.ENPlugins.Presentation { public partial class App : Application { protected override void OnStartup(StartupEventArgs e) { base.OnStartup(e); var installer = new SelfInstaller(Path.Combine( @"C:\Users\lvyi\AppData\Local\Walterlv", "Walterlv.ENPlugins.Presentation.exe")); var state = installer.TryInstall(); switch (state) { case InstalledState.Installed: case InstalledState.Updated: new InstallTipWindow().Show(); break; case InstalledState.Ran: new MainWindow().Show(); break; case InstalledState.ShouldRerun: Shutdown(); break; } } } }
附全部源码
本文代码在 https://gist.github.com/walterlv/33bdd62e2411c69c2699038e2bc97488 。
using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Security.Principal; namespace Walterlv.Installing { /// <summary> /// 自安装或字更新的安装器。 /// </summary> public class SelfInstaller { /// <summary> /// 初始化 <see cref="SelfInstaller"/> 的新实例。 /// </summary> /// <param name="targetFilePath">要安装的主程序的目标路径。</param> /// <param name="installingProcedure">如果需要在安装后执行额外的安装步骤,则指定自定义的安装步骤。</param> public SelfInstaller(string targetFilePath, IInstallingProcedure installingProcedure = null) { TargetFileInfo = new FileInfo(targetFilePath ?? throw new ArgumentNullException(nameof(targetFilePath))); InstallingProcedure = installingProcedure; } /// <summary> /// 获取要安装的主程序的目标路径。 /// </summary> private FileInfo TargetFileInfo { get; } /// <summary> /// 获取或设置当应用重新启动自己的时候应该使用的参数。 /// </summary> public string RunSelfArguments { get; set; } = "--rerun-reason {reason}"; /// <summary> /// 获取此自安装器安装中需要执行的自定义安装步骤。 /// </summary> public IInstallingProcedure InstallingProcedure { get; } /// <summary> /// 尝试安装,并返回安装结果。调用者可能需要对安装结果进行必要的操作。 /// </summary> public InstalledState TryInstall() { if (!CheckRunningInNormalPrivilege()) { // 降权处理。 Process.Start("explorer.exe", BuildRerunArguments("Privilege", true)); return InstalledState.ShouldRerun; } var state = InstallOrUpdate(); switch (state) { // 已安装或更新,由已安装的程序处理安装后操作。 case InstalledState.Installed: case InstalledState.Updated: case InstalledState.ShouldRerun: Process.Start("explorer.exe", BuildRerunArguments(state.ToString(), true, TargetFileInfo.FullName)); return state; } state = InstallingProcedure?.AfterInstall() ?? InstalledState.Ran; return state; } /// <summary> /// 检查程序是否运行在普通用户权限下(此安装器仅限运行在普通权限)。 /// </summary> private bool CheckRunningInNormalPrivilege() { var identity = WindowsIdentity.GetCurrent(); var principal = new WindowsPrincipal(identity); return !principal.IsInRole(WindowsBuiltInRole.Administrator); } /// <summary> /// 进行安装或更新。执行后将返回安装状态以及安装后的目标程序路径。 /// </summary> private InstalledState InstallOrUpdate() { var extensionFilePath = TargetFileInfo.FullName; var selfFilePath = Assembly.GetExecutingAssembly().Location; // 判断当前是否已经运行在插件目录下。如果已经在那里运行,那么不需要安装。 if (string.Equals(extensionFilePath, selfFilePath, StringComparison.CurrentCultureIgnoreCase)) { // 继续运行自己即可。 return InstalledState.Ran; } // 判断插件目录下的软件版本是否比较新,如果插件目录已经比较新,那么不需要安装。 var isOldOneExists = File.Exists(extensionFilePath); if (isOldOneExists) { var (currentVersion, installedVersion) = GetVersions(); if (currentVersion <= installedVersion) { // 运行已安装目录下的自己。 return InstalledState.ShouldRerun; } } // 将自己复制到插件目录进行安装。 CopySelfToInstall(); return isOldOneExists ? InstalledState.Updated : InstalledState.Installed; (Version currentVersion, Version installedVersion) GetVersions() { Version installedVersion; try { var installed = Assembly.ReflectionOnlyLoadFrom(extensionFilePath); var installedVersionString = installed.GetCustomAttributesData() .FirstOrDefault(x => x.AttributeType.FullName == typeof(AssemblyFileVersionAttribute).FullName) ?.ConstructorArguments[0].Value as string ?? "0.0"; installedVersion = new Version(installedVersionString); } catch (FileLoadException) { installedVersion = new Version(0, 0); } catch (BadImageFormatException) { installedVersion = new Version(0, 0); } var current = Assembly.GetExecutingAssembly(); var currentVersionString = current.GetCustomAttribute<AssemblyFileVersionAttribute>()?.Version ?? "0.0"; var currentVersion = new Version(currentVersionString); return (currentVersion, installedVersion); } } /// <summary> /// 将自己复制到目标安装路径。 /// </summary> private void CopySelfToInstall() { var extensionFolder = TargetFileInfo.Directory.FullName; var extensionFilePath = TargetFileInfo.FullName; var selfFilePath = Assembly.GetExecutingAssembly().Location; try { if (!Directory.Exists(extensionFolder)) { Directory.CreateDirectory(extensionFolder); } File.Copy(selfFilePath, extensionFilePath, true); } catch (IOException) { File.Move(extensionFilePath, extensionFilePath + ".bak"); File.Copy(selfFilePath, extensionFilePath, true); } } /// <summary> /// 生成用于重启自身的启动参数。 /// </summary> /// <param name="rerunReason">表示重启原因的一个单词(不能包含空格)。</param> /// <param name="includeExecutablePath"></param> /// <param name="executablePath"></param> /// <returns></returns> private string BuildRerunArguments(string rerunReason, bool includeExecutablePath, string executablePath = null) { if (rerunReason == null) { throw new ArgumentNullException(nameof(rerunReason)); } if (rerunReason.Contains(" ")) { throw new ArgumentException("重启原因不能包含空格", nameof(rerunReason)); } var args = new List<string>(); if (includeExecutablePath) { args.Add(string.IsNullOrWhiteSpace(executablePath) ? Assembly.GetEntryAssembly().Location : executablePath); } if (!string.IsNullOrWhiteSpace(RunSelfArguments)) { args.Add(RunSelfArguments.Replace("reason", rerunReason)); } return string.Join(" ", args); } } /// <summary> /// 表示安装完后的状态。 /// </summary> public enum InstalledState { /// <summary> /// 已安装。 /// </summary> Installed, /// <summary> /// 已更新。说明运行此程序时,已经存在一个旧版本的应用。 /// </summary> Updated, /// <summary> /// 已代理启动新的程序,所以此程序需要退出。 /// </summary> ShouldRerun, /// <summary> /// 没有执行安装、更新或代理,表示此程序现在是正常启动。 /// </summary> Ran, } }
本文会经常更新,请阅读原文: https://walterlv.com/post/simple-windows-app-self-installer.html ,以避免陈旧错误知识的误导,同时有更好的阅读体验。
本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。欢迎转载、使用、重新发布,但务必保留文章署名 吕毅 (包含链接:https://walterlv.com ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请 与我联系 (walter.lv@qq.com) 。
以上所述就是小编给大家介绍的《制作一个极简的 .NET 客户端应用自安装或自更新程序》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 支付宝客户端架构解析:iOS 客户端启动性能优化初探
- 自己动手做数据库客户端: BashSQL开源数据库客户端
- 支付宝客户端架构解析:Android 客户端启动速度优化之「垃圾回收」
- 客户端HTTP缓存
- 简析移动客户端安全
- 配置Hadoop集群客户端
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
数据结构与算法分析
维斯 / 人民邮电 / 2006-10 / 59.00元
《数据结构与算法分析:C++描述》秉承Weiss著全一贯的严谨风格,同时又突出了实践。书中充分应用了现代C++语言特性,透彻地讲述了数据结构的原理和应用,不仅使学生具备算法分析能力,能够开发高效的程序,而且让学生掌握良好的程序设计技巧。一起来看看 《数据结构与算法分析》 这本书的介绍吧!