Skip to content

Instantly share code, notes, and snippets.

@walterlv
Created February 27, 2019 02:37
Show Gist options
  • Save walterlv/33bdd62e2411c69c2699038e2bc97488 to your computer and use it in GitHub Desktop.
Save walterlv/33bdd62e2411c69c2699038e2bc97488 to your computer and use it in GitHub Desktop.
这是一个简单的自更新程序的类。使用此类型,程序初次运行的时候会安装自己,如果已安装旧版本会更新自己,如果已安装最新则直接运行。
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