Click here to Skip to main content
11,492,962 members (60,476 online)
Click here to Skip to main content

Auto Updatable Application

, 29 Jul 2011 CPOL 22.4K 2.4K 88
Rate this:
Please Sign up or sign in to vote.
Explains how a .NET application can update its own binaries and dependencies

Introduction

Some applications may need to update themselves periodically. Typically the applications which are designed to be highly extensible usually may update few modules and thus can get new capabilities and functionality dynamically, just like plugins.

.NET applications can take advantage of loading updatable modules in separate AppDomains and unload them when update is available and reload them with updated assemblies. One problem that you may encounter is that the assembly files which are loaded get locked and this prevents replacing them. If you are using separate AppDomains to load assemblies, then the solution is easier. You can enable shadow copy feature for AppDomain which actually makes a copy of the assembly before loading, thus original assemblies remain unlocked and you can replace them while the application is running.

If you are not using a separate AppDomain and you want to update the assemblies including your application EXE, then you need to do some tricky things as explained in the rest of the article.

Background

I wanted to basically run the application and let the application update its own EXE and other assemblies from some remote location. This requires starting the application in such a way that it do not lock its assemblies as normally happens when an application starts. If the application can update itself, distribution of application can be done from a centralized location. I am not including any logic to download the assemblies in this article.

Using the Code

I found two solutions for the problem as below.

Solution One

Here I have used an additional application (i.e., Starter.exe) along with the main application. The Starter.exe should be present in the application's start up directory. If you are downloading code, then make sure that you copy the Startup.exe after building.

The code for Solution one is as below:

internal static class Program
    {
        private static readonly string DomainName = "MarvinDomain";
        private static readonly string CatchFolder = "AssemblyCatch";

    
        [STAThread]
        private static void Main()
        {
if (AppDomain.CurrentDomain.FriendlyName != DomainName)
{
    string exeFilePath = Assembly.GetExecutingAssembly().Location;
    string applicationFolder = Path.GetDirectoryName(exeFilePath);

    string starterExePath = applicationFolder + "\\Starter.exe";
    //Debugger.Launch(); // You can uncomment to automatically launch VS for debugging
    //Solution one - Using Separate EXE to start application.
    if (File.Exists(starterExePath)) // Check if starter EXE is available
    {
        XmlSerializer serialier = new XmlSerializer(typeof(AppDomainSetup));
        string xmlFilePath = applicationFolder + "\\XmlData.xml";
        using (var stream = File.Create(xmlFilePath))
        {
	serialier.Serialize(stream, AppDomain.CurrentDomain.SetupInformation);
        }

        exeFilePath = exeFilePath.Replace(" ", "*");
        xmlFilePath = xmlFilePath.Replace(" ", "*");
        string args = exeFilePath + " " + xmlFilePath;

        Process starter = Process.Start(starterExePath, args);
    }

In Main method, the application checks for the FriendlyName of current AppDomain if it’s matching to a specific name (i.e. MarvinDomain) .This can be anything that you want.

If the name is not matching, then the application checks if Starter.exe is available in its startup path. If it gets this EXE, it starts the new process and that will launch the Starter.exe. While launching, it passes two string arguments to the new process:

  1. Its own EXE path
  2. XML File path.

The application serializes AppDomainSetup for current AppDomain to XML file and passes it as a second string argument.

Note that I have replaced the spaces in the file path with star because if space is there, it generates additional arguments.

Now when Starter.exe runs, it receives the EXE path of our main application and path of XML file which contains serialized AppDomainSetup from the string arguments in its Main method. It then creates the new AppDomain and enables shadow copy feature to this newly created domain. To do so, it makes use deserialized AppDomainSetup and changes the relevant properties. If deserialized AppDomainSetup is not available, it creates a new one. It then runs the application, i.e., our main application in this new domain.

Code for Starter Application

private static readonly string DomainName = "MarvinDomain";
        private static AppDomainSetup setup;

        private static void Main(string[] args)
        {
string executablePath = string.Empty; 
           //  Debugger.Launch();       	// You can uncomment to automatically 
					// launch VS for debugging
if (args.Length > 0)         
{
    executablePath = args[0];
    executablePath = executablePath.Replace("*", " ");
    if (args.Length > 1)
    {
        string xmlFilePath = args[1];
        xmlFilePath = xmlFilePath.Replace("*", " ");
        if (File.Exists(xmlFilePath))
        {
XmlSerializer serializer = new XmlSerializer(typeof(AppDomainSetup));
using (var stream = File.Open(xmlFilePath, FileMode.Open, FileAccess.Read))
{
    setup = serializer.Deserialize(stream) as AppDomainSetup;
}
File.Delete(xmlFilePath);
        }
    }
}
if (File.Exists(executablePath))
{
    AppDomainSetup appDomainShodowCopySetup = AppDomain.CurrentDomain.SetupInformation;

    if (setup != null)
    {
        appDomainShodowCopySetup = setup;
    }

    appDomainShodowCopySetup.ShadowCopyFiles = true.ToString();

    appDomainShodowCopySetup.CachePath = Path.GetDirectoryName
					(executablePath) + "\\AssemblyCatch";

    AppDomain marvinDomain = AppDomain.CreateDomain
				(DomainName, null, appDomainShodowCopySetup);

    marvinDomain.ExecuteAssembly(executablePath);

    AppDomain.Unload(marvinDomain);
}
}
}

When application runs in new domain which has the expected name, this time our main Application will do its normal job, like running the MainForm instead of looking for Starter.exe.

To check the demo, download the binaries and click UpgradableApplication.exe which is a WinForm application.You will note that an additional catch folder for assemblies will be created inside startup folder and all assemblies will be copied to this folder. To make sure that our UpgradableApplication.exe is not locked, you can delete the file while the application is running.

Solution Two

Though solution one works, it requires additional Startup.exe and if you look into Windows Task Manager you will see the name of Starter.exe instead of our main EXE.

The demo application uses solution two if it does not find Starter.exe. So to check the demo for this solution, you can just rename Starter.exe to something else and the second approach will be used. The second approach is not radically different. The code is as below:

if (!applicationFolder.EndsWith(CatchFolder))
        {
string copyDirectoryPath = applicationFolder + "\\" + CatchFolder;
if (!Directory.Exists(copyDirectoryPath))
{
    Directory.CreateDirectory(copyDirectoryPath);
}
DateTime now = DateTime.Now;
string dateTimeStr = now.Date.Day.ToString() + now.Month.ToString() 
         + now.Second.ToString() + now.Millisecond.ToString();
string copyExePath = copyDirectoryPath + "\\CopyOf" +
          Path.GetFileNameWithoutExtension(exeFilePath)
         + dateTimeStr + ".exe";
           
File.Copy(exeFilePath, copyExePath,false );

Process.Start(copyExePath);
return;
        }

        Thread mainThread = new Thread(() =>
         {
 // Debugger.Launch();    // You can uncomment to automatically launch VS for debugging
 AppDomainSetup appDomainShodowCopySetup = AppDomain.CurrentDomain.SetupInformation;

 appDomainShodowCopySetup.ShadowCopyFiles = true.ToString();
 appDomainShodowCopySetup.ApplicationBase = applicationFolder.Replace(CatchFolder, "");
 appDomainShodowCopySetup.CachePath = 
	Path.GetDirectoryName(exeFilePath) + "\\" + CatchFolder;
 //Configure shadow copy directories 
 //appDomainShodowCopySetup.ShadowCopyDirectories = "C:\\DllsToBeShadowCopyied";
 AppDomain marvinDomain = AppDomain.CreateDomain
			(DomainName, null, appDomainShodowCopySetup);

 marvinDomain.ExecuteAssembly(exeFilePath);

 AppDomain.Unload(marvinDomain);
         });

        mainThread.Start();
    }
    return;
}

This approach also makes use of FriendlyName of the current AppDomain along with startup path of application. Application checks its startup path if it is not from a catch path, then it copies its EXE to AssemblyCatch folder and launches the new process that uses EXE from new path. I have used the system date time to give a unique name to the copied EXE file. When new process is launched this time, it's from catch path, now this time the application will create a new domain with shadow copy feature enabled and will launch the application in this new domain. When application runs in new domain, it has valid Friendly name for domain as well as running form catch path so this time it will do its normal job of running a MainForm.

I am using a new thread for creating a new AppDomain since I encountered a problem to unload AppDomain on default Main thread.

It's up to the application developer how to get updates and when. Application may keep checking periodically for updates in background and if it finds, it will download. Application can inform user to restart application or if possible it can restart itself and get new changes.You can specify the folders from which the assemblies will be copied by using property ShadowCopyDirectories of AppDomainSetup. To prevent restarting the entire application, you can also design an application that will update only few modules which can be in separate domain and can be unloaded and reloaded once update is available.

Points of Interest

Here I have managed to make enable shadow copy to default application domain. As a result, the original application assemblies remains unlocked and those can be replaced by application itself. With shadow copying for few modules, you can make sure that your application can continue to run forever without restart and is still updated.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Prafulla Hunde
Software Developer Yokogawa IA Technologies India
India India
I am software developer with over 7 years exp.I have been working in industrial automation domain using MS technology.I have primarily worked on winform/WPF desktop applications.
using C# with .net framework 2.0,3.5& 4.0. I am interested and trying to get comfortable with WPF , Linq and new features..

Comments and Discussions

 
GeneralMy vote of 5 Pin
johannesnestler11-Jul-14 5:04
memberjohannesnestler11-Jul-14 5:04 
GeneralA simpler (?) solution Pin
ShdNx1-Aug-11 10:28
memberShdNx1-Aug-11 10:28 
GeneralRe: A simpler (?) solution Pin
Prafulla Hunde1-Aug-11 20:35
memberPrafulla Hunde1-Aug-11 20:35 
GeneralRe: A simpler (?) solution Pin
alhambra-eidos4-Aug-11 1:45
memberalhambra-eidos4-Aug-11 1:45 
GeneralRe: A simpler (?) solution Pin
Prafulla Hunde4-Aug-11 3:58
memberPrafulla Hunde4-Aug-11 3:58 
AnswerRe: A simpler (?) solution Pin
ShdNx11-Aug-11 1:22
memberShdNx11-Aug-11 1:22 
GeneralMy Own Solution Pin
Matt U.29-Jul-11 18:31
memberMatt U.29-Jul-11 18:31 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.150520.1 | Last Updated 29 Jul 2011
Article Copyright 2011 by Prafulla Hunde
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid