17

I'm writing a Asp.Net Core application which should be able to update itself (replace its own binaries while running).

This MSDN article describes shadow copying with the classical .Net framework, which would be exactly what I need. But the whole AppDomain thing is missing in .Net Core.

So my questions are:

  • Is there an alternative way in .Net Core to enable shadow copying the assemblies?
  • Are there other mechanisms in .Net Core that allow to build a self-updating application?
Robert Hegner
  • 9,014
  • 7
  • 62
  • 98
  • Did u find any work around for this ? Unfortunately, It's been 2 years since launch of .Net core and seems like there's still no support for this. I am also facing similar problem: https://stackoverflow.com/questions/47895998/asp-net-core-updating-or-publishing-new-dlls-without-stopping-the-application – Jay Shah Dec 19 '17 at 22:25
  • Have you found solution for that? – Marek Urbanowicz Jan 29 '18 at 17:59
  • @MU Please see my new answer where I describe the solution I'm using now. – Robert Hegner Jan 30 '18 at 07:21

5 Answers5

24

Since there is no built in mechanism in .NET Core for doing this, I ended up implementing my own custom solution. It works roughly like this:

  1. The running application downloads and extracts new binaries to a new folder.
  2. The running application starts a small updater process. The following parameters are passed to the updater process via command line:
    • Process id of the running application
    • Binary path of the running application
    • Path of the downloaded binaries
  3. The running application exits itself.
  4. The updater process waits until the running application has exited (using the process id) or forcefully kills the running application if it doesn't exit by itself within a given timeout.
  5. The updater process deletes the existing binaries and copies the new downloaded binaries over.
  6. The updater process starts the new version of the main application.

Make sure you do as much as possible in the main application (downloading, unpacking, validation, ...) and keep the updater process as simple as possible (minimize risk of failing).

This approach has proven to be quite stable.

Robert Hegner
  • 9,014
  • 7
  • 62
  • 98
6

There's no build in shadow copying facilities in .NET Core

davidfowl
  • 37,120
  • 7
  • 93
  • 103
2

.Net API Browser indicates that the property required to set this up in .Net Core is but AppDomainSetup is not.

To be clear, AppDomain was added in .Net Standard 2.0 but creating a domain is not currently Supported

Digital Coyote
  • 326
  • 2
  • 12
  • 2
    AppDomain is part of .NET Standard 2.0 but is not fully implemented in .NET Core, see https://github.com/dotnet/standard/blob/fe8d520d5ab2d5cdf281222329ff83d96b09f7f4/docs/faq.md#is-appdomain-part-of-net-standard – 0xced Sep 03 '18 at 19:36
  • 1
    Yes `AppDomain` now seems to be available, but `AppDomainSetup`, which is needed to configure shadow copying, is still not available as of .NET Core 2.1. So it looks like shadow-copying is still not supported. – Robert Hegner Sep 04 '18 at 06:25
1

To save someone having to do what I just did and make this - this only copies files with a different date modified time. I checked and rebuilding your app only changes this on a few files. This makes for a very fast self-loader that then starts the exe in the new location, and exits the exe doing the loading that was running from the old location. This may rely on a few things like your DLL running the code must be named the same as the EXE that starts it.

Works in .Net 5:

using System;
using System.Diagnostics;
using System.IO;

namespace NetworkHelper
{
    public static class LocalCopier
    {
        public static void EnsureRunningLocally(string callingAssemblyDotLocation)
        {
            var assemblyFileFriendlyName = Path.GetFileName(callingAssemblyDotLocation.Replace(".", "-"));
            var assemblyDirToCheck = Path.GetDirectoryName(callingAssemblyDotLocation);
            var localLocation = Configuration.Tools.AppsLocation + assemblyFileFriendlyName + "\\";
            var assemblyFinalExePath = localLocation + assemblyFileFriendlyName.Replace("-dll", ".exe"); 
            
            // Check what assembly passed in path starts with
            var runningFromNetwork = callingAssemblyDotLocation.ToLower().StartsWith(@"\\w2k3nas1\");
            if (callingAssemblyDotLocation.ToLower().StartsWith(@"i:\"))  runningFromNetwork = true;

            if (!runningFromNetwork) return;
            
            // Check if copied to local already
            Directory.CreateDirectory(localLocation);

            // Foreach file in source dir, recursively
            CopyOnlyDifferentFiles(assemblyDirToCheck, localLocation);

            Process.Start(assemblyFinalExePath);
            
            Environment.Exit(0);
        }

        private static void CopyOnlyDifferentFiles(string sourceFolderPath, string destinationFolderPath)
        {
            string[] originalFiles = Directory.GetFiles(sourceFolderPath, "*", SearchOption.AllDirectories);

            Array.ForEach(originalFiles, (originalFileLocation) =>
            {
                FileInfo originalFile = new FileInfo(originalFileLocation);
                FileInfo destFile = new FileInfo(originalFileLocation.Replace(sourceFolderPath, destinationFolderPath));

                if (destFile.Exists)
                {
                    if (originalFile.LastWriteTime != destFile.LastWriteTime)
                    {
                        originalFile.CopyTo(destFile.FullName, true);
                    }
                }
                else
                {
                    Directory.CreateDirectory(destFile.DirectoryName);
                    originalFile.CopyTo(destFile.FullName, false);
                }
            });
        }
    }
}

Note that "\w2k3nas1" and "i:" are examples of network locations where if it is running from those, it should copy itself to a local directory, I use application data/roaming/localApps and then restart itself from the new directory.

This can all be put into a reference library and be called from any client apps with: NetworkHelpers.LocalCopier.EnsureRunningLocally(Assembly.GetExecutingAssembly().Location);

(Here, Assembly.GetExecutingAssembly().Location is passed in from the calling app, because if you were to run that from in the reference project, you'd get that library's dll instead.)

0

I made my own solution with PowerShell Core (available on Windows/Linux/Mac).

You can use the following script to create a powershell script to update the app. IMHO: PowerShell solution is better than an external update app: script is transparent and no additional overhead for background services that lives outside of your app.

Don't forget to inject your variables:

# We don't need progress bars to consume CPU
$ProgressPreference = 'SilentlyContinue'
# Stopping the current app
$appDll = '{assemblyName}.dll'
Stop-Process -Id {processId} -Force
$appFolder = '{folderPath}'
Set-Location -Path $appFolder
# Source file location
$source = '{updateLink}'
# Destination to save the file (folder of the script)
$updateName = Get-Date -Format 'up_dd_MM_yyyy_HH_mm'
$updateNameFile = $updateName + '_update.zip'
$updateZipPath = Join-Path -Path $appFolder -ChildPath $updateNameFile
# Download the update
Invoke-WebRequest -Uri $source -OutFile $updateZipPath
# Classic Unzip
Expand-Archive -Path $updateZipPath -DestinationPath $appFolder -Force
# Cleaning
Remove-Item -Path $updateZipPath
Start-Process -FilePath 'dotnet' -ArgumentList $appDll
TheAccessMan
  • 133
  • 10