CSDN博客

img tmfc

.net自动更新

发表于2004/9/14 12:34:00  855人阅读

Step 1:  Build the application to update

In this step we will build the application to auto-update.  If you want, you can substitute in your own application here.  You can also use the pre-built sample application included in the Samples/SampleApp/SampleApp directory of the zip file.  However for the purpose of proving that there isn’t anything special about SampleApp, we’ll walk through its creation.

 

1.        Use Visual Studio .NET to create a new Windows Application project, name it “SampleApp”.  

2.        Give the form an interesting background color of your choice.  We will be using background color to differentiate between versions later.

3.        Now let’s add a tiny bit of functionality to this application in the form of a button that opens a form residing in a separate assembly.  First add a button to your form.   The zip file contains an assembly with a simple Windows Form in it.  Add a reference to the Samples/SampleApp/SimpleForm assembly in the zip.  Then add two lines of code to your button event handler:

 

       SimpleForm.Form1 F = new SimpleForm.Form1();

       F.Show();

 

4.        Switch your build flag to build RELEASE instead of debug.  This will allow us to avoid pdb file locking problems later when we build a new version of the application while the original copy is still running. 

5.        Build and test your application.  It should look similar to the Samples/SampleApp/SampleApp in the zip file.

 

Step 2:  Add the .NET Application Updater Component

In this step we will add the .NET Application Updater component to SampleApp.

 

1.        In the components tab of the Visual Studio .NET toolbox, right click and select ‘Customize Toolbox’. Select the ‘.NET Framework Components’ tab.  Browse and select the AppUpdater.dll in the AppUpdater project included in the zip.  Click OK.

2.        An AppUpdater icon should now show up at the bottom of the list of components in the toolbox.  Drag and drop the AppUpdater component onto the SampleApp Form.  An appUpdater1 instance of the .NET Application Updater component should be instantiated and appear below the form.

 

Step 3:  Configure the .NET Application Updater Component

In this step we will configure the .NET Application Updater component.  To do this, select the appUpdater1 component and open its properties.  The following section contains a description of each property and what value to set it to.  Note that you only need to change the first four properties for this example, for the rest, the defaults are adequate.

 

AppUpdater Properties – These are the core properties of the .NET Application Updater and will need to be set for this application as follows:

 

Property Name

Description

AutoFileLoad

 

This controls the on-demand download feature described later, for now set this to true.

ChangeDetectionMode

 

This enum determines how to check for updates.  In this example, we will use a server manifest check, so set this value to “ServerManifestCheck”.

ShowDefaultUI

 

The .NET Application Updater component has a set of simple UI for notifying the user of events such as a new update becoming available or errors during updates.  This UI can be replaced with custom application specific UI by disabling the default UI, hooking the appropriate events (ex. OnUpdateComplete) and popping up the custom UI.  For this example we will use the default UI, so set this value to true.

UpdateUrl

 

The UpdateUrl is what determines where the updater looks for updates.  In this case we are using a server manifest to check for updates, so this property should be set to the URL of the server manifest.  For this example, set it to http://yourWebserver/SampleApp_ServerSetup/UpdateVersion.xml.  Replace ‘yourWebserver’ with the name of your Web server. 


Downloader Properties – The AppUpdater component has two sub-components.  The first is called the Downloader and controls the download and installation of the component.  Below is a description of the properties, but all defaults will work fine for our SampleApp.

 

Property Name

Description

DownloadRetryAttempts

 

If a failure occurs during download (ex the Web server goes down) the downloader will try again a little later.  This property controls the number of times the downloader will retry the network request before treating it as a complete application update failure.

SecondsBeteweenDownloadRety

 

The number of seconds before retrying the network request.

UpdateRetryAttempts

 

If a serious error occurs during the update process, (ex. The downloader has exceeded the DownloadRetryAttempts), an application update error is generated.  By default, the update attempt will stop, but will attempt to resume the next time the application is started (ex… maybe the update Web server was just down for day).  This property controls how many times an update will be attempted.  If this value is exceeded, the updater aborts the update, resets its state and goes back to checking for updates.   

