Skip to content

Commit

Permalink
Merge pull request #373 from HotCakeX/implementing-SignTool-download-…
Browse files Browse the repository at this point in the history
…natively

SignTool downloader and better self-updater
  • Loading branch information
HotCakeX authored Oct 27, 2024
2 parents 4610453 + a4620eb commit 797dce7
Show file tree
Hide file tree
Showing 16 changed files with 905 additions and 297 deletions.
12 changes: 4 additions & 8 deletions AppControl Manager/Pages/Update.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -63,16 +63,12 @@

<controls:WrapPanel Orientation="Horizontal">

<ProgressRing Visibility="Collapsed" x:Name="DownloadProgressRingForMSIXFile" Width="60" Height="60" Margin="5,5,15,5" Value="0" IsIndeterminate="False" Minimum="0" Maximum="100"/>

<Button x:Name="CheckForUpdateButton" Content="Check for update"
Style="{StaticResource AccentButtonStyle}" Margin="0,0,15,0"
Click="CheckForUpdateButton_Click"
PointerEntered="CheckForUpdateButton_PointerEntered"
PointerExited="CheckForUpdateButton_PointerExited"
PointerPressed="CheckForUpdateButton_PointerPressed"/>

<TeachingTip x:Name="CheckForUpdateButtonTeachingTip"
Target="{x:Bind CheckForUpdateButton}"
Subtitle="Will download and install the latest version from the GitHub repository. The time it takes depends on your network connection. Approxmitate download size is 140 MB." />
Click="CheckForUpdateButton_Click"
ToolTipService.ToolTip="Will download and install the latest version from the GitHub repository. The time it takes depends on your network connection. Approxmitate download size is 140 MB."/>

</controls:WrapPanel>

