Keep your Apps Fresh with the MS Updater Application Block

ince the late ’90s there has been a significant move away from desktop applications in favor of Web-based thin clients. Part of the reason was the Internet boom; Web technologies suddenly became popular, and architects discovered that the internet model allowed them to cut deployment costs. The lure of centralized management and applications accessible from any Internet-connected computer proved overwhelming. The majority of new projects were planned as Web applications, and many existing thick client applications were converted to Web technologies, with varying degrees of success. The terms “fat” client and “bloatware” were coined to describe desktop apps, not only to distinguish them from thin clients, but also to draw attention to their unwieldy nature; desktop apps often consist of a lot of files, hog precious disk space, and require a large amount of local processing power and resources to run.

The general consensus for the last few years has been that desktop apps just can’t be deployed quickly and easily?they require developers to create a setup program and test the application on a variety of platforms. Worse, deployment problems don’t stop with the initial installation; any updates to the application require yet another installation process. Users don’t take kindly to having to reinstall applications repeatedly to patch bugs, so developers had to make sure that applications were tested thoroughly before deployment, further raising costs. This is in stark contrast to thin clients?users just point their browser at your site, and they have the latest version of your code.

Despite the deployment benefits, thin clients offer a poor user experience in comparison to desktop apps. The request/response model of HTTP is too slow and disjointed for anything more complex than the simplest of applications. The lowest common denominator UI toolbox?those controls available on every browser?consists of only basic controls. Development of richer tools requires mastery of several very different technologies, such as DHTML, CSS, client-side scripting, and Flash, each of which, despite standards efforts, is at least somewhat dependent on the client’s choice of OS, browser manufacturer, and even the browser version. Even after mastering these challenges, Web developers must still contend with the stateless model and the performance and logic problems caused by the separation of client-side and server-side code. Many thick client conversion projects went awry because the thin client model is simply not suitable for data-intensive applications where performance and a feature-rich UI are essential. The fact is that thin clients just cannot replace thick clients completely.

So, building thin client applications did provide some real advantages, but they also carry intrinsic complexities that hinder development and limit application interactivity. Fortunately, thick client technologies haven’t rested on their laurels. For the last few years, thin clients have been king, but now desktop applications are making a comeback, with improved connectivity and deployment capabilities, under the new moniker “smart client”.

The New Smart Clients
The new generation of desktop applications offers all the standard rich client features of traditional Win32 apps, combined with the ease of deployment of thin clients, and a new “offline” model that thin clients are unable to provide. The smart client will come into its own on Microsoft’s new operating system?Longhorn will blur the distinction between thick and thin clients?but fortunately, you don’t need to wait until Longhorn ships to develop self-updating Windows Forms applications; Microsoft’s Updater Application Block, which is provided for free and supplied with source code, allows you to build the new deployment model directly into your applications today. In fact, the pattern and approach taken by the Application Block’s developers has largely been incorporated into the new ClickOnce deployment model of Visual Studio 2005 and Longhorn, so an understanding of how the application block works will give you an early insight into how application development and deployment will work in the future.

Introducing the Updater Application Block
The Updater block lets you deploy updates to a central deployment server and have all of your .NET Windows Forms applications automatically check for and download updated files. For each update, you deploy all the new version’s files to a folder on the deployment server, which contains a configuration file known as the server manifest. On the client, a small application, AppStart.exe, serves as a controller that launches an instance of your application, loads information about the installed version from a client-side manifest, and periodically polls the server for the availability of updates. This entire process is transparent to users, who don’t have to do anything special to receive or install application updates. When AppStart.exe discovers an update, it downloads the updated files on a background thread, minimizing disruption to the user.

The Resources section provides a link to the Updater block’s documentation on MSDN. The documentation provides a comprehensive overview of how the block works, and the basics of how to implement it. Although the documentation is good, there’s a lot of it, and the block can be tricky to configure and get working. There are also one or two issues with the way the block has been designed which you’ll need to resolve. Fortunately, the block comes complete with source code, so you’ll see how to customize AppStart.exe to provide the features you need for your own applications.

Initial Setup
To begin, download the Updater block installer and run it to install the documentation and source code. Note that the installer installs both VB.NET source and C# source even though this article uses only C# for the examples. You will need to integrate the Updater Block source code with your own Windows Forms solution, so take a copy of the source from the following location :

    C:Program FilesMicrosoft Application Blocks for       .NETUpdaterCodeCS      Microsoft.ApplicationBlocks.Updater

