winget-cli

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

RepairInterop.cs (18408B)


      1 // -----------------------------------------------------------------------------
      2 // <copyright file="RepairInterop.cs" company="Microsoft Corporation">
      3 //     Copyright (c) Microsoft Corporation. Licensed under the MIT License.
      4 // </copyright>
      5 // -----------------------------------------------------------------------------
      6 
      7 namespace AppInstallerCLIE2ETests.Interop
      8 {
      9     using System;
     10     using System.IO;
     11     using System.Linq;
     12     using System.Text;
     13     using System.Threading.Tasks;
     14     using AppInstallerCLIE2ETests.Helpers;
     15     using Microsoft.Management.Deployment;
     16     using Microsoft.Management.Deployment.Projection;
     17     using NUnit.Framework;
     18 
     19     /// <summary>
     20     /// Repair Interop Tests for COM/WinRT Interop classes.
     21     /// </summary>
     22     [TestFixtureSource(typeof(InstanceInitializersSource), nameof(InstanceInitializersSource.InProcess), Category = nameof(InstanceInitializersSource.InProcess))]
     23     [TestFixtureSource(typeof(InstanceInitializersSource), nameof(InstanceInitializersSource.OutOfProcess), Category = nameof(InstanceInitializersSource.OutOfProcess))]
     24     public class RepairInterop : BaseInterop
     25     {
     26         private string installDir;
     27         private PackageManager packageManager;
     28         private PackageCatalogReference compositeSource;
     29 
     30         /// <summary>
     31         /// Initializes a new instance of the <see cref="RepairInterop"/> class.
     32         /// </summary>
     33         /// <param name="initializer">Initializer.</param>
     34         public RepairInterop(IInstanceInitializer initializer)
     35             : base(initializer)
     36         {
     37         }
     38 
     39         /// <summary>
     40         /// Test setup.
     41         /// </summary>
     42         [SetUp]
     43         public void Init()
     44         {
     45             this.packageManager = this.TestFactory.CreatePackageManager();
     46             this.installDir = TestCommon.GetRandomTestDir();
     47 
     48             // Create a composite source
     49             var options = this.TestFactory.CreateCreateCompositePackageCatalogOptions();
     50             var testSource = this.packageManager.GetPackageCatalogByName(Constants.TestSourceName);
     51             Assert.NotNull(testSource, $"{Constants.TestSourceName} cannot be null");
     52             options.Catalogs.Add(testSource);
     53             options.CompositeSearchBehavior = CompositeSearchBehavior.AllCatalogs;
     54             this.compositeSource = this.packageManager.CreateCompositePackageCatalog(options);
     55         }
     56 
     57         /// <summary>
     58         /// Test Repair MSI Installer.
     59         /// </summary>
     60         /// <returns>A <see cref="Task"/> representing the asynchronous unit test. </returns>
     61         [Test]
     62         public async Task RepairMSIInstaller()
     63         {
     64             string msiInstallerPackageId = "AppInstallerTest.TestMsiRepair";
     65             var searchResult = await this.FindAndInstallPackage(msiInstallerPackageId, this.installDir, null);
     66 
     67             // Note: The 'msiexec repair' command requires the original installer file to be present at the location registered in the ARP (Add/Remove Programs).
     68             // In our test scenario, the MSI installer file is initially placed in a temporary location and then deleted, which can cause the repair operation to fail.
     69             // To work around this, we copy the installer file to the ARP source directory before running the repair command.
     70             // A more permanent solution would be to modify the MSI installer to cache the installer file in a known location and register that location as the installer source.
     71             // This would allow the 'msiexec repair' command to function as expected.
     72             string installerSourceDir = TestCommon.CopyInstallerFileToARPInstallSourceDirectory(TestCommon.GetTestDataFile("AppInstallerTestMsiInstallerV2.msi"), Constants.MsiInstallerProductCode, true);
     73 
     74             // Repair the package
     75             await this.RepairPackageAndValidateStatus(searchResult.CatalogPackage, this.TestFactory.CreateRepairOptions(), RepairResultStatus.Ok);
     76 
     77             // Cleanup
     78             Assert.True(TestCommon.VerifyTestMsiInstalledAndCleanup(this.installDir));
     79 
     80             if (installerSourceDir != null && Directory.Exists(installerSourceDir))
     81             {
     82                 Directory.Delete(installerSourceDir, true);
     83             }
     84         }
     85 
     86         /// <summary>
     87         /// Test Repair MSIX Installer.
     88         /// </summary>
     89         /// <returns>representing the asynchronous unit test.</returns>
     90         [Test]
     91         public async Task RepairNonStoreMSIXPackage()
     92         {
     93             string msixPackageId = "AppInstallerTest.TestMsixInstaller";
     94             var searchResult = await this.FindAndInstallPackage(msixPackageId, this.installDir, null);
     95 
     96             // Repair the package
     97             await this.RepairPackageAndValidateStatus(searchResult.CatalogPackage, this.TestFactory.CreateRepairOptions(), RepairResultStatus.Ok);
     98 
     99             // Cleanup
    100             Assert.True(TestCommon.VerifyTestMsixInstalledAndCleanup());
    101         }
    102 
    103         /// <summary>
    104         /// Test MSIX non-store package repair with machine scope.
    105         /// </summary>
    106         /// <returns>representing the asynchronous unit test.</returns>
    107         [Test]
    108         public async Task RepairNonStoreMsixPackageWithMachineScope()
    109         {
    110             var findPackages = this.FindAllPackages(this.compositeSource, PackageMatchField.Id, PackageFieldMatchOption.ContainsCaseInsensitive, "Microsoft.Paint");
    111 
    112             if (findPackages == null || findPackages.Count == 0)
    113             {
    114                 Assert.Ignore("Test skipped as Microsoft.Paint_8wekyb3d8bbwe can't be found.");
    115             }
    116 
    117             var searchResult = findPackages.First();
    118 
    119             if (searchResult == null || searchResult.CatalogPackage == null || searchResult.CatalogPackage.InstalledVersion == null)
    120             {
    121                 Assert.Ignore("Test skipped as Microsoft.Paint_8wekyb3d8bbwe is not installed.");
    122             }
    123 
    124             // Repair the package
    125             var repairOptions = this.TestFactory.CreateRepairOptions();
    126             repairOptions.PackageRepairScope = PackageRepairScope.System;
    127 
    128             await this.RepairPackageAndValidateStatus(searchResult.CatalogPackage, repairOptions, RepairResultStatus.RepairError, Constants.ErrorCode.ERROR_INSTALL_SYSTEM_NOT_SUPPORTED);
    129         }
    130 
    131         /// <summary>
    132         /// Test repair of a Burn installer that has a "modify" repair behavior specified in the manifest.
    133         /// </summary>
    134         /// <returns>representing the asynchronous unit test.</returns>
    135         [Test]
    136         public async Task RepairBurnInstallerWithModifyBehavior()
    137         {
    138             var replaceInstallerArguments = this.GetReplacementArguments(this.installDir, "2.0.0.0", "TestModifyRepair", useHKLM: true);
    139             var searchResult = await this.FindAndInstallPackage(Constants.ModifyRepairInstaller, this.installDir, replaceInstallerArguments.ToString());
    140 
    141             // Repair the package
    142             var repairOptions = this.TestFactory.CreateRepairOptions();
    143             repairOptions.PackageRepairMode = PackageRepairMode.Silent;
    144 
    145             await this.RepairPackageAndValidateStatus(searchResult.CatalogPackage, repairOptions, RepairResultStatus.Ok);
    146 
    147             // Cleanup
    148             Assert.True(TestCommon.VerifyTestExeRepairCompletedAndCleanup(this.installDir, "Modify Repair operation"));
    149         }
    150 
    151         /// <summary>
    152         /// Tests the repair operation of a Burn installer that was installed in user scope but is being repaired in an admin context.
    153         /// </summary>
    154         /// <returns>representing the asynchronous unit test.</returns>
    155         [Test]
    156         public async Task RepairBurnInstallerInAdminContextWithUserScopeInstall()
    157         {
    158             if (this.TestFactory.Context != ClsidContext.InProc)
    159             {
    160                 Assert.Ignore("Test is only applicable for InProc context.");
    161             }
    162 
    163             string replaceInstallerArguments = this.GetReplacementArguments(this.installDir, "2.0.0.0", "TestUserScopeInstallRepairInAdminContext");
    164             var searchResult = await this.FindAndInstallPackage("AppInstallerTest.TestUserScopeInstallRepairInAdminContext", this.installDir, replaceInstallerArguments);
    165 
    166             // Repair the package
    167             var repairOptions = this.TestFactory.CreateRepairOptions();
    168             repairOptions.PackageRepairMode = PackageRepairMode.Silent;
    169 
    170             await this.RepairPackageAndValidateStatus(searchResult.CatalogPackage, repairOptions, RepairResultStatus.RepairError, Constants.ErrorCode.ERROR_ADMIN_CONTEXT_REPAIR_PROHIBITED);
    171 
    172             // Cleanup
    173             TestCommon.CleanupTestExeAndDirectory(this.installDir);
    174         }
    175 
    176         /// <summary>
    177         /// Test repair of a Exe installer that has a "uninstaller" repair behavior specified in the manifest and NoModify ARP flag set.
    178         /// </summary>
    179         /// <returns>representing the asynchronous unit test.</returns>
    180         [Test]
    181         public async Task RepairBurnInstallerWithModifyBehaviorAndNoModifyFlag()
    182         {
    183             string replaceInstallerArguments = this.GetReplacementArguments(this.installDir, "2.0.0.0", "TestModifyRepairWithNoModify", useHKLM: true, noModify: true);
    184             var searchResult = await this.FindAndInstallPackage("AppInstallerTest.TestModifyRepairWithNoModify", this.installDir, replaceInstallerArguments);
    185 
    186             // Repair the package
    187             var repairOptions = this.TestFactory.CreateRepairOptions();
    188             repairOptions.PackageRepairMode = PackageRepairMode.Silent;
    189 
    190             await this.RepairPackageAndValidateStatus(searchResult.CatalogPackage, repairOptions, RepairResultStatus.RepairError, Constants.ErrorCode.ERROR_REPAIR_NOT_SUPPORTED);
    191 
    192             // Cleanup
    193             TestCommon.CleanupTestExeAndDirectory(this.installDir);
    194         }
    195 
    196         /// <summary>
    197         /// Tests the scenario where the repair operation is not supported for Portable Installer type.
    198         /// </summary>
    199         /// <returns>representing the asynchronous unit test.</returns>
    200         [Test]
    201         public async Task RepairOperationNotSupportedForPortableInstaller()
    202         {
    203             string installDir = TestCommon.GetPortablePackagesDirectory();
    204             string packageId, commandAlias, fileName, packageDirName, productCode;
    205             packageId = "AppInstallerTest.TestPortableExe";
    206             packageDirName = productCode = packageId + "_" + Constants.TestSourceIdentifier;
    207             commandAlias = fileName = "AppInstallerTestExeInstaller.exe";
    208 
    209             var searchResult = await this.FindAndInstallPackage(packageId, null, null);
    210 
    211             // Repair the package
    212             var repairOptions = this.TestFactory.CreateRepairOptions();
    213             repairOptions.PackageRepairMode = PackageRepairMode.Silent;
    214 
    215             await this.RepairPackageAndValidateStatus(searchResult.CatalogPackage, repairOptions, RepairResultStatus.RepairError, Constants.ErrorCode.ERROR_REPAIR_NOT_SUPPORTED);
    216 
    217             // If no location specified, default behavior is to create a package directory with the name "{packageId}_{sourceId}"
    218             TestCommon.VerifyPortablePackage(Path.Combine(installDir, packageDirName), commandAlias, fileName, productCode, true);
    219         }
    220 
    221         /// <summary>
    222         /// Test repair of a Exe installer that has a "uninstaller" repair behavior specified in the manifest.
    223         /// </summary>
    224         /// <returns>representing the asynchronous unit test.</returns>
    225         [Test]
    226         public async Task RepairExeInstallerWithUninstallerBehavior()
    227         {
    228             string replaceInstallerArguments = this.GetReplacementArguments(this.installDir, "2.0.0.0", "UninstallerRepair", useHKLM: true);
    229             var searchResult = await this.FindAndInstallPackage("AppInstallerTest.UninstallerRepair", this.installDir, replaceInstallerArguments);
    230 
    231             // Repair the package
    232             await this.RepairPackageAndValidateStatus(searchResult.CatalogPackage, this.TestFactory.CreateRepairOptions(), RepairResultStatus.Ok);
    233 
    234             // Cleanup
    235             Assert.True(TestCommon.VerifyTestExeRepairCompletedAndCleanup(this.installDir, "Uninstaller Repair operation"));
    236         }
    237 
    238         /// <summary>
    239         /// Test repair of a Exe installer that has a "uninstaller" repair behavior specified in the manifest and NoRepair ARP flag set.
    240         /// </summary>
    241         /// <returns>representing the asynchronous unit test.</returns>
    242         [Test]
    243         public async Task RepairExeInstallerWithUninstallerBehaviorAndNoRepairFlag()
    244         {
    245             string replaceInstallerArguments = this.GetReplacementArguments(this.installDir, "2.0.0.0", "UninstallerRepairWithNoRepair", useHKLM: true, noRepair: true);
    246             var searchResult = await this.FindAndInstallPackage("AppInstallerTest.UninstallerRepairWithNoRepair", this.installDir, replaceInstallerArguments);
    247 
    248             // Repair the package
    249             await this.RepairPackageAndValidateStatus(searchResult.CatalogPackage, this.TestFactory.CreateRepairOptions(), RepairResultStatus.RepairError, Constants.ErrorCode.ERROR_REPAIR_NOT_SUPPORTED);
    250 
    251             // Cleanup
    252             TestCommon.CleanupTestExeAndDirectory(this.installDir);
    253         }
    254 
    255         /// <summary>
    256         /// Test repair of a Nullsoft installer that has a "uninstaller" repair behavior specified in the manifest.
    257         /// </summary>
    258         /// <returns>representing the asynchronous unit test.</returns>
    259         [Test]
    260         public async Task RepairNullsoftInstallerWithUninstallerBehavior()
    261         {
    262             string replaceInstallerArguments = this.GetReplacementArguments(this.installDir, "2.0.0.0", "NullsoftUninstallerRepair", useHKLM: true);
    263             var searchResult = await this.FindAndInstallPackage("AppInstallerTest.NullsoftUninstallerRepair", this.installDir, replaceInstallerArguments.ToString());
    264 
    265             // Repair the package
    266             await this.RepairPackageAndValidateStatus(searchResult.CatalogPackage, this.TestFactory.CreateRepairOptions(), RepairResultStatus.Ok);
    267 
    268             // Cleanup
    269             Assert.True(TestCommon.VerifyTestExeRepairCompletedAndCleanup(this.installDir, "Uninstaller Repair operation"));
    270         }
    271 
    272         /// <summary>
    273         /// Test repair of a Inno installer that has a "installer" repair behavior specified in the manifest.
    274         /// </summary>
    275         /// <returns>representing the asynchronous unit test.</returns>
    276         [Test]
    277         public async Task RepairInnoInstallerWithInstallerRepairBehavior()
    278         {
    279             string replaceInstallerArguments = this.GetReplacementArguments(this.installDir, "2.0.0.0", "TestInstallerRepair", useHKLM: true);
    280             var searchResult = await this.FindAndInstallPackage("AppInstallerTest.TestInstallerRepair", this.installDir, replaceInstallerArguments);
    281 
    282             // Repair the package
    283             await this.RepairPackageAndValidateStatus(searchResult.CatalogPackage, this.TestFactory.CreateRepairOptions(), RepairResultStatus.Ok);
    284 
    285             // Cleanup
    286             Assert.True(TestCommon.VerifyTestExeRepairCompletedAndCleanup(this.installDir, "Installer Repair operation"));
    287         }
    288 
    289         private async Task<MatchResult> FindAndInstallPackage(string packageId, string installDir, string replacementInstallerArguments)
    290         {
    291             // Find a package
    292             var searchResult = this.FindOnePackage(this.compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, packageId);
    293 
    294             // Configure installation
    295             var installOptions = this.TestFactory.CreateInstallOptions();
    296             installOptions.PackageInstallMode = PackageInstallMode.Silent;
    297 
    298             if (!string.IsNullOrEmpty(installDir))
    299             {
    300                 installOptions.PreferredInstallLocation = installDir;
    301             }
    302 
    303             if (!string.IsNullOrEmpty(replacementInstallerArguments))
    304             {
    305                 installOptions.ReplacementInstallerArguments = replacementInstallerArguments;
    306             }
    307 
    308             // Install the package
    309             var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions);
    310             Assert.AreEqual(InstallResultStatus.Ok, installResult.Status);
    311 
    312             // Find package again, but this time it should be detected as installed
    313             searchResult = this.FindOnePackage(this.compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, packageId);
    314             Assert.NotNull(searchResult.CatalogPackage.InstalledVersion);
    315 
    316             // Return the search result
    317             return searchResult;
    318         }
    319 
    320         private async Task RepairPackageAndValidateStatus(CatalogPackage package, RepairOptions repairOptions, RepairResultStatus expectedStatus, int expectedErrorCode = 0)
    321         {
    322             var repairResult = await this.packageManager.RepairPackageAsync(package, repairOptions);
    323             Assert.AreEqual(expectedStatus, repairResult.Status);
    324 
    325             if (expectedStatus != RepairResultStatus.Ok)
    326             {
    327                 Assert.AreEqual(expectedErrorCode, repairResult.ExtendedErrorCode.HResult);
    328             }
    329         }
    330 
    331         private string GetReplacementArguments(string installDir, string version, string displayName, bool useHKLM = false, bool noModify = false, bool noRepair = false)
    332         {
    333             var replacementArguments = new StringBuilder($"/InstallDir {installDir} /Version {version} /DisplayName {displayName}");
    334 
    335             // Machine scope install.
    336             if (useHKLM)
    337             {
    338                 replacementArguments.Append($" /UseHKLM");
    339             }
    340 
    341             // Instructs test installer to set NoModify ARP flag.
    342             if (noModify)
    343             {
    344                 replacementArguments.Append($" /NoModify");
    345             }
    346 
    347             // Instructs test installer to set NoRepair ARP flag.
    348             if (noRepair)
    349             {
    350                 replacementArguments.Append($" /NoRepair");
    351             }
    352 
    353             return replacementArguments.ToString();
    354         }
    355     }
    356 }