Expand Down
369 changes: 323 additions & 46 deletions AppControl Manager/Pages/Update.xaml.cs

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion AppControl Manager/Shared Logics/ConfigureISGServices.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
namespace WDACConfig
{
internal static class ConfigureISGServices
public static class ConfigureISGServices
{
/// <summary>
/// Starts the AppIdTel and sets the appidsvc service to auto start
Expand Down
204 changes: 204 additions & 0 deletions AppControl Manager/Shared Logics/SignToolHelper.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
using System;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Net.Http;
using System.Runtime.InteropServices;
using System.Text.Json;

#nullable enable

Expand Down Expand Up @@ -64,5 +69,204 @@ public static void Sign(FileInfo ciPath, FileInfo signToolPathFinal, string cert
throw new InvalidOperationException($"SignTool failed with exit code {process.ExitCode}. Error: {error}");
}
}


/// <summary>
/// Downloads the latest version of the Microsoft.Windows.SDK.BuildTools NuGet package.
/// Extracts the SignTool.exe from it and returns the path to it.
/// Copies it to the User Configurations directory.
/// </summary>
private static string Download()
{
DirectoryInfo stagingArea = StagingArea.NewStagingArea("GetSignTool");

using HttpClient client = new();

string packageName = "Microsoft.Windows.SDK.BuildTools";

Logger.Write("Finding the latest version of the Microsoft.Windows.SDK.BuildTools package from NuGet");

// Get the list of versions
string versionsUrl = $"https://api.nuget.org/v3-flatcontainer/{packageName}/index.json";
string versionsResponse = client.GetStringAsync(versionsUrl).GetAwaiter().GetResult();

// Parse the JSON to get the latest version
JsonDocument versionsJson = JsonDocument.Parse(versionsResponse);
JsonElement versions = versionsJson.RootElement.GetProperty("versions");
string? latestVersion = versions[versions.GetArrayLength() - 1].GetString() ?? throw new InvalidOperationException("Failed to get the latest version of the package.");

// Construct the download link for the latest version's .nupkg
string downloadUrl = $"https://api.nuget.org/v3-flatcontainer/{packageName}/{latestVersion}/{packageName}.{latestVersion}.nupkg";

Logger.Write($"Downloading the latest .nupkg package file version '{latestVersion}' from the following URL: {downloadUrl}");

// Download the .nupkg file
string filePath = Path.Combine(stagingArea.FullName, $"{packageName}.{latestVersion}.nupkg");
using (Stream downloadStream = client.GetStreamAsync(downloadUrl).GetAwaiter().GetResult())
using (FileStream fileStream = new(filePath, FileMode.Create, FileAccess.Write, FileShare.None))
{
downloadStream.CopyTo(fileStream);
}

Logger.Write($"Downloaded package to {filePath}");

// Extract the .nupkg file
string extractPath = Path.Combine(stagingArea.FullName, "extracted");
ZipFile.ExtractToDirectory(filePath, extractPath);

Logger.Write($"Extracted package to {extractPath}");


string binDirectoryPath = Path.Combine(extractPath, "bin");
// Get the directory that has the version, since it varies we need to get it implicitly
string[] versionDirectories = Directory.GetDirectories(binDirectoryPath);

if (versionDirectories.Length == 0)
{
throw new DirectoryNotFoundException("No version directories found in 'bin'.");
}

// There should be only one
string versionDirectory = versionDirectories.First();
string signtoolPath = Path.Combine(versionDirectory, "x64", "signtool.exe");

if (!File.Exists(signtoolPath))
{
throw new FileNotFoundException("signtool.exe not found in the expected path.");
}

// The final path that is in the User configurations directory and will be returned and saved in User configs
string finalSignToolPath = Path.Combine(GlobalVars.UserConfigDir, "SignTool.exe");

File.Copy(signtoolPath, finalSignToolPath, true);

Directory.Delete(stagingArea.ToString(), true);

Logger.Write($"Path to signtool.exe: {finalSignToolPath}");

return finalSignToolPath;

}


/// <summary>
/// Verifies if the SignTool.exe is of a version greater than one specified in the method
/// </summary>
/// <param name="filePath"></param>
/// <returns></returns>
private static bool Verify(string filePath)
{
try
{
FileVersionInfo fileInfo = FileVersionInfo.GetVersionInfo(filePath);
return (new Version(fileInfo.ProductVersion!) > new Version("10.0.22621.2428"));
}
catch
{
return false;
}
}



/// <summary>
/// Returns the architecture of the current OS
/// </summary>
/// <returns></returns>
/// <exception cref="InvalidOperationException"></exception>
private static string GetArchitecture()
{
if (RuntimeInformation.OSArchitecture is Architecture.X64)
{
return "x64";
}
else if (RuntimeInformation.OSArchitecture is Architecture.Arm64)
{
return "arm64";
}
else
{
throw new InvalidOperationException("Only X64 and ARM64 platforms are supported.");
}
}



/// <summary>
/// Gets the path to SignTool.exe and verifies it to make sure it's valid
/// If the SignTool.exe path is not provided by parameter, it will try to detect it automatically by checking if Windows SDK is installed
/// If the SignTool.exe path is not provided by parameter and it could not be detected automatically, it will try to download it from NuGet
/// </summary>
/// <param name="filePath"></param>
/// <returns></returns>
public static string GetSignToolPath(string? filePath = null)
{
// The path to return at the end
string? signToolPath = null;

// If Sign tool path wasn't provided by parameter or it doesn't exist on the file system, try to detect it automatically
if (string.IsNullOrWhiteSpace(filePath) && !Path.Exists(filePath))
{

try
{

Logger.Write("SignTool.exe path was not provided by parameter, trying to detect it automatically");

string baseDir = @"C:\Program Files (x86)\Windows Kits\10\bin";
string targetArchitecture = GetArchitecture();

// Get the directory with the highest version in its name
string? latestSigntoolPath = Directory.GetDirectories(baseDir)
.Select(dir => new DirectoryInfo(dir))
.Where(dir => Version.TryParse(Path.GetFileName(dir.Name), out _)) // Ensure directory is a version
.OrderByDescending(dir => new Version(dir.Name)) // Order by version
.First().ToString(); // Get the highest version

if (latestSigntoolPath is not null)
{
// Construct the full SignTool.exe path
string constructedFinalPath = Path.Combine(latestSigntoolPath, targetArchitecture, "signtool.exe");

// If it checks out, assign it to the output variable
if (Verify(constructedFinalPath))
{
signToolPath = constructedFinalPath;
Logger.Write($"Successfully detected the SignTool.exe on the system: {constructedFinalPath}");
}
}

}
catch (Exception ex)
{
Logger.Write($"Failed to detect SignTool.exe path automatically: {ex.Message}");
}

}

// If Sign tool path was provided by parameter, use it
else
{
Logger.Write("SignTool.exe path was provided by parameter");

if (Verify(filePath))
{
Logger.Write("The provided SignTool.exe is valid");
signToolPath = filePath;
}
else
{
Logger.Write("The provided SignTool.exe is not valid");
}
}

// Download the SignTool.exe if it's still null
signToolPath ??= Download();

Logger.Write($"Setting the SignTool path in the common user configurations to: {signToolPath}");
_ = UserConfiguration.Set(SignToolCustomPath: signToolPath);

return signToolPath;
}
}
}
6 changes: 6 additions & 0 deletions AppControl Manager/Shared Logics/StagingArea.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ namespace WDACConfig
{
public static class StagingArea
{
/// <summary>
/// Creating a directory as a staging area for a job and returns the path to that directory
/// </summary>
/// <param name="cmdletName"></param>
/// <returns></returns>
/// <exception cref="ArgumentException"></exception>
public static DirectoryInfo NewStagingArea(string cmdletName)
{
if (string.IsNullOrWhiteSpace(cmdletName))
Expand Down
Loading

0 comments on commit 797dce7

Please sign in to comment.