C#[WinForm]實現自動更新


C#[WinForm]實現自動更新

winform程序相對web程序而言,功能更強大,編程更方便,但軟件更新卻相當麻煩,要到客戶端一台一台地升級,面對這個實際問題,在最近的一個小項目中,本人設計了一個通過軟件實現自動升級技術方案,彌補了這一缺陷,有較好的參考價值
實現原理:在WebServices中實現一個GetVer的WebMethod方法,其作用是獲取當前的最新版本。 然后將現在版本與最新版本比較,如果有新版本,則進行升級。
步驟:
    
1、准備一個XML文件 (Update.xml)。
<?xml version="1.0" encoding="utf-8" ?> 
<product>
<version>1.0.1818.42821</version>
<description>修正一些Bug</description>
<filelist count="4" sourcepath="./update/">
<item name="City.xml" size="">
<value />
</item>
<item name="CustomerApplication.exe" size="">
<value />
</item>
<item name="Interop.SHDocVw.dll" size="">
<value />
</item>
<item name="Citys.xml" size="">
<value />
</item>
</filelist>
</product>

作用是作為一個升級用的模板。
    
2、WebServices的GetVer方法。

[WebMethod(Description="取得更新版本")]
public string GetVer()
{
XmlDocument doc = new XmlDocument();
doc.Load(Server.MapPath("update.xml"));
XmlElement root = doc.DocumentElement;
return root.SelectSingleNode("version").InnerText;
}
     
3、WebServices的GetUpdateData方法。

[WebMethod(Description="在線更新軟件")]
[SoapHeader("sHeader")]
public System.Xml.XmlDocument GetUpdateData()
{
//驗證用戶是否登陸
if(sHeader==null)
return null;
if(!DataProvider.GetInstance.CheckLogin(sHeader.Username,sHeader.Password))
return null;
//取得更新的xml模板內容
XmlDocument doc = new XmlDocument();
doc.Load(Server.MapPath("update.xml"));
XmlElement root = doc.DocumentElement;
//看看有幾個文件需要更新
XmlNode updateNode = root.SelectSingleNode("filelist");
string path = updateNode.Attributes["sourcepath"].Value;
int count = int.Parse(updateNode.Attributes["count"].Value);
//將xml中的value用實際內容替換
for(int i=0;i<count;i++)
{
XmlNode itemNode = updateNode.ChildNodes[i];
string fileName = path + itemNode.Attributes["name"].Value;
FileStream fs = File.OpenRead(Server.MapPath(fileName));
itemNode.Attributes["size"].Value = fs.Length.ToString();
BinaryReader br = new BinaryReader(fs);
//這里是文件的實際內容,使用了Base64String編碼
itemNode.SelectSingleNode("value").InnerText = Convert.ToBase64String(br.ReadBytes((int)fs.Length),0,(int)fs.Length);
br.Close();
fs.Close();
}
return doc;
}
   
4、在客戶端進行的工作。
首先引用此WebServices,例如命名為:WebSvs,

string nVer = Start.GetService.GetVer(); 
if(Application.ProductVersion.CompareTo(nVer)<=0)
update();

