Self-update / shadow-copy with Asp.Net Core
Asked Answered
A

5

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?
Adelladella answered 14/3, 2017 at 8:5 Comment(3)
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: #47896498Delegation
Have you found solution for that?Fennie
@MU Please see my new answer where I describe the solution I'm using now.Adelladella
A
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.

Adelladella answered 30/1, 2018 at 7:19 Comment(2)
Hi @Robert, in the off chance that you've created and open-sourced your solution (since it seems like it could be very generic), is your solution available anywhere (eg nuget)? Thanks!Danieldaniela
Any kind of snippets on gist.github.com or so?Brew
D
6

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

Disbelieve answered 16/3, 2017 at 9:37 Comment(0)
C
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

Creosol answered 19/7, 2018 at 17:24 Comment(2)
AppDomain is part of .NET Standard 2.0 but is not fully implemented in .NET Core, see github.com/dotnet/standard/blob/…Ritzy
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.Adelladella
H
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.)

Heigl answered 31/3, 2021 at 22:17 Comment(0)
A
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
Armidaarmiger answered 25/6, 2022 at 18:39 Comment(2)
What about Administrator Rights? "Stop-Process -Id {processId} -Force" does not workCiao
please try Stop-Process -Id {processId} -Force -verb runasArmidaarmiger

© 2022 - 2024 — McMap. All rights reserved.