Robert Hegner
Robert Hegner

Reputation: 9376

Self-update / shadow-copy with Asp.Net Core

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:

Upvotes: 17

Views: 5947

Answers (5)

John Smith
John Smith

Reputation: 153

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

Upvotes: 1

PockyBum522
PockyBum522

Reputation: 59

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

Upvotes: 1

Digital Coyote
Digital Coyote

Reputation: 341

.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

Upvotes: 2

Robert Hegner
Robert Hegner

Reputation: 9376

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.

Upvotes: 24

davidfowl
davidfowl

Reputation: 38764

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

Upvotes: 6

Related Questions