Skip to content

Commit

Permalink
Winget Auto-Update & Install (#1686)
Browse files Browse the repository at this point in the history
* Figuring it out

* Update to both instal and update the package... tests

* Added the ability to both install and update the appliation using Winget. This should run on Windows Server by skipping all the checks as winget is not supported ,we still deploy to chocolaty.
  • Loading branch information
MrHinsh authored Oct 24, 2023
1 parent e086c96 commit 0c6e3f6
Show file tree
Hide file tree
Showing 7 changed files with 305 additions and 19 deletions.
6 changes: 6 additions & 0 deletions MigrationTools.lutconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<LUTConfig Version="1.0">
<Repository />
<ParallelBuilds>true</ParallelBuilds>
<ParallelTestRuns>true</ParallelTestRuns>
<TestCaseTimeout>180000</TestCaseTimeout>
</LUTConfig>
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
},
"init": {
"commandName": "Project",
"commandLineArgs": "init --options Full"
"commandLineArgs": "init --options Full skipVersionCheck"
},
"execute2": {
"commandName": "Project",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MigrationTools.Services;
using Serilog;
using Serilog.Events;

namespace MigrationTools.Host.Services.Tests
{
[TestClass]
public class DetectVersionService2Tests
{

[TestInitialize]
public void Setup()
{
var loggers = new LoggerConfiguration().MinimumLevel.Verbose().Enrich.FromLogContext();
loggers.WriteTo.Logger(logger => logger
.WriteTo.Debug(restrictedToMinimumLevel: LogEventLevel.Verbose));
Log.Logger = loggers.CreateLogger();
Log.Logger.Information("Logger is initialized");
}


[TestMethod, TestCategory("L3")]
public void DetectVersionServiceTest_Initialise()
{
IDetectVersionService2 dos = new DetectVersionService2(new TelemetryLoggerMock());
Assert.IsNotNull(dos);
}

[TestMethod, TestCategory("L3")]
public void DetectVersionServiceTest_Update()
{
IDetectVersionService2 dos = new DetectVersionService2(new TelemetryLoggerMock());
dos.UpdateFromSource();

Assert.IsNotNull(dos);
}


}
}
3 changes: 2 additions & 1 deletion src/MigrationTools.Host/MigrationToolHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,8 @@ public static IHostBuilder CreateDefaultBuilder(string[] args)
// Services
services.AddTransient<IDetectOnlineService, DetectOnlineService>();
services.AddTransient<IDetectVersionService, DetectVersionService>();
//services.AddTransient<IDetectVersionService, DetectVersionService>();
services.AddTransient<IDetectVersionService2, DetectVersionService2>();
// Config
services.AddSingleton<IEngineConfigurationBuilder, EngineConfigurationBuilder>();
Expand Down
173 changes: 173 additions & 0 deletions src/MigrationTools.Host/Services/DetectVersionService2.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Threading;
using Microsoft.ApplicationInsights.DataContracts;
using Microsoft.Extensions.Logging;
using MigrationTools.DataContracts.Pipelines;
using MigrationTools.EndpointEnrichers;
using NuGet.Common;
using NuGet.Configuration;
using NuGet.Protocol;
using NuGet.Protocol.Core.Types;
using NuGet.Versioning;
using Serilog;
using WGetNET;
using Version = System.Version;

namespace MigrationTools.Host.Services
{
public class DetectVersionService2 : IDetectVersionService2
{
private readonly ITelemetryLogger _Telemetry;

public string PackageId { get; set; }

private WinGetPackageManager packageManager;
private WinGetPackage package = null;

public Version RunningVersion { get; private set; }
public Version AvailableVersion { get; private set; }

public Version InstalledVersion { get; private set; }

public bool IsPackageInstalled { get; private set; } = false;

public bool IsPackageManagerInstalled { get; private set; } = false;

public bool IsUpdateAvailable {
get
{
return (InstalledVersion < AvailableVersion);
}
}

public bool IsRunningInDebug
{
get
{
return RunningVersion == new Version("0.0.0.1");
}
}

public bool IsNewLocalVersionAvailable
{
get
{
return (RunningVersion >= InstalledVersion);
}
}

public DetectVersionService2(ITelemetryLogger telemetry)
{
_Telemetry = telemetry;
PackageId = "nkdAgility.AzureDevOpsMigrationTools";

RunningVersion = Assembly.GetEntryAssembly().GetName().Version;
InitialiseService();
}

private void InitialiseService()
{
DateTime startTime = DateTime.Now;
using (var bench = new Benchmark("DetectVersionService2::InitialiseService"))
{
//////////////////////////////////
WinGetInfo wingetInfo = new WinGetInfo();
IsPackageManagerInstalled = wingetInfo.WinGetInstalled;
if (IsPackageManagerInstalled)
{
Log.Debug("The Windows Package Manager is installed!");
packageManager = new WinGetPackageManager();
}
try
{

if (IsPackageManagerInstalled)
{
Log.Debug("Searching for package!");
package = packageManager.GetInstalledPackages(PackageId).GroupBy(e => e.Id, (id, g) => g.First()).SingleOrDefault();
if (package != null)
{
AvailableVersion = new Version(package.AvailableVersion);
InstalledVersion = new Version(package.Version);
Log.Debug("Found package with id {PackageId}", PackageId);
IsPackageInstalled = true;
}
_Telemetry.TrackDependency(new DependencyTelemetry("PackageRepository", "winget", PackageId, AvailableVersion == null ? "nullVersion" : AvailableVersion.ToString(), startTime, bench.Elapsed, "200", IsPackageInstalled));
}
}
catch (Exception ex)
{
Log.Error(ex, "DetectVersionService");
IsPackageInstalled = false;
_Telemetry.TrackDependency(new DependencyTelemetry("PackageRepository", "winget", PackageId, AvailableVersion == null ? "nullVersion" : AvailableVersion.ToString(), startTime, bench.Elapsed, "500", IsPackageInstalled));
}
}
}


public void UpdateFromSource()
{
using (var bench = new Benchmark("DetectVersionService2::UpdateFromSource"))
{
if (IsPackageInstalled && IsUpdateAvailable)
{
Log.Information("Running winget update {PackageId} from v{InstalledVersion} to v{AvailableVersion}", PackageId, InstalledVersion, AvailableVersion);
System.Threading.Tasks.Task t = packageManager.UpgradePackageAsync(PackageId);
while (!t.IsCompleted)
{
Log.Information("Update running...");
System.Threading.Thread.Sleep(3000);
}
Log.Information("Update Complete...");
InitialiseService();
}
else if (!IsPackageInstalled)
{
Log.Information("Running winget install {PackageId} from v{InstalledVersion} to v{AvailableVersion}", PackageId, InstalledVersion, AvailableVersion);

System.Threading.Tasks.Task t = packageManager.InstallPackageAsync(PackageId);
while (!t.IsCompleted)
{
Log.Information("Install running...");
System.Threading.Thread.Sleep(5000);
}
Log.Information("Install Complete...");
InitialiseService();
}
}

}
}

public class Benchmark : IDisposable
{
private readonly Stopwatch timer = new Stopwatch();
private readonly string benchmarkName;

public TimeSpan Elapsed
{
get
{
return timer.Elapsed;
}
}

public Benchmark(string benchmarkName)
{
this.benchmarkName = benchmarkName;
timer.Start();
Log.Debug("{benchmarkName}||START", benchmarkName);
}

public void Dispose()
{
timer.Stop();
Log.Debug("{benchmarkName}||STOP Elapsed: {timerElapsed}", benchmarkName, timer.Elapsed);
}
}

}
24 changes: 24 additions & 0 deletions src/MigrationTools.Host/Services/IDetectVersionService2.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using System;

namespace MigrationTools.Host.Services
{
public interface IDetectVersionService2
{
public Version AvailableVersion { get; }

public Version InstalledVersion { get; }

public Version RunningVersion { get; }

public bool IsPackageManagerInstalled { get; }

public bool IsPackageInstalled { get; }
string PackageId { get; }

bool IsUpdateAvailable { get; }
bool IsRunningInDebug { get; }
bool IsNewLocalVersionAvailable { get; }

void UpdateFromSource();
}
}
74 changes: 57 additions & 17 deletions src/MigrationTools.Host/StartupService.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.Eventing.Reader;
using System.Linq;
using System.Reflection;
using System.Threading;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using MigrationTools.Host.Services;
using Serilog;
using static System.Net.Mime.MediaTypeNames;

namespace MigrationTools.Host
{
Expand All @@ -20,12 +24,12 @@ internal class StartupService : IStartupService
{
private readonly IHostApplicationLifetime _LifeTime;
private readonly IDetectOnlineService _detectOnlineService;
private readonly IDetectVersionService _detectVersionService;
private readonly IDetectVersionService2 _detectVersionService;
private readonly ILogger<StartupService> _logger;
private readonly ITelemetryLogger _telemetryLogger;
private static Stopwatch _mainTimer = new Stopwatch();

public StartupService(IHostApplicationLifetime lifeTime, IDetectOnlineService detectOnlineService, IDetectVersionService detectVersionService, ILogger<StartupService> logger, ITelemetryLogger telemetryLogger)
public StartupService(IHostApplicationLifetime lifeTime, IDetectOnlineService detectOnlineService, IDetectVersionService2 detectVersionService, ILogger<StartupService> logger, ITelemetryLogger telemetryLogger)
{
_LifeTime = lifeTime;
_detectOnlineService = detectOnlineService;
Expand All @@ -38,28 +42,64 @@ public void RunStartupLogic(string[] args)
{
ApplicationStartup(args);
Configure(_LifeTime);
if (_detectOnlineService.IsOnline())
if (_detectOnlineService.IsOnline() && !args.Contains("skipVersionCheck"))
{
Version latestVersion = _detectVersionService.GetLatestVersion();
Log.Verbose("Package Management Info:");
Log.Debug(" IsPackageManagerInstalled: {IsPackageManagerInstalled}", _detectVersionService.IsPackageManagerInstalled);
Log.Debug(" IsPackageInstalled: {IsPackageInstalled}", _detectVersionService.IsPackageInstalled);
Log.Debug(" IsUpdateAvailable: {IsUpdateAvailable}", _detectVersionService.IsUpdateAvailable);
Log.Debug(" IsNewLocalVersionAvailable: {IsNewLocalVersionAvailable}", _detectVersionService.IsNewLocalVersionAvailable);
Log.Debug(" IsRunningInDebug: {IsRunningInDebug}", _detectVersionService.IsRunningInDebug);
Log.Verbose("Full version data: ${_detectVersionService}", _detectVersionService);

_logger.LogInformation($"Latest version detected as {{{nameof(latestVersion)}}}", latestVersion);
var version = Assembly.GetEntryAssembly().GetName().Version;
if (latestVersion > version)
Log.Information("Verion Info:");
Log.Information(" Running: {RunningVersion}", _detectVersionService.RunningVersion);
Log.Information(" Installed: {InstalledVersion}", _detectVersionService.InstalledVersion);
Log.Information(" Available: {AvailableVersion}", _detectVersionService.AvailableVersion);


if (!_detectVersionService.IsPackageManagerInstalled)
{
_logger.LogWarning("You are currently running version {Version} and a newer version ({LatestVersion}) is available. You should update now using Winget command 'winget nkdAgility.AzureDevOpsMigrationTools' from the Windows Terminal.", version, latestVersion);
if (!args.Contains("skipVersionCheck"))
{
#if !DEBUG
Console.WriteLine("Do you want to continue? (y/n)");
if (Console.ReadKey().Key != ConsoleKey.Y)
Log.Warning("Windows Client: The Windows Package Manager is not installed, we use it to determine if you have the latest version, and to make sure that this application is up to date. You can download and install it from https://aka.ms/getwinget. After which you can call `winget install {PackageId}` from the Windows Terminal to get a manged version of this program.", _detectVersionService.PackageId);
Log.Warning("Windows Server: If you are running on Windows Server you can use the experimental version of Winget, or you can still use Chocolatey to manage the install. Install chocolatey from https://chocolatey.org/install and then use `choco install vsts-sync-migrator` to install, and `choco upgrade vsts-sync-migrator` to upgrade to newer versions.", _detectVersionService.PackageId);
} else
{
if (!_detectVersionService.IsPackageInstalled)
{
_logger.LogWarning("User aborted to update version");
throw new Exception("User Abort");

Log.Information("It looks like this application has been installed from a zip, would you like to use the managed version?");
Console.WriteLine("Do you want install the managed version? (y/n)");
if (Console.ReadKey().Key == ConsoleKey.Y)
{

_detectVersionService.UpdateFromSource();
}
}
#endif
if (_detectVersionService.IsUpdateAvailable && _detectVersionService.IsPackageInstalled)
{
Log.Information("It looks like this application has been installed from a zip, would you like to use the managed version from Winget?");
Console.WriteLine("Do you want install the managed version? (y/n)");
if (Console.ReadKey().Key == ConsoleKey.Y)
{
_detectVersionService.UpdateFromSource();
}
}
if (_detectVersionService.IsNewLocalVersionAvailable && _detectVersionService.IsPackageInstalled)
{
Log.Information("It looks like this package ({PackageId}) has been updated locally to version {InstalledVersion} and you are not running the latest version?", _detectVersionService.PackageId, _detectVersionService.InstalledVersion);
Console.WriteLine("Do you want to quit and restart? (y/n)");
if (Console.ReadKey().Key == ConsoleKey.Y)
{
Log.Information("Restarting as {CommandLine}", Environment.CommandLine);
Process.Start("devopsmigration", string.Join(" ", Environment.GetCommandLineArgs().Skip(1)));
Thread.Sleep(2000);
Environment.Exit(0);
}
}
}
} else
{
/// not online or you have specified not to
Log.Warning("You are either not online or have chosen `skipVersionCheck`. We will not check for a newer version of the tools.", _detectVersionService.PackageId);
}
}

Expand Down

0 comments on commit 0c6e3f6

Please sign in to comment.