ValidateAssemblies

 

This controls the level of validation done on downloaded assemblies.  See the security section of this paper for more info.

 

Poller Properties – The second sub-component of the AppUpdater is the Poller.  The Poller controls the update checks.  Below is a description of the properties, but all defaults will work fine for our SampleApp.

 

Property Name

Description

AutoStart

 

A Boolean that controls whether or not the Poller should begin polling for updates on application startup or whether it should wait until it is explicitly started programmatically.

DownloadOnDetection

A Boolean that controls whether or not the Poller starts  a download of an update immediately after a new update is found, or whether the download must be started explicitly by a call to the DownloadUdpate() method.

InitialPollInterval

 

The number of seconds after application startup before the first update check is performed.

PollInterval

 

After the first update check, the PollInterval controls the number of seconds between each subsequent update check.  Note:  By default this checks every 30 seconds; clearly you will want to reduce the frequency for your application.

 

When all is said and done, your property grid should look the following:

 

 

 

The Samples/SampleApp/SampleApp_Complete directory contains a version of the application correctly setup.

 

Step 4:  Build & Deploy V1 of the application to the client

In this step we will build the V1 version of the application and deploy it to the client.  The deployment is essentially simulating what the install program for your application will do. 

 

1.        In the SampleApp project, open the AssemblyInfo.cs file.  Change the AssemblyVersion value from "1.0.*" to “1.0.0.0”.  This will cause the built assembly to get marked with a value of “1.0.0.0” instead of the ever increasing value Visual Studio normally assigns.

2.        Build the application.

3.        Copy the Samples/SampleApp/SampleApp_ClientSetup directory from the zip onto your local machine.  It doesn’t matter where you copy it, however the program files directory is the most realistic place to put it since that is where most applications get installed.  You’ll notice that SampleApp_ClientSetup directory already has AppStart.exe included.  AppStart.config is already set to point into the 1.0.0.0 directory and run SampleApp.exe.

4.        Copy the complete SampleApp (Appupdater.dll, SimpleForm.dll & SampleApp.exe) from the release build directory of SampleApp to the SampleApp_ClientSetup/1.0.0.0 directory on your client. 

 

At this point a fully functional version of the application should be “installed” on the client and executable by running AppStart.exe

 

Step 5: Setup the Web server

In this step we will setup the Web server for use in rolling out application updates.  The .NET Application Updater component uses HTTP-DAV to download the application update and thus requires a Web server that supports HTTP-DAV.  IIS 5.0 that comes with Windows 2000 and newer operating systems support HTTP-DAV.

 

1.        Copy the Samples/SampleApp_ServerSetup directory from the zip into the wwwroot directory of your Web server.  Note:  Any location on your Web server can be used, but be sure to update the UpdateUrl property accordingly.  Notice that UpdateVersion.xml is already setup for use as the server manifest file.

2.        For completeness, copy the V1 version of SampleApp into the 1.0.0.0 folder of the Web server.

3.        Enable IIS “Directory Browsing” for the SampleApp_ServerSetup directory on your Web server.  Since the .NET Application Updater component enumerates the contents of directories during download, “Directory Browsing” must be enabled.  To do this, open the “Internet Information Services” manager in the control panel.  Select the SampleApp_ServerSetup folder and open its properties.  Select the Directory Tab.  Check “Directory Browsing,” and click OK. 

 

Step 6:  Automatically update the application

OK, now it’s time to see the results of all this hard work by automatically rolling out a new version. 

 

1.        If the SampleApp version you deployed to the client isn’t running, launch it and leave it running.  Remember to use AppStart.exe. 

2.        Go back to Visual Studio and change something noticeable on the SampleApp form (ex… change the background color).

3.        Change the VersionInfo in AssemblyInfo.cs to be 2.0.0.0.

4.        Rebuild.

