Skip to content

Commit

Permalink
Installer checks for .NET 8.0, and downloads and installs it if requi…
Browse files Browse the repository at this point in the history
…red.

Issue #150
  • Loading branch information
pre-martin committed Oct 24, 2024
1 parent e487222 commit 5eddbf0
Show file tree
Hide file tree
Showing 6 changed files with 162 additions and 7 deletions.
5 changes: 5 additions & 0 deletions StreamDeckSimHub.Installer/Actions/AbstractInstallerAction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,11 @@ public async Task<ActionResult> Execute()

protected abstract Task<ActionResult> ExecuteInternal();

protected void LogInfo(string message)
{
_logger.Info(message);
}

protected void SetAndLogInfo(string message)
{
Message = message;
Expand Down
112 changes: 112 additions & 0 deletions StreamDeckSimHub.Installer/Actions/CheckDotnetRuntime.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
// Copyright (C) 2024 Martin Renner
// LGPL-3.0-or-later (see file COPYING and COPYING.LESSER)

using System;
using System.IO;
using System.Net;
using System.Security.Cryptography;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using StreamDeckSimHub.Installer.Tools;

namespace StreamDeckSimHub.Installer.Actions
{
public class CheckDotnetRuntime : AbstractInstallerAction
{
private readonly Regex _dotnetDesktop = new Regex(@"Microsoft.WindowsDesktop.App (\d+\.\d+\.\d+).*", RegexOptions.IgnoreCase);
private readonly Version _dotnetRequired = new Version(8, 0, 10);
private const string BaseUrl = "https://download.visualstudio.microsoft.com/download/pr/f398d462-9d4e-4b9c-abd3-86c54262869a/4a8e3a10ca0a9903a989578140ef0499/";
private const string InstallerName = "windowsdesktop-runtime-8.0.10-win-x64.exe";
private const string InstallerHash = "914fb306fb1308c59e293d86c75fc4cca2cc72163c2af3e6eed0a30bec0a54a8f95d22ec6084fd9e1579cb0576ffa0942f513b7b4c6b4c3a2bc942fe21f0461d";
private readonly string _installerFile = Path.Combine(Path.GetTempPath(), InstallerName);

public override string Name => "Checking .NET Desktop Runtime version";

protected override async Task<ActionResult> ExecuteInternal()
{
if (FindRuntime()) return ActionResult.Success;

if (!await DownloadRuntime()) return ActionResult.Error;

return Install() ? ActionResult.Success : ActionResult.Warning;
}

private bool FindRuntime()
{
try
{
var exitCode = ProcessTools.RunCommand($"dotnet --list-runtimes", out var output);
LogInfo($"\"dotnet --list-runtimes\" exited with code {exitCode}");
foreach (var line in output)
{
var match = _dotnetDesktop.Match(line);
if (match.Groups.Count >= 2)
{
var candidate = new Version(match.Groups[1].Value);
if (candidate >= _dotnetRequired)
{
SetAndLogInfo($"Found .NET Desktop Runtime version {candidate}");
return true;
}
}
}

SetAndLogInfo(".NET Desktop Runtime not found");
return false;
}
catch (Exception e)
{
SetAndLogError(e, "Failed to determine installed .NET Desktop Runtime");
return false;
}
}

private async Task<bool> DownloadRuntime()
{
try
{
SetAndLogInfo($"Downloading .NET Desktop Runtime {_dotnetRequired}");

var webClient = new WebClient();
await webClient.DownloadFileTaskAsync(BaseUrl + InstallerName, _installerFile);
using (var sha512 = SHA512.Create())
{
using (var fileStream = File.OpenRead(_installerFile))
{
var calculatedChecksum = sha512.ComputeHash(fileStream);
var calculatedChecksumString = BitConverter.ToString(calculatedChecksum).Replace("-", string.Empty).ToLowerInvariant();
if (calculatedChecksumString != InstallerHash)
{
SetAndLogError("Invalid checksum for downloaded file");
return false;
}
}
}

return true;
}
catch (Exception e)
{
SetAndLogError(e, "Failed to download .NET Desktop Runtime");
return false;
}
}

private bool Install()
{
SetAndLogInfo($"Installing .NET Desktop Runtime {_dotnetRequired}");
// see https://learn.microsoft.com/en-us/dotnet/core/install/windows#command-line-options
var exitCode = ProcessTools.RunCommand($"{_installerFile} /install /quiet /norestart", out var output);
LogInfo($"Installer exited with code {exitCode}");
File.Delete(_installerFile);
if (exitCode == 0 || exitCode == 3010)
{
SetAndLogInfo($"Installed .NET Desktop Runtime {_dotnetRequired}");
return true;
}

SetAndLogInfo($"Possible probleme while installing .NET Desktop Runtime {_dotnetRequired}: Exit code {exitCode}");
return false;
}
}
}
3 changes: 2 additions & 1 deletion StreamDeckSimHub.Installer/MainWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
xmlns:local="clr-namespace:StreamDeckSimHub.Installer"
xmlns:localActions="clr-namespace:StreamDeckSimHub.Installer.Actions"
mc:Ignorable="d"
Title="Stream Deck SimHub Plugin Installer" MinHeight="580" Width="600" SizeToContent="Height">
Title="Stream Deck SimHub Plugin Installer" MinHeight="620" Width="600" SizeToContent="Height">

<Window.DataContext>
<local:MainWindowViewModel />
Expand Down Expand Up @@ -37,6 +37,7 @@
<ItemsControl ItemsSource="{Binding InstallerSteps}">
<d:ItemsControl.ItemsSource>
<x:Array Type="{x:Type localActions:IInstallerAction}">
<localActions:CheckDotnetRuntime Message=".NET runtime is fine" ActionResultColor="{x:Static localActions:ActionColors.SuccessBrush}" />
<localActions:StopStreamDeckSoftware Message="Stream Deck software is not running. Stopping not required." ActionResultColor="{x:Static localActions:ActionColors.InactiveBrush}" />
<localActions:InstallStreamDeckPlugin Message="Installation successful." ActionResultColor="{x:Static localActions:ActionColors.SuccessBrush}" />
<localActions:StartStreamDeckSoftware Message="Some message." ActionResultColor="{x:Static localActions:ActionColors.WarningBrush}" />
Expand Down
28 changes: 22 additions & 6 deletions StreamDeckSimHub.Installer/MainWindowViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,18 @@
using System;
using System.Collections.ObjectModel;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using NLog;
using StreamDeckSimHub.Installer.Actions;

namespace StreamDeckSimHub.Installer
{
public class MainWindowViewModel : ObservableObject
{
private static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger();
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
private static readonly Brush SuccessBrush = Brushes.Green;
private static readonly Brush WarningBrush = Brushes.Orange;
private static readonly Brush ErrorBrush = Brushes.Red;
Expand All @@ -33,13 +35,27 @@ public class MainWindowViewModel : ObservableObject
public IAsyncRelayCommand InstallCommand => _installCommand ?? (_installCommand = new AsyncRelayCommand(Install));

private async Task Install()
{
// Execute installation in a new task to make the UI more responsive.
await Task.Run(InstallTask);
}

private async Task InstallTask()
{
ClearResultText();
InstallerSteps.Clear();
Application.Current.Dispatcher.Invoke(() => InstallerSteps.Clear());
Logger.Info("========== Starting installation ==========");

var checkDotnet = new CheckDotnetRuntime();
Application.Current.Dispatcher.Invoke(() => InstallerSteps.Add(checkDotnet));
if (await checkDotnet.Execute() == ActionResult.Error)
{
SetErrorResultText();
return;
}

var stopStreamDeck = new StopStreamDeckSoftware();
InstallerSteps.Add(stopStreamDeck);
Application.Current.Dispatcher.Invoke(() => InstallerSteps.Add(stopStreamDeck));
if (await stopStreamDeck.Execute() == ActionResult.Error)
{
SetErrorResultText();
Expand All @@ -48,15 +64,15 @@ private async Task Install()

var result = ActionResult.Success;
var installStreamDeckPlugin = new InstallStreamDeckPlugin();
InstallerSteps.Add(installStreamDeckPlugin);
Application.Current.Dispatcher.Invoke(() => InstallerSteps.Add(installStreamDeckPlugin));
result = SetHigherResultLevel(await installStreamDeckPlugin.Execute(), result);

var startStreamDeck = new StartStreamDeckSoftware();
InstallerSteps.Add(startStreamDeck);
Application.Current.Dispatcher.Invoke(() => InstallerSteps.Add(startStreamDeck));
result = SetHigherResultLevel(await startStreamDeck.Execute(), result);

var verifySimHubPlugin = new VerifySimHubPlugin();
InstallerSteps.Add(verifySimHubPlugin);
Application.Current.Dispatcher.Invoke(() => InstallerSteps.Add(verifySimHubPlugin));
result = SetHigherResultLevel(await verifySimHubPlugin.Execute(), result);

switch (result)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Compile Include="Actions\CheckDotnetRuntime.cs" />
<Compile Include="App.xaml.cs">
<DependentUpon>App.xaml</DependentUpon>
<SubType>Code</SubType>
Expand Down
20 changes: 20 additions & 0 deletions StreamDeckSimHub.Installer/Tools/ProcessTools.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (C) 2024 Martin Renner
// LGPL-3.0-or-later (see file COPYING and COPYING.LESSER)

using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
Expand Down Expand Up @@ -36,5 +37,24 @@ public static void StartProcess(string fileName, string workingDirectory = null)
process.StartInfo.UseShellExecute = true;
process.Start();
}

/// <summary>
/// Runs a command via <c>cmd.exe</c> and returns its exit code as well as its output as a string array - each line
/// one entry in the array.
/// </summary>
public static int RunCommand(string command, out string[] output)
{
var process = new Process();
process.StartInfo.FileName = "cmd.exe";
process.StartInfo.Arguments = $"/c {command}";
process.StartInfo.UseShellExecute = false;
process.StartInfo.CreateNoWindow = true;
process.StartInfo.RedirectStandardOutput = true;
process.Start();
var stdout = process.StandardOutput.ReadToEnd();
process.WaitForExit();
output = stdout.Split(Environment.NewLine.ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
return process.ExitCode;
}
}
}

0 comments on commit 5eddbf0

Please sign in to comment.