在本代碼中 Start.GetService是WebSvs的一個Static 實例。首先檢查版本,將結果與當前版本進行比較,如果為新版本則執行UpDate方法。
void update()
{
this.statusBarPanel1.Text = "正在下載...";
System.Xml.XmlDocument doc = ((System.Xml.XmlDocument)Start.GetService.GetUpdateData());
doc.Save(Application.StartupPath + @"\update.xml");
System.Diagnostics.Process.Start(Application.StartupPath + @"\update.exe");
Close();
Application.Exit();
}
這里為了簡單起見,沒有使用異步方法,當然使用異步方法能更好的提高客戶體驗,這個需要讀者們自己去添加。:) update的作用是將升級的XML文件下載下來,保存為執行文件目錄下的一個Update.xml文件。任務完成,退出程序,等待Update.Exe 來進行升級。     
5、Update.Exe 的內容。
private void Form1_Load(object sender, System.EventArgs e)
{
System.Diagnostics.Process[] ps = System.Diagnostics.Process.GetProcesses();
foreach(System.Diagnostics.Process p in ps)
{
//MessageBox.Show(p.ProcessName);
if(p.ProcessName.ToLower()=="customerapplication")
{
p.Kill();
break;
}
}
XmlDocument doc = new XmlDocument();
doc.Load(Application.StartupPath + @"\update.xml");
XmlElement root = doc.DocumentElement;
XmlNode updateNode = root.SelectSingleNode("filelist");
string path = updateNode.Attributes["sourcepath"].Value;
int count = int.Parse(updateNode.Attributes["count"].Value);
for(int i=0;i<count;i++)
{
XmlNode itemNode = updateNode.ChildNodes[i];
string fileName = itemNode.Attributes["name"].Value;
FileInfo fi = new FileInfo(fileName);
fi.Delete();
//File.Delete(Application.StartupPath + @"\" + fileName);
this.label1.Text = "正在更新: " + fileName + " (" + itemNode.Attributes["size"].Value + ") ...";
FileStream fs = File.Open(fileName,FileMode.Create,FileAccess.Write);
fs.Write(System.Convert.FromBase64String(itemNode.SelectSingleNode("value").InnerText),0,int.Parse(itemNode.Attributes["size"].Value));
fs.Close();
}
label1.Text = "更新完成";
File.Delete(Application.StartupPath + @"\update.xml");
label1.Text = "正在重新啟動應用程序...";
System.Diagnostics.Process.Start("CustomerApplication.exe");
Close();
Application.Exit();
}
這個代碼也很容易懂,首先就是找到主進程,如果沒有關閉,則用Process.Kill()來關閉主程序。然后則用一個XmlDocument來Load程序生成的update.xml文件。用xml文件里指定的路徑和文件名來生成指定的文件,在這之前先前已經存在的文件刪除。更新完畢后,則重新啟動主應用程序。這樣更新就完成了。
 
View Code

C#自動更新

在client根據server的配置文件判斷是否有新版本,有的話下載更新信息:更新Ip、版本號、更新文件等,下載到本地。
再根據ip進行下載到本地臨時文件中,下載完成后,提示用戶是否更新。如果更新關閉當前系統,啟動更新服務將臨時文件中的文件替換到要替換的程序目錄下,代碼如下:
<br>/// <summary>
 /// 根據服務器上的數據進行同步數據
 /// </summary>
 /// <param name="sender"></param>
 /// <param name="e"></param>
 private void btnKeepUser_Click(object sender, EventArgs e)
 {
 #region web download
 bool isUpdate =false;
 string[] fileName =new string[] { "Accounting_Client.dll","Accounting_Component.dll","Accounting_Entity.dll","Accounting_UI.dll" }; //要下載的文件
 AutoUpdate update =new AutoUpdate();
 for (int i = 0; i < fileName.Length; i++)
 {
 isUpdate= update.start(fileName[i]);
 if (isUpdate ==false)
 {
 break;
 }
 }
 if (isUpdate ==false)
 {
 MessageBox.Show("Update failure");
 }
 else 
 {
 //自動下載完成后,將保存的路徑和要替換的路徑保存到ini配置文件中
 #region 寫入INI文件
 //寫入ini文件
 string s = Application.ExecutablePath;
 string s1;
 s1 = s.Replace("ini.exe","updateVersionConfig.ini");
 //寫入版本信息
 string origin = System.Windows.Forms.Application.StartupPath +"";
 string destination = System.Windows.Forms.Application.StartupPath;
 string path ="C:\\updateVersionConfig.ini";
 
 WritePrivateProfileString("updateInfo","origin", origin, path); //寫入源地址
 WritePrivateProfileString("updateInfo","destination" , destination, path); //寫入更新地址
 #endregion
 #region 啟動自動更新,關閉當前系統
 //System.Diagnostics.Process[] proc = System.Diagnostics.Process.GetProcessesByName("TIMS");
 ////關閉原有應用程序的所有進程
 //foreach (System.Diagnostics.Process pro in proc)
 //{
 // pro.Kill();
 //}
 //啟動更新程序
 string strUpdaterPath = System.Windows.Forms.Application.StartupPath +"";
 this.Close();
 System.Diagnostics.Process.Start(strUpdaterPath);
 #endregion
 }
 
 #endregion
 
 
 //ProgressBar bar = new ProgressBar(); 
 //string curVersion = GetLastestVersionByFtp("");
 //#region 讀取本地當前的版本
 ////此處獲得軟件的版本信息
 //string currentUser = "info";
 //StringBuilder temp = new StringBuilder(80000000);
 //string section = "versionInfo";
 //string iniPath = GlobalFile.GlobalConfigurationInfo.AddressOfIni; //"G:\\test\\userConfig.ini";
 //int i = GetPrivateProfileString(section, currentUser, "", temp, 80000000, iniPath);
 //string versionInfo = temp.ToString();
 //int versionPos = versionInfo.LastIndexOf("=");
 //versionInfo = versionInfo.Substring(versionPos + 1, versionInfo.Length - (versionPos + 1));
 //#endregion
 ////獲得本地版本
 //string localVersion = versionInfo;
 ////獲得服務器上的版本信息
 //UserLoginClient ftpInfo = new UserLoginClient();
 //string[] result = ftpInfo.getFtpInformation(localVersion);
 //if (result == null)
 //{
 // label2.Text = "已是最新版本!";
 // return;
 //}
 ////獲得FTP返回的消息 0:最新版本號 1: FTP登陸名 2:FTP登陸密碼 3: FTP登陸IP
 //string ftpVersion = result[0];
 //string ftpLoginName = result[1];
 //string ftpLoginPwd = result[2];
 //string ftpIp = result[3];
 ////如果已是最新版本
 //if (ftpVersion == localVersion)
 //{
 // label2.Text = "已是最新版本!";
 //}
 //else
 //{
 // //根據最新的版本號下載程序到本地
 
 // label2.Text = "已不是最新版本!";
 //}
 }