5.        Go to the Web server and create a 2.0.0.0 directory as a peer to the 1.0.0.0 directory.  Copy the new version of the application from the release build directory into the new 2.0.0.0 directory on the Web server.

6.        Open UpdateVersion.xml and change AvailableVersion to be 2.0.0.0.  Change the ApplicationURL to point at the new 2.0.0.0 directory.

7.        Save the changes to UpdateVersion.xml. 

 

As soon as you save the new UpdateVersion.xml, within 30 seconds the running copy of SampleApp should detect the newly available version.  SampleApp will then download the new version, apply the update, and pop up the default UI asking the user if they want to restart and start using the new version immediately.  Click “Yes” in response to this dialog.  SampleApp should restart and be running the new version.  If you look at the client deployment of SampleApp you will notice there is now a 2.0.0.0 directory next to the original 1.0.0.0 directory.   The 1.0.0.0 directory will be cleaned up the next time an update occurs.

 

On-Demand Install

By exploiting the extensible nature of the .NET Framework, the .NET Application Updater component is able to enable another feature, On-Demand installation.  By enabling On-Demand installation, only the main exe needs to be explicitly installed on the client.  The remainder of the application can be automatically downloaded and installed on an as needed basis.

 

You can see this feature in action with the SampleApp you just finished building and deploying.   In SampleApp we enabled the On-Demand install feature by setting the AutoFileLoad property to true.  First close SampleApp if it is still running.  Next, in the 2.0.0.0 folder of the SampleApp client deployment, delete SimpleForm.dll.  You’ll recall that SimpleForm.dll is the assembly that contains the form that is displayed when the button on SampleApp is clicked.  With SimpleForm.dll deleted, you would expect the application to throw an exception when the button is clicked.  Go ahead and try it by running SampleApp (remember to use AppStart.exe) and click the button in SampleApp.   

 

What happened?  It worked.  In fact if you look in the 2.0.0.0 folder you’ll see that SimpleForm.dll has reappeared.  So how did this happen?  The CLR is fundamentally programmable and extensible.  When the CLR could not find the SimpleForm.dll assembly, instead of simply failing, it raised the AppDomain.AssemblyResolve event.  The .NET Application Updater component hooks that event.  The .NET Application Updater component knows where a valid copy of SimpleForm.dll resides; on the deployment Web server.  It then downloads the assembly to the local application directory and tells the CLR to try to load the assembly again.  The second load attempt then succeeds. 

 

On-Demand installation is enabled and disabled via the AutoFileLoad property on the .NET Application Updater component.  It is a powerful feature that can make the initial download and install of your application tiny, but can be difficult to use correctly.  You must think hard about where the assembly boundaries reside in your application and what actions will cause an assembly to be downloaded.  Because the assembly download involves network I/O, it will take variable lengths of time to download.  During the assembly download, the application is frozen waiting for the assembly download to complete.

 

Deployment Security

While the ability to roll out application updates automatically has great benefits, it also comes with an inherent danger.  When you make it easier to roll out updates, if not careful, you could also make it easier to roll out malicious code.  There are two dangers.  The first danger is that someone will spoof the Web server used to deploy updates with their own Web server.  They could then use that Web server to roll out a virus in place of your application.   The easiest way to prevent spoofing or any other tampering over the wire is to use HTTPS.  To use HTTPS with the .NET Application Updater component, simply use HTTPS URLs in place of HTTP URLs. 

 

However, HTTPS is not a silver bullet.  There are two problems with HTTPS. The first is scalability.  Using HTTPS requires the server to encrypt all of the files that are downloaded from the Web server.  If an application update is large, the cost of encrypting the update can overburden the server.   The other issue with HTTPS is that it does nothing to address the second security danger.   The second danger has to do with your Web server being compromised, either from an insider or from an outsider hacking your server.  If your Web server is compromised, it’s bad enough, but if it also means that a thousand clients are also compromised via automatic updates, it could be disastrous.

 

