基于C#实现的软件自动更新程序,之前在网上搜集了两款软件自动更新程序,在实际应用中,对部分BUG进行修复,添加+完善一些功能。这里,推荐给大家。代码完全开放,可以根据实际应用场景,作调整。
先来看看第一款软件自动更新程序的效果图吧:
它的原理非常简单,它是根据文件的日期,进行比较,然后在服务端修改生成一个XML文件,软件在启动的时候,验证是否更新,如果需要更新,则终止主程序,启动自动更新程序,更新成功后,再启动主程序。
它的大体结构是这样的:
1、需要一个网站,把需要更新的文件都存放在这个网站下的UpdateServer文件夹,它自身提供了一个生成更新配置文件的UpdateListBuilder.exe工具,只需点击执行exe,便可在服务端生成更新所需的xml配置文件。格式如下:
<AutoUpdater>
<UpdateInfo>
<UpdateTime Date="2014-2-21" />
<Version Num="1.0.0.0" />
<UpdateSize Size="32046" />
</UpdateInfo>
<UpdateFileList>
<UpdateFile>\K3SP.exe</UpdateFile>
<UpdateFile>\conf\menu.xml</UpdateFile>
</UpdateFileList>
</AutoUpdater>
这里的K3SP.exe是主程序exe。
2、主程序添加如下代码:
/// <summary>
/// 检测更新
/// </summary>
private void checkUpdate()
{
strUpdateURL = getConfigValue(strUpdateXmlPath, "Url"); //读取本地xml中配置的更新服务器的URL
string strLastUpdateDate = getConfigValue(strUpdateXmlPath, "UpDate"); //读取本地xml中配置的最近一次更新日期
if (strUpdateURL.Substring(strUpdateURL.Length - 1) != "/") //如果配置的xml中URL没带最后一个反斜杠,则加一下,防止出错
strUpdateURL += "/";
strTheUpdateDate = getTheLastUpdateTime(strUpdateURL); //获得更新服务器端的此次更新日期
if (!String.IsNullOrEmpty(strTheUpdateDate) && !String.IsNullOrEmpty(strLastUpdateDate)) //日期都不为空
{
if (DateTime.Compare(
Convert.ToDateTime(strTheUpdateDate, CultureInfo.InvariantCulture),
Convert.ToDateTime(strLastUpdateDate, CultureInfo.InvariantCulture)) > 0) //字符转日期,并比较日期大小
{
//本次更新日期 大于 最近一次更新日期,开始更新
try
{
if (new K3SP.lib.ClassCheckProIsRun().checkProcess(strUpdaterProFileName, strUpdaterProPath))
{
classMsg.messageInfoBox("更新程序" + strUpdaterProFileName + "已打开!");
}
else
{
Process.Start(strUpdaterProPath);
}
}
catch (Win32Exception ex)
{
classMsg.messageInfoBox(ex.Message); //主程序未更新成功或者被误删掉,再更新一遍
}
Application.Exit(); //退出主程序
}
}
}
在窗体加载事件,调用checkUpdate方法进行检测更新。
再来看看第二种自动更新的效果吧:
该工具自身是英文的,在此基础上,进行了部分汉化,又增加了AutoUpdateBuilder自动生成更新配置文件的程序,方便我们在服务端维护。
这里,我做了个简单的demo程序。为使用改程序,我还新加了一个AutoUpdaterTool的工具,来进行软件的升级更新。
先来,看看AutoUpdaterTool都做了些什么?
/// <summary>
/// 应用程序的主入口点。
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
//Application.Run(new Form1());
#region check and download new version program
bool bHasError = false;
IAutoUpdater autoUpdater = new KnightsWarriorAutoupdater.AutoUpdater();
//主程序执行路径
string strExePath = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "AutoUpdaterDemo.exe");
try
{
if (new ClassCheckProIsRun().checkProcessForProName(strExePath))//检测主程序是否执行
{
MessageBox.Show(@"进程中检测到主程序正在运行,请先关闭才可更新。");
return;
}
autoUpdater.Update();
}
catch (WebException exp)
{
MessageBox.Show(@"不能够找到指定的资源!");
bHasError = true;
}
catch (XmlException exp)
{
bHasError = true;
MessageBox.Show(@"下载升级文件时发生错误!");
}
catch (NotSupportedException exp)
{
bHasError = true;
MessageBox.Show(@"更新地址配置错误");
}
catch (ArgumentException exp)
{
bHasError = true;
MessageBox.Show(@"下载升级文件时发生错误");
}
catch (Exception exp)
{
bHasError = true;
MessageBox.Show(@"升级过程中发生错误!");
}
finally
{
if (bHasError == true)
{
try
{
autoUpdater.RollBack();
}
catch (Exception)
{
//Log the message to your file or database
}
}
}
#endregion
System.Diagnostics.Process.Start(strExePath, ((KnightsWarriorAutoupdater.AutoUpdater)autoUpdater).isCancel.ToString());
Application.Exit();
}
我建了一个Winform程序,不要窗口,检测更新程序,都写在了Main函数里。
接下来,在看看AutoUpdaterDemo示例程序的检测方法:
/// <summary>
///
/// </summary>
public bool IsCancelUpdate { get; set; }
/// <summary>
/// 检测更新
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Form1_Load(object sender, EventArgs e)
{
if (!IsCancelUpdate) CheckUpdate();
}
/// <summary>
/// 自动更新程序路径
/// </summary>
private readonly string autoUpdaterPath = AppDomain.CurrentDomain.BaseDirectory + "AutoUpdaterTool.exe";
/// <summary>
/// 检测更新程序
/// </summary>
void CheckUpdate()
{
try
{
IAutoUpdater autoUpdater = new KnightsWarriorAutoupdater.AutoUpdater();
if (autoUpdater.CheckUpdate())
{
if (new lib.ClassCheckProIsRun().checkProcess(autoUpdaterPath))
{
MessageBox.Show(@"更新程序已经运行!");
return;
}
System.Diagnostics.Process.Start(autoUpdaterPath);
Application.Exit();
}
}
catch (Exception exception)
{
MessageBox.Show(exception.Message);
}
}
注意:在这里,我给窗体新加了一个IsCancelUpdate的属性,这个为下面的取消更新做了铺垫。
这个AutoUpdaterDemo的入口函数,我添加了参数,来实现取消更新的功能。
/// <summary>
/// 应用程序的主入口点。
/// </summary>
[STAThread]
static void Main(string[] args)
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
var mainForm = new Form1();
if (args != null && args.Length > 0) mainForm.IsCancelUpdate = Convert.ToBoolean(args[0]);
Application.Run(mainForm);
}
妙处就在这里。为什么要这么做?因为自动更新程序和主程序,是两个进程,我们在主程序里检测更新,如果客户端取消更新,又会启动主程序,主程序又会检测更新,这样形成了一个死循环。
这个参数,是通过自动更新工具AutoUpdaterTool.exe来传递的,代码如下:
/// <summary>
/// 应用程序的主入口点。
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
//Application.Run(new Form1());
#region check and download new version program
bool bHasError = false;
IAutoUpdater autoUpdater = new KnightsWarriorAutoupdater.AutoUpdater();
//主程序执行路径
string strExePath = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "AutoUpdaterDemo.exe");
try
{
if (new ClassCheckProIsRun().checkProcessForProName(strExePath))//检测主程序是否执行
{
MessageBox.Show(@"进程中检测到主程序正在运行,请先关闭才可更新。");
return;
}
autoUpdater.Update();
}
catch (WebException exp)
{
MessageBox.Show(@"不能够找到指定的资源!");
bHasError = true;
}
catch (XmlException exp)
{
bHasError = true;
MessageBox.Show(@"下载升级文件时发生错误!");
}
catch (NotSupportedException exp)
{
bHasError = true;
MessageBox.Show(@"更新地址配置错误");
}
catch (ArgumentException exp)
{
bHasError = true;
MessageBox.Show(@"下载升级文件时发生错误");
}
catch (Exception exp)
{
bHasError = true;
MessageBox.Show(@"升级过程中发生错误!");
}
finally
{
if (bHasError == true)
{
try
{
autoUpdater.RollBack();
}
catch (Exception)
{
//Log the message to your file or database
}
}
}
#endregion
System.Diagnostics.Process.Start(strExePath, ((KnightsWarriorAutoupdater.AutoUpdater)autoUpdater).isCancel.ToString());
Application.Exit();
就在这段代码的最后,启动这个进程的时候,我把取消isCancel这个属性传递给主程序。
isCancel是我为AutoUpdater对象添加的一个属性。
另外,我还为IAutoUpdater接口,公开了CheckUpdate方法,用来在AutoUpdaterDemo中,调用该方法,检测是否需要更新。
再来看看,为自动生成更新配置文件的程序。注意:要想支持文件夹的更新,需要简单的修改下代码即可。
using System;
using System.Collections.Generic;
using System.Configuration;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
/******************************************************************************************************************
*
*
* 说 明: 生成自动更新配置文件(版本:Version1.0.0)
* 作 者:李朝强
* 日 期:2015/05/19
* 修 改:
* 参 考:http://my.oschina.net/lichaoqiang/
* 备 注:暂无...
*
*
* ***************************************************************************************************************/
namespace AutoUpdaterBuilder
{
class Program
{
/// <summary>
/// <![CDATA[主程序入口]]>
/// </summary>
/// <param name="args"></param>
static void Main(string[] args)
{
Console.Title = "更新文档生成工具!";
try
{
//远程服务器URL
string strServerUrl = System.Configuration.ConfigurationManager.AppSettings["ServerUrl"];
//创建Xml文档
XmlDocument xml = new XmlDocument();
var declaration = xml.CreateXmlDeclaration("1.0", "utf-8", null);
xml.AppendChild(declaration);
var root = xml.CreateElement("UpdateFileList");//根节点
string strBaseDirectory = AppDomain.CurrentDomain.BaseDirectory;//应用程序根目录
string[] strFiles = Directory.GetFiles(strBaseDirectory);//获取目录下所有文件
//版本信息
Version v = new Version(System.Configuration.ConfigurationManager.AppSettings["Version"]);
Version theNewVersion = new Version(v.Major, v.Minor, v.Build, v.Revision + 1);//生成新的版本号
List<FileInfo> updataFileInfos = new List<FileInfo>();//更新文件列表
//循环更新文件
foreach (string strfile in strFiles)
{
if (strfile.Contains("AutoUpdaterBuilder.exe") ||
strfile.EndsWith(".xml") ||
strfile.EndsWith(".config")) continue;
//文件名
FileInfo file = new FileInfo(strfile);
var localFile = xml.CreateElement("LocalFile");
localFile.SetAttribute("path", file.Name);
localFile.SetAttribute("url", string.Format("{0}/{1}", strServerUrl, file.Name));
localFile.SetAttribute("lastver", theNewVersion.ToString());
localFile.SetAttribute("size", file.Length.ToString());
localFile.SetAttribute("needRestart", "false");
root.AppendChild(localFile);
updataFileInfos.Add(file);
ShowMessge("正在创建文件“{0}”的更新配置", file.Name);
}
xml.AppendChild(root);
string strSaveToPath = System.IO.Path.Combine(strBaseDirectory, "AutoupdateService.xml");//配置文件保存路径
xml.Save(strSaveToPath);
//更新日志
string[] strUpdateLogs = Array.ConvertAll(updataFileInfos.ToArray(), (t) => string.Format("[{0}] {1}", t.LastWriteTime.ToString("yyyy/MM/dd HH:mm:ss"), t.Name));
File.WriteAllLines(Path.Combine(strBaseDirectory, "UpdateLog.txt"), strUpdateLogs);
//修改配置文件
ExeConfigurationFileMap fileMap = new ExeConfigurationFileMap();
fileMap.ExeConfigFilename = System.IO.Path.Combine(strBaseDirectory, "AutoUpdaterBuilder.exe.config");
System.Configuration.Configuration configuration = System.Configuration.ConfigurationManager.OpenMappedExeConfiguration(fileMap, ConfigurationUserLevel.None);
configuration.AppSettings.Settings["Version"].Value = theNewVersion.ToString();
configuration.Save(ConfigurationSaveMode.Modified);
Console.WriteLine("更新文件生成成功,2s后退出!");
System.Threading.Thread.Sleep(2000);//线程休眠2s
}
catch (Exception exception)
{
Console.WriteLine(exception.Message);
Console.ReadLine();
}
}
/// <summary>
/// <![CDATA[输出消息]]>
/// </summary>
/// <param name="format"></param>
/// <param name="args"></param>
static void ShowMessge(string format, params object[] args)
{
Console.WriteLine(format, arg: args);
}
/// <summary>
///
/// </summary>
/// <param name="format"></param>
/// <param name="args"></param>
static void Log(string format, params object[] args)
{
}
}
}
生成后的xml文件
<?xml version="1.0" encoding="utf-8"?>
<UpdateFileList>
<LocalFile path="AutoUpdater.dll" url="http://localhost:8833//AutoUpdater.dll" lastver="1.0.0.8" size="43520" needRestart="false" />
<LocalFile path="AutoUpdaterDemo.exe" url="http://localhost:8833//AutoUpdaterDemo.exe" lastver="1.0.0.8" size="9728" needRestart="false" />
<LocalFile path="phpStudy.rar" url="http://localhost:8833//phpStudy.rar" lastver="1.0.0.8" size="45633237" needRestart="false" />
<LocalFile path="update.txt" url="http://localhost:8833//update.txt" lastver="1.0.0.8" size="45" needRestart="false" />
<LocalFile path="新增测试文件.txt" url="http://localhost:8833//新增测试文件.txt" lastver="1.0.0.8" size="0" needRestart="false" />
</UpdateFileList>
怎样部署?
很简单,首先,我们只需要建一个网站,把AutoUpdaterBuilder.exe和AutoUpdaterBuilder.exe.config文件放进去,然后再把需要更新的文件,放入改文件夹下。
<appSettings>
<remove key="ServerUrl"/>
<remove key="Version"/>
<add key="ServerUrl" value="http://localhost:8833/"/>
<add key="Version" value="1.0.0.1"/>
</appSettings>
以上是AutoUpdaterBuilder.exe.config配置文件。
其次,在主程序上添加对AutoUpdater.dll的引用,主要是为了调用它的检测更新方法。
今天就写到这吧,感兴趣的朋友,可以亲手尝试下。