From fa2b9ecb19c3554b07d0e9aa7645cbb747776b47 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 3 Aug 2026 17:17:06 +0000 Subject: [PATCH 1/2] Initial plan From 2ca77152735ffb664a7577305bd4d18171bbeaf1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 3 Aug 2026 17:41:47 +0000 Subject: [PATCH 2/2] Locate sdk-manifests in MSI admin image regardless of WiX layout Co-authored-by: marcpopMSFT <12663534+marcpopMSFT@users.noreply.github.com> --- .../Install/WindowsMsiManifestInstaller.cs | 45 +++++++++++++++---- .../GivenAWindowsMsiManifestInstaller.cs | 32 +++++++++++++ 2 files changed, 69 insertions(+), 8 deletions(-) diff --git a/src/Cli/dotnet/Commands/Workload/Install/WindowsMsiManifestInstaller.cs b/src/Cli/dotnet/Commands/Workload/Install/WindowsMsiManifestInstaller.cs index a852b715f8ba..48ecd1ec70eb 100644 --- a/src/Cli/dotnet/Commands/Workload/Install/WindowsMsiManifestInstaller.cs +++ b/src/Cli/dotnet/Commands/Workload/Install/WindowsMsiManifestInstaller.cs @@ -129,14 +129,7 @@ public async Task ExtractManifestAsync(string nupkgPath, string targetPath) } } - var manifestsFolder = Path.Combine(msiExtractionPath, "dotnet", "sdk-manifests"); - - string? manifestFolder = null; - string? manifestsFeatureBandFolder = Directory.GetDirectories(manifestsFolder).SingleOrDefault(); - if (manifestsFeatureBandFolder != null) - { - manifestFolder = Directory.GetDirectories(manifestsFeatureBandFolder).SingleOrDefault(); - } + string? manifestFolder = FindExtractedManifestFolder(msiExtractionPath); if (manifestFolder == null) { @@ -155,6 +148,42 @@ public async Task ExtractManifestAsync(string nupkgPath, string targetPath) } } + /// + /// Locates the extracted manifest directory in the layout produced by the administrative install of a + /// manifest or workload set MSI. + /// + /// + /// The MSI lays its content down under <Program Files>\dotnet\sdk-manifests\<feature band>\..., + /// but the name of the Program Files directory in the administrative image depends on the WiX version that + /// built the MSI: WiX v3 collapsed it into the target directory (<target>\dotnet\sdk-manifests), + /// while WiX v4+ emits a named directory for it (for example <target>\PFiles64\dotnet\sdk-manifests). + /// Search for the sdk-manifests directory instead of assuming a fixed depth so that both layouts work. + /// + /// + /// The directory the MSI was administratively installed to. + /// The extracted manifest directory, or if it could not be located. + internal static string? FindExtractedManifestFolder(string msiExtractionPath) + { + if (!Directory.Exists(msiExtractionPath)) + { + return null; + } + + string? manifestsFolder = Directory.EnumerateDirectories(msiExtractionPath, "sdk-manifests", SearchOption.AllDirectories).FirstOrDefault(); + if (manifestsFolder == null) + { + return null; + } + + string? manifestsFeatureBandFolder = Directory.GetDirectories(manifestsFolder).SingleOrDefault(); + if (manifestsFeatureBandFolder == null) + { + return null; + } + + return Directory.GetDirectories(manifestsFeatureBandFolder).SingleOrDefault(); + } + /// /// Equivalent to . /// diff --git a/test/dotnet.Tests/CommandTests/Workload/Install/GivenAWindowsMsiManifestInstaller.cs b/test/dotnet.Tests/CommandTests/Workload/Install/GivenAWindowsMsiManifestInstaller.cs index fd4822a3cd6a..a541731d63e6 100644 --- a/test/dotnet.Tests/CommandTests/Workload/Install/GivenAWindowsMsiManifestInstaller.cs +++ b/test/dotnet.Tests/CommandTests/Workload/Install/GivenAWindowsMsiManifestInstaller.cs @@ -39,4 +39,36 @@ public void GetManifestPackageIdReturnsTheArchitectureQualifiedManifestPackageId packageId.ToString().Should().Be( $"{manifestId}.Manifest-{featureBand}.Msi.{RuntimeInformation.ProcessArchitecture}".ToLowerInvariant()); } + + // MSIs built with WiX v3 collapse the Program Files directory into the administrative install target. + [TestMethod] + public void FindExtractedManifestFolderLocatesTheManifestInTheWiXV3AdminInstallLayout() + { + var testDirectory = TestAssetsManager.CreateTestDirectory().Path; + var expected = Path.Combine(testDirectory, "dotnet", "sdk-manifests", "6.0.100", "test.manifest"); + Directory.CreateDirectory(expected); + + WindowsMsiManifestInstaller.FindExtractedManifestFolder(testDirectory).Should().Be(expected); + } + + // MSIs built with WiX v4 and newer emit a named directory for Program Files in the administrative image. + [TestMethod] + public void FindExtractedManifestFolderLocatesTheManifestInTheWiXV4AdminInstallLayout() + { + var testDirectory = TestAssetsManager.CreateTestDirectory().Path; + var expected = Path.Combine(testDirectory, "PFiles64", "dotnet", "sdk-manifests", "6.0.100", "workloadsets"); + Directory.CreateDirectory(expected); + + WindowsMsiManifestInstaller.FindExtractedManifestFolder(testDirectory).Should().Be(expected); + } + + [TestMethod] + public void FindExtractedManifestFolderReturnsNullWhenThereIsNoManifest() + { + var testDirectory = TestAssetsManager.CreateTestDirectory().Path; + Directory.CreateDirectory(Path.Combine(testDirectory, "PFiles64", "dotnet")); + + WindowsMsiManifestInstaller.FindExtractedManifestFolder(testDirectory).Should().BeNull(); + WindowsMsiManifestInstaller.FindExtractedManifestFolder(Path.Combine(testDirectory, "does-not-exist")).Should().BeNull(); + } }