翻译:用Application Updater Block生成一个自我更新的WinForms 应用

在过去的两个星期里, 我一直在做我的第一个真正的.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 会启动你的应用程序,如果更新文件下载完成之后还有可能要重启. 它需要知道启动你最新的程序的目录位置.
修改配置文件以配合当前的版本:

1<appstart>
2<clientapplicationinfo>
3<appfoldername>C:\Program Files\YourApp\1.0.0.0</appfoldername>
4<appexename>YourAppName.exe</appexename>
5<installedversion>1.0.0.0</installedversion>
6<lastupdated>2004-06-10T15:33:17.3745836-04:00</lastupdated>
7</clientapplicationinfo>
8</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 节:

1<configsections>
2<section name="appUpdater" type="Microsoft.ApplicationBlocks.ApplicationUpdater.UpdaterSectionHandler,Microsoft.ApplicationBlocks.ApplicationUpdater"></section>
3</configsections>

接下来,我们需要添加一个 Version 键到我们的 appsettings 中, 我们首先设置我们的本地版本为 1.0.0.0, 这样我们就可以测试自动更新到版本 1.0.0.1

1<appsettings>
2<add key="VERSION" value="1.0.0.0"></add>
3</appsettings>

最后,, 加入 appUpdater 节到你的配置文件中. 我这里用一对方括号把你要修改的值包含起来. 你可以直接从你上一步生成的 PublicKey.xml文件中复制

  1<rsakeyvalue> 元素. 
  2
  3<xmlfile> 元素必须要指向你在Step #6创建的虚拟目录的 URL . 
  4
  5<appupdater>
  6<updaterconfiguration>
  7<polling type="Seconds" value="120"></polling>
  8<loglistener logpath="C:\Program Files\YourApp\UpdaterLog.txt"></loglistener>
  9<downloader assembly="Microsoft.ApplicationBlocks.ApplicationUpdater,Version=1.0.0.0,Culture=neutral,PublicKeyToken=null" type="Microsoft.ApplicationBlocks.ApplicationUpdater.Downloaders.BITSDownloader"></downloader>
 10<validator assembly="Microsoft.ApplicationBlocks.ApplicationUpdater,Version=1.0.0.0,Culture=neutral,PublicKeyToken=null" type="Microsoft.ApplicationBlocks.ApplicationUpdater.Validators.RSAValidator">
 11<key>
 12<rsakeyvalue>
 13<modulus>[YOUR MODULUS KEY]</modulus>
 14<exponent>[YOUR EXPONENET]</exponent>
 15</rsakeyvalue>
 16</key>
 17</validator>
 18<application name="[YOUR APP NAME]" usevalidation="true">
 19<client>
 20<basedir>C:\Program Files\YourApp</basedir>
 21<xmlfile>C:\Program Files\YourApp\AppStart.exe.config</xmlfile>
 22<tempdir>C:\Program Files\YourApp\temp</tempdir>
 23</client>
 24<server>
 25<xmlfile>http://[YOUR URL]/ServerManifest.xml</xmlfile>
 26<xmlfiledest>C:\Program Files\YourApp\ServerManifest.xml</xmlfiledest>
 27<maxwaitxmlfile>60000</maxwaitxmlfile>
 28</server>
 29</application>
 30</updaterconfiguration>
 31</appupdater>
 32
 33Step #8 发布版本 1.0.0.0 
 34
 35设置应用程序版本号. 可以通过设置在 AssemblyInfo.cs 文件中的版本属性来设置版本号. 
 36
 37[assembly: AssemblyVersion("1.0.0.0")] 
 38
 39编译应用程序并复制 1.0.0.0 版程序到你程序的 1.0.0.0 目录中. “C:\Program Files\YourApp\1.0.0.0“ 
 40
 41这里,你需要运行一下 AppStart.exe. 更新过程会失败,因为我们并没有把发布 ServerManifest XML 文件来指示应用程序新版本是否可用. 你可以检查日志文件,位置在 C:\Program Files\YourApp\ 目录中. 
 42
 43Step #9 构建版本 1.0.0.1 
 44
 45这是最有趣的部分. 首先, 通过更新应用程序的 AssemblyInfo.cs 和 App.config 文件内容来生成修订版本 1.0.0.1 . 编译程序, 然后复制文件到step #6生成的Web服务器目录中. 
 46
 47Step #10 生成服务器的清单文件 
 48
 49这个是最后一步. 如果你对本步骤中的.config文件作了任何修改的话,都必须把本步骤重来一遍. 做法如下: 
 50
 51再次运行 ManifestUtility 程序.   
 52在 “Update files folder“ 选择器中选择 1.0.0.1 目录 .   
 53输入更新位置的 URL .   
 54输入新版本号 1.0.0.1   
 55打开之前生成的 PrivateKey.xml 文件.   
 56选择验证类 “Microsoft.ApplicationBlocks.ApplicationUpdater.Validators.RSAValidator”   
 57鼠标点击 CreateManifest, 并保存 ServerManifest.xml 文件到你的虚拟服务器目录中.   
 58就这些! Pheeew! 从你的 C:\Program Files\YourApp\ 目录中运行你的 AppStart.exe . 你的程序就会被装入, 当你的程序运行的时候,你就会得到一个提示 “新版本可用” . 新版本会下载到目录 C:\Program Files\YourApp\1.0.0.1 中, 然后程序会自动重启. 如果有任何问题, 记得检查一下日志文件. 这些日志在诊断问题的时候会很有用的. 
 59
 60-Brendan 
 61
 62posted on Thursday, June 10, 2004 11:25 AM 
 63
 64附录:文中步骤 #2 包含的代码如下: 
 65
 66Auto-Update Stuff#region Auto-Update Stuff   
 67  
 68private ApplicationUpdateManager _updater = null;   
 69private Thread _updaterThread = null;   
 70private const int UPDATERTHREAD_JOIN_TIMEOUT = 3 * 1000;   
 71  
 72private delegate void MarshalEventDelegate( object sender, UpdaterActionEventArgs e );   
 73  
 74private void InitializeAutoUpdate()   
 75{   
 76// hook ProcessExit for a chance to clean up when closed peremptorily   
 77AppDomain.CurrentDomain.ProcessExit +=new EventHandler(CurrentDomain_ProcessExit);   
 78  
 79// make an Updater for use in-process with us   
 80_updater = new ApplicationUpdateManager();   
 81  
 82// hook Updater events   
 83_updater.DownloadStarted +=new UpdaterActionEventHandler( OnUpdaterDownloadStarted );   
 84_updater.FilesValidated +=new UpdaterActionEventHandler( OnUpdaterFilesValidated );   
 85_updater.UpdateAvailable +=new UpdaterActionEventHandler( OnUpdaterUpdateAvailable );   
 86_updater.DownloadCompleted +=new UpdaterActionEventHandler(OnUpdaterDownloadCompleted);   
 87  
 88// start the updater on a separate thread so that our UI remains responsive   
 89_updaterThread = new Thread( new ThreadStart( _updater.StartUpdater ) );   
 90_updaterThread.Start();   
 91  
 92// get version from config, set caption correctly   
 93string version = System.Configuration.ConfigurationSettings.AppSettings["version"];   
 94this.Text = this.Text + String.Format(" v. {0}", version);   
 95}   
 96  
 97private void CurrentDomain_ProcessExit(object sender, EventArgs e)   
 98{   
 99StopUpdater();   
100}   
101  
102  
103private void StopUpdater()   
104{   
105// tell updater to stop   
106_updater.StopUpdater();   
107if( null != _updaterThread )   
108{   
109// join the updater thread with a suitable timeout   
110bool isThreadJoined = _updaterThread.Join( UPDATERTHREAD_JOIN_TIMEOUT );   
111// check if we joined, if we didn't interrupt the thread   
112if( !isThreadJoined )   
113{   
114_updaterThread.Interrupt();   
115}   
116_updaterThread = null;   
117}   
118}   
119  
120/**//// <summary>   
121/// This handler gets fired by the Windows UI thread that is the main STA thread for THIS FORM. It takes the same   
122/// arguments as the event handler below it--sender, e--and acts on them using the main thread NOT the eventing thread   
123/// </summary>   
124/// <param name="sender"/>marshalled reference to the original event's sender argument   
125/// <param name="e"/>marshalled reference to the original event's args   
126private void OnUpdaterDownloadStartedHandler( object sender, UpdaterActionEventArgs e )   
127{   
128Debug.WriteLine("Thread: " + Thread.CurrentThread.GetHashCode().ToString());   
129  
130Debug.WriteLine(String.Format( " DownloadStarted for application '{0}'", e.ApplicationName ));   
131}   
132  
133  
134/**//// <summary>   
135/// Event handler for Updater event. This event is fired by the originating thread from "inside" the Updater. While it is   
136/// possible for this same thread to act on our UI, it is NOT a good thing to do--UI is not threadsafe.   
137/// Therefore here we marshal from the Eventing thread (belongs to Updater) to our window thread using the synchronous Invoke   
138/// mechanism.   
139/// </summary>   
140/// <param name="sender"/>event sender in this case ApplicationUpdaterManager   
141/// <param name="e"/>the UpdaterActionEventArgs packaged by Updater, which gives us access to update information   
142private void OnUpdaterDownloadStarted( object sender, UpdaterActionEventArgs e )   
143{   
144// using the synchronous "Invoke". This marshals from the eventing thread--which comes from the Updater and should not   
145// be allowed to enter and "touch" the UI's window thread   
146// so we use Invoke which allows us to block the Updater thread at will while only allowing window thread to update UI   
147Debug.WriteLine( String.Format( "[OnUpdaterDownloadStarted]Thread: {0}", Thread.CurrentThread.GetHashCode().ToString()) );   
148this.Invoke(   
149new MarshalEventDelegate( this.OnUpdaterDownloadStartedHandler ),   
150new object[] { sender, e } );   
151}   
152  
153  
154/**//// <summary>   
155/// This handler gets fired by the Windows UI thread that is the main STA thread for THIS FORM. It takes the same   
156/// arguments as the event handler below it--sender, e--and acts on them using the main thread NOT the eventing thread   
157/// </summary>   
158/// <param name="sender"/>marshalled reference to the original event's sender argument   
159/// <param name="e"/>marshalled reference to the original event's args   
160private void OnUpdaterFilesValidatedHandler( object sender, UpdaterActionEventArgs e )   
161{   
162Debug.WriteLine(String.Format("FilesValidated successfully for application '{0}' ", e.ApplicationName));   
163  
164// ask user to use new app   
165DialogResult dialog = MessageBox.Show(   
166"Would you like to stop this application and open the new version?", "Open New Version?", MessageBoxButtons.YesNo );   
167if( DialogResult.Yes == dialog )   
168{   
169StartNewVersion( e.ServerInformation );   
170}   
171}   
172  
173/**//// <summary>   
174/// Event handler for Updater event. This event is fired by the originating thread from "inside" the Updater. While it is   
175/// possible for this same thread to act on our UI, it is NOT a good thing to do--UI is not threadsafe.   
176/// Therefore here we marshal from the Eventing thread (belongs to Updater) to our window thread using the synchronous Invoke   
177/// mechanism.   
178/// </summary>   
179/// <param name="sender"/>event sender in this case ApplicationUpdaterManager   
180/// <param name="e"/>the UpdaterActionEventArgs packaged by Updater, which gives us access to update information   
181private void OnUpdaterFilesValidated( object sender, UpdaterActionEventArgs e )   
182{   
183// using the asynchronous "BeginInvoke".   
184// we don't need/want to block here   
185this.BeginInvoke(   
186new MarshalEventDelegate( this.OnUpdaterFilesValidatedHandler ),   
187new object[] { sender, e } );   
188}   
189  
190  
191/**//// <summary>   
192/// This handler gets fired by the Windows UI thread that is the main STA thread for THIS FORM. It takes the same   
193/// arguments as the event handler below it--sender, e--and acts on them using the main thread NOT the eventing thread   
194/// </summary>   
195/// <param name="sender"/>marshalled reference to the original event's sender argument   
196/// <param name="e"/>marshalled reference to the original event's args   
197private void OnUpdaterUpdateAvailableHandler( object sender, UpdaterActionEventArgs e )   
198{   
199Debug.WriteLine("Thread: " + Thread.CurrentThread.GetHashCode().ToString());   
200  
201string message = String.Format(   
202"Update available: The new version on the server is {0} and current version is {1} would you like to upgrade?",   
203e.ServerInformation.AvailableVersion,   
204System.Configuration.ConfigurationSettings.AppSettings["version"] ) ;   
205  
206// for update available we actually WANT to block the downloading thread so we can refuse an update   
207// and reset until next polling cycle;   
208// NOTE that we don't block the thread _in the UI_, we have it blocked at the marshalling dispatcher "OnUpdaterUpdateAvailable"   
209DialogResult dialog = MessageBox.Show( message, "Update Available", MessageBoxButtons.YesNo );   
210  
211if( DialogResult.No == dialog )   
212{   
213// if no, stop the updater for this app   
214_updater.StopUpdater( e.ApplicationName );   
215Debug.WriteLine("Update Cancelled.");   
216}   
217else   
218{   
219Debug.WriteLine("Update in progress.");   
220}   
221}   
222  
223/**//// <summary>   
224/// Event handler for Updater event. This event is fired by the originating thread from "inside" the Updater. While it is   
225/// possible for this same thread to act on our UI, it is NOT a good thing to do--UI is not threadsafe.   
226/// Therefore here we marshal from the Eventing thread (belongs to Updater) to our window thread using the synchronous Invoke   
227/// mechanism.   
228/// </summary>   
229/// <param name="sender"/>event sender in this case ApplicationUpdaterManager   
230/// <param name="e"/>the UpdaterActionEventArgs packaged by Updater, which gives us access to update information   
231private void OnUpdaterUpdateAvailable( object sender, UpdaterActionEventArgs e )   
232{   
233// using the synchronous "Invoke". This marshals from the eventing thread--which comes from the Updater and should not   
234// be allowed to enter and "touch" the UI's window thread   
235// so we use Invoke which allows us to block the Updater thread at will while only allowing window thread to update UI   
236this.Invoke(   
237new MarshalEventDelegate( this.OnUpdaterUpdateAvailableHandler ),   
238new object[] { sender, e } );   
239}   
240  
241  
242/**//// <summary>   
243/// This handler gets fired by the Windows UI thread that is the main STA thread for THIS FORM. It takes the same   
244/// arguments as the event handler below it--sender, e--and acts on them using the main thread NOT the eventing thread   
245/// </summary>   
246/// <param name="sender"/>marshalled reference to the original event's sender argument   
247/// <param name="e"/>marshalled reference to the original event's args   
248private void OnUpdaterDownloadCompletedHandler( object sender, UpdaterActionEventArgs e )   
249{   
250Debug.WriteLine("Download Completed.");   
251  
252}   
253  
254/**//// <summary>   
255/// Event handler for Updater event. This event is fired by the originating thread from "inside" the Updater. While it is   
256/// possible for this same thread to act on our UI, it is NOT a good thing to do--UI is not threadsafe.   
257/// Therefore here we marshal from the Eventing thread (belongs to Updater) to our window thread using the synchronous Invoke   
258/// mechanism.   
259/// </summary>   
260/// <param name="sender"/>event sender in this case ApplicationUpdaterManager   
261/// <param name="e"/>the UpdaterActionEventArgs packaged by Updater, which gives us access to update information   
262private void OnUpdaterDownloadCompleted( object sender, UpdaterActionEventArgs e )   
263{   
264// using the synchronous "Invoke". This marshals from the eventing thread--which comes from the Updater and should not   
265// be allowed to enter and "touch" the UI's window thread   
266// so we use Invoke which allows us to block the Updater thread at will while only allowing window thread to update UI   
267this.Invoke(   
268new MarshalEventDelegate( this.OnUpdaterDownloadCompletedHandler ),   
269new object[] { sender, e } );   
270}   
271  
272  
273private void StartNewVersion( ServerApplicationInfo server )   
274{   
275XmlDocument doc = new XmlDocument();   
276  
277// load config file to get base dir   
278doc.Load( AppDomain.CurrentDomain.SetupInformation.ConfigurationFile );   
279  
280// get the base dir   
281string baseDir = doc.SelectSingleNode("configuration/appUpdater/UpdaterConfiguration/application/client/baseDir").InnerText;   
282string newDir = Path.Combine( baseDir, "AppStart.exe" );   
283  
284ProcessStartInfo process = new ProcessStartInfo( newDir );   
285process.WorkingDirectory = Path.Combine( newDir , server.AvailableVersion );   
286  
287// launch new version (actually, launch AppStart.exe which HAS pointer to new version )   
288Process.Start( process );   
289  
290// tell updater to stop   
291CurrentDomain_ProcessExit( null, null );   
292// leave this app   
293Environment.Exit( 0 );   
294}</xmlfile></rsakeyvalue>
Published At
Categories with Web编程
Tagged with
comments powered by Disqus