用Application Updater Block生成一个自我更新的WinForms 应用
bigtall
在过去的两个星期里, 我一直在做我的第一个真正的.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 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: 生成你的公钥和私钥
运行 "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 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">
<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 发布版本 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
2
private ApplicationUpdateManager _updater = null; 3
private Thread _updaterThread = null; 4
private const int UPDATERTHREAD_JOIN_TIMEOUT = 3 * 1000; 5
6
private delegate void MarshalEventDelegate( object sender, UpdaterActionEventArgs e ); 7
8
private void InitializeAutoUpdate() 9
{ 10
// hook ProcessExit for a chance to clean up when closed peremptorily 11
AppDomain.CurrentDomain.ProcessExit +=new EventHandler(CurrentDomain_ProcessExit); 12
13
// make an Updater for use in-process with us 14
_updater = new ApplicationUpdateManager(); 15
16
// hook Updater events 17
_updater.DownloadStarted +=new UpdaterActionEventHandler( OnUpdaterDownloadStarted ); 18
_updater.FilesValidated +=new UpdaterActionEventHandler( OnUpdaterFilesValidated ); 19
_updater.UpdateAvailable +=new UpdaterActionEventHandler( OnUpdaterUpdateAvailable ); 20
_updater.DownloadCompleted +=new UpdaterActionEventHandler(OnUpdaterDownloadCompleted); 21
22
// start the updater on a separate thread so that our UI remains responsive 23
_updaterThread = new Thread( new ThreadStart( _updater.StartUpdater ) ); 24
_updaterThread.Start(); 25
26
// get version from config, set caption correctly 27
string version = System.Configuration.ConfigurationSettings.AppSettings["version"]; 28
this.Text = this.Text + String.Format(" v. {0}", version); 29
} 30
31
private void CurrentDomain_ProcessExit(object sender, EventArgs e) 32
{ 33
StopUpdater(); 34
} 35
36
37
private void StopUpdater() 38
{ 39
// tell updater to stop 40
_updater.StopUpdater(); 41
if( null != _updaterThread ) 42
{ 43
// join the updater thread with a suitable timeout 44
bool isThreadJoined = _updaterThread.Join( UPDATERTHREAD_JOIN_TIMEOUT ); 45
// check if we joined, if we didn't interrupt the thread 46
if( !isThreadJoined ) 47
{ 48
_updaterThread.Interrupt(); 49
} 50
_updaterThread = null; 51
} 52
} 53
54
/**//// <summary> 55
/// This handler gets fired by the Windows UI thread that is the main STA thread for THIS FORM. It takes the same 56
/// arguments as the event handler below it--sender, e--and acts on them using the main thread NOT the eventing thread 57
/// </summary> 58
/// <param name="sender">marshalled reference to the original event's sender argument</param> 59
/// <param name="e">marshalled reference to the original event's args</param> 60
private void OnUpdaterDownloadStartedHandler( object sender, UpdaterActionEventArgs e ) 61
{ 62
Debug.WriteLine("Thread: " + Thread.CurrentThread.GetHashCode().ToString()); 63
64
Debug.WriteLine(String.Format( " DownloadStarted for application '{0}'", e.ApplicationName )); 65
} 66
67
68
/**//// <summary> 69
/// Event handler for Updater event. This event is fired by the originating thread from "inside" the Updater. While it is 70
/// possible for this same thread to act on our UI, it is NOT a good thing to do--UI is not threadsafe. 71
/// Therefore here we marshal from the Eventing thread (belongs to Updater) to our window thread using the synchronous Invoke 72
/// mechanism. 73
/// </summary> 74
/// <param name="sender">event sender in this case ApplicationUpdaterManager</param> 75
/// <param name="e">the UpdaterActionEventArgs packaged by Updater, which gives us access to update information</param> 76
private void OnUpdaterDownloadStarted( object sender, UpdaterActionEventArgs e ) 77
{ 78
// using the synchronous "Invoke". This marshals from the eventing thread--which comes from the Updater and should not 79
// be allowed to enter and "touch" the UI's window thread 80
// so we use Invoke which allows us to block the Updater thread at will while only allowing window thread to update UI 81
Debug.WriteLine( String.Format( "[OnUpdaterDownloadStarted]Thread: {0}", Thread.CurrentThread.GetHashCode().ToString()) ); 82
this.Invoke( 83
new MarshalEventDelegate( this.OnUpdaterDownloadStartedHandler ), 84
new object[] { sender, e } ); 85
} 86
87
88
/**//// <summary> 89
/// This handler gets fired by the Windows UI thread that is the main STA thread for THIS FORM. It takes the same 90
/// arguments as the event handler below it--sender, e--and acts on them using the main thread NOT the eventing thread 91
/// </summary> 92
/// <param name="sender">marshalled reference to the original event's sender argument</param> 93
/// <param name="e">marshalled reference to the original event's args</param> 94
private void OnUpdaterFilesValidatedHandler( object sender, UpdaterActionEventArgs e ) 95
{ 96
Debug.WriteLine(String.Format("FilesValidated successfully for application '{0}' ", e.ApplicationName)); 97
98
// ask user to use new app 99
DialogResult dialog = MessageBox.Show( 100
"Would you like to stop this application and open the new version?", "Open New Version?", MessageBoxButtons.YesNo ); 101
if( DialogResult.Yes == dialog ) 102
{ 103
StartNewVersion( e.ServerInformation ); 104
} 105
} 106
107
/**//// <summary> 108
/// Event handler for Updater event. This event is fired by the originating thread from "inside" the Updater. While it is 109
/// possible for this same thread to act on our UI, it is NOT a good thing to do--UI is not threadsafe. 110
/// Therefore here we marshal from the Eventing thread (belongs to Updater) to our window thread using the synchronous Invoke 111
/// mechanism. 112
/// </summary> 113
/// <param name="sender">event sender in this case ApplicationUpdaterManager</param> 114
/// <param name="e">the UpdaterActionEventArgs packaged by Updater, which gives us access to update information</param> 115
private void OnUpdaterFilesValidated( object sender, UpdaterActionEventArgs e ) 116
{ 117
// using the asynchronous "BeginInvoke". 118
// we don't need/want to block here 119
this.BeginInvoke( 120
new MarshalEventDelegate( this.OnUpdaterFilesValidatedHandler ), 121
new object[] { sender, e } ); 122
} 123
124
125
/**//// <summary> 126
/// This handler gets fired by the Windows UI thread that is the main STA thread for THIS FORM. It takes the same 127
/// arguments as the event handler below it--sender, e--and acts on them using the main thread NOT the eventing thread 128
/// </summary> 129
/// <param name="sender">marshalled reference to the original event's sender argument</param> 130
/// <param name="e">marshalled reference to the original event's args</param> 131
private void OnUpdaterUpdateAvailableHandler( object sender, UpdaterActionEventArgs e ) 132
{ 133
Debug.WriteLine("Thread: " + Thread.CurrentThread.GetHashCode().ToString()); 134
135
string message = String.Format( 136
"Update available: The new version on the server is {0} and current version is {1} would you like to upgrade?", 137
e.ServerInformation.AvailableVersion, 138
System.Configuration.ConfigurationSettings.AppSettings["version"] ) ; 139
140
// for update available we actually WANT to block the downloading thread so we can refuse an update 141
// and reset until next polling cycle; 142
// NOTE that we don't block the thread _in the UI_, we have it blocked at the marshalling dispatcher "OnUpdaterUpdateAvailable" 143
DialogResult dialog = MessageBox.Show( message, "Update Available", MessageBoxButtons.YesNo ); 144
145
if( DialogResult.No == dialog ) 146
{ 147
// if no, stop the updater for this app 148
_updater.StopUpdater( e.ApplicationName ); 149
Debug.WriteLine("Update Cancelled."); 150
} 151
else 152
{ 153
Debug.WriteLine("Update in progress."); 154
} 155
} 156
157
/**//// <summary> 158
/// Event handler for Updater event. This event is fired by the originating thread from "inside" the Updater. While it is 159
/// possible for this same thread to act on our UI, it is NOT a good thing to do--UI is not threadsafe. 160
/// Therefore here we marshal from the Eventing thread (belongs to Updater) to our window thread using the synchronous Invoke 161
/// mechanism. 162
/// </summary> 163
/// <param name="sender">event sender in this case ApplicationUpdaterManager</param> 164
/// <param name="e">the UpdaterActionEventArgs packaged by Updater, which gives us access to update information</param> 165
private void OnUpdaterUpdateAvailable( object sender, UpdaterActionEventArgs e ) 166
{ 167
// using the synchronous "Invoke". This marshals from the eventing thread--which comes from the Updater and should not 168
// be allowed to enter and "touch" the UI's window thread 169
// so we use Invoke which allows us to block the Updater thread at will while only allowing window thread to update UI 170
this.Invoke( 171
new MarshalEventDelegate( this.OnUpdaterUpdateAvailableHandler ), 172
new object[] { sender, e } ); 173
} 174
175
176
/**//// <summary> 177
/// This handler gets fired by the Windows UI thread that is the main STA thread for THIS FORM. It takes the same 178
/// arguments as the event handler below it--sender, e--and acts on them using the main thread NOT the eventing thread 179
/// </summary> 180
/// <param name="sender">marshalled reference to the original event's sender argument</param> 181
/// <param name="e">marshalled reference to the original event's args</param> 182
private void OnUpdaterDownloadCompletedHandler( object sender, UpdaterActionEventArgs e ) 183
{ 184
Debug.WriteLine("Download Completed."); 185
186
} 187
188
/**//// <summary> 189
/// Event handler for Updater event. This event is fired by the originating thread from "inside" the Updater. While it is 190
/// possible for this same thread to act on our UI, it is NOT a good thing to do--UI is not threadsafe. 191
/// Therefore here we marshal from the Eventing thread (belongs to Updater) to our window thread using the synchronous Invoke 192
/// mechanism. 193
/// </summary> 194
/// <param name="sender">event sender in this case ApplicationUpdaterManager</param> 195
/// <param name="e">the UpdaterActionEventArgs packaged by Updater, which gives us access to update information</param> 196
private void OnUpdaterDownloadCompleted( object sender, UpdaterActionEventArgs e ) 197
{ 198
// using the synchronous "Invoke". This marshals from the eventing thread--which comes from the Updater and should not 199
// be allowed to enter and "touch" the UI's window thread 200
// so we use Invoke which allows us to block the Updater thread at will while only allowing window thread to update UI 201
this.Invoke( 202
new MarshalEventDelegate( this.OnUpdaterDownloadCompletedHandler ), 203
new object[] { sender, e } ); 204
} 205
206
207
private void StartNewVersion( ServerApplicationInfo server ) 208
{ 209
XmlDocument doc = new XmlDocument(); 210
211
// load config file to get base dir 212
doc.Load( AppDomain.CurrentDomain.SetupInformation.ConfigurationFile ); 213
214
// get the base dir 215
string baseDir = doc.SelectSingleNode("configuration/appUpdater/UpdaterConfiguration/application/client/baseDir").InnerText; 216
string newDir = Path.Combine( baseDir, "AppStart.exe" ); 217
218
ProcessStartInfo process = new ProcessStartInfo( newDir ); 219
process.WorkingDirectory = Path.Combine( newDir , server.AvailableVersion ); 220
221
// launch new version (actually, launch AppStart.exe which HAS pointer to new version ) 222
Process.Start( process ); 223
224
// tell updater to stop 225
CurrentDomain_ProcessExit( null, null ); 226
// leave this app 227
Environment.Exit( 0 ); 228
} 229
230

转自:http://www.cnblogs.com/bigtall/archive/2004/12/09/74781.aspx


浙公网安备 33010602011771号