Created
February 27, 2019 02:37
-
-
Save walterlv/33bdd62e2411c69c2699038e2bc97488 to your computer and use it in GitHub Desktop.
这是一个简单的自更新程序的类。使用此类型,程序初次运行的时候会安装自己,如果已安装旧版本会更新自己,如果已安装最新则直接运行。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment