Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Winget Repair - Eliminate installer type mapping for MSI/WIX and MSIX NonStore ,code refactoring & E2E Test Coverage #4534

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
ed277fb
[WinGet][Repair] Winget Repair - Eliminate installer type mapping for…
Madhusudhan-MSFT Jun 4, 2024
99bf2e4
AppInstallerTestExeInstaller - Repair support
Madhusudhan-MSFT Jun 4, 2024
c928c65
Winget Repair E2E Test cases
Madhusudhan-MSFT Jun 4, 2024
470856c
Fix: Remove unused namespaces causing spell check errors
Madhusudhan-MSFT Jun 4, 2024
78e7613
Fix: To address MSI repair test failures, AppInstallerTest.TestMsiIns…
Madhusudhan-MSFT Jun 4, 2024
fec6f13
Extend NoRepair and NoModify ARP support for AppInstallerTestExeInsta…
Madhusudhan-MSFT Jun 5, 2024
cb35e5a
Extend winget repair tests to include negative test scenarios
Madhusudhan-MSFT Jun 5, 2024
1fa5ac0
Resolving Spellcheck error by changing the test manifest name to 'Tes…
Madhusudhan-MSFT Jun 5, 2024
bb5acc4
Clean up unused test Setup method
Madhusudhan-MSFT Jun 6, 2024
5b32349
Removed unused includes and test data from project files
Madhusudhan-MSFT Jun 7, 2024
da362f3
Removed internal implementation comments from RepairFlow.cpp.
Madhusudhan-MSFT Jun 7, 2024
e918ee9
Refactor RepairFlow and update function names
Madhusudhan-MSFT Jun 7, 2024
0ef11dc
`Refactor SetPackageFamilyNamesInContext usage in RepairFlow.cpp`
Madhusudhan-MSFT Jun 7, 2024
6fd832b
Updated repair functions and improved code readability
Madhusudhan-MSFT Jun 10, 2024
f65bb70
Renamed `RepairMsixNonStorePackage` to `RepairMsixPackage`
Madhusudhan-MSFT Jun 10, 2024
de203b4
`Refactor msix /msi installer mapping requirment logic in RepairFlow.…
Madhusudhan-MSFT Jun 11, 2024
cd6c65e
PR Feedback Fix : Refine MSIX handling & simplify installer checks
Madhusudhan-MSFT Jun 15, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 7 additions & 9 deletions src/AppInstallerCLICore/Commands/RepairCommand.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -104,22 +104,20 @@ namespace AppInstaller::CLI
Workflow::GetManifestFromArg <<
Workflow::ReportManifestIdentity <<
Workflow::SearchSourceUsingManifest <<
Workflow::EnsureOneMatchFromSearchResult(OperationType::Repair) <<
Workflow::GetInstalledPackageVersion <<
Workflow::SelectInstaller <<
Workflow::EnsureApplicableInstaller <<
Workflow::RepairSinglePackage;
Workflow::EnsureOneMatchFromSearchResult(OperationType::Repair);
}
else
{
context <<
Workflow::SearchSourceForSingle <<
Workflow::HandleSearchResultFailures <<
Workflow::EnsureOneMatchFromSearchResult(OperationType::Repair) <<
Workflow::ReportPackageIdentity <<
Workflow::GetInstalledPackageVersion <<
Workflow::SelectApplicablePackageVersion <<
Workflow::RepairSinglePackage;
Workflow::ReportPackageIdentity;
}

context <<
Workflow::GetInstalledPackageVersion <<
Workflow::SelectApplicableInstallerIfNecessary <<
Workflow::RepairSinglePackage;
}
}
222 changes: 136 additions & 86 deletions src/AppInstallerCLICore/Workflows/RepairFlow.cpp

Large diffs are not rendered by default.

9 changes: 8 additions & 1 deletion src/AppInstallerCLICore/Workflows/RepairFlow.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ namespace AppInstaller::CLI::Workflow
// Outputs:RepairString?, ProductCodes?, PackageFamilyNames?
void GetRepairInfo(Execution::Context& context);

// Perform the repair operation for the MSIX package.
// Perform the repair operation for the MSIX NonStore package.
// RequiredArgs:None
// Inputs:PackageFamilyNames , InstallScope?
// Outputs:None
Expand All @@ -47,6 +47,13 @@ namespace AppInstaller::CLI::Workflow
// Outputs:Manifest, PackageVersion, Installer
void SelectApplicablePackageVersion(Execution::Context& context);

/// <summary>
/// Select the applicable installer for the installed package if necessary.
// RequiredArgs:None
// Inputs: Package,InstalledPackageVersion, AvailablePackageVersions
// Outputs:Manifest, PackageVersion, Installer
void SelectApplicableInstallerIfNecessary(Execution::Context& context);

// Perform the repair operation for the single package.
// RequiredArgs:None
// Inputs: SearchResult, InstalledPackage, ApplicableInstaller
Expand Down
7 changes: 7 additions & 0 deletions src/AppInstallerCLIE2ETests/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ public class Constants
public const string LocalServerCertPathParameter = "LocalServerCertPath";
public const string ExeInstallerPathParameter = "ExeTestInstallerPath";
public const string MsiInstallerPathParameter = "MsiTestInstallerPath";
public const string MsiInstallerV2PathParameter = "MsiTestInstallerV2Path";
public const string MsixInstallerPathParameter = "MsixTestInstallerPath";
public const string PackageCertificatePathParameter = "PackageCertificatePath";
public const string PowerShellModulePathParameter = "PowerShellModulePath";
Expand Down Expand Up @@ -58,6 +59,7 @@ public class Constants
public const string ZipInstaller = "AppInstallerTestZipInstaller";
public const string ExeInstallerFileName = "AppInstallerTestExeInstaller.exe";
public const string MsiInstallerFileName = "AppInstallerTestMsiInstaller.msi";
public const string MsiInstallerV2FileName = "AppInstallerTestMsiInstallerV2.msi";
public const string MsixInstallerFileName = "AppInstallerTestMsixInstaller.msix";
public const string ZipInstallerFileName = "AppInstallerTestZipInstaller.zip";
public const string IndexPackage = "source.msix";
Expand Down Expand Up @@ -91,6 +93,7 @@ public class Constants
public const string TestExeInstalledFileName = "TestExeInstalled.txt";
public const string TestExeUninstallerFileName = "UninstallTestExe.bat";
public const string TestExeUninstalledFileName = "TestExeUninstalled.txt";
public const string TestExeRepairCompletedFileName = "TestExeRepairCompleted.txt";

// PowerShell Cmdlets
public const string FindCmdlet = "Find-WinGetPackage";
Expand Down Expand Up @@ -263,6 +266,10 @@ public class ErrorCode
public const int ERROR_INVALID_RESUME_STATE = unchecked((int)0x8A150070);
public const int ERROR_CANNOT_OPEN_CHECKPOINT_INDEX = unchecked((int)0x8A150071);

public const int ERROR_NO_REPAIR_INFO_FOUND = unchecked((int)0x8A150079);
public const int ERROR_REPAIR_NOT_SUPPORTED = unchecked((int)0x8A15007C);
public const int ERROR_ADMIN_CONTEXT_REPAIR_PROHIBITED = unchecked((int)0x8A15007D);

public const int ERROR_INSTALL_PACKAGE_IN_USE = unchecked((int)0x8A150101);
public const int ERROR_INSTALL_INSTALL_IN_PROGRESS = unchecked((int)0x8A150102);
public const int ERROR_INSTALL_FILE_IN_USE = unchecked((int)0x8A150103);
Expand Down
131 changes: 126 additions & 5 deletions src/AppInstallerCLIE2ETests/Helpers/TestCommon.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ namespace AppInstallerCLIE2ETests.Helpers
using System.Security.Principal;
using System.Text;
using System.Threading;
using System.Web;
using AppInstallerCLIE2ETests;
using AppInstallerCLIE2ETests.PowerShell;
using Microsoft.Management.Deployment;
Expand Down Expand Up @@ -470,6 +471,32 @@ public static bool VerifyTestExeInstalled(string installDir, string expectedCont
return verifyInstallSuccess;
}

/// <summary>
/// Verifies if the repair of the test executable was successful.
/// </summary>
/// <param name="installDir">The directory where the test executable is installed.</param>
/// <param name="expectedContent">The expected content in the test executable file. This is optional.</param>
/// <returns>Returns true if the repair was successful, false otherwise.</returns>
public static bool VerifyTestExeRepairSuccessful(string installDir, string expectedContent = null)
{
bool verifyRepairSuccess = true;

if (!File.Exists(Path.Combine(installDir, Constants.TestExeRepairCompletedFileName)))
{
TestContext.Out.WriteLine($"{Constants.TestExeRepairCompletedFileName} not found at {installDir}");
verifyRepairSuccess = false;
}

if (verifyRepairSuccess && !string.IsNullOrEmpty(expectedContent))
{
string content = File.ReadAllText(Path.Combine(installDir, Constants.TestExeRepairCompletedFileName));
TestContext.Out.WriteLine($"TestExeRepairCompleted.txt content: {content}");
verifyRepairSuccess = content.Contains(expectedContent);
}

return verifyRepairSuccess;
}

/// <summary>
/// Verify installer and manifest downloaded correctly and cleanup.
/// </summary>
Expand Down Expand Up @@ -543,6 +570,35 @@ public static bool VerifyTestExeInstalled(string installDir, string expectedCont
return downloadResult;
}

/// <summary>
/// Best effort test exe cleanup.
/// </summary>
/// <param name="installDir">Install directory.</param>
public static void BestEffortTestExeCleanup(string installDir)
{
var uninstallerPath = Path.Combine(installDir, Constants.TestExeUninstallerFileName);
if (File.Exists(uninstallerPath))
{
RunCommand(Path.Combine(installDir, Constants.TestExeUninstallerFileName));
}
}

/// <summary>
/// Best effort test exe cleanup and install directory cleanup.
/// </summary>
/// <param name="installDir">Install directory.</param>
public static void CleanupTestExeAndDirectory(string installDir)
{
// Always try clean up and ignore clean up failure
BestEffortTestExeCleanup(installDir);

// Delete the install directory to reclaim disk space
if (Directory.Exists(installDir))
{
Directory.Delete(installDir, true);
}
}

/// <summary>
/// Verify exe installer correctly and then uninstall it.
/// </summary>
Expand All @@ -554,15 +610,25 @@ public static bool VerifyTestExeInstalledAndCleanup(string installDir, string ex
bool verifyInstallSuccess = VerifyTestExeInstalled(installDir, expectedContent);

// Always try clean up and ignore clean up failure
var uninstallerPath = Path.Combine(installDir, Constants.TestExeUninstallerFileName);
if (File.Exists(uninstallerPath))
{
RunCommand(Path.Combine(installDir, Constants.TestExeUninstallerFileName));
}
BestEffortTestExeCleanup(installDir);

return verifyInstallSuccess;
}

/// <summary>
/// Verify exe repair completed and cleanup.
/// </summary>
/// <param name="installDir">Install directory.</param>
/// <param name="expectedContent">Optional expected context.</param>
/// <returns>True if success.</returns>
public static bool VerifyTestExeRepairCompletedAndCleanup(string installDir, string expectedContent = null)
{
bool verifyRepairSuccess = VerifyTestExeRepairSuccessful(installDir, expectedContent);
CleanupTestExeAndDirectory(installDir);

return verifyRepairSuccess;
}

/// <summary>
/// Verify msi installed correctly.
/// </summary>
Expand Down Expand Up @@ -912,6 +978,61 @@ public static string GetExpectedModulePath(TestModuleLocation location)
}
}

/// <summary>
/// Copy the installer file to the ARP InstallSource directory.
/// </summary>
/// <param name="installerFilePath">Test installer to be copied.</param>
/// <param name="productCode">Installer Product.</param>
/// <param name="useWoW6432Node">is WoW6432Node to use.</param>
/// <returns>Returns the installer source directory if the file operation is successful, otherwise returns an empty string.</returns>
public static string CopyInstallerFileToARPInstallSourceDirectory(string installerFilePath, string productCode, bool useWoW6432Node = false)
{
if (string.IsNullOrEmpty(installerFilePath))
{
new ArgumentNullException(nameof(installerFilePath));
}

if (!File.Exists(installerFilePath))
{
new FileNotFoundException(installerFilePath);
}

string outputDirectory = string.Empty;

// Define the registry paths for both x64 and x86
string registryPath = useWoW6432Node
? $@"SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\{productCode}"
: $@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{productCode}";

// Open the registry key where the uninstall information is stored
using (RegistryKey key = Registry.LocalMachine.OpenSubKey(registryPath))
{
if (key != null)
{
// Read the InstallSource value
string arpInstallSourceDirectory = key.GetValue("InstallSource") as string;

if (!string.IsNullOrEmpty(arpInstallSourceDirectory))
{
// Copy the MSI installer to the InstallSource directory
string installerFileName = Path.GetFileName(installerFilePath);
string installerDestinationPath = Path.Combine(arpInstallSourceDirectory, installerFileName);

if (!Directory.Exists(arpInstallSourceDirectory))
{
Directory.CreateDirectory(arpInstallSourceDirectory);
}

File.Copy(installerFilePath, installerDestinationPath, true);

outputDirectory = arpInstallSourceDirectory;
}
}
}

return outputDirectory;
}

/// <summary>
/// Run winget command via direct process.
/// </summary>
Expand Down
25 changes: 24 additions & 1 deletion src/AppInstallerCLIE2ETests/Helpers/TestIndex.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------
// <copyright file="TestIndex.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. Licensed under the MIT License.
// </copyright>
Expand All @@ -22,6 +22,7 @@ static TestIndex()
// Expected path for the installers.
TestIndex.ExeInstaller = Path.Combine(TestSetup.Parameters.StaticFileRootPath, Constants.ExeInstaller, Constants.ExeInstallerFileName);
TestIndex.MsiInstaller = Path.Combine(TestSetup.Parameters.StaticFileRootPath, Constants.MsiInstaller, Constants.MsiInstallerFileName);
TestIndex.MsiInstallerV2 = Path.Combine(TestSetup.Parameters.StaticFileRootPath, Constants.MsiInstaller, Constants.MsiInstallerV2FileName);
TestIndex.MsixInstaller = Path.Combine(TestSetup.Parameters.StaticFileRootPath, Constants.MsixInstaller, Constants.MsixInstallerFileName);
TestIndex.ZipInstaller = Path.Combine(TestSetup.Parameters.StaticFileRootPath, Constants.ZipInstaller, Constants.ZipInstallerFileName);
}
Expand All @@ -36,6 +37,11 @@ static TestIndex()
/// </summary>
public static string MsiInstaller { get; private set; }

/// <summary>
/// Gets the signed msi installerV2 path used by the manifests in the E2E test.
/// </summary>
public static string MsiInstallerV2 { get; private set; }

/// <summary>
/// Gets the signed msix installer path used by the manifests in the E2E test.
/// </summary>
Expand Down Expand Up @@ -73,6 +79,16 @@ public static void GenerateE2ESource()
throw new FileNotFoundException(testParams.MsiInstallerPath);
}

if (string.IsNullOrEmpty(testParams.MsiInstallerV2Path))
{
throw new ArgumentNullException($"{Constants.MsiInstallerV2PathParameter} is required");
}

if (!File.Exists(testParams.MsiInstallerV2Path))
{
throw new FileNotFoundException(testParams.MsiInstallerV2Path);
}

if (string.IsNullOrEmpty(testParams.MsixInstallerPath))
{
throw new ArgumentNullException($"{Constants.MsixInstallerPathParameter} is required");
Expand Down Expand Up @@ -118,6 +134,13 @@ public static void GenerateE2ESource()
HashToken = "<MSIHASH>",
},
new LocalInstaller
{
Type = InstallerType.Msi,
Name = Path.Combine(Constants.MsiInstaller, Constants.MsiInstallerV2FileName),
Input = testParams.MsiInstallerPath,
HashToken = "<MSIHASHV2>",
},
new LocalInstaller
{
Type = InstallerType.Msix,
Name = Path.Combine(Constants.MsixInstaller, Constants.MsixInstallerFileName),
Expand Down
6 changes: 6 additions & 0 deletions src/AppInstallerCLIE2ETests/Helpers/TestSetup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ private TestSetup()
this.ExeInstallerPath = this.InitializeFileParam(Constants.ExeInstallerPathParameter);
this.MsiInstallerPath = this.InitializeFileParam(Constants.MsiInstallerPathParameter);
this.MsixInstallerPath = this.InitializeFileParam(Constants.MsixInstallerPathParameter);
this.MsiInstallerV2Path = this.InitializeFileParam(Constants.MsiInstallerV2PathParameter);

this.ForcedExperimentalFeatures = this.InitializeStringArrayParam(Constants.ForcedExperimentalFeaturesParameter);
}
Expand Down Expand Up @@ -103,6 +104,11 @@ public static TestSetup Parameters
/// </summary>
public string MsiInstallerPath { get; }

/// <summary>
/// Gets the msi installer V2 path.
/// </summary>
public string MsiInstallerV2Path { get; }

/// <summary>
/// Gets the msix installer path.
/// </summary>
Expand Down
Loading
Loading