您的位置:首页 > 移动开发 > Objective-C

自动更新程序的制作方法

2005-08-15 11:48 393 查看
自动更新程序的制作方法[/b]
[/b]
[/b]
[/b]利用Microsoft的Application Blocks可以很简单地让程序可以自动更新。其基本原理是:利用Application Blocks软件包里面的一个.exe文件,(不妨命名为App.exe)作为长期运行的程序,这个程序不是真正的主体程序,而是会指向各个版本的程
4000
序,假如原来版本是1.0.0.0.exe,后来的版本是1.0.0.1.exe,那么运行App.exe时,它就先后指向这两个不同版本的exe。下面说说具体步骤:
1. [/b]安装[/b]Application Blocks
[/b]记下安装路径,因为要用到里面的工程文件和dll等
2. [/b]在项目中加入代码和引用:[/b]
[/b]把下列工程加入到你的工程所在的解决方案:
Microsoft.ApplicationBlocks.ApplicationUpdater
Microsoft.ApplicationBlocks.ApplicationUpdater.Interfaces
Microsoft.ApplicationBlocks.ExceptionManagement
Microsoft.ApplicationBlocks.ExceptionManagement.Interfaces
如果你选择默认安装的话,它们的位置可能是: C:/Program Files/Microsoft ApplicationBlocksfor .NET/Updater/Code/CS/Microsoft.ApplicationBlocks.Updater
(建议:把它们拷贝过来,放在同一个文件夹中,否则的话换了一台机器,或者改了路径的话就找不回这些工程了)
在你的工程中(这里为 YourAppName)引用下列工程
Microsoft.ApplicationBlocks.ApplicationUpdater
Microsoft.ApplicationBlocks.ApplicationUpdater.Interfaces
Microsoft.ApplicationBlocks.ExceptionManagement
Microsoft.ApplicationBlocks.ExceptionManagement.Interfaces
把下列命名空间加入到你Form的.cs文件中
using System.Runtime.InteropServices;
using System.Runtime.Serialization;
using System.Threading;
using System.Diagnostics;
using System.IO;
using System.Xml;

特别提出关于引用的问题:
在项目YourAppName中(自己原先写代码的那个项目)要引用下列文件:
Microsoft.ApplicationBlocks.ApplicationUpdater.dll
Microsoft.ApplicationBlocks.ApplicationUpdater.Interfaces .dll
Microsoft.ApplicationBlocks.ExceptionManagement.dll
Microsoft.ApplicationBlocks.ExceptionManagement.Interfaces .dll

3. [/b]把附录中的代码添加到你原来就有的的工程文件中去,这段代码是处理更新操作所用到的。在需要更新的时候调用函数[/b]InitializeAutoUpdate()[/b]即可。[/b]
[/b]
[/b]4. [/b] [/b]生成你应用程序的发布目录结构并配置[/b] AppStart.exe
[/b]生成一个用于客户端程序安装的目录. 本例子中,我们用如下的目录:
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“
[/b]5.[/b] 修改[/b] AppStart.exe.config [/b]文件[/b]
[/b]
[/b]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>

[/b]appFolderName 存放当前版本的exe所在的文件夹
appExeName是运行的exe的文件名
installedVersion是当前版本号
lastUpdated是最新更新的时间
更新成功以后这几个项都会自动更改

[/b]6.[/b] 生成你的公钥和私钥[/b]
[/b]运行
"C:/ProgramFiles/MicrosoftApplicationBlocksfor .NET/Updater/Code/CS/Microsoft.ApplicationBlocks.Updater/ManifestUtility/bin/Debug/ManifestUtility.exe" 这个exe可能在软件包中会不存在,但是仍然可以在上述路径找到它的源文件,用C#.net编译运行即可。选择 “File..Generate Keys” 会提示你是否需要保存: PublicKey.xml 和 PrivateKey.xml 这两个密钥接下来就会用到.
这些密钥只要生成一次就可以了, 因为下面几个地方需要引用到RSA公钥和私钥. 你需要把这些密钥存放在一个安全的地方,因为在发布一个新的更新的时候会用到它。

[/b]7.[/b] 创建[/b]IIS [/b]虚拟目录[/b]
[/b]
[/b]在你的Web服务器上生成一个目录来存放你的更新文件. 在这两个目录中要放两样东西 1) ServerManifest.xml 文件,包含最后版本的一些信息;2) 你的新程序的目录. 在这个目录里,生成一个目录来存放你的新版本程序. 在我们的例子中,我们用这两个目录, C:/Inetpub/AppUpdates 和C:/Inetpub/AppUpdates/1.0.0.1
用 IIS 管理器生成一个虚拟目录指向刚才的实际目录. 记下你的 URL, 在上传步骤中我们需要用到它. 你必须要打开虚拟目录的“目录浏览”选项.
ServerManifest.xml的生成要参考第11步,注意:这是最容易出错的地方了!
[/b]
[/b]8. [/b]配置你的版本[/b] 1.0.0.0 [/b]的[/b]App.config [/b]文件[/b]
[/b]
[/b]这里,我们会需要往里添加一些新东西[/b]. [/b]首先[/b], [/b]我们需要加入一个[/b]configSections [/b]元素来定义我们的[/b] appUpdater [/b]节[/b]:
[/b]
[/b]<configSections> <section name="appUpdater" type="Microsoft.ApplicationBlocks.ApplicationUpdater.UpdaterSectionHandler,Microsoft.ApplicationBlocks.ApplicationUpdater" /> </configSections>
[/b]接下来,我们需要添加一个[/b] Version [/b]键到我们的[/b] appsettings [/b]中[/b], [/b]我们首先设置我们的本地版本为[/b] 1.0.0.0, [/b]这样我们就可以测试自动更新到版本[/b] 1.0.0.1
[/b]
[/b]<appSettings> <add key="VERSION" value="1.0.0.0" /> </appSettings>
[/b]最后,[/b], [/b]加入[/b] appUpdater [/b]节到你的配置文件中[/b]. [/b]我这里用一对方括号把你要修改的值包含起来[/b]. [/b]你可以直接从你上一步生成的[/b] PublicKey.xml[/b]文件中复制[/b] <RSAKeyValue> [/b]元素[/b].
[/b]
[/b]<xmlFile> [/b]元素必须要指向你在[/b]Step #6[/b]创建的虚拟目录的[/b] URL .
[/b]
<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>
[/b]需要修改的地方只有[/b][ ][/b]括住的地方,[/b]
[/b]<Modulus>[YOUR MODULUS KEY]</Modulus> 和<Exponent>[YOUR EXPONENET]</Exponent> 可以在PublicKey.xml中找到
[YOUR URL ]就是ServerManifest.xml的URL地址<xmlFileDest>C:/Program Files/YourApp/ServerManifest.xml</xmlFileDest>中的路径就是更新成功后生成的ServerManifest.xml的路径
[/b]
[/b]9.[/b] 发布版本[/b] 1.0.0.0
[/b]
[/b]设置应用程序版本号. 可以通过设置在 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/ 目录中.

[/b]10. [/b]构建版本[/b] 1.0.0.1
[/b]
[/b]首先, 通过更新应用程序的 AssemblyInfo.cs 和 App.config 文件内容来生成修订版本 1.0.0.1 . 编译程序, 然后复制文件到step 7生成的Web服务器目录中.
这里需要改动的地方很少。只要在AssemblyInfo.cs中修改[assembly: AssemblyVersion("1.0.0.1")]
在App.config中修改<add key="VERSION" value="1.0.0.1" />

[/b]
[/b]11. [/b]生成服务器的清单文件[/b]
[/b]
[/b]这个是最后一步. 如果你对本步骤中的.config文件作了任何修改的话,都必须把本步骤重来一遍. 做法如下:

再次运行 ManifestUtility 程序.
在 “Update files folder“ 选择器中选择 1.0.0.1 目录 .
输入更新位置的 URL . (这个是最容易出错的地方,不要漏掉/1.0.0.1)
输入新版本号 1.0.0.1
打开之前生成的 PrivateKey.xml 文件.
选择验证类 “Microsoft.ApplicationBlocks.ApplicationUpdater.Validators.RSAValidator”
鼠标点击 CreateManifest, 并保存 ServerManifest.xml 文件到你的虚拟服务器目录中. 它默认的文件名是Manifest.xml,要把它改过来的。
从你的 C:/Program Files/YourApp/ 目录中运行你的 AppStart.exe . 你的程序就会被装入, 当你的程序运行的时候,你就会得到一个提示 “新版本可用” . 新版本会下载到目录 C:/Program Files/YourApp/1.0.0.1 中, 然后程序会自动重启. 如果有任何问题, 记得检查一下日志文件. 这些日志在诊断问题的时候会很有用的.

[/b]细节方面:[/b]
[/b]1. [/b]除了使用[/b]BITS[/b]([/b]background intellectual transmission service [/b])方式传送之外还可以使用[/b]FTP[/b]方式传送。[/b]
[/b]2. [/b]假如在服务器端只需要放要更新的控件和可执行文件([/b]*.exe[/b]),配置文件([/b]*.exe.config[/b])就行了。不需要把所有的文件都放进去,如果成功执行的话,程序会先把原先版本的所有文件复制到新版本的文件夹里面,并且会自动更新新的控件。[/b]
[/b]
[/b]
[/b]
[/b]
[/b]
[/b]
[/b]
[/b]附录:[/b]
[/b]
[/b] 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()
{
try
{
// 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();
}
catch (Exception ex)
{MessageBox.Show(ex.Message);}

// 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));

//hzh
GetPath(e.ServerInformation);
// 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 );
DialogResult dialog = MessageBox.Show(
"你想立即停用当前应用程序并运行新版本程序吗?", "运行新版本程序?", 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"] ) ;

string message = String.Format(
"升级提示:服务器上最新的版本是 {0} ,你当前使用的版本是 {1} 。你希望进行升级吗?",
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 );
DialogResult dialog = MessageBox.Show( message, "升级提示", 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 )
{
// GetPath(e.ServerInformation);
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" );
MessageBox.Show(newDir);
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 );
}

//hzh
private static void CopyDirRecurse( string sourcePath, string destinationPath )
{

// get dir info which may be file or dir info object
DirectoryInfo dirInfo = new DirectoryInfo( sourcePath );

foreach( FileSystemInfo fsi in dirInfo.GetFileSystemInfos() )
{
if ( fsi is FileInfo )
{
// if file object just copy
if (File.Exists(destinationPath +@"/"+ fsi.Name)==false)
{
File.Copy( fsi.FullName,destinationPath+@"/" + fsi.Name,false);
}
}
else
{
// must be a directory, create destination sub-folder and recurse to copy files
if (Directory.Exists(destinationPath +@"/"+ fsi.Name)==false)
{
Directory.CreateDirectory( destinationPath +@"/"+ fsi.Name );
}
CopyDirRecurse( fsi.FullName, destinationPath+@"/" + fsi.Name );
}
}
}

//hzh
private void GetPath( 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 sourcePath=baseDir+@"/"+System.Configuration.ConfigurationSettings.AppSettings["version"] ;
string destinationPath= baseDir+@"/"+server.AvailableVersion;
// 将旧版本的文件复制到新版本文件夹里
CopyDirRecurse(sourcePath,destinationPath);
}
[/b]
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息