View Code

inf文件介紹

INF文件全稱Information File文件,是Winodws操作系統下用來描述設備或文件等數據信息的文件。INF文件是由標准的ASCII碼組成,您可以用任何一款文字編輯器查看修改其中的內容。一般我們總是認為INF文件是系統設備的驅動程序,其實這是錯誤的認識,Windows之所以在安裝某些硬件的驅動時提示需要INF文件是因為INF文件為該設備提供了一個全面描述硬件參數和相應驅動文件(DLL文件)的信息。就好比我們看着說明書安裝電腦硬件一樣,我們就是Windows系統,說明書就是INF文件。INF文件功能非常強大,幾乎能完成日常操作的所有功能。您可以把它看成是Windows系統底下的超強批初理。要熟練掌握和理解甚至是編寫INF文件需要對其內部結構有相當的認識。下面就讓我們來深入到INF文件中的內部一窺其真面貌吧!
 
INF文件的組成有節(Sections),鍵(Key)和值(value)三部分。
關鍵節有
[Version]版本描述信息,主要用於版本控制。
[Strings]字符串信息,用於常量定義。
[DestinationDirs]定義系統路徑信息。
[SourceDisksNames]指明源盤信息。
[SourceDisksNames]指明源盤文件名。
[DefaultInstall]開始執行安裝。
其它的節可以自定義,下面用一實例來具體講解。
 
程序代碼
[Version]
Signature=$Chicago$
Provider=%Author%
 
[Strings]
Product="添加文件關聯演示"
Version="1.0"
Author="Xunchi"
Copyright="Copyright 2005"
CustomFile="inf" ;修改您需要的文件名后綴
Program="NOTEPAD.EXE" ;修改您需要關聯的應用程序名
 
[Add.Reg]
HKCR,"."%CustomFile%,"",FLG_ADDREG_TYPE_SZ ,%CustomFile%File
HKCR,%CustomFile%File,"",FLG_ADDREG_TYPE_SZ,安裝信息
HKCR,%CustomFile%"File\shell","",FLG_ADDREG_TYPE_SZ,open
HKCR,%CustomFile%"File\shell\open\command","",FLG_ADDREG_TYPE_SZ,%program% %1
 
