File: MsiTests.cs
Web Access
Project: src\src\Microsoft.DotNet.Build.Tasks.Workloads.Tests\Microsoft.DotNet.Build.Tasks.Workloads.Tests.csproj (Microsoft.DotNet.Build.Tasks.Workloads.Tests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System;
using System.IO;
using System.Linq;
using FluentAssertions;
using Microsoft.Arcade.Test.Common;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using Microsoft.Deployment.WindowsInstaller;
using Microsoft.DotNet.Build.Tasks.Workloads.Msi;
using Microsoft.NET.Sdk.WorkloadManifestReader;
using Xunit;
 
namespace Microsoft.DotNet.Build.Tasks.Workloads.Tests
{
    [Collection("6.0.200 Toolchain manifest tests")]
    public class MsiTests : TestBase
    {
        private static ITaskItem BuildManifestMsi(string path, string msiVersion = "1.2.3", string platform = "x64", string msiOutputPath = null)
        {
            TaskItem packageItem = new(path);
            WorkloadManifestPackage pkg = new(packageItem, PackageRootDirectory, new Version(msiVersion));
            pkg.Extract();
            WorkloadManifestMsi msi = new(pkg, platform, new MockBuildEngine(), WixToolsetPath, BaseIntermediateOutputPath,
                isSxS: true);
            return string.IsNullOrWhiteSpace(msiOutputPath) ? msi.Build(MsiOutputPath) : msi.Build(msiOutputPath);
        }
 
        [WindowsOnlyFact]
        public void WorkloadManifestsIncludeInstallationRecords()
        {
            ITaskItem msi603 = BuildManifestMsi(Path.Combine(TestAssetsPath, "microsoft.net.workload.mono.toolchain.manifest-6.0.200.6.0.3.nupkg"), 
                msiOutputPath: Path.Combine(MsiOutputPath, "mrec"));
            string msiPath603 = msi603.GetMetadata(Metadata.FullPath);
 
            MsiUtils.GetAllRegistryKeys(msiPath603).Should().Contain(r =>
              r.Key == @"SOFTWARE\Microsoft\dotnet\InstalledManifests\x64\Microsoft.NET.Workload.Mono.ToolChain.Manifest-6.0.200\6.0.3"
            );
        }
 
        [WindowsOnlyFact]
        public void ItCanBuildSideBySideManifestMsis()
        {
            string PackageRootDirectory = Path.Combine(BaseIntermediateOutputPath, "pkg");
 
            // Build 6.0.200 manifest for version 6.0.3
            ITaskItem msi603 = BuildManifestMsi(Path.Combine(TestAssetsPath, "microsoft.net.workload.mono.toolchain.manifest-6.0.200.6.0.3.nupkg"));
            string msiPath603 = msi603.GetMetadata(Metadata.FullPath);
 
            // Build 6.0.200 manifest for version 6.0.4
            ITaskItem msi604 = BuildManifestMsi(Path.Combine(TestAssetsPath, "microsoft.net.workload.mono.toolchain.manifest-6.0.200.6.0.4.nupkg"));
            string msiPath604 = msi604.GetMetadata(Metadata.FullPath);
 
            // For upgradable MSIs, the 6.0.4 and 6.0.3 copies of the package would have generated the same
            // upgrade code to ensure upgrades along the manifest feature band. For SxS they should be different.
            Assert.NotEqual(MsiUtils.GetProperty(msiPath603, MsiProperty.UpgradeCode), MsiUtils.GetProperty(msiPath604, MsiProperty.UpgradeCode));
 
            // Provider keys for SxS MSIs should be different while upgrade related MSIs should have stable provider keys.
            Assert.Equal("Microsoft.NET.Workload.Mono.ToolChain,6.0.200,6.0.3,x64", MsiUtils.GetProviderKeyName(msiPath603));
            Assert.Equal("Microsoft.NET.Workload.Mono.ToolChain,6.0.200,6.0.4,x64", MsiUtils.GetProviderKeyName(msiPath604));
 
            // WiX populates the DefaultDir column using "short name | long name" pairs.
            MsiUtils.GetAllDirectories(msiPath603).Should().Contain(d =>
                d.Directory == "ManifestVersionDir" &&
                d.DirectoryParent == "ManifestIdDir" &&
                d.DefaultDir.EndsWith("|6.0.3"));
            MsiUtils.GetAllDirectories(msiPath604).Should().Contain(d =>
                d.Directory == "ManifestVersionDir" &&
                d.DirectoryParent == "ManifestIdDir" &&
                d.DefaultDir.EndsWith("|6.0.4"));
 
            // Generated MSI should return the path where the .wixobj files are located so
            // WiX packs can be created for post-build signing.
            Assert.NotNull(msi603.GetMetadata(Metadata.WixObj));
            Assert.NotNull(msi604.GetMetadata(Metadata.WixObj));
        }
 
        [WindowsOnlyFact]
        public void ItCanBuildAManifestMsi()
        {
            string PackageRootDirectory = Path.Combine(BaseIntermediateOutputPath, "pkg");
            TaskItem packageItem = new(Path.Combine(TestAssetsPath, "microsoft.net.workload.mono.toolchain.manifest-6.0.200.6.0.3.nupkg"));
            WorkloadManifestPackage pkg = new(packageItem, PackageRootDirectory, new Version("1.2.3"));
            pkg.Extract();
            WorkloadManifestMsi msi = new(pkg, "x64", new MockBuildEngine(), WixToolsetPath, BaseIntermediateOutputPath);
 
            ITaskItem item = msi.Build(MsiOutputPath);
 
            string msiPath = item.GetMetadata(Metadata.FullPath);
 
            // Process the summary information stream's template to extract the MSIs target platform.
            using SummaryInfo si = new(msiPath, enableWrite: false);
 
            // UpgradeCode is predictable/stable for manifest MSIs.
            Assert.Equal("{E4761192-882D-38E9-A3F4-14B6C4AD12BD}", MsiUtils.GetProperty(msiPath, MsiProperty.UpgradeCode));
            Assert.Equal("1.2.3", MsiUtils.GetProperty(msiPath, MsiProperty.ProductVersion));
            Assert.Equal("Microsoft.NET.Workload.Mono.ToolChain,6.0.200,x64", MsiUtils.GetProviderKeyName(msiPath));
            Assert.Equal("x64;1033", si.Template);
 
            // There should be no version directory present if the old upgrade model is used.
            MsiUtils.GetAllDirectories(msiPath).Select(d => d.Directory).Should().NotContain("ManifestVersionDir");
 
            // Generated MSI should return the path where the .wixobj files are located so
            // WiX packs can be created for post-build signing.
            Assert.NotNull(item.GetMetadata(Metadata.WixObj));
        }
 
        [WindowsOnlyFact]
        public void ItCanBuildATemplatePackMsi()
        {
            string PackageRootDirectory = Path.Combine(BaseIntermediateOutputPath, "pkg");
            string packagePath = Path.Combine(TestAssetsPath, "microsoft.ios.templates.15.2.302-preview.14.122.nupkg");
 
            WorkloadPack p = new(new WorkloadPackId("Microsoft.iOS.Templates"), "15.2.302-preview.14.122", WorkloadPackKind.Template, null);
            TemplatePackPackage pkg = new(p, packagePath, new[] { "x64" }, PackageRootDirectory);
            pkg.Extract();
            var buildEngine = new MockBuildEngine();
            WorkloadPackMsi msi = new(pkg, "x64", buildEngine, WixToolsetPath, BaseIntermediateOutputPath);
            ITaskItem item = msi.Build(MsiOutputPath);
 
            string msiPath = item.GetMetadata(Metadata.FullPath);
 
            // Process the summary information stream's template to extract the MSIs target platform.
            using SummaryInfo si = new(msiPath, enableWrite: false);
 
            // UpgradeCode is predictable/stable for pack MSIs since they are seeded using the package identity (ID & version).
            Assert.Equal("{EC4D6B34-C9DE-3984-97FD-B7AC96FA536A}", MsiUtils.GetProperty(msiPath, MsiProperty.UpgradeCode));
            // The version is set using the package major.minor.patch
            Assert.Equal("15.2.302.0", MsiUtils.GetProperty(msiPath, MsiProperty.ProductVersion));
            Assert.Equal("Microsoft.iOS.Templates,15.2.302-preview.14.122,x64", MsiUtils.GetProviderKeyName(msiPath));
            Assert.Equal("x64;1033", si.Template);
 
            // Template packs should pull in the raw nupkg. We can verify by query the File table. There should
            // only be a single file.
            FileRow fileRow = MsiUtils.GetAllFiles(msiPath).FirstOrDefault();
            Assert.Contains("microsoft.ios.templates.15.2.302-preview.14.122.nupk", fileRow.FileName);
 
            // Generated MSI should return the path where the .wixobj files are located so
            // WiX packs can be created for post-build signing.
            Assert.NotNull(item.GetMetadata(Metadata.WixObj));
        }
    }
}