This folder contains the source code directories listed in Table 1, which you should copy into your application’s solution folder:

Table 1. Copy the directories shown in the Folder column into your solution’s folder. The Notes column shows a description of each folder’s contents.

Folder Notes
AppStart Contains code for the AppStart assembly, which is the host process that will run your application and manage polling of the update server and download of files. References the ApplicationUpdater assembly.
Microsoft.ApplicationBlocks.ApplicationUpdater The main Updater assembly.
Microsoft.ApplicationBlocks.ApplicationUpdater.Interfaces Referenced by the Updater assembly, and used to provide the base interfaces for creating custom downloaders, post-processors, and validators to extend the application block.
Microsoft.ApplicationBlocks.ExceptionManagement & Microsoft.ApplicationBlocks.ExceptionManagement.Interfaces The Exception Management application block assemblies, which are referenced by AppStart and the Updater assembly.

After copying the source code into your solution folder, open up your solution in Visual Studio.NET, and add the new projects to your solution; you should also ensure that the references between the projects are set up as described in Table 1.

It’s All About AppStart.exe
As mentioned earlier, the Updater block comes with an application called AppStart.exe which serves as a host process that loads configuration data, launches your application, and invokes the Updater block code to do its work. This means that, instead of launching your application directly, you need the user to launch AppStart, which in turn will locate and load your application’s .exe file using configuration data from the AppStart assembly’s app.config file.

Figure 1. Updater Directory Structure: You must install your application with a specific directory structure for the Application Update Block to work properly.

This might seem a little strange at first, but it’s necessary so that polling and downloads can run in a process separate from that of your application, letting users continue using your application without being interrupted.

For this to work, you need to ensure that your application has a very specific directory structure. You install AppStart and the Updater assemblies in the root of your application’s folder hierarchy (see Figure 1). You install your application’s files in a subfolder; you can give the subfolder any name you like, but note that Microsoft recommends that you give it the same name as the initial version of your application (see Figure 2).The reason for this will become clear shortly.

For the update process to work, any shortcuts to your application must point to AppStart.exe, and not directly to your application. The best way to ensure this is to create an application installer that builds the correct directory structure and creates the relevant shortcuts.

Figure 2. Application Directory Structure: During the installation, place your application’s files in a directory named according to the current version of your application.

Creating an Application Installer
You need to create an installer that will reproduce the correct file structure and shortcuts on your users’ machines. To do this:

  1. Add a Setup project to your solution;
  2. Select the new project in the Solution Explorer, and select View | File System;
  3. Add the Primary Output and Content Files from AppStart to the Application Folder for the target machine. Because AppStart references the Updater and Exception Management block assemblies, the installer will pull these into the folder as well (see Figure 3).
Figure 3. Setup Project: Add the Primary Output and Content Files from AppStart to the Application Folder for the target machine.

You then need to create the subfolder for your application files:

  1. Right-click the Application Folder and select Add | Folder;
  2. Change the new folder’s name to “”;
  3. Select the folder and choose Add | Project Output from the context menu;
  4. Select your Windows Forms application project, and ensure that Primary Output is selected (see Figure 4).
Figure 4. Installation Application Folder: In the install project, create a folder named the same as your application’s version number, and add the Primary Output from your project to that folder.

The installer will create a “Company NameProduct Name” directory structure under your user’s Program Files directory. To ensure that the installer creates correctly named folders:

  1. Select the setup project in the Solution Explorer;
  2. Change the Author property to the name of your organization;
  3. Change the Product Name from its default setting (the name of the setup project) to the name of your application.

After following these steps, you need to create the shortcuts that your user will use to launch AppStart:

  1. Select the Primary output for your Windows Forms application;
  2. Choose Create Shortcut from the context menu;
  3. Rename the shortcut to something appropriate, and drag it into the User’s Desktop and User’s Programs Menu folders (if these aren’t visible, then add them by choosing the appropriate folders from the context menu). Figure 5 shows how your Setup application should look.
Figure 5. Creating Shortcuts: Rename your shortcut appropriately and drag it into the User’s Desktop and User’s Programs Menu folders in the Setup project.

