自动更新+安装程序的制作

最近公司做一个winform的项目要求客户端能够自动更新。更新版本以打包形式下载并可以主动进行安装。

借鉴了网上的写法,总结了一套自己的思路,仅供参考。

一、自动更新的实现

让客户端实现自动更新,通常做法是在客户端部署一个单独的自动更新程序。主程序启动后,访问服务端,检查配置文件是
否有更新版本,有更新版本就启动更新程序,由更新负责下载更新版本,并更新客户端程序,流程如下:

image当流程进行到红色部分的是后就调用更新程序进行更新。

1)版本判断:

客户端和服务端都部署同一个版本文件,客户端登陆时发送验证给服务端判断版本是否一致。

Version.xml代码

<iq xmlns="http://www.dynastech.com/xmtp" from="*@domcool.local/updater" to="*@domcool.local/updater" type="get" 
id="508f3e88-4bb0-4585-a5c6-cc41ef57fef3">
  <query xmlns="http://www.dynastech.com/xmtp/disco#update" version="20090922" lastUpdateTime="2009-09-22" 
fileUrl="http://172.0.0.1/UCCompanion/UCCompanionSetup(0922).zip">
    <x xmlns="http://www.dynastech.com/xmtp/item">
    </x>
  </query>
</iq>
版本文件主要比较服务端Version.xml文件和客户端Version.xml文件中Version(版本号)是否一致,如果服务端Version属性
大于客户端的Version属性,则通过服务端的fileUrl属性获取新版本的下载地址。供更新程序使用。
 
2)删除原有更新包
所有客户端更新文件均下载到C:\Documents and Settings\当前用户名\Local Settings\Temp 文件夹内,当客户端运行后首先判
断是否有新更新包需要下载,如果没有则判断该临时文件夹内是否有旧有安装文件,如果存在,则删除旧有安装文件。
private void RemoveOldSetupFile()
 {
     try
     {
         string temp = System.Environment.GetEnvironmentVariable("TEMP");
         string folder = new DirectoryInfo(temp).FullName;
         if (File.Exists(folder + @"\" + setupName + ".exe"))
         {
             File.Delete(folder + @"\" + setupName + ".exe");
         }
         if (File.Exists(folder + @"\" + setupName + ".msi"))
         {
             File.Delete(folder + @"\" + setupName + ".msi");
         }
     }
     catch { }

 }

备注:关于获取系统特殊文件夹的方法见博客 http://www.cnblogs.com/thornfield_he/archive/2009/09/22/1571719.html

3)启动下载程序

下载程序和客户端程序是相互独立的,可以通过客户端开启新线程启动下载程序。下载程序在文件下载结束后可以关掉客户端程序,
并开启新线程启动安装程序进行安装。

private void Update()
{
    if (ShouldUpdate(query.Version, this.version))
    {
        MessageBox.Show("请更新客户端文件到版本[" + query.Version + "]", "更新提示", MessageBoxButtons.OK, 
MessageBoxIcon.Asterisk);
        System.Diagnostics.Process.Start(Application.StartupPath + @"\AutoUpdater.exe", query.FileUrl);
    }
    else { RemoveOldSetupFile(); }
}

private bool ShouldUpdate(string serverVersion, string localVersion)
{
    if (!string.IsNullOrEmpty(serverVersion) && !string.IsNullOrEmpty(localVersion))
    {
        return serverVersion.CompareTo(localVersion) > 0;
    }
    return true;
}
 
调用AutoUpdater.exe文件时需要传入文件下载地址。
System.Diagnostics.Process.Start(Application.StartupPath + @"\AutoUpdater.exe", query.FileUrl); 
 
4)下载程序代码
下载程序界面
image 
 
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Net;
using System.IO;
using System.Threading;
using System.Diagnostics;

namespace AutoUpdater
{
    public partial class MainForm : Form
    {
        private WebClient client;
        private string URl;
        private string fileName;
        private string path;
        private const string applicationFile = "Setup";

        public MainForm(string url)
        {
            InitializeComponent();

            this.URl = url;
            client = new WebClient();
            client.DownloadProgressChanged += new DownloadProgressChangedEventHandler(client_DownloadProgressChanged);
            client.DownloadFileCompleted += new AsyncCompletedEventHandler(client_DownloadFileCompleted);
            client.Proxy = WebRequest.DefaultWebProxy;
            client.Proxy.Credentials = new NetworkCredential();

            this.Hide();
            //Thread thread = new Thread(UpdateFile);
            //Thread.Sleep(15000);
            //thread.Start();
            UpdateFile();
        }

        public MainForm()
        {
            InitializeComponent();
        }

        /// <summary>
        /// 下载完成调用
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void client_DownloadFileCompleted(object sender, AsyncCompletedEventArgs e)
        {
            label1.Text = "文件接收完成";
            UnZip();
            RunUpdate();
        }

        /// <summary>
        /// 下载进度条
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void client_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
        {
            this.progressBar1.Value = e.ProgressPercentage;
        }

        /// <summary>
        /// 开始下载
        /// </summary>
        private void StartDownload()
        {
            fileName = URl.Substring(URl.LastIndexOf("/") + 1, URl.Length - URl.LastIndexOf("/") - 1);
            path = GetTempFolder();

            try
            {
                WebRequest myre = WebRequest.Create(URl);
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message, "Error");
            }

            try
            {
                label1.Text = "开始下载文件...";
                client.DownloadFileAsync(new Uri(URl), path + @"\" + fileName);

            }
            catch (WebException exp)
            {
                label1.Text = exp.Message;
            }
        }

        /// <summary>
        /// 解压压缩包,格式必须是*.zip,否则不能解压
        /// 因为是调用Windows内部api进行解压,只能够识别zip压缩包
        /// 必须添加C:\WINDOWS\system32\shell32.dll的引用
        /// </summary>
        private void UnZip()
        {
            try
            {
                Shell32.ShellClass sc = new Shell32.ShellClass();
                Shell32.Folder SrcFolder = sc.NameSpace(this.path + @"\" + this.fileName);
                Shell32.Folder DestFolder = sc.NameSpace(this.path);
                Shell32.FolderItems items = SrcFolder.Items();
                DestFolder.CopyHere(items, 20);
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }

        /// <summary>
        /// 获取下载文件夹地址及解压文件存放地址
        /// 此地址默认为C:\Documents and Settings\当前用户名\Local Settings\Temp 文件夹
        /// </summary>
        /// <returns></returns>
        private string GetTempFolder()
        {
            string folder = System.Environment.GetEnvironmentVariable("TEMP");
            return new DirectoryInfo(folder).FullName;
        }

        /// <summary>
        /// 开始下载文件
        /// </summary>
        private void UpdateFile()
        {
            this.Hide();
            //如果临时文件夹存在setup安装文件,就直接调用安装文件
            if (File.Exists(GetTempFolder() + @"\" + applicationFile + ".exe") && File.Exists(GetTempFolder() + 
@"\" + applicationFile + ".msi"))
            {
                label1.Text = "开始下载文件...";
                this.progressBar1.Value = this.progressBar1.Maximum;
                label1.Text = "文件接收完成";
                RunUpdate();
            }
            //如果临时文件夹不存在setup安装文件,就从网络下载
            else
            {
                RemoveSetupFile();
                StartDownload();
            }
        }

        /// <summary>
        /// 清除旧有已下载的安装文件
        /// </summary>
        private static void RemoveSetupFile()
        {
            try
            {
                string temp = System.Environment.GetEnvironmentVariable("TEMP");
                string folder = new DirectoryInfo(temp).FullName;
                if (File.Exists(folder + @"\" + applicationFile + ".exe"))
                {
                    File.Delete(folder + @"\" + applicationFile + ".exe");
                }
                if (File.Exists(folder + @"\" + applicationFile + ".msi"))
                {
                    File.Delete(folder + @"\" + applicationFile + ".msi");
                }
            }
            catch { }
        }

        /// <summary>
        /// 下载完毕,开始执行更新程序
        /// </summary>
        private void RunUpdate()
        {
            try
            {
                foreach (Process p in Process.GetProcesses())
                {
                    if (p.ProcessName.ToLower().StartsWith("uccompanion"))
                    {
                        if (MessageBox.Show("UCCompanion正在运行,是否关闭当前程序安装更新?", "安装UCCompanion",
 MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
                        {
                            p.Kill();
                            Process.Start(GetTempFolder() + @"\" + applicationFile + ".exe");
                        }
                        else
                        {
                            MessageBox.Show("UCCompanion下载完成,将在下次启动时提醒更新!");
                        }
                    }
                }

            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
            finally
            {
                this.Close();
            }
        }

        /// <summary>
        /// 重载WindProc判断点击关闭按钮(X)时,隐藏程序界面
        /// </summary>
        /// <param name="msg"></param>
        protected override void WndProc(ref Message msg)
        {
            const int WM_SYSCOMMAND = 0x0112;
            const int SC_CLOSE = 0xF060;

            if (msg.Msg == WM_SYSCOMMAND && ((int)msg.WParam == SC_CLOSE))
            {
                this.Hide();
                return;
            }
            base.WndProc(ref msg);
        }

        /// <summary>
        /// 双击图标弹出界面
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void icon_notify_MouseDoubleClick(object sender, MouseEventArgs e)
        {
            this.Show();
            this.WindowState = FormWindowState.Normal;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void MainForm_SizeChanged(object sender, EventArgs e)
        {
            if (this.WindowState == FormWindowState.Minimized)
            {
                this.Hide();
            }
        }


        private void MainForm_Load(object sender, EventArgs e)
        {
            this.Hide();
        }

    }

    static class Program
    {
        /// <summary>
        /// 启动,接收传入网址作为参数
        /// </summary>
        /// <param name="agr"></param>
        [STAThread]
        static void Main(string[] agr)
        {
            if (agr.Length == 1 && agr[0].StartsWith(@"http://"))
            {
                MainForm form = new MainForm(agr[0]);
                Application.Run(form);
            }
        }
    }
}

程序代码

将AutoUpdater项目生成的文件添加到客户端文件中,在客户端的Update()方法里调用updater,实现更新文件的下载。

以上就已经实现了自动更新功能,下面将讨论文件安装包的制作。
 

二、安装包的制作

1)创建安装项目

image

 

 

2)鼠标右击Setup项目选择>视图,可以看到制作安装包常见的视图有以下几个

image

最常用的视图有“文件系统”,“用户界面”和“启动条件”。

3)指定安装属性

鼠标左键单击项目名称,记住是左键单击,然后点击属性标签,注意:不是右击的属性

image

 

 

a.需要注意的是Version属性,每次版本更新时Version值必须后面的版本大于前面的版本。每次更改Version值时Projectcode会更改一次。

其中你修改安装项目的版本号时,比如从v1.00 到1.01,在你再次生成项目的时候,会提示你是否允许修改ProductCode,选择"是",
程序会自动修改ProductCode,选择否将保持相同的ProductCode,即不能自动卸载旧的版本.

b.在以后版本中要确认和以前的版本两个版本有不同的ProductCode和相同的UpgradeCode

c.manufacturer属性指定制造商名称。

d.detectnewerinstalledversion属性选择为true,

e.removepreviousversions选择为true

 

鼠标左键单击项目名称,此次是右键单击,然后点击属性,弹出属性页,选择“系统必备”。

在打开的系统必备页中,选中如下中的选择项,这个很重要!!!!!1!!!!!选上以后,在生成的安装文件
含.netframework组件.(这个选项默认是没有选中的)。

image

 

4)文件系统视图

文件系统视图左侧根目录树下有3个子节点。

a.应用程序文件夹:将所有待打包的应用程序的可执行文件和相应的类库和组件拖动到该目录下。该目录可以创建子
目录,项目安装完毕以后的文件夹结构会和该目录下结构一致。

如图:

image

然后右击左边的"应用程序文件夹"打开属性对话框,修改文件释放路径,[ProgramFilesFolder][Manufacturer]\[ProductName]。
安装程序默认安装目录会是"c:\programm file\制造商名称\安装解决方案名称";

image

b.用户的“程序”菜单和用户桌面:用于在开始菜单创建文件快捷方式

在应用程序文件夹中将需要生成的快捷方式的文件添加快捷方式并拖动到用户的“程序”菜单和用户桌面

 

 

 

image

c.添加文件卸载功能

在添加你的应用程序项目的时候,多添加一个msiexec.exe进去,这个文件在c:\windows\system32文件夹下。

为其在程序菜单添加一个快捷方式,把他的名字改成"Uninstall.exe",指定Icon快捷方式显示的图标。然后下面我们
要的做的就是查找这个部署项目的ProductCode了,鼠标左键单击项目名称,记住是左键单击,然后点击属性标签,注意:
不是右击的属性,这个区别很大,这时你就可以看到ProductCode了

image

 

 

 

然后打开你创建的那个卸载程序的快捷方式的属性对话框,在Aguements属性中输入"/x {ProductCode}"

5)用户界面视图

在“欢迎使用”后,“安装文件夹”前添加“许可协议”对话框。

licensefile选择协议,协议的格式为rtf。

image

6)启动条件视图

为启动安装程序制定最低framework要求。

image

7)实现安装、卸载过程中的其他额外的操作。比如安装结束后启动程序,卸载程序后同时删除网络下载打安装包等功能。

a.新建一个空的项目InstallCompenent,步骤为:解决方案->右键添加->新建项目->选择"空项目"->
输入名称"InstallCompenent"->确定,完成项目的添加.

b.在InstallCompenent项目中右键->添加->新建项->选择安装程序类->输入名称"Installer",完成installer类的添加.

修改代码为:

using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Configuration.Install;
using System.Reflection;
using System.IO;


namespace InstallCompenent
{
    [RunInstaller(true)]
    public partial class UccompanionInstaller : Installer
    {
        private const string zipPacket = "UCCompanionSetup(0918).zip";
        /// <summary>   
        /// 应用程序入口   
        /// </summary>   
        public static void Main()
        {
        }
        /// <summary>   
        /// 构造函数   
        /// </summary>   
        public UccompanionInstaller()
        {
            InitializeComponent();
        }
        /// <summary>   
        /// 重写安装完成后函数   
        /// 实现安装完成后自动启动已安装的程序   
        /// </summary>   
        /// <param name="savedState"></param>   
        protected override void OnAfterInstall(IDictionary savedState)
        {
            base.OnAfterInstall(savedState);
            Assembly asm = Assembly.GetExecutingAssembly();
            string path = asm.Location.Remove(asm.Location.LastIndexOf("\\")) + "\\";
            System.Diagnostics.Process.Start(path + "\\UCCompanion.exe");
        }
        /// <summary>   
        /// 重写安装过程方法   
        /// </summary>   
        /// <param name="stateSaver"></param>   
        public override void Install(IDictionary stateSaver)
        {
            base.Install(stateSaver);
        }
        /// <summary>   
        /// 重写安装之前方法   
        /// </summary>   
        /// <param name="savedState"></param>   
        protected override void OnBeforeInstall(IDictionary savedState)
        {
            base.OnBeforeInstall(savedState);
        }
        /// <summary>   
        /// 重写卸载方法   
        /// 卸载程序后也删除程序的安装包
        /// </summary>   
        /// <param name="savedState"></param>   
        public override void Uninstall(IDictionary savedState)
        {
            string temp = System.Environment.GetEnvironmentVariable("TEMP");
            string folder = new DirectoryInfo(temp).FullName;
            if (File.Exists(folder + @"\setup.exe"))
            {
                File.Delete(folder + @"\setup.exe");
            }
            if (File.Exists(folder + @"\setup.msi"))
            {
                File.Delete(folder + @"\setup.msi");
            }
            if (File.Exists(folder + @"\"+zipPacket))
            {
                File.Delete(folder + @"\"+zipPacket);
            }
            base.Uninstall(savedState);
        }
        /// <summary>   
        /// 重写回滚方法   
        /// </summary>   
        /// <param name="savedState"></param>   
        public override void Rollback(IDictionary savedState)
        {
            base.Rollback(savedState);
        }
    }
}

 

c.在安装项目中右键->添加项目输出->选择"项目"->InstallCompenent.完成主输出项目的添加.

d.打开自定义操作编辑器,在安装->右键->添加自定义操作->选择"应用程序文件夹"->选择"主输出来自InstallCompenent",完成添加.

好了,点击“生成解决方案”,即可以生成带有卸载功能的安装程序了。

posted @ 2009-09-22 15:50  何翔华  阅读(4846)  评论(8编辑  收藏  举报