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文件里指定的路徑和文件名來生成指定的文件,在這之前先前已經存在的文件刪除。更新完畢后,則重新啟動主應用程序。這樣更新就完成了。
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 = "已不是最新版本!"; //} }
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/
自動更新

現在但凡是一個程序都有相應的升級程序,如果你的程序沒有相應的升級程序,那么你就需要留意了。你的用戶很可能丟失!!!網上關於自動升級的例子也有很多,前幾天一個朋友很苦惱的跟我說它的客戶在逐漸減少(據他所說,他都客戶因為他的程序升級很麻煩,所以很多人放棄了使用它的軟件),問我說怎么辦?其實他也知道該怎么辦?所以...朋友嘛!就給他做了一個自動升級程序。恰好今天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(); } }