Build and test the installer by selecting “Install” from the installer project. If you double-click one of the shortcuts created during the installation, you’ll find that not much happens; if you are very lucky, after a short interval your application will load. Otherwise, you’ll just get an exception indicating that AppStart has not been configured correctly.

Configuring AppStart
Because AppStart is responsible for launching the latest version of your application, and polling the deployment server for updates, it requires a hefty set of configuration data before it will work. This is possibly the trickiest part of the process, as there are numerous settings and each must be configured exactly right. On top of this, there are a few “gotchas” and problems you will need to work through.

The Updater block documentation lists several ways to configure AppStart, but by far the easiest and most flexible approach is to put the configuration for both AppStart and the Application Updater assembly into a single app.config file in the AppStart project. You need to add the app.config file yourself (Add | New Item | Application Configuration File).

The documentation for the block lists the required settings in some detail, so I won’t reproduce them here, but you should note the following caveats:

The AppStart app.config holds the configuration settings for both the AppStart and AppUpdater assemblies, so create sections for both assemblies in the tag, as follows:


In the section you set configuration data about your installed application, including the name of your application’s .exe file, the initial version (which you should explicitly set in the AssemblyInfo.cs file), and the last updated timestamp (just use the default setting defined in the documentation).

You will also need to specify the folder where AppStart can locate your application exe file; unfortunately, this setting requires an absolute path to your application’s executable file. This is a design flaw with the current version of the block?you can only specify an absolute path if you know with absolute certainty where your application will be installed. Unfortunately, users can elect to install your application to any location they like on their machine. This may not be a problem for you (for instance, if you work within an organization and can dictate installation locations), but if it is you’ll find one possible solution later in this article. For now, define the as a location relative to the AppStart.exe location?the folder that you earlier named “”, the initial version of your application.

You also need to set the tag to this value, to indicate to the Updater which version of your application the user initially has installed. At runtime the Updater compares this version with the version specified in the server manifest, and only downloads updates with a higher version number. The Updater creates a new folder for the downloaded application files, named after the folder that contained the update on the server (this is where a good update folder naming convention comes in handy?name the folders after the version numbers they represent). After the download is complete, the Updater changes the value of the element to the newly created folder, and changes to reflect the new version number. This ensures that AppStart will launch the newly installed version the next time the user launches your application.

Your config section should resemble the example below:

                 WinFormsApplication.exe                     2003-05-04T14:49:18.4483296-05:00                  

Configuring the Updater
In the section you specify the config data for the Updater block itself. As with the section, your first point of reference when setting up this section should be the Updater block documentation, but the following tips should save you some time. The easiest parts to configure are the and sections, which are self-explanatory. You should copy the default settings given in the documentation.

You can use a custom validator to ensure that downloaded code hasn’t been tampered with by third parties. Doing so is both optional (and configurable with the useValidation attribute of the element); however, omitting any of the settings generates a configuration exception, so you must include valid configuration data for this section. This is another flaw in the Updater block’s design?after all, if you don’t want to use validation, why should you have go to the bother of having to generate keys and set up the configuration? That said, it’s highly recommended that you use validation for security reasons.

The Updater block comes with a useful and tastefully coloured tool called the Manifest Utility. The tool has a dual function: You use it to create the server manifests for your application’s updates, and you can also use it to generate the public and private keys to configure the Updater block’s validation functionality. Locate the utility’s source (which is in the same place as the Updater code), and add it to your solution. Set the utility as the startup project and run it. Select the File | Generate Keys menu item. Doing that creates two XML files in a location of your choosing.

  • Paste the contents of the public key file into the section of the element in the AppStart app.config file.
  • You’ll paste the contents of the private key XML file into the Key field of the Manifest Utility when you generate the server manifest file. You won’t do that just yet, so just save it for now (in a secure place, of course).

Note the following caveats:

  • The Manifest Utility will generate only RSA (Rivest, Shamir, and Adelman) keys, so set your AppStart app.config file to use RSA as the validator (see the Updater documentation on how to do this). You also need to make sure that the RSA assembly is selected in the Manifest Utility’s “Validator Class” field when generating the server manifest file.
  • You only need to create the keys once for each application, because the public key is distributed with your application. If you do generate multiple keys, make sure that the public and private keys you’re using match properly; otherwise, the validation will fail.
  • You should establish a suitable method for storing the private keys for your applications?treat them as you would any sensitive company secret by storing them in a secure location with limited and audited access.

