4

In the following sample app I create a new AppDomain and I execute it with shadow copy enabled. From the new AppDomain I then try to delete (replace) the original main exe. However I get an "access is denied error". Interestingly, after launching the program, from Windows Explorer it is possible to rename the main exe (but not to delete it).

Can shadow copy work for runtime overwriting of the main exe?

static void Main(string[] args)
{
    // enable comments if you wanna try to overwrite the original exe (with a 
    // copy of itself made in the default AppDomain) instead of deleting it

    if (AppDomain.CurrentDomain.IsDefaultAppDomain())
    {
        Console.WriteLine("I'm the default domain");
        System.Reflection.Assembly currentAssembly = System.Reflection.Assembly.GetExecutingAssembly();
        string startupPath = currentAssembly.Location;

        //if (!File.Exists(startupPath + ".copy"))
        //    File.Copy(startupPath, startupPath + ".copy");

        AppDomainSetup setup = new AppDomainSetup();
        setup.ApplicationName = Path.GetFileName(startupPath);
        setup.ShadowCopyFiles = "true";

        AppDomain domain = AppDomain.CreateDomain(setup.ApplicationName, AppDomain.CurrentDomain.Evidence, setup);
        domain.SetData("APPPATH", startupPath);

        domain.ExecuteAssembly(setup.ApplicationName, args);

        return;
    }

    Console.WriteLine("I'm the created domain");
    Console.WriteLine("Replacing main exe. Press any key to continue");
    Console.ReadLine();

    string mainExePath = (string)AppDomain.CurrentDomain.GetData("APPPATH");
    //string copyPath = mainExePath + ".copy";
    try
    {
        File.Delete(mainExePath );
        //File.Copy(copyPath, mainExePath );
    }
    catch (Exception ex)
    {
        Console.WriteLine("Error! " + ex.Message);
        Console.ReadLine();
        return;
    }

    Console.WriteLine("Succesfull!");
    Console.ReadLine();
}
Mauro Ganswer
  • 1,379
  • 1
  • 19
  • 33
  • What are you trying to achieve? Overwriting the exe *should* be denied, as the file is in use. – Iain Ballard Mar 14 '14 at 18:39
  • yes, I would like to overwrite the exe. From MSDN: "Shadow copying enables assemblies that are used in an application domain to be updated without unloading the application domain". Is this valid only for dll and not for the exe? – Mauro Ganswer Mar 14 '14 at 21:29
  • That shadow copy is the other way around -- copy first, then run the copy so the original can be replaced. From MSDN: "When an application domain is configured to shadow copy files, assemblies from the application path are copied to another location and loaded from that location. The copy is locked, but the original assembly file is unlocked and can be updated." It would be helpful to know if you are doing an experiment, trying to modify runtime behaviour, or trying to do an in-place update? Or something else? – Iain Ballard Mar 14 '14 at 22:04
  • I need for an in-place update and I'm trying to do exactly what is reported in MSDN, i.e. I'm trying to run the copy not the original. The copy I do manually is just for then trying to overwrite the original exe in order to demonstrate that the original exe is not locked. This manual copy has nothing to do with the shadow copy and you could even remove it and then trying for instance to delete the original exe (if I understand correctly the purpose of the shadow copy). The shadow copy files are created (automatically and in another directory) once the new AppDomain is executed. – Mauro Ganswer Mar 15 '14 at 07:38
  • Have a look at MEF - you can use it to hot swap plugin code quite easily. Changing your running application is not going to be something you can do by simply changing the source exe. See this question: http://stackoverflow.com/questions/15232228/is-there-are-reference-implementation-of-hot-swapping-in-net – Iain Ballard Mar 15 '14 at 08:12
  • So if I understand correctly shadow copy alone works for overwriting dll, while for overwriting the exe you need shadow copy + MEF. Can you please confirm? – Mauro Ganswer Mar 15 '14 at 09:33
  • No running code can be replaced just by changing the underlying file. Executing native code is mapped in memory, not run from disk. Some virtual machines (like Erlang's) can handle the reloading, but most systems can't. Shadow copy allows you to replace the *original* while a *copy is runnng*. To hot swap code in .Net, you must have a host exe (which doesn't itself change) with a process to load and swap code as needed. MEF doesn't handle all of this out of the box, but it's not too difficult for an experienced developer. I will try to dig out some example code – Iain Ballard Mar 16 '14 at 18:54

3 Answers3

10

You can achive self updating application within a single application with multiple AppDomains. The trick is move application executable to a temporary directory and copy back to your directory, then load the copied executable in a new AppDomain.

static class Program
{
    private const string DELETED_FILES_SUBFOLDER = "__delete";

    /// <summary>
    /// The main entry point for the application.
    /// </summary>
    [LoaderOptimization(LoaderOptimization.MultiDomainHost)]
    [STAThread]
    static int Main()
    {
        // Check if shadow copying is already enabled
        if (AppDomain.CurrentDomain.IsDefaultAppDomain())
        {
            // Get the startup path.
            string assemblyPath = System.Reflection.Assembly.GetExecutingAssembly().Location;
            string assemblyDirectory = Path.GetDirectoryName(assemblyPath);
            string assemblyFile = Path.GetFileName(assemblyPath);

            // Check deleted files folders existance
            string deletionDirectory = Path.Combine(assemblyDirectory, DELETED_FILES_SUBFOLDER);
            if (Directory.Exists(deletionDirectory))
            {
                // Delete old files from this folder
                foreach (var oldFile in Directory.EnumerateFiles(deletionDirectory, String.Format("{0}_*{1}", Path.GetFileNameWithoutExtension(assemblyFile), Path.GetExtension(assemblyFile))))
                {
                    File.Delete(Path.Combine(deletionDirectory, oldFile));
                }
            }
            else
            {
                Directory.CreateDirectory(deletionDirectory);
            }
            // Move the current assembly to the deletion folder.
            string movedFileName = String.Format("{0}_{1:yyyyMMddHHmmss}{2}", Path.GetFileNameWithoutExtension(assemblyFile), DateTime.Now, Path.GetExtension(assemblyFile));
            string movedFilePath = Path.Combine(assemblyDirectory, DELETED_FILES_SUBFOLDER, movedFileName);
            File.Move(assemblyPath, movedFilePath);
            // Copy the file back
            File.Copy(movedFilePath, assemblyPath);

            bool reload = true;
            while (reload)
            {
                // Create the setup for the new domain
                AppDomainSetup setup = new AppDomainSetup();
                setup.ApplicationName = assemblyFile;
                setup.ShadowCopyFiles = true.ToString().ToLowerInvariant();

                // Create an application domain. Run 
                AppDomain domain = AppDomain.CreateDomain(setup.ApplicationName, AppDomain.CurrentDomain.Evidence, setup);

                // Start application by executing the assembly.
                int exitCode = domain.ExecuteAssembly(setup.ApplicationName);
                reload = !(exitCode == 0);
                AppDomain.Unload(domain);
            }
            return 2;
        }
        else
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            MainForm mainForm = new MainForm();
            Application.Run(mainForm);
            return mainForm.ExitCode;
        }
    }
}
Suleyman OZ
  • 152
  • 1
  • 12
0

As it's an interesting use case of MEF, I've bashed out a quick demo of how to hot-swap running code in C#. This is very simple and leaves out a lot of edge cases.

https://github.com/i-e-b/MefExperiments

Notable classes:

  • src/PluginWatcher/PluginWatcher.cs -- watches a folder for new implementations of a contract
  • src/HotSwap.Contracts/IHotSwap.cs -- minimal base contract for a hot-swap
  • src/HotSwapDemo.App/Program.cs -- Does the live code swap

This doesn't lock the task .dlls in the Plugins folder, so you can delete old versions once new ones are deployed. Hope that helps.

Iain Ballard
  • 4,433
  • 34
  • 39
0

You asked specifically to use ShadowCopy for the update process. If that (and why would it be?) not a fixed requirement, these ones were real eye openers for me:

https://visualstudiomagazine.com/articles/2017/12/15/replace-running-app.aspx

https://www.codeproject.com/Articles/731954/Simple-Auto-Update-Let-your-application-update-i

It comes down to you renaming the target file (which is allowed, even when it is locked since it is running) and then moving/copying the desired file to the now freed destination.

The vs-magazine article is very detailed, including some nifty tricks like finding out if a file is in use by current application (though only for exe, for .dlls and others one has to come up with a solution).

Andreas Reiff
  • 7,961
  • 10
  • 50
  • 104