用Application Updater Block生成一個自我更新的WinForms 應用(amart client)

 

在過去的兩個星期裏, 我一直在做我的第一個真正的.net WinForm應用的開發.  這是一個很有趣的過程,我一直在瘋了似的學習東西.  其中之一就是我要允許應用程序能夠用微軟的Application Updater Block進行自我更新。  當它正常工作的那一刻,讓我有一種很大的成就感,同時我也意識到微軟沒有提供那種按步驟順序的例子。  Duncan Mackenzie 有一個 很好的blog文章 可以做一個開始,但是這個例子是VB做的並且沒有提供RSA公鑰和私鑰的細節情況,所以我決定說一下我的工作過程。  希望能對你有用!

Step #1 Install the Application Blocks

Download the Updater Application Block from Microsoft .

Run the MSI Installer.

Step #2 在項目中加入代碼和引用:

把下列工程加入到你的WinForm工程所在的解決方案:

Microsoft.ApplicationBlocks.ApplicationUpdater
Microsoft.ApplicationBlocks.ApplicationUpdater.Interfaces
Microsoft.ApplicationBlocks.ExceptionManagement
Microsoft.ApplicationBlocks.ExceptionManagement.Interfaces

如果你選擇默認安裝的話,它們的位置可能是:

C:/Program Files/Microsoft Application Blocks for .NET/Updater/Code/CS/Microsoft.ApplicationBlocks.Updater

在你的WinForm工程中引用下列工程

Microsoft.ApplicationBlocks.ApplicationUpdater
Microsoft.ApplicationBlocks.ApplicationUpdater.Interfaces
Microsoft.ApplicationBlocks.ExceptionManagement

把下列命名空間加入到你Form的.cs文件中 

 

using System.Runtime.InteropServices; 
using System.Runtime.Serialization; 
using System.Threading; 
using System.Diagnostics; 
using System.IO; 
using System.Xml; 

然後 添加這個位置的應用程序更新代碼到你的代碼中.  你需要從你的MainForm初始化方法中調用 InitializeAutoUpdate()。

Step #3 生成你應用程序的發佈目錄結構並配置 AppStart.exe

生成一個用於客戶端程序安裝的目錄.  本例子中,我們用如下的目錄:

C:/Program Files/YourApp/1.0.0.0/

現在複製 AppStart.exe 和 AppStart.exe.config 到類似如下的根目錄中

 C:/Program Files/YourApp/AppStart.exe
 C:/Program Files/YourApp/AppStart.exe.config

說明: 這兩個文件你可以在如下目錄中找到 “C:/Program Files/Microsoft Application Blocks for .NET/Updater/Code/CS/Microsoft.ApplicationBlocks.Updater/AppStart/bin/Debug“  

Step #4 修改 AppStart.exe.config 文件

AppStart.exe 會啓動你的應用程序,如果更新文件下載完成之後還有可能要重啓.  它需要知道啓動你最新的程序的目錄位置. 
修改配置文件以配合當前的版本:

 

<appStart> 
  
<ClientApplicationInfo> 
    
<appFolderName>C:Program FilesYourApp1.0.0.0</appFolderName> 
    
<appExeName>YourAppName.exe</appExeName> 
    
<installedVersion>1.0.0.0</installedVersion> 
    
<lastUpdated>2004-06-10T15:33:17.3745836-04:00</lastUpdated> 
  
</ClientApplicationInfo> 
</appStart> 

Step #5: 生成你的公鑰和私鑰

運行 "C:/Program Files/Microsoft Application Blocks for .NET/Updater/Code/CS/Microsoft.ApplicationBlocks.Updater/ManifestUtility/bin/Debug/ManifestUtility.exe"

選擇 “File..Generate Keys”  會提示你是否需要保存: PublicKey.xml 和 PrivateKey.xml  這兩個密鑰接下來就會用到. 

我這裏要提醒大家,這些密鑰只要生成一次就可以了, 因爲下面幾個地方需要引用到RSA公鑰和私鑰.  你需要把這些密鑰存放在一個安全的地方,因爲在發佈一個新的更新的時候會用到它

Step #6 創建IIS 虛擬目錄

在你的Web服務器上生成一個目錄來存放你的更新文件.  在這兩個目錄中要放兩樣東西 1)  ServerManifest.xml 文件,包含最後版本的一些信息;2) 你的新程序的目錄. 在這個目錄裏,生成一個目錄來存放你的新版本程序.  在我們的例子中,我們用這兩個目錄, C:/Inetpub/AppUpdates  和C:/Inetpub/AppUpdates/1.0.0.1

用 IIS 管理器生成一個虛擬目錄指向剛纔的實際目錄.  記下你的 URL, 在上傳步驟中我們需要用到它.  你必須要打開虛擬目錄的“目錄瀏覽”選項.

Step #7. 配置你的版本 1.0.0.0 的App.config 文件

這裏,我們會需要往裏添加一些新東西.  首先, 我們需要加入一個configSections 元素來定義我們的 appUpdater 節:

<configSections>
  <section name="appUpdater" type="Microsoft.ApplicationBlocks.ApplicationUpdater.UpdaterSectionHandler,Microsoft.ApplicationBlocks.ApplicationUpdater" />
</configSections>

接下來,我們需要添加一個 Version 鍵到我們的 appsettings 中, 我們首先設置我們的本地版本爲 1.0.0.0, 這樣我們就可以測試自動更新到版本 1.0.0.1

<appSettings>
  <add key="VERSION" value="1.0.0.0" />
</appSettings>

最後,, 加入 appUpdater 節到你的配置文件中.  我這裏用一對方括號把你要修改的值包含起來.  你可以直接從你上一步生成的 PublicKey.xml文件中複製 <RSAKeyValue> 元素.

<xmlFile> 元素必須要指向你在Step #6創建的虛擬目錄的 URL .

 

<appUpdater> 
  
<UpdaterConfiguration> 
   
<polling type="Seconds" value="120" /> 
   
<logListener logPath="C:Program FilesYourAppUpdaterLog.txt" /> 
   
<downloader type="Microsoft.ApplicationBlocks.ApplicationUpdater.Downloaders.BITSDownloader" 
assembly
="Microsoft.ApplicationBlocks.ApplicationUpdater,Version=1.0.0.0,Culture=neutral,PublicKeyToken=null"/> 
   
<validator type="Microsoft.ApplicationBlocks.ApplicationUpdater.Validators.RSAValidator" assembly="Microsoft.ApplicationBlocks.ApplicationUpdater,Version=1.0.0.0,Culture=neutral,PublicKeyToken=null"> 
  
<key> 
   
<RSAKeyValue> 
  
<Modulus>[YOUR MODULUS KEY]</Modulus> 
  
<Exponent>[YOUR EXPONENET]</Exponent> 
  
</RSAKeyValue> 
  
</key> 
  
</validator>  
  
<application name="[YOUR APP NAME]" useValidation="true"> 
    
<client> 
      
<baseDir>C:Program FilesYourApp</baseDir> 
      
<xmlFile>C:Program FilesYourAppAppStart.exe.config</xmlFile> 
      
<tempDir>C:Program FilesYourApp emp</tempDir> 
    
</client> 
    
<server> 
     
<xmlFile>http://[YOUR URL]/ServerManifest.xml</xmlFile> 
     <xmlFileDest>C:Program FilesYourAppServerManifest.xml</xmlFileDest> 
     
<maxWaitXmlFile>60000</maxWaitXmlFile> 
    
</server> 
  
</application> 
 
</UpdaterConfiguration> 
 
</appUpdater> 

Step #8 發佈版本 1.0.0.0

設置應用程序版本號.  可以通過設置在 AssemblyInfo.cs 文件中的版本屬性來設置版本號.

[assembly: AssemblyVersion("1.0.0.0")]

編譯應用程序並複製 1.0.0.0 版程序到你程序的 1.0.0.0 目錄中. “C:/Program Files/YourApp/1.0.0.0“

這裏,你需要運行一下 AppStart.exe.  更新過程會失敗,因爲我們並沒有把發佈 ServerManifest XML 文件來指示應用程序新版本是否可用.  你可以檢查日誌文件,位置在 C:/Program Files/YourApp/ 目錄中.

Step #9 構建版本 1.0.0.1 

這是最有趣的部分.  首先, 通過更新應用程序的 AssemblyInfo.cs 和 App.config 文件內容來生成修訂版本 1.0.0.1 .  編譯程序, 然後複製文件到step #6生成的Web服務器目錄中. 

Step #10 生成服務器的清單文件

這個是最後一步.  如果你對本步驟中的.config文件作了任何修改的話,都必須把本步驟重來一遍.  做法如下:

再次運行 ManifestUtility 程序. 
在 “Update files folder“ 選擇器中選擇 1.0.0.1 目錄 . 
輸入更新位置的 URL . 
輸入新版本號 1.0.0.1
打開之前生成的 PrivateKey.xml 文件.
選擇驗證類 “Microsoft.ApplicationBlocks.ApplicationUpdater.Validators.RSAValidator”
鼠標點擊 CreateManifest, 並保存 ServerManifest.xml 文件到你的虛擬服務器目錄中.
就這些!  Pheeew!  從你的 C:/Program Files/YourApp/ 目錄中運行你的 AppStart.exe .  你的程序就會被裝入, 當你的程序運行的時候,你就會得到一個提示 “新版本可用” .  新版本會下載到目錄 C:/Program Files/YourApp/1.0.0.1 中, 然後程序會自動重啓.  如果有任何問題, 記得檢查一下日誌文件.  這些日誌在診斷問題的時候會很有用的.

-Brendan

posted on Thursday, June 10, 2004 11:25 AM

附錄:文中步驟 #2 包含的代碼如下:

 

Auto-Update StuffAuto-Update Stuff
/**//// <summary> 
/// This handler gets fired by the Windows UI thread that is the main STA thread for THIS FORM.  It takes the same  
/// arguments as the event handler below it--sender, e--and acts on them using the main thread NOT the eventing thread 
/// </summary> 
/// <param name="sender">marshalled reference to the original event's sender argument</param> 
/// <param name="e">marshalled reference to the original event's args</param> 

private void OnUpdaterUpdateAvailableHandler( object sender, UpdaterActionEventArgs e ) 
{     
    Debug.WriteLine(
"Thread: " + Thread.CurrentThread.GetHashCode().ToString()); 
 
    
string message = String.Format(  
        
"Update available:  The new version on the server is {0} and current version is {1} would you like to upgrade?",  
        e.ServerInformation.AvailableVersion,   
        System.Configuration.ConfigurationSettings.AppSettings[
"version"] ) ; 
 
    
//  for update available we actually WANT to block the downloading thread so we can refuse an update 
    
//  and reset until next polling cycle; 
    
//  NOTE that we don't block the thread _in the UI_, we have it blocked at the marshalling dispatcher "OnUpdaterUpdateAvailable" 
    DialogResult dialog = MessageBox.Show( message, "Update Available", MessageBoxButtons.YesNo ); 
 
    
if( DialogResult.No == dialog ) 
    

        
//  if no, stop the updater for this app 
        _updater.StopUpdater( e.ApplicationName ); 
        Debug.WriteLine(
"Update Cancelled."); 
    }
 
    
else 
    

        Debug.WriteLine(
"Update in progress."); 
    }
 
}
 
 
/**//// <summary> 
/// Event handler for Updater event.  This event is fired by the originating thread from "inside" the Updater.  While it is 
/// possible for this same thread to act on our UI, it is NOT a good thing to do--UI is not threadsafe.   
/// Therefore here we marshal from the Eventing thread (belongs to Updater) to our window thread using the synchronous Invoke 
/// mechanism. 
/// </summary> 
/// <param name="sender">event sender in this case ApplicationUpdaterManager</param> 
/// <param name="e">the UpdaterActionEventArgs packaged by Updater, which gives us access to update information</param> 

private void OnUpdaterUpdateAvailable( object sender, UpdaterActionEventArgs e ) 

    
//  using the synchronous "Invoke".  This marshals from the eventing thread--which comes from the Updater and should not 
    
//  be allowed to enter and "touch" the UI's window thread 
    
//  so we use Invoke which allows us to block the Updater thread at will while only allowing window thread to update UI 
    this.Invoke(  
        
new MarshalEventDelegate( this.OnUpdaterUpdateAvailableHandler ),  
        
new object[] { sender, e } ); 
}
 
 
     
/**//// <summary> 
/// This handler gets fired by the Windows UI thread that is the main STA thread for THIS FORM.  It takes the same  
/// arguments as the event handler below it--sender, e--and acts on them using the main thread NOT the eventing thread 
/// </summary> 
/// <param name="sender">marshalled reference to the original event's sender argument</param> 
/// <param name="e">marshalled reference to the original event's args</param> 

private void OnUpdaterDownloadCompletedHandler( object sender, UpdaterActionEventArgs e ) 

    Debug.WriteLine(
"Download Completed."); 
 
}
 
 
/**//// <summary> 
/// Event handler for Updater event.  This event is fired by the originating thread from "inside" the Updater.  While it is 
/// possible for this same thread to act on our UI, it is NOT a good thing to do--UI is not threadsafe.   
/// Therefore here we marshal from the Eventing thread (belongs to Updater) to our window thread using the synchronous Invoke 
/// mechanism. 
/// </summary> 
/// <param name="sender">event sender in this case ApplicationUpdaterManager</param> 
/// <param name="e">the UpdaterActionEventArgs packaged by Updater, which gives us access to update information</param> 

private void OnUpdaterDownloadCompleted( object sender, UpdaterActionEventArgs e ) 

    
//  using the synchronous "Invoke".  This marshals from the eventing thread--which comes from the Updater and should not 
    
//  be allowed to enter and "touch" the UI's window thread 
    
//  so we use Invoke which allows us to block the Updater thread at will while only allowing window thread to update UI 
    this.Invoke(  
        
new MarshalEventDelegate( this.OnUpdaterDownloadCompletedHandler ),  
        
new object[] { sender, e } ); 
}
 
 
 
private void StartNewVersion( ServerApplicationInfo server ) 

    XmlDocument doc 
= new XmlDocument(); 
 
    
//  load config file to get base dir 
    doc.Load( AppDomain.CurrentDomain.SetupInformation.ConfigurationFile ); 
 
    
//  get the base dir 
    string baseDir = doc.SelectSingleNode("configuration/appUpdater/UpdaterConfiguration/application/client/baseDir").InnerText; 
    
string newDir = Path.Combine( baseDir, "AppStart.exe" ); 
 
    ProcessStartInfo process 
= new ProcessStartInfo( newDir ); 
    process.WorkingDirectory 
= Path.Combine( newDir , server.AvailableVersion ); 
 
    
//  launch new version (actually, launch AppStart.exe which HAS pointer to new version ) 
    Process.Start( process ); 
 
    
//  tell updater to stop 
    CurrentDomain_ProcessExit( nullnull ); 
    
//  leave this app 
    Environment.Exit( 0 ); 
}
 
發佈了22 篇原創文章 · 獲贊 3 · 訪問量 9萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章