You use the section for values specific to your application. Unfortunately this is another area where the design is flawed, because you need to specify absolute file paths again. You’ll see how to solve the problem later in this article; for now, make sure that any paths or file locations you set in this section are relative links.

The element doesn’t appear to be used by the Updater in any meaningful way; the documentation mentions in passing that it is the “the local path to the folder where the application is deployed,” but as you’re going to see how to convert the Updater to use relative links anyway, you should leave this element blank.

The is the location of the config file that contains the AppStart configuration settings. The Updater must to be able to locate the config file to determine if your application needs updating, and to update the config file after a successful download occurs. Set the value to the file you are currently editing?the app.config for AppStart?but remember that when you compile the project, Visual Studio converts the filename from app.config to AppStart.exe.config.

The element should contain the name of a temporary directory to which the Updater will download files. The Updater creates and deletes this directory on the fly during downloads, so you don’t need to specify a pre-existing location.

                              AppStart.exe.config         Application Download Cache                                     http://YourDeploymentServer/YourRootFolder               /YourApplicationUpdateFolder/Manifest.xml                  AppManifest.xml         60000               

The element contains configuration data for the server manifest file; this is the XML file used to describe the content of each of your application’s updates. You’ll see how to create the manifest using the Manifest Utility later.

The element describes the URL of the manifest file. This setting is critical?if you set it incorrectly, the Updater will fail and silently record an error in the event log. You’ll generate the Manifest in the next stage, so for now, leave this setting blank or use the example as a template.

The element describes the location to which the manifest will be copied on the client machine. Again, when used “as is”, the Updater requires this value to be an absolute file path, but set it to a relative path for now.

The is the default timeout for the manifest file download; leave this at its default setting.

Deploying an Application Update
To deploy an update, you must create a server manifest file that contains information about the new version and its files. Before you generate the manifest, you should set up your deployment server and create a folder on a network share to contain your application updates. You should adopt the following pattern so that you and your release team can build an extensible framework for making application updates available on your network:

   |---Root   |       |---Application Name   |       |       |---Manifest.xml   |       |       |---   |       |       |       |---

Using this structure lets you set up a deployment server for multiple versions of multiple applications. It’s sensible to give the folder that will hold your application files the same name as the new version number?because the Updater will download the files into an identically named folder on the client, and will help keep the multiple versions of your app’s files neatly organized.

After copying your application files into the new folder you’re ready to fire up the Manifest Utility (see Figure 6).