To solve this problem, the .NET Application Updater component uses the ability to strong name a .NET assembly to verify the authenticity of the assembly upon download.  If the .NET Application Updater detects that an assembly is not signed with your application’s key during download, the update is aborted.  This means that only someone that has the private key of your application can build updates that can be automatically deployed.  .NET security is based on keeping your private key secret.  Even inside your own organization, only the few select people should have access to your private key.

 

To validate assemblies, the .NET Application Updater component verifies that the public key on the exe of your current installed application matches the public key on downloaded updates.  The embedded public key can only be the same if the two assemblies were signed with the same secret private key.  Because the assembly is loaded by the CLR in order to verify its public key, the CLR does its normal hash value check to ensure the assembly is in fact a real assembly and has not been tampered with.  If a verification check fails, it is treated just like any other update failure.  To enable verification on download, simply strong name all of your application assemblies and set the ValidateAssemblies property on the .NET Application Updater component to True.

 

Assembly validation on download works great, but in practice, applications will often have components signed with different private keys.  For example, your application may have two files:  The exe assembly signed with your private key and a dll assembly that contains a 3rd party charting control you purchased to use in your application.  This 3rd party assembly may be signed with the 3rd party’s private key and not yours.  What makes it even more complicated is that the set of valid private keys that can be used to sign assemblies in your application could change from version to version.  How do you auto-update these types of applications?   To solve this problem, you create an assembly that contains a list of valid public keys for your application.  Sign this assembly with the application’s main private key (the key that the exe of the application is signed with) and place this assembly in the directory with the application update on the Web server.  Before the update download process begins, the .NET Application Updater will check for a well known assembly “AppUpdaterKeys.dll” in the application update directory on the server.  If present, the assembly will be downloaded.  The assembly will be verified against the main application public key.  If the signature is valid, the list of keys will be extracted.  From that point on, any of the keys in that list will be treated as valid signatures for the files in the update.  The zip file that comes with this paper contains an AppUpdaterKeys project that can be used to build your own AppUpdaterKeys assembly.  The AppUpdaterKeys.dll also allows you to specify a set of file exceptions.  Files specified as exceptions can be downloaded and updated without passing the validation check.  This can be useful when your application contains non-assembly files, such as pdb files for debugging.

 

The recommended approach to security is to use HTTPS URLs to perform update checks.  This provides a first level of spoofing protection.  For update download, it’s best not to use HTTPS URLs to avoid the overhead on your Web server.  Instead, strong name your application assemblies and enable the Validate Assemblies feature.

 

Extensibility and Terrarium

