桌面应用更新程序
启动时,根据主程序本地的版本文件(LocalVersion.xml),拿到远程更新地址,比较远程配置的版本文件(ServerVersion.xml)
如果有新版本,则判断更新程序是否位于系统盘,且是否为管理员身份运行
如果位于系统盘,且不是管理员身份运行,则重新以管理员身份运行更新重启,操作系统会弹出账号控制提示给客户
如果不是则打开主窗体,提示有新版本可以更新,是否下载更新
(这个机制可以达到无论客户把程序装在系统盘还是其他盘都不会影响更新操作,并且如果客户不是装在系统盘,还可以免去每次检查更新系统都弹出"权限控制"的提示给客户)
下载更新完成后,会自动删除下载的更新包,并且重新启动主程序
在主程序启动时,同时启动更新程序,即可检查更新了,如果客户选中暂时不更新,下次打开还是会提示更新,如果想跳过该版本,那就得在取消的时候做点操作
把远程最新版本号,修改到主程序本地的版本文件(LocalVersion.xml)
底部有代码百度网盘下载地址
首先更新程序的入口做运行身份检查
static class Program
{
/// <summary>
/// 应用程序的主入口点。
/// </summary>
[STAThread]
static void Main(string[] args)
{
var temp = Update.HasNewVersion();
if (Convert.ToBoolean(temp.result))
{
if (Update.IsSystemPath() && ! Update.IsAdministrator())
{
Update.ReStartAppWithAdministrator();
}
else
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1(temp));
}
}
else
{
Environment.Exit(0);
}
}
}
然后是使用WebClient进行文件下载
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.Linq;
using System.Windows.Forms;
using System.IO;
using System.Diagnostics;
using System.Security.Principal;
namespace UpdateApp
{
public class Update
{
WebClient _client;
public Update()
{
_client = new WebClient();
_client.DownloadFileCompleted += (sender, args) => { DownloadFileCompleted?.Invoke(sender, args); };
_client.DownloadProgressChanged += (sender, args) => { DownloadProgressChanged?.Invoke(sender, args); };
}
/// <summary>
/// 在异步文件下载操作完成时发生。
/// </summary>
public Action<object, AsyncCompletedEventArgs> DownloadFileCompleted { get; set; }
/// <summary>
/// 在异步下载操作成功转换部分或全部数据后发生
/// </summary>
public Action<object, DownloadProgressChangedEventArgs> DownloadProgressChanged { get; set; }
/// <summary>
/// 根据名字 关闭进程
/// </summary>
/// <param name="ProcessName"></param>
/// <returns></returns>
public static bool CloseProcess(string ProcessName)
{
bool result = false;
var temp = System.Diagnostics.Process.GetProcessesByName(ProcessName);
foreach (var item in temp)
{
try
{
item.Kill();
result = true;
}
catch
{
}
}
return result;
}
/// <summary>
/// 检查是否有新版本
/// </summary>
/// <returns></returns>
public static dynamic HasNewVersion()
{
try
{
var appPath = Directory.GetParent(Application.StartupPath).FullName;
XElement localxdoc = XElement.Load(appPath + "\\LocalVersion.xml");
var localV = localxdoc.Element("version").Value;
var localU = localxdoc.Element("url").Value;
//应用程序名称
var localAppName = localxdoc.Element("appName").Value;
//更新过程必须关掉的进程列表
var localKillList = localxdoc.Element("killList").Value;
XElement serverxdoc = XElement.Load(localU);
//服务端版本
var serverV = serverxdoc.Element("version").Value;
//更新包地址
var serverU = serverxdoc.Element("url").Value;
//是否必须更新
var serverRU = serverxdoc.Element("requiredUpdate").Value;
var temp2 = Convert.ToInt32(serverV.Replace(".", ""));
var temp3 = Convert.ToInt32(localV.Replace(".", ""));
return new
{
result = temp2 > temp3,
url = serverU,
appName = localAppName,
packName = Path.GetFileNameWithoutExtension(serverU),
killList = localKillList,
requiredUpdate = serverRU
};
}
catch {
return new { result = false };
}
}
/// <summary>
/// 异步下载文件
/// </summary>
/// <param name="param"></param>
public void Download(dynamic param)
{
var savePath = AppDomain.CurrentDomain.BaseDirectory;
var downUrl = param.url.ToString();
_client.DownloadFileAsync(new Uri(downUrl), savePath + param.packName.ToString() + ".zip", param);
}
/// <summary>
/// 判断本程序当前是否运行在系统盘
/// </summary>
/// <returns></returns>
public static bool IsSystemPath()
{
//系统盘路径
string path = Environment.GetEnvironmentVariable("systemdrive");
return Application.StartupPath.StartsWith(path, StringComparison.OrdinalIgnoreCase);
}
/// <summary>
/// 判断本程序当前是否以管理员身份运行
/// </summary>
/// <returns></returns>
public static bool IsAdministrator()
{
WindowsIdentity current = WindowsIdentity.GetCurrent();
WindowsPrincipal windowsPrincipal = new WindowsPrincipal(current);
return windowsPrincipal.IsInRole(WindowsBuiltInRole.Administrator);
}
/// <summary>
/// 以管理员身份重新启动程序
/// </summary>
public static void ReStartAppWithAdministrator()
{
try
{
System.Diagnostics.ProcessStartInfo startInfo = new System.Diagnostics.ProcessStartInfo();
startInfo.UseShellExecute = true;
//startInfo.WorkingDirectory = $@"{AppDomain.CurrentDomain.BaseDirectory}";
startInfo.FileName = $@"{Application.StartupPath}\{Process.GetCurrentProcess().ProcessName}.exe";
//设置启动动作,确保以管理员身份运行
startInfo.Verb = "runas";
System.Diagnostics.Process.Start(startInfo);
}
catch
{
}
}
}
}
文件下载完成后开始,复制新文件到安装目录
/// <summary>
/// 更新包下载完成,开始更新操作
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void DownloadFileCompleted(object sender, AsyncCompletedEventArgs e)
{
if (!e.Cancelled && e.UserState != null)
{
MessageBox.Show(Properties.Resources.DownLoadSuccess, Properties.Resources.NewVersion, MessageBoxButtons.OK);
System.Threading.CancellationTokenSource cts = new System.Threading.CancellationTokenSource();
//解压文件、复制文件、删除文件 做个假进度
Task.Factory.StartNew(() => {
BeginInvoke((Action)(() => {
progressBar1.Minimum = 0;
progressBar1.Maximum = 100;
progressBar1.Value = 0;
label1.Text = "";
}));
Random r = new Random();
while (!cts.Token.IsCancellationRequested)
{
if (progressBar1.Value >= 100)
{
BeginInvoke(new Action(() => { progressBar1.Value = 0; }));
}
else
{
BeginInvoke(new Action(() => { progressBar1.Value += 1; }));
}
System.Threading.Thread.Sleep(r.Next(10, 100));
}
BeginInvoke(new Action(() => { progressBar1.Value = 100; }));
});
Task.Factory.StartNew(() => {
dynamic temp = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(e.UserState));
//杀掉所有需要关闭的进程
var killList = temp.killList.ToString().Split(new string[] { "," }, StringSplitOptions.RemoveEmptyEntries);
foreach (var item in killList)
{
UpdateApp.Update.CloseProcess(item);
}
//获取主程序的所在路径,这里因为是在主程序的安装根目录里放了一个文件夹,文件夹里面放更新程序的结构..\App\App.exe ..\App\UpdateApp\UpdateApp.exe
var appPath = Directory.GetParent(Application.StartupPath).FullName;
//下载到的更新包的所在路径,与更新程序所在位置相同
var packPath = Application.StartupPath + "\\" + temp.packName.ToString() + ".zip";
//更新包解压路径,与更新程序所在位置相同
var unpackPath = Application.StartupPath + "\\" + temp.packName;
//开始解压
myZip.ZipHelper.UnZip(packPath, Application.StartupPath);
//复制新文件到主程序安装目录,至于文件复制失败之类的情况,没有处理,重新安装客户端吧
myZip.ZipHelper.CopyDirectory(unpackPath, appPath);
//操作完成,删除更新包
File.Delete(packPath);
//删除解压后的更新包
myZip.ZipHelper.DeleteFolder(unpackPath);
Directory.Delete(unpackPath, true);
//停止进度条
cts.Cancel();
MessageBox.Show(Properties.Resources.UpdateSuccess, Properties.Resources.NewVersion, MessageBoxButtons.OK);
//更新完成,重新打开主应用程序
Process p = new Process();
p.StartInfo.FileName = $@"{appPath}\{temp.appName.ToString()}.exe";
p.Start();
//退出更新程序
Environment.Exit(0);
});
}
else
{
MessageBox.Show(Properties.Resources.DownLoadFail, Properties.Resources.NewVersion, MessageBoxButtons.OK);
Environment.Exit(0);
}
}
解压用到了ICSharpCode.SharpZipLib.dll,所以更新包需要打包成zip
using ICSharpCode.SharpZipLib.Zip;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
//ICSharpCode.SharpZipLib.dll
namespace myZip
{
/// <summary>
/// Zip压缩帮助类
/// </summary>
public class ZipHelper
{
/// <summary>
/// 压缩文件夹 包括子文件夹
/// </summary>
/// <param name="filesPath"></param>
/// <param name="zipFilePath"></param>
/// <param name="compressionLevel"></param>
public void CreateZipFile(string filesPath, string zipFilePath, int compressionLevel = 9)
{
if (!Directory.Exists(filesPath))
{
return;
}
if (Path.GetExtension(zipFilePath) != ".zip")
{
zipFilePath = zipFilePath + ".zip";
}
string[] filenames = Directory.GetFiles(filesPath, "*.*", SearchOption.AllDirectories);
ZipOutputStream stream = new ZipOutputStream(File.Create(zipFilePath));
stream.SetLevel(compressionLevel); // 压缩级别 0-9
byte[] buffer = new byte[4096]; //缓冲区大小
foreach (string file in filenames)
{
if(file != zipFilePath)
{
ZipEntry entry = new ZipEntry(file.Replace(filesPath+"\\", ""));
entry.DateTime = DateTime.Now;
stream.PutNextEntry(entry);
using (FileStream fs = File.OpenRead(file))
{
int sourceBytes;
do
{
sourceBytes = fs.Read(buffer, 0, buffer.Length);
stream.Write(buffer, 0, sourceBytes);
} while (sourceBytes > 0);
}
}
}
stream.Finish();
stream.Close();
}
///<summary>
/// 清空指定的文件夹,但不删除文件夹
/// </summary>
/// <param name="dir"></param>
public static void DeleteFolder(string dir)
{
System.Threading.Tasks.Parallel.ForEach(Directory.GetFileSystemEntries(dir), (d) => {
try
{
if (File.Exists(d))
{
FileInfo fi = new FileInfo(d);
if (fi.Attributes.ToString().IndexOf("ReadOnly") != -1)
fi.Attributes = FileAttributes.Normal;
File.Delete(d);//直接删除其中的文件
}
else
{
DirectoryInfo d1 = new DirectoryInfo(d);
if (d1.GetFiles().Length != 0)
{
DeleteFolder(d1.FullName);递归删除子文件夹
}
Directory.Delete(d,true);
}
}
catch
{
}
});
if (Directory.GetFileSystemEntries(dir).Length > 0)
{
DeleteFolder(dir);
}
}
public static void CopyDirectory(string srcPath, string destPath)
{
try
{
DirectoryInfo dir = new DirectoryInfo(srcPath);
FileSystemInfo[] fileinfo = dir.GetFileSystemInfos(); //获取目录下(不包含子目录)的文件和子目录
foreach (FileSystemInfo i in fileinfo)
{
if (i is DirectoryInfo) //判断是否文件夹
{
if (!Directory.Exists(destPath + "\\" + i.Name))
{
Directory.CreateDirectory(destPath + "\\" + i.Name); //目标目录下不存在此文件夹即创建子文件夹
}
CopyDirectory(i.FullName, destPath + "\\" + i.Name); //递归调用复制子文件夹
}
else
{
File.Copy(i.FullName, destPath + "\\" + i.Name, true); //不是文件夹即复制文件,true表示可以覆盖同名文件
}
}
}
catch (Exception e)
{
throw e;
}
}
/// <summary>
/// 功能:解压zip格式的文件。
/// </summary>
/// <param name="zipFilePath">压缩文件路径</param>
/// <param name="unZipDir">解压文件存放路径,为空时默认与压缩文件同一级目录下,跟压缩文件同名的文件夹</param>
/// <returns>解压是否成功</returns>
public static void UnZip(string zipFilePath, string unZipDir="")
{
if (zipFilePath == string.Empty)
{
throw new Exception("压缩文件不能为空!");
}
if (!File.Exists(zipFilePath))
{
throw new FileNotFoundException("压缩文件不存在!");
}
//解压文件夹为空时默认与压缩文件同一级目录下,跟压缩文件同名的文件夹
if (unZipDir == string.Empty)
unZipDir = zipFilePath.Replace(Path.GetFileName(zipFilePath), Path.GetFileNameWithoutExtension(zipFilePath));
if (!unZipDir.EndsWith("/"))
unZipDir += "/";
if (!Directory.Exists(unZipDir))
Directory.CreateDirectory(unZipDir);
using (var s = new ZipInputStream(File.OpenRead(zipFilePath)))
{
ZipEntry theEntry;
while ((theEntry = s.GetNextEntry()) != null)
{
string directoryName = Path.GetDirectoryName(theEntry.Name);
string fileName = Path.GetFileName(theEntry.Name);
if (!string.IsNullOrEmpty(directoryName))
{
Directory.CreateDirectory(unZipDir + directoryName);
}
if (directoryName != null && !directoryName.EndsWith("/"))
{
}
if (fileName != String.Empty)
{
using (FileStream streamWriter = File.Create(unZipDir + theEntry.Name))
{
int size;
byte[] data = new byte[2048];
while (true)
{
size = s.Read(data, 0, data.Length);
if (size > 0)
{
streamWriter.Write(data, 0, size);
}
else
{
break;
}
}
}
}
}
}
}
}
}
然后在主程序里启动检查更新的程序
wpf在程序启动时,同时启动检查更新程序
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
try
{
Task.Factory.StartNew(() => {
//更新程序位于主程序安装目录的“UpdateApp”文件夹里面
string exePath = $@"{AppDomain.CurrentDomain.BaseDirectory}UpdateApp\UpdateApp.exe";
if (File.Exists(exePath))
{
System.Diagnostics.Process p = new System.Diagnostics.Process();
p.StartInfo.FileName = exePath;
p.Start();
}
});
}catch {}
}
}
winfrom在程序启动时,同时启动检查更新程序
static class Program
{
/// <summary>
/// 应用程序的主入口点。
/// </summary>
[STAThread]
static void Main(string[] args)
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
try
{
Task.Factory.StartNew(() => {
//更新程序位于主程序安装目录的“UpdateApp”文件夹里面
string exePath = $@"{AppDomain.CurrentDomain.BaseDirectory}UpdateApp\UpdateApp.exe";
if (File.Exists(exePath))
{
System.Diagnostics.Process p = new System.Diagnostics.Process();
p.StartInfo.FileName = exePath;
p.Start();
}
});
}catch {}
}
}
当然也可以在其他地方,或者点击一下再启动,都可以
然后就是各个文件该放在哪里的问题了
主程序安装目录
UpdateApp里面的就是更新程序的相关文件而已了
客户端的配置这就完了然后是服务端发布更新包
新建个文件夹可以用版本号来命名eg:2.0.0.0,里面文件结构与主程序的安装结构要保持一致,并且排除“UpdateApp”更新程序文件夹,并且要包含新版的“LocalVersion.xml”(里面的版本号修改为2.0.0.0,同时也可以修改里面的其它字段)
LocalVersion.xml
<?xml version="1.0" encoding="utf-8" ?>
<root>
<version remark="本地版本号">2.0.0.0</version>
<url remark="检查更新地址">http://localhost/update/ServerVersion.xml</url>
<appName remark="应用程序名称">MyApp</appName>
<killList remark="更新过程中需要关掉的应用程序名称,多个使用英文逗号分割,没有则为空">MyApp,CefSharp.BrowserSubprocess</killList>
</root>
<!--客户端版本配置xml例子-->
然后压缩成zip包,放到web服务器里,同时更新“ServerVersion.xml”,更新版本号为2.0.0.0,配置好更新包的所在url,还可以配置这次的版本更新是否是必须的,如果“requiredUpdate”设为“true”,表示更新程序不会弹出“是否更新”的对话框给客户看到,而是直接下载更新,如果设为“false”,则会弹出提示对话框
ServerVersion.xml
<?xml version="1.0" encoding="utf-8" ?>
<root>
<version remark="最新版本号">2.0.0.0</version>
<url remark="最新版本包">http://localhost/update/2.0.0.0.zip</url>
<requiredUpdate remark="是否必须更新">true</requiredUpdate>
</root>
<!--服务端新版本配置xml例子-->
所以检查版本是否有更新的位置和更新包的下载位置是可以分开的,只要配合url就可以了
百度网盘下载
链接: https://pan.baidu.com/s/1jhh_sTppEUBaD-Ldjta7cw 提取码: zg4t
https://gitee.com/l-cj/mycode/tree/master/UpdateApp
---------------------------------------------------2019年12月19日13:02:57----------------------------------------
.net 框架设为2.0
增加更新时 停、启指定windows服务功能
<?xml version="1.0" encoding="utf-8" ?>
<root>
<version remark="本地版本号">1.0.0.0</version>
<url remark="检查更新地址">http://localhost/ServerVersion.xml</url>
<reStartList remark="更新完成后需要启动的应用程序名称">DesktopApp</reStartList>
<reStartServiceList remark="更新完成后需要启动的服务名称"></reStartServiceList>
<killList remark="更新过程中需要关掉的应用程序名称">DesktopApp</killList>
<killServiceList remark="更新过程中需要关掉的服务名称"></killServiceList>
</root>
<!--客户端版本配置xml例子-->