Figure 6. The Manifest Utility: This utility lets you specify options for the location and version of files used to update your application, along with RSA security to validate that the downloads requested by the Updater application haven’t been tampered with.
  1. Enter the path to the new version of your application’s files into the Update Files Folder field. The Updater will download all the new files into a new folder, so you need to make sure that all of the files required for your new version to run are available in this folder?even those that haven’t changed from the previous version.
  2. Set the Update Location field to the folder on your deployment server from which the update will be downloaded.
  3. Enter the new version number into the Version field. The Updater compares this version number with installed version number to determine whether it needs to download an update.
  4. Paste the private key you generated earlier into the Key textbox (remember to set the Validator Class combo to “RSA,” or the manifest creation will fail with an exception.

This is the minimum amount of configuration data you need to enter; you can now hit the Create Manifest button to generate the XML manifest file. Deploy the manifest to the server, and remember to set the element in the config section of the AppStart app.config file to the manifest’s location.

The following tips may save you some time when configuring this last crucial step:

  • It sounds like common sense, but ensure that the network location you have selected for your updates is visible via HTTP from your client machines.
  • Remember to apply the proper permissions to the update folder and manifest file, so that the Updater can access the manifest file and read its contents, and then successfully stream the application files to the client.
  • The Updater will access the manifest file over HTTP, so you may find the best solution is to set up a virtual directory to distribute application updates. Doing that will resolve most security problems;
  • The folder containing your updated application files needs to contain all the files required for your application to run; it is not possible to update individual files or assemblies (without changes to the Updater block), and if you try to do this the updates will download just fine but your application will fail to run when the user launches the new version;
  • You’ll usually need to generate a manifest each time you need to deploy a new version of your application, but remember to reuse the same private key for all versions of your application.

AppStart Overview
If you’ve worked along with this article, by now you should have set up and configured the Updater for your application and created a network share or virtual directory that you can use to deploy updates. Your app is almost ready to be deployed, but you need to complete the AppStart component provided by Microsoft with the block code so that it will launch the Updater block and manage downloads.

The AppStart code, as provided with the Updater block, is incomplete; AppStart’s Main() method checks to make sure your app isn’t already running, and launches your app in a new process. It doesn’t contain any code to manage the Updater and download updates. This is to ensure that AppStart remains generic so it can be used in a variety of different deployment scenarios. Microsoft has provided several QuickStart samples with the Updater Block installation to illustrate each usage scenario.

As you’re building a self-updating Windows Forms app, the Self Updating QuickStart is the place to begin (you’ll find the code in the SelfUpdating2.cs file in the QuickStart SelfUpdating folder with the rest of the Updater block source). Have a look through the code for this version of AppStart and familiarize yourself with it; you’ll need to create your own version of AppStart or your application, using this as a template. I’m not going to reproduce the code in the QuickStart here?I’ll leave you to create your own AppStart using the information in the next few sections.

The first thing to notice is that AppStart launches the Updater on a new thread; the code in AppStart’s Initialize() method calls into ApplicationUpdateManager.StartUpdater(). After launching, all subsequent communication is triggered via events raised from the Updater. You may be aware that Windows Forms controls aren’t thread safe, which means that you can only access Win Forms controls from the thread on which they were created. To ensure that this rule isn’t broken (which can lead to random deadlocks, InvalidOperation exceptions and other strange and difficult to reproduce behavior), the Updater events are handled using the AppStart form’s Invoke() method which uses a delegate to a method that is guaranteed to be invoked on the UI thread. There is an excellent discussion of the Win Forms thread safety issue, along with sample code at:

To summarize then, AppStart launch your application in a new process, which ensures that the user can continue on with working in your application while AppStart drives the Updater. The MS-supplied code lacks a means to exit AppStart when the user terminates your app; without this, AppStart will get “stranded”, and continue running. To prevent this, you need to change the StartApp_Process() method in AppStart, as follows (note that I take no responsibility for the coding standards in use by the authors of the application block?few of the MS App Blocks conform to the .NET Design Guidelines, and I usually have to exclude them from my FxCop projects):

   ProcessStartInfo p = new ProcessStartInfo (_exePath);   p.WorkingDirectory = Path.GetDirectoryName(_exePath);   _process = Process.Start(p);      // Add the following lines to ensure that AppStart    // terminates when the app process exits:   _process.EnableRaisingEvents = true;   _process.Exited += new EventHandler(process_Exited);

The process_Exited() method (helpfully created for you by Visual Studio) should stop the Application Updater if it is running, and also kill the AppStart process with the line:


Now that you’ve ensured that both AppStart and the Updater will be cleaned up when a user closes your application, you can get on with wiring up the Updater’s events. The Updater events all provide an ApplicationUpdaterEventArgs class argument, which supplies comprehensive information about the new version, derived from the server manifest file. The available information includes the application name, the new version number, the location of the new version on the network, and even information about the files included in the update.

You should subscribe to the Updater events shown in Table 2:

Table 2. The table shows the Updater events that you should subscribe to, along with a description and a usage scenario for each.

Event Description AppStart Action
UpdateAvailable Fires when the Updater reads the server manifest for your app and the version number of the update is greater than the version installed. Use this event to notify users that an update is available; you might display a MessageBox giving them the option of downloading the update (you should never force an update on the user), and perhaps provide a link to information about the benefits of upgrading to the new version.Note that unless you prevent it, the Updater will continue to run, downloading the updated files after raising the UpdateAvailable event. To prevent this, you must block the Updater thread in your handler for the event (the best way is to use a modal dialog or MessageBox), and call the ApplicationUpdateManager.StopUpdater() method if the user decides to reject the update.
DownloadStarted Fires when the Updater begins to download the update. You might want to take advantage of this method to provide feedback to the user on the progress of the download. To provide this kind of feedback, you ideally need to know how many files are to be downloaded, and some way of calculating how long it takes each to download.The ApplicationUpdaterEventArgs class provides you with a Files array from which you can obtain the number of files to be downloaded. It’s a bit trickier to work out when each has been successfully downloaded, but you can use a FileSystemWatcher on the download temp directory (you set this in the element of the Updater config section). Note that the downloader will generate .tmp files as it streams down the bits, and you may find that the FileWatcher will notify you two or three times per file as the .tmp files are downloaded and transformed into the actual application files. I recommend you wire up both the Created and Changed events of the FileSystemWatcher to ensure you receive proper notifications during the download.Using this information, you could set the Maximum property of a Progress Bar control to the number of files, and call the Increment() method in the handler for the FileSystemWatcher events.
DownloadCompleted Fires when the application files have been downloaded and moved from the temporary download folder into the new application folder. You can use this event to close down any dialogs you displayed to indicate download progress, and ask users if they want to close the application and launch the new version (the QuickStart shows you how to do this). Likewise, if your update made changes that require a reboot, this is the time to ask users if they want to reboot right away or later.

Updater Block Design Flaw?Eliminating Absolute Links
As you’ve seen, the block relies on absolute paths links being provided in the configuration file. But this reliance causes big problems, because you can’t always guarantee that users will install your application to a specific location. Fortunately, fixing the application block is relatively simple using some basic Reflection techniques to locate the directory in which the Updater components are executing, and appending the relative link to the application name that you defined in the config file. Unfortunately, there are quite a few places where changes need to be made. Remember that the changes described in this section are purely optional, and should be undertaken only if you’re determined to use relative links in the config file. If you can ensure that all the file paths in the config file are absolute links, everything will work just fine without these modifications.

Start by changing the first line in the StartApp_Process() method, which combines the (supposedly containing an absolute path to the folder your app is installed in) and the settings defined in the AppStart config section.

   _exePath = Path.Combine(      _config.FolderName , _config.ExePath );

You want to change the preceding line to use Reflection to obtain the name of the folder in which AppStart is executing, and to append the (relative) folder name and your application’s executable name to it, as follows (if you remember, your app folder is a subfolder of the folder containing AppStart):

   string appFolder =      Path.GetDirectoryName(     Assembly.GetExecutingAssembly().     GetModules()[0].FullyQualifiedName);   _exePath = Path.Combine(appFolder +       _config.FolderName, _config.ExePath);

You’ll fix the Updater in a similar way. Table 3 lists the location of the changes you should make:

Table 3. The table lists the changes you must make to the Updater application to remove the Updater Block’s dependency on absolute paths.

File Class Member Notes
UpdaterConfiguration.cs ClientConfiguration XmlFile set


BaseDir set Remove the !Path.IsPathRooted check?it will throw anexception if you don’t use an absolute link!
TempDir set Again, remove the Path.IsPathRooted check.
ServerConfiguration ServerManifestFileDestination set


All these property sets are very similar, so I’ll use the XmlFile set as an example. In this property set, the _xmlFile field is set to value; change this as follows:

   string folder = Path.GetDirectoryName(      Assembly.GetExecutingAssembly().      GetModules()[0].FullyQualifiedName);   string relativeLocation = folder + value;   _xmlFile = relativeLocation; 

As you can see, this uses code similar to that used in AppStart to remove the dependency on an absolute path.

Final Thoughts
Every time the Updater downloads a new version of your application to the client, it creates a new folder to hold the new version; unfortunately, it leaves the folder(s) for the outdated version of your application and all its files intact. That’s untidy, leaves open the possibility for users to run an older version, and bloats the disk space requirements for your application. Fortunately, there’s a solution to this?use a custom Updater Post Processor (note the fields for this in Figure 6). A Post Processor runs after a download completes. To get a working example, download the source for Sam Santiago’s custom Updater Post Processor, which deletes all but the two most recent downloads, giving you a rollback path in case of problems.

The Updater block gives you the means to create self-updating Windows Forms applications today. It may not be perfect, but it does provide a glimpse of a new Microsoft distribution mechanism called ClickOnce, that will debut with Visual Studio 2005. ClickOnce will also rely on configuration and manifest files in order for it to work, but the development environment will generate these for you, so most of the headaches of using the Updater block will go away. The other headache?having to write code to process the manifests and control the download?will also disappear, thanks to the System.DeploymentFramework service installed with version 2.0 of the .NET Framework. Getting to grips with the Updater Block will provide you with a thorough understanding of how .NET 2.0 and Longhorn will manage application deployment.

Share the Post:
Share on facebook
Share on twitter
Share on linkedin

More From DevX