commit 96974fbc383811617bfcc957d9d4b32126edb26f parent 7b28fb42cb7a15d5f9145155ea73c410c7146599 Author: Madhusudhan-MSFT <53235553+Madhusudhan-MSFT@users.noreply.github.com> Date: Sat, 17 Feb 2024 22:25:24 -0800 winget repair cli implementation (#4168) Winget Repair CLI implementation. The changes include: - The initial support for the winget repair feature for the following installer types: - Burn, Exe, Inno - these require a repair behavior and a repair switch to perform the repair operation. - If the repair behavior is Modify, the repair switch will apply the ModifyPath command from the ARP registry flag. - If the repair behavior is Installer, the repair switch will apply to the matching downloaded installer from the search results. - If the repair behavior is Uninstaller, the repair switch will apply the UninstallString command from the ARP registry flag. - MSI/WIX - for these, msiexec /f will be used to perform the default repair supported by the platform. - MSStore - this will call the StartProductInstallAsync API with the repair flag set. - MSIX - this will call the RegisterPackage API. - Portable installation is not supported yet. **[How validated:]** **[Manual Tests:]** - Compile the code changes. - Deploy AppInstallerCLIPackage. **[Local Manifest Tests:]** - Execute the following local manifest scenarios: - wingetdev repair --manifest E:\Winget\WinGetRepair\Manifests\GDK_RB_Uninstall. - The manifest uses v 1.7.0 and points to the latest GDK installer where Repair Behavior = "Uninstaller" and Repair = "/repair". - wingetdev repair --manifest E:\Winget\WinGetRepair\Manifests\GDK_RB_Installer. - The manifest uses v 1.7.0 and points to the latest GDK installer where Repair Behavior = "Installer" and Repair = "/repair". - This is a little tricky because the latest GDK installer is not available on the winget-pkgs repo. It is necessary to replace the version downloaded zip file with the latest version as a zip file to test this scenario. - wingetdev repair --manifest E:\Winget\WinGetRepair\Manifests\GDK_RB_Modify. - The manifest uses v 1.7.0 and points to the latest GDK installer where Repair Behavior = "Modify" and Repair = "/repair". **[MSStore App Repair Test]** - wingetdev repair --id 9NBDXK71NK08. - This is to validate the MSStore repair scenario. **[MSI]** - wingetdev repair --manifest E:\Investigations\Winget\WinGetRepair\Manifests\TestMSI - This is to validate MSI package [TODOs:] - Add unit tests and E2E tests - [x] I have signed the [Contributor License Agreement](https://cla.opensource.microsoft.com/microsoft/winget-pkgs). - [x] This pull request is related to an issue. ----- ###### Microsoft Reviewers: [Open in CodeFlow](https://microsoft.github.io/open-pr/?codeflow=https://github.com/microsoft/winget-cli/pull/4168) --------- Co-authored-by: Yao Sun <yaosun@microsoft.com> Diffstat:
36 files changed, 1206 insertions(+), 16 deletions(-)
diff --git a/.github/actions/spelling/expect.txt b/.github/actions/spelling/expect.txt @@ -310,6 +310,7 @@ nuffing objbase objidl ofile +omus onefuzz ools oop diff --git a/doc/Settings.md b/doc/Settings.md @@ -311,4 +311,4 @@ You can enable the feature as shown below. "experimentalFeatures": { "configuration03": true }, -```- \ No newline at end of file +``` diff --git a/doc/windows/package-manager/winget/returnCodes.md b/doc/windows/package-manager/winget/returnCodes.md @@ -132,6 +132,11 @@ ms.localizationpriority: medium | 0x8A150076 | -1978335114 | APPINSTALLER_CLI_ERROR_AUTHENTICATION_INTERACTIVE_REQUIRED | Authentication failed. Interactive authentication required. | | 0x8A150077 | -1978335113 | APPINSTALLER_CLI_ERROR_AUTHENTICATION_CANCELLED_BY_USER | Authentication failed. User cancelled. | | 0x8A150078 | -1978335112 | APPINSTALLER_CLI_ERROR_AUTHENTICATION_INCORRECT_ACCOUNT | Authentication failed. Authenticated account is not the desired account. | +| 0x8A150079 | -1978335111 | APPINSTALLER_CLI_ERROR_NO_REPAIR_INFO_FOUND | Repair command not found. | +| 0x8A15007A | -1978335110 | APPINSTALLER_CLI_ERROR_REPAIR_NOT_APPLICABLE | Repair operation is not applicable. | +| 0x8A15007B | -1978335109 | APPINSTALLER_CLI_ERROR_EXEC_REPAIR_FAILED | Repair operation failed. | +| 0x8A15007C | -1978335108 | APPINSTALLER_CLI_ERROR_REPAIR_NOT_SUPPORTED | The installer technology in use doesn't support repair. | +| 0x8A15007D | -1978335107 | APPINSTALLER_CLI_ERROR_ADMIN_CONTEXT_REPAIR_PROHIBITED | Repair operations involving administrator privileges are not permitted on packages installed within the user scope. | ## Install errors. diff --git a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj @@ -368,6 +368,7 @@ <ClInclude Include="Commands\HashCommand.h" /> <ClInclude Include="Commands\ListCommand.h" /> <ClInclude Include="Commands\PinCommand.h" /> + <ClInclude Include="Commands\RepairCommand.h" /> <ClInclude Include="Commands\SearchCommand.h" /> <ClInclude Include="Commands\ShowCommand.h" /> <ClInclude Include="Commands\InstallCommand.h" /> @@ -413,6 +414,7 @@ <ClInclude Include="Workflows\PinFlow.h" /> <ClInclude Include="Workflows\PortableFlow.h" /> <ClInclude Include="Workflows\PromptFlow.h" /> + <ClInclude Include="Workflows\RepairFlow.h" /> <ClInclude Include="Workflows\SettingsFlow.h" /> <ClInclude Include="Workflows\ShellExecuteInstallerHandler.h" /> <ClInclude Include="Workflows\InstallFlow.h" /> @@ -437,6 +439,7 @@ <ClCompile Include="Commands\ErrorCommand.cpp" /> <ClCompile Include="Commands\ImportCommand.cpp" /> <ClCompile Include="Commands\PinCommand.cpp" /> + <ClCompile Include="Commands\RepairCommand.cpp" /> <ClCompile Include="Commands\TestCommand.cpp" /> <ClCompile Include="ConfigurationCommon.cpp" /> <ClCompile Include="ConfigurationContext.cpp" /> @@ -487,6 +490,7 @@ <ClCompile Include="Workflows\PinFlow.cpp" /> <ClCompile Include="Workflows\PortableFlow.cpp" /> <ClCompile Include="Workflows\PromptFlow.cpp" /> + <ClCompile Include="Workflows\RepairFlow.cpp" /> <ClCompile Include="Workflows\SettingsFlow.cpp" /> <ClCompile Include="Workflows\ShellExecuteInstallerHandler.cpp" /> <ClCompile Include="Workflows\InstallFlow.cpp" /> diff --git a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters @@ -242,6 +242,12 @@ <ClInclude Include="CheckpointManager.h"> <Filter>Header Files</Filter> </ClInclude> + <ClInclude Include="Commands\RepairCommand.h"> + <Filter>Commands</Filter> + </ClInclude> + <ClInclude Include="Workflows\RepairFlow.h"> + <Filter>Workflows</Filter> + </ClInclude> </ItemGroup> <ItemGroup> <ClCompile Include="pch.cpp"> @@ -450,6 +456,12 @@ </ClCompile> <ClCompile Include="Commands\ErrorCommand.cpp"> <Filter>Commands</Filter> + </ClCompile> + <ClCompile Include="Commands\RepairCommand.cpp"> + <Filter>Commands</Filter> + </ClCompile> + <ClCompile Include="Workflows\RepairFlow.cpp"> + <Filter>Workflows</Filter> </ClCompile> </ItemGroup> <ItemGroup> diff --git a/src/AppInstallerCLICore/Commands/RepairCommand.cpp b/src/AppInstallerCLICore/Commands/RepairCommand.cpp @@ -0,0 +1,121 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "RepairCommand.h" +#include "Workflows/RepairFlow.h" +#include "Workflows/CompletionFlow.h" +#include "Workflows/InstallFlow.h" + +namespace AppInstaller::CLI +{ + using namespace AppInstaller::CLI::Execution; + using namespace AppInstaller::CLI::Workflow; + + std::vector<Argument> RepairCommand::GetArguments() const + { + return { + Argument::ForType(Args::Type::Query), // -q + Argument::ForType(Args::Type::Manifest), // -m + Argument::ForType(Args::Type::Id), // -id + Argument::ForType(Args::Type::Name), // -n + Argument::ForType(Args::Type::Channel), + Argument::ForType(Args::Type::Moniker), // -mn + Argument::ForType(Args::Type::Version), // -v + Argument::ForType(Args::Type::ProductCode), + Argument::ForType(Args::Type::InstallArchitecture), // -arch + Argument{ Execution::Args::Type::InstallScope, Resource::String::InstalledScopeArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }, + Argument::ForType(Args::Type::Source), // -s + Argument::ForType(Args::Type::Interactive), // -i + Argument::ForType(Args::Type::Silent), // -h + Argument::ForType(Args::Type::Log), // -o + Argument::ForType(Args::Type::IgnoreLocalArchiveMalwareScan), // -ignore-local-archive-malware-scan + Argument::ForType(Args::Type::AcceptSourceAgreements), // -accept-source-agreements + Argument::ForType(Args::Type::AcceptPackageAgreements), + Argument::ForType(Args::Type::Locale), + Argument::ForType(Args::Type::CustomHeader), + Argument::ForType(Args::Type::AuthenticationMode), + Argument::ForType(Args::Type::AuthenticationAccount), + Argument::ForType(Args::Type::Force), + Argument::ForType(Args::Type::HashOverride), + Argument::ForType(Args::Type::Exact), + }; + } + + Resource::LocString RepairCommand::ShortDescription() const + { + return { Resource::String::RepairCommandShortDescription }; + } + + Resource::LocString RepairCommand::LongDescription() const + { + return { Resource::String::RepairCommandLongDescription }; + } + + void RepairCommand::Complete(Execution::Context& context, Execution::Args::Type valueType) const + { + if (valueType == Execution::Args::Type::Manifest || + valueType == Execution::Args::Type::Log) + { + // Intentionally output nothing to allow pass through to filesystem. + return; + } + + switch (valueType) + { + case Execution::Args::Type::Id: + case Execution::Args::Type::Name: + case Execution::Args::Type::Moniker: + case Execution::Args::Type::Version: + case Execution::Args::Type::Channel: + case Execution::Args::Type::Source: + context << + Workflow::CompleteWithSingleSemanticsForValueUsingExistingSource(valueType); + break; + } + } + + Utility::LocIndView RepairCommand::HelpLink() const + { + // TODO: point to the right place + return "https://aka.ms/winget-command-repair"_liv; + } + + void RepairCommand::ValidateArgumentsInternal(Execution::Args& execArgs) const + { + Argument::ValidateCommonArguments(execArgs); + } + + void RepairCommand::ExecuteInternal(Execution::Context& context) const + { + context.SetFlags(Execution::ContextFlag::InstallerExecutionUseRepair); + + context << + Workflow::ReportExecutionStage(ExecutionStage::Discovery) << + Workflow::OpenSource() << + Workflow::OpenCompositeSource(DetermineInstalledSource(context)); + + if (context.Args.Contains(Args::Type::Manifest)) + { + context << + Workflow::GetManifestFromArg << + Workflow::ReportManifestIdentity << + Workflow::SearchSourceUsingManifest << + Workflow::EnsureOneMatchFromSearchResult(OperationType::Repair) << + Workflow::GetInstalledPackageVersion << + Workflow::SelectInstaller << + Workflow::EnsureApplicableInstaller << + Workflow::RepairSinglePackage; + } + else + { + context << + Workflow::SearchSourceForSingle << + Workflow::HandleSearchResultFailures << + Workflow::EnsureOneMatchFromSearchResult(OperationType::Repair) << + Workflow::ReportPackageIdentity << + Workflow::GetInstalledPackageVersion << + Workflow::SelectApplicablePackageVersion << + Workflow::RepairSinglePackage; + } + } +} diff --git a/src/AppInstallerCLICore/Commands/RepairCommand.h b/src/AppInstallerCLICore/Commands/RepairCommand.h @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Command.h" + +namespace AppInstaller::CLI +{ + struct RepairCommand final : public Command + { + RepairCommand(std::string_view parent) : Command("repair", parent) {} + + std::vector<Argument> GetArguments() const override; + + Resource::LocString ShortDescription() const override; + Resource::LocString LongDescription() const override; + + void Complete(Execution::Context& context, Execution::Args::Type valueType) const override; + + Utility::LocIndView HelpLink() const override; + + protected: + void ValidateArgumentsInternal(Execution::Args& execArgs) const override; + void ExecuteInternal(Execution::Context& context) const override; + }; +} diff --git a/src/AppInstallerCLICore/Commands/RootCommand.cpp b/src/AppInstallerCLICore/Commands/RootCommand.cpp @@ -26,6 +26,7 @@ #include "DownloadCommand.h" #include "ErrorCommand.h" #include "ResumeCommand.h" +#include "RepairCommand.h" #include "Resources.h" #include "TableOutput.h" @@ -184,6 +185,7 @@ namespace AppInstaller::CLI std::make_unique<DownloadCommand>(FullName()), std::make_unique<ErrorCommand>(FullName()), std::make_unique<ResumeCommand>(FullName()), + std::make_unique<RepairCommand>(FullName()), #if _DEBUG std::make_unique<DebugCommand>(FullName()), #endif diff --git a/src/AppInstallerCLICore/ExecutionContext.h b/src/AppInstallerCLICore/ExecutionContext.h @@ -74,6 +74,7 @@ namespace AppInstaller::CLI::Execution Resume = 0x200, RebootRequired = 0x400, RegisterResume = 0x800, + InstallerExecutionUseRepair = 0x1000, }; DEFINE_ENUM_FLAG_OPERATORS(ContextFlag); diff --git a/src/AppInstallerCLICore/ExecutionContextData.h b/src/AppInstallerCLICore/ExecutionContextData.h @@ -63,6 +63,8 @@ namespace AppInstaller::CLI::Execution Pins, ConfigurationContext, DownloadDirectory, + ModifyPath, + RepairString, Max }; @@ -267,5 +269,17 @@ namespace AppInstaller::CLI::Execution { using value_t = std::filesystem::path; }; + + template<> + struct DataMapping<Data::ModifyPath> + { + using value_t = std::string; + }; + + template<> + struct DataMapping<Data::RepairString> + { + using value_t = std::string; + }; } } diff --git a/src/AppInstallerCLICore/Resources.h b/src/AppInstallerCLICore/Resources.h @@ -324,6 +324,7 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(MSStoreAppBlocked); WINGET_DEFINE_RESOURCE_STRINGID(MSStoreInstallOrUpdateFailed); WINGET_DEFINE_RESOURCE_STRINGID(MSStoreInstallTryGetEntitlement); + WINGET_DEFINE_RESOURCE_STRINGID(MSStoreRepairFailed); WINGET_DEFINE_RESOURCE_STRINGID(MSStoreStoreClientBlocked); WINGET_DEFINE_RESOURCE_STRINGID(MultipleExclusiveArgumentsProvided); WINGET_DEFINE_RESOURCE_STRINGID(MultipleInstalledPackagesFound); @@ -339,12 +340,14 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(NestedInstallerNotSpecified); WINGET_DEFINE_RESOURCE_STRINGID(NestedInstallerNotSupported); WINGET_DEFINE_RESOURCE_STRINGID(NoApplicableInstallers); + WINGET_DEFINE_RESOURCE_STRINGID(NoAdminRepairForUserScopePackage); WINGET_DEFINE_RESOURCE_STRINGID(NoExperimentalFeaturesMessage); WINGET_DEFINE_RESOURCE_STRINGID(NoInstalledPackageFound); WINGET_DEFINE_RESOURCE_STRINGID(NoPackageFound); WINGET_DEFINE_RESOURCE_STRINGID(NoPackageSelectionArgumentProvided); WINGET_DEFINE_RESOURCE_STRINGID(NoPackagesFoundInImportFile); WINGET_DEFINE_RESOURCE_STRINGID(Notes); + WINGET_DEFINE_RESOURCE_STRINGID(NoRepairInfoFound); WINGET_DEFINE_RESOURCE_STRINGID(NoUninstallInfoFound); WINGET_DEFINE_RESOURCE_STRINGID(NoUpgradeArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(NoVTArgumentDescription); @@ -419,6 +422,16 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(RebootRequiredToEnableWindowsFeatureOverrideRequired); WINGET_DEFINE_RESOURCE_STRINGID(RelatedLink); WINGET_DEFINE_RESOURCE_STRINGID(RenameArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(RepairAbandoned); + WINGET_DEFINE_RESOURCE_STRINGID(RepairCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(RepairCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(RepairDifferentInstallTechnology); + WINGET_DEFINE_RESOURCE_STRINGID(RepairFailedWithCode); + WINGET_DEFINE_RESOURCE_STRINGID(RepairFlowNoMatchingVersion); + WINGET_DEFINE_RESOURCE_STRINGID(RepairFlowRepairSuccess); + WINGET_DEFINE_RESOURCE_STRINGID(RepairFlowReturnCodeSystemNotSupported); + WINGET_DEFINE_RESOURCE_STRINGID(RepairFlowStartingPackageRepair); + WINGET_DEFINE_RESOURCE_STRINGID(RepairOperationNotSupported); WINGET_DEFINE_RESOURCE_STRINGID(ReparsePointsNotSupportedError); WINGET_DEFINE_RESOURCE_STRINGID(ReportIdentityForAgreements); WINGET_DEFINE_RESOURCE_STRINGID(ReportIdentityFound); diff --git a/src/AppInstallerCLICore/Workflows/InstallFlow.cpp b/src/AppInstallerCLICore/Workflows/InstallFlow.cpp @@ -464,6 +464,8 @@ namespace AppInstaller::CLI::Workflow void ReportInstallerResult::operator()(Execution::Context& context) const { + bool isRepair = WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::InstallerExecutionUseRepair); + DWORD installResult = context.Get<Execution::Data::OperationReturnCode>(); const auto& additionalSuccessCodes = context.Get<Execution::Data::Installer>()->InstallerSuccessCodes; if (installResult != 0 && (std::find(additionalSuccessCodes.begin(), additionalSuccessCodes.end(), installResult) == additionalSuccessCodes.end())) @@ -506,7 +508,15 @@ namespace AppInstaller::CLI::Workflow if (FAILED(terminationHR)) { const auto& manifest = context.Get<Execution::Data::Manifest>(); - Logging::Telemetry().LogInstallerFailure(manifest.Id, manifest.Version, manifest.Channel, m_installerType, installResult); + + if (isRepair) + { + Logging::Telemetry().LogRepairFailure(manifest.Id, manifest.Version, m_installerType, installResult); + } + else + { + Logging::Telemetry().LogInstallerFailure(manifest.Id, manifest.Version, manifest.Channel, m_installerType, installResult); + } if (m_isHResult) { @@ -533,7 +543,14 @@ namespace AppInstaller::CLI::Workflow } else { - context.Reporter.Info() << Resource::String::InstallFlowInstallSuccess << std::endl; + if (isRepair) + { + context.Reporter.Info() << Resource::String::RepairFlowRepairSuccess << std::endl; + } + else + { + context.Reporter.Info() << Resource::String::InstallFlowInstallSuccess << std::endl; + } } } @@ -611,6 +628,21 @@ namespace AppInstaller::CLI::Workflow const auto& installer = context.Get<Execution::Data::Installer>(); + // This check is only necessary for the Repair workflow when operating on an installer with RepairBehavior set to Installer. + if (WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::InstallerExecutionUseRepair)) + { + if (installer->RepairBehavior != RepairBehaviorEnum::Installer) + { + return; + } + + // At present, the installer repair behavior scenario is restricted to Exe, Inno, Nullsoft, and Burn installer types. + if (!DoesInstallerTypeRequireRepairBehaviorForRepair(installer->EffectiveInstallerType())) + { + return; + } + } + // This installer cannot be run elevated, but we are running elevated. // Implementation of de-elevation is complex; simply block for now. if (installer->ElevationRequirement == ElevationRequirementEnum::ElevationProhibited && Runtime::IsRunningAsAdmin()) @@ -776,7 +808,7 @@ namespace AppInstaller::CLI::Workflow } CATCH_LOG() - void ReportARPChanges(Execution::Context& context) try + void ReportARPChanges(Execution::Context& context) try { if (!context.Contains(Execution::Data::ARPCorrelationData)) { @@ -826,7 +858,7 @@ namespace AppInstaller::CLI::Workflow for (auto&& upgradeCode : upgradeCodes) { AppsAndFeaturesEntry entry = baseEntry; - entry.UpgradeCode= std::move(upgradeCode).get(); + entry.UpgradeCode = std::move(upgradeCode).get(); entries.push_back(std::move(entry)); } diff --git a/src/AppInstallerCLICore/Workflows/MSStoreInstallerHandler.cpp b/src/AppInstallerCLICore/Workflows/MSStoreInstallerHandler.cpp @@ -146,6 +146,47 @@ namespace AppInstaller::CLI::Workflow } } + void MSStoreRepair(Execution::Context& context) + { + auto productId = Utility::ConvertToUTF16(context.Get<Execution::Data::Installer>()->ProductId); + auto scope = Manifest::ConvertToScopeEnum(context.Args.GetArg(Execution::Args::Type::InstallScope)); + bool isSilentMode = context.Args.Contains(Execution::Args::Type::Silent); + bool force = context.Args.Contains(Execution::Args::Type::Force); + + auto repairOperation = MSStoreOperation(MSStoreOperationType::Repair, productId, scope, isSilentMode, force); + + context.Reporter.Info() << Resource::String::RepairFlowStartingPackageRepair << std::endl; + + HRESULT hr = S_OK; + context.Reporter.ExecuteWithProgress( + [&](IProgressCallback& progress) + { + hr = repairOperation.StartAndWaitForOperation(progress); + }); + + if (SUCCEEDED(hr)) + { + context.Reporter.Info() << Resource::String::RepairFlowRepairSuccess << std::endl; + } + else + { + if (hr == APPINSTALLER_CLI_ERROR_INSTALL_SYSTEM_NOT_SUPPORTED) + { + context.Reporter.Error() << Resource::String::InstallFlowReturnCodeSystemNotSupported << std::endl; + context.Add<Execution::Data::OperationReturnCode>(static_cast<DWORD>(APPINSTALLER_CLI_ERROR_INSTALL_SYSTEM_NOT_SUPPORTED)); + } + else + { + auto errorCodeString = GetErrorCodeString(hr); + context.Reporter.Error() << Resource::String::MSStoreRepairFailed(errorCodeString) << std::endl; + context.Add<Execution::Data::OperationReturnCode>(hr); + AICLI_LOG(CLI, Error, << "MSStore repair failed. ProductId: " << Utility::ConvertToUTF8(productId) << " HResult: " << errorCodeString); + } + + AICLI_TERMINATE_CONTEXT(hr); + } + } + void EnsureStorePolicySatisfied(Execution::Context& context) { auto productId = Utility::ConvertToUTF16(context.Get<Execution::Data::Installer>()->ProductId); diff --git a/src/AppInstallerCLICore/Workflows/MSStoreInstallerHandler.h b/src/AppInstallerCLICore/Workflows/MSStoreInstallerHandler.h @@ -20,6 +20,12 @@ namespace AppInstaller::CLI::Workflow // Outputs: None void MSStoreUpdate(Execution::Context& context); + // Attempt to repair the installation of an Store app that is already installed + // Required Args: None + // Inputs: Installer + // Outputs: None + void MSStoreRepair(Execution::Context& context); + // Ensure the Store app is not blocked by policy. // Required Args: None // Inputs: Installer diff --git a/src/AppInstallerCLICore/Workflows/RepairFlow.cpp b/src/AppInstallerCLICore/Workflows/RepairFlow.cpp @@ -0,0 +1,506 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "RepairFlow.h" +#include "Workflows/ShellExecuteInstallerHandler.h" +#include "Workflows/WorkflowBase.h" +#include "Workflows/DownloadFlow.h" +#include "Workflows/ArchiveFlow.h" +#include "Workflows/InstallFlow.h" +#include "Workflows/PromptFlow.h" +#include "winget/ManifestCommon.h" +#include "AppInstallerDeployment.h" +#include "AppInstallerMsixInfo.h" +#include "AppInstallerSynchronization.h" +#include "MSStoreInstallerHandler.h" +#include "ManifestComparator.h" + +using namespace AppInstaller::Manifest; +using namespace AppInstaller::Msix; +using namespace AppInstaller::Repository; + +namespace AppInstaller::CLI::Workflow +{ + // Internal implementation details + namespace + { + // Sets the uninstall string in the context. + // RequiredArgs: + // Inputs:InstalledPackageVersion + // Outputs:SilentUninstallString, UninstallString + void SetUninstallStringInContext(Execution::Context& context) + { + const auto& installedPackageVersion = context.Get<Execution::Data::InstalledPackageVersion>(); + IPackageVersion::Metadata packageMetadata = installedPackageVersion->GetMetadata(); + + // Default to silent unless it is not present or interactivity is requested + auto uninstallCommandItr = packageMetadata.find(PackageVersionMetadata::SilentUninstallCommand); + + if ((!context.Args.Contains(Execution::Args::Type::Silent) && uninstallCommandItr == packageMetadata.end()) + || context.Args.Contains(Execution::Args::Type::Interactive)) + { + auto interactiveItr = packageMetadata.find(PackageVersionMetadata::StandardUninstallCommand); + if (interactiveItr != packageMetadata.end()) + { + uninstallCommandItr = interactiveItr; + } + } + + if (uninstallCommandItr == packageMetadata.end()) + { + context.Reporter.Error() << Resource::String::NoRepairInfoFound << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_NO_REPAIR_INFO_FOUND); + } + + context.Add<Execution::Data::UninstallString>(uninstallCommandItr->second); + } + + // Sets the modify path in the context. + // RequiredArgs:None + // Inputs:InstalledPackageVersion + // Outputs:ModifyPath + void SetModifyPathInContext(Execution::Context& context) + { + const auto& installedPackageVersion = context.Get<Execution::Data::InstalledPackageVersion>(); + IPackageVersion::Metadata packageMetadata = installedPackageVersion->GetMetadata(); + + // Default to silent unless it is not present or interactivity is requested + auto modifyPathItr = packageMetadata.find(PackageVersionMetadata::StandardModifyCommand); + if (modifyPathItr == packageMetadata.end()) + { + context.Reporter.Error() << Resource::String::NoRepairInfoFound << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_NO_REPAIR_INFO_FOUND); + } + + context.Add<Execution::Data::ModifyPath>(modifyPathItr->second); + } + + // Sets the product codes in the context. + // RequiredArgs:None + // Inputs:InstalledPackageVersion + // Outputs:ProductCodes + void SetProductCodesInContext(Execution::Context& context) + { + const auto& installedPackageVersion = context.Get<Execution::Data::InstalledPackageVersion>(); + auto productCodes = installedPackageVersion->GetMultiProperty(PackageVersionMultiProperty::ProductCode); + + if (productCodes.empty()) + { + context.Reporter.Error() << Resource::String::NoRepairInfoFound << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_NO_REPAIR_INFO_FOUND); + } + + context.Add<Execution::Data::ProductCodes>(productCodes); + } + + // Sets the package family names in the context. + // RequiredArgs:None + // Inputs:InstalledPackageVersion + // Outputs:PackageFamilyNames + void SetPackageFamilyNamesInContext(Execution::Context& context) + { + const auto& installedPackageVersion = context.Get<Execution::Data::InstalledPackageVersion>(); + + auto packageFamilyNames = installedPackageVersion->GetMultiProperty(PackageVersionMultiProperty::PackageFamilyName); + if (packageFamilyNames.empty()) + { + context.Reporter.Error() << Resource::String::NoRepairInfoFound << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_NO_REPAIR_INFO_FOUND); + } + + context.Add<Execution::Data::PackageFamilyNames>(packageFamilyNames); + } + + // The function performs a preliminary check on the installed package by reading its ARP registry flags for NoModify and NoRepair to confirm if the repair operation is applicable. + // RequiredArgs:None + // Inputs:InstalledPackageVersion, NoModify ?, NoRepair ? + // Outputs:None + void ApplicabilityCheckForInstalledPackage(Execution::Context& context) + { + // Installed Package repair applicability check + const auto& installedPackageVersion = context.Get<Execution::Data::InstalledPackageVersion>(); + + const std::string installerType = context.Get<Execution::Data::InstalledPackageVersion>()->GetMetadata()[PackageVersionMetadata::InstalledType]; + InstallerTypeEnum installerTypeEnum = ConvertToInstallerTypeEnum(installerType); + + if (installerTypeEnum == InstallerTypeEnum::Portable || installerTypeEnum == InstallerTypeEnum::Unknown) + { + context.Reporter.Error() << Resource::String::RepairOperationNotSupported << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_REPAIR_NOT_SUPPORTED); + } + + IPackageVersion::Metadata packageMetadata = installedPackageVersion->GetMetadata(); + + auto noModifyItr = packageMetadata.find(PackageVersionMetadata::NoModify); + std::string noModifyARPFlag = noModifyItr != packageMetadata.end() ? noModifyItr->second : std::string(); + + auto noRepairItr = packageMetadata.find(PackageVersionMetadata::NoRepair); + std::string noRepairARPFlag = noRepairItr != packageMetadata.end() ? noRepairItr->second : std::string(); + + if (Utility::IsDwordFlagSet(noModifyARPFlag) || Utility::IsDwordFlagSet(noRepairARPFlag)) + { + context.Reporter.Error() << Resource::String::RepairOperationNotSupported << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_REPAIR_NOT_SUPPORTED); + } + } + + // This function performs a preliminary check on the available matching package by reading its manifest entries for repair behavior to determine the type of repair operation and repair switch are applicable + // RequiredArgs:None + // Inputs:InstallerType, RepairBehavior + // Outputs:None + void ApplicabilityCheckForAvailablePackage(Execution::Context& context) + { + // Selected Installer repair applicability check + auto installerType = context.Get<Execution::Data::Installer>()->EffectiveInstallerType(); + auto repairBehavior = context.Get<Execution::Data::Installer>()->RepairBehavior; + + if (installerType == InstallerTypeEnum::Portable || installerType == InstallerTypeEnum::Unknown) + { + context.Reporter.Error() << Resource::String::RepairOperationNotSupported << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_REPAIR_NOT_SUPPORTED); + } + + // Repair behavior is required for Burn, Inno, Nullsoft, Exe installers + if (DoesInstallerTypeRequireRepairBehaviorForRepair(installerType) && + repairBehavior == RepairBehaviorEnum::Unknown) + { + context.Reporter.Error() << Resource::String::NoRepairInfoFound << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_NO_REPAIR_INFO_FOUND); + } + } + + // Generate the repair string based on the repair behavior and installer type. + // RequiredArgs:None + // Inputs:BaseInstallerType, RepairBehavior, ModifyPath?, UninstallString?, InstallerArgs + // Outputs:RepairString + void GenerateRepairString(Execution::Context& context) + { + const auto& installer = context.Get<Execution::Data::Installer>(); + auto installerType = installer->BaseInstallerType; + auto repairBehavior = installer->RepairBehavior; + + std::string repairCommand; + + switch (repairBehavior) + { + case RepairBehaviorEnum::Modify: + { + SetModifyPathInContext(context); + repairCommand.append(context.Get<Execution::Data::ModifyPath>()); + } + break; + case RepairBehaviorEnum::Installer: + { + // [NOTE:] We will ShellExecuteInstall for this scenario which uses installer path directly.so no need for repair command generation. + // We prepare installer download and archive extraction here. + context << + ShowInstallationDisclaimer << + ShowPromptsForSinglePackage(/* ensureAcceptance */ true) << + DownloadInstaller; + + if (installerType == InstallerTypeEnum::Zip) + { + context << + ScanArchiveFromLocalManifest << + ExtractFilesFromArchive << + VerifyAndSetNestedInstaller; + } + } + break; + case RepairBehaviorEnum::Uninstaller: + { + SetUninstallStringInContext(context); + repairCommand.append(context.Get<Execution::Data::UninstallString>()); + } + break; + case RepairBehaviorEnum::Unknown: + default: + context.Reporter.Error() << Resource::String::NoRepairInfoFound << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_NO_REPAIR_INFO_FOUND); + } + + context << + GetInstallerArgs; + + // Following is not applicable for RepairBehaviorEnum::Installer, as we can call ShellExecuteInstall directly with repair argument. + if (repairBehavior != RepairBehaviorEnum::Installer) + { + if (repairCommand.empty()) + { + context.Reporter.Error() << Resource::String::NoRepairInfoFound << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_NO_REPAIR_INFO_FOUND); + } + + repairCommand.append(" "); + repairCommand.append(context.Get<Execution::Data::InstallerArgs>()); + context.Add<Execution::Data::RepairString>(repairCommand); + } + } + } + + void RunRepairForRepairBehaviorBasedInstaller(Execution::Context& context) + { + const auto& installer = context.Get<Execution::Data::Installer>(); + auto repairBehavior = installer->RepairBehavior; + + if (repairBehavior == RepairBehaviorEnum::Modify || repairBehavior == RepairBehaviorEnum::Uninstaller) + { + context << + ShellExecuteRepairImpl << + ReportRepairResult(RepairBehaviorToString(repairBehavior), APPINSTALLER_CLI_ERROR_EXEC_REPAIR_FAILED); + } + else if (repairBehavior == RepairBehaviorEnum::Installer) + { + context << + ShellExecuteInstallImpl << + ReportInstallerResult(RepairBehaviorToString(repairBehavior), APPINSTALLER_CLI_ERROR_EXEC_REPAIR_FAILED); + } + else + { + context.Reporter.Error() << Resource::String::NoRepairInfoFound << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_NO_REPAIR_INFO_FOUND); + } + } + + void RepairMsiBasedInstaller(Execution::Context& context) + { + context << + ShellExecuteMsiExecRepair << + ReportRepairResult("MsiExec", APPINSTALLER_CLI_ERROR_EXEC_REPAIR_FAILED); + } + + void RepairApplicabilityCheck(Execution::Context& context) + { + context << + ApplicabilityCheckForInstalledPackage << + ApplicabilityCheckForAvailablePackage; + } + + void ExecuteRepair(Execution::Context& context) + { + // [TODO:] At present, the repair flow necessitates a mapped available installer. + // However, future refactoring should allow for msix/msi repair without the need for one. + + const auto& installer = context.Get<Execution::Data::Installer>(); + InstallerTypeEnum installerTypeEnum = installer->EffectiveInstallerType(); + + Synchronization::CrossProcessInstallLock lock; + + if (!ExemptFromSingleInstallLocking(installerTypeEnum)) + { + // Acquire the lock , if the operation is cancelled it will return false so we will also return. + if (!context.Reporter.ExecuteWithProgress([&](IProgressCallback& callback) + { + callback.SetProgressMessage(Resource::String::InstallWaitingOnAnother()); + return lock.Acquire(callback); + })) + { + AICLI_LOG(CLI, Info, << "Abandoning attempt to acquire repair lock due to cancellation"); + return; + } + } + + switch (installerTypeEnum) + { + case InstallerTypeEnum::Burn: + case InstallerTypeEnum::Exe: + case InstallerTypeEnum::Inno: + case InstallerTypeEnum::Nullsoft: + { + context << + RunRepairForRepairBehaviorBasedInstaller; + } + break; + case InstallerTypeEnum::Msi: + case InstallerTypeEnum::Wix: + { + context << + RepairMsiBasedInstaller; + } + break; + case InstallerTypeEnum::Msix: + { + context << + RepairMsixPackage; + } + break; + case InstallerTypeEnum::MSStore: + { + context << + EnsureStorePolicySatisfied << + MSStoreRepair; + } + break; + case InstallerTypeEnum::Portable: + default: + THROW_HR(HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED)); + } + } + + void GetRepairInfo(Execution::Context& context) + { + const auto& installer = context.Get<Execution::Data::Installer>(); + InstallerTypeEnum installerTypeEnum = installer->EffectiveInstallerType(); + + switch (installerTypeEnum) + { + case InstallerTypeEnum::Burn: + case InstallerTypeEnum::Exe: + case InstallerTypeEnum::Inno: + case InstallerTypeEnum::Nullsoft: + { + context << + GenerateRepairString; + } + break; + case InstallerTypeEnum::Msi: + case InstallerTypeEnum::Wix: + { + context << + SetProductCodesInContext; + } + break; + case InstallerTypeEnum::Msix: + { + context << + SetPackageFamilyNamesInContext; + } + break; + case InstallerTypeEnum::MSStore: + break; + case InstallerTypeEnum::Portable: + default: + THROW_HR(HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED)); + } + } + + void RepairMsixPackage(Execution::Context& context) + { + bool isMachineScope = Manifest::ConvertToScopeEnum(context.Args.GetArg(Execution::Args::Type::InstallScope)) == Manifest::ScopeEnum::Machine; + + const auto& packageFamilyNames = context.Get<Execution::Data::PackageFamilyNames>(); + context.Reporter.Info() << Resource::String::RepairFlowStartingPackageRepair << std::endl; + + for (const auto& packageFamilyName : packageFamilyNames) + { + auto packageFullName = Msix::GetPackageFullNameFromFamilyName(packageFamilyName); + + if (!packageFullName.has_value()) + { + AICLI_LOG(CLI, Warning, << "No package found with family name: " << packageFamilyName); + continue; + } + + AICLI_LOG(CLI, Info, << "Repairing package: " << packageFullName.value()); + + try + { + if (!isMachineScope) + { + // Best effort repair by registering the package. + context.Reporter.ExecuteWithProgress(std::bind(Deployment::RegisterPackage, packageFamilyName, std::placeholders::_1)); + } + else + { + context.Reporter.Error() << Resource::String::RepairFlowReturnCodeSystemNotSupported << std::endl; + context.Add<Execution::Data::OperationReturnCode>(static_cast<DWORD>(APPINSTALLER_CLI_ERROR_INSTALL_SYSTEM_NOT_SUPPORTED)); + AICLI_LOG(CLI, Error, << "Device wide repair for msix type is not supported."); + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_INSTALL_SYSTEM_NOT_SUPPORTED); + } + } + catch (const wil::ResultException& re) + { + context.Add<Execution::Data::OperationReturnCode>(re.GetErrorCode()); + context << ReportRepairResult("MSIX", re.GetErrorCode(), true); + return; + } + } + + context.Reporter.Info() << Resource::String::RepairFlowRepairSuccess << std::endl; + } + + void RepairSinglePackage(Execution::Context& context) + { + context << + RepairApplicabilityCheck << + GetRepairInfo << + ReportExecutionStage(ExecutionStage::Execution) << + ExecuteRepair << + ReportExecutionStage(ExecutionStage::PostExecution); + } + + void SelectApplicablePackageVersion(Execution::Context& context) + { + const auto& installedPackage = context.Get<Execution::Data::InstalledPackageVersion>(); + + Utility::Version installedVersion = Utility::Version(installedPackage->GetProperty(PackageVersionProperty::Version)); + if (installedVersion.IsUnknown()) + { + context.Reporter.Error() << Resource::String::NoApplicableInstallers << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_NO_APPLICABLE_INSTALLER); + } + + std::string_view requestedVersion = context.Args.Contains(Execution::Args::Type::Version) ? context.Args.GetArg(Execution::Args::Type::Version) : installedVersion.ToString(); + // If it's Store source with only one version unknown, use the unknown version for available version mapping. + const auto& package = context.Get<Execution::Data::Package>(); + auto versionKeys = package->GetAvailableVersionKeys(); + if (versionKeys.size() == 1) + { + auto packageVersion = package->GetAvailableVersion(versionKeys.at(0)); + if (packageVersion->GetSource().IsWellKnownSource(WellKnownSource::MicrosoftStore) && + Utility::Version{ packageVersion->GetProperty(PackageVersionProperty::Version) }.IsUnknown()) + { + requestedVersion = ""; + } + } + + context << + GetManifestWithVersionFromPackage( + requestedVersion, + context.Args.GetArg(Execution::Args::Type::Channel), false) << + SelectInstaller << + EnsureApplicableInstaller; + } + + void ReportRepairResult::operator()(Execution::Context& context) const + { + DWORD repairResult = context.Get<Execution::Data::OperationReturnCode>(); + + if (repairResult != 0) + { + const auto& repairPackage = context.Get<Execution::Data::PackageVersion>(); + + Logging::Telemetry().LogRepairFailure( + repairPackage->GetProperty(PackageVersionProperty::Id), + repairPackage->GetProperty(PackageVersionProperty::Version), + m_repairType, + repairResult); + + if (m_isHResult) + { + context.Reporter.Error() + << Resource::String::RepairFailedWithCode(Utility::LocIndView{ GetUserPresentableMessage(repairResult) }) + << std::endl; + } + else + { + context.Reporter.Error() + << Resource::String::RepairFailedWithCode(repairResult) + << std::endl; + } + + // Show log path if available + if (context.Contains(Execution::Data::LogPath) && std::filesystem::exists(context.Get<Execution::Data::LogPath>())) + { + auto installerLogPath = Utility::LocIndString{ context.Get<Execution::Data::LogPath>().u8string() }; + context.Reporter.Info() << Resource::String::InstallerLogAvailable(installerLogPath) << std::endl; + } + + AICLI_TERMINATE_CONTEXT(m_hr); + } + else + { + context.Reporter.Info() << Resource::String::RepairFlowRepairSuccess << std::endl; + } + } +}+ \ No newline at end of file diff --git a/src/AppInstallerCLICore/Workflows/RepairFlow.h b/src/AppInstallerCLICore/Workflows/RepairFlow.h @@ -0,0 +1,78 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "ExecutionContext.h" + +namespace AppInstaller::CLI::Workflow +{ + // Execute the repair operation for RepairBehavior based installers. + // RequiredArgs:None + // Inputs: RepairBehavior, RepairString + // Outputs:None + void RunRepairForRepairBehaviorBasedInstaller(Execution::Context& context); + + // Execute the repair operation for MSI based installers. + // RequiredArgs:None + // Inputs: ProductCodes + // Outputs:None + void RepairMsiBasedInstaller(Execution::Context& context); + + // Applicability check for repair operation. + // RequiredArgs:None + // Inputs:InstalledPackageVersion, NoModify ?, NoRepair ? + // Outputs:None + void RepairApplicabilityCheck(Execution::Context& context); + + // Execute the repair operation. + // RequiredArgs:None + // Inputs: InstallerType, RepairBehavior ?, RepairString? , ProductCodes?, PackageFamilyNames? + // Outputs:None + void ExecuteRepair(Execution::Context& context); + + // Obtains the necessary information for repair operation. + // RequiredArgs:None + // Inputs:InstallerType + // Outputs:RepairString?, ProductCodes?, PackageFamilyNames? + void GetRepairInfo(Execution::Context& context); + + // Perform the repair operation for the MSIX package. + // RequiredArgs:None + // Inputs:PackageFamilyNames , InstallScope? + // Outputs:None + void RepairMsixPackage(Execution::Context& context); + + // Select the applicable package version by matching the installed package version with the available package version. + // RequiredArgs:None + // Inputs: Package,InstalledPackageVersion, AvailablePackageVersions + // Outputs:Manifest, PackageVersion, Installer + void SelectApplicablePackageVersion(Execution::Context& context); + + // Perform the repair operation for the single package. + // RequiredArgs:None + // Inputs: SearchResult, InstalledPackage, ApplicableInstaller + // Outputs:None + void RepairSinglePackage(Execution::Context& context); + + // Reports the result of the repair. + // Required Args: None + // Inputs: None + // Outputs: None + struct ReportRepairResult : public WorkflowTask + { + ReportRepairResult(std::string_view repairType, HRESULT hr, bool isHResult = false) : + WorkflowTask("ReportRepairResult"), + m_repairType(repairType), + m_hr(hr), + m_isHResult(isHResult) {} + + void operator()(Execution::Context& context) const override; + + private: + // Repair type used for reporting failure. + std::string_view m_repairType; + // Result to return if the repair fails. + HRESULT m_hr; + // Whether the result is an HRESULT. + bool m_isHResult; + }; +} diff --git a/src/AppInstallerCLICore/Workflows/ShellExecuteInstallerHandler.cpp b/src/AppInstallerCLICore/Workflows/ShellExecuteInstallerHandler.cpp @@ -76,6 +76,7 @@ namespace AppInstaller::CLI::Workflow std::string GetInstallerArgsTemplate(Execution::Context& context) { bool isUpdate = WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::InstallerExecutionUseUpdate); + bool isRepair = WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::InstallerExecutionUseRepair); const auto& installer = context.Get<Execution::Data::Installer>(); const auto& installerSwitches = installer->Switches; @@ -114,6 +115,17 @@ namespace AppInstaller::CLI::Workflow installerArgs += ' ' + installerSwitches.at(InstallerSwitchType::Log); } + // Construct repair arg. Custom switches and othe args are not applicable for repair scenario so we can return here. + if (isRepair) + { + if (installerSwitches.find(InstallerSwitchType::Repair) != installerSwitches.end()) + { + installerArgs += ' ' + installerSwitches.at(InstallerSwitchType::Repair); + } + + return installerArgs; + } + // Construct custom arg. if (installerSwitches.find(InstallerSwitchType::Custom) != installerSwitches.end()) { @@ -202,11 +214,45 @@ namespace AppInstaller::CLI::Workflow return args; } + + // Gets the arguments for repairing an MSI with MsiExec + std::string GetMsiExecRepairArgs(Execution::Context& context, const Utility::LocIndString& productCode) + { + // https://learn.microsoft.com/en-us/windows/win32/msi/command-line-options + // Available Options for '/f [p|o|e|d|c|a|u|m|s|v] <Product.msi | ProductCode>' + // Default parameter for '/f' is 'omus' + // o - Reinstall all files regardless of version + // m - Rewrite all required registry entries (This is the default option) + // u - Rewrite all required user-specific registry entries (This is the default option) + // s - Overwrite all existing shortcuts (This is the default option) + std::string args = "/f " + productCode.get(); + + // https://learn.microsoft.com/en-us/windows/win32/msi/standard-installer-command-line-options + if (context.Args.Contains(Execution::Args::Type::Silent)) + { + args += " /quiet /norestart"; + } + else if (!context.Args.Contains(Execution::Args::Type::Interactive)) + { + args += " /passive /norestart"; + } + + return args; + } } void ShellExecuteInstallImpl(Execution::Context& context) { - context.Reporter.Info() << Resource::String::InstallFlowStartingPackageInstall << std::endl; + bool isRepair = WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::InstallerExecutionUseRepair); + + if (isRepair) + { + context.Reporter.Info() << Resource::String::RepairFlowStartingPackageRepair << std::endl; + } + else + { + context.Reporter.Info() << Resource::String::InstallFlowStartingPackageInstall << std::endl; + } const auto& installer = context.Get<Execution::Data::Installer>(); const std::string& installerArgs = context.Get<Execution::Data::InstallerArgs>(); @@ -234,7 +280,15 @@ namespace AppInstaller::CLI::Workflow if (!installResult) { - context.Reporter.Warn() << Resource::String::InstallAbandoned << std::endl; + if (isRepair) + { + context.Reporter.Warn() << Resource::String::RepairAbandoned << std::endl; + } + else + { + context.Reporter.Warn() << Resource::String::InstallAbandoned << std::endl; + } + AICLI_TERMINATE_CONTEXT(E_ABORT); } else @@ -287,6 +341,49 @@ namespace AppInstaller::CLI::Workflow } } + void ShellExecuteRepairImpl(Execution::Context& context) + { + context.Reporter.Info() << Resource::String::RepairFlowStartingPackageRepair << std::endl; + + std::wstring commandUtf16 = Utility::ConvertToUTF16(context.Get<Execution::Data::RepairString>()); + + // When running as admin, block attempt to repair user scope installed package. + // [NOTE:] This check is to address the security concern related to above scenario. + if (Runtime::IsRunningAsAdmin()) + { + auto installedPackageVersion = context.Get<Execution::Data::InstalledPackageVersion>(); + const std::string installedScopeString = installedPackageVersion->GetMetadata()[PackageVersionMetadata::InstalledScope]; + auto scopeEnum = ConvertToScopeEnum(installedScopeString); + + if (scopeEnum == ScopeEnum::User) + { + context.Reporter.Error() << Resource::String::NoAdminRepairForUserScopePackage << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_ADMIN_CONTEXT_REPAIR_PROHIBITED); + } + } + + // Parse the command string as application and command line for CreateProcess + wil::unique_cotaskmem_string app = nullptr; + wil::unique_cotaskmem_string args = nullptr; + THROW_IF_FAILED(SHEvaluateSystemCommandTemplate(commandUtf16.c_str(), &app, NULL, &args)); + + auto repairResult = context.Reporter.ExecuteWithProgress( + std::bind(InvokeShellExecute, + std::filesystem::path(app.get()), + Utility::ConvertToUTF8(args.get()), + std::placeholders::_1)); + + if (!repairResult) + { + context.Reporter.Error() << Resource::String::RepairAbandoned << std::endl; + AICLI_TERMINATE_CONTEXT(E_ABORT); + } + else + { + context.Add<Execution::Data::OperationReturnCode>(repairResult.value()); + } + } + void ShellExecuteMsiExecUninstall(Execution::Context& context) { const auto& productCodes = context.Get<Execution::Data::ProductCodes>(); @@ -305,7 +402,7 @@ namespace AppInstaller::CLI::Workflow if (!uninstallResult) { - context.Reporter.Warn() << Resource::String::UninstallAbandoned << std::endl; + context.Reporter.Error() << Resource::String::UninstallAbandoned << std::endl; AICLI_TERMINATE_CONTEXT(E_ABORT); } else @@ -315,6 +412,34 @@ namespace AppInstaller::CLI::Workflow } } + void ShellExecuteMsiExecRepair(Execution::Context& context) + { + const auto& productCodes = context.Get<Execution::Data::ProductCodes>(); + context.Reporter.Info() << Resource::String::RepairFlowStartingPackageRepair << std::endl; + + const std::filesystem::path msiexecPath{ ExpandEnvironmentVariables(L"%windir%\\system32\\msiexec.exe") }; + + for (const auto& productCode : productCodes) + { + AICLI_LOG(CLI, Info, << "Repairing: " << productCode); + auto repairResult = context.Reporter.ExecuteWithProgress( + std::bind(InvokeShellExecute, + msiexecPath, + GetMsiExecRepairArgs(context, productCode), + std::placeholders::_1)); + + if (!repairResult) + { + context.Reporter.Error() << Resource::String::RepairAbandoned << std::endl; + AICLI_TERMINATE_CONTEXT(E_ABORT); + } + else + { + context.Add<Execution::Data::OperationReturnCode>(repairResult.value()); + } + } + } + #ifndef AICLI_DISABLE_TEST_HOOKS std::optional<DWORD> s_EnableWindowsFeatureResult_Override{}; diff --git a/src/AppInstallerCLICore/Workflows/ShellExecuteInstallerHandler.h b/src/AppInstallerCLICore/Workflows/ShellExecuteInstallerHandler.h @@ -35,6 +35,18 @@ namespace AppInstaller::CLI::Workflow // Outputs: InstallerArgs void GetInstallerArgs(Execution::Context& context); + // Repair is done through invoking ShellExecute on downloaded installer. + // Required Args: None + // Inputs: Manifest?, InstallerPath, InstallerArgs + // Outputs: OperationReturnCode + void ShellExecuteRepairImpl(Execution::Context& context); + + // Repair the MSI + // Required Args: None + // Inputs: ProductCodes + // Output: None + void ShellExecuteMsiExecRepair(Execution::Context& context); + // Enables the Windows Feature dependency by invoking ShellExecute on the DISM executable. // Required Args: None // Inputs: Windows Feature dependency diff --git a/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp b/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp @@ -952,6 +952,7 @@ namespace AppInstaller::CLI::Workflow case OperationType::Uninstall: case OperationType::Pin: case OperationType::Upgrade: + case OperationType::Repair: context.Reporter.Info() << Resource::String::NoInstalledPackageFound << std::endl; break; case OperationType::Completion: @@ -981,7 +982,7 @@ namespace AppInstaller::CLI::Workflow { Logging::Telemetry().LogMultiAppMatch(); - if (m_operationType == OperationType::Upgrade || m_operationType == OperationType::Uninstall ) + if (m_operationType == OperationType::Upgrade || m_operationType == OperationType::Uninstall || m_operationType == OperationType::Repair) { context.Reporter.Warn() << Resource::String::MultipleInstalledPackagesFound << std::endl; context << ReportMultiplePackageFoundResult; @@ -1225,10 +1226,11 @@ namespace AppInstaller::CLI::Workflow void SelectInstaller(Execution::Context& context) { bool isUpdate = WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::InstallerExecutionUseUpdate); + bool isRepair = WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::InstallerExecutionUseRepair); IPackageVersion::Metadata installationMetadata; - if (isUpdate) + if (isUpdate || isRepair) { installationMetadata = context.Get<Execution::Data::InstalledPackageVersion>()->GetMetadata(); } @@ -1241,8 +1243,16 @@ namespace AppInstaller::CLI::Workflow auto onlyInstalledType = std::find(inapplicabilities.begin(), inapplicabilities.end(), InapplicabilityFlags::InstalledType); if (onlyInstalledType != inapplicabilities.end()) { - context.Reporter.Info() << Resource::String::UpgradeDifferentInstallTechnology << std::endl; - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_UPDATE_NOT_APPLICABLE); + if (isRepair) + { + context.Reporter.Info() << Resource::String::RepairDifferentInstallTechnology << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_REPAIR_NOT_APPLICABLE); + } + else + { + context.Reporter.Info() << Resource::String::UpgradeDifferentInstallTechnology << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_UPDATE_NOT_APPLICABLE); + } } } diff --git a/src/AppInstallerCLICore/Workflows/WorkflowBase.h b/src/AppInstallerCLICore/Workflows/WorkflowBase.h @@ -42,6 +42,7 @@ namespace AppInstaller::CLI::Workflow Uninstall, Upgrade, Download, + Repair, }; // A task in the workflow. diff --git a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw @@ -2757,4 +2757,61 @@ Please specify one of them using the --source option to proceed.</value> <value>The {0} source requires authentication. Authentication prompt may appear when necessary. Authenticated information will be shared with the source for access authorization.</value> <comment>{Locked="{0}"}</comment> </data> + <data name="RepairCommandLongDescription" xml:space="preserve"> + <value>Repairs the selected package, either found by searching the installed packages list or directly from a manifest. By default, the query must case-insensitively match the id, name, or moniker of the package. Other fields can be used by passing their appropriate option.</value> + <comment>id, name, and moniker are all named values in our context, and may benefit from not being translated. The match must be for any of them, with comparison ignoring case.</comment> + </data> + <data name="RepairCommandShortDescription" xml:space="preserve"> + <value>Repairs the selected package</value> + </data> + <data name="NoRepairInfoFound" xml:space="preserve"> + <value>The repair command for this package cannot be found. Please reach out to the package publisher for support.</value> + </data> + <data name="RepairDifferentInstallTechnology" xml:space="preserve"> + <value>The installer technology in use does not match the version currently installed.</value> + </data> + <data name="APPINSTALLER_CLI_ERROR_NO_REPAIR_INFO_FOUND" xml:space="preserve"> + <value>Repair command not found.</value> + </data> + <data name="APPINSTALLER_CLI_ERROR_REPAIR_NOT_SUPPORTED" xml:space="preserve"> + <value>The installer technology in use doesn't support repair.</value> + </data> + <data name="RepairFlowRepairSuccess" xml:space="preserve"> + <value>Repair operation completed successfully.</value> + </data> + <data name="RepairAbandoned" xml:space="preserve"> + <value>Repair abandoned</value> + </data> + <data name="RepairFlowStartingPackageRepair" xml:space="preserve"> + <value>Starting package repair...</value> + </data> + <data name="MSStoreRepairFailed" xml:space="preserve"> + <value>Failed to repair Microsoft Store package. Error code: {0}</value> + <comment>{Locked="{0}"} Error message displayed when a Microsoft Store application package fails to repair. {0} is a placeholder replaced by an error code.</comment> + </data> + <data name="APPINSTALLER_CLI_ERROR_EXEC_REPAIR_FAILED" xml:space="preserve"> + <value>Repair operation failed.</value> + </data> + <data name="APPINSTALLER_CLI_ERROR_REPAIR_NOT_APPLICABLE" xml:space="preserve"> + <value>Repair operation is not applicable.</value> + </data> + <data name="RepairFlowNoMatchingVersion" xml:space="preserve"> + <value>No matching package versions are available from the configured sources.</value> + </data> + <data name="RepairFlowReturnCodeSystemNotSupported" xml:space="preserve"> + <value>The current system configuration does not support the repair of this package.</value> + </data> + <data name="RepairOperationNotSupported" xml:space="preserve"> + <value>The installer technology in use does not support repair.</value> + </data> + <data name="NoAdminRepairForUserScopePackage" xml:space="preserve"> + <value>The package installed for user scope cannot be repaired when running with administrator privileges.</value> + </data> + <data name="APPINSTALLER_CLI_ERROR_ADMIN_CONTEXT_REPAIR_PROHIBITED" xml:space="preserve"> + <value>Repair operations involving administrator privileges are not permitted on packages installed within the user scope.</value> + </data> + <data name="RepairFailedWithCode" xml:space="preserve"> + <value>Repair failed with exit code: {0}</value> + <comment>{Locked="{0}"} Error message displayed when an attempt to repair an application package fails. {0} is a placeholder replaced by an error code.</comment> + </data> </root> \ No newline at end of file diff --git a/src/AppInstallerCommonCore/AppInstallerTelemetry.cpp b/src/AppInstallerCommonCore/AppInstallerTelemetry.cpp @@ -725,6 +725,33 @@ namespace AppInstaller::Logging } } + void TelemetryTraceLogger::LogRepairFailure(std::string_view id, std::string_view version, std::string_view type, uint32_t errorCode) const noexcept + { + if (IsTelemetryEnabled()) + { + AICLI_TraceLoggingWriteActivity( + "RepairFailure", + TraceLoggingUInt32(m_subExecutionId, "SubExecutionId"), + AICLI_TraceLoggingStringView(id, "Id"), + AICLI_TraceLoggingStringView(version, "Version"), + AICLI_TraceLoggingStringView(type, "Type"), + TraceLoggingUInt32(errorCode, "ErrorCode"), + TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance), + TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA)); + + if (m_useSummary) + { + m_summary.PackageIdentifier = id; + m_summary.PackageVersion = version; + m_summary.RepairExecutionType = type; + m_summary.RepairErrorCode = errorCode; + + } + } + + AICLI_LOG(CLI, Error, << type << " repair failed: " << errorCode); + } + TelemetryTraceLogger::~TelemetryTraceLogger() { if (IsTelemetryEnabled()) @@ -798,6 +825,8 @@ namespace AppInstaller::Logging AICLI_TraceLoggingStringView(m_summary.ARPPublisher, "ARPPublisher"), AICLI_TraceLoggingStringView(m_summary.DOUrl, "DOUrl"), TraceLoggingHResult(m_summary.DOHResult, "DOHResult"), + AICLI_TraceLoggingStringView(m_summary.RepairExecutionType, "RepairExecutionType"), + TraceLoggingUInt32(m_summary.RepairErrorCode, "RepairErrorCode"), TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance | PDT_ProductAndServiceUsage | PDT_SoftwareSetupAndInventory), TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES)); } diff --git a/src/AppInstallerCommonCore/Deployment.cpp b/src/AppInstallerCommonCore/Deployment.cpp @@ -124,7 +124,7 @@ namespace AppInstaller::Deployment RemovePackage(packageFullName, RemovalOptions::None, cb); } CATCH_LOG(); - }); + }); Uri uriObject(Utility::ConvertToUTF16(uri)); @@ -229,7 +229,7 @@ namespace AppInstaller::Deployment RemovePackage(packageFullName, RemovalOptions::RemoveForAllUsers, cb); } CATCH_LOG(); - }); + }); Uri uriObject(Utility::ConvertToUTF16(uri)); PartialPercentProgressCallback progress{ callback, 100 }; @@ -322,4 +322,18 @@ namespace AppInstaller::Deployment return packages.begin() != packages.end(); } + + void RegisterPackage( + std::string_view packageFamilyName, + IProgressCallback& callback) + { + size_t id = GetDeploymentOperationId(); + AICLI_LOG(Core, Info, << "Starting RegisterPackageByFullNameAsync operation #" << id << ": " << packageFamilyName); + + PackageManager packageManager; + winrt::hstring packageFamilyNameWide = Utility::ConvertToUTF16(packageFamilyName).c_str(); + auto deployOperation = packageManager.RegisterPackageByFamilyNameAsync(packageFamilyNameWide, nullptr, DeploymentOptions::None, nullptr, nullptr); + + WaitForDeployment(deployOperation, id, callback); + } } diff --git a/src/AppInstallerCommonCore/MSStore.cpp b/src/AppInstallerCommonCore/MSStore.cpp @@ -102,7 +102,7 @@ namespace AppInstaller::MSStore // Best effort verifying/acquiring product ownership. std::ignore = EnsureFreeEntitlement(m_productId, m_scope); - if (m_type == MSStoreOperationType::Install) + if (m_type == MSStoreOperationType::Install || m_type == MSStoreOperationType::Repair) { return InstallPackage(progress); } @@ -122,6 +122,12 @@ namespace AppInstaller::MSStore installOptions.CompletedInstallToastNotificationMode(AppInstallationToastNotificationMode::NoToast); } + if (m_type == MSStoreOperationType::Repair) + { + // Attempt to repair the installation of an app that is already installed. + installOptions.Repair(true); + } + if (m_scope == Manifest::ScopeEnum::Machine) { // TODO: There was a bug in InstallService where admin user is incorrectly identified as not admin, diff --git a/src/AppInstallerCommonCore/Manifest/ManifestCommon.cpp b/src/AppInstallerCommonCore/Manifest/ManifestCommon.cpp @@ -899,6 +899,15 @@ namespace AppInstaller::Manifest installerType == InstallerTypeEnum::Msix; } + bool DoesInstallerTypeRequireRepairBehaviorForRepair(InstallerTypeEnum installerType) + { + return + installerType == InstallerTypeEnum::Burn || + installerType == InstallerTypeEnum::Inno || + installerType == InstallerTypeEnum::Nullsoft || + installerType == InstallerTypeEnum::Exe; + } + bool IsArchiveType(InstallerTypeEnum installerType) { return (installerType == InstallerTypeEnum::Zip); diff --git a/src/AppInstallerCommonCore/Public/AppInstallerDeployment.h b/src/AppInstallerCommonCore/Public/AppInstallerDeployment.h @@ -47,4 +47,9 @@ namespace AppInstaller::Deployment // Calls winrt::Windows::Management::Deployment::PackageManager::FindPackagesForUser bool IsRegistered(std::string_view packageFamilyName); + + // Calls winrt::Windows::Management::Deployment::PackageManager::RegisterPackageByFamilyNameAsync + void RegisterPackage( + std::string_view packageFamilyName, + IProgressCallback& callback); } diff --git a/src/AppInstallerCommonCore/Public/AppInstallerTelemetry.h b/src/AppInstallerCommonCore/Public/AppInstallerTelemetry.h @@ -110,6 +110,10 @@ namespace AppInstaller::Logging std::string UninstallerExecutionType; UINT32 UninstallerErrorCode = 0; + // LogRepairFailure + std::string RepairExecutionType; + UINT32 RepairErrorCode = 0; + // LogSuccessfulInstallARPChange UINT64 ChangesToARP = 0; UINT64 MatchesInARP = 0; @@ -236,6 +240,9 @@ namespace AppInstaller::Logging // Logs a failed uninstallation attempt. void LogUninstallerFailure(std::string_view id, std::string_view version, std::string_view type, uint32_t errorCode) const noexcept; + // Logs a failed repair attempt. + void LogRepairFailure(std::string_view id, std::string_view version, std::string_view type, uint32_t errorCode) const noexcept; + // Logs data about the changes that ocurred in the ARP entries based on an install. // First 4 arguments are well known values for the package that we installed. // The next 3 are counts of the number of packages in each category. diff --git a/src/AppInstallerCommonCore/Public/winget/MSStore.h b/src/AppInstallerCommonCore/Public/winget/MSStore.h @@ -18,6 +18,7 @@ namespace AppInstaller::MSStore { Install, Update, + Repair, }; struct MSStoreOperation diff --git a/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h b/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h @@ -431,6 +431,9 @@ namespace AppInstaller::Manifest // Gets a value indicating whether the given installer requires admin for install. bool DoesInstallerTypeRequireAdminForMachineScopeInstall(InstallerTypeEnum installerType); + // Gets a value indicating whether the given installer requires RepairBehavior for repair. + bool DoesInstallerTypeRequireRepairBehaviorForRepair(InstallerTypeEnum installerType); + // Gets a value indicating whether the given installer type is an archive. bool IsArchiveType(InstallerTypeEnum installerType); diff --git a/src/AppInstallerRepositoryCore/Microsoft/ARPHelper.cpp b/src/AppInstallerRepositoryCore/Microsoft/ARPHelper.cpp @@ -530,6 +530,11 @@ namespace AppInstaller::Repository::Microsoft AddMetadataIfPresent(arpKey, UninstallString, index, manifestId, PackageVersionMetadata::StandardUninstallCommand); AddMetadataIfPresent(arpKey, QuietUninstallString, index, manifestId, PackageVersionMetadata::SilentUninstallCommand); + // Pick up ModifyPath for repair. + AddMetadataIfPresent(arpKey, ModifyPath, index, manifestId, PackageVersionMetadata::StandardModifyCommand); + AddMetadataIfPresent(arpKey, NoModify, index, manifestId, PackageVersionMetadata::NoModify); + AddMetadataIfPresent(arpKey, NoRepair, index, manifestId, PackageVersionMetadata::NoRepair); + // Pick up Language to enable proper selection of language for upgrade. AddMetadataIfPresent(arpKey, Language, index, manifestId, PackageVersionMetadata::InstalledLocale); diff --git a/src/AppInstallerRepositoryCore/Microsoft/ARPHelper.h b/src/AppInstallerRepositoryCore/Microsoft/ARPHelper.h @@ -55,6 +55,12 @@ namespace AppInstaller::Repository::Microsoft const std::wstring SystemComponent{ L"SystemComponent" }; // REG_SZ const std::wstring DisplayIcon{ L"DisplayIcon" }; + // REG_DWORD + const std::wstring NoModify{ L"NoModify" }; + // REG_DWORD + const std::wstring NoRepair{ L"NoRepair" }; + // REG_SZ + const std::wstring ModifyPath{ L"ModifyPath" }; // Gets the registry key associated with the given scope and architecture on this platform. // May return an empty key if there is no valid location (bad combination or not found). diff --git a/src/AppInstallerRepositoryCore/Public/winget/RepositorySearch.h b/src/AppInstallerRepositoryCore/Public/winget/RepositorySearch.h @@ -202,6 +202,12 @@ namespace AppInstaller::Repository UserIntentArchitecture, // The locale of user intent UserIntentLocale, + // The standard modify command; which may be interactive + StandardModifyCommand, + // No Modify flag + NoModify, + // No Repair flag + NoRepair, }; // Convert a PackageVersionMetadata to a string. diff --git a/src/AppInstallerSharedLib/AppInstallerStrings.cpp b/src/AppInstallerSharedLib/AppInstallerStrings.cpp @@ -867,4 +867,24 @@ namespace AppInstaller::Utility THROW_HR_IF(E_UNEXPECTED, !StringFromGUID2(value, buffer, ARRAYSIZE(buffer))); return ConvertToUTF8(buffer); } + + bool IsDwordFlagSet(const std::string& value) + { + if (std::empty(value)) + { + return false; + } + + try + { + DWORD dwordValue = std::stoul(value); + + // If the value is 0, then it is not set. + return dwordValue != 0; + } + catch (...) + { + return false; + } + } } diff --git a/src/AppInstallerSharedLib/Errors.cpp b/src/AppInstallerSharedLib/Errors.cpp @@ -208,6 +208,11 @@ namespace AppInstaller WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_AUTHENTICATION_INTERACTIVE_REQUIRED, "Authentication failed. Interactive authentication required."), WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_AUTHENTICATION_CANCELLED_BY_USER, "Authentication failed. User cancelled."), WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_AUTHENTICATION_INCORRECT_ACCOUNT, "Authentication failed. Authenticated account is not the desired account."), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_NO_REPAIR_INFO_FOUND, "Repair command not found."), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_REPAIR_NOT_APPLICABLE, "Repair operation is not applicable."), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_EXEC_REPAIR_FAILED, "Repair operation failed."), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_REPAIR_NOT_SUPPORTED, "The installer technology in use doesn't support repair."), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_ADMIN_CONTEXT_REPAIR_PROHIBITED, "Repair operations involving administrator privileges are not permitted on packages installed within the user scope."), // Install errors. WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_INSTALL_PACKAGE_IN_USE, "Application is currently running. Exit the application then try again."), diff --git a/src/AppInstallerSharedLib/Public/AppInstallerErrors.h b/src/AppInstallerSharedLib/Public/AppInstallerErrors.h @@ -138,6 +138,11 @@ #define APPINSTALLER_CLI_ERROR_AUTHENTICATION_INTERACTIVE_REQUIRED ((HRESULT)0x8A150076) #define APPINSTALLER_CLI_ERROR_AUTHENTICATION_CANCELLED_BY_USER ((HRESULT)0x8A150077) #define APPINSTALLER_CLI_ERROR_AUTHENTICATION_INCORRECT_ACCOUNT ((HRESULT)0x8A150078) +#define APPINSTALLER_CLI_ERROR_NO_REPAIR_INFO_FOUND ((HRESULT)0x8A150079) +#define APPINSTALLER_CLI_ERROR_REPAIR_NOT_APPLICABLE ((HRESULT)0x8A15007A) +#define APPINSTALLER_CLI_ERROR_EXEC_REPAIR_FAILED ((HRESULT)0x8A15007B) +#define APPINSTALLER_CLI_ERROR_REPAIR_NOT_SUPPORTED ((HRESULT)0x8A15007C) +#define APPINSTALLER_CLI_ERROR_ADMIN_CONTEXT_REPAIR_PROHIBITED ((HRESULT)0x8A15007D) // Install errors. #define APPINSTALLER_CLI_ERROR_INSTALL_PACKAGE_IN_USE ((HRESULT)0x8A150101) diff --git a/src/AppInstallerSharedLib/Public/AppInstallerStrings.h b/src/AppInstallerSharedLib/Public/AppInstallerStrings.h @@ -268,4 +268,7 @@ namespace AppInstaller::Utility // Converts the given GUID value to a string. std::string ConvertGuidToString(const GUID& value); + + // Converts the input string to a DWORD value using std::stoul and returns a boolean value based on the resulting DWORD value. + bool IsDwordFlagSet(const std::string& value); }