Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Auto Updatable Application

0.00/5 (No votes)
29 Jul 2011 3  
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 has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here