[DefaultInstall]
AddReg=Add.Reg
 
  在[Version]節中"Signature"項定義了該INF文件需要運行在何種操作系統版本中。有$Windows NT$, $Chicago$, or $Windows 95$三個值供選擇,一般選擇$Chicago$即可。項Provider中定義了該文件的創作來源,%Author%指引用Author項的值。您也可自定其它項來描述該INF文件的版本信息。該INF文件的作用是關聯文件,所以主要是對注冊表的操作,我們來看[Add.Reg]節,共四條語句,格式都是一樣。HKCR表示根HKEY_CLASSES_ROOT,第二個參數是子鍵的路徑名,第三個參數是表明值的類型,最后是值(具體見附表)。以上都是對操作的定義與過程,在節[DefaultInstall]中是開始執行要安裝的流程,AddReg表明是對注冊表進行操作,操作對象是Add.Reg節中的定義。如果您把AddReg換成DelReg則是刪除注冊表中的鍵值。當鼠標單擊該INF文件在彈出的菜單中選擇“安裝”就開始執行您所定義的操作。該示例在系統的INF文件右鍵菜單中增加了查看編輯功能並設置了默認動作,因為在安裝了不了解的INF文件有可能對系統產生不良的影響,這樣雙擊文件就可打開編輯該文件了。
 
  再看看INF文件在文件操作方面的能力吧。請看下面的一個例子。
 
程序代碼
[Version]
Signature=$Chicago$
Provider=%Author%
[Strings]
Product="文件復制和安裝演示"
Version="1.0"
Author="Xunchi"
Copyright="Copyright 2005"
 
[FileList]
ProcessList.exe ;此文件已在當前目錄下,下同。
 
[FileList1]
Wordpad.exe
[DestinationDirs]
FileList=11 ;安裝到Windows的系統目錄
FileList1=10 ;安裝到Windows目錄
[DefaultInstall]
Copyfiles=FileList,FileList1
 
  相同的節的作用與上一例類似,請注意新出現的節[FileList],這是我自定義的節名,它表示了一個文件組,[FileList1]也類似。在節[DestinationDirs]中需定義每個文件組復制到的目錄。Copyfiles指明了需要進行復制的文件組。
  INF文件的操作還包括服務(NT系統)程序的安裝和卸載,INI文件的轉換等。由於這些操作都比較的復雜和繁瑣,且有一定的危險性故下次有機會再向大家進行深入探討。
  最后我們來看一下INF文件的執行機制,這時你也許要問不就是簡單的執行一下“安裝”嗎?知其然不知其所以然知識水平是不會提高的。在“文件夾選項”中的“文件類型”找到INF文件的“安裝”命令看到一串命令。“rundll32.exe setupapi,InstallHinfSection DefaultInst_all 132 %1”它表示了運行Dll文件setupapi.dll中的命令InstallHinfSection並傳遞給它起始節的名字 DefaultInstall。可見起始節是可以自定義的。INF文件的執行也可用在各種支持API調用的編程工具中。至此INF文件的結構和運行機制我們已基本了解,現在就讓你的思維開動起來,讓它更好的為我們工作吧。
本文中,我們需要通過inf文件來完成的任務是:將Microsoft.mshtml.dll文件移動到C:\Windows\Microsoft.NET\Framework\v2.0.50727文件夾中(前提是本機已安裝Framework.Net 2.0),然后安裝QmsSetup.msi文件。
inf文件編寫
[version]
signature="$CHICAGO$"
AdvancedINF=2.0
[FileList]
Microsoft.mshtml.dll
[DestinationDirs]
FileList=0,"C:\Windows\Microsoft.NET\Framework\v2.0.50727\"
[Add.Code]
QmsSetup.msi=QmsSetup.msi
[QmsSetup.msi]
file-win32-x86=thiscab
clsid={CF50DCDE-D952-431C-A4B1-6C62FD5500EE}
FileVersion=1,0,0,0
[Setup Hooks]
RunSetup=RunSetup
[RunSetup]
run="""msiexec.exe""" /i """%EXTRACT_DIR%\QmsSetup.msi""" /qn
[DefaultInstall]
CopyFiles=FileList
編寫完inf文件后,還需要借助CABARC.EXE這個cab包打包工具。將inf文件,QmsSetup.msi,Microsoft.mshtml.dll和cabarc.exe放入同一個文件夾中。桌面左下角“開始”,“運行”,輸入cmd,打開命令提示符工具,進入准備好的文件的目錄,執行命令:cabarc n QmsSetup.cab QmsSetup.inf QmsSetup.msi Microsoft.mshtml.dll。顯示“Completed successfully” ,打開所在目錄,就可以看到生成的cab包了。
 
cab包生成完成后,需要嵌入到網頁中。代碼如下:
<object name="QmsSetup" style="display:none" id="QmsSetup" classid="CLSID:CF50DCDE-D952-431C-A4B1-6C62FD5500EE" codebase="../Cab/QmsSetup.cab"></object>
 
odebase屬性是cab所在的文件路徑。
此外,將該網頁添加到瀏覽器的可信任站點中,並在自定義級別中設置ActiveX為啟用,設置完成就可以打開網頁測試了。
最后,補充一篇老外的博文,寫的很詳細。
http://www.olavaukan.com/2010/08/creating-an-activex-control-in-net-using-c/
 
View Code

自動更新

現在但凡是一個程序都有相應的升級程序,如果你的程序沒有相應的升級程序,那么你就需要留意了。你的用戶很可能丟失!!!網上關於自動升級的例子也有很多,前幾天一個朋友很苦惱的跟我說它的客戶在逐漸減少(據他所說,他都客戶因為他的程序升級很麻煩,所以很多人放棄了使用它的軟件),問我說怎么辦?其實他也知道該怎么辦?所以...朋友嘛!就給他做了一個自動升級程序。恰好今天CSDN上的一位老友也詢問這件事情,所以就把代碼共享大家了。
先個幾個圖:

 

 
主要原理(相當簡單):
升級程序一定要是一個單獨的exe,最好不要和你的程序綁到一起(否則升級程序無法啟動)。主程序退出----升級程序啟動----升級程序訪問你的網站的升級配置文件-----讀取配置文件里面信息-----下載-----升級程序關閉----主程序啟動
主要代碼:
1.讀取配置文件:
        private void GetUpdateInfo()
        {
            //獲取服務器信息
            WebClient client = new WebClient();
            doc = new XmlDocument();
            try
            {
                doc.Load(client.OpenRead("http://192.168.3.43/update/update.xml"));
                //doc.Load(client.OpenRead(Config.IniReadValue("Update","UpdateURL",Application.StartupPath+"//config.ini")+"//update.xml"));
                //doc.Load(Application.StartupPath + "//update.xml");
                client = null;
            }
            catch
            {
                this.labHtml.Text = "無法取得更新文件!程序升級失敗!";
                return;
            }
            if (doc == null)
                return;
            //分析文件
            XmlNode node;
            //獲取文件列表
            string RootPath = doc.SelectSingleNode("Product/FileRootPath").InnerText.Trim();
            node = doc.SelectSingleNode("Product/FileList");
            if (node != null)
            {
                foreach (XmlNode xn in node.ChildNodes)
                {
                    this.listView1.Items.Add(new ListViewItem(new string[]
                        {
                             xn.Attributes["Name"].Value.ToString(),                            
                             new WebFileInfo(RootPath+xn.Attributes["Name"].Value.ToString()).GetFileSize().ToString(),
                            "---"
                        }));
                }
            }
        }
2.文件下載: 
/// <summary>
/// 下載文件
/// </summary>
public void Download()
{
FileStream fs = new FileStream( this.strFile,FileMode.Create,FileAccess.Write,FileShare.ReadWrite );
try
{
this.objWebRequest = (HttpWebRequest)WebRequest.Create( this.strUrl );
this.objWebRequest.AllowAutoRedirect = true;
// int nOffset = 0;
long nCount = 0;
byte[] buffer = new byte[ 4096 ]; //4KB
int nRecv = 0; //接收到的字節數
this.objWebResponse = (HttpWebResponse)this.objWebRequest.GetResponse();
Stream recvStream = this.objWebResponse.GetResponseStream();
long nMaxLength = (int)this.objWebResponse.ContentLength;
if( this.bCheckFileSize && nMaxLength != this.nFileSize )
{
throw new Exception( string.Format( "文件/"{0}/"被損壞,無法下載!",Path.GetFileName( this.strFile ) ) );
}
if( this.DownloadFileStart != null )
this.DownloadFileStart( new DownloadFileStartEventArgs( (int)nMaxLength ) );
while( true )
{
nRecv = recvStream.Read( buffer,0,buffer.Length );
if( nRecv == 0 )
break;
fs.Write( buffer,0,nRecv );
nCount += nRecv;
//引發下載塊完成事件
if( this.DownloadFileBlock != null )
this.DownloadFileBlock( new DownloadFileEventArgs( (int)nMaxLength,(int)nCount ) );
}
recvStream.Close();
//引發下載完成事件
if( this.DownloadFileComplete != null )
this.DownloadFileComplete( this,EventArgs.Empty );
}
finally
{
fs.Close();
}
}
 
View Code

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM