Mauro Ganswer
Mauro Ganswer

Reputation: 1419

Shadow copy with AppDomain to overwrite exe at runtime

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();
}

Upvotes: 4

Views: 6219

Answers (3)

Andreas Reiff
Andreas Reiff

Reputation: 8404

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).

Upvotes: 0

Suleyman OZ
Suleyman OZ

Reputation: 170

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;
        }
    }
}

Upvotes: 10

Iain Ballard
Iain Ballard

Reputation: 4818

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.

Upvotes: 0

Related Questions