In the sample we walked through earlier in this paper we enabled auto-deployment in an application simply by dropping a component into an application and setting some properties.  While this works well for many applications, some applications require a higher degree of control that can only be achieved by writing code.  The .NET Application Updater component has an API that can be used to customize and replace application update behavior.  The .NET Terrarium game (http://www.gotdotnet.com/terrarium/) I mentioned earlier is a heavy user of these extensibility hooks.   Let’s take a look at how Terrarium uses the .NET Application Updater component’s extensibility mechanisms. 

 

Terrarium uses a custom XML Web service to check for updates.  Terrarium hooks the OnCheckForUpdate event of the .NET Application Updater component and calls the XML Web service within this event handler.  OnCheckForUpdate fires on the poller thread.  Thus the XML Web service request is performed on a separate thread from the UI thread and does not lock the UI.  The call to the XML Web service passes the assembly version of the client’s current Terrarium install.   The XML Web service then decides whether or not a newer update is available for that client.  Two values are returned.   The first indicates whether or not an update is available.  The second is the URL where the update resides.  For scalability reasons, in practice, one server is used to check for updates and a separate server is used to download updates.   On the return from the XML Web service call, if a new update is available, Terrarium sets the UpdateUrl property of the .NET Application Updater component to the URL returned from the Web service.   This instructs the .NET Application Updater to use that URL for the next update download.  Finally, Terrarium returns true in the OnCheckForUpdate event handler if an update is available.

 

Terrarium also uses the custom XML Web service to increase scalability.  Updates to Terrarium exceed 10 MB in size.  Terrarium also checks for updates very frequently.  Thus, in the beginning, when a new update was rolled out, it wasn’t uncommon to have hundreds of clients asking for updates at once.  This resulted in Gigabytes worth of download requests and the server couldn’t handle the load.  Moving away from using HTTPS and decreasing the frequency of update checks helped the situation, but the ultimate solution was to build some intelligence into the XML Web service.  When the server is under load, the XML Web service only tells the first X number of clients that an update is available.  All of the rest are told that no new update is available.  The next time the clients check for updates, the next X set of clients are told an update is available.  In this way, the update is rolled out in waves and the server is not overloaded.     

 

Terrarium puts up its own custom UI that integrates better with the overall application.  Thus Terrarium disables the default .NET Application Updater UI.  Terrarium hooks the OnUpdateComplete event.   The OnUpdateComplete event includes a set of information, such as whether or not the update was successful, what, if anything, caused failure, and an English string with an explanation of the problem that can be presented to the user.  The OnUpdateComplete event fires on the main UI thread of the application.  Thus Terrarium raises UI to the user, informing them of update success or failure, directly in the OnUpdateComplete event handler. 

 

The .NET Application Updater component also includes additional APIs that allow explicit control over when actions like update checks and updates are performed.  These actions are controllable via the CheckForUpdate () and ApplyUpdate() methods respectively. 

 

Debugging

The source code for the .NET Application Updater component is provided so that you can debug problems and add functionality as needed.  However, before you dive too far into the source code, this section will spell out some first level debugging options, as well as describe the most frequently encountered problems by consumers of this component. 

 

The .NET Application Updater creates a hidden log file named AppUpdate.log that resides in the same directory as AppStart.exe.  All successful and failed updates are recorded in this log.  The log file is especially useful when there is a particular client machine that won’t update successfully.  You can use the log to determine when and how the update failed.  In addition, the .NET Application Updater component uses the Debug class of the .NET Framework to write out a lot of useful information.  If you run your application in the debugger, you will see this information in the output window.  You can essentially watch the .NET Application Updater go through its update steps and see where the problem is occurring.

 

If for whatever reason, you just can’t get the .NET Application Updater to work, make sure of the following before you debug too far, more than likely one of these is the problem you are encountering:

 

  • Do you have IIS Directory Browsing turned on?  If not, the downloader will see an empty directory on the Web server where the update resides.  The updater will then download and install an update of zero files, leaving you with the original unchanged application. See the “Setting up the Web server” section earlier in this paper for instructions on how to enable Directory Browsing.
  • Do you have everything deployed correctly and the correct URLs set?  It’s easy to make a mistake here.  Look at the URLs being output by the .NET Application Updater when running in the debugger.  Navigate to these URLs in the browser and make sure they are valid.
  • If your app is installed in the program files directory, are you an admin or power user on the box?  If not, you won’t have the write access necessary to update the application.
  • Did you create the AppUpdater object on the main UI thread of the application?  If not, the updater will not be able to present UI and will fail when firing events back to the UI thread.
  • Is the update succeeding, but the app failing to automatically restart to pick up new updates?  The .NET Application Updater component attempts to restart the application by calling the Application.Exit method.  However, that method is not guaranteed to shut down an application.  If you spawn & leave separate threads running, it will not close the process.  The solution is to ensure that all threads are terminated via the Application.OnExit event, or hook the .NET Application Updater’s OnUpdateComplete event and handle the shutdown yourself.

 

Summary

Ease of deployment for client applications was a major goal for the first version of the .NET Framework.   There is no other technology for building client applications that addresses the deployment problems as well as the .NET Framework.  Ease of deployment will continue to be a major goal for future versions of the .NET Framework.  The .NET Application Updater component described here represents some of our thinking in terms of scenarios we would like to enable directly in future versions of the .NET Framework.  However, until that time the .NET Application Updater component is a great way to get started building auto-updating applications.

0 0

相关博文

我的热门文章

img
取 消
img