From 7a173ff33f3256a10986d1c6b077cc539ab892e6 Mon Sep 17 00:00:00 2001 From: Michael Suchacz Date: Tue, 1 Apr 2025 10:13:32 +0000 Subject: [PATCH 1/3] added windows app sdk fetching and bundling into the installer --- Installer/Program.cs | 42 ++++++++++++++++++++++++++++------- scripts/Get-WindowsAppSdk.ps1 | 34 ++++++++++++++++++++++++++++ scripts/Publish.ps1 | 5 +++++ scripts/files/.gitignore | 1 + 4 files changed, 74 insertions(+), 8 deletions(-) create mode 100644 scripts/Get-WindowsAppSdk.ps1 diff --git a/Installer/Program.cs b/Installer/Program.cs index 7945f5b..eeb65d5 100644 --- a/Installer/Program.cs +++ b/Installer/Program.cs @@ -116,6 +116,9 @@ public class BootstrapperOptions : SharedOptions [Option('m', "msi-path", Required = true, HelpText = "Path to the MSI package to embed")] public string MsiPath { get; set; } + [Option('w', "windows-app-sdk-path", Required = true, HelpText = "Path to the Windows App Sdk package to embed")] + public string WindowsAppSdkPath { get; set; } + public new void Validate() { base.Validate(); @@ -124,6 +127,8 @@ public class BootstrapperOptions : SharedOptions throw new ArgumentException($"Logo PNG file not found at '{LogoPng}'", nameof(LogoPng)); if (!SystemFile.Exists(MsiPath)) throw new ArgumentException($"MSI package not found at '{MsiPath}'", nameof(MsiPath)); + if (!SystemFile.Exists(WindowsAppSdkPath)) + throw new ArgumentException($"Windows App Sdk package not found at '{WindowsAppSdkPath}'", nameof(WindowsAppSdkPath)); } } @@ -337,16 +342,16 @@ private static int BuildBundle(BootstrapperOptions opts) { opts.Validate(); - if (!DotNetRuntimePackagePayloads.TryGetValue(opts.Platform, out var payload)) + if (!DotNetRuntimePackagePayloads.TryGetValue(opts.Platform, out var dotNetRuntimePayload)) throw new ArgumentException($"Invalid architecture '{opts.Platform}' specified", nameof(opts.Platform)); - // TODO: it would be nice to include the WindowsAppRuntime but - // Microsoft makes it difficult to check from a regular - // installer: - // https://learn.microsoft.com/en-us/windows/apps/windows-app-sdk/check-windows-app-sdk-versions - // https://github.com/microsoft/WindowsAppSDK/discussions/2437 + var windowsAppSdkPaylod = new ExePackagePayload + { + SourceFile = opts.WindowsAppSdkPath + }; + var bundle = new Bundle(ProductName, - new ExePackage + new ExePackage // .NET Runtime { PerMachine = true, // Don't uninstall the runtime when the bundle is uninstalled. @@ -362,7 +367,28 @@ private static int BuildBundle(BootstrapperOptions opts) // anyway. The MSI will fatally exit if the runtime really isn't // available, and the user can install it themselves. Vital = false, - Payloads = [payload], + Payloads = [dotNetRuntimePayload], + }, + // TODO: right now we are including the Windows App Sdk in the bundle + // and always install it + // Microsoft makes it difficult to check if it exists from a regular installer: + // https://learn.microsoft.com/en-us/windows/apps/windows-app-sdk/check-windows-app-sdk-versions + // https://github.com/microsoft/WindowsAppSDK/discussions/2437 + new ExePackage // Windows App Sdk + { + PerMachine = true, + Permanent = true, + Cache = PackageCacheAction.remove, + // There is no license agreement for this SDK. + InstallArguments = "--quiet", + Vital = false, + Payloads = + [ + new ExePackagePayload + { + SourceFile = opts.WindowsAppSdkPath + } + ], }, new MsiPackage(opts.MsiPath) { diff --git a/scripts/Get-WindowsAppSdk.ps1 b/scripts/Get-WindowsAppSdk.ps1 new file mode 100644 index 0000000..a9ca02a --- /dev/null +++ b/scripts/Get-WindowsAppSdk.ps1 @@ -0,0 +1,34 @@ +# Usage: Get-WindowsAppSdk.ps1 -arch +param ( + [ValidateSet("x64", "arm64")] + [Parameter(Mandatory = $true)] + [string] $arch +) + +function Download-File([string] $url, [string] $outputPath, [string] $etagFile) { + Write-Host "Downloading '$url' to '$outputPath'" + # We use `curl.exe` here because `Invoke-WebRequest` is notoriously slow. + & curl.exe ` + --progress-bar ` + -v ` + --show-error ` + --fail ` + --location ` + --etag-compare $etagFile ` + --etag-save $etagFile ` + --output $outputPath ` + $url + if ($LASTEXITCODE -ne 0) { throw "Failed to download $url" } + if (!(Test-Path $outputPath) -or (Get-Item $outputPath).Length -eq 0) { + throw "Failed to download '$url', output file '$outputPath' is missing or empty" + } +} + +# Download the Windows App Sdk binary from Microsoft for this platform if we don't have +# it yet (or it's different). +$windowsAppSdkMajorVersion = "1.6" +$windowsAppSdkFullVersion = "1.6.250228001" +$windowsAppSdkPath = Join-Path $PSScriptRoot "files\windows-app-sdk-$($arch).exe" +$windowsAppSdkUri = "https://aka.ms/windowsappsdk/$($windowsAppSdkMajorVersion)/$($windowsAppSdkFullVersion)/windowsappruntimeinstall-$($arch).exe" +$windowsAppSdkEtagFile = $windowsAppSdkPath + ".etag" +Download-File $windowsAppSdkUri $windowsAppSdkPath $windowsAppSdkEtagFile \ No newline at end of file diff --git a/scripts/Publish.ps1 b/scripts/Publish.ps1 index dccb39f..4390dfa 100644 --- a/scripts/Publish.ps1 +++ b/scripts/Publish.ps1 @@ -175,6 +175,10 @@ Copy-Item $mutagenAgentsSrcPath $mutagenAgentsDestPath if ($LASTEXITCODE -ne 0) { throw "Failed to build MSI" } Add-CoderSignature $msiOutputPath +$getWindowsAppSdk = Join-Path $scriptRoot "Get-WindowsAppSdk.ps1" +& $getWindowsAppSdk -arch $arch +$windowsAppSdkPath = Join-Path $scriptRoot "files\windows-app-sdk-$($arch).exe" + # Build the bootstrapper & dotnet.exe run --project .\Installer\Installer.csproj -c Release -- ` build-bootstrapper ` @@ -184,6 +188,7 @@ Add-CoderSignature $msiOutputPath --output-path $outputPath ` --icon-file "App\coder.ico" ` --msi-path $msiOutputPath ` + --windows-app-sdk-path $windowsAppSdkPath ` --logo-png "scripts\files\logo.png" if ($LASTEXITCODE -ne 0) { throw "Failed to build bootstrapper" } diff --git a/scripts/files/.gitignore b/scripts/files/.gitignore index 859c764..9080d92 100644 --- a/scripts/files/.gitignore +++ b/scripts/files/.gitignore @@ -1,3 +1,4 @@ mutagen-*.tar.gz mutagen-*.exe *.etag +windows-app-sdk-*.exe \ No newline at end of file From 021e29c1107ed73ec5c374df32edfba589e86250 Mon Sep 17 00:00:00 2001 From: Michael Suchacz Date: Tue, 1 Apr 2025 10:21:20 +0000 Subject: [PATCH 2/3] formatted Installer's Program.cs --- Installer/Program.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Installer/Program.cs b/Installer/Program.cs index eeb65d5..092cc93 100644 --- a/Installer/Program.cs +++ b/Installer/Program.cs @@ -382,9 +382,9 @@ private static int BuildBundle(BootstrapperOptions opts) // There is no license agreement for this SDK. InstallArguments = "--quiet", Vital = false, - Payloads = + Payloads = [ - new ExePackagePayload + new ExePackagePayload { SourceFile = opts.WindowsAppSdkPath } From 90d5fece965b89ac3b9eef9bd9ca8b39cefa2955 Mon Sep 17 00:00:00 2001 From: Michael Suchacz Date: Tue, 1 Apr 2025 10:30:54 +0000 Subject: [PATCH 3/3] removed unused declaration --- Installer/Program.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Installer/Program.cs b/Installer/Program.cs index 092cc93..78965e4 100644 --- a/Installer/Program.cs +++ b/Installer/Program.cs @@ -345,11 +345,6 @@ private static int BuildBundle(BootstrapperOptions opts) if (!DotNetRuntimePackagePayloads.TryGetValue(opts.Platform, out var dotNetRuntimePayload)) throw new ArgumentException($"Invalid architecture '{opts.Platform}' specified", nameof(opts.Platform)); - var windowsAppSdkPaylod = new ExePackagePayload - { - SourceFile = opts.WindowsAppSdkPath - }; - var bundle = new Bundle(ProductName, new ExePackage // .NET Runtime {