Updater Application Block實踐

Step #1 Install the Application Blocks 安裝自動更新組件

Download the Updater Application Block from Microsoft.

Step #2 Add the Code and References to Your Project: 添加代碼和引用到工程

Add the following projects to the solution containing your Winforms project :

把以下工程添加到你的解決方案

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

They should be located in the following folder, if you've accepted the defaults 默認的安裝位置在

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

Reference the following projects from within your Winforms Project 在工程中添加引用

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

Add the following namespaces to your form's .cs file.

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

using Microsoft.ApplicationBlocks.ApplicationUpdater; 工程要引用其中的類

Then add the application updater code located here to your code. You'll need to call InitializeAutoUpdate() from your MainForm's Initialize method.把以下代碼添加到工程首頁。

Step #3 Create your Application's Deployment Directory Structure and Configure AppStart.exe 創建發佈目錄並配置配置文件

Create a folder for your client installation. For example purposes, we'll use the following directory: 創建客戶端安裝目錄,如:

C:/Program Files/YourApp/1.0.0.0/

Now copy the AppStart.exe and AppStart.exe.config into the root directory like so: 把以下兩個文件拷貝到安裝的根目錄(C:/Program Files/YourApp)

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

Note: You can find these two files in the “C:/Program Files/Microsoft Application Blocks for .NET/Updater/Code/CS/Microsoft.ApplicationBlocks.Updater/AppStart/bin/Debug“ directory.

 

Step #4 Modify the AppStart.exe.config File 修改配置文件AppStart.exe.config

Appstart.exe.config將負責啓動你的程序。

Modify this config file to match the current version like so: 按照你程序的當前版本配置此文件:


<appStart>
<ClientApplicationInfo>
<appFolderName>C:/Program Files/YourApp/1.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: Create Your Public and Private Keys 創建公匙和私匙

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

Choose “File..Generate Keys” You will be prompted to save the following keys: PublicKey.xml and PrivateKey.xml You'll be using these keys in the next steps.

From what I can tell it's best to create these keys once, because you'll need to reference these RSA public and private keys in a couple of places. You'll want to keep your keys in a safe place, because you'll need them when pushing new updates.

此工具生成成對的公匙和私匙。

Step #6 Create Your IIS Virtual Directory

Create a directory on your WebServer to house your updates. You will end up with two things in this directory

1) a ServerManifest.xml file which will contain the latest version information, and

2) your new application directory. Within this directory, create a directory to house your new application version. So, for our example, we'll create the directories, C:/Inetpub/AppUpdates and C:/Inetpub/AppUpdates/1.0.0.1

Use IIS Manager to create a virtual directory pointing to this physical directory. Remember your URL, as you will need it in an upcoming step. You will have to enable directory browsing for this virtual directory.

創建IIS的虛擬目錄,在此存放你的新程序版本,例如:

C:/Inetpub/AppUpdates 包括你新版本程序的信息的配置文件將放在此目錄

C:/Inetpub/AppUpdates/1.0.0.1 新版本的程序放在此目錄

Step #7. Configure Your Version 1.0.0.0 App.config File 配置你程序的配置文件(你的程序名.config即完整程序名加.config例如:App01.exe.config 程序將默認尋找與其同名的.config配置文件,{DLL沒有此項功能})

Here, we'll need to add a few things. First, we need to add a configSections element to define our appUpdater section:


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

Next we need to add the Version key to our appsettings key, we're first going to set our local versoin to 1.0.0.0, so that we can test the auto-update to version 1.0.0.1

<appSettings>
<add key="VERSION" value="1.0.0.0" /> 注意版本號一定要與程序的設定的版本號匹配
</appSettings>

Finally, add the appUpdater section to your config file. I've put brackets where you need to edit the values. You can just copy the <RSAKeyValue> element from the PublicKey.xml file that you created in the previous step. 把剛纔生成的公匙填到配置文件的相應位置,(以下[]中)。

<appUpdater>
<UpdaterConfiguration>
<polling type="Seconds" value="120" />
<logListener logPath="C:/Program Files/YourApp/UpdaterLog.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"> 此處是你程序的名稱,如App01,無需後綴
<client>
<baseDir>C:/Program Files/YourApp</baseDir>
<xmlFile>C:/Program Files/YourApp/AppStart.exe.config</xmlFile>
<tempDir>C:/Program Files/YourApp/temp</tempDir>
</client>
<server>
<xmlFile>http://[YOUR URL]/ServerManifest.xml</xmlFile>
<xmlFileDest>C:/Program Files/YourApp/ServerManifest.xml</xmlFileDest>
<maxWaitXmlFile>60000</maxWaitXmlFile>
</server>
</application>
</UpdaterConfiguration>
</appUpdater>

Step #8 Deploy Version 1.0.0.0 發佈版本1.0.0.0


Version your app. You do this by setting the version attribute of your app's AssemblyInfo.cs file. 修改你程序的版本號(在AssemblyInfo.cs中)

[assembly: AssemblyVersion("1.0.0.0")]

Build the Application and copy version 1.0.0.0 to your program file's 1.0.0.0 directory. “C:/Program Files/YourApp/1.0.0.0“將版本1.0.0.0拷貝到你的客戶端安裝目錄(注意,在版本號下1.0.0.0)

At this point, you should be able to run AppStart.exe. The update process should fail, since we haven't deployed the ServerManifest XML file which tells our app that there's a new version available. You can inspect the log files that should be created in your C:/Program Files/YourApp/ directory.

Step #9 Buld Version 1.0.0.1 創建新版本1.0.0.1

將新版本拷貝到你的發佈目錄,在step #6創建的C:/Inetpub/AppUpdates/1.0.0.1下

注意同時拷貝到發佈目錄的還應該有:配置文件 App01.exe.config

因爲可能找不到微軟的組件,以下幾個文件也一同拷貝,(否則升級時可能會報錯system.io.filenotfoundException的錯誤,找不到相應的文件)

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

注意以上文件都在版本號的目錄下,C:/Inetpub/AppUpdates/1.0.0.1

Step #10 Create Your Server Manifest File 生成服務端的升級說明文件

This should be the last step. Any changes you make to the .config files from this point on will require repeating this step. To do this:

Launch the ManifestUtility program again. 再次用工具 ManifestUtility (step #5中的工具)
Choose the 1.0.0.1 directory from the “Update files folder“ chooser. 選擇新版本程序的目錄(注意是帶版本號的目錄)
Enter the URL of the update location. 新版本的HTTP的址(注意帶版本號的目錄)
Enter version 1.0.0.1 新的程序版本號
Open your PrivateKey.xml file created earlier. Key處添加你剛纔生成的私匙
Choose the validator class 選擇校驗類爲RSA的“Microsoft.ApplicationBlocks.ApplicationUpdater.Validators.RSAValidator”
Click CreateManifest, and save the ServerManifest.xml file to your virtual server directory. 生成新版本說明文件,存放在虛擬目錄下(注意是不帶版本號的,C:/Inetpub/AppUpdates/)
生成的ServerManifest.xml文件中說明了要升級的文件的內容,1.0.0.1下的內容都將下載。

step 2 的代碼
#region Auto-Update Stuff

private ApplicationUpdateManager _updater = null;
private Thread _updaterThread = null;
private const int UPDATERTHREAD_JOIN_TIMEOUT = 3 * 1000;

private delegate void MarshalEventDelegate( object sender, UpdaterActionEventArgs e );

private void InitializeAutoUpdate()
{
// hook ProcessExit for a chance to clean up when closed peremptorily
AppDomain.CurrentDomain.ProcessExit +=new EventHandler(CurrentDomain_ProcessExit);

// make an Updater for use in-process with us
_updater = new ApplicationUpdateManager();

// hook Updater events
_updater.DownloadStarted +=new UpdaterActionEventHandler( OnUpdaterDownloadStarted );
_updater.FilesValidated +=new UpdaterActionEventHandler( OnUpdaterFilesValidated );
_updater.UpdateAvailable +=new UpdaterActionEventHandler( OnUpdaterUpdateAvailable );
_updater.DownloadCompleted +=new UpdaterActionEventHandler(OnUpdaterDownloadCompleted);

// start the updater on a separate thread so that our UI remains responsive
_updaterThread = new Thread( new ThreadStart( _updater.StartUpdater ) );
_updaterThread.Start();

// get version from config, set caption correctly
string version = System.Configuration.ConfigurationSettings.AppSettings["version"];
this.Text = this.Text + String.Format(" v. {0}", version);
}

private void CurrentDomain_ProcessExit(object sender, EventArgs e)
{
StopUpdater();
}


private void StopUpdater()
{
// tell updater to stop
_updater.StopUpdater();
if( null != _updaterThread )
{
// join the updater thread with a suitable timeout
bool isThreadJoined = _updaterThread.Join( UPDATERTHREAD_JOIN_TIMEOUT );
// check if we joined, if we didn't interrupt the thread
if( !isThreadJoined )
{
_updaterThread.Interrupt();
}
_updaterThread = null;
}
}

/// <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 OnUpdaterDownloadStartedHandler( object sender, UpdaterActionEventArgs e )
{
Debug.WriteLine("Thread: " + Thread.CurrentThread.GetHashCode().ToString());

Debug.WriteLine(String.Format( " DownloadStarted for application '{0}'", e.ApplicationName ));
}


/// <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 OnUpdaterDownloadStarted( 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
Debug.WriteLine( String.Format( "[OnUpdaterDownloadStarted]Thread: {0}", Thread.CurrentThread.GetHashCode().ToString()) );
this.Invoke(
new MarshalEventDelegate( this.OnUpdaterDownloadStartedHandler ),
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 OnUpdaterFilesValidatedHandler( object sender, UpdaterActionEventArgs e )
{
Debug.WriteLine(String.Format("FilesValidated successfully for application '{0}' ", e.ApplicationName));

// ask user to use new app
DialogResult dialog = MessageBox.Show(
"Would you like to stop this application and open the new version?", "Open New Version?", MessageBoxButtons.YesNo );
if( DialogResult.Yes == dialog )
{
StartNewVersion( e.ServerInformation );
}
}

/// <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 OnUpdaterFilesValidated( object sender, UpdaterActionEventArgs e )
{
// using the asynchronous "BeginInvoke".
// we don't need/want to block here
this.BeginInvoke(
new MarshalEventDelegate( this.OnUpdaterFilesValidatedHandler ),
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 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( null, null );
// leave this app
Environment.Exit( 0 );
}

 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章