From 2ee3cd6e33153afc1a872a5575a170b5d6fcad28 Mon Sep 17 00:00:00 2001 From: Madhusudhan Gumbalapura Sudarshan Date: Tue, 6 Aug 2024 17:02:17 -0700 Subject: [PATCH 01/35] Add Skeleton Implementation for Repair COM APIs Invocable from E2E Tests This commit includes: 1. Necessary additions and updates to PackageManager.idl to define repair-specific COM API contracts and runtime class declarations. 2. Implementation of repair-specific runtime class and PackageManager runtime class repair contract methods skeleton. 3. GUID mapping for In-proc and Out-of-proc COM API class definitions. 4. Updates to `Package.appxmanifest` and `Microsoft.Management.Deployment.InProc.dll.manifest` to include new COM class entries for `RepairOptions`. 5. Updates to `ComClsids.cpp` and `ComClsids.h` to include CLSID definitions and redirection logic for `RepairOptions`. 6. Modifications to `ClassesDefinition.cs` and `WinGetProjectionFactory.cs` to define and create instances of `RepairOptions`and repair com api support for C# winrt projection interop library. --- .../Package.appxmanifest | 2 + ....Management.Deployment.InProc.dll.manifest | 5 +- .../ClassesDefinition.cs | 14 +- .../WinGetProjectionFactory.cs | 6 +- .../ComClsids.cpp | 9 +- .../Microsoft.Management.Deployment.vcxproj | 4 + ...soft.Management.Deployment.vcxproj.filters | 7 + .../PackageManager.cpp | 9 ++ .../PackageManager.idl | 128 +++++++++++++++++- .../Public/ComClsids.h | 5 +- .../RepairOptions.cpp | 92 +++++++++++++ .../RepairOptions.h | 49 +++++++ .../RepairResult.cpp | 45 ++++++ .../RepairResult.h | 36 +++++ src/WindowsPackageManager/main.cpp | 1 + 15 files changed, 404 insertions(+), 8 deletions(-) create mode 100644 src/Microsoft.Management.Deployment/RepairOptions.cpp create mode 100644 src/Microsoft.Management.Deployment/RepairOptions.h create mode 100644 src/Microsoft.Management.Deployment/RepairResult.cpp create mode 100644 src/Microsoft.Management.Deployment/RepairResult.h diff --git a/src/AppInstallerCLIPackage/Package.appxmanifest b/src/AppInstallerCLIPackage/Package.appxmanifest index 18d9ca333e..6604fcee74 100644 --- a/src/AppInstallerCLIPackage/Package.appxmanifest +++ b/src/AppInstallerCLIPackage/Package.appxmanifest @@ -78,6 +78,8 @@ + + diff --git a/src/Microsoft.Management.Deployment.InProc/Microsoft.Management.Deployment.InProc.dll.manifest b/src/Microsoft.Management.Deployment.InProc/Microsoft.Management.Deployment.InProc.dll.manifest index 55778515a8..f9e41531c7 100644 --- a/src/Microsoft.Management.Deployment.InProc/Microsoft.Management.Deployment.InProc.dll.manifest +++ b/src/Microsoft.Management.Deployment.InProc/Microsoft.Management.Deployment.InProc.dll.manifest @@ -43,6 +43,9 @@ clsid="{80CF9D63-5505-4342-B9B4-BB87895CA8BB}" threadingModel="Both" description="PackageManagerSettings"/> + - diff --git a/src/Microsoft.Management.Deployment.Projection/ClassesDefinition.cs b/src/Microsoft.Management.Deployment.Projection/ClassesDefinition.cs index 49012a2e1e..bf39299cc8 100644 --- a/src/Microsoft.Management.Deployment.Projection/ClassesDefinition.cs +++ b/src/Microsoft.Management.Deployment.Projection/ClassesDefinition.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. namespace Microsoft.Management.Deployment.Projection @@ -114,6 +114,18 @@ internal static class ClassesDefinition { [ClsidContext.InProc] = new Guid("80CF9D63-5505-4342-B9B4-BB87895CA8BB"), } + }, + + [typeof(RepairOptions)] = new () + { + ProjectedClassType = typeof(RepairOptions), + InterfaceType = typeof(IRepairOptions), + Clsids = new Dictionary() + { + [ClsidContext.InProc] = new Guid("30C024C4-852C-4DD4-9810-1348C51EF9BB"), + [ClsidContext.OutOfProc] = new Guid("0498F441-3097-455F-9CAF-148F28293865"), + [ClsidContext.OutOfProcDev] = new Guid("E62BB1E7-C7B2-4AEC-9E28-FB649B30FF03"), + } } }; diff --git a/src/Microsoft.Management.Deployment.Projection/WinGetProjectionFactory.cs b/src/Microsoft.Management.Deployment.Projection/WinGetProjectionFactory.cs index 4e457fed2e..73b75d5c70 100644 --- a/src/Microsoft.Management.Deployment.Projection/WinGetProjectionFactory.cs +++ b/src/Microsoft.Management.Deployment.Projection/WinGetProjectionFactory.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. namespace Microsoft.Management.Deployment.Projection @@ -33,6 +33,8 @@ public WinGetProjectionFactory(IInstanceInitializer instanceInitializer) public AuthenticationArguments CreateAuthenticationArguments() => InstanceInitializer.CreateInstance(); - public PackageManagerSettings CreatePackageManagerSettings() => InstanceInitializer.CreateInstance(); + public PackageManagerSettings CreatePackageManagerSettings() => InstanceInitializer.CreateInstance(); + + public RepairOptions CreateRepairOptions() => InstanceInitializer.CreateInstance(); } } diff --git a/src/Microsoft.Management.Deployment/ComClsids.cpp b/src/Microsoft.Management.Deployment/ComClsids.cpp index 7d6f6aefe2..d76c1daf10 100644 --- a/src/Microsoft.Management.Deployment/ComClsids.cpp +++ b/src/Microsoft.Management.Deployment/ComClsids.cpp @@ -13,7 +13,8 @@ #include "PackageMatchFilter.h" #include "PackageManagerSettings.h" #include "DownloadOptions.h" -#include "AuthenticationArguments.h" +#include "AuthenticationArguments.h" +#include "RepairOptions.h" #pragma warning( pop ) namespace winrt::Microsoft::Management::Deployment @@ -55,10 +56,14 @@ namespace winrt::Microsoft::Management::Deployment else if (IsEqualCLSID(clsid, WINGET_INPROC_COM_CLSID_PackageManagerSettings)) { return __uuidof(winrt::Microsoft::Management::Deployment::implementation::PackageManagerSettings); + } + else if (IsEqualCLSID(clsid, WINGET_INPROC_COM_CLSID_RepairOptions)) + { + return __uuidof(winrt::Microsoft::Management::Deployment::implementation::RepairOptions); } else { return CLSID_NULL; } } -} \ No newline at end of file +} diff --git a/src/Microsoft.Management.Deployment/Microsoft.Management.Deployment.vcxproj b/src/Microsoft.Management.Deployment/Microsoft.Management.Deployment.vcxproj index 8047c96419..a1c3e41274 100644 --- a/src/Microsoft.Management.Deployment/Microsoft.Management.Deployment.vcxproj +++ b/src/Microsoft.Management.Deployment/Microsoft.Management.Deployment.vcxproj @@ -217,6 +217,8 @@ + + @@ -257,6 +259,8 @@ Create + + diff --git a/src/Microsoft.Management.Deployment/Microsoft.Management.Deployment.vcxproj.filters b/src/Microsoft.Management.Deployment/Microsoft.Management.Deployment.vcxproj.filters index fac175dc11..3a3f48aa98 100644 --- a/src/Microsoft.Management.Deployment/Microsoft.Management.Deployment.vcxproj.filters +++ b/src/Microsoft.Management.Deployment/Microsoft.Management.Deployment.vcxproj.filters @@ -38,6 +38,8 @@ + + @@ -81,6 +83,8 @@ + + @@ -95,4 +99,7 @@ {9c3907ed-84d9-4485-9b15-04c50717f0ab} + + + \ No newline at end of file diff --git a/src/Microsoft.Management.Deployment/PackageManager.cpp b/src/Microsoft.Management.Deployment/PackageManager.cpp index 20c5dfb18f..ee437bf315 100644 --- a/src/Microsoft.Management.Deployment/PackageManager.cpp +++ b/src/Microsoft.Management.Deployment/PackageManager.cpp @@ -1066,5 +1066,14 @@ namespace winrt::Microsoft::Management::Deployment::implementation return GetPackageOperation(true, std::move(queueItem)); } + winrt::Windows::Foundation::IAsyncOperationWithProgress PackageManager::RepairPackageAsync(winrt::Microsoft::Management::Deployment::CatalogPackage package, winrt::Microsoft::Management::Deployment::RepairOptions options) + { + return winrt::Windows::Foundation::IAsyncOperationWithProgress(); + } + + winrt::Windows::Foundation::IAsyncOperationWithProgress PackageManager::GetRepairProgress(winrt::Microsoft::Management::Deployment::CatalogPackage package, winrt::Microsoft::Management::Deployment::PackageCatalogInfo catalogInfo) + { + return winrt::Windows::Foundation::IAsyncOperationWithProgress(); + } CoCreatableMicrosoftManagementDeploymentClass(PackageManager); } diff --git a/src/Microsoft.Management.Deployment/PackageManager.idl b/src/Microsoft.Management.Deployment/PackageManager.idl index bab9af2985..e9e653e3d2 100644 --- a/src/Microsoft.Management.Deployment/PackageManager.idl +++ b/src/Microsoft.Management.Deployment/PackageManager.idl @@ -2,7 +2,7 @@ // Licensed under the MIT License. namespace Microsoft.Management.Deployment { - [contractversion(10)] + [contractversion(11)] apicontract WindowsPackageManagerContract{}; /// State of the install @@ -151,6 +151,72 @@ namespace Microsoft.Management.Deployment UInt32 UninstallerErrorCode { get; }; } + /// State of the repair + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 11)] + enum PackageRepairProgressState + { + /// The repair is queued but not yet active. Cancellation of the IAsyncOperationWithProgress in this + /// state will prevent the package from repairing. + Queued, + /// The repair is in progress. Cancellation of the IAsyncOperationWithProgress in this state will not + /// stop the repair or the post repair steps. + Repairing, + /// The repair has completed and cleanup actions are in progress. Cancellation of the + /// IAsyncOperationWithProgress in this state will not stop cleanup or roll back the repair. + PostRepair, + /// The operation has completed. + Finished, + }; + + /// Progress object for the repair + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 11)] + struct RepairProgress + { + /// State of the repair + PackageRepairProgressState State; + + /// Repair percentage if known. + Double RepairCompletionProgress; + }; + + /// Status of the repair call + /// Implementation Note: Errors mapped from AppInstallerErrors.h + /// DESIGN NOTE: RepairResultStatus from AppInstallerErrors.h is not implemented in V1. + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 11)] + enum RepairResultStatus + { + Ok, + BlockedByPolicy, + CatalogError, + InternalError, + InvalidOptions, + RepairError, + ManifestError, + NoApplicableInstallers, + PackageAgreementsNotAccepted, + }; + + /// Result of the repair + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 11)] + runtimeclass RepairResult + { + /// Used by a caller to correlate the repair with a caller's data. + String CorrelationData { get; }; + + /// Whether a restart is required to complete the repair. + Boolean RebootRequired { get; }; + + /// Batched error code, example APPINSTALLER_CLI_ERROR_SHELLEXEC_INSTALL_FAILED + RepairResultStatus Status { get; }; + + /// The error code of the overall operation. + HRESULT ExtendedErrorCode { get; }; + + /// The error code from the repair attempt. Only valid if the Status is RepairError. + /// This value's meaning will require knowledge of the specific repairer or repair technology. + UInt32 InstallerErrorCode { get; }; + } + /// State of the download [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 7)] enum PackageDownloadProgressState @@ -1071,6 +1137,57 @@ namespace Microsoft.Management.Deployment String CorrelationData; } + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 11)] + enum PackageRepairMode + { + /// The default experience for the installer. Installer may show some UI. + Default, + /// Runs the installer in silent mode. This suppresses the installer's UI to the extent + /// possible (installer may still show some required UI). + Silent, + /// Runs the installer in interactive mode. + Interactive, + }; + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 11)] + enum PackageRepairScope + { + /// Use default repair behavior. + Any, + /// Repair for current user. Currently only applicable to msix. + User, + /// Repair for all users. + System, + }; + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 11)] + runtimeclass RepairOptions + { + RepairOptions(); + + /// Optionally specifies the version from the package to repair. If unspecified the version matching + /// CatalogPackage.GetLatestVersion() is used. + PackageVersionId PackageVersionId; + + /// The package Repair scope. + PackageRepairScope PackageRepairScope; + + /// The package repair mode. + PackageRepairMode PackageRepairMode; + + /// Optional parameter specifying the directory where installers are downloaded when the repair behavior is of type Installer. + String DownloadDirectory; + + /// Optional parameter specifying Accept the package agreements required for download. + Boolean AcceptPackageAgreements; + + /// Used by a caller to correlate the download with a caller's data. + /// The string must be JSON encoded. + String CorrelationData; + + String LogOutputPath; + } + /// IMPLEMENTATION NOTE: Documentation from AppInstaller::Manifest::Documentation [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 6)] runtimeclass Documentation @@ -1256,6 +1373,15 @@ namespace Microsoft.Management.Deployment // Get download progress Windows.Foundation.IAsyncOperationWithProgress GetDownloadProgress(CatalogPackage package, PackageCatalogInfo catalogInfo); } + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 11)] + { + // Repair the specified package + Windows.Foundation.IAsyncOperationWithProgress RepairPackageAsync(CatalogPackage package, RepairOptions options); + + // Get repair progress + Windows.Foundation.IAsyncOperationWithProgress GetRepairProgress(CatalogPackage package, PackageCatalogInfo catalogInfo); + } } /// Global settings for PackageManager operations. diff --git a/src/Microsoft.Management.Deployment/Public/ComClsids.h b/src/Microsoft.Management.Deployment/Public/ComClsids.h index d42097f871..8d787d635f 100644 --- a/src/Microsoft.Management.Deployment/Public/ComClsids.h +++ b/src/Microsoft.Management.Deployment/Public/ComClsids.h @@ -14,6 +14,7 @@ #define WINGET_OUTOFPROC_COM_CLSID_ConfigurationStaticFunctions "73D763B7-2937-432F-A97A-D98A4A596126" #define WINGET_OUTOFPROC_COM_CLSID_DownloadOptions "4CBABE76-7322-4BE4-9CEA-2589A80682DC" #define WINGET_OUTOFPROC_COM_CLSID_AuthenticationArguments "BA580786-BDE3-4F6C-B8F3-44698AC8711A" +#define WINGET_OUTOFPROC_COM_CLSID_RepairOptions "0498F441-3097-455F-9CAF-148F28293865" #else #define WINGET_OUTOFPROC_COM_CLSID_PackageManager "74CB3139-B7C5-4B9E-9388-E6616DEA288C" #define WINGET_OUTOFPROC_COM_CLSID_FindPackagesOptions "1BD8FF3A-EC50-4F69-AEEE-DF4C9D3BAA96" @@ -24,6 +25,7 @@ #define WINGET_OUTOFPROC_COM_CLSID_ConfigurationStaticFunctions "C9ED7917-66AB-4E31-A92A-F65F18EF7933" #define WINGET_OUTOFPROC_COM_CLSID_DownloadOptions "8EF324ED-367C-4880-83E5-BB2ABD0B72F6" #define WINGET_OUTOFPROC_COM_CLSID_AuthenticationArguments "6484A61D-50FA-41F0-B71E-F4370C6EB37C" +#define WINGET_OUTOFPROC_COM_CLSID_RepairOptions "E62BB1E7-C7B2-4AEC-9E28-FB649B30FF03" #endif // Clsids only used in in-proc invocation @@ -41,6 +43,7 @@ namespace winrt::Microsoft::Management::Deployment const CLSID WINGET_INPROC_COM_CLSID_PackageManagerSettings = { 0x80CF9D63, 0x5505, 0x4342, 0xB9, 0xB4, 0xBB, 0x87, 0x89, 0x5C, 0xA8, 0xBB }; // 80CF9D63-5505-4342-B9B4-BB87895CA8BB const CLSID WINGET_INPROC_COM_CLSID_DownloadOptions = { 0x4288DF96, 0xFDC9, 0x4B68, 0xB4, 0x03, 0x19, 0x3D, 0xBB, 0xF5, 0x6A, 0x24 }; // 4288DF96-FDC9-4B68-B403-193DBBF56A24 const CLSID WINGET_INPROC_COM_CLSID_AuthenticationArguments = { 0x8D593114, 0x1CF1, 0x43B9, 0x87, 0x22, 0x4D, 0xBB, 0x30, 0x10, 0x32, 0x96 }; // 8D593114-1CF1-43B9-8722-4DBB30103296 + const CLSID WINGET_INPROC_COM_CLSID_RepairOptions = { 0x30c024c4, 0x852c, 0x4dd4, 0x98, 0x10, 0x13, 0x48, 0xc5, 0x1e, 0xf9, 0xbb }; // {30C024C4-852C-4DD4-9810-1348C51EF9BB} CLSID GetRedirectedClsidFromInProcClsid(REFCLSID clsid); -} \ No newline at end of file +} diff --git a/src/Microsoft.Management.Deployment/RepairOptions.cpp b/src/Microsoft.Management.Deployment/RepairOptions.cpp new file mode 100644 index 0000000000..d18487f69b --- /dev/null +++ b/src/Microsoft.Management.Deployment/RepairOptions.cpp @@ -0,0 +1,92 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#include "pch.h" +#pragma warning( push ) +#pragma warning ( disable : 4467 6388) +// 6388 Allow CreateInstance. +#include +// 4467 Allow use of uuid attribute for com object creation. +#include "RepairOptions.h" +#pragma warning( pop ) +#include "RepairOptions.g.cpp" +#include "Converters.h" +#include "Helpers.h" + +namespace winrt::Microsoft::Management::Deployment::implementation +{ + RepairOptions::RepairOptions() + { + } + + winrt::Microsoft::Management::Deployment::PackageVersionId RepairOptions::PackageVersionId() + { + return m_packageVersionId; + } + void RepairOptions::PackageVersionId(winrt::Microsoft::Management::Deployment::PackageVersionId const& value) + { + m_packageVersionId = value; + } + + winrt::Microsoft::Management::Deployment::PackageRepairScope RepairOptions::PackageRepairScope() + { + return m_packageRepairScope; + } + + void RepairOptions::PackageRepairScope(winrt::Microsoft::Management::Deployment::PackageRepairScope const& value) + { + m_packageRepairScope = value; + } + + winrt::Microsoft::Management::Deployment::PackageRepairMode RepairOptions::PackageRepairMode() + { + return m_packageRepairMode; + } + + void RepairOptions::PackageRepairMode(winrt::Microsoft::Management::Deployment::PackageRepairMode const& value) + { + m_packageRepairMode = value; + } + + hstring RepairOptions::DownloadDirectory() + { + return hstring(m_downloadPath); + } + + void RepairOptions::DownloadDirectory(hstring const& value) + { + m_downloadPath = value; + } + + bool RepairOptions::AcceptPackageAgreements() + { + return m_acceptPackageAgreements; + } + + void RepairOptions::AcceptPackageAgreements(bool value) + { + m_acceptPackageAgreements = value; + } + + hstring RepairOptions::LogOutputPath() + { + return hstring(m_logOutputPath); + } + + void RepairOptions::LogOutputPath(hstring const& value) + { + m_logOutputPath = value; + } + + hstring RepairOptions::CorrelationData() + { + return hstring(m_correlationData); + } + + void RepairOptions::CorrelationData(hstring const& value) + { + m_correlationData = value; + } + + CoCreatableMicrosoftManagementDeploymentClass(RepairOptions); +} diff --git a/src/Microsoft.Management.Deployment/RepairOptions.h b/src/Microsoft.Management.Deployment/RepairOptions.h new file mode 100644 index 0000000000..e4e9e83c92 --- /dev/null +++ b/src/Microsoft.Management.Deployment/RepairOptions.h @@ -0,0 +1,49 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "RepairOptions.g.h" +#include "Public/ComClsids.h" + +namespace winrt::Microsoft::Management::Deployment::implementation +{ + [uuid(WINGET_OUTOFPROC_COM_CLSID_RepairOptions)] + struct RepairOptions : RepairOptionsT + { + RepairOptions(); + + winrt::Microsoft::Management::Deployment::PackageVersionId PackageVersionId(); + void PackageVersionId(winrt::Microsoft::Management::Deployment::PackageVersionId const& value); + hstring DownloadDirectory(); + void DownloadDirectory(hstring const& value); + winrt::Microsoft::Management::Deployment::PackageRepairScope PackageRepairScope(); + void PackageRepairScope(winrt::Microsoft::Management::Deployment::PackageRepairScope const& value); + winrt::Microsoft::Management::Deployment::PackageRepairMode PackageRepairMode(); + void PackageRepairMode(winrt::Microsoft::Management::Deployment::PackageRepairMode const& value); + bool AcceptPackageAgreements(); + void AcceptPackageAgreements(bool value); + hstring LogOutputPath(); + void LogOutputPath(hstring const& value); + hstring CorrelationData(); + void CorrelationData(hstring const& value); + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) + private: + winrt::Microsoft::Management::Deployment::PackageVersionId m_packageVersionId{ nullptr }; + std::wstring m_downloadPath = L""; + bool m_acceptPackageAgreements = false; + std::wstring m_logOutputPath = L""; + std::wstring m_correlationData = L""; + winrt::Microsoft::Management::Deployment::PackageRepairScope m_packageRepairScope = winrt::Microsoft::Management::Deployment::PackageRepairScope::Any; + winrt::Microsoft::Management::Deployment::PackageRepairMode m_packageRepairMode = winrt::Microsoft::Management::Deployment::PackageRepairMode::Default; +#endif + }; +} + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) +namespace winrt::Microsoft::Management::Deployment::factory_implementation +{ + struct RepairOptions : RepairOptionsT + { + }; +} +#endif diff --git a/src/Microsoft.Management.Deployment/RepairResult.cpp b/src/Microsoft.Management.Deployment/RepairResult.cpp new file mode 100644 index 0000000000..76860c26ee --- /dev/null +++ b/src/Microsoft.Management.Deployment/RepairResult.cpp @@ -0,0 +1,45 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#include "pch.h" +#include "RepairResult.h" +#include "RepairResult.g.cpp" +#include + +namespace winrt::Microsoft::Management::Deployment::implementation +{ + void RepairResult::Initialize( + winrt::Microsoft::Management::Deployment::RepairResultStatus status, + winrt::hresult extendedErrorCode, + uint32_t installerErrorCode, + hstring const& correlationData, + bool rebootRequired) + { + m_status = status; + m_extendedErrorCode = extendedErrorCode; + m_installerErrorCode = installerErrorCode; + m_correlationData = correlationData; + m_rebootRequired = rebootRequired; + } + hstring RepairResult::CorrelationData() + { + return hstring(m_correlationData); + } + bool RepairResult::RebootRequired() + { + return m_rebootRequired; + } + winrt::Microsoft::Management::Deployment::RepairResultStatus RepairResult::Status() + { + return m_status; + } + winrt::hresult RepairResult::ExtendedErrorCode() + { + return m_extendedErrorCode; + } + + uint32_t RepairResult::InstallerErrorCode() + { + return m_installerErrorCode; + } +} diff --git a/src/Microsoft.Management.Deployment/RepairResult.h b/src/Microsoft.Management.Deployment/RepairResult.h new file mode 100644 index 0000000000..2f9384e4f5 --- /dev/null +++ b/src/Microsoft.Management.Deployment/RepairResult.h @@ -0,0 +1,36 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "RepairResult.g.h" + +namespace winrt::Microsoft::Management::Deployment::implementation +{ + struct RepairResult : RepairResultT + { + RepairResult() = default; + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) + void Initialize( + winrt::Microsoft::Management::Deployment::RepairResultStatus status, + winrt::hresult extendedErrorCode, + uint32_t installerErrorCode, + hstring const& correlationData, + bool rebootRequired); +#endif + + hstring CorrelationData(); + bool RebootRequired(); + winrt::Microsoft::Management::Deployment::RepairResultStatus Status(); + winrt::hresult ExtendedErrorCode(); + uint32_t InstallerErrorCode(); + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) + private: + std::wstring m_correlationData = L""; + bool m_rebootRequired = false; + winrt::Microsoft::Management::Deployment::RepairResultStatus m_status = winrt::Microsoft::Management::Deployment::RepairResultStatus::Ok; + winrt::hresult m_extendedErrorCode = S_OK; + uint32_t m_installerErrorCode = 0; +#endif + }; +} diff --git a/src/WindowsPackageManager/main.cpp b/src/WindowsPackageManager/main.cpp index 609735d86c..21016db93d 100644 --- a/src/WindowsPackageManager/main.cpp +++ b/src/WindowsPackageManager/main.cpp @@ -29,6 +29,7 @@ CoCreatableClassWrlCreatorMapInclude(DownloadOptions); CoCreatableClassWrlCreatorMapInclude(PackageMatchFilter); CoCreatableClassWrlCreatorMapInclude(AuthenticationArguments); CoCreatableClassWrlCreatorMapInclude(PackageManagerSettings); +CoCreatableClassWrlCreatorMapInclude(RepairOptions); // Shim for configuration static functions CoCreatableClassWrlCreatorMapInclude(ConfigurationStaticFunctionsShim); From 24bd7a0ee6d25a95d2658a3fc389a9861073e60c Mon Sep 17 00:00:00 2001 From: Madhusudhan Gumbalapura Sudarshan Date: Tue, 6 Aug 2024 17:27:45 -0700 Subject: [PATCH 02/35] Implement Sample Interop Test for Winget Repair COM API This commit includes: - A sample interop test to invoke the Winget repair out-of-proc COM API. - Validation to ensure the COM API endpoint is invocable. --- .../Interop/RepairInterop.cs | 106 ++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 src/AppInstallerCLIE2ETests/Interop/RepairInterop.cs diff --git a/src/AppInstallerCLIE2ETests/Interop/RepairInterop.cs b/src/AppInstallerCLIE2ETests/Interop/RepairInterop.cs new file mode 100644 index 0000000000..d0561ee30d --- /dev/null +++ b/src/AppInstallerCLIE2ETests/Interop/RepairInterop.cs @@ -0,0 +1,106 @@ +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace AppInstallerCLIE2ETests.Interop +{ + using System; + using System.IO; + using System.Threading.Tasks; + using AppInstallerCLIE2ETests.Helpers; + using Microsoft.Management.Deployment; + using Microsoft.Management.Deployment.Projection; + using NUnit.Framework; + + /// + /// Repair Interop Tests for COM/WinRT Interop classes. + /// + ////[TestFixtureSource(typeof(InstanceInitializersSource), nameof(InstanceInitializersSource.InProcess), Category = nameof(InstanceInitializersSource.InProcess))] + [TestFixtureSource(typeof(InstanceInitializersSource), nameof(InstanceInitializersSource.OutOfProcess), Category = nameof(InstanceInitializersSource.OutOfProcess))] + public class RepairInterop : BaseInterop + { + private string installDir; + private PackageManager packageManager; + private PackageCatalogReference compositeSource; + + /// + /// Initializes a new instance of the class. + /// + /// Initializer. + public RepairInterop(IInstanceInitializer initializer) + : base(initializer) + { + } + + /// + /// Test setup. + /// + [SetUp] + public void Init() + { + this.packageManager = this.TestFactory.CreatePackageManager(); + this.installDir = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + + // Create a composite source + var options = this.TestFactory.CreateCreateCompositePackageCatalogOptions(); + var testSource = this.packageManager.GetPackageCatalogByName(Constants.TestSourceName); + Assert.NotNull(testSource, $"{Constants.TestSourceName} cannot be null"); + options.Catalogs.Add(testSource); + options.CompositeSearchBehavior = CompositeSearchBehavior.AllCatalogs; + this.compositeSource = this.packageManager.CreateCompositePackageCatalog(options); + } + + /// + /// Test Repair MSI Installer. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task RepairMSIInstaller() + { + // Find a package + var searchResult = this.FindOnePackage(this.compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, "AppInstallerTest.TestMsiRepair"); + + // Configure installation + var installOptions = this.TestFactory.CreateInstallOptions(); + installOptions.PackageInstallMode = PackageInstallMode.Silent; + installOptions.PreferredInstallLocation = this.installDir; + + // Install the package + var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); + + // Find package again, but this time it should be detected as installed + searchResult = this.FindOnePackage(this.compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, "AppInstallerTest.TestMsiRepair"); + Assert.NotNull(searchResult.CatalogPackage.InstalledVersion); + + // Note: The 'msiexec repair' command requires the original installer file to be present at the location registered in the ARP (Add/Remove Programs). + // 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. + // To work around this, we copy the installer file to the ARP source directory before running the repair command. + // 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. + // This would allow the 'msiexec repair' command to function as expected. + string installerSourceDir = TestCommon.CopyInstallerFileToARPInstallSourceDirectory(TestCommon.GetTestDataFile("AppInstallerTestMsiInstallerV2.msi"), Constants.MsiInstallerProductCode, true); + + // Repair the package + var repairOptions = this.TestFactory.CreateRepairOptions(); + repairOptions.PackageRepairMode = PackageRepairMode.Silent; + + //// We need to pass argument to indicate silent repair - this should be added to the RepairOptions class. + //// repairOptions.RepairString - This should be added to the RepairOptions class. + + var repairResult = await this.packageManager.RepairPackageAsync(searchResult.CatalogPackage, repairOptions); + Assert.AreEqual(RepairResultStatus.Ok, repairResult.Status); + + // Uninstall the package + var uninstallResult = await this.packageManager.UninstallPackageAsync(searchResult.CatalogPackage, this.TestFactory.CreateUninstallOptions()); + Assert.AreEqual(UninstallResultStatus.Ok, uninstallResult.Status); + Assert.True(TestCommon.VerifyTestMsiInstalledAndCleanup(this.installDir)); + + if (installerSourceDir != null && Directory.Exists(installerSourceDir)) + { + Directory.Delete(installerSourceDir, true); + } + } + } +} From c9b38a44e4f1807fd5ec07580491a70efc294568 Mon Sep 17 00:00:00 2001 From: Madhusudhan Gumbalapura Sudarshan Date: Thu, 15 Aug 2024 12:08:19 -0700 Subject: [PATCH 03/35] [Updates to PackageManager.idl :] Add DownloadError status and rename InstallerErrorCode - Updated `RepairResultStatus` enum to include `DownloadError`. - Renamed `InstallerErrorCode` to `RepairErrorCode` in `RepairResult`. - Added inline comments for `LogOutputPath` property for directing log output. - Updated `RepairResult.cpp` and `RepairResult.h` for renaming. --- src/Microsoft.Management.Deployment/PackageManager.idl | 4 +++- src/Microsoft.Management.Deployment/RepairResult.cpp | 9 ++++----- src/Microsoft.Management.Deployment/RepairResult.h | 6 +++--- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/Microsoft.Management.Deployment/PackageManager.idl b/src/Microsoft.Management.Deployment/PackageManager.idl index e9e653e3d2..30fb44a58c 100644 --- a/src/Microsoft.Management.Deployment/PackageManager.idl +++ b/src/Microsoft.Management.Deployment/PackageManager.idl @@ -188,6 +188,7 @@ namespace Microsoft.Management.Deployment Ok, BlockedByPolicy, CatalogError, + DownloadError, InternalError, InvalidOptions, RepairError, @@ -214,7 +215,7 @@ namespace Microsoft.Management.Deployment /// The error code from the repair attempt. Only valid if the Status is RepairError. /// This value's meaning will require knowledge of the specific repairer or repair technology. - UInt32 InstallerErrorCode { get; }; + UInt32 RepairErrorCode { get; }; } /// State of the download @@ -1185,6 +1186,7 @@ namespace Microsoft.Management.Deployment /// The string must be JSON encoded. String CorrelationData; + /// Directs the logging to a log file. If provided, the installer must have write access to the file String LogOutputPath; } diff --git a/src/Microsoft.Management.Deployment/RepairResult.cpp b/src/Microsoft.Management.Deployment/RepairResult.cpp index 76860c26ee..7edabfeb20 100644 --- a/src/Microsoft.Management.Deployment/RepairResult.cpp +++ b/src/Microsoft.Management.Deployment/RepairResult.cpp @@ -11,13 +11,13 @@ namespace winrt::Microsoft::Management::Deployment::implementation void RepairResult::Initialize( winrt::Microsoft::Management::Deployment::RepairResultStatus status, winrt::hresult extendedErrorCode, - uint32_t installerErrorCode, + uint32_t repairErrorCode, hstring const& correlationData, bool rebootRequired) { m_status = status; m_extendedErrorCode = extendedErrorCode; - m_installerErrorCode = installerErrorCode; + m_repairErrorCode = repairErrorCode; m_correlationData = correlationData; m_rebootRequired = rebootRequired; } @@ -37,9 +37,8 @@ namespace winrt::Microsoft::Management::Deployment::implementation { return m_extendedErrorCode; } - - uint32_t RepairResult::InstallerErrorCode() + uint32_t RepairResult::RepairErrorCode() { - return m_installerErrorCode; + return m_repairErrorCode; } } diff --git a/src/Microsoft.Management.Deployment/RepairResult.h b/src/Microsoft.Management.Deployment/RepairResult.h index 2f9384e4f5..fd6e016e55 100644 --- a/src/Microsoft.Management.Deployment/RepairResult.h +++ b/src/Microsoft.Management.Deployment/RepairResult.h @@ -13,7 +13,7 @@ namespace winrt::Microsoft::Management::Deployment::implementation void Initialize( winrt::Microsoft::Management::Deployment::RepairResultStatus status, winrt::hresult extendedErrorCode, - uint32_t installerErrorCode, + uint32_t repairErrorCode, hstring const& correlationData, bool rebootRequired); #endif @@ -22,7 +22,7 @@ namespace winrt::Microsoft::Management::Deployment::implementation bool RebootRequired(); winrt::Microsoft::Management::Deployment::RepairResultStatus Status(); winrt::hresult ExtendedErrorCode(); - uint32_t InstallerErrorCode(); + uint32_t RepairErrorCode(); #if !defined(INCLUDE_ONLY_INTERFACE_METHODS) private: @@ -30,7 +30,7 @@ namespace winrt::Microsoft::Management::Deployment::implementation bool m_rebootRequired = false; winrt::Microsoft::Management::Deployment::RepairResultStatus m_status = winrt::Microsoft::Management::Deployment::RepairResultStatus::Ok; winrt::hresult m_extendedErrorCode = S_OK; - uint32_t m_installerErrorCode = 0; + uint32_t m_repairErrorCode = 0; #endif }; } From b208c30a52dd67195d68b927a86a90cad02a0203 Mon Sep 17 00:00:00 2001 From: Madhusudhan Gumbalapura Sudarshan Date: Thu, 15 Aug 2024 12:19:07 -0700 Subject: [PATCH 04/35] Added integration logic for Repair COM APIs: - Integrated Repair COM APIs with workflow. - Added `COMRepairCommand` class. - Updated `ContextOrchestrator` with `CreateItemForRepair`. - Added `GetManifestRepairScope` in `Converters`. - Enhanced `PackageManager` with repair functions and async methods. --- .../Commands/COMCommand.cpp | 10 +- src/AppInstallerCLICore/Commands/COMCommand.h | 10 ++ .../ContextOrchestrator.cpp | 10 +- src/AppInstallerCLICore/ContextOrchestrator.h | 2 + .../Converters.cpp | 15 ++ .../Converters.h | 26 ++- .../PackageManager.cpp | 158 +++++++++++++++++- .../PackageManager.h | 5 + 8 files changed, 221 insertions(+), 15 deletions(-) diff --git a/src/AppInstallerCLICore/Commands/COMCommand.cpp b/src/AppInstallerCLICore/Commands/COMCommand.cpp index 14b5b80a54..c82a576acd 100644 --- a/src/AppInstallerCLICore/Commands/COMCommand.cpp +++ b/src/AppInstallerCLICore/Commands/COMCommand.cpp @@ -7,7 +7,8 @@ #include "Workflows/PromptFlow.h" #include "Workflows/UninstallFlow.h" #include "Workflows/WorkflowBase.h" -#include "Workflows/DependenciesFlow.h" +#include "Workflows/DependenciesFlow.h" +#include "Workflows/RepairFlow.h" namespace AppInstaller::CLI { @@ -45,4 +46,11 @@ namespace AppInstaller::CLI context << Workflow::UninstallSinglePackage; } + + void COMRepairCommand::ExecuteInternal(Execution::Context& context) const + { + context << + Workflow::SelectApplicableInstallerIfNecessary << + Workflow::RepairSinglePackage; + } } diff --git a/src/AppInstallerCLICore/Commands/COMCommand.h b/src/AppInstallerCLICore/Commands/COMCommand.h index 4ecedae490..cd30dc46b1 100644 --- a/src/AppInstallerCLICore/Commands/COMCommand.h +++ b/src/AppInstallerCLICore/Commands/COMCommand.h @@ -33,5 +33,15 @@ namespace AppInstaller::CLI protected: void ExecuteInternal(Execution::Context& context) const override; + }; + + // IMPORTANT: To use this command, the caller should have already retrieved the InstalledPackageVersion and added it to the Context Data + struct COMRepairCommand final : public Command + { + constexpr static std::string_view CommandName = "repair"sv; + COMRepairCommand(std::string_view parent) : Command(CommandName, parent) {} + + protected: + void ExecuteInternal(Execution::Context& context) const override; }; } diff --git a/src/AppInstallerCLICore/ContextOrchestrator.cpp b/src/AppInstallerCLICore/ContextOrchestrator.cpp index 3b7ee3481e..41e35af7ab 100644 --- a/src/AppInstallerCLICore/ContextOrchestrator.cpp +++ b/src/AppInstallerCLICore/ContextOrchestrator.cpp @@ -30,7 +30,7 @@ namespace AppInstaller::CLI::Execution // Get command queue name based on command name. std::string_view GetCommandQueueName(std::string_view commandName) { - if (commandName == COMInstallCommand::CommandName || commandName == COMUninstallCommand::CommandName) + if (commandName == COMInstallCommand::CommandName || commandName == COMUninstallCommand::CommandName || commandName == COMRepairCommand::CommandName) { return OperationCommandQueueName; } @@ -363,6 +363,7 @@ namespace AppInstaller::CLI::Execution case PackageOperationType::Upgrade: return "root:upgrade"sv; case PackageOperationType::Uninstall: return "root:uninstall"sv; case PackageOperationType::Download: return "root:download"sv; + case PackageOperationType::Repair: return "root:repair"sv; default: return "unknown"; } } @@ -394,4 +395,11 @@ namespace AppInstaller::CLI::Execution item->AddCommand(std::make_unique<::AppInstaller::CLI::COMDownloadCommand>(RootCommand::CommandName)); return item; } + + std::unique_ptr OrchestratorQueueItemFactory::CreateItemForRepair(std::wstring packageId, std::wstring sourceId, std::unique_ptr context) + { + std::unique_ptr item = std::make_unique(OrchestratorQueueItemId(std::move(packageId), std::move(sourceId)), std::move(context), PackageOperationType::Repair); + item->AddCommand(std::make_unique<::AppInstaller::CLI::COMRepairCommand>(RootCommand::CommandName)); + return item; + } } diff --git a/src/AppInstallerCLICore/ContextOrchestrator.h b/src/AppInstallerCLICore/ContextOrchestrator.h index 6a99ea3e61..5b6c17e977 100644 --- a/src/AppInstallerCLICore/ContextOrchestrator.h +++ b/src/AppInstallerCLICore/ContextOrchestrator.h @@ -47,6 +47,7 @@ namespace AppInstaller::CLI::Execution Upgrade, Uninstall, Download, + Repair, }; struct OrchestratorQueueItem @@ -101,6 +102,7 @@ namespace AppInstaller::CLI::Execution static std::unique_ptr CreateItemForSearch(std::wstring packageId, std::wstring sourceId, std::unique_ptr context); // Create queue item for download static std::unique_ptr CreateItemForDownload(std::wstring packageId, std::wstring sourceId, std::unique_ptr context); + static std::unique_ptr CreateItemForRepair(std::wstring packageId, std::wstring sourceId, std::unique_ptr context); }; struct ContextOrchestrator diff --git a/src/Microsoft.Management.Deployment/Converters.cpp b/src/Microsoft.Management.Deployment/Converters.cpp index b17c3dd322..e016a95dd4 100644 --- a/src/Microsoft.Management.Deployment/Converters.cpp +++ b/src/Microsoft.Management.Deployment/Converters.cpp @@ -353,6 +353,21 @@ namespace winrt::Microsoft::Management::Deployment::implementation return ::AppInstaller::Manifest::ScopeEnum::Unknown; } + ::AppInstaller::Manifest::ScopeEnum GetManifestRepairScope(winrt::Microsoft::Management::Deployment::PackageRepairScope scope) + { + switch (scope) + { + case winrt::Microsoft::Management::Deployment::PackageRepairScope::Any: + return ::AppInstaller::Manifest::ScopeEnum::Unknown; + case winrt::Microsoft::Management::Deployment::PackageRepairScope::User: + return ::AppInstaller::Manifest::ScopeEnum::User; + case winrt::Microsoft::Management::Deployment::PackageRepairScope::System: + return ::AppInstaller::Manifest::ScopeEnum::Machine; + } + + return ::AppInstaller::Manifest::ScopeEnum::Unknown; + } + winrt::Microsoft::Management::Deployment::ElevationRequirement GetDeploymentElevationRequirement(::AppInstaller::Manifest::ElevationRequirementEnum elevationRequirement) { switch (elevationRequirement) diff --git a/src/Microsoft.Management.Deployment/Converters.h b/src/Microsoft.Management.Deployment/Converters.h index e07f3dc70c..e3b100c07a 100644 --- a/src/Microsoft.Management.Deployment/Converters.h +++ b/src/Microsoft.Management.Deployment/Converters.h @@ -30,8 +30,9 @@ namespace winrt::Microsoft::Management::Deployment::implementation winrt::Microsoft::Management::Deployment::AuthenticationType GetDeploymentAuthenticationType(::AppInstaller::Authentication::AuthenticationType authType); ::AppInstaller::Authentication::AuthenticationMode GetAuthenticationMode(winrt::Microsoft::Management::Deployment::AuthenticationMode authMode); ::AppInstaller::Authentication::AuthenticationArguments GetAuthenticationArguments(winrt::Microsoft::Management::Deployment::AuthenticationArguments authArgs); + ::AppInstaller::Manifest::ScopeEnum GetManifestRepairScope(winrt::Microsoft::Management::Deployment::PackageRepairScope scope); -#define WINGET_GET_OPERATION_RESULT_STATUS(_installResultStatus_, _uninstallResultStatus_, _downloadResultStatus_) \ +#define WINGET_GET_OPERATION_RESULT_STATUS(_installResultStatus_, _uninstallResultStatus_, _downloadResultStatus_, _repairResultStatus_) \ if constexpr (std::is_same_v) \ { \ resultStatus = TStatus::_installResultStatus_; \ @@ -44,6 +45,10 @@ namespace winrt::Microsoft::Management::Deployment::implementation { \ resultStatus = TStatus::_downloadResultStatus_; \ } \ + else if constexpr (std::is_same_v) \ + { \ + resultStatus = TStatus::_repairResultStatus_; \ + } \ template TStatus GetOperationResultStatus(::AppInstaller::CLI::Workflow::ExecutionStage executionStage, winrt::hresult hresult) @@ -70,19 +75,26 @@ namespace winrt::Microsoft::Management::Deployment::implementation resultStatus = TStatus::InvalidOptions; break; case APPINSTALLER_CLI_ERROR_NO_APPLICABLE_INSTALLER: - WINGET_GET_OPERATION_RESULT_STATUS(NoApplicableInstallers, InternalError, NoApplicableInstallers); + WINGET_GET_OPERATION_RESULT_STATUS(NoApplicableInstallers, InternalError, NoApplicableInstallers, NoApplicableInstallers); break; case APPINSTALLER_CLI_ERROR_UPDATE_NOT_APPLICABLE: case APPINSTALLER_CLI_ERROR_UPGRADE_VERSION_UNKNOWN: case APPINSTALLER_CLI_ERROR_UPGRADE_VERSION_NOT_NEWER: - WINGET_GET_OPERATION_RESULT_STATUS(NoApplicableUpgrade, InternalError, InternalError); + WINGET_GET_OPERATION_RESULT_STATUS(NoApplicableUpgrade, InternalError, InternalError, InternalError); break; case APPINSTALLER_CLI_ERROR_NO_UNINSTALL_INFO_FOUND: case APPINSTALLER_CLI_ERROR_EXEC_UNINSTALL_COMMAND_FAILED: - WINGET_GET_OPERATION_RESULT_STATUS(InstallError, UninstallError, InternalError); + WINGET_GET_OPERATION_RESULT_STATUS(InstallError, UninstallError, InternalError, InternalError); + break; + case APPINSTALLER_CLI_ERROR_NO_REPAIR_INFO_FOUND: + case APPINSTALLER_CLI_ERROR_REPAIR_NOT_APPLICABLE: + case APPINSTALLER_CLI_ERROR_EXEC_REPAIR_FAILED: + case APPINSTALLER_CLI_ERROR_REPAIR_NOT_SUPPORTED: + case APPINSTALLER_CLI_ERROR_ADMIN_CONTEXT_REPAIR_PROHIBITED: + WINGET_GET_OPERATION_RESULT_STATUS(InstallError, UninstallError, InternalError, RepairError); break; case APPINSTALLER_CLI_ERROR_PACKAGE_AGREEMENTS_NOT_ACCEPTED: - WINGET_GET_OPERATION_RESULT_STATUS(PackageAgreementsNotAccepted, InternalError, PackageAgreementsNotAccepted); + WINGET_GET_OPERATION_RESULT_STATUS(PackageAgreementsNotAccepted, InternalError, PackageAgreementsNotAccepted, PackageAgreementsNotAccepted); break; case APPINSTALLER_CLI_ERROR_CANNOT_WRITE_TO_UPLEVEL_INDEX: case APPINSTALLER_CLI_ERROR_INDEX_INTEGRITY_COMPROMISED: @@ -110,13 +122,13 @@ namespace winrt::Microsoft::Management::Deployment::implementation resultStatus = TStatus::CatalogError; break; case ::AppInstaller::CLI::Workflow::ExecutionStage::Download: - WINGET_GET_OPERATION_RESULT_STATUS(DownloadError, InternalError, DownloadError); + WINGET_GET_OPERATION_RESULT_STATUS(DownloadError, InternalError, DownloadError, DownloadError); break; case ::AppInstaller::CLI::Workflow::ExecutionStage::PreExecution: resultStatus = TStatus::InternalError; break; case ::AppInstaller::CLI::Workflow::ExecutionStage::Execution: - WINGET_GET_OPERATION_RESULT_STATUS(InstallError, UninstallError, InternalError); + WINGET_GET_OPERATION_RESULT_STATUS(InstallError, UninstallError, InternalError, RepairError); break; case ::AppInstaller::CLI::Workflow::ExecutionStage::PostExecution: resultStatus = TStatus::InternalError; diff --git a/src/Microsoft.Management.Deployment/PackageManager.cpp b/src/Microsoft.Management.Deployment/PackageManager.cpp index ee437bf315..9cd4ca5298 100644 --- a/src/Microsoft.Management.Deployment/PackageManager.cpp +++ b/src/Microsoft.Management.Deployment/PackageManager.cpp @@ -25,6 +25,7 @@ #include "DownloadResult.h" #include "InstallResult.h" #include "UninstallResult.h" +#include "RepairResult.h" #include "PackageCatalogInfo.h" #include "PackageCatalogReference.h" #include "PackageVersionInfo.h" @@ -215,6 +216,13 @@ namespace winrt::Microsoft::Management::Deployment::implementation return *downloadResult; } + winrt::Microsoft::Management::Deployment::RepairResult GetRepairResult(::Workflow::ExecutionStage executionStage, winrt::hresult terminationHR, uint32_t repairError, winrt::hstring correlationData, bool rebootRequired) + { + winrt::Microsoft::Management::Deployment::RepairResultStatus repairResultStatus = GetOperationResultStatus(executionStage, terminationHR); + auto repairResult = winrt::make_self>(); + repairResult->Initialize(repairResultStatus, terminationHR, repairError, correlationData, rebootRequired); + return *repairResult; + } template TResult GetOperationResult(::Workflow::ExecutionStage executionStage, winrt::hresult terminationHR, uint32_t operationError, winrt::hstring correlationData, bool rebootRequired) @@ -231,9 +239,13 @@ namespace winrt::Microsoft::Management::Deployment::implementation { return GetDownloadResult(executionStage, terminationHR, correlationData); } + else if constexpr (std::is_same_v) + { + return GetRepairResult(executionStage, terminationHR, operationError, correlationData, rebootRequired); + } } -#define WINGET_GET_PROGRESS_STATE(_installState_, _uninstallState_) \ +#define WINGET_GET_PROGRESS_STATE(_installState_, _uninstallState_, _repairState_) \ if constexpr (std::is_same_v) \ { \ progressState = TState::_installState_; \ @@ -241,7 +253,11 @@ namespace winrt::Microsoft::Management::Deployment::implementation else if constexpr (std::is_same_v) \ { \ progressState = TState::_uninstallState_; \ - } + } \ + else if constexpr (std::is_same_v) \ + { \ + progressState = TState::_repairState_; \ + } \ template std::optional GetProgress( @@ -289,7 +305,7 @@ namespace winrt::Microsoft::Management::Deployment::implementation // Wait until installer starts to report operation. break; case ::Workflow::ExecutionStage::Execution: - WINGET_GET_PROGRESS_STATE(Installing, Uninstalling); + WINGET_GET_PROGRESS_STATE(Installing, Uninstalling, Repairing); downloadProgress = 1; if (reportType == ReportType::ExecutionPhaseUpdate) { @@ -317,7 +333,7 @@ namespace winrt::Microsoft::Management::Deployment::implementation { // Send PostInstall progress when it switches to PostExecution phase. reportProgress = true; - WINGET_GET_PROGRESS_STATE(PostInstall, PostUninstall); + WINGET_GET_PROGRESS_STATE(PostInstall, PostUninstall, PostRepair); downloadProgress = 1; operationProgress = 1; } @@ -340,6 +356,11 @@ namespace winrt::Microsoft::Management::Deployment::implementation TProgress progress{ progressState, downloadBytesDownloaded, downloadBytesRequired, downloadProgress }; return progress; } + else if constexpr (std::is_same_v) + { + TProgress progress{ progressState, operationProgress }; + return progress; + } } else { @@ -548,6 +569,45 @@ namespace winrt::Microsoft::Management::Deployment::implementation } } + void PopulateContextFromRepairOptions( + ::AppInstaller::CLI::Execution::Context* context, + winrt::Microsoft::Management::Deployment::RepairOptions options) + { + if (options) + { + if (!options.LogOutputPath().empty()) + { + context->Args.AddArg(Execution::Args::Type::Log, ::AppInstaller::Utility::ConvertToUTF8(options.LogOutputPath())); + context->Args.AddArg(Execution::Args::Type::VerboseLogs); + } + + if (!options.DownloadDirectory().empty()) + { + context->Args.AddArg(Execution::Args::Type::DownloadDirectory, ::AppInstaller::Utility::ConvertToUTF8(options.DownloadDirectory())); + } + + if (options.PackageRepairMode() == PackageRepairMode::Interactive) + { + context->Args.AddArg(Execution::Args::Type::Interactive); + } + else if (options.PackageRepairMode() == PackageRepairMode::Silent) + { + context->Args.AddArg(Execution::Args::Type::Silent); + } + + if (options.AcceptPackageAgreements()) + { + context->Args.AddArg(Execution::Args::Type::AcceptPackageAgreements); + } + + auto repairScope = GetManifestRepairScope(options.PackageRepairScope()); + if (repairScope != ::AppInstaller::Manifest::ScopeEnum::Unknown) + { + context->Args.AddArg(Execution::Args::Type::InstallScope, ScopeToString(repairScope)); + } + } + } + template std::unique_ptr CreateContextFromOperationOptions( TOptions options, @@ -571,6 +631,10 @@ namespace winrt::Microsoft::Management::Deployment::implementation { PopulateContextFromDownloadOptions(context.get(), options); } + else if constexpr (std::is_same_v) + { + PopulateContextFromRepairOptions(context.get(), options); + } return context; } @@ -697,6 +761,24 @@ namespace winrt::Microsoft::Management::Deployment::implementation return Execution::OrchestratorQueueItemFactory::CreateItemForDownload(std::wstring{ package.Id() }, std::wstring{ packageVersionInfo.PackageCatalog().Info().Id() }, std::move(comContext)); } + std::unique_ptr CreateQueueItemForRepair( + std::unique_ptr<::AppInstaller::CLI::Execution::COMContext> comContext, + winrt::Microsoft::Management::Deployment::CatalogPackage package, + winrt::Microsoft::Management::Deployment::RepairOptions options) + { + // Add installed version + AddInstalledVersionToContext(package.InstalledVersion(), comContext.get()); + + // Add Package which is used to co-relate installed package with available package for repair + winrt::Microsoft::Management::Deployment::implementation::CatalogPackage* catalogPackageImpl = get_self(package); + std::shared_ptr<::AppInstaller::Repository::ICompositePackage> internalPackage = catalogPackageImpl->GetRepositoryPackage(); + comContext->Add(internalPackage); + + comContext->SetFlags(AppInstaller::CLI::Execution::ContextFlag::InstallerExecutionUseRepair); + + return Execution::OrchestratorQueueItemFactory::CreateItemForRepair(std::wstring{ package.Id() }, std::wstring{ package.InstalledVersion().PackageCatalog().Info().Id() }, std::move(comContext)); + } + template winrt::Windows::Foundation::IAsyncOperationWithProgress GetPackageOperation( bool canCancelQueueItem, @@ -737,6 +819,10 @@ namespace winrt::Microsoft::Management::Deployment::implementation { queueItem = CreateQueueItemForDownload(std::move(comContext), package, options); } + else if constexpr (std::is_same_v) + { + queueItem = CreateQueueItemForRepair(std::move(comContext), package, options); + } Execution::ContextOrchestrator::Instance().EnqueueAndRunItem(queueItem); @@ -755,6 +841,11 @@ namespace winrt::Microsoft::Management::Deployment::implementation TProgress queuedProgress{ TProgressState::Queued, 0 }; report_progress(queuedProgress); } + else if constexpr (std::is_same_v) + { + TProgress queuedProgress{ TProgressState::Queued, 0 }; + report_progress(queuedProgress); + } } { // correlation data is not passed in when retrieving an existing queue item, so get it from the existing context. @@ -1066,14 +1157,69 @@ namespace winrt::Microsoft::Management::Deployment::implementation return GetPackageOperation(true, std::move(queueItem)); } +#define WINGET_RETURN_REPAIR_RESULT_HR_IF(hr, boolVal) { if(boolVal) { return GetEmptyAsynchronousResultForOperation(hr, correlationData); }} +#define WINGET_RETURN_REPAIR_RESULT_HR_IF_FAILED(hr) { WINGET_RETURN_REPAIR_RESULT_HR_IF(hr, FAILED(hr)) } + winrt::Windows::Foundation::IAsyncOperationWithProgress PackageManager::RepairPackageAsync(winrt::Microsoft::Management::Deployment::CatalogPackage package, winrt::Microsoft::Management::Deployment::RepairOptions options) { - return winrt::Windows::Foundation::IAsyncOperationWithProgress(); + hstring correlationData = (options) ? options.CorrelationData() : L""; + + // options and catalog can both be null, package must be set. + WINGET_RETURN_REPAIR_RESULT_HR_IF(APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS, !package); + // the package should have an installed version to be uninstalled. + WINGET_RETURN_REPAIR_RESULT_HR_IF(APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS, !package.InstalledVersion()); + + HRESULT hr = S_OK; + std::wstring callerProcessInfoString; + try + { + // Check for permissions and get caller info for telemetry. + // This must be done before any co_awaits since it requires info from the rpc caller thread. + auto [hrGetCallerId, callerProcessId] = GetCallerProcessId(); + WINGET_RETURN_REPAIR_RESULT_HR_IF_FAILED(hrGetCallerId); + WINGET_RETURN_REPAIR_RESULT_HR_IF_FAILED(EnsureProcessHasCapability(Capability::PackageManagement, callerProcessId)); + callerProcessInfoString = TryGetCallerProcessInfo(callerProcessId); + } + WINGET_CATCH_STORE(hr, APPINSTALLER_CLI_ERROR_COMMAND_FAILED); + WINGET_RETURN_REPAIR_RESULT_HR_IF_FAILED(hr); + + return GetPackageOperation( + true /*canCancelQueueItem*/, nullptr /*queueItem*/, package, options, std::move(callerProcessInfoString)); } winrt::Windows::Foundation::IAsyncOperationWithProgress PackageManager::GetRepairProgress(winrt::Microsoft::Management::Deployment::CatalogPackage package, winrt::Microsoft::Management::Deployment::PackageCatalogInfo catalogInfo) { - return winrt::Windows::Foundation::IAsyncOperationWithProgress(); + hstring correlationData; + WINGET_RETURN_REPAIR_RESULT_HR_IF(APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS, !package); + + HRESULT hr = S_OK; + std::shared_ptr queueItem = nullptr; + bool canCancelQueueItem = false; + try + { + // Check for permissions + // This must be done before any co_awaits since it requires info from the rpc caller thread. + auto [hrGetCallerId, callerProcessId] = GetCallerProcessId(); + WINGET_RETURN_REPAIR_RESULT_HR_IF_FAILED(hrGetCallerId); + canCancelQueueItem = SUCCEEDED(EnsureProcessHasCapability(Capability::PackageManagement, callerProcessId)); + if (!canCancelQueueItem) + { + WINGET_RETURN_REPAIR_RESULT_HR_IF_FAILED(EnsureProcessHasCapability(Capability::PackageQuery, callerProcessId)); + } + + // Get the queueItem synchronously. + queueItem = GetExistingQueueItemForPackage(package, catalogInfo); + if (queueItem == nullptr || + queueItem->GetPackageOperationType() != PackageOperationType::Repair) + { + return nullptr; + } + } + WINGET_CATCH_STORE(hr, APPINSTALLER_CLI_ERROR_COMMAND_FAILED); + WINGET_RETURN_REPAIR_RESULT_HR_IF_FAILED(hr); + + return GetPackageOperation( + canCancelQueueItem, std::move(queueItem)); } CoCreatableMicrosoftManagementDeploymentClass(PackageManager); } diff --git a/src/Microsoft.Management.Deployment/PackageManager.h b/src/Microsoft.Management.Deployment/PackageManager.h index 3b07342be3..f2aa575d5e 100644 --- a/src/Microsoft.Management.Deployment/PackageManager.h +++ b/src/Microsoft.Management.Deployment/PackageManager.h @@ -41,6 +41,11 @@ namespace winrt::Microsoft::Management::Deployment::implementation DownloadPackageAsync(winrt::Microsoft::Management::Deployment::CatalogPackage package, winrt::Microsoft::Management::Deployment::DownloadOptions options); winrt::Windows::Foundation::IAsyncOperationWithProgress GetDownloadProgress(winrt::Microsoft::Management::Deployment::CatalogPackage package, winrt::Microsoft::Management::Deployment::PackageCatalogInfo catalogInfo); + // Contract 11.0 + winrt::Windows::Foundation::IAsyncOperationWithProgress + RepairPackageAsync(winrt::Microsoft::Management::Deployment::CatalogPackage package, winrt::Microsoft::Management::Deployment::RepairOptions options); + winrt::Windows::Foundation::IAsyncOperationWithProgress + GetRepairProgress(winrt::Microsoft::Management::Deployment::CatalogPackage package, winrt::Microsoft::Management::Deployment::PackageCatalogInfo catalogInfo); }; #if !defined(INCLUDE_ONLY_INTERFACE_METHODS) From 79a42200b214c00b24b4ba28cd4e88948bb2bf5b Mon Sep 17 00:00:00 2001 From: Madhusudhan Gumbalapura Sudarshan Date: Thu, 15 Aug 2024 14:21:15 -0700 Subject: [PATCH 05/35] Added E2E tests for Winget COM Repair API: - Introduced RepairInterop.cs for InProc & Out of Proc COM Repair APIs. - Extended GroupPolicyForInterop.cs to cover COM Repair API scenarios. --- .../Interop/GroupPolicyForInterop.cs | 40 ++- .../Interop/RepairInterop.cs | 311 ++++++++++++++++-- ...stBurnInstaller.MissingRepairBehavior.yaml | 1 - .../TestBurnInstaller.ModifyRepair.yaml | 1 - ...urnInstaller.ModifyRepairWithNoModify.yaml | 1 - .../TestExeInstaller.UninstallerRepair.yaml | 1 - ...staller.UninstallerRepairWithNoRepair.yaml | 1 - .../TestInnoInstaller.InstallerRepair.yaml | 1 - ...nnoInstaller.UserScopeInstallerRepair.yaml | 18 + ...stNullsoftInstaller.UninstallerRepair.yaml | 1 - 10 files changed, 339 insertions(+), 37 deletions(-) create mode 100644 src/AppInstallerCLIE2ETests/TestData/Manifests/TestInnoInstaller.UserScopeInstallerRepair.yaml diff --git a/src/AppInstallerCLIE2ETests/Interop/GroupPolicyForInterop.cs b/src/AppInstallerCLIE2ETests/Interop/GroupPolicyForInterop.cs index d4d215efa0..d5e0456c21 100644 --- a/src/AppInstallerCLIE2ETests/Interop/GroupPolicyForInterop.cs +++ b/src/AppInstallerCLIE2ETests/Interop/GroupPolicyForInterop.cs @@ -8,6 +8,7 @@ namespace AppInstallerCLIE2ETests.Interop { using System; using System.IO; + using System.Text; using System.Threading.Tasks; using AppInstallerCLIE2ETests.Helpers; using Microsoft.Management.Deployment; @@ -86,6 +87,10 @@ public void DisableWinGetPolicy() Assert.AreEqual(Constants.BlockByWinGetPolicyErrorMessage, groupPolicyException.Message); Assert.AreEqual(Constants.ErrorCode.ERROR_BLOCKED_BY_POLICY, groupPolicyException.HResult); + groupPolicyException = Assert.Catch(() => { RepairOptions repairOptions = this.TestFactory.CreateRepairOptions(); }); + Assert.AreEqual(Constants.BlockByWinGetPolicyErrorMessage, groupPolicyException.Message); + Assert.AreEqual(Constants.ErrorCode.ERROR_BLOCKED_BY_POLICY, groupPolicyException.HResult); + // PackageManagerSettings is not implemented in context OutOfProcDev if (this.TestFactory.Context == ClsidContext.InProc) { @@ -115,34 +120,59 @@ public async Task DisableWinGetCommandLineInterfacesPolicy() options.CompositeSearchBehavior = CompositeSearchBehavior.AllCatalogs; PackageCatalogReference compositeSource = packageManager.CreateCompositePackageCatalog(options); + string testPackageId = "AppInstallerTest.TestModifyRepair"; + // Find package - var searchResult = this.FindOnePackage(compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, Constants.ExeInstallerPackageId); + var searchResult = this.FindOnePackage(compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, testPackageId); // Configure installation var installOptions = this.TestFactory.CreateInstallOptions(); installOptions.PackageInstallMode = PackageInstallMode.Silent; - installOptions.PreferredInstallLocation = installDir; installOptions.AcceptPackageAgreements = true; + StringBuilder replacementArgs = new StringBuilder(); + replacementArgs.Append($"/InstallDir {installDir} /Version 2.0.0.0 /DisplayName TestModifyRepair"); + + // For In-proc calls runs in Admin context in the test environment, repair functionality has restriction that user scope installed package cannot be repaired in admin elevate context. for security concerns + // This is a test coverage workaround that in proc calls will install the package in machine scope where as out of proc calls will install the package in user scope. + if (this.TestFactory.Context == ClsidContext.InProc) + { + replacementArgs.Append(" /UseHKLM"); + } + + installOptions.ReplacementInstallerArguments = replacementArgs.ToString(); + // Install var installResult = await packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); // Find package again, but this time it should detect the installed version - searchResult = this.FindOnePackage(compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, Constants.ExeInstallerPackageId); + searchResult = this.FindOnePackage(compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, testPackageId); Assert.NotNull(searchResult.CatalogPackage.InstalledVersion); + // Repair + var repairOptions = this.TestFactory.CreateRepairOptions(); + repairOptions.PackageRepairMode = PackageRepairMode.Silent; + var repairResult = await packageManager.RepairPackageAsync(searchResult.CatalogPackage, repairOptions); + Assert.AreEqual(RepairResultStatus.Ok, repairResult.Status); + // Uninstall var uninstallResult = await packageManager.UninstallPackageAsync(searchResult.CatalogPackage, this.TestFactory.CreateUninstallOptions()); Assert.AreEqual(UninstallResultStatus.Ok, uninstallResult.Status); Assert.True(TestCommon.VerifyTestExeUninstalled(installDir)); + // Clean up. + if (Directory.Exists(installDir)) + { + Directory.Delete(installDir, true); + } + // Download var downloadResult = await packageManager.DownloadPackageAsync(searchResult.CatalogPackage, this.TestFactory.CreateDownloadOptions()); Assert.AreEqual(DownloadResultStatus.Ok, downloadResult.Status); var packageVersion = "2.0.0.0"; - string downloadDir = Path.Combine(TestCommon.GetDefaultDownloadDirectory(), $"{Constants.ExeInstallerPackageId}_{packageVersion}"); - Assert.True(TestCommon.VerifyInstallerDownload(downloadDir, "TestExeInstaller", packageVersion, ProcessorArchitecture.X86, TestCommon.Scope.Unknown, PackageInstallerType.Exe)); + string downloadDir = Path.Combine(TestCommon.GetDefaultDownloadDirectory(), $"{testPackageId}_{packageVersion}"); + Assert.True(TestCommon.VerifyInstallerDownload(downloadDir, "TestModifyRepair", packageVersion, ProcessorArchitecture.X86, TestCommon.Scope.Unknown, PackageInstallerType.Burn, "en-US")); } } } diff --git a/src/AppInstallerCLIE2ETests/Interop/RepairInterop.cs b/src/AppInstallerCLIE2ETests/Interop/RepairInterop.cs index d0561ee30d..be9d670c1f 100644 --- a/src/AppInstallerCLIE2ETests/Interop/RepairInterop.cs +++ b/src/AppInstallerCLIE2ETests/Interop/RepairInterop.cs @@ -8,6 +8,7 @@ namespace AppInstallerCLIE2ETests.Interop { using System; using System.IO; + using System.Text; using System.Threading.Tasks; using AppInstallerCLIE2ETests.Helpers; using Microsoft.Management.Deployment; @@ -17,7 +18,7 @@ namespace AppInstallerCLIE2ETests.Interop /// /// Repair Interop Tests for COM/WinRT Interop classes. /// - ////[TestFixtureSource(typeof(InstanceInitializersSource), nameof(InstanceInitializersSource.InProcess), Category = nameof(InstanceInitializersSource.InProcess))] + [TestFixtureSource(typeof(InstanceInitializersSource), nameof(InstanceInitializersSource.InProcess), Category = nameof(InstanceInitializersSource.InProcess))] [TestFixtureSource(typeof(InstanceInitializersSource), nameof(InstanceInitializersSource.OutOfProcess), Category = nameof(InstanceInitializersSource.OutOfProcess))] public class RepairInterop : BaseInterop { @@ -41,7 +42,7 @@ public RepairInterop(IInstanceInitializer initializer) public void Init() { this.packageManager = this.TestFactory.CreatePackageManager(); - this.installDir = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + this.installDir = TestCommon.GetRandomTestDir(); // Create a composite source var options = this.TestFactory.CreateCreateCompositePackageCatalogOptions(); @@ -58,49 +59,309 @@ public void Init() /// A representing the asynchronous unit test. [Test] public async Task RepairMSIInstaller() + { + string msiInstallerPackageId = "AppInstallerTest.TestMsiRepair"; + var searchResult = await this.FindAndInstallPackage(msiInstallerPackageId, this.installDir, null); + + // Note: The 'msiexec repair' command requires the original installer file to be present at the location registered in the ARP (Add/Remove Programs). + // 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. + // To work around this, we copy the installer file to the ARP source directory before running the repair command. + // 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. + // This would allow the 'msiexec repair' command to function as expected. + string installerSourceDir = TestCommon.CopyInstallerFileToARPInstallSourceDirectory(TestCommon.GetTestDataFile("AppInstallerTestMsiInstallerV2.msi"), Constants.MsiInstallerProductCode, true); + + // Repair the package + await this.RepairPackageAndValidateStatus(searchResult.CatalogPackage, this.TestFactory.CreateRepairOptions(), RepairResultStatus.Ok); + + // Cleanup + Assert.True(TestCommon.VerifyTestMsiInstalledAndCleanup(this.installDir)); + + if (installerSourceDir != null && Directory.Exists(installerSourceDir)) + { + Directory.Delete(installerSourceDir, true); + } + } + + /// + /// Test Repair MSIX Installer. + /// + /// representing the asynchronous unit test. + [Test] + public async Task RepairNonStoreMSIXPackage() + { + string msixPackageId = "AppInstallerTest.TestMsixInstaller"; + var searchResult = await this.FindAndInstallPackage(msixPackageId, this.installDir, null); + + // Repair the package + await this.RepairPackageAndValidateStatus(searchResult.CatalogPackage, this.TestFactory.CreateRepairOptions(), RepairResultStatus.Ok); + + // Cleanup + Assert.True(TestCommon.VerifyTestMsixInstalledAndCleanup()); + } + + /// + /// Test MSIX non-store package repair with machine scope. + /// + /// representing the asynchronous unit test. + [Test] + public async Task RepairNonStoreMsixPackageWithMachineScope() + { + // Find package again, but this time it should be detected as installed + var searchResult = this.FindOnePackage(this.compositeSource, PackageMatchField.Name, PackageFieldMatchOption.Equals, "Paint"); + + if (searchResult == null || + (searchResult != null && searchResult.CatalogPackage == null) || + (searchResult != null && searchResult.CatalogPackage.InstalledVersion == null)) + { + Assert.Ignore("Test skipped as Microsoft.Paint_8wekyb3d8bbwe is not installed."); + } + + // Repair the package + var repairOptions = this.TestFactory.CreateRepairOptions(); + repairOptions.PackageRepairScope = PackageRepairScope.System; + + await this.RepairPackageAndValidateStatus(searchResult.CatalogPackage, repairOptions, RepairResultStatus.RepairError, Constants.ErrorCode.ERROR_INSTALL_SYSTEM_NOT_SUPPORTED); + } + + /// + /// Test repair of a Burn installer that has a "modify" repair behavior specified in the manifest. + /// + /// representing the asynchronous unit test. + [Test] + public async Task RepairBurnInstallerWithModifyBehavior() + { + string burnInstallerPackageId = "AppInstallerTest.TestModifyRepair"; + var replaceInstallerArguments = this.GetReplacementArgumentsBasedTestFixtureContext($"/InstallDir {this.installDir} /Version 2.0.0.0 /DisplayName TestModifyRepair"); + + var searchResult = await this.FindAndInstallPackage(burnInstallerPackageId, this.installDir, replaceInstallerArguments.ToString()); + + // Repair the package + var repairOptions = this.TestFactory.CreateRepairOptions(); + repairOptions.PackageRepairMode = PackageRepairMode.Silent; + + await this.RepairPackageAndValidateStatus(searchResult.CatalogPackage, repairOptions, RepairResultStatus.Ok); + + // Cleanup + Assert.True(TestCommon.VerifyTestExeRepairCompletedAndCleanup(this.installDir, "Modify Repair operation")); + } + + /// + /// Tests the repair operation of a Burn installer that was installed in user scope but is being repaired in an admin context. + /// + /// representing the asynchronous unit test. + [Test] + public async Task RepairBurnInstallerInAdminContextWithUserScopeInstall() + { + if (this.TestFactory.Context != ClsidContext.InProc) + { + Assert.Ignore("Test is only applicable for InProc context."); + } + + string burnInstallerPackageId = "AppInstallerTest.TestUserScopeInstallRepairInAdminContext"; + string replaceInstallerArguments = $"/InstallDir {this.installDir} /Version 2.0.0.0 /DisplayName TestUserScopeInstallRepairInAdminContext"; + + var searchResult = await this.FindAndInstallPackage(burnInstallerPackageId, this.installDir, replaceInstallerArguments); + + // Repair the package + var repairOptions = this.TestFactory.CreateRepairOptions(); + repairOptions.PackageRepairMode = PackageRepairMode.Silent; + + await this.RepairPackageAndValidateStatus(searchResult.CatalogPackage, repairOptions, RepairResultStatus.RepairError, Constants.ErrorCode.ERROR_ADMIN_CONTEXT_REPAIR_PROHIBITED); + + // Cleanup + TestCommon.CleanupTestExeAndDirectory(this.installDir); + } + + /// + /// Test repair of a Exe installer that has a "uninstaller" repair behavior specified in the manifest and NoModify ARP flag set. + /// + /// representing the asynchronous unit test. + [Test] + public async Task RepairBurnInstallerWithModifyBehaviorAndNoModifyFlag() + { + string burnInstallerPackageId = "AppInstallerTest.TestModifyRepairWithNoModify"; + var replaceInstallerArguments = this.GetReplacementArgumentsBasedTestFixtureContext($"/InstallDir {this.installDir} /Version 2.0.0.0 /DisplayName TestModifyRepairWithNoModify"); + replaceInstallerArguments.Append(" /NoModify"); + + var searchResult = await this.FindAndInstallPackage(burnInstallerPackageId, this.installDir, replaceInstallerArguments.ToString()); + + // Repair the package + var repairOptions = this.TestFactory.CreateRepairOptions(); + repairOptions.PackageRepairMode = PackageRepairMode.Silent; + + await this.RepairPackageAndValidateStatus(searchResult.CatalogPackage, repairOptions, RepairResultStatus.RepairError, Constants.ErrorCode.ERROR_REPAIR_NOT_SUPPORTED); + + // Cleanup + TestCommon.CleanupTestExeAndDirectory(this.installDir); + } + + /// + /// Tests the scenario where the repair operation is not supported for Portable Installer type. + /// + /// representing the asynchronous unit test. + [Test] + public async Task RepairOperationNotSupportedForPortableInstaller() + { + string installDir = TestCommon.GetPortablePackagesDirectory(); + string packageId, commandAlias, fileName, packageDirName, productCode; + packageId = "AppInstallerTest.TestPortableExe"; + packageDirName = productCode = packageId + "_" + Constants.TestSourceIdentifier; + commandAlias = fileName = "AppInstallerTestExeInstaller.exe"; + + var searchResult = await this.FindAndInstallPackage(packageId, null, null); + + // Repair the package + var repairOptions = this.TestFactory.CreateRepairOptions(); + repairOptions.PackageRepairMode = PackageRepairMode.Silent; + + await this.RepairPackageAndValidateStatus(searchResult.CatalogPackage, repairOptions, RepairResultStatus.RepairError, Constants.ErrorCode.ERROR_REPAIR_NOT_SUPPORTED); + + // If no location specified, default behavior is to create a package directory with the name "{packageId}_{sourceId}" + TestCommon.VerifyPortablePackage(Path.Combine(installDir, packageDirName), commandAlias, fileName, productCode, true); + } + + /// + /// Test repair of a Exe installer that has a "uninstaller" repair behavior specified in the manifest. + /// + /// representing the asynchronous unit test. + [Test] + public async Task RepairExeInstallerWithUninstallerBehavior() + { + string exeInstallerPackageId = "AppInstallerTest.UninstallerRepair"; + var replaceInstallerArguments = this.GetReplacementArgumentsBasedTestFixtureContext($"/InstallDir {this.installDir} /Version 2.0.0.0 /DisplayName UninstallerRepair"); + + var searchResult = await this.FindAndInstallPackage(exeInstallerPackageId, this.installDir, replaceInstallerArguments.ToString()); + + // Repair the package + await this.RepairPackageAndValidateStatus(searchResult.CatalogPackage, this.TestFactory.CreateRepairOptions(), RepairResultStatus.Ok); + + // Cleanup + Assert.True(TestCommon.VerifyTestExeRepairCompletedAndCleanup(this.installDir, "Uninstaller Repair operation")); + } + + /// + /// Test repair of a Exe installer that has a "uninstaller" repair behavior specified in the manifest and NoRepair ARP flag set. + /// + /// representing the asynchronous unit test. + [Test] + public async Task RepairExeInstallerWithUninstallerBehaviorAndNoRepairFlag() + { + string exeInstallerPackageId = "AppInstallerTest.UninstallerRepairWithNoRepair"; + var replaceInstallerArguments = this.GetReplacementArgumentsBasedTestFixtureContext($"/InstallDir {this.installDir} /Version 2.0.0.0 /DisplayName UninstallerRepairWithNoRepair"); + replaceInstallerArguments.Append(" /NoRepair"); + + var searchResult = await this.FindAndInstallPackage(exeInstallerPackageId, this.installDir, replaceInstallerArguments.ToString()); + + // Repair the package + await this.RepairPackageAndValidateStatus(searchResult.CatalogPackage, this.TestFactory.CreateRepairOptions(), RepairResultStatus.RepairError, Constants.ErrorCode.ERROR_REPAIR_NOT_SUPPORTED); + + // Cleanup + TestCommon.CleanupTestExeAndDirectory(this.installDir); + } + + /// + /// Test repair of a Nullsoft installer that has a "uninstaller" repair behavior specified in the manifest. + /// + /// representing the asynchronous unit test. + [Test] + public async Task RepairNullsoftInstallerWithUninstallerBehavior() + { + string nullsoftTestPackageId = "AppInstallerTest.NullsoftUninstallerRepair"; + var replaceInstallerArguments = this.GetReplacementArgumentsBasedTestFixtureContext($"/InstallDir {this.installDir} /Version 2.0.0.0 /DisplayName NullsoftUninstallerRepair"); + + var searchResult = await this.FindAndInstallPackage(nullsoftTestPackageId, this.installDir, replaceInstallerArguments.ToString()); + + // Repair the package + await this.RepairPackageAndValidateStatus(searchResult.CatalogPackage, this.TestFactory.CreateRepairOptions(), RepairResultStatus.Ok); + + // Cleanup + Assert.True(TestCommon.VerifyTestExeRepairCompletedAndCleanup(this.installDir, "Uninstaller Repair operation")); + } + + /// + /// Test repair of a Inno installer that has a "installer" repair behavior specified in the manifest. + /// + /// representing the asynchronous unit test. + [Test] + public async Task RepairInnoInstallerWithInstallerRepairBehavior() + { + string innoTestPackageId = string.Empty; + string replaceInstallerArguments = string.Empty; + + if (this.TestFactory.Context == ClsidContext.InProc) + { + innoTestPackageId = "AppInstallerTest.TestInstallerRepair"; + replaceInstallerArguments = $"/InstallDir {this.installDir} /Version 2.0.0.0 /DisplayName TestInstallerRepair /UseHKLM"; + } + else + { + innoTestPackageId = "AppInstallerTest.TestUserScopeInstallerRepair"; + replaceInstallerArguments = $"/InstallDir {this.installDir} /Version 2.0.0.0 /DisplayName TestUserScopeInstallerRepair"; + } + + var searchResult = await this.FindAndInstallPackage(innoTestPackageId, this.installDir, replaceInstallerArguments); + + // Repair the package + await this.RepairPackageAndValidateStatus(searchResult.CatalogPackage, this.TestFactory.CreateRepairOptions(), RepairResultStatus.Ok); + + // Cleanup + Assert.True(TestCommon.VerifyTestExeRepairCompletedAndCleanup(this.installDir, "Installer Repair operation")); + } + + private async Task FindAndInstallPackage(string packageId, string installDir, string replacementInstallerArguments) { // Find a package - var searchResult = this.FindOnePackage(this.compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, "AppInstallerTest.TestMsiRepair"); + var searchResult = this.FindOnePackage(this.compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, packageId); // Configure installation var installOptions = this.TestFactory.CreateInstallOptions(); installOptions.PackageInstallMode = PackageInstallMode.Silent; - installOptions.PreferredInstallLocation = this.installDir; + + if (!string.IsNullOrEmpty(installDir)) + { + installOptions.PreferredInstallLocation = installDir; + } + + if (!string.IsNullOrEmpty(replacementInstallerArguments)) + { + installOptions.ReplacementInstallerArguments = replacementInstallerArguments; + } // Install the package var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); // Find package again, but this time it should be detected as installed - searchResult = this.FindOnePackage(this.compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, "AppInstallerTest.TestMsiRepair"); + searchResult = this.FindOnePackage(this.compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, packageId); Assert.NotNull(searchResult.CatalogPackage.InstalledVersion); - // Note: The 'msiexec repair' command requires the original installer file to be present at the location registered in the ARP (Add/Remove Programs). - // 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. - // To work around this, we copy the installer file to the ARP source directory before running the repair command. - // 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. - // This would allow the 'msiexec repair' command to function as expected. - string installerSourceDir = TestCommon.CopyInstallerFileToARPInstallSourceDirectory(TestCommon.GetTestDataFile("AppInstallerTestMsiInstallerV2.msi"), Constants.MsiInstallerProductCode, true); - - // Repair the package - var repairOptions = this.TestFactory.CreateRepairOptions(); - repairOptions.PackageRepairMode = PackageRepairMode.Silent; + // Return the search result + return searchResult; + } - //// We need to pass argument to indicate silent repair - this should be added to the RepairOptions class. - //// repairOptions.RepairString - This should be added to the RepairOptions class. + private async Task RepairPackageAndValidateStatus(CatalogPackage package, RepairOptions repairOptions, RepairResultStatus expectedStatus, int expectedErrorCode = 0) + { + var repairResult = await this.packageManager.RepairPackageAsync(package, repairOptions); + Assert.AreEqual(expectedStatus, repairResult.Status); - var repairResult = await this.packageManager.RepairPackageAsync(searchResult.CatalogPackage, repairOptions); - Assert.AreEqual(RepairResultStatus.Ok, repairResult.Status); + if (expectedStatus != RepairResultStatus.Ok) + { + Assert.AreEqual(expectedErrorCode, repairResult.ExtendedErrorCode.HResult); + } + } - // Uninstall the package - var uninstallResult = await this.packageManager.UninstallPackageAsync(searchResult.CatalogPackage, this.TestFactory.CreateUninstallOptions()); - Assert.AreEqual(UninstallResultStatus.Ok, uninstallResult.Status); - Assert.True(TestCommon.VerifyTestMsiInstalledAndCleanup(this.installDir)); + private StringBuilder GetReplacementArgumentsBasedTestFixtureContext(string replacementArguments) + { + var replacementArgsBuilder = new StringBuilder(replacementArguments); - if (installerSourceDir != null && Directory.Exists(installerSourceDir)) + // For In-proc calls runs in Admin context in the test environment, repair functionality has restriction that user scope installed package cannot be repaired in admin elevate context. for security concerns + // This is a test coverage workaround that in proc calls will install the package in machine scope where as out of proc calls will install the package in user scope. + if (this.TestFactory.Context == ClsidContext.InProc) { - Directory.Delete(installerSourceDir, true); + replacementArgsBuilder.Append(" /UseHKLM"); } + + return replacementArgsBuilder; } } } diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestBurnInstaller.MissingRepairBehavior.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestBurnInstaller.MissingRepairBehavior.yaml index d490a7b4d1..45cb4314a0 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestBurnInstaller.MissingRepairBehavior.yaml +++ b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestBurnInstaller.MissingRepairBehavior.yaml @@ -9,7 +9,6 @@ Installers: InstallerType: burn InstallerSha256: ProductCode: '{A499DD5E-8DC5-4AD2-911A-BCD0263295E9}' - ElevationRequirement: elevationRequired InstallerSwitches: InstallLocation: /InstallDir Custom: /Version 2.0.0.0 /DisplayName TestMissingRepairBehavior /UseHKLM diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestBurnInstaller.ModifyRepair.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestBurnInstaller.ModifyRepair.yaml index 70b44ec797..b8b6ebae94 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestBurnInstaller.ModifyRepair.yaml +++ b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestBurnInstaller.ModifyRepair.yaml @@ -10,7 +10,6 @@ Installers: InstallerType: burn InstallerSha256: ProductCode: '{A499DD5E-8DC5-4AD2-911A-BCD0263295E9}' - ElevationRequirement: elevationRequired InstallerSwitches: InstallLocation: /InstallDir Custom: /Version 2.0.0.0 /DisplayName TestModifyRepair /UseHKLM diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestBurnInstaller.ModifyRepairWithNoModify.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestBurnInstaller.ModifyRepairWithNoModify.yaml index 65a93af82f..751d8a4621 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestBurnInstaller.ModifyRepairWithNoModify.yaml +++ b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestBurnInstaller.ModifyRepairWithNoModify.yaml @@ -10,7 +10,6 @@ Installers: InstallerType: burn InstallerSha256: ProductCode: '{A499DD5E-8DC5-4AD2-911A-BCD0263295E9}' - ElevationRequirement: elevationRequired InstallerSwitches: InstallLocation: /InstallDir Custom: /Version 2.0.0.0 /DisplayName TestModifyRepairWithNoModify /UseHKLM /NoModify diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller.UninstallerRepair.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller.UninstallerRepair.yaml index 6bd501d5c1..1bad3082a4 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller.UninstallerRepair.yaml +++ b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller.UninstallerRepair.yaml @@ -10,7 +10,6 @@ Installers: InstallerType: exe InstallerSha256: ProductCode: '{A499DD5E-8DC5-4AD2-911A-BCD0263295E9}' - ElevationRequirement: elevationRequired InstallerSwitches: InstallLocation: /InstallDir Custom: /Version 2.0.0.0 /DisplayName UninstallerRepair /UseHKLM diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller.UninstallerRepairWithNoRepair.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller.UninstallerRepairWithNoRepair.yaml index e816c82e04..e1c550b986 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller.UninstallerRepairWithNoRepair.yaml +++ b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller.UninstallerRepairWithNoRepair.yaml @@ -10,7 +10,6 @@ Installers: InstallerType: exe InstallerSha256: ProductCode: '{A499DD5E-8DC5-4AD2-911A-BCD0263295E9}' - ElevationRequirement: elevationRequired InstallerSwitches: InstallLocation: /InstallDir Custom: /Version 2.0.0.0 /DisplayName UninstallerRepairWithNoRepair /UseHKLM /NoRepair diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestInnoInstaller.InstallerRepair.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestInnoInstaller.InstallerRepair.yaml index a43a34722b..e498ec6963 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestInnoInstaller.InstallerRepair.yaml +++ b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestInnoInstaller.InstallerRepair.yaml @@ -10,7 +10,6 @@ Installers: InstallerType: inno InstallerSha256: ProductCode: '{A499DD5E-8DC5-4AD2-911A-BCD0263295E9}' - ElevationRequirement: elevationRequired InstallerSwitches: InstallLocation: /InstallDir Custom: /Version 2.0.0.0 /DisplayName TestInstallerRepair /UseHKLM diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestInnoInstaller.UserScopeInstallerRepair.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestInnoInstaller.UserScopeInstallerRepair.yaml new file mode 100644 index 0000000000..e1fb8aa2d5 --- /dev/null +++ b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestInnoInstaller.UserScopeInstallerRepair.yaml @@ -0,0 +1,18 @@ +PackageIdentifier: AppInstallerTest.TestUserScopeInstallerRepair +PackageVersion: 2.0.0.0 +PackageLocale: en-US +PackageName: TestUserScopeInstallerRepair +Publisher: AppInstallerTest +RepairBehavior: installer +Installers: + - Architecture: x86 + InstallerUrl: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe + InstallerType: inno + InstallerSha256: + ProductCode: '{A499DD5E-8DC5-4AD2-911A-BCD0263295E9}' + InstallerSwitches: + InstallLocation: /InstallDir + Custom: /Version 2.0.0.0 /DisplayName TestUserScopeInstallerRepair + Repair: /repair +ManifestType: singleton +ManifestVersion: 1.7.0 diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestNullsoftInstaller.UninstallerRepair.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestNullsoftInstaller.UninstallerRepair.yaml index c1444e6626..659befcd52 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestNullsoftInstaller.UninstallerRepair.yaml +++ b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestNullsoftInstaller.UninstallerRepair.yaml @@ -10,7 +10,6 @@ Installers: InstallerType: exe InstallerSha256: ProductCode: '{A499DD5E-8DC5-4AD2-911A-BCD0263295E9}' - ElevationRequirement: elevationRequired InstallerSwitches: InstallLocation: /InstallDir Custom: /Version 2.0.0.0 /DisplayName NullsoftUninstallerRepair /UseHKLM From 1ab97db265be59f87461433c6351f30a8f87f4bc Mon Sep 17 00:00:00 2001 From: Madhusudhan Gumbalapura Sudarshan Date: Thu, 15 Aug 2024 15:27:49 -0700 Subject: [PATCH 06/35] Fix test failure in RepairCommand for NonstoreMSIX machine scope repair Adjusted test assertion in RepairCommand.cs to check for the more general string "Microsoft.Paint" instead of "Microsoft.Paint_8wekyb3d8bbwe". This change ensures compatibility with version-specific output variations. --- src/AppInstallerCLIE2ETests/RepairCommand.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AppInstallerCLIE2ETests/RepairCommand.cs b/src/AppInstallerCLIE2ETests/RepairCommand.cs index 53c361ad72..11af2bd588 100644 --- a/src/AppInstallerCLIE2ETests/RepairCommand.cs +++ b/src/AppInstallerCLIE2ETests/RepairCommand.cs @@ -94,7 +94,7 @@ public void RepairNonStoreMsixPackageWithMachineScope() Assert.Ignore("Test skipped as Microsoft.Paint_8wekyb3d8bbwe is not installed."); } - Assert.True(result.StdOut.Contains("Microsoft.Paint_8wekyb3d8bbwe")); + Assert.True(result.StdOut.Contains("Microsoft.Paint")); result = TestCommon.RunAICLICommand("repair", "Microsoft.Paint_8wekyb3d8bbwe --scope machine"); Assert.AreEqual(Constants.ErrorCode.ERROR_INSTALL_SYSTEM_NOT_SUPPORTED, result.ExitCode); From fa8c6d5f03f0d5f83b3d516eec65324d83ca1d62 Mon Sep 17 00:00:00 2001 From: Madhusudhan Gumbalapura Sudarshan Date: Thu, 15 Aug 2024 18:23:06 -0700 Subject: [PATCH 07/35] Fix for RepairNonStoreMsixPackageWithMachineScope test failure - Changed `PackageMatchField` from `Name` to `Id`. - Updated search value from `"Paint"` to `"Microsoft.Paint_8wekyb3d8bbwe"` to ensure accurate package identification. --- src/AppInstallerCLIE2ETests/Interop/RepairInterop.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AppInstallerCLIE2ETests/Interop/RepairInterop.cs b/src/AppInstallerCLIE2ETests/Interop/RepairInterop.cs index be9d670c1f..6d58b70601 100644 --- a/src/AppInstallerCLIE2ETests/Interop/RepairInterop.cs +++ b/src/AppInstallerCLIE2ETests/Interop/RepairInterop.cs @@ -107,7 +107,7 @@ public async Task RepairNonStoreMSIXPackage() public async Task RepairNonStoreMsixPackageWithMachineScope() { // Find package again, but this time it should be detected as installed - var searchResult = this.FindOnePackage(this.compositeSource, PackageMatchField.Name, PackageFieldMatchOption.Equals, "Paint"); + var searchResult = this.FindOnePackage(this.compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, "Microsoft.Paint_8wekyb3d8bbwe"); if (searchResult == null || (searchResult != null && searchResult.CatalogPackage == null) || From 0e43224d06231e65f0c993ae064b80dafd6dfe53 Mon Sep 17 00:00:00 2001 From: Madhusudhan Gumbalapura Sudarshan Date: Thu, 15 Aug 2024 23:45:46 -0700 Subject: [PATCH 08/35] Test failure fix for: RepairNonStoreMsixPackageWithMachineScope These changes ensure the test only proceeds if a package is found, preventing false failures and improving test accuracy. --- src/AppInstallerCLIE2ETests/Interop/RepairInterop.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/AppInstallerCLIE2ETests/Interop/RepairInterop.cs b/src/AppInstallerCLIE2ETests/Interop/RepairInterop.cs index 6d58b70601..0a523826c2 100644 --- a/src/AppInstallerCLIE2ETests/Interop/RepairInterop.cs +++ b/src/AppInstallerCLIE2ETests/Interop/RepairInterop.cs @@ -8,6 +8,7 @@ namespace AppInstallerCLIE2ETests.Interop { using System; using System.IO; + using System.Linq; using System.Text; using System.Threading.Tasks; using AppInstallerCLIE2ETests.Helpers; @@ -107,7 +108,14 @@ public async Task RepairNonStoreMSIXPackage() public async Task RepairNonStoreMsixPackageWithMachineScope() { // Find package again, but this time it should be detected as installed - var searchResult = this.FindOnePackage(this.compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, "Microsoft.Paint_8wekyb3d8bbwe"); + var findPackages = this.FindAllPackages(this.compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, "Microsoft.Paint_8wekyb3d8bbwe"); + + if (findPackages.Count == 0) + { + Assert.Ignore("Test skipped as Microsoft.Paint_8wekyb3d8bbwe cannot be found."); + } + + var searchResult = findPackages.First(); if (searchResult == null || (searchResult != null && searchResult.CatalogPackage == null) || From 7d112a408ecbe872040640b641b69dea845be6a0 Mon Sep 17 00:00:00 2001 From: Madhusudhan Gumbalapura Sudarshan Date: Fri, 16 Aug 2024 11:30:53 -0700 Subject: [PATCH 09/35] Fix OOP COM Repair Test Failures The following tests failed in the Azure pipeline due to ERROR_ADMIN_CONTEXT_REPAIR_PROHIBITED (0x8A15007D): - DisableWinGetCommandLineInterfacesPolicy - RepairBurnInstallerWithModifyBehavior - RepairExeInstallerWithUninstallerBehavior - RepairNullsoftInstallerWithUninstallerBehavior The problem is due to the OOP COM Server running as admin in the pipeline, which is not the case for local test runs. This discrepancy may indicate different test configurations in the pipeline. This prevents the repair of user-scope installed packages. The solution is to install the test package to system scope, similar to In-Proc tests. - Made minor optimizations to the replacement arguments string building logic. --- .../Interop/GroupPolicyForInterop.cs | 13 +-- .../Interop/RepairInterop.cs | 91 ++++++++----------- ...nnoInstaller.UserScopeInstallerRepair.yaml | 18 ---- 3 files changed, 37 insertions(+), 85 deletions(-) delete mode 100644 src/AppInstallerCLIE2ETests/TestData/Manifests/TestInnoInstaller.UserScopeInstallerRepair.yaml diff --git a/src/AppInstallerCLIE2ETests/Interop/GroupPolicyForInterop.cs b/src/AppInstallerCLIE2ETests/Interop/GroupPolicyForInterop.cs index d5e0456c21..e7a3bfff58 100644 --- a/src/AppInstallerCLIE2ETests/Interop/GroupPolicyForInterop.cs +++ b/src/AppInstallerCLIE2ETests/Interop/GroupPolicyForInterop.cs @@ -129,18 +129,7 @@ public async Task DisableWinGetCommandLineInterfacesPolicy() var installOptions = this.TestFactory.CreateInstallOptions(); installOptions.PackageInstallMode = PackageInstallMode.Silent; installOptions.AcceptPackageAgreements = true; - - StringBuilder replacementArgs = new StringBuilder(); - replacementArgs.Append($"/InstallDir {installDir} /Version 2.0.0.0 /DisplayName TestModifyRepair"); - - // For In-proc calls runs in Admin context in the test environment, repair functionality has restriction that user scope installed package cannot be repaired in admin elevate context. for security concerns - // This is a test coverage workaround that in proc calls will install the package in machine scope where as out of proc calls will install the package in user scope. - if (this.TestFactory.Context == ClsidContext.InProc) - { - replacementArgs.Append(" /UseHKLM"); - } - - installOptions.ReplacementInstallerArguments = replacementArgs.ToString(); + installOptions.ReplacementInstallerArguments = $"/InstallDir {installDir} /Version 2.0.0.0 /DisplayName TestModifyRepair /UseHKLM"; // Install var installResult = await packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); diff --git a/src/AppInstallerCLIE2ETests/Interop/RepairInterop.cs b/src/AppInstallerCLIE2ETests/Interop/RepairInterop.cs index 0a523826c2..bb97ea768f 100644 --- a/src/AppInstallerCLIE2ETests/Interop/RepairInterop.cs +++ b/src/AppInstallerCLIE2ETests/Interop/RepairInterop.cs @@ -107,19 +107,16 @@ public async Task RepairNonStoreMSIXPackage() [Test] public async Task RepairNonStoreMsixPackageWithMachineScope() { - // Find package again, but this time it should be detected as installed - var findPackages = this.FindAllPackages(this.compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, "Microsoft.Paint_8wekyb3d8bbwe"); + var findPackages = this.FindAllPackages(this.compositeSource, PackageMatchField.Id, PackageFieldMatchOption.ContainsCaseInsensitive, "Microsoft.Paint"); - if (findPackages.Count == 0) + if (findPackages == null || findPackages.Count == 0) { - Assert.Ignore("Test skipped as Microsoft.Paint_8wekyb3d8bbwe cannot be found."); + Assert.Ignore("Test skipped as Microsoft.Paint_8wekyb3d8bbwe can't be found."); } var searchResult = findPackages.First(); - if (searchResult == null || - (searchResult != null && searchResult.CatalogPackage == null) || - (searchResult != null && searchResult.CatalogPackage.InstalledVersion == null)) + if (searchResult == null || searchResult.CatalogPackage == null || searchResult.CatalogPackage.InstalledVersion == null) { Assert.Ignore("Test skipped as Microsoft.Paint_8wekyb3d8bbwe is not installed."); } @@ -138,10 +135,8 @@ public async Task RepairNonStoreMsixPackageWithMachineScope() [Test] public async Task RepairBurnInstallerWithModifyBehavior() { - string burnInstallerPackageId = "AppInstallerTest.TestModifyRepair"; - var replaceInstallerArguments = this.GetReplacementArgumentsBasedTestFixtureContext($"/InstallDir {this.installDir} /Version 2.0.0.0 /DisplayName TestModifyRepair"); - - var searchResult = await this.FindAndInstallPackage(burnInstallerPackageId, this.installDir, replaceInstallerArguments.ToString()); + var replaceInstallerArguments = this.GetReplacementArguments(this.installDir, "2.0.0.0", "TestModifyRepair", useHKLM: true); + var searchResult = await this.FindAndInstallPackage("AppInstallerTest.TestModifyRepair", this.installDir, replaceInstallerArguments.ToString()); // Repair the package var repairOptions = this.TestFactory.CreateRepairOptions(); @@ -165,10 +160,8 @@ public async Task RepairBurnInstallerInAdminContextWithUserScopeInstall() Assert.Ignore("Test is only applicable for InProc context."); } - string burnInstallerPackageId = "AppInstallerTest.TestUserScopeInstallRepairInAdminContext"; - string replaceInstallerArguments = $"/InstallDir {this.installDir} /Version 2.0.0.0 /DisplayName TestUserScopeInstallRepairInAdminContext"; - - var searchResult = await this.FindAndInstallPackage(burnInstallerPackageId, this.installDir, replaceInstallerArguments); + string replaceInstallerArguments = this.GetReplacementArguments(this.installDir, "2.0.0.0", "TestUserScopeInstallRepairInAdminContext"); + var searchResult = await this.FindAndInstallPackage("AppInstallerTest.TestUserScopeInstallRepairInAdminContext", this.installDir, replaceInstallerArguments); // Repair the package var repairOptions = this.TestFactory.CreateRepairOptions(); @@ -187,11 +180,8 @@ public async Task RepairBurnInstallerInAdminContextWithUserScopeInstall() [Test] public async Task RepairBurnInstallerWithModifyBehaviorAndNoModifyFlag() { - string burnInstallerPackageId = "AppInstallerTest.TestModifyRepairWithNoModify"; - var replaceInstallerArguments = this.GetReplacementArgumentsBasedTestFixtureContext($"/InstallDir {this.installDir} /Version 2.0.0.0 /DisplayName TestModifyRepairWithNoModify"); - replaceInstallerArguments.Append(" /NoModify"); - - var searchResult = await this.FindAndInstallPackage(burnInstallerPackageId, this.installDir, replaceInstallerArguments.ToString()); + string replaceInstallerArguments = this.GetReplacementArguments(this.installDir, "2.0.0.0", "TestModifyRepairWithNoModify", useHKLM: true, noModify: true); + var searchResult = await this.FindAndInstallPackage("AppInstallerTest.TestModifyRepairWithNoModify", this.installDir, replaceInstallerArguments); // Repair the package var repairOptions = this.TestFactory.CreateRepairOptions(); @@ -235,10 +225,8 @@ public async Task RepairOperationNotSupportedForPortableInstaller() [Test] public async Task RepairExeInstallerWithUninstallerBehavior() { - string exeInstallerPackageId = "AppInstallerTest.UninstallerRepair"; - var replaceInstallerArguments = this.GetReplacementArgumentsBasedTestFixtureContext($"/InstallDir {this.installDir} /Version 2.0.0.0 /DisplayName UninstallerRepair"); - - var searchResult = await this.FindAndInstallPackage(exeInstallerPackageId, this.installDir, replaceInstallerArguments.ToString()); + string replaceInstallerArguments = this.GetReplacementArguments(this.installDir, "2.0.0.0", "UninstallerRepair", useHKLM: true); + var searchResult = await this.FindAndInstallPackage("AppInstallerTest.UninstallerRepair", this.installDir, replaceInstallerArguments); // Repair the package await this.RepairPackageAndValidateStatus(searchResult.CatalogPackage, this.TestFactory.CreateRepairOptions(), RepairResultStatus.Ok); @@ -254,11 +242,8 @@ public async Task RepairExeInstallerWithUninstallerBehavior() [Test] public async Task RepairExeInstallerWithUninstallerBehaviorAndNoRepairFlag() { - string exeInstallerPackageId = "AppInstallerTest.UninstallerRepairWithNoRepair"; - var replaceInstallerArguments = this.GetReplacementArgumentsBasedTestFixtureContext($"/InstallDir {this.installDir} /Version 2.0.0.0 /DisplayName UninstallerRepairWithNoRepair"); - replaceInstallerArguments.Append(" /NoRepair"); - - var searchResult = await this.FindAndInstallPackage(exeInstallerPackageId, this.installDir, replaceInstallerArguments.ToString()); + string replaceInstallerArguments = this.GetReplacementArguments(this.installDir, "2.0.0.0", "UninstallerRepairWithNoRepair", useHKLM: true, noRepair: true); + var searchResult = await this.FindAndInstallPackage("AppInstallerTest.UninstallerRepairWithNoRepair", this.installDir, replaceInstallerArguments); // Repair the package await this.RepairPackageAndValidateStatus(searchResult.CatalogPackage, this.TestFactory.CreateRepairOptions(), RepairResultStatus.RepairError, Constants.ErrorCode.ERROR_REPAIR_NOT_SUPPORTED); @@ -274,10 +259,8 @@ public async Task RepairExeInstallerWithUninstallerBehaviorAndNoRepairFlag() [Test] public async Task RepairNullsoftInstallerWithUninstallerBehavior() { - string nullsoftTestPackageId = "AppInstallerTest.NullsoftUninstallerRepair"; - var replaceInstallerArguments = this.GetReplacementArgumentsBasedTestFixtureContext($"/InstallDir {this.installDir} /Version 2.0.0.0 /DisplayName NullsoftUninstallerRepair"); - - var searchResult = await this.FindAndInstallPackage(nullsoftTestPackageId, this.installDir, replaceInstallerArguments.ToString()); + string replaceInstallerArguments = this.GetReplacementArguments(this.installDir, "2.0.0.0", "NullsoftUninstallerRepair", useHKLM: true); + var searchResult = await this.FindAndInstallPackage("AppInstallerTest.NullsoftUninstallerRepair", this.installDir, replaceInstallerArguments.ToString()); // Repair the package await this.RepairPackageAndValidateStatus(searchResult.CatalogPackage, this.TestFactory.CreateRepairOptions(), RepairResultStatus.Ok); @@ -293,21 +276,8 @@ public async Task RepairNullsoftInstallerWithUninstallerBehavior() [Test] public async Task RepairInnoInstallerWithInstallerRepairBehavior() { - string innoTestPackageId = string.Empty; - string replaceInstallerArguments = string.Empty; - - if (this.TestFactory.Context == ClsidContext.InProc) - { - innoTestPackageId = "AppInstallerTest.TestInstallerRepair"; - replaceInstallerArguments = $"/InstallDir {this.installDir} /Version 2.0.0.0 /DisplayName TestInstallerRepair /UseHKLM"; - } - else - { - innoTestPackageId = "AppInstallerTest.TestUserScopeInstallerRepair"; - replaceInstallerArguments = $"/InstallDir {this.installDir} /Version 2.0.0.0 /DisplayName TestUserScopeInstallerRepair"; - } - - var searchResult = await this.FindAndInstallPackage(innoTestPackageId, this.installDir, replaceInstallerArguments); + string replaceInstallerArguments = this.GetReplacementArguments(this.installDir, "2.0.0.0", "TestInstallerRepair", useHKLM: true); + var searchResult = await this.FindAndInstallPackage("AppInstallerTest.TestInstallerRepair", this.installDir, replaceInstallerArguments); // Repair the package await this.RepairPackageAndValidateStatus(searchResult.CatalogPackage, this.TestFactory.CreateRepairOptions(), RepairResultStatus.Ok); @@ -358,18 +328,29 @@ private async Task RepairPackageAndValidateStatus(CatalogPackage package, Repair } } - private StringBuilder GetReplacementArgumentsBasedTestFixtureContext(string replacementArguments) + private string GetReplacementArguments(string installDir, string version, string displayName, bool useHKLM = false, bool noModify = false, bool noRepair = false) { - var replacementArgsBuilder = new StringBuilder(replacementArguments); + var replacementArguments = new StringBuilder($"/InstallDir {installDir} /Version {version} /DisplayName {displayName}"); + + // Machine scope install. + if (useHKLM) + { + replacementArguments.Append($" /UseHKLM"); + } + + // Instructs test installer to set NoModify ARP flag. + if (noModify) + { + replacementArguments.Append($" /NoModify"); + } - // For In-proc calls runs in Admin context in the test environment, repair functionality has restriction that user scope installed package cannot be repaired in admin elevate context. for security concerns - // This is a test coverage workaround that in proc calls will install the package in machine scope where as out of proc calls will install the package in user scope. - if (this.TestFactory.Context == ClsidContext.InProc) + // Instructs test installer to set NoRepair ARP flag. + if (noRepair) { - replacementArgsBuilder.Append(" /UseHKLM"); + replacementArguments.Append($" /NoRepair"); } - return replacementArgsBuilder; + return replacementArguments.ToString(); } } } diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestInnoInstaller.UserScopeInstallerRepair.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestInnoInstaller.UserScopeInstallerRepair.yaml deleted file mode 100644 index e1fb8aa2d5..0000000000 --- a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestInnoInstaller.UserScopeInstallerRepair.yaml +++ /dev/null @@ -1,18 +0,0 @@ -PackageIdentifier: AppInstallerTest.TestUserScopeInstallerRepair -PackageVersion: 2.0.0.0 -PackageLocale: en-US -PackageName: TestUserScopeInstallerRepair -Publisher: AppInstallerTest -RepairBehavior: installer -Installers: - - Architecture: x86 - InstallerUrl: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe - InstallerType: inno - InstallerSha256: - ProductCode: '{A499DD5E-8DC5-4AD2-911A-BCD0263295E9}' - InstallerSwitches: - InstallLocation: /InstallDir - Custom: /Version 2.0.0.0 /DisplayName TestUserScopeInstallerRepair - Repair: /repair -ManifestType: singleton -ManifestVersion: 1.7.0 From b34ad4b5c9b75ad10a761a5fe6c81abb6c9c909f Mon Sep 17 00:00:00 2001 From: Madhusudhan Gumbalapura Sudarshan Date: Mon, 19 Aug 2024 16:57:20 -0700 Subject: [PATCH 10/35] Remove DownloadDirectory parameter and related logic The DownloadDirectory parameter and its associated logic have been removed from WinGet COM Repair logic. This includes: - Removal of the DownloadDirectory from the interface definition in PackageManager.idl. - Removal of the code that adds the DownloadDirectory argument to the context in PackageManager.cpp. - Removal of the getter and setter methods in RepairOptions.cpp. - Removal of the m_downloadPath member variable in RepairOptions.h. --- src/Microsoft.Management.Deployment/PackageManager.cpp | 5 ----- src/Microsoft.Management.Deployment/PackageManager.idl | 3 --- src/Microsoft.Management.Deployment/RepairOptions.cpp | 10 ---------- src/Microsoft.Management.Deployment/RepairOptions.h | 1 - 4 files changed, 19 deletions(-) diff --git a/src/Microsoft.Management.Deployment/PackageManager.cpp b/src/Microsoft.Management.Deployment/PackageManager.cpp index 9cd4ca5298..29c7b838e0 100644 --- a/src/Microsoft.Management.Deployment/PackageManager.cpp +++ b/src/Microsoft.Management.Deployment/PackageManager.cpp @@ -581,11 +581,6 @@ namespace winrt::Microsoft::Management::Deployment::implementation context->Args.AddArg(Execution::Args::Type::VerboseLogs); } - if (!options.DownloadDirectory().empty()) - { - context->Args.AddArg(Execution::Args::Type::DownloadDirectory, ::AppInstaller::Utility::ConvertToUTF8(options.DownloadDirectory())); - } - if (options.PackageRepairMode() == PackageRepairMode::Interactive) { context->Args.AddArg(Execution::Args::Type::Interactive); diff --git a/src/Microsoft.Management.Deployment/PackageManager.idl b/src/Microsoft.Management.Deployment/PackageManager.idl index 30fb44a58c..33d1843904 100644 --- a/src/Microsoft.Management.Deployment/PackageManager.idl +++ b/src/Microsoft.Management.Deployment/PackageManager.idl @@ -1176,9 +1176,6 @@ namespace Microsoft.Management.Deployment /// The package repair mode. PackageRepairMode PackageRepairMode; - /// Optional parameter specifying the directory where installers are downloaded when the repair behavior is of type Installer. - String DownloadDirectory; - /// Optional parameter specifying Accept the package agreements required for download. Boolean AcceptPackageAgreements; diff --git a/src/Microsoft.Management.Deployment/RepairOptions.cpp b/src/Microsoft.Management.Deployment/RepairOptions.cpp index d18487f69b..0ab6e4a42d 100644 --- a/src/Microsoft.Management.Deployment/RepairOptions.cpp +++ b/src/Microsoft.Management.Deployment/RepairOptions.cpp @@ -48,16 +48,6 @@ namespace winrt::Microsoft::Management::Deployment::implementation m_packageRepairMode = value; } - hstring RepairOptions::DownloadDirectory() - { - return hstring(m_downloadPath); - } - - void RepairOptions::DownloadDirectory(hstring const& value) - { - m_downloadPath = value; - } - bool RepairOptions::AcceptPackageAgreements() { return m_acceptPackageAgreements; diff --git a/src/Microsoft.Management.Deployment/RepairOptions.h b/src/Microsoft.Management.Deployment/RepairOptions.h index e4e9e83c92..23c2fbd5ad 100644 --- a/src/Microsoft.Management.Deployment/RepairOptions.h +++ b/src/Microsoft.Management.Deployment/RepairOptions.h @@ -29,7 +29,6 @@ namespace winrt::Microsoft::Management::Deployment::implementation #if !defined(INCLUDE_ONLY_INTERFACE_METHODS) private: winrt::Microsoft::Management::Deployment::PackageVersionId m_packageVersionId{ nullptr }; - std::wstring m_downloadPath = L""; bool m_acceptPackageAgreements = false; std::wstring m_logOutputPath = L""; std::wstring m_correlationData = L""; From 01beb2459217e6fa35634b5499d89edf7608e2bb Mon Sep 17 00:00:00 2001 From: Madhusudhan Gumbalapura Sudarshan Date: Mon, 19 Aug 2024 17:24:17 -0700 Subject: [PATCH 11/35] Add CLSID for RepairOptions in Microsoft.Management.Deployment.OutOfProc Factory.cpp Introduce a new CLSID for RepairOptions in the Microsoft::Management::Deployment::OutOfProc namespace. Update the s_nameCLSIDPairs array to include the new NameCLSIDPair for RepairOptions, increasing the array size from 8 to 9. This change enables the system to recognize and work with RepairOptions similarly to other components. --- src/Microsoft.Management.Deployment.OutOfProc/Factory.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.Management.Deployment.OutOfProc/Factory.cpp b/src/Microsoft.Management.Deployment.OutOfProc/Factory.cpp index a7d8c15c07..ad7c0d66ca 100644 --- a/src/Microsoft.Management.Deployment.OutOfProc/Factory.cpp +++ b/src/Microsoft.Management.Deployment.OutOfProc/Factory.cpp @@ -21,6 +21,7 @@ namespace Microsoft::Management::Deployment::OutOfProc constexpr CLSID CLSID_CreateCompositePackageCatalogOptions = { 0x526534B8, 0x7E46, 0x47C8, { 0x84, 0x16, 0xB1, 0x68, 0x5C, 0x32, 0x7D, 0x37 } }; //526534B8-7E46-47C8-8416-B1685C327D37 constexpr CLSID CLSID_DownloadOptions = { 0x4CBABE76, 0x7322, 0x4BE4, { 0x9C, 0xEA, 0x25, 0x89, 0xA8, 0x06, 0x82, 0xDC } }; //4CBABE76-7322-4BE4-9CEA-2589A80682DC constexpr CLSID CLSID_AuthenticationArguments = { 0xBA580786, 0xBDE3, 0x4F6C, { 0xB8, 0xF3, 0x44, 0x69, 0x8A, 0xC8, 0x71, 0x1A } }; //BA580786-BDE3-4F6C-B8F3-44698AC8711A + constexpr CLSID CLSID_RepairOptions = { 0x0498F441, 0x3097, 0x455F, { 0x9C, 0xAF, 0x14, 0x8F, 0x28, 0x29, 0x38, 0x65 } }; //0498F441-3097-455F-9CAF-148F28293865 #else constexpr CLSID CLSID_PackageManager = { 0x74CB3139, 0xB7C5, 0x4B9E, { 0x93, 0x88, 0xE6, 0x61, 0x6D, 0xEA, 0x28, 0x8C } }; //74CB3139-B7C5-4B9E-9388-E6616DEA288C constexpr CLSID CLSID_InstallOptions = { 0x44FE0580, 0x62F7, 0x44D4, { 0x9E, 0x91, 0xAA, 0x96, 0x14, 0xAB, 0x3E, 0x86 } }; //44FE0580-62F7-44D4-9E91-AA9614AB3E86 @@ -30,6 +31,7 @@ namespace Microsoft::Management::Deployment::OutOfProc constexpr CLSID CLSID_CreateCompositePackageCatalogOptions = { 0xEE160901, 0xB317, 0x4EA7, { 0x9C, 0xC6, 0x53, 0x55, 0xC6, 0xD7, 0xD8, 0xA7 } }; //EE160901-B317-4EA7-9CC6-5355C6D7D8A7 constexpr CLSID CLSID_DownloadOptions = { 0x8EF324ED, 0x367C, 0x4880, { 0x83, 0xE5, 0xBB, 0x2A, 0xBD, 0x0B, 0x72, 0xF6 } }; //8EF324ED-367C-4880-83E5-BB2ABD0B72F6 constexpr CLSID CLSID_AuthenticationArguments = { 0x6484A61D, 0x50FA, 0x41F0, { 0xB7, 0x1E, 0xF4, 0x37, 0x0C, 0x6E, 0xB3, 0x7C } }; //6484A61D-50FA-41F0-B71E-F4370C6EB37C + constexpr CLSID CLSID_RepairOptions = { 0xE62BB1E7, 0xC7B2, 0x4AEC, { 0x9E, 0x28, 0xFB, 0x64, 0x9B, 0x30, 0xFF, 0x03 } }; //E62BB1E7-C7B2-4AEC-9E28-FB649B30FF03 #endif struct NameCLSIDPair @@ -38,7 +40,7 @@ namespace Microsoft::Management::Deployment::OutOfProc GUID CLSID; }; - constexpr std::array s_nameCLSIDPairs + constexpr std::array s_nameCLSIDPairs { NameCLSIDPair{ L"Microsoft.Management.Deployment.PackageManager"sv, CLSID_PackageManager }, NameCLSIDPair{ L"Microsoft.Management.Deployment.InstallOptions"sv, CLSID_InstallOptions }, @@ -48,6 +50,7 @@ namespace Microsoft::Management::Deployment::OutOfProc NameCLSIDPair{ L"Microsoft.Management.Deployment.CreateCompositePackageCatalogOptions"sv, CLSID_CreateCompositePackageCatalogOptions }, NameCLSIDPair{ L"Microsoft.Management.Deployment.DownloadOptions"sv, CLSID_DownloadOptions }, NameCLSIDPair{ L"Microsoft.Management.Deployment.AuthenticationArguments"sv, CLSID_AuthenticationArguments }, + NameCLSIDPair{ L"Microsoft.Management.Deployment.RepairOptions"sv, CLSID_RepairOptions } }; bool IsCLSIDPresent(const GUID& clsid) From 97d48e8a989371bfda3cbdd9b705719ac884defb Mon Sep 17 00:00:00 2001 From: Madhusudhan Gumbalapura Sudarshan Date: Mon, 19 Aug 2024 17:29:09 -0700 Subject: [PATCH 12/35] Remove DownloadDirectory methods from RepairOptions.h --- src/Microsoft.Management.Deployment/RepairOptions.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Microsoft.Management.Deployment/RepairOptions.h b/src/Microsoft.Management.Deployment/RepairOptions.h index 23c2fbd5ad..79e4b2c38d 100644 --- a/src/Microsoft.Management.Deployment/RepairOptions.h +++ b/src/Microsoft.Management.Deployment/RepairOptions.h @@ -13,8 +13,6 @@ namespace winrt::Microsoft::Management::Deployment::implementation winrt::Microsoft::Management::Deployment::PackageVersionId PackageVersionId(); void PackageVersionId(winrt::Microsoft::Management::Deployment::PackageVersionId const& value); - hstring DownloadDirectory(); - void DownloadDirectory(hstring const& value); winrt::Microsoft::Management::Deployment::PackageRepairScope PackageRepairScope(); void PackageRepairScope(winrt::Microsoft::Management::Deployment::PackageRepairScope const& value); winrt::Microsoft::Management::Deployment::PackageRepairMode PackageRepairMode(); From ec3822f61d56235560c0c17dda62bfc8814ed418 Mon Sep 17 00:00:00 2001 From: Madhusudhan Gumbalapura Sudarshan Date: Fri, 23 Aug 2024 13:40:18 -0700 Subject: [PATCH 13/35] Add Repair-WinGetPackage powershell cmdlet to Microsoft.WinGet.Client Introduced a new cmdlet `Repair-WinGetPackage` to the Microsoft.WinGet.Client PowerShell module. This cmdlet allows users to repair packages with different modes (Default, Silent, Interactive) and provides detailed results of the repair operation. Key changes: - Added new classes and methods to support the repair functionality. - Updated `Format.ps1xml` to include a new view definition for `PSRepairResult`. - Added new localized strings for repair operations. - Updated `README.md` and added a sample script `Sample_RepairPackage.ps1`. - Included `Repair-WinGetPackage` in the list of exported cmdlets in `Microsoft.WinGet.Client.psd1`. --- .../Cmdlets/PSObjects/PSPackageRepairMode.cs | 29 ++++ .../Cmdlets/RepairPackageCmdlet.cs | 70 +++++++++ .../Commands/RepairPackageCommand.cs | 126 ++++++++++++++++ .../Helpers/ManagementDeploymentFactory.cs | 16 ++ .../Helpers/PSEnumHelpers.cs | 19 ++- .../Helpers/PackageManagerWrapper.cs | 15 +- .../Helpers/RepairOperationWithProgress.cs | 44 ++++++ .../PSObjects/PSRepairResult.cs | 137 ++++++++++++++++++ .../Properties/Resources.Designer.cs | 18 +++ .../Properties/Resources.resx | 7 + .../Examples/Sample_RepairPackage.ps1 | 22 +++ .../ModuleFiles/Format.ps1xml | 70 ++++++++- .../ModuleFiles/Microsoft.WinGet.Client.psd1 | 1 + .../Microsoft.WinGet.Client/README.md | 3 +- 14 files changed, 571 insertions(+), 6 deletions(-) create mode 100644 src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/PSObjects/PSPackageRepairMode.cs create mode 100644 src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/RepairPackageCmdlet.cs create mode 100644 src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/RepairPackageCommand.cs create mode 100644 src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/RepairOperationWithProgress.cs create mode 100644 src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSRepairResult.cs create mode 100644 src/PowerShell/Microsoft.WinGet.Client/Examples/Sample_RepairPackage.ps1 diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/PSObjects/PSPackageRepairMode.cs b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/PSObjects/PSPackageRepairMode.cs new file mode 100644 index 0000000000..538a444f71 --- /dev/null +++ b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/PSObjects/PSPackageRepairMode.cs @@ -0,0 +1,29 @@ +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Cmdlets.Cmdlets.PSObjects +{ + /// + /// Must match Microsoft.Management.Deployment.PackageRepairMode. + /// + public enum PSPackageRepairMode + { + /// + /// Default. + /// + Default, + + /// + /// Silent. + /// + Silent, + + /// + /// Interactive. + /// + Interactive, + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/RepairPackageCmdlet.cs b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/RepairPackageCmdlet.cs new file mode 100644 index 0000000000..ea86869f64 --- /dev/null +++ b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/RepairPackageCmdlet.cs @@ -0,0 +1,70 @@ +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Commands +{ + using System.Management.Automation; + using Microsoft.WinGet.Client.Cmdlets.Cmdlets.PSObjects; + using Microsoft.WinGet.Client.Commands.Common; + using Microsoft.WinGet.Client.Common; + using Microsoft.WinGet.Client.Engine.Commands; + using Microsoft.WinGet.Client.Engine.PSObjects; + + /// + /// This class defines the repair package command. + /// + [Cmdlet( + VerbsDiagnostic.Repair, + Constants.WinGetNouns.Package, + DefaultParameterSetName = Constants.FoundSet, + SupportsShouldProcess = true)] + [OutputType(typeof(PSRepairResult))] + public sealed class RepairPackageCmdlet : PackageCmdlet + { + private RepairPackageCommand command = null; + + /// + /// Gets or sets the desired mode for the repair process. + /// + [Parameter(ValueFromPipelineByPropertyName = true)] + public PSPackageRepairMode Mode { get; set; } = PSPackageRepairMode.Default; + + /// + /// Gets or sets the path to the logging file. + /// + [Parameter] + public string Log { get; set; } + + /// + /// Repairs a package from the local system. + /// + protected override void ProcessRecord() + { + this.command = new RepairPackageCommand( + this, + this.PSCatalogPackage, + this.Version, + this.Log, + this.Id, + this.Name, + this.Moniker, + this.Source, + this.Query); + this.command.Repair(this.MatchOption.ToString(), this.Mode.ToString()); + } + + /// + /// Interrupts currently running code within the command. + /// + protected override void StopProcessing() + { + if (this.command != null) + { + this.command.Cancel(); + } + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/RepairPackageCommand.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/RepairPackageCommand.cs new file mode 100644 index 0000000000..f0a2434b98 --- /dev/null +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/RepairPackageCommand.cs @@ -0,0 +1,126 @@ +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Commands +{ + using System; + using System.Management.Automation; + using System.Threading.Tasks; + using Microsoft.Management.Deployment; + using Microsoft.WinGet.Client.Engine.Commands.Common; + using Microsoft.WinGet.Client.Engine.Helpers; + using Microsoft.WinGet.Client.Engine.PSObjects; + using Microsoft.WinGet.Resources; + + /// + /// This class defines the repair package command. + /// + public sealed class RepairPackageCommand : PackageCommand + { + /// + /// Initializes a new instance of the class. + /// + /// Caller cmdlet. + /// PSCatalogPackage. + /// Version to repair. + /// Logging file location. + /// Package identifier. + /// Name of package. + /// Moniker of package. + /// Source to search. if null, all are searched. + /// Match against any field of a package. + public RepairPackageCommand( + PSCmdlet psCmdlet, + PSCatalogPackage pSCatalogPackage, + string version, + string log, + string id, + string name, + string moniker, + string source, + string[] query) + : base(psCmdlet) + { + if (pSCatalogPackage != null) + { + this.CatalogPackage = pSCatalogPackage; + } + + this.Version = version; + + this.Id = id; + this.Name = name; + this.Moniker = moniker; + this.Source = source; + this.Query = query; + + this.Log = log; + } + + /// + /// Gets or sets the path to the logging file. + /// + private string? Log { get; set; } + + /// + /// Process repair package. + /// + /// PSPackageFieldMatchOption. + /// PSPackageRepairMode. + public void Repair( + string psPackageFieldMatchOption, + string psPackageRepairMode) + { + var result = this.Execute( + async () => await this.GetPackageAndExecuteAsync( + CompositeSearchBehavior.LocalCatalogs, + PSEnumHelpers.ToPackageFieldMatchOption(psPackageFieldMatchOption), + async (package, version) => + { + var repairOptions = this.GetRepairOptions(version, PSEnumHelpers.ToPackageRepairMode(psPackageRepairMode)); + return await PackageManagerWrapper.Instance.RepairPackageAsync(package, repairOptions); + })); + + if (result != null) + { + this.Write(WinGet.Common.Command.StreamType.Object, new PSRepairResult(result.Item1, result.Item2)); + } + } + + private RepairOptions GetRepairOptions( + PackageVersionId? version, + PackageRepairMode repairMode) + { + var options = ManagementDeploymentFactory.Instance.CreateRepairOptions(); + + if (this.Log != null) + { + options.LogOutputPath = this.Log; + } + + options.PackageRepairMode = repairMode; + + if (version != null) + { + options.PackageVersionId = version; + } + + return options; + } + + private async Task RepairResultAsync( + CatalogPackage catalogPackage, + RepairOptions repairOptions) + { + var progressOperation = new RepairOperationWithProgress( + this, + string.Format(Resources.ProgressRecordActivityRepairing, catalogPackage.Name)); + + return await progressOperation.ExecuteAsync( + () => PackageManagerWrapper.Instance.RepairPackageAsync(catalogPackage, repairOptions)); + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/ManagementDeploymentFactory.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/ManagementDeploymentFactory.cs index 5994ed7cf7..b541b20363 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/ManagementDeploymentFactory.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/ManagementDeploymentFactory.cs @@ -30,6 +30,7 @@ internal sealed class ManagementDeploymentFactory private static readonly Guid UninstallOptionsClsid = Guid.Parse("E1D9A11E-9F85-4D87-9C17-2B93143ADB8D"); private static readonly Guid PackageMatchFilterClsid = Guid.Parse("D02C9DAF-99DC-429C-B503-4E504E4AB000"); private static readonly Guid DownloadOptionsClsid = Guid.Parse("4CBABE76-7322-4BE4-9CEA-2589A80682DC"); + private static readonly Guid RepairOptionsClsid = Guid.Parse("0498F441-3097-455F-9CAF-148F28293865"); #else private static readonly Guid PackageManagerClsid = Guid.Parse("74CB3139-B7C5-4B9E-9388-E6616DEA288C"); private static readonly Guid FindPackagesOptionsClsid = Guid.Parse("1BD8FF3A-EC50-4F69-AEEE-DF4C9D3BAA96"); @@ -38,6 +39,7 @@ internal sealed class ManagementDeploymentFactory private static readonly Guid UninstallOptionsClsid = Guid.Parse("AA2A5C04-1AD9-46C4-B74F-6B334AD7EB8C"); private static readonly Guid PackageMatchFilterClsid = Guid.Parse("3F85B9F4-487A-4C48-9035-2903F8A6D9E8"); private static readonly Guid DownloadOptionsClsid = Guid.Parse("8EF324ED-367C-4880-83E5-BB2ABD0B72F6"); + private static readonly Guid RepairOptionsClsid = Guid.Parse("E62BB1E7-C7B2-4AEC-9E28-FB649B30FF03"); #endif [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "COM only usage.")] private static readonly Type? PackageManagerType = Type.GetTypeFromCLSID(PackageManagerClsid); @@ -53,7 +55,11 @@ internal sealed class ManagementDeploymentFactory private static readonly Type? PackageMatchFilterType = Type.GetTypeFromCLSID(PackageMatchFilterClsid); [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "COM only usage.")] private static readonly Type? DownloadOptionsType = Type.GetTypeFromCLSID(DownloadOptionsClsid); + [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "COM only usage.")] + private static readonly Type? RepairOptionsType = Type.GetTypeFromCLSID(RepairOptionsClsid); + // These GUIDs correspond to the CSWinRT interface IIDs generated for Microsoft.Management.Deployment.Projection + // and are auto-generated by the WinRT tool. private static readonly Guid PackageManagerIid = Guid.Parse("B375E3B9-F2E0-5C93-87A7-B67497F7E593"); private static readonly Guid FindPackagesOptionsIid = Guid.Parse("A5270EDD-7DA7-57A3-BACE-F2593553561F"); private static readonly Guid CreateCompositePackageCatalogOptionsIid = Guid.Parse("21ABAA76-089D-51C5-A745-C85EEFE70116"); @@ -61,6 +67,7 @@ internal sealed class ManagementDeploymentFactory private static readonly Guid UninstallOptionsIid = Guid.Parse("3EBC67F0-8339-594B-8A42-F90B69D02BBE"); private static readonly Guid PackageMatchFilterIid = Guid.Parse("D981ECA3-4DE5-5AD7-967A-698C7D60FC3B"); private static readonly Guid DownloadOptionsIid = Guid.Parse("94C92C4B-43F5-5CA3-BBBE-9F432C9546BC"); + private static readonly Guid RepairOptionsIid = Guid.Parse("828399EF-E0E0-5DA0-973B-B80468A04E9D"); private static readonly IEnumerable ValidArchs = new Architecture[] { Architecture.X86, Architecture.X64 }; @@ -153,6 +160,15 @@ public PackageMatchFilter CreatePackageMatchFilter() return Create(PackageMatchFilterType, PackageMatchFilterIid); } + /// + /// Creates an instance of the class. + /// + /// A instance. + public RepairOptions CreateRepairOptions() + { + return Create(RepairOptionsType, RepairOptionsIid); + } + [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "COM only usage.")] private static T Create(Type? type, in Guid iid) where T : new() diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/PSEnumHelpers.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/PSEnumHelpers.cs index 8a00417fd4..be963f4cf2 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/PSEnumHelpers.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/PSEnumHelpers.cs @@ -1,4 +1,4 @@ -// ----------------------------------------------------------------------------- +// ----------------------------------------------------------------------------- // // Copyright (c) Microsoft Corporation. Licensed under the MIT License. // @@ -8,6 +8,7 @@ namespace Microsoft.WinGet.Client.Engine.Helpers { using System; using Microsoft.Management.Deployment; + using Newtonsoft.Json.Linq; using Windows.System; /// @@ -132,5 +133,21 @@ public static PackageInstallerType ToPackageInstallerType(string value) _ => throw new InvalidOperationException(), }; } + + /// + /// Converts PSPackageRepairMode string value to PackageRepairMode. + /// + /// PSPackageRepairMode string value. + /// PackageRepairMode. + public static PackageRepairMode ToPackageRepairMode(string value) + { + return value switch + { + "Default" => PackageRepairMode.Default, + "Silent" => PackageRepairMode.Silent, + "Interactive" => PackageRepairMode.Interactive, + _ => throw new InvalidOperationException(), + }; + } } } diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/PackageManagerWrapper.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/PackageManagerWrapper.cs index 451369fb57..94770f7138 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/PackageManagerWrapper.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/PackageManagerWrapper.cs @@ -1,4 +1,4 @@ -// ----------------------------------------------------------------------------- +// ----------------------------------------------------------------------------- // // Copyright (c) Microsoft Corporation. Licensed under the MIT License. // @@ -88,6 +88,19 @@ public IAsyncOperationWithProgress Down false); } + /// + /// Wrapper for RepairPackagesAsync. + /// + /// The package to repair. + /// The repair options. + /// An async operation with progress. + public IAsyncOperationWithProgress RepairPackageAsync(CatalogPackage package, RepairOptions options) + { + return this.Execute( + () => this.packageManager.RepairPackageAsync(package, options), + false); + } + /// /// Wrapper for GetPackageCatalogs. /// diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/RepairOperationWithProgress.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/RepairOperationWithProgress.cs new file mode 100644 index 0000000000..d773dd1fa4 --- /dev/null +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/RepairOperationWithProgress.cs @@ -0,0 +1,44 @@ +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Helpers +{ + using System; + using System.Management.Automation; + using Microsoft.Management.Deployment; + using Microsoft.WinGet.Common.Command; + using Microsoft.WinGet.Resources; + using Windows.Foundation; + + /// + /// Handler for Repair Operation with Progress. + /// + internal class RepairOperationWithProgress : OperationWithProgressBase + { + /// + /// Initializes a new instance of the class. + /// + /// instance. + /// Activity. + public RepairOperationWithProgress(PowerShellCmdlet pwshCmdlet, string activity) + : base(pwshCmdlet, activity) + { + } + + /// + public override void Progress(IAsyncOperationWithProgress operation, RepairProgress progress) + { + ProgressRecord record = new (this.ActivityId, this.Activity, progress.State.ToString()) + { + RecordType = ProgressRecordType.Processing, + }; + + record.StatusDescription = Resources.Repairing; + record.PercentComplete = (int)(progress.RepairCompletionProgress * 100); + this.PwshCmdlet.Write(StreamType.Progress, record); + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSRepairResult.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSRepairResult.cs new file mode 100644 index 0000000000..973bf1587d --- /dev/null +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSRepairResult.cs @@ -0,0 +1,137 @@ +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.PSObjects +{ + using System; + using Microsoft.Management.Deployment; + + /// + /// PSRepairResult. + /// + public sealed class PSRepairResult + { + private readonly RepairResult repairResult; + private readonly CatalogPackage catalogPackage; + + /// + /// Initializes a new instance of the class. + /// + /// The Repair result COM Object. + /// The catalog package COM Object. + public PSRepairResult(RepairResult repairResult, CatalogPackage catalogPackage) + { + this.repairResult = repairResult; + this.catalogPackage = catalogPackage; + } + + /// + /// Gets the id of the repaired package. + /// + public string Id + { + get + { + return this.catalogPackage.Id; + } + } + + /// + /// Gets the name of the repaired package. + /// + public string Name + { + get + { + return this.catalogPackage.Name; + } + } + + /// + /// Gets the source name of the repaired package. + /// + public string Source + { + get + { + return this.catalogPackage.DefaultInstallVersion.PackageCatalog.Info.Name; + } + } + + /// + /// Gets the correlation data of the repair result. + /// + public string CorrelationData + { + get + { + return this.repairResult.CorrelationData; + } + } + + /// + /// Gets the extended error code exception of the failed repair result. + /// + public Exception ExtendedErrorCode + { + get + { + return this.repairResult.ExtendedErrorCode; + } + } + + /// + /// Gets a value indicating whether a reboot is required. + /// + public bool RebootRequired + { + get + { + return this.repairResult.RebootRequired; + } + } + + /// + /// Gets the error code of a repair. + /// + public uint RepairErrorCode + { + get + { + return this.repairResult.RepairErrorCode; + } + } + + /// + /// Gets the status of the repair result. + /// + public string Status + { + get + { + return this.repairResult.Status.ToString(); + } + } + + /// + /// If the repair succeeded. + /// + /// True if repair succeeded. + public bool Succeeded() + { + return this.repairResult.Status == RepairResultStatus.Ok; + } + + /// + /// Message with error information. + /// + /// Error message. + public string ErrorMessage() + { + return $"RepairStatus : '{this.Status}' RepairErrorCode: '{this.RepairErrorCode}' ExtendedError: '{this.ExtendedErrorCode.HResult}'"; + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Properties/Resources.Designer.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Properties/Resources.Designer.cs index f04162a2db..ee99fb3090 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Properties/Resources.Designer.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Properties/Resources.Designer.cs @@ -240,6 +240,15 @@ internal static string ProgressRecordActivityInstalling { } } + /// + /// Looks up a localized string similar to Repairing '{0}'. + /// + internal static string ProgressRecordActivityRepairing { + get { + return ResourceManager.GetString("ProgressRecordActivityRepairing", resourceCulture); + } + } + /// /// Looks up a localized string similar to Uninstalling '{0}'. /// @@ -303,6 +312,15 @@ internal static string RepairFailureMessage { } } + /// + /// Looks up a localized string similar to Repairing. + /// + internal static string Repairing { + get { + return ResourceManager.GetString("Repairing", resourceCulture); + } + } + /// /// Looks up a localized string similar to This cmdlet requires administrator privileges to execute.. /// diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Properties/Resources.resx b/src/PowerShell/Microsoft.WinGet.Client.Engine/Properties/Resources.resx index 735c55e853..297f3cac5f 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Properties/Resources.resx +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Properties/Resources.resx @@ -237,4 +237,11 @@ Exporting '{0}' {Locked="{0}"} {0} - The name of the package being exported. + + Repairing '{0}' + {Locked="{0}"} {0} - The name of the package being repaired. + + + Repairing + \ No newline at end of file diff --git a/src/PowerShell/Microsoft.WinGet.Client/Examples/Sample_RepairPackage.ps1 b/src/PowerShell/Microsoft.WinGet.Client/Examples/Sample_RepairPackage.ps1 new file mode 100644 index 0000000000..a9a1622659 --- /dev/null +++ b/src/PowerShell/Microsoft.WinGet.Client/Examples/Sample_RepairPackage.ps1 @@ -0,0 +1,22 @@ +<# + .SYNOPSIS + Example for 'Repair-WinGetPackage' cmdlet. + Repairs the specified application. +#> + +# TODO: Replace parameter with actual module name from PSGallery once module is released. +Param ( + [Parameter(Mandatory)] + $ModulePath +) + +Import-Module -Name $ModulePath + +# Repair a package by name +Repair-WinGetPackage -Name "PowerToys FileLocksmith Context Menu" + +# Repair a package by version and package identifier. +Repair-WinGetPackage -Id "MSIX\Microsoft.PowerToys.FileLocksmithContextMenu_1.0.0.0_neutral__8wekyb3d8bbwe" + +# Repair a package from a specific source +Repair-WinGetPackage -Id "MSIX\Microsoft.PowerToys.FileLocksmithContextMenu_1.0.0.0_neutral__8wekyb3d8bbwe" -Source winget diff --git a/src/PowerShell/Microsoft.WinGet.Client/ModuleFiles/Format.ps1xml b/src/PowerShell/Microsoft.WinGet.Client/ModuleFiles/Format.ps1xml index 0578d18083..baca8db997 100644 --- a/src/PowerShell/Microsoft.WinGet.Client/ModuleFiles/Format.ps1xml +++ b/src/PowerShell/Microsoft.WinGet.Client/ModuleFiles/Format.ps1xml @@ -1,4 +1,4 @@ - + @@ -347,11 +347,75 @@ $_.ProductCodes - + + + + Microsoft.WinGet.Client.Engine.PSObjects.PSRepairResult + + Microsoft.WinGet.Client.Engine.PSObjects.PSRepairResult + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + $_.Id + + + $_.Name + + + $_.Source + + + $_.RepairErrorCode + + + $_.Status + + + $_.RebootRequired + + + $_.ExtendedErrorCode + + + $_.CorrelationData + + + + - \ No newline at end of file + diff --git a/src/PowerShell/Microsoft.WinGet.Client/ModuleFiles/Microsoft.WinGet.Client.psd1 b/src/PowerShell/Microsoft.WinGet.Client/ModuleFiles/Microsoft.WinGet.Client.psd1 index c0f3786ee1..de61eec0a5 100644 --- a/src/PowerShell/Microsoft.WinGet.Client/ModuleFiles/Microsoft.WinGet.Client.psd1 +++ b/src/PowerShell/Microsoft.WinGet.Client/ModuleFiles/Microsoft.WinGet.Client.psd1 @@ -94,6 +94,7 @@ CmdletsToExport = @( 'Remove-WinGetSource' 'Reset-WinGetSource' 'Export-WinGetPackage' + 'Repair-WinGetPackage' ) # Variables to export from this module diff --git a/src/PowerShell/Microsoft.WinGet.Client/README.md b/src/PowerShell/Microsoft.WinGet.Client/README.md index 538afd1814..7feb687b24 100644 --- a/src/PowerShell/Microsoft.WinGet.Client/README.md +++ b/src/PowerShell/Microsoft.WinGet.Client/README.md @@ -1,4 +1,4 @@ -# Windows Package Manager PowerShell Module +# Windows Package Manager PowerShell Module The Windows Package Manager PowerShell Module is made up on two components @@ -41,6 +41,7 @@ If the new cmdlet introduces a new dependency, please make sure to add it in the - Get-WinGetSettings - Remove-WinGetSource - Reset-WinGetSource +- Repair-WinGetPackage ## Quick Start Guide From 752766b41b165026cb83770ac6ef12fccfbcacac Mon Sep 17 00:00:00 2001 From: Madhusudhan Gumbalapura Sudarshan Date: Fri, 23 Aug 2024 14:00:59 -0700 Subject: [PATCH 14/35] Fix parameter name in RepairPackageCommand constructor to address SpellCheck error Renamed the parameter `pSCatalogPackage` to `psCatalogPackage` in the `RepairPackageCommand` constructor and updated all references to ensure consistency and improve readability with camelCase naming. --- .../Commands/RepairPackageCommand.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/RepairPackageCommand.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/RepairPackageCommand.cs index f0a2434b98..cec9495c82 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/RepairPackageCommand.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/RepairPackageCommand.cs @@ -24,7 +24,7 @@ public sealed class RepairPackageCommand : PackageCommand /// Initializes a new instance of the class. /// /// Caller cmdlet. - /// PSCatalogPackage. + /// PSCatalogPackage. /// Version to repair. /// Logging file location. /// Package identifier. @@ -34,7 +34,7 @@ public sealed class RepairPackageCommand : PackageCommand /// Match against any field of a package. public RepairPackageCommand( PSCmdlet psCmdlet, - PSCatalogPackage pSCatalogPackage, + PSCatalogPackage psCatalogPackage, string version, string log, string id, @@ -44,9 +44,9 @@ public RepairPackageCommand( string[] query) : base(psCmdlet) { - if (pSCatalogPackage != null) + if (psCatalogPackage != null) { - this.CatalogPackage = pSCatalogPackage; + this.CatalogPackage = psCatalogPackage; } this.Version = version; From ffc4f8f727337aac1f379ce42b47b0f48ca782e1 Mon Sep 17 00:00:00 2001 From: Madhusudhan Gumbalapura Sudarshan Date: Fri, 23 Aug 2024 22:27:08 -0700 Subject: [PATCH 15/35] Fix Format.ps1xml formatting issue. Reformatted XML structure in Format.ps1xml file to resolve 'Node TableRowEntries is missing' error. Adjusted the and its nested elements accordingly. --- .../ModuleFiles/Format.ps1xml | 62 +++++++++---------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/src/PowerShell/Microsoft.WinGet.Client/ModuleFiles/Format.ps1xml b/src/PowerShell/Microsoft.WinGet.Client/ModuleFiles/Format.ps1xml index baca8db997..91202b0842 100644 --- a/src/PowerShell/Microsoft.WinGet.Client/ModuleFiles/Format.ps1xml +++ b/src/PowerShell/Microsoft.WinGet.Client/ModuleFiles/Format.ps1xml @@ -385,37 +385,37 @@ - - - - - - $_.Id - - - $_.Name - - - $_.Source - - - $_.RepairErrorCode - - - $_.Status - - - $_.RebootRequired - - - $_.ExtendedErrorCode - - - $_.CorrelationData - - - - + + + + + $_.Id + + + $_.Name + + + $_.Source + + + $_.RepairErrorCode + + + $_.Status + + + $_.RebootRequired + + + $_.ExtendedErrorCode + + + $_.CorrelationData + + + + + From 673e93067397d91955cfdc58e15f26e3ea98cb61 Mon Sep 17 00:00:00 2001 From: Madhusudhan Gumbalapura Sudarshan Date: Fri, 23 Aug 2024 22:48:07 -0700 Subject: [PATCH 16/35] Add test case for Repair-WinGetPackage cmdlet - Introduced `Install|Repair|Uninstall-WinGetPackage` test suite in `Microsoft.WinGet.Client.Tests.ps1` to cover installation, repair, and uninstallation of MSIX, Burn Installer, and Exe Installer packages. - Each test verifies expected results, including non-null results, correct package Ids, names, sources, error codes, statuses, and reboot requirements. - `BeforeAll` block adds a test source, and `AfterAll` block ensures cleanup by uninstalling test packages. --- .../tests/Microsoft.WinGet.Client.Tests.ps1 | 136 ++++++++++++++++++ 1 file changed, 136 insertions(+) diff --git a/src/PowerShell/tests/Microsoft.WinGet.Client.Tests.ps1 b/src/PowerShell/tests/Microsoft.WinGet.Client.Tests.ps1 index 46f9603586..9d23fff8de 100644 --- a/src/PowerShell/tests/Microsoft.WinGet.Client.Tests.ps1 +++ b/src/PowerShell/tests/Microsoft.WinGet.Client.Tests.ps1 @@ -308,6 +308,142 @@ Describe 'Install|Update|Uninstall-WinGetPackage' { } } +Describe 'Install|Repair|Uninstall-WinGetPackage' { + + BeforeAll { + AddTestSource + } + + It 'Install MSIX By Id' { + $result = Install-WinGetPackage -Id AppInstallerTest.TestMsixInstaller + + $result | Should -Not -BeNullOrEmpty -ErrorAction Stop + $result.Id | Should -Be "AppInstallerTest.TestMsixInstaller" + $result.Name | Should -Be "TestMsixInstaller" + $result.Source | Should -Be "TestSource" + $result.InstallerErrorCode | Should -Be 0 + $result.Status | Should -Be 'Ok' + $result.RebootRequired | Should -Be 'False' + } + + It 'Repair MSIX By Id' { + $result = Repair-WinGetPackage -Id AppInstallerTest.TestMsixInstaller + + $result | Should -Not -BeNullOrEmpty -ErrorAction Stop + $result.Id | Should -Be "AppInstallerTest.TestMsixInstaller" + $result.Name | Should -Be "TestMsixInstaller" + $result.Source | Should -Be "TestSource" + $result.RepairErrorCode | Should -Be 0 + $result.Status | Should -Be 'Ok' + $result.RebootRequired | Should -Be 'False' + } + + It 'Uninstall MSIX By Id' { + $result = Uninstall-WinGetPackage -Id AppInstallerTest.TestMsixInstaller + + $result | Should -Not -BeNullOrEmpty -ErrorAction Stop + $result.Id | Should -Be "AppInstallerTest.TestMsixInstaller" + $result.Name | Should -Be "TestMsixInstaller" + $result.Source | Should -Be "TestSource" + $result.UninstallerErrorCode | Should -Be 0 + $result.Status | Should -Be 'Ok' + $result.RebootRequired | Should -Be 'False' + } + + It 'Install Burn Installer By Id' { + $result = Install-WinGetPackage -Id AppInstallerTest.TestModifyRepair + + $result | Should -Not -BeNullOrEmpty -ErrorAction Stop + $result.Id | Should -Be "AppInstallerTest.TestModifyRepair" + $result.Name | Should -Be "TestModifyRepair" + $result.Source | Should -Be "TestSource" + $result.InstallerErrorCode | Should -Be 0 + $result.Status | Should -Be 'Ok' + $result.RebootRequired | Should -Be 'False' + } + + It 'Modify Repair Burn Installer By Id' { + $result = Repair-WinGetPackage -Id AppInstallerTest.TestModifyRepair + + $result | Should -Not -BeNullOrEmpty -ErrorAction Stop + $result.Id | Should -Be "AppInstallerTest.TestModifyRepair" + $result.Name | Should -Be "TestModifyRepair" + $result.Source | Should -Be "TestSource" + $result.RepairErrorCode | Should -Be 0 + $result.Status | Should -Be 'Ok' + $result.RebootRequired | Should -Be 'False' + } + + It 'Uninstall Burn Installer By Id' { + $result = Uninstall-WinGetPackage -Id AppInstallerTest.TestModifyRepair + + $result | Should -Not -BeNullOrEmpty -ErrorAction Stop + $result.Id | Should -Be "AppInstallerTest.TestModifyRepair" + $result.Name | Should -Be "TestModifyRepair" + $result.Source | Should -Be "TestSource" + $result.UninstallerErrorCode | Should -Be 0 + $result.Status | Should -Be 'Ok' + $result.RebootRequired | Should -Be 'False' + } + + It 'Install Exe Installer By Id' { + $result = Install-WinGetPackage -Id AppInstallerTest.UninstallerRepair + + $result | Should -Not -BeNullOrEmpty -ErrorAction Stop + $result.Id | Should -Be "AppInstallerTest.UninstallerRepair" + $result.Name | Should -Be "UninstallerRepair" + $result.Source | Should -Be "TestSource" + $result.InstallerErrorCode | Should -Be 0 + $result.Status | Should -Be 'Ok' + $result.RebootRequired | Should -Be 'False' + } + + It 'Uninstaller Repair Exe Installer By Id' { + $result = Repair-WinGetPackage -Id AppInstallerTest.UninstallerRepair + + $result | Should -Not -BeNullOrEmpty -ErrorAction Stop + $result.Id | Should -Be "AppInstallerTest.UninstallerRepair" + $result.Name | Should -Be "UninstallerRepair" + $result.Source | Should -Be "TestSource" + $result.RepairErrorCode | Should -Be 0 + $result.Status | Should -Be 'Ok' + $result.RebootRequired | Should -Be 'False' + } + + It "Uninstall Exe Installer By Id" { + $result = Uninstall-WinGetPackage -Id AppInstallerTest.UninstallerRepair + + $result | Should -Not -BeNullOrEmpty -ErrorAction Stop + $result.Id | Should -Be "AppInstallerTest.UninstallerRepair" + $result.Name | Should -Be "UninstallerRepair" + $result.Source | Should -Be "TestSource" + $result.UninstallerErrorCode | Should -Be 0 + $result.Status | Should -Be 'Ok' + $result.RebootRequired | Should -Be 'False' + } + + AfterAll { + # Uninstall all test packages after each for proper cleanup. + $testMsix = Get-WinGetPackage -Id AppInstallerTest.TestMsixInstaller + if ($testMsix.Count -gt 0) + { + Uninstall-WinGetPackage -Id AppInstallerTest.TestMsixInstaller + } + + $testBurn = Get-WinGetPackage -Id AppInstallerTest.TestModifyRepair + if ($testBurn.Count -gt 0) + { + Uninstall-WinGetPackage -Id AppInstallerTest.TestModifyRepair + } + + $testExe = Get-WinGetPackage -Id AppInstallerTest.UninstallerRepair + if ($testExe.Count -gt 0) + { + Uninstall-WinGetPackage -Id AppInstallerTest.UninstallerRepair + } + } +} + Describe 'Get-WinGetPackage' { BeforeAll { From dc7b1aa9ab63e7a184071268356a4e3d75a8b404 Mon Sep 17 00:00:00 2001 From: Madhusudhan Gumbalapura Sudarshan Date: Sat, 24 Aug 2024 01:49:38 -0700 Subject: [PATCH 17/35] Refactor and optimize Repair-WinGetPackage tests - Added `Validate-WinGetResultCommonFields` and `Validate-WinGetPackageOperationResult` functions in `Microsoft.WinGet.Client.Tests.ps1` to validate common fields and specific operation results for WinGet package operations. - Refactor install, repair, and uninstall test cases to use these functions, removing individual validation steps and adding contexts for better organization. - Simplify test logic by creating an expected result object and passing it to the validation function. --- .../tests/Microsoft.WinGet.Client.Tests.ps1 | 194 +++++++++--------- 1 file changed, 101 insertions(+), 93 deletions(-) diff --git a/src/PowerShell/tests/Microsoft.WinGet.Client.Tests.ps1 b/src/PowerShell/tests/Microsoft.WinGet.Client.Tests.ps1 index 9d23fff8de..03253a944a 100644 --- a/src/PowerShell/tests/Microsoft.WinGet.Client.Tests.ps1 +++ b/src/PowerShell/tests/Microsoft.WinGet.Client.Tests.ps1 @@ -126,6 +126,38 @@ BeforeAll { { return Join-Path -Path $env:Temp -ChildPath "WingetPwshTest-$(New-Guid)" } + + function Validate-WinGetResultCommonFields([psobject]$result, [psobject]$expected) { + $result | Should -Not -BeNullOrEmpty -ErrorAction Stop + $result.Id | Should -Be $expected.Id + $result.Name | Should -Be $expected.Name + $result.Source | Should -Be $expected.Source + $result.Status | Should -Be $expected.Status + } + + function Validate-WinGetPackageOperationResult([psobject]$result, [psobject]$expected, [string]$operationType) + { + Validate-WinGetResultCommonFields $result $expected + $result.RebootRequired | Should -Be $expected.RebootRequired + + switch ($operationType) { + 'install' { + $result.InstallerErrorCode | Should -Be $expected.InstallerErrorCode + } + 'update' { + $result.InstallerErrorCode | Should -Be $expected.InstallerErrorCode + } + 'repair' { + $result.RepairErrorCode | Should -Be $expected.RepairErrorCode + } + 'uninstall' { + $result.UninstallerErrorCode | Should -Be $expected.UninstallerErrorCode + } + default { + throw "Unknown operation type: $operationType" + } + } + } } Describe 'Get-WinGetVersion' { @@ -314,112 +346,88 @@ Describe 'Install|Repair|Uninstall-WinGetPackage' { AddTestSource } - It 'Install MSIX By Id' { - $result = Install-WinGetPackage -Id AppInstallerTest.TestMsixInstaller - - $result | Should -Not -BeNullOrEmpty -ErrorAction Stop - $result.Id | Should -Be "AppInstallerTest.TestMsixInstaller" - $result.Name | Should -Be "TestMsixInstaller" - $result.Source | Should -Be "TestSource" - $result.InstallerErrorCode | Should -Be 0 - $result.Status | Should -Be 'Ok' - $result.RebootRequired | Should -Be 'False' - } - - It 'Repair MSIX By Id' { - $result = Repair-WinGetPackage -Id AppInstallerTest.TestMsixInstaller - - $result | Should -Not -BeNullOrEmpty -ErrorAction Stop - $result.Id | Should -Be "AppInstallerTest.TestMsixInstaller" - $result.Name | Should -Be "TestMsixInstaller" - $result.Source | Should -Be "TestSource" - $result.RepairErrorCode | Should -Be 0 - $result.Status | Should -Be 'Ok' - $result.RebootRequired | Should -Be 'False' - } - - It 'Uninstall MSIX By Id' { - $result = Uninstall-WinGetPackage -Id AppInstallerTest.TestMsixInstaller - - $result | Should -Not -BeNullOrEmpty -ErrorAction Stop - $result.Id | Should -Be "AppInstallerTest.TestMsixInstaller" - $result.Name | Should -Be "TestMsixInstaller" - $result.Source | Should -Be "TestSource" - $result.UninstallerErrorCode | Should -Be 0 - $result.Status | Should -Be 'Ok' - $result.RebootRequired | Should -Be 'False' - } - - It 'Install Burn Installer By Id' { - $result = Install-WinGetPackage -Id AppInstallerTest.TestModifyRepair + Context 'MSIX Repair Scenario' { + $expectedResult = [PSCustomObject]@{ + Id = "AppInstallerTest.TestMsixInstaller" + Name = "TestMsixInstaller" + Source = "TestSource" + Status = 'Ok' + RebootRequired = 'False' + } - $result | Should -Not -BeNullOrEmpty -ErrorAction Stop - $result.Id | Should -Be "AppInstallerTest.TestModifyRepair" - $result.Name | Should -Be "TestModifyRepair" - $result.Source | Should -Be "TestSource" - $result.InstallerErrorCode | Should -Be 0 - $result.Status | Should -Be 'Ok' - $result.RebootRequired | Should -Be 'False' - } + It 'Install MSIX By Id' { + $result = Install-WinGetPackage -Id AppInstallerTest.TestMsixInstaller + $expectedResult.InstallerErrorCode = 0; + Validate-WinGetPackageOperationResult $result $expectedResult 'install' + } - It 'Modify Repair Burn Installer By Id' { - $result = Repair-WinGetPackage -Id AppInstallerTest.TestModifyRepair + It 'Repair MSIX By Id' { + $result = Repair-WinGetPackage -Id AppInstallerTest.TestMsixInstaller + $expectedResult.RepairErrorCode = 0 + Validate-WinGetPackageOperationResult $result $expectedResult 'repair' + } - $result | Should -Not -BeNullOrEmpty -ErrorAction Stop - $result.Id | Should -Be "AppInstallerTest.TestModifyRepair" - $result.Name | Should -Be "TestModifyRepair" - $result.Source | Should -Be "TestSource" - $result.RepairErrorCode | Should -Be 0 - $result.Status | Should -Be 'Ok' - $result.RebootRequired | Should -Be 'False' + It 'Uninstall MSIX By Id' { + $result = Uninstall-WinGetPackage -Id AppInstallerTest.TestMsixInstaller + $expectedResult.UninstallerErrorCode = 0 + Validate-WinGetPackageOperationResult $result $expectedResult 'uninstall' + } } - It 'Uninstall Burn Installer By Id' { - $result = Uninstall-WinGetPackage -Id AppInstallerTest.TestModifyRepair + Context 'Burn installer "Modify" Repair Scenario' { + $expectedResult = [PSCustomObject]@{ + Id = "AppInstallerTest.TestModifyRepair" + Name = "TestModifyRepair" + Source = "TestSource" + Status = 'Ok' + RebootRequired = 'False' + } - $result | Should -Not -BeNullOrEmpty -ErrorAction Stop - $result.Id | Should -Be "AppInstallerTest.TestModifyRepair" - $result.Name | Should -Be "TestModifyRepair" - $result.Source | Should -Be "TestSource" - $result.UninstallerErrorCode | Should -Be 0 - $result.Status | Should -Be 'Ok' - $result.RebootRequired | Should -Be 'False' - } + It 'Install Burn Installer By Id' { + $result = Install-WinGetPackage -Id AppInstallerTest.TestModifyRepair + $expectedResult.InstallerErrorCode = 0 + Validate-WinGetPackageOperationResult $result $expectedResult 'install' + } - It 'Install Exe Installer By Id' { - $result = Install-WinGetPackage -Id AppInstallerTest.UninstallerRepair + It 'Repair Burn Installer By Id' { + $result = Repair-WinGetPackage -Id AppInstallerTest.TestModifyRepair + $expectedResult.RepairErrorCode = 0 + Validate-WinGetPackageOperationResult $result $expectedResult 'repair' + } - $result | Should -Not -BeNullOrEmpty -ErrorAction Stop - $result.Id | Should -Be "AppInstallerTest.UninstallerRepair" - $result.Name | Should -Be "UninstallerRepair" - $result.Source | Should -Be "TestSource" - $result.InstallerErrorCode | Should -Be 0 - $result.Status | Should -Be 'Ok' - $result.RebootRequired | Should -Be 'False' + It 'Uninstall Burn Installer By Id' { + $result = Uninstall-WinGetPackage -Id AppInstallerTest.TestModifyRepair + $expectedResult.UninstallerErrorCode = 0 + Validate-WinGetPackageOperationResult $result $expectedResult 'uninstall' + } } - It 'Uninstaller Repair Exe Installer By Id' { - $result = Repair-WinGetPackage -Id AppInstallerTest.UninstallerRepair + Context 'Exe Installer "Uninstaller" Repair Scenario' { + $expectedResult = [PSCustomObject]@{ + Id = "AppInstallerTest.UninstallerRepair" + Name = "UninstallerRepair" + Source = "TestSource" + Status = 'Ok' + RebootRequired = 'False' + } - $result | Should -Not -BeNullOrEmpty -ErrorAction Stop - $result.Id | Should -Be "AppInstallerTest.UninstallerRepair" - $result.Name | Should -Be "UninstallerRepair" - $result.Source | Should -Be "TestSource" - $result.RepairErrorCode | Should -Be 0 - $result.Status | Should -Be 'Ok' - $result.RebootRequired | Should -Be 'False' - } + It 'Install Exe Installer By Id' { + $result = Install-WinGetPackage -Id AppInstallerTest.UninstallerRepair + $expectedResult.InstallerErrorCode = 0 + Validate-WinGetPackageOperationResult $result $expectedResult 'install' + } - It "Uninstall Exe Installer By Id" { - $result = Uninstall-WinGetPackage -Id AppInstallerTest.UninstallerRepair + It 'Uninstaller Repair Exe Installer By Id' { + $result = Repair-WinGetPackage -Id AppInstallerTest.UninstallerRepair + $expectedResult.RepairErrorCode = 0 + Validate-WinGetPackageOperationResult $result $expectedResult 'repair' + } - $result | Should -Not -BeNullOrEmpty -ErrorAction Stop - $result.Id | Should -Be "AppInstallerTest.UninstallerRepair" - $result.Name | Should -Be "UninstallerRepair" - $result.Source | Should -Be "TestSource" - $result.UninstallerErrorCode | Should -Be 0 - $result.Status | Should -Be 'Ok' - $result.RebootRequired | Should -Be 'False' + It "Uninstall Exe Installer By Id" { + $result = Uninstall-WinGetPackage -Id AppInstallerTest.UninstallerRepair + $expectedResult.UninstallerErrorCode = 0 + Validate-WinGetPackageOperationResult $result $expectedResult 'uninstall' + } } AfterAll { From db2bc395b47884202aaba7609046b40de20c7757 Mon Sep 17 00:00:00 2001 From: Madhusudhan Gumbalapura Sudarshan Date: Sat, 24 Aug 2024 11:41:33 -0700 Subject: [PATCH 18/35] Fixed repair scenario test failures - Addressed repair scenario test failures by adding InstallerErrorCode, RepairErrorCode, and UninstallerErrorCode properties with a default value of 0 to the test cases expectedResult object - Removed unnecessary error code assignment This fix resolves the runtime exceptions: 1. The property 'InstallerErrorCode' cannot be found on this object, 2. The property 'RepairErrorCode' cannot be found on this object, and 3. The property 'UninstallerErrorCode' cannot be found on this object'. --- .../tests/Microsoft.WinGet.Client.Tests.ps1 | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/PowerShell/tests/Microsoft.WinGet.Client.Tests.ps1 b/src/PowerShell/tests/Microsoft.WinGet.Client.Tests.ps1 index 03253a944a..0c965d637f 100644 --- a/src/PowerShell/tests/Microsoft.WinGet.Client.Tests.ps1 +++ b/src/PowerShell/tests/Microsoft.WinGet.Client.Tests.ps1 @@ -353,23 +353,23 @@ Describe 'Install|Repair|Uninstall-WinGetPackage' { Source = "TestSource" Status = 'Ok' RebootRequired = 'False' + InstallerErrorCode = 0 + RepairErrorCode = 0 + UninstallerErrorCode = 0 } It 'Install MSIX By Id' { $result = Install-WinGetPackage -Id AppInstallerTest.TestMsixInstaller - $expectedResult.InstallerErrorCode = 0; Validate-WinGetPackageOperationResult $result $expectedResult 'install' } It 'Repair MSIX By Id' { $result = Repair-WinGetPackage -Id AppInstallerTest.TestMsixInstaller - $expectedResult.RepairErrorCode = 0 Validate-WinGetPackageOperationResult $result $expectedResult 'repair' } It 'Uninstall MSIX By Id' { $result = Uninstall-WinGetPackage -Id AppInstallerTest.TestMsixInstaller - $expectedResult.UninstallerErrorCode = 0 Validate-WinGetPackageOperationResult $result $expectedResult 'uninstall' } } @@ -381,23 +381,23 @@ Describe 'Install|Repair|Uninstall-WinGetPackage' { Source = "TestSource" Status = 'Ok' RebootRequired = 'False' + InstallerErrorCode = 0 + RepairErrorCode = 0 + UninstallerErrorCode = 0 } It 'Install Burn Installer By Id' { $result = Install-WinGetPackage -Id AppInstallerTest.TestModifyRepair - $expectedResult.InstallerErrorCode = 0 Validate-WinGetPackageOperationResult $result $expectedResult 'install' } It 'Repair Burn Installer By Id' { $result = Repair-WinGetPackage -Id AppInstallerTest.TestModifyRepair - $expectedResult.RepairErrorCode = 0 Validate-WinGetPackageOperationResult $result $expectedResult 'repair' } It 'Uninstall Burn Installer By Id' { $result = Uninstall-WinGetPackage -Id AppInstallerTest.TestModifyRepair - $expectedResult.UninstallerErrorCode = 0 Validate-WinGetPackageOperationResult $result $expectedResult 'uninstall' } } @@ -409,23 +409,23 @@ Describe 'Install|Repair|Uninstall-WinGetPackage' { Source = "TestSource" Status = 'Ok' RebootRequired = 'False' + InstallerErrorCode = 0 + RepairErrorCode = 0 + UninstallerErrorCode = 0 } It 'Install Exe Installer By Id' { $result = Install-WinGetPackage -Id AppInstallerTest.UninstallerRepair - $expectedResult.InstallerErrorCode = 0 Validate-WinGetPackageOperationResult $result $expectedResult 'install' } It 'Uninstaller Repair Exe Installer By Id' { $result = Repair-WinGetPackage -Id AppInstallerTest.UninstallerRepair - $expectedResult.RepairErrorCode = 0 Validate-WinGetPackageOperationResult $result $expectedResult 'repair' } It "Uninstall Exe Installer By Id" { $result = Uninstall-WinGetPackage -Id AppInstallerTest.UninstallerRepair - $expectedResult.UninstallerErrorCode = 0 Validate-WinGetPackageOperationResult $result $expectedResult 'uninstall' } } From e8e30131529e1d0d344aca448e7daf54d0a70c43 Mon Sep 17 00:00:00 2001 From: Madhusudhan Gumbalapura Sudarshan Date: Sat, 24 Aug 2024 19:05:16 -0700 Subject: [PATCH 19/35] Add BeforeEach blocks to initialize $expectedResult in tests, fixing the variable initialization issue that results in test failures --- .../tests/Microsoft.WinGet.Client.Tests.ps1 | 60 ++++++++++--------- 1 file changed, 33 insertions(+), 27 deletions(-) diff --git a/src/PowerShell/tests/Microsoft.WinGet.Client.Tests.ps1 b/src/PowerShell/tests/Microsoft.WinGet.Client.Tests.ps1 index 0c965d637f..c8edc7b138 100644 --- a/src/PowerShell/tests/Microsoft.WinGet.Client.Tests.ps1 +++ b/src/PowerShell/tests/Microsoft.WinGet.Client.Tests.ps1 @@ -347,15 +347,17 @@ Describe 'Install|Repair|Uninstall-WinGetPackage' { } Context 'MSIX Repair Scenario' { - $expectedResult = [PSCustomObject]@{ - Id = "AppInstallerTest.TestMsixInstaller" - Name = "TestMsixInstaller" - Source = "TestSource" - Status = 'Ok' - RebootRequired = 'False' - InstallerErrorCode = 0 - RepairErrorCode = 0 - UninstallerErrorCode = 0 + BeforeEach { + $expectedResult = [PSCustomObject]@{ + Id = "AppInstallerTest.TestMsixInstaller" + Name = "TestMsixInstaller" + Source = "TestSource" + Status = 'Ok' + RebootRequired = 'False' + InstallerErrorCode = 0 + RepairErrorCode = 0 + UninstallerErrorCode = 0 + } } It 'Install MSIX By Id' { @@ -375,15 +377,17 @@ Describe 'Install|Repair|Uninstall-WinGetPackage' { } Context 'Burn installer "Modify" Repair Scenario' { - $expectedResult = [PSCustomObject]@{ - Id = "AppInstallerTest.TestModifyRepair" - Name = "TestModifyRepair" - Source = "TestSource" - Status = 'Ok' - RebootRequired = 'False' - InstallerErrorCode = 0 - RepairErrorCode = 0 - UninstallerErrorCode = 0 + BeforeEach { + $expectedResult = [PSCustomObject]@{ + Id = "AppInstallerTest.TestModifyRepair" + Name = "TestModifyRepair" + Source = "TestSource" + Status = 'Ok' + RebootRequired = 'False' + InstallerErrorCode = 0 + RepairErrorCode = 0 + UninstallerErrorCode = 0 + } } It 'Install Burn Installer By Id' { @@ -403,15 +407,17 @@ Describe 'Install|Repair|Uninstall-WinGetPackage' { } Context 'Exe Installer "Uninstaller" Repair Scenario' { - $expectedResult = [PSCustomObject]@{ - Id = "AppInstallerTest.UninstallerRepair" - Name = "UninstallerRepair" - Source = "TestSource" - Status = 'Ok' - RebootRequired = 'False' - InstallerErrorCode = 0 - RepairErrorCode = 0 - UninstallerErrorCode = 0 + BeforeEach { + $expectedResult = [PSCustomObject]@{ + Id = "AppInstallerTest.UninstallerRepair" + Name = "UninstallerRepair" + Source = "TestSource" + Status = 'Ok' + RebootRequired = 'False' + InstallerErrorCode = 0 + RepairErrorCode = 0 + UninstallerErrorCode = 0 + } } It 'Install Exe Installer By Id' { From 4f688125fb30ae94a06c7a8b15091b7d88c1beaf Mon Sep 17 00:00:00 2001 From: Madhusudhan Gumbalapura Sudarshan Date: Sat, 24 Aug 2024 23:10:08 -0700 Subject: [PATCH 20/35] Refactored test cases for Install|Update|Uninstall-WinGetPackage cmdlets: - Added BeforeEach block to define expected result objects. - Replaced inline assertions with Validate-WinGetPackageOperationResult. - Centralized validation logic to improve readability and maintainability. --- .../tests/Microsoft.WinGet.Client.Tests.ps1 | 86 +++++++------------ 1 file changed, 33 insertions(+), 53 deletions(-) diff --git a/src/PowerShell/tests/Microsoft.WinGet.Client.Tests.ps1 b/src/PowerShell/tests/Microsoft.WinGet.Client.Tests.ps1 index c8edc7b138..b4614819f6 100644 --- a/src/PowerShell/tests/Microsoft.WinGet.Client.Tests.ps1 +++ b/src/PowerShell/tests/Microsoft.WinGet.Client.Tests.ps1 @@ -252,76 +252,56 @@ Describe 'Install|Update|Uninstall-WinGetPackage' { AddTestSource } - It 'Install by Id' { - $result = Install-WinGetPackage -Id AppInstallerTest.TestExeInstaller -Version '1.0.0.0' + BeforeEach { + $expectedExeInstallerResult = [PSCustomObject]@{ + Id = "AppInstallerTest.TestExeInstaller" + Name = "TestExeInstaller" + Source = "TestSource" + Status = 'Ok' + RebootRequired = 'False' + InstallerErrorCode = 0 + UninstallerErrorCode = 0 + } - $result | Should -Not -BeNullOrEmpty -ErrorAction Stop - $result.Id | Should -Be "AppInstallerTest.TestExeInstaller" - $result.Name | Should -Be "TestExeInstaller" - $result.Source | Should -Be "TestSource" - $result.InstallerErrorCode | Should -Be 0 - $result.Status | Should -Be 'Ok' - $result.RebootRequired | Should -Be 'False' + $expectedPortableInstallerResult = [PSCustomObject]@{ + Id = "AppInstallerTest.TestPortableExe" + Name = "TestPortableExe" + Source = "TestSource" + Status = 'Ok' + RebootRequired = 'False' + InstallerErrorCode = 0 + UninstallerErrorCode = 0 + } } + It 'Install by Id' { + $result = Install-WinGetPackage -Id AppInstallerTest.TestExeInstaller -Version '1.0.0.0' + Validate-WinGetPackageOperationResult $result $expectedExeInstallerResult 'install' + } + It 'Install by exact Name and Version' { $result = Install-WinGetPackage -Name TestPortableExe -Version '2.0.0.0' -MatchOption Equals - - $result | Should -Not -BeNullOrEmpty -ErrorAction Stop - $result.Id | Should -Be "AppInstallerTest.TestPortableExe" - $result.Name | Should -Be "TestPortableExe" - $result.Source | Should -Be "TestSource" - $result.InstallerErrorCode | Should -Be 0 - $result.Status | Should -Be 'Ok' - $result.RebootRequired | Should -Be 'False' + Validate-WinGetPackageOperationResult $result $expectedPortableInstallerResult 'install' } - + It 'Update by Id' { $result = Update-WinGetPackage -Id AppInstallerTest.TestExeInstaller - - $result | Should -Not -BeNullOrEmpty -ErrorAction Stop - $result.Id | Should -Be "AppInstallerTest.TestExeInstaller" - $result.Name | Should -Be "TestExeInstaller" - $result.Source | Should -Be "TestSource" - $result.InstallerErrorCode | Should -Be 0 - $result.Status | Should -Be 'Ok' - $result.RebootRequired | Should -Be 'False' + Validate-WinGetPackageOperationResult $result $expectedExeInstallerResult 'update' } - + It 'Update by Name' { $result = Update-WinGetPackage -Name TestPortableExe - - $result | Should -Not -BeNullOrEmpty -ErrorAction Stop - $result.Id | Should -Be "AppInstallerTest.TestPortableExe" - $result.Name | Should -Be "TestPortableExe" - $result.Source | Should -Be "TestSource" - $result.InstallerErrorCode | Should -Be 0 - $result.Status | Should -Be 'Ok' - $result.RebootRequired | Should -Be 'False' + Validate-WinGetPackageOperationResult $result $expectedPortableInstallerResult 'update' } - + It 'Uninstall by Id' { $result = Uninstall-WinGetPackage -Id AppInstallerTest.TestExeInstaller - - $result | Should -Not -BeNullOrEmpty -ErrorAction Stop - $result.Id | Should -Be "AppInstallerTest.TestExeInstaller" - $result.Name | Should -Be "TestExeInstaller" - $result.Source | Should -Be "TestSource" - $result.UninstallerErrorCode | Should -Be 0 - $result.Status | Should -Be 'Ok' - $result.RebootRequired | Should -Be 'False' + Validate-WinGetPackageOperationResult $result $expectedExeInstallerResult 'uninstall' } - + It 'Uninstall by Name' { $result = Uninstall-WinGetPackage -Name TestPortableExe - - $result | Should -Not -BeNullOrEmpty -ErrorAction Stop - $result.Id | Should -Be "AppInstallerTest.TestPortableExe" - $result.Name | Should -Be "TestPortableExe" - $result.Source | Should -Be "TestSource" - $result.UninstallerErrorCode | Should -Be 0 - $result.Status | Should -Be 'Ok' - $result.RebootRequired | Should -Be 'False' + Validate-WinGetPackageOperationResult $result $expectedPortableInstallerResult 'uninstall' } AfterAll { From dce2094d042655d7d3293c1405139a896e00c8ff Mon Sep 17 00:00:00 2001 From: Madhusudhan Gumbalapura Sudarshan Date: Sat, 24 Aug 2024 23:13:46 -0700 Subject: [PATCH 21/35] Expand Repair test scenario to include Installer-based Repair scenario. --- .../tests/Microsoft.WinGet.Client.Tests.ps1 | 52 ++++++++++++++++--- 1 file changed, 44 insertions(+), 8 deletions(-) diff --git a/src/PowerShell/tests/Microsoft.WinGet.Client.Tests.ps1 b/src/PowerShell/tests/Microsoft.WinGet.Client.Tests.ps1 index b4614819f6..f0ac32379d 100644 --- a/src/PowerShell/tests/Microsoft.WinGet.Client.Tests.ps1 +++ b/src/PowerShell/tests/Microsoft.WinGet.Client.Tests.ps1 @@ -278,27 +278,27 @@ Describe 'Install|Update|Uninstall-WinGetPackage' { $result = Install-WinGetPackage -Id AppInstallerTest.TestExeInstaller -Version '1.0.0.0' Validate-WinGetPackageOperationResult $result $expectedExeInstallerResult 'install' } - + It 'Install by exact Name and Version' { $result = Install-WinGetPackage -Name TestPortableExe -Version '2.0.0.0' -MatchOption Equals Validate-WinGetPackageOperationResult $result $expectedPortableInstallerResult 'install' } - + It 'Update by Id' { $result = Update-WinGetPackage -Id AppInstallerTest.TestExeInstaller Validate-WinGetPackageOperationResult $result $expectedExeInstallerResult 'update' } - + It 'Update by Name' { $result = Update-WinGetPackage -Name TestPortableExe Validate-WinGetPackageOperationResult $result $expectedPortableInstallerResult 'update' } - + It 'Uninstall by Id' { $result = Uninstall-WinGetPackage -Id AppInstallerTest.TestExeInstaller Validate-WinGetPackageOperationResult $result $expectedExeInstallerResult 'uninstall' } - + It 'Uninstall by Name' { $result = Uninstall-WinGetPackage -Name TestPortableExe Validate-WinGetPackageOperationResult $result $expectedPortableInstallerResult 'uninstall' @@ -317,7 +317,7 @@ Describe 'Install|Update|Uninstall-WinGetPackage' { { Uninstall-WinGetPackage -Id AppInstallerTest.TestPortableExe } - } + } } Describe 'Install|Repair|Uninstall-WinGetPackage' { @@ -416,6 +416,36 @@ Describe 'Install|Repair|Uninstall-WinGetPackage' { } } + Context 'Inno "Installer" Repair Scenario' { + BeforeEach { + $expectedResult = [PSCustomObject]@{ + Id = "AppInstallerTest.TestInstallerRepair" + Name = "TestInstallerRepair" + Source = "TestSource" + Status = 'Ok' + RebootRequired = 'False' + InstallerErrorCode = 0 + RepairErrorCode = 0 + UninstallerErrorCode = 0 + } + } + + It 'Install Exe Installer By Id' { + $result = Install-WinGetPackage -Id AppInstallerTest.TestInstallerRepair + Validate-WinGetPackageOperationResult $result $expectedResult 'install' + } + + It 'Installer Repair Exe Installer By Id' { + $result = Repair-WinGetPackage -Id AppInstallerTest.TestInstallerRepair + Validate-WinGetPackageOperationResult $result $expectedResult 'repair' + } + + It "Uninstall Exe Installer By Id" { + $result = Uninstall-WinGetPackage -Id AppInstallerTest.TestInstallerRepair + Validate-WinGetPackageOperationResult $result $expectedResult 'uninstall' + } + } + AfterAll { # Uninstall all test packages after each for proper cleanup. $testMsix = Get-WinGetPackage -Id AppInstallerTest.TestMsixInstaller @@ -423,18 +453,24 @@ Describe 'Install|Repair|Uninstall-WinGetPackage' { { Uninstall-WinGetPackage -Id AppInstallerTest.TestMsixInstaller } - + $testBurn = Get-WinGetPackage -Id AppInstallerTest.TestModifyRepair if ($testBurn.Count -gt 0) { Uninstall-WinGetPackage -Id AppInstallerTest.TestModifyRepair } - + $testExe = Get-WinGetPackage -Id AppInstallerTest.UninstallerRepair if ($testExe.Count -gt 0) { Uninstall-WinGetPackage -Id AppInstallerTest.UninstallerRepair } + + $testInno = Get-WinGetPackage -Id AppInstallerTest.TestInstallerRepair + if ($testInno.Count -gt 0) + { + Uninstall-WinGetPackage -Id AppInstallerTest.TestInstallerRepair + } } } From 995bd5877905eecd263fef0026cddf8a74ac7e5c Mon Sep 17 00:00:00 2001 From: Madhusudhan Gumbalapura Sudarshan Date: Mon, 26 Aug 2024 19:41:34 -0700 Subject: [PATCH 22/35] Correct comment in PackageManager.cpp for package repair --- src/Microsoft.Management.Deployment/PackageManager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.Management.Deployment/PackageManager.cpp b/src/Microsoft.Management.Deployment/PackageManager.cpp index 29c7b838e0..8ce35939aa 100644 --- a/src/Microsoft.Management.Deployment/PackageManager.cpp +++ b/src/Microsoft.Management.Deployment/PackageManager.cpp @@ -1161,7 +1161,7 @@ namespace winrt::Microsoft::Management::Deployment::implementation // options and catalog can both be null, package must be set. WINGET_RETURN_REPAIR_RESULT_HR_IF(APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS, !package); - // the package should have an installed version to be uninstalled. + // the package should have an installed version to be Repaired. WINGET_RETURN_REPAIR_RESULT_HR_IF(APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS, !package.InstalledVersion()); HRESULT hr = S_OK; From 442a7a52a3f42ca33255ee1c0b850f9f178742ed Mon Sep 17 00:00:00 2001 From: Madhusudhan Gumbalapura Sudarshan Date: Mon, 26 Aug 2024 19:42:11 -0700 Subject: [PATCH 23/35] Refactor namespace for PSPackageRepairMode enum Changed the namespace for the `PSPackageRepairMode` enum from `Microsoft.WinGet.Client.Cmdlets.Cmdlets.PSObjects` to `Microsoft.WinGet.Client.PSObjects`. Updated `RepairPackageCmdlet.cs` to reflect this change, ensuring correct references. --- .../Cmdlets/PSObjects/PSPackageRepairMode.cs | 2 +- .../Cmdlets/RepairPackageCmdlet.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/PSObjects/PSPackageRepairMode.cs b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/PSObjects/PSPackageRepairMode.cs index 538a444f71..445378fa26 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/PSObjects/PSPackageRepairMode.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/PSObjects/PSPackageRepairMode.cs @@ -4,7 +4,7 @@ // // ----------------------------------------------------------------------------- -namespace Microsoft.WinGet.Client.Cmdlets.Cmdlets.PSObjects +namespace Microsoft.WinGet.Client.PSObjects { /// /// Must match Microsoft.Management.Deployment.PackageRepairMode. diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/RepairPackageCmdlet.cs b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/RepairPackageCmdlet.cs index ea86869f64..e1b1075d56 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/RepairPackageCmdlet.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/RepairPackageCmdlet.cs @@ -7,11 +7,11 @@ namespace Microsoft.WinGet.Client.Commands { using System.Management.Automation; - using Microsoft.WinGet.Client.Cmdlets.Cmdlets.PSObjects; using Microsoft.WinGet.Client.Commands.Common; using Microsoft.WinGet.Client.Common; using Microsoft.WinGet.Client.Engine.Commands; using Microsoft.WinGet.Client.Engine.PSObjects; + using Microsoft.WinGet.Client.PSObjects; /// /// This class defines the repair package command. From 828284fe3ede5c6a01afd0421a032fdc7ead6ad1 Mon Sep 17 00:00:00 2001 From: Madhusudhan Gumbalapura Sudarshan Date: Mon, 26 Aug 2024 19:43:31 -0700 Subject: [PATCH 24/35] Add Repair-WinGetPackage cmdlet documentation Updated `Microsoft.WinGet.Client.md` to include the new `Repair-WinGetPackage` cmdlet. Created `Repair-WinGetPackage.md` to document the cmdlet, including synopsis, syntax, detailed description, examples, parameter descriptions, common parameters, input/output types, and related links. --- .../Microsoft.WinGet.Client.md | 5 +- .../Repair-WinGetPackage.md | 303 ++++++++++++++++++ 2 files changed, 307 insertions(+), 1 deletion(-) create mode 100644 src/PowerShell/Help/Microsoft.WinGet.Client/Repair-WinGetPackage.md diff --git a/src/PowerShell/Help/Microsoft.WinGet.Client/Microsoft.WinGet.Client.md b/src/PowerShell/Help/Microsoft.WinGet.Client/Microsoft.WinGet.Client.md index 44689febb5..dadbca712e 100644 --- a/src/PowerShell/Help/Microsoft.WinGet.Client/Microsoft.WinGet.Client.md +++ b/src/PowerShell/Help/Microsoft.WinGet.Client/Microsoft.WinGet.Client.md @@ -1,4 +1,4 @@ ---- +--- Module Name: Microsoft.WinGet.Client ms.date: 08/01/2024 Module Guid: e11157e2-cd24-4250-83b8-c6654ea4926a @@ -52,6 +52,9 @@ Install a WinGet Package. ### [Remove-WinGetSource](Remove-WinGetSource.md) Removes a configured source. +### [Repair-WinGetPackage](Repair-WinGetPackage.md) +Repairs a WinGet Package. + ### [Repair-WinGetPackageManager](Repair-WinGetPackageManager.md) Repairs the WinGet client. diff --git a/src/PowerShell/Help/Microsoft.WinGet.Client/Repair-WinGetPackage.md b/src/PowerShell/Help/Microsoft.WinGet.Client/Repair-WinGetPackage.md new file mode 100644 index 0000000000..e84f07a709 --- /dev/null +++ b/src/PowerShell/Help/Microsoft.WinGet.Client/Repair-WinGetPackage.md @@ -0,0 +1,303 @@ +--- +external help file: Microsoft.WinGet.Client.Cmdlets.dll-Help.xml +Module Name: Microsoft.WinGet.Client +ms.date: 08/26/2024 +online version: +schema: 2.0.0 +--- + +# Repair-WinGetPackage + +## SYNOPSIS +Repairs a WinGet Package. + +## SYNTAX + +### FoundSet (Default) +``` +Repair-WinGetPackage [-Mode ] [-Log ] [-Version ] [-Id ] + [-Name ] [-Moniker ] [-Source ] [[-Query] ] + [-MatchOption ] [-ProgressAction ] [-WhatIf] [-Confirm] + [] +``` + +### GivenSet +``` +Repair-WinGetPackage [-Mode ] [-Log ] [[-PSCatalogPackage] ] + [-Version ] [-ProgressAction ] [-WhatIf] [-Confirm] [] +``` + +## DESCRIPTION +This command repairs a WinGet package from your computer, provided the package includes repair support. +The command includes parameters to specify values used to search for installed packages. By default, +all string-based searches are case-insensitive substring searches. Wildcards are not supported. +You can change the search behavior using the **MatchOption** parameter. + +> **Note: Not all packages support repair.** + +## EXAMPLES + +### Example 1: Repair a package using a query +```powershell +Repair-WinGetPackage -Query "Microsoft.GDK.2406" +``` +This example shows how to repair a package using a query. The **Query** parameter is positional, so you +don't need to include the parameter name before the query string. + +### Example 3 : Repair a package by Id +```powershell +Repair-WinGetPackage -Id "Microsoft.GDK.2406" +``` +This example shows how to repair a package by specifying the package identifier. + +If the package identifier is available from more than one source, you must provide additional search +criteria to select a specific instance of the package. + +### Example 3: Repair a package using by Name +```powershell +Repair-WinGetPackage -Name "Microsoft Game Development Kit - 240602 (June 2024 Update 2)" +``` +This example shows how to repair a package using the package name. + +> **Note: Please note that the examples mentioned above are mainly reference examples for the repair cmdlet and may not be operational as is, since many installers don't support repair as a standard functionality. For the Microsoft.GDK.2406 example, the assumption is that Microsoft.GDK.2406 supports repair capability and the author of the installer has provided the necessary repair context/switches in the Package Manifest in the Package Source referenced by the WinGet Client.** + +## PARAMETERS + +### -Id + +Specify the package identifier to search for. By default, the command does a case-insensitive +substring match. + +```yaml +Type: System.String +Parameter Sets: FoundSet +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### -Log + +Specify the location for the installer repair log. The value can be a fully-qualified or relative path and must include the file name. For example: `$env:TEMP\package.log`. + +> **Note: Not all installers support this option.** + +```yaml +Type: System.String +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -MatchOption + +Specify the match option for a WinGet package query. This parameter accepts the following values: + +```yaml +Type: Microsoft.WinGet.Client.PSObjects.PSPackageFieldMatchOption +Parameter Sets: FoundSet +Aliases: +Accepted values: Equals, EqualsCaseInsensitive, StartsWithCaseInsensitive, ContainsCaseInsensitive + +Required: False +Position: Named +Default value: None +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### -Mode + +Specify the output mode for the installer. The parameter accepts the following values: + +```yaml +Type: Microsoft.WinGet.Client.PSObjects.PSPackageRepairMode +Parameter Sets: (All) +Aliases: +Accepted values: Default, Silent, Interactive + +Required: False +Position: Named +Default value: None +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### -Moniker + +Specify the moniker of the WinGet package to repair. For example, the moniker for the +Microsoft.PowerShell package is `pwsh`. + +```yaml +Type: System.String +Parameter Sets: FoundSet +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### -Name + +Specify the name of the WinGet package name. The name contains space, you must enclose the name in +quotes. + +```yaml +Type: System.String +Parameter Sets: FoundSet +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### -PSCatalogPackage +Provide **PSCatalogPackage** object. You can get a **PSCatalogPackage** object by using the +`Find-WinGetPackage` or `Get-WingetPackage` commands. + + +```yaml +Type: Microsoft.WinGet.Client.Engine.PSObjects.PSCatalogPackage +Parameter Sets: GivenSet +Aliases: InputObject + +Required: False +Position: 0 +Default value: None +Accept pipeline input: True (ByPropertyName, ByValue) +Accept wildcard characters: False +``` + +### -Query + +Specify one or more strings to search for. By default, the command searches all configured sources. +Wildcards are not supported. The command compares the value provided to the following package +manifest properties: + + - `PackageIdentifier` + - `PackageName` + - `Moniker` + - `Tags` + +The command does a case-insensitive substring comparison of these properties. + +```yaml +Type: System.String[] +Parameter Sets: FoundSet +Aliases: + +Required: False +Position: 0 +Default value: None +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### -Source + +Specify the name of a configured WinGet source. + +```yaml +Type: System.String +Parameter Sets: FoundSet +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### -Version + +Specify the version of the package to be repaired. + +```yaml +Type: System.String +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### -Confirm + +Prompts you for confirmation before running the cmdlet. + +```yaml +Type: SSystem.Management.Automation.witchParameter +Parameter Sets: (All) +Aliases: cf + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + + +### -WhatIf +Shows what would happen if the cmdlet runs. +The cmdlet is not run. + +```yaml +Type: System.Management.Automation.SwitchParameter +Parameter Sets: (All) +Aliases: wi + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### CommonParameters +This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). + +## INPUTS + +### Microsoft.WinGet.Client.PSObjects.PSPackageRepairMode + +### Microsoft.WinGet.Client.Engine.PSObjects.PSCatalogPackage + +### System.String + +### System.String[] + +### Microsoft.WinGet.Client.PSObjects.PSPackageFieldMatchOption + +## OUTPUTS + +### Microsoft.WinGet.Client.Engine.PSObjects.PSRepairResult + +## NOTES + +## RELATED LINKS + +[Find-WinGetPackage](Find-WinGetPackage.md) + +[Install-WinGetPackage](Install-WinGetPackage.md) + +[Uninstall-WinGetPackage](Uninstall-WinGetPackage.md) From d8553a477f3351e0b595a460111984769f451463 Mon Sep 17 00:00:00 2001 From: Madhusudhan Gumbalapura Sudarshan Date: Mon, 26 Aug 2024 20:02:29 -0700 Subject: [PATCH 25/35] Fix typo in -Confirm parameter type in Repair-WinGetPackage.md & Spell check error fix. Corrected the type definition of the -Confirm parameter from 'SSystem.Management.Automation.witchParameter' to 'System.Management.Automation.SwitchParameter' in the Repair-WinGetPackage.md file. --- .github/actions/spelling/allow.txt | 1 + .../Help/Microsoft.WinGet.Client/Repair-WinGetPackage.md | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/actions/spelling/allow.txt b/.github/actions/spelling/allow.txt index 809babdc53..c4b8f171cb 100644 --- a/.github/actions/spelling/allow.txt +++ b/.github/actions/spelling/allow.txt @@ -112,6 +112,7 @@ florelis FLUSHEACHLINE forcerestart gdi +GDK HCCE hcertstore HCRYPTMSG diff --git a/src/PowerShell/Help/Microsoft.WinGet.Client/Repair-WinGetPackage.md b/src/PowerShell/Help/Microsoft.WinGet.Client/Repair-WinGetPackage.md index e84f07a709..2b62027f9d 100644 --- a/src/PowerShell/Help/Microsoft.WinGet.Client/Repair-WinGetPackage.md +++ b/src/PowerShell/Help/Microsoft.WinGet.Client/Repair-WinGetPackage.md @@ -245,7 +245,7 @@ Accept wildcard characters: False Prompts you for confirmation before running the cmdlet. ```yaml -Type: SSystem.Management.Automation.witchParameter +Type: System.Management.Automation.SwitchParameter Parameter Sets: (All) Aliases: cf From 55efd2cbb6f6b65ee0ac812d6bfe0b3af7b439f5 Mon Sep 17 00:00:00 2001 From: Madhusudhan Gumbalapura Sudarshan Date: Wed, 4 Sep 2024 13:13:05 -0700 Subject: [PATCH 26/35] Add missing interfaces for RepairOptions and RepairResult collections Added new interfaces in the `Microsoft.Management.Deployment` namespace within the `PackageManager.idl` file. Added interfaces for handling collections of `RepairOptions` and `RepairResult`: - `Windows.Foundation.Collections.IVector` - `Windows.Foundation.Collections.IVectorView` - `Windows.Foundation.Collections.IVector` - `Windows.Foundation.Collections.IVectorView` These changes enhance the functionality related to repair operations in the package management system by allowing manipulation and viewing of these collections. --- src/Microsoft.Management.Deployment/PackageManager.idl | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Microsoft.Management.Deployment/PackageManager.idl b/src/Microsoft.Management.Deployment/PackageManager.idl index 33d1843904..bc870d750b 100644 --- a/src/Microsoft.Management.Deployment/PackageManager.idl +++ b/src/Microsoft.Management.Deployment/PackageManager.idl @@ -1470,5 +1470,9 @@ namespace Microsoft.Management.Deployment interface Windows.Foundation.Collections.IVectorView; interface Windows.Foundation.Collections.IVector; interface Windows.Foundation.Collections.IVectorView; + interface Windows.Foundation.Collections.IVector; + interface Windows.Foundation.Collections.IVectorView; + interface Windows.Foundation.Collections.IVector; + interface Windows.Foundation.Collections.IVectorView; } } From 859cd0497a8893fa35fc7698dcd99ddef261a324 Mon Sep 17 00:00:00 2001 From: Madhusudhan Gumbalapura Sudarshan Date: Fri, 6 Sep 2024 15:06:46 -0700 Subject: [PATCH 27/35] Update `PackageManager.idl` comments and refactor powershell repair methods - Updated the comment in `PackageManager.idl` to clarify that `CorrelationData` is used for the repair process. - In `RepairPackageCommand.cs`, replaced the method call to `PackageManagerWrapper.Instance.RepairPackageAsync` with `this.RepairPackageAsync`. - Renamed `RepairResultAsync` to `RepairPackageAsync` for better clarity. --- src/Microsoft.Management.Deployment/PackageManager.idl | 2 +- .../Commands/RepairPackageCommand.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Microsoft.Management.Deployment/PackageManager.idl b/src/Microsoft.Management.Deployment/PackageManager.idl index bc870d750b..1f65f4a0ff 100644 --- a/src/Microsoft.Management.Deployment/PackageManager.idl +++ b/src/Microsoft.Management.Deployment/PackageManager.idl @@ -1179,7 +1179,7 @@ namespace Microsoft.Management.Deployment /// Optional parameter specifying Accept the package agreements required for download. Boolean AcceptPackageAgreements; - /// Used by a caller to correlate the download with a caller's data. + /// Used by a caller to correlate the repair with a caller's data. /// The string must be JSON encoded. String CorrelationData; diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/RepairPackageCommand.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/RepairPackageCommand.cs index cec9495c82..4d2af1f924 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/RepairPackageCommand.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/RepairPackageCommand.cs @@ -81,7 +81,7 @@ public void Repair( async (package, version) => { var repairOptions = this.GetRepairOptions(version, PSEnumHelpers.ToPackageRepairMode(psPackageRepairMode)); - return await PackageManagerWrapper.Instance.RepairPackageAsync(package, repairOptions); + return await this.RepairPackageAsync(package, repairOptions); })); if (result != null) @@ -111,7 +111,7 @@ private RepairOptions GetRepairOptions( return options; } - private async Task RepairResultAsync( + private async Task RepairPackageAsync( CatalogPackage catalogPackage, RepairOptions repairOptions) { From 84cadf0e976e997fd3f82b28b4c2d1caadf0fc69 Mon Sep 17 00:00:00 2001 From: Madhusudhan Gumbalapura Sudarshan Date: Fri, 6 Sep 2024 18:23:40 -0700 Subject: [PATCH 28/35] Update contract version to 12 in PackageManager.idl Revised the contract version from 11 to 12 for repair-specific implementations because version 11 conflicted with the previous addition but code wasn't in sync. --- .../PackageManager.idl | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Microsoft.Management.Deployment/PackageManager.idl b/src/Microsoft.Management.Deployment/PackageManager.idl index 1f14e11fae..1876db3b2d 100644 --- a/src/Microsoft.Management.Deployment/PackageManager.idl +++ b/src/Microsoft.Management.Deployment/PackageManager.idl @@ -2,7 +2,7 @@ // Licensed under the MIT License. namespace Microsoft.Management.Deployment { - [contractversion(11)] // For version 1.9 + [contractversion(12)] // For version 1.9 apicontract WindowsPackageManagerContract{}; /// State of the install @@ -152,7 +152,7 @@ namespace Microsoft.Management.Deployment } /// State of the repair - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 11)] + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 12)] enum PackageRepairProgressState { /// The repair is queued but not yet active. Cancellation of the IAsyncOperationWithProgress in this @@ -169,7 +169,7 @@ namespace Microsoft.Management.Deployment }; /// Progress object for the repair - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 11)] + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 12)] struct RepairProgress { /// State of the repair @@ -182,7 +182,7 @@ namespace Microsoft.Management.Deployment /// Status of the repair call /// Implementation Note: Errors mapped from AppInstallerErrors.h /// DESIGN NOTE: RepairResultStatus from AppInstallerErrors.h is not implemented in V1. - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 11)] + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 12)] enum RepairResultStatus { Ok, @@ -198,7 +198,7 @@ namespace Microsoft.Management.Deployment }; /// Result of the repair - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 11)] + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 12)] runtimeclass RepairResult { /// Used by a caller to correlate the repair with a caller's data. @@ -1144,7 +1144,7 @@ namespace Microsoft.Management.Deployment String CorrelationData; } - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 11)] + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 12)] enum PackageRepairMode { /// The default experience for the installer. Installer may show some UI. @@ -1156,7 +1156,7 @@ namespace Microsoft.Management.Deployment Interactive, }; - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 11)] + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 12)] enum PackageRepairScope { /// Use default repair behavior. @@ -1167,7 +1167,7 @@ namespace Microsoft.Management.Deployment System, }; - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 11)] + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 12)] runtimeclass RepairOptions { RepairOptions(); @@ -1379,7 +1379,7 @@ namespace Microsoft.Management.Deployment Windows.Foundation.IAsyncOperationWithProgress GetDownloadProgress(CatalogPackage package, PackageCatalogInfo catalogInfo); } - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 11)] + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 12)] { // Repair the specified package Windows.Foundation.IAsyncOperationWithProgress RepairPackageAsync(CatalogPackage package, RepairOptions options); From f9c3ecdff9b7695db4e234064e8fe2bff3abf876 Mon Sep 17 00:00:00 2001 From: Madhusudhan Gumbalapura Sudarshan Date: Mon, 9 Sep 2024 14:48:58 -0700 Subject: [PATCH 29/35] Revert "Update contract version to 12 in PackageManager.idl" This reverts commit 84cadf0e976e997fd3f82b28b4c2d1caadf0fc69. --- .../PackageManager.idl | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Microsoft.Management.Deployment/PackageManager.idl b/src/Microsoft.Management.Deployment/PackageManager.idl index 1876db3b2d..1f14e11fae 100644 --- a/src/Microsoft.Management.Deployment/PackageManager.idl +++ b/src/Microsoft.Management.Deployment/PackageManager.idl @@ -2,7 +2,7 @@ // Licensed under the MIT License. namespace Microsoft.Management.Deployment { - [contractversion(12)] // For version 1.9 + [contractversion(11)] // For version 1.9 apicontract WindowsPackageManagerContract{}; /// State of the install @@ -152,7 +152,7 @@ namespace Microsoft.Management.Deployment } /// State of the repair - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 12)] + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 11)] enum PackageRepairProgressState { /// The repair is queued but not yet active. Cancellation of the IAsyncOperationWithProgress in this @@ -169,7 +169,7 @@ namespace Microsoft.Management.Deployment }; /// Progress object for the repair - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 12)] + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 11)] struct RepairProgress { /// State of the repair @@ -182,7 +182,7 @@ namespace Microsoft.Management.Deployment /// Status of the repair call /// Implementation Note: Errors mapped from AppInstallerErrors.h /// DESIGN NOTE: RepairResultStatus from AppInstallerErrors.h is not implemented in V1. - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 12)] + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 11)] enum RepairResultStatus { Ok, @@ -198,7 +198,7 @@ namespace Microsoft.Management.Deployment }; /// Result of the repair - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 12)] + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 11)] runtimeclass RepairResult { /// Used by a caller to correlate the repair with a caller's data. @@ -1144,7 +1144,7 @@ namespace Microsoft.Management.Deployment String CorrelationData; } - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 12)] + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 11)] enum PackageRepairMode { /// The default experience for the installer. Installer may show some UI. @@ -1156,7 +1156,7 @@ namespace Microsoft.Management.Deployment Interactive, }; - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 12)] + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 11)] enum PackageRepairScope { /// Use default repair behavior. @@ -1167,7 +1167,7 @@ namespace Microsoft.Management.Deployment System, }; - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 12)] + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 11)] runtimeclass RepairOptions { RepairOptions(); @@ -1379,7 +1379,7 @@ namespace Microsoft.Management.Deployment Windows.Foundation.IAsyncOperationWithProgress GetDownloadProgress(CatalogPackage package, PackageCatalogInfo catalogInfo); } - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 12)] + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 11)] { // Repair the specified package Windows.Foundation.IAsyncOperationWithProgress RepairPackageAsync(CatalogPackage package, RepairOptions options); From 3d3b435da002db8aa1663109743d38c84d4b8471 Mon Sep 17 00:00:00 2001 From: Madhusudhan Gumbalapura Sudarshan Date: Fri, 13 Sep 2024 11:18:45 -0700 Subject: [PATCH 30/35] Remove GetRepairProgress method from PackageManager - The GetRepairProgress method has been removed from both the implementation file (PackageManager.cpp) and the interface definition file (PackageManager.idl). - This method was designed to return the progress of a repair operation for a given package and catalog information to allow another client to find asynchronously. However, we currently do not have a practical use case for this functionality. If this requirement arises in the future, we will consider adding it back. --- .../PackageManager.cpp | 35 ------------------- .../PackageManager.idl | 3 -- 2 files changed, 38 deletions(-) diff --git a/src/Microsoft.Management.Deployment/PackageManager.cpp b/src/Microsoft.Management.Deployment/PackageManager.cpp index 8ce35939aa..ac5dfdfe75 100644 --- a/src/Microsoft.Management.Deployment/PackageManager.cpp +++ b/src/Microsoft.Management.Deployment/PackageManager.cpp @@ -1181,40 +1181,5 @@ namespace winrt::Microsoft::Management::Deployment::implementation return GetPackageOperation( true /*canCancelQueueItem*/, nullptr /*queueItem*/, package, options, std::move(callerProcessInfoString)); } - - winrt::Windows::Foundation::IAsyncOperationWithProgress PackageManager::GetRepairProgress(winrt::Microsoft::Management::Deployment::CatalogPackage package, winrt::Microsoft::Management::Deployment::PackageCatalogInfo catalogInfo) - { - hstring correlationData; - WINGET_RETURN_REPAIR_RESULT_HR_IF(APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS, !package); - - HRESULT hr = S_OK; - std::shared_ptr queueItem = nullptr; - bool canCancelQueueItem = false; - try - { - // Check for permissions - // This must be done before any co_awaits since it requires info from the rpc caller thread. - auto [hrGetCallerId, callerProcessId] = GetCallerProcessId(); - WINGET_RETURN_REPAIR_RESULT_HR_IF_FAILED(hrGetCallerId); - canCancelQueueItem = SUCCEEDED(EnsureProcessHasCapability(Capability::PackageManagement, callerProcessId)); - if (!canCancelQueueItem) - { - WINGET_RETURN_REPAIR_RESULT_HR_IF_FAILED(EnsureProcessHasCapability(Capability::PackageQuery, callerProcessId)); - } - - // Get the queueItem synchronously. - queueItem = GetExistingQueueItemForPackage(package, catalogInfo); - if (queueItem == nullptr || - queueItem->GetPackageOperationType() != PackageOperationType::Repair) - { - return nullptr; - } - } - WINGET_CATCH_STORE(hr, APPINSTALLER_CLI_ERROR_COMMAND_FAILED); - WINGET_RETURN_REPAIR_RESULT_HR_IF_FAILED(hr); - - return GetPackageOperation( - canCancelQueueItem, std::move(queueItem)); - } CoCreatableMicrosoftManagementDeploymentClass(PackageManager); } diff --git a/src/Microsoft.Management.Deployment/PackageManager.idl b/src/Microsoft.Management.Deployment/PackageManager.idl index 1f14e11fae..01481a9a9f 100644 --- a/src/Microsoft.Management.Deployment/PackageManager.idl +++ b/src/Microsoft.Management.Deployment/PackageManager.idl @@ -1383,9 +1383,6 @@ namespace Microsoft.Management.Deployment { // Repair the specified package Windows.Foundation.IAsyncOperationWithProgress RepairPackageAsync(CatalogPackage package, RepairOptions options); - - // Get repair progress - Windows.Foundation.IAsyncOperationWithProgress GetRepairProgress(CatalogPackage package, PackageCatalogInfo catalogInfo); } } From e915dde02ea968050280d0f49d4de670eb1ed6ef Mon Sep 17 00:00:00 2001 From: Madhusudhan Gumbalapura Sudarshan Date: Sat, 28 Sep 2024 13:23:22 -0700 Subject: [PATCH 31/35] Add new repair options and rename error codes (API Review Feedback) Updated `Converters.h` to use `NoApplicableRepairer` instead of `NoApplicableInstallers`. Added handling for `AllowHashMismatch`, `BypassIsStoreClientBlockedPolicyCheck`, and `Force` in `PackageManager.cpp` and `PackageManager.idl`. - Renamed `NoApplicableInstallers` to `NoApplicableRepairer` and `RepairErrorCode` to `RepairerErrorCode` in `PackageManager.idl`. Added getter and setter methods for new options in `RepairOptions.cpp` and declarations in `RepairOptions.h`. Updated `RepairResult.cpp` and `RepairResult.h` to use `repairerErrorCode`. Enhanced `RepairPackageCmdlet.cs` and `RepairPackageCommand.cs` to handle new parameters. Updated `PSRepairResult.cs` to use `RepairerErrorCode`. --- .../Converters.h | 2 +- .../PackageManager.cpp | 15 ++++++++++ .../PackageManager.idl | 13 ++++++-- .../RepairOptions.cpp | 30 +++++++++++++++++++ .../RepairOptions.h | 10 +++++++ .../RepairResult.cpp | 8 ++--- .../RepairResult.h | 6 ++-- .../Cmdlets/RepairPackageCmdlet.cs | 14 +++++++++ .../Commands/RepairPackageCommand.cs | 19 ++++++++++++ .../PSObjects/PSRepairResult.cs | 2 +- 10 files changed, 108 insertions(+), 11 deletions(-) diff --git a/src/Microsoft.Management.Deployment/Converters.h b/src/Microsoft.Management.Deployment/Converters.h index e3b100c07a..cabdde797e 100644 --- a/src/Microsoft.Management.Deployment/Converters.h +++ b/src/Microsoft.Management.Deployment/Converters.h @@ -75,7 +75,7 @@ namespace winrt::Microsoft::Management::Deployment::implementation resultStatus = TStatus::InvalidOptions; break; case APPINSTALLER_CLI_ERROR_NO_APPLICABLE_INSTALLER: - WINGET_GET_OPERATION_RESULT_STATUS(NoApplicableInstallers, InternalError, NoApplicableInstallers, NoApplicableInstallers); + WINGET_GET_OPERATION_RESULT_STATUS(NoApplicableInstallers, InternalError, NoApplicableInstallers, NoApplicableRepairer); break; case APPINSTALLER_CLI_ERROR_UPDATE_NOT_APPLICABLE: case APPINSTALLER_CLI_ERROR_UPGRADE_VERSION_UNKNOWN: diff --git a/src/Microsoft.Management.Deployment/PackageManager.cpp b/src/Microsoft.Management.Deployment/PackageManager.cpp index ac5dfdfe75..4ab38ef5f0 100644 --- a/src/Microsoft.Management.Deployment/PackageManager.cpp +++ b/src/Microsoft.Management.Deployment/PackageManager.cpp @@ -595,6 +595,21 @@ namespace winrt::Microsoft::Management::Deployment::implementation context->Args.AddArg(Execution::Args::Type::AcceptPackageAgreements); } + if (options.AllowHashMismatch()) + { + context->Args.AddArg(Execution::Args::Type::HashOverride); + } + + if (options.BypassIsStoreClientBlockedPolicyCheck()) + { + context->SetFlags(Execution::ContextFlag::BypassIsStoreClientBlockedPolicyCheck); + } + + if (options.Force()) + { + context->Args.AddArg(Execution::Args::Type::Force); + } + auto repairScope = GetManifestRepairScope(options.PackageRepairScope()); if (repairScope != ::AppInstaller::Manifest::ScopeEnum::Unknown) { diff --git a/src/Microsoft.Management.Deployment/PackageManager.idl b/src/Microsoft.Management.Deployment/PackageManager.idl index 01481a9a9f..6fe1965922 100644 --- a/src/Microsoft.Management.Deployment/PackageManager.idl +++ b/src/Microsoft.Management.Deployment/PackageManager.idl @@ -193,7 +193,7 @@ namespace Microsoft.Management.Deployment InvalidOptions, RepairError, ManifestError, - NoApplicableInstallers, + NoApplicableRepairer, PackageAgreementsNotAccepted, }; @@ -215,7 +215,7 @@ namespace Microsoft.Management.Deployment /// The error code from the repair attempt. Only valid if the Status is RepairError. /// This value's meaning will require knowledge of the specific repairer or repair technology. - UInt32 RepairErrorCode { get; }; + UInt32 RepairerErrorCode { get; }; } /// State of the download @@ -1189,8 +1189,17 @@ namespace Microsoft.Management.Deployment /// The string must be JSON encoded. String CorrelationData; + /// Continues the download even if the hash in the catalog does not match the linked installer used for repair. + Boolean AllowHashMismatch; + /// Directs the logging to a log file. If provided, the installer must have write access to the file String LogOutputPath; + + /// Force the operation to continue upon non security related failures. + Boolean Force; + + /// Bypasses the Disabled Store Policy + Boolean BypassIsStoreClientBlockedPolicyCheck; } /// IMPLEMENTATION NOTE: Documentation from AppInstaller::Manifest::Documentation diff --git a/src/Microsoft.Management.Deployment/RepairOptions.cpp b/src/Microsoft.Management.Deployment/RepairOptions.cpp index 0ab6e4a42d..e321c69483 100644 --- a/src/Microsoft.Management.Deployment/RepairOptions.cpp +++ b/src/Microsoft.Management.Deployment/RepairOptions.cpp @@ -78,5 +78,35 @@ namespace winrt::Microsoft::Management::Deployment::implementation m_correlationData = value; } + bool RepairOptions::AllowHashMismatch() + { + return m_allowHashMismatch; + } + + void RepairOptions::AllowHashMismatch(bool value) + { + m_allowHashMismatch = value; + } + + bool RepairOptions::BypassIsStoreClientBlockedPolicyCheck() + { + return m_bypassIsStoreClientBlockedPolicyCheck; + } + + void RepairOptions::BypassIsStoreClientBlockedPolicyCheck(bool value) + { + m_bypassIsStoreClientBlockedPolicyCheck = value; + } + + bool RepairOptions::Force() + { + return m_force; + } + + void RepairOptions::Force(bool value) + { + m_force = value; + } + CoCreatableMicrosoftManagementDeploymentClass(RepairOptions); } diff --git a/src/Microsoft.Management.Deployment/RepairOptions.h b/src/Microsoft.Management.Deployment/RepairOptions.h index 79e4b2c38d..2794d1f231 100644 --- a/src/Microsoft.Management.Deployment/RepairOptions.h +++ b/src/Microsoft.Management.Deployment/RepairOptions.h @@ -23,11 +23,21 @@ namespace winrt::Microsoft::Management::Deployment::implementation void LogOutputPath(hstring const& value); hstring CorrelationData(); void CorrelationData(hstring const& value); + bool AllowHashMismatch(); + void AllowHashMismatch(bool value); + bool BypassIsStoreClientBlockedPolicyCheck(); + void BypassIsStoreClientBlockedPolicyCheck(bool value); + bool Force(); + void Force(bool value); + #if !defined(INCLUDE_ONLY_INTERFACE_METHODS) private: winrt::Microsoft::Management::Deployment::PackageVersionId m_packageVersionId{ nullptr }; bool m_acceptPackageAgreements = false; + bool m_allowHashMismatch = false; + bool m_bypassIsStoreClientBlockedPolicyCheck = false; + bool m_force = false; std::wstring m_logOutputPath = L""; std::wstring m_correlationData = L""; winrt::Microsoft::Management::Deployment::PackageRepairScope m_packageRepairScope = winrt::Microsoft::Management::Deployment::PackageRepairScope::Any; diff --git a/src/Microsoft.Management.Deployment/RepairResult.cpp b/src/Microsoft.Management.Deployment/RepairResult.cpp index 7edabfeb20..c36725528a 100644 --- a/src/Microsoft.Management.Deployment/RepairResult.cpp +++ b/src/Microsoft.Management.Deployment/RepairResult.cpp @@ -11,13 +11,13 @@ namespace winrt::Microsoft::Management::Deployment::implementation void RepairResult::Initialize( winrt::Microsoft::Management::Deployment::RepairResultStatus status, winrt::hresult extendedErrorCode, - uint32_t repairErrorCode, + uint32_t repairerErrorCode, hstring const& correlationData, bool rebootRequired) { m_status = status; m_extendedErrorCode = extendedErrorCode; - m_repairErrorCode = repairErrorCode; + m_repairerErrorCode = repairerErrorCode; m_correlationData = correlationData; m_rebootRequired = rebootRequired; } @@ -37,8 +37,8 @@ namespace winrt::Microsoft::Management::Deployment::implementation { return m_extendedErrorCode; } - uint32_t RepairResult::RepairErrorCode() + uint32_t RepairResult::RepairerErrorCode() { - return m_repairErrorCode; + return m_repairerErrorCode; } } diff --git a/src/Microsoft.Management.Deployment/RepairResult.h b/src/Microsoft.Management.Deployment/RepairResult.h index fd6e016e55..de41b23579 100644 --- a/src/Microsoft.Management.Deployment/RepairResult.h +++ b/src/Microsoft.Management.Deployment/RepairResult.h @@ -13,7 +13,7 @@ namespace winrt::Microsoft::Management::Deployment::implementation void Initialize( winrt::Microsoft::Management::Deployment::RepairResultStatus status, winrt::hresult extendedErrorCode, - uint32_t repairErrorCode, + uint32_t repairerErrorCode, hstring const& correlationData, bool rebootRequired); #endif @@ -22,7 +22,7 @@ namespace winrt::Microsoft::Management::Deployment::implementation bool RebootRequired(); winrt::Microsoft::Management::Deployment::RepairResultStatus Status(); winrt::hresult ExtendedErrorCode(); - uint32_t RepairErrorCode(); + uint32_t RepairerErrorCode(); #if !defined(INCLUDE_ONLY_INTERFACE_METHODS) private: @@ -30,7 +30,7 @@ namespace winrt::Microsoft::Management::Deployment::implementation bool m_rebootRequired = false; winrt::Microsoft::Management::Deployment::RepairResultStatus m_status = winrt::Microsoft::Management::Deployment::RepairResultStatus::Ok; winrt::hresult m_extendedErrorCode = S_OK; - uint32_t m_repairErrorCode = 0; + uint32_t m_repairerErrorCode = 0; #endif }; } diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/RepairPackageCmdlet.cs b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/RepairPackageCmdlet.cs index e1b1075d56..49e5bba479 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/RepairPackageCmdlet.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/RepairPackageCmdlet.cs @@ -38,6 +38,18 @@ public sealed class RepairPackageCmdlet : PackageCmdlet [Parameter] public string Log { get; set; } + /// + /// Gets or sets a value indicating whether to skip the installer hash validation check. + /// + [Parameter(ValueFromPipelineByPropertyName = true)] + public SwitchParameter AllowHashMismatch { get; set; } + + /// + /// Gets or sets a value indicating whether to continue upon non security related failures. + /// + [Parameter(ValueFromPipelineByPropertyName = true)] + public SwitchParameter Force { get; set; } + /// /// Repairs a package from the local system. /// @@ -45,6 +57,8 @@ protected override void ProcessRecord() { this.command = new RepairPackageCommand( this, + this.AllowHashMismatch.ToBool(), + this.Force.ToBool(), this.PSCatalogPackage, this.Version, this.Log, diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/RepairPackageCommand.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/RepairPackageCommand.cs index 4d2af1f924..5127c3abc6 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/RepairPackageCommand.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/RepairPackageCommand.cs @@ -25,6 +25,8 @@ public sealed class RepairPackageCommand : PackageCommand /// /// Caller cmdlet. /// PSCatalogPackage. + /// To skip the installer hash validation check. + /// To continue upon non security related failures. /// Version to repair. /// Logging file location. /// Package identifier. @@ -34,6 +36,8 @@ public sealed class RepairPackageCommand : PackageCommand /// Match against any field of a package. public RepairPackageCommand( PSCmdlet psCmdlet, + bool allowHashMismatch, + bool force, PSCatalogPackage psCatalogPackage, string version, string log, @@ -44,6 +48,9 @@ public RepairPackageCommand( string[] query) : base(psCmdlet) { + this.Force = force; + this.AllowHashMismatch = allowHashMismatch; + if (psCatalogPackage != null) { this.CatalogPackage = psCatalogPackage; @@ -65,6 +72,16 @@ public RepairPackageCommand( /// private string? Log { get; set; } + /// + /// Gets or sets a value indicating whether to continue upon non security related failures. + /// + private bool Force { get; set; } + + /// + /// Gets or sets a value indicating whether to skip the installer hash validation check. + /// + private bool AllowHashMismatch { get; set; } + /// /// Process repair package. /// @@ -95,6 +112,8 @@ private RepairOptions GetRepairOptions( PackageRepairMode repairMode) { var options = ManagementDeploymentFactory.Instance.CreateRepairOptions(); + options.AllowHashMismatch = this.AllowHashMismatch; + options.Force = this.Force; if (this.Log != null) { diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSRepairResult.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSRepairResult.cs index 973bf1587d..332e59bf11 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSRepairResult.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSRepairResult.cs @@ -101,7 +101,7 @@ public uint RepairErrorCode { get { - return this.repairResult.RepairErrorCode; + return this.repairResult.RepairerErrorCode; } } From d00a2aa12335ff960f118e3f89b8268ec7944707 Mon Sep 17 00:00:00 2001 From: Madhusudhan Gumbalapura Sudarshan Date: Tue, 1 Oct 2024 21:26:30 -0700 Subject: [PATCH 32/35] Update GUID and XML docs in ManagementDeploymentFactory - Updated `RepairOptionsIid` GUID in `ManagementDeploymentFactory` to align with WinRT MIDL-generated interface ID for `RepairOptions` resulted from API review feedback changes in last commit. This resolves COMException: Failed to create instance: -2147467262 when PowerShell tests invoke Repair-WinGetPackage cmdlet. - Corrected XML documentation for `CreateRepairOptions` method to accurately describe it creates an instance of `RepairOptions` class instead of `PackageMatchFilter` class. --- .../Helpers/ManagementDeploymentFactory.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/ManagementDeploymentFactory.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/ManagementDeploymentFactory.cs index b541b20363..c6e92189aa 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/ManagementDeploymentFactory.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/ManagementDeploymentFactory.cs @@ -67,7 +67,7 @@ internal sealed class ManagementDeploymentFactory private static readonly Guid UninstallOptionsIid = Guid.Parse("3EBC67F0-8339-594B-8A42-F90B69D02BBE"); private static readonly Guid PackageMatchFilterIid = Guid.Parse("D981ECA3-4DE5-5AD7-967A-698C7D60FC3B"); private static readonly Guid DownloadOptionsIid = Guid.Parse("94C92C4B-43F5-5CA3-BBBE-9F432C9546BC"); - private static readonly Guid RepairOptionsIid = Guid.Parse("828399EF-E0E0-5DA0-973B-B80468A04E9D"); + private static readonly Guid RepairOptionsIid = Guid.Parse("263F0546-2D7E-53A0-B8D1-75B74817FF18"); private static readonly IEnumerable ValidArchs = new Architecture[] { Architecture.X86, Architecture.X64 }; @@ -161,9 +161,9 @@ public PackageMatchFilter CreatePackageMatchFilter() } /// - /// Creates an instance of the class. + /// Creates an instance of the class. /// - /// A instance. + /// A instance. public RepairOptions CreateRepairOptions() { return Create(RepairOptionsType, RepairOptionsIid); From 4f87879a4c00149d718103769c3c480627d33e88 Mon Sep 17 00:00:00 2001 From: Madhusudhan Gumbalapura Sudarshan Date: Wed, 2 Oct 2024 15:43:33 -0700 Subject: [PATCH 33/35] Enhance error handling for Repair-WinGetPackage command-let - Introduced a new exception class `WinGetRepairPackageException` in `WinGetRepairPackageException.cs` to encapsulate various error scenarios during the repair operation. - Updated `RepairPackageCommand.cs` to throw `WinGetRepairPackageException` when specific error conditions are met during the repair process. - Added new error codes in `ErrorCode.cs` to represent different repair-related errors such as `NoRepairInfoFound`, `RepairNotApplicable`, `RepairerFailure`, `RepairNotSupported`, and `AdminContextRepairProhibited`. - Enhanced `Resources.Designer.cs` and `Resources.resx` with new localized strings to provide user-friendly error messages for the new error codes. - Modified the `RepairPackageCommand.cs` to handle and throw exceptions with detailed error messages based on the new error codes and localized strings. --- .../Commands/RepairPackageCommand.cs | 12 +++ .../Common/ErrorCode.cs | 25 ++++++ .../WinGetRepairPackageException.cs | 79 +++++++++++++++++++ .../Properties/Resources.Designer.cs | 54 +++++++++++++ .../Properties/Resources.resx | 18 +++++ 5 files changed, 188 insertions(+) create mode 100644 src/PowerShell/Microsoft.WinGet.Client.Engine/Exceptions/WinGetRepairPackageException.cs diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/RepairPackageCommand.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/RepairPackageCommand.cs index 5127c3abc6..150271ca69 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/RepairPackageCommand.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/RepairPackageCommand.cs @@ -11,6 +11,7 @@ namespace Microsoft.WinGet.Client.Engine.Commands using System.Threading.Tasks; using Microsoft.Management.Deployment; using Microsoft.WinGet.Client.Engine.Commands.Common; + using Microsoft.WinGet.Client.Engine.Exceptions; using Microsoft.WinGet.Client.Engine.Helpers; using Microsoft.WinGet.Client.Engine.PSObjects; using Microsoft.WinGet.Resources; @@ -103,6 +104,17 @@ public void Repair( if (result != null) { + if (result.Item1.Status == RepairResultStatus.RepairError + && result.Item1.ExtendedErrorCode != null) + { + if (result.Item1.ExtendedErrorCode.InnerException != null) + { + throw new WinGetRepairPackageException(result.Item1.ExtendedErrorCode.HResult, result.Item1.RepairerErrorCode, result.Item1.ExtendedErrorCode.InnerException); + } + + throw new WinGetRepairPackageException(result.Item1.ExtendedErrorCode.HResult, result.Item1.RepairerErrorCode); + } + this.Write(WinGet.Common.Command.StreamType.Object, new PSRepairResult(result.Item1, result.Item2)); } } diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Common/ErrorCode.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Common/ErrorCode.cs index ed9c83b98a..f5a927e49e 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Common/ErrorCode.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Common/ErrorCode.cs @@ -30,5 +30,30 @@ internal static class ErrorCode /// Error code for ERROR_PACKAGE_NOT_REGISTERED_FOR_USER. /// public const int PackageNotRegisteredForUser = unchecked((int)0x80073D35); + + /// + /// Error code for APPINSTALLER_CLI_ERROR_NO_REPAIR_INFO_FOUND. + /// + public const int NoRepairInfoFound = unchecked((int)0x8A150079); + + /// + /// Error code for APPINSTALLER_CLI_ERROR_REPAIR_NOT_APPLICABLE. + /// + public const int RepairNotApplicable = unchecked((int)0x8A15007A); + + /// + /// Error code for APPINSTALLER_CLI_ERROR_EXEC_REPAIR_FAILED . + /// + public const int RepairerFailure = unchecked((int)0x8A15007B); + + /// + /// Error code for APPINSTALLER_CLI_ERROR_REPAIR_NOT_SUPPORTED. + /// + public const int RepairNotSupported = unchecked((int)0x8A15007C); + + /// + /// Error code for APPINSTALLER_CLI_ERROR_ADMIN_CONTEXT_REPAIR_PROHIBITED. + /// + public const int AdminContextRepairProhibited = unchecked((int)0x8A15007D); } } diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Exceptions/WinGetRepairPackageException.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Exceptions/WinGetRepairPackageException.cs new file mode 100644 index 0000000000..76071d645c --- /dev/null +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Exceptions/WinGetRepairPackageException.cs @@ -0,0 +1,79 @@ +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Exceptions +{ + using System; + using System.Management.Automation; + using Microsoft.WinGet.Client.Engine.Common; + using Microsoft.WinGet.Resources; + + /// + /// WinGetRepairPackageException. + /// + [Serializable] + public class WinGetRepairPackageException : RuntimeException + { + /// + /// Initializes a new instance of the class. + /// + /// Repair operation ExtendedErrorCode Hresult. + public WinGetRepairPackageException(int hresult) + : base(GetMessage(hresult)) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Repair operation ExtendedErrorCode Hresult. + /// Repairer exit code. + public WinGetRepairPackageException(int hresult, uint repairerExitCode) + : base(GetMessage(hresult, repairerExitCode)) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Repair operation ExtendedErrorCode Hresult. + /// InnerException. + public WinGetRepairPackageException(int hresult, Exception innerException) + : base(GetMessage(hresult), innerException) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Repair operation ExtendedErrorCode Hresult. + /// Repairer exit code. + /// InnerException. + public WinGetRepairPackageException(int hresult, uint repairerExitCode, Exception innerException) + : base(GetMessage(hresult, repairerExitCode), innerException) + { + } + + private static string GetMessage(int hresult, uint repairerExitCode = 0) + { + switch (hresult) + { + case ErrorCode.NoRepairInfoFound: + return Resources.NoRepairInfoFound; + case ErrorCode.RepairerFailure: + return string.Format(Resources.RepairerFailure, repairerExitCode); + case ErrorCode.RepairNotSupported: + return Resources.RepairOperationNotSupported; + case ErrorCode.RepairNotApplicable: + return Resources.RepairDifferentInstallTechnology; + case ErrorCode.AdminContextRepairProhibited: + return Resources.NoAdminRepairForUserScopePackage; + default: + return string.Format(Resources.UnknownRepairFailure, hresult); + } + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Properties/Resources.Designer.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Properties/Resources.Designer.cs index ee99fb3090..f814bbc44d 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Properties/Resources.Designer.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Properties/Resources.Designer.cs @@ -213,6 +213,15 @@ internal static string InvalidVersionExceptionMessage { } } + /// + /// Looks up a localized string similar to Repair operations involving administrator privileges are not permitted on packages installed within the user scope.. + /// + internal static string NoAdminRepairForUserScopePackage { + get { + return ResourceManager.GetString("NoAdminRepairForUserScopePackage", resourceCulture); + } + } + /// /// Looks up a localized string similar to No packages matched the given input criteria.. /// @@ -222,6 +231,15 @@ internal static string NoPackageFoundExceptionMessage { } } + /// + /// Looks up a localized string similar to The repair command for this package is not available in the Package Manifest. Please reach out to the package publisher for assistance.. + /// + internal static string NoRepairInfoFound { + get { + return ResourceManager.GetString("NoRepairInfoFound", resourceCulture); + } + } + /// /// Looks up a localized string similar to Exporting '{0}'. /// @@ -303,6 +321,24 @@ internal static string RepairAppExecutionAliasMessage { } } + /// + /// Looks up a localized string similar to The installer technology in use does not match the version currently installed.. + /// + internal static string RepairDifferentInstallTechnology { + get { + return ResourceManager.GetString("RepairDifferentInstallTechnology", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The repair operation was unsuccessful, exiting with RepairerExit code: {0}.. + /// + internal static string RepairerFailure { + get { + return ResourceManager.GetString("RepairerFailure", resourceCulture); + } + } + /// /// Looks up a localized string similar to Failed to repair winget.. /// @@ -321,6 +357,15 @@ internal static string Repairing { } } + /// + /// Looks up a localized string similar to The current installer technology does not support repair. Please reach out to the package vendor for assistance.. + /// + internal static string RepairOperationNotSupported { + get { + return ResourceManager.GetString("RepairOperationNotSupported", resourceCulture); + } + } + /// /// Looks up a localized string similar to This cmdlet requires administrator privileges to execute.. /// @@ -348,6 +393,15 @@ internal static string Uninstalling { } } + /// + /// Looks up a localized string similar to An unexpected error happened while trying to repair the package. HResult:{0}. + /// + internal static string UnknownRepairFailure { + get { + return ResourceManager.GetString("UnknownRepairFailure", resourceCulture); + } + } + /// /// Looks up a localized string similar to User settings file is invalid.. /// diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Properties/Resources.resx b/src/PowerShell/Microsoft.WinGet.Client.Engine/Properties/Resources.resx index 297f3cac5f..05a78bb06b 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Properties/Resources.resx +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Properties/Resources.resx @@ -244,4 +244,22 @@ Repairing + + The repair command for this package is not available in the Package Manifest. Please reach out to the package publisher for assistance. + + + The installer technology in use does not match the version currently installed. + + + The repair operation was unsuccessful, exiting with RepairerExit code: {0}. + + + The current installer technology does not support repair. Please reach out to the package vendor for assistance. + + + Repair operations involving administrator privileges are not permitted on packages installed within the user scope. + + + An unexpected error happened while trying to repair the package. HResult:{0} + \ No newline at end of file From 309e40ea3cd14e0866e244ef07a2cab159c1d8ac Mon Sep 17 00:00:00 2001 From: Madhusudhan Gumbalapura Sudarshan Date: Fri, 4 Oct 2024 18:14:17 -0700 Subject: [PATCH 34/35] PR Feedback implementation: - Added inline comment for `COMRepairCommand` in `COMCommand.cpp`. - Updated inline comments in `ContextOrchestrator.cpp` and `ContextOrchestrator.h` to reflect repair commands specific changes. - Introduced `ModifyRepairInstaller` constant in `Constants.cs` and refactored related files for consistency. - Modified `Converters.h` to map error codes for repair operations. - Removed an unnecessary `Natvis` include from `Microsoft.Management.Deployment.vcxproj.filters`. - Corrected a comment in `PackageManager.cpp` about repair requirements. - Enhanced `RepairPackageCommand.cs` to search both local and remote catalogs. - Updated resource strings in `Resources.Designer.cs` and `Resources.resx` for clarity in error messages. --- .github/actions/spelling/allow.txt | 1 - .github/actions/spelling/expect.txt | 1 + src/AppInstallerCLICore/Commands/COMCommand.cpp | 1 + src/AppInstallerCLICore/ContextOrchestrator.cpp | 2 +- src/AppInstallerCLICore/ContextOrchestrator.h | 1 + src/AppInstallerCLIE2ETests/Constants.cs | 1 + .../Interop/GroupPolicyForInterop.cs | 9 +++------ src/AppInstallerCLIE2ETests/Interop/RepairInterop.cs | 2 +- src/Microsoft.Management.Deployment/Converters.h | 2 +- .../Microsoft.Management.Deployment.vcxproj.filters | 3 --- src/Microsoft.Management.Deployment/PackageManager.cpp | 2 +- .../Commands/RepairPackageCommand.cs | 2 +- .../Properties/Resources.Designer.cs | 4 ++-- .../Properties/Resources.resx | 10 ++++++---- 14 files changed, 20 insertions(+), 21 deletions(-) diff --git a/.github/actions/spelling/allow.txt b/.github/actions/spelling/allow.txt index c4b8f171cb..809babdc53 100644 --- a/.github/actions/spelling/allow.txt +++ b/.github/actions/spelling/allow.txt @@ -112,7 +112,6 @@ florelis FLUSHEACHLINE forcerestart gdi -GDK HCCE hcertstore HCRYPTMSG diff --git a/.github/actions/spelling/expect.txt b/.github/actions/spelling/expect.txt index 1ecef0b368..3da65567e8 100644 --- a/.github/actions/spelling/expect.txt +++ b/.github/actions/spelling/expect.txt @@ -185,6 +185,7 @@ fundraiser fuzzer fzanollo gcpi +GDK GES GESMBH getwinget diff --git a/src/AppInstallerCLICore/Commands/COMCommand.cpp b/src/AppInstallerCLICore/Commands/COMCommand.cpp index c82a576acd..4af70ddc13 100644 --- a/src/AppInstallerCLICore/Commands/COMCommand.cpp +++ b/src/AppInstallerCLICore/Commands/COMCommand.cpp @@ -47,6 +47,7 @@ namespace AppInstaller::CLI Workflow::UninstallSinglePackage; } + // IMPORTANT: To use this command, the caller should have already retrieved the InstalledPackageVersion and added it to the Context Data void COMRepairCommand::ExecuteInternal(Execution::Context& context) const { context << diff --git a/src/AppInstallerCLICore/ContextOrchestrator.cpp b/src/AppInstallerCLICore/ContextOrchestrator.cpp index 41e35af7ab..2d1783c566 100644 --- a/src/AppInstallerCLICore/ContextOrchestrator.cpp +++ b/src/AppInstallerCLICore/ContextOrchestrator.cpp @@ -12,7 +12,7 @@ namespace AppInstaller::CLI::Execution { namespace { - // Operation command queue used by install and uninstall commands. + // Operation command queue used by install, uninstall and repair commands. constexpr static std::string_view OperationCommandQueueName = "operation"sv; // Callback function used by worker threads in the queue. diff --git a/src/AppInstallerCLICore/ContextOrchestrator.h b/src/AppInstallerCLICore/ContextOrchestrator.h index 5b6c17e977..2faed47e30 100644 --- a/src/AppInstallerCLICore/ContextOrchestrator.h +++ b/src/AppInstallerCLICore/ContextOrchestrator.h @@ -102,6 +102,7 @@ namespace AppInstaller::CLI::Execution static std::unique_ptr CreateItemForSearch(std::wstring packageId, std::wstring sourceId, std::unique_ptr context); // Create queue item for download static std::unique_ptr CreateItemForDownload(std::wstring packageId, std::wstring sourceId, std::unique_ptr context); + // Create queue item for repair static std::unique_ptr CreateItemForRepair(std::wstring packageId, std::wstring sourceId, std::unique_ptr context); }; diff --git a/src/AppInstallerCLIE2ETests/Constants.cs b/src/AppInstallerCLIE2ETests/Constants.cs index da1510292c..09ceb04d0f 100644 --- a/src/AppInstallerCLIE2ETests/Constants.cs +++ b/src/AppInstallerCLIE2ETests/Constants.cs @@ -60,6 +60,7 @@ public class Constants public const string MsiInstallerV2FileName = "AppInstallerTestMsiInstallerV2.msi"; public const string MsixInstallerFileName = "AppInstallerTestMsixInstaller.msix"; public const string ZipInstallerFileName = "AppInstallerTestZipInstaller.zip"; + public const string ModifyRepairInstaller = "AppInstallerTest.TestModifyRepair"; public const string IndexPackage = "source.msix"; public const string MakeAppx = "makeappx.exe"; public const string SignTool = "signtool.exe"; diff --git a/src/AppInstallerCLIE2ETests/Interop/GroupPolicyForInterop.cs b/src/AppInstallerCLIE2ETests/Interop/GroupPolicyForInterop.cs index e7a3bfff58..6b2e5ad826 100644 --- a/src/AppInstallerCLIE2ETests/Interop/GroupPolicyForInterop.cs +++ b/src/AppInstallerCLIE2ETests/Interop/GroupPolicyForInterop.cs @@ -120,14 +120,11 @@ public async Task DisableWinGetCommandLineInterfacesPolicy() options.CompositeSearchBehavior = CompositeSearchBehavior.AllCatalogs; PackageCatalogReference compositeSource = packageManager.CreateCompositePackageCatalog(options); - string testPackageId = "AppInstallerTest.TestModifyRepair"; - // Find package - var searchResult = this.FindOnePackage(compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, testPackageId); + var searchResult = this.FindOnePackage(compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, Constants.ModifyRepairInstaller); // Configure installation var installOptions = this.TestFactory.CreateInstallOptions(); - installOptions.PackageInstallMode = PackageInstallMode.Silent; installOptions.AcceptPackageAgreements = true; installOptions.ReplacementInstallerArguments = $"/InstallDir {installDir} /Version 2.0.0.0 /DisplayName TestModifyRepair /UseHKLM"; @@ -136,7 +133,7 @@ public async Task DisableWinGetCommandLineInterfacesPolicy() Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); // Find package again, but this time it should detect the installed version - searchResult = this.FindOnePackage(compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, testPackageId); + searchResult = this.FindOnePackage(compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, Constants.ModifyRepairInstaller); Assert.NotNull(searchResult.CatalogPackage.InstalledVersion); // Repair @@ -160,7 +157,7 @@ public async Task DisableWinGetCommandLineInterfacesPolicy() var downloadResult = await packageManager.DownloadPackageAsync(searchResult.CatalogPackage, this.TestFactory.CreateDownloadOptions()); Assert.AreEqual(DownloadResultStatus.Ok, downloadResult.Status); var packageVersion = "2.0.0.0"; - string downloadDir = Path.Combine(TestCommon.GetDefaultDownloadDirectory(), $"{testPackageId}_{packageVersion}"); + string downloadDir = Path.Combine(TestCommon.GetDefaultDownloadDirectory(), $"{Constants.ModifyRepairInstaller}_{packageVersion}"); Assert.True(TestCommon.VerifyInstallerDownload(downloadDir, "TestModifyRepair", packageVersion, ProcessorArchitecture.X86, TestCommon.Scope.Unknown, PackageInstallerType.Burn, "en-US")); } } diff --git a/src/AppInstallerCLIE2ETests/Interop/RepairInterop.cs b/src/AppInstallerCLIE2ETests/Interop/RepairInterop.cs index bb97ea768f..7fc265dc18 100644 --- a/src/AppInstallerCLIE2ETests/Interop/RepairInterop.cs +++ b/src/AppInstallerCLIE2ETests/Interop/RepairInterop.cs @@ -136,7 +136,7 @@ public async Task RepairNonStoreMsixPackageWithMachineScope() public async Task RepairBurnInstallerWithModifyBehavior() { var replaceInstallerArguments = this.GetReplacementArguments(this.installDir, "2.0.0.0", "TestModifyRepair", useHKLM: true); - var searchResult = await this.FindAndInstallPackage("AppInstallerTest.TestModifyRepair", this.installDir, replaceInstallerArguments.ToString()); + var searchResult = await this.FindAndInstallPackage(Constants.ModifyRepairInstaller, this.installDir, replaceInstallerArguments.ToString()); // Repair the package var repairOptions = this.TestFactory.CreateRepairOptions(); diff --git a/src/Microsoft.Management.Deployment/Converters.h b/src/Microsoft.Management.Deployment/Converters.h index cabdde797e..fa0993e3ae 100644 --- a/src/Microsoft.Management.Deployment/Converters.h +++ b/src/Microsoft.Management.Deployment/Converters.h @@ -91,7 +91,7 @@ namespace winrt::Microsoft::Management::Deployment::implementation case APPINSTALLER_CLI_ERROR_EXEC_REPAIR_FAILED: case APPINSTALLER_CLI_ERROR_REPAIR_NOT_SUPPORTED: case APPINSTALLER_CLI_ERROR_ADMIN_CONTEXT_REPAIR_PROHIBITED: - WINGET_GET_OPERATION_RESULT_STATUS(InstallError, UninstallError, InternalError, RepairError); + WINGET_GET_OPERATION_RESULT_STATUS(InternalError, InternalError, InternalError, RepairError); break; case APPINSTALLER_CLI_ERROR_PACKAGE_AGREEMENTS_NOT_ACCEPTED: WINGET_GET_OPERATION_RESULT_STATUS(PackageAgreementsNotAccepted, InternalError, PackageAgreementsNotAccepted, PackageAgreementsNotAccepted); diff --git a/src/Microsoft.Management.Deployment/Microsoft.Management.Deployment.vcxproj.filters b/src/Microsoft.Management.Deployment/Microsoft.Management.Deployment.vcxproj.filters index 3a3f48aa98..128b48aeff 100644 --- a/src/Microsoft.Management.Deployment/Microsoft.Management.Deployment.vcxproj.filters +++ b/src/Microsoft.Management.Deployment/Microsoft.Management.Deployment.vcxproj.filters @@ -99,7 +99,4 @@ {9c3907ed-84d9-4485-9b15-04c50717f0ab} - - - \ No newline at end of file diff --git a/src/Microsoft.Management.Deployment/PackageManager.cpp b/src/Microsoft.Management.Deployment/PackageManager.cpp index 4ab38ef5f0..7981505348 100644 --- a/src/Microsoft.Management.Deployment/PackageManager.cpp +++ b/src/Microsoft.Management.Deployment/PackageManager.cpp @@ -1176,7 +1176,7 @@ namespace winrt::Microsoft::Management::Deployment::implementation // options and catalog can both be null, package must be set. WINGET_RETURN_REPAIR_RESULT_HR_IF(APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS, !package); - // the package should have an installed version to be Repaired. + // the package should have an installed version to be repaired. WINGET_RETURN_REPAIR_RESULT_HR_IF(APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS, !package.InstalledVersion()); HRESULT hr = S_OK; diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/RepairPackageCommand.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/RepairPackageCommand.cs index 150271ca69..133c4df86b 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/RepairPackageCommand.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/RepairPackageCommand.cs @@ -94,7 +94,7 @@ public void Repair( { var result = this.Execute( async () => await this.GetPackageAndExecuteAsync( - CompositeSearchBehavior.LocalCatalogs, + CompositeSearchBehavior.LocalCatalogs | CompositeSearchBehavior.RemotePackagesFromRemoteCatalogs, PSEnumHelpers.ToPackageFieldMatchOption(psPackageFieldMatchOption), async (package, version) => { diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Properties/Resources.Designer.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Properties/Resources.Designer.cs index f814bbc44d..060cb7b614 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Properties/Resources.Designer.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Properties/Resources.Designer.cs @@ -331,7 +331,7 @@ internal static string RepairDifferentInstallTechnology { } /// - /// Looks up a localized string similar to The repair operation was unsuccessful, exiting with RepairerExit code: {0}.. + /// Looks up a localized string similar to The repair operation was unsuccessful, exiting with Repairer error code: {0}.. /// internal static string RepairerFailure { get { @@ -394,7 +394,7 @@ internal static string Uninstalling { } /// - /// Looks up a localized string similar to An unexpected error happened while trying to repair the package. HResult:{0}. + /// Looks up a localized string similar to An unexpected error happened while trying to repair the package. Error code:{0}. /// internal static string UnknownRepairFailure { get { diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Properties/Resources.resx b/src/PowerShell/Microsoft.WinGet.Client.Engine/Properties/Resources.resx index 05a78bb06b..174ff2c4ab 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Properties/Resources.resx +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Properties/Resources.resx @@ -1,4 +1,4 @@ - +