winget-cli

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

UninstallFlow.cpp (21299B)


      1 // Copyright (c) Microsoft Corporation.
      2 // Licensed under the MIT License.
      3 #include "pch.h"
      4 #include "UninstallFlow.h"
      5 #include "InstallFlow.h"
      6 #include "WorkflowBase.h"
      7 #include "DependenciesFlow.h"
      8 #include "ShellExecuteInstallerHandler.h"
      9 #include "AppInstallerMsixInfo.h"
     10 #include "PortableFlow.h"
     11 #include <AppInstallerDeployment.h>
     12 #include <AppInstallerSynchronization.h>
     13 #include <winget/Runtime.h>
     14 #include <winget/PackageVersionSelection.h>
     15 
     16 using namespace AppInstaller::CLI::Execution;
     17 using namespace AppInstaller::Manifest;
     18 using namespace AppInstaller::Msix;
     19 using namespace AppInstaller::Repository;
     20 using namespace AppInstaller::Registry;
     21 using namespace AppInstaller::CLI::Portable;
     22 using namespace AppInstaller::Utility::literals;
     23 
     24 namespace AppInstaller::CLI::Workflow
     25 {
     26     namespace
     27     {
     28         // Helper for RecordUninstall
     29         struct UninstallCorrelatedSources
     30         {
     31             struct Item
     32             {
     33                 Utility::LocIndString Identifier;
     34                 Source FromSource;
     35                 std::string SourceIdentifier;
     36             };
     37 
     38             void AddIfRemoteAndNotPresent(Source&& source, const Utility::LocIndString& identifier)
     39             {
     40                 const auto details = source.GetDetails();
     41                 if (!source.ContainsAvailablePackages())
     42                 {
     43                     return;
     44                 }
     45 
     46                 for (const auto& item : Items)
     47                 {
     48                     if (item.SourceIdentifier == details.Identifier)
     49                     {
     50                         return;
     51                     }
     52                 }
     53 
     54                 Items.emplace_back(Item{ identifier, std::move(source), details.Identifier });
     55             }
     56 
     57             void AddIfRemoteAndNotPresent(const std::shared_ptr<IPackageVersion>& packageVersion)
     58             {
     59                 AddIfRemoteAndNotPresent(packageVersion->GetSource(), packageVersion->GetProperty(PackageVersionProperty::Id));
     60             }
     61 
     62             void AddIfRemoteAndNotPresent(const std::shared_ptr<IPackage>& package)
     63             {
     64                 AddIfRemoteAndNotPresent(package->GetSource(), package->GetProperty(PackageProperty::Id));
     65             }
     66 
     67             std::vector<Item> Items;
     68         };
     69     }
     70 
     71     void UninstallSinglePackage(Execution::Context& context)
     72     {
     73         std::shared_ptr<ICompositePackage> package = context.Get<Execution::Data::Package>();
     74         std::shared_ptr<IPackage> installed = package->GetInstalled();
     75         std::vector<Repository::PackageVersionKey> installedVersionKeys;
     76         if (installed)
     77         {
     78             installedVersionKeys = installed->GetVersionKeys();
     79         }
     80 
     81         // Handle multiple installed versions when we have been told to uninstall all of them.
     82         if (installedVersionKeys.size() > 1 && context.Args.Contains(Execution::Args::Type::AllVersions))
     83         {
     84             bool allSucceeded = true;
     85             size_t versionsCount = installedVersionKeys.size();
     86             size_t versionsProgress = 0;
     87 
     88             for (const auto& key : installedVersionKeys)
     89             {
     90                 context.Reporter.Info() << '(' << ++versionsProgress << '/' << versionsCount << ") "_liv;
     91 
     92                 // We want to do best effort to uninstall all versions regardless of previous failures
     93                 auto subContextPtr = context.CreateSubContext();
     94                 Execution::Context& uninstallContext = *subContextPtr;
     95                 auto previousThreadGlobals = uninstallContext.SetForCurrentThread();
     96 
     97                 uninstallContext.Add<Execution::Data::Package>(package);
     98                 uninstallContext.Add<Execution::Data::InstalledPackageVersion>(installed->GetVersion(key));
     99 
    100                 // Prevent individual exceptions from breaking out of the loop
    101                 try
    102                 {
    103                     uninstallContext <<
    104                         Workflow::UninstallSinglePackageVersion;
    105                 }
    106                 catch (...)
    107                 {
    108                     uninstallContext.SetTerminationHR(Workflow::HandleException(uninstallContext, std::current_exception()));
    109                 }
    110 
    111                 uninstallContext.Reporter.Info() << std::endl;
    112 
    113                 if (uninstallContext.IsTerminated())
    114                 {
    115                     if (context.IsTerminated() && context.GetTerminationHR() == E_ABORT)
    116                     {
    117                         // This means that the subcontext being terminated is due to an overall abort
    118                         context.Reporter.Info() << Resource::String::Cancelled << std::endl;
    119                         return;
    120                     }
    121 
    122                     allSucceeded = false;
    123                 }
    124             }
    125 
    126             if (!allSucceeded)
    127             {
    128                 AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_MULTIPLE_UNINSTALL_FAILED);
    129             }
    130         }
    131         else if (installedVersionKeys.size() > 1 && !context.Args.Contains(Execution::Args::Type::TargetVersion))
    132         {
    133             context.Reporter.Error() << Resource::String::UninstallFailedDueToMultipleVersions << std::endl;
    134             AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_MULTIPLE_APPLICATIONS_FOUND);
    135         }
    136         else
    137         {
    138             context <<
    139                 Workflow::GetInstalledPackageVersion <<
    140                 Workflow::UninstallSinglePackageVersion;
    141         }
    142     }
    143 
    144     void UninstallSinglePackageVersion(Execution::Context& context)
    145     {
    146         context <<
    147             Workflow::ReportInstalledPackageVersionIdentity <<
    148             Workflow::EnsureSupportForUninstall <<
    149             Workflow::GetUninstallInfo <<
    150             Workflow::GetDependenciesInfoForUninstall <<
    151             Workflow::ReportDependencies(Resource::String::UninstallCommandReportDependencies) <<
    152             Workflow::ReportExecutionStage(ExecutionStage::Execution) <<
    153             Workflow::ExecuteUninstaller <<
    154             Workflow::ReportExecutionStage(ExecutionStage::PostExecution) <<
    155             Workflow::RecordUninstall;
    156     }
    157 
    158     void UninstallMultiplePackages(Execution::Context& context)
    159     {
    160         bool allSucceeded = true;
    161         size_t packagesCount = context.Get<Execution::Data::PackageSubContexts>().size();
    162         size_t packagesProgress = 0;
    163 
    164         for (auto& packageContext : context.Get<Execution::Data::PackageSubContexts>())
    165         {
    166             packagesProgress++;
    167             context.Reporter.Info() << '(' << packagesProgress << '/' << packagesCount << ") "_liv;
    168 
    169             // We want to do best effort to uninstall all packages regardless of previous failures
    170             Execution::Context& uninstallContext = *packageContext;
    171             auto previousThreadGlobals = uninstallContext.SetForCurrentThread();
    172 
    173             // Prevent individual exceptions from breaking out of the loop
    174             try
    175             {
    176                 uninstallContext <<
    177                     Workflow::ReportPackageIdentity <<
    178                     Workflow::UninstallSinglePackage;
    179             }
    180             catch (...)
    181             {
    182                 uninstallContext.SetTerminationHR(Workflow::HandleException(uninstallContext, std::current_exception()));
    183             }
    184 
    185             uninstallContext.Reporter.Info() << std::endl;
    186 
    187             if (uninstallContext.IsTerminated())
    188             {
    189                 if (context.IsTerminated() && context.GetTerminationHR() == E_ABORT)
    190                 {
    191                     // This means that the subcontext being terminated is due to an overall abort
    192                     context.Reporter.Info() << Resource::String::Cancelled << std::endl;
    193                     return;
    194                 }
    195 
    196                 allSucceeded = false;
    197             }
    198         }
    199 
    200         if (!allSucceeded)
    201         {
    202             AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_MULTIPLE_UNINSTALL_FAILED);
    203         }
    204     }
    205 
    206     void GetUninstallInfo(Execution::Context& context)
    207     {
    208         auto installedPackageVersion = context.Get<Execution::Data::InstalledPackageVersion>();
    209 
    210         if (!installedPackageVersion)
    211         {
    212             AICLI_LOG(CLI, Verbose, << "No installed package version; cannot get uninstall information.");
    213             return;
    214         }
    215 
    216         const std::string installedTypeString = installedPackageVersion->GetMetadata()[PackageVersionMetadata::InstalledType];
    217         switch (ConvertToInstallerTypeEnum(installedTypeString))
    218         {
    219         case InstallerTypeEnum::Exe:
    220         case InstallerTypeEnum::Burn:
    221         case InstallerTypeEnum::Inno:
    222         case InstallerTypeEnum::Nullsoft:
    223         {
    224             IPackageVersion::Metadata packageMetadata = installedPackageVersion->GetMetadata();
    225 
    226             // Default to silent unless it is not present or interactivity is requested
    227             auto uninstallCommandItr = packageMetadata.find(PackageVersionMetadata::SilentUninstallCommand);
    228             if (uninstallCommandItr == packageMetadata.end() || context.Args.Contains(Execution::Args::Type::Interactive))
    229             {
    230                 auto interactiveItr = packageMetadata.find(PackageVersionMetadata::StandardUninstallCommand);
    231                 if (interactiveItr != packageMetadata.end())
    232                 {
    233                     uninstallCommandItr = interactiveItr;
    234                 }
    235             }
    236 
    237             if (uninstallCommandItr == packageMetadata.end())
    238             {
    239                 context.Reporter.Error() << Resource::String::NoUninstallInfoFound << std::endl;
    240                 AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_NO_UNINSTALL_INFO_FOUND);
    241             }
    242 
    243             context.Add<Execution::Data::UninstallString>(uninstallCommandItr->second);
    244             break;
    245         }
    246         case InstallerTypeEnum::Msi:
    247         case InstallerTypeEnum::Wix:
    248         {
    249             // Uninstall strings for MSI don't include UI level (/q) needed to avoid interactivity,
    250             // so we handle them differently.
    251             auto productCodes = installedPackageVersion->GetMultiProperty(PackageVersionMultiProperty::ProductCode);
    252             if (productCodes.empty())
    253             {
    254                 context.Reporter.Error() << Resource::String::NoUninstallInfoFound << std::endl;
    255                 AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_NO_UNINSTALL_INFO_FOUND);
    256             }
    257 
    258             context.Add<Execution::Data::ProductCodes>(std::move(productCodes));
    259             break;
    260         }
    261         case InstallerTypeEnum::Msix:
    262         case InstallerTypeEnum::MSStore:
    263         {
    264             auto packageFamilyNames = installedPackageVersion->GetMultiProperty(PackageVersionMultiProperty::PackageFamilyName);
    265             if (packageFamilyNames.empty())
    266             {
    267                 context.Reporter.Error() << Resource::String::NoUninstallInfoFound << std::endl;
    268                 AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_NO_UNINSTALL_INFO_FOUND);
    269             }
    270 
    271             context.Add<Execution::Data::PackageFamilyNames>(packageFamilyNames);
    272             break;
    273         }
    274         case InstallerTypeEnum::Portable:
    275         {
    276             auto productCodes = installedPackageVersion->GetMultiProperty(PackageVersionMultiProperty::ProductCode);
    277             if (productCodes.empty())
    278             {
    279                 context.Reporter.Error() << Resource::String::NoUninstallInfoFound << std::endl;
    280                 AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_NO_UNINSTALL_INFO_FOUND);
    281             }
    282 
    283             const std::string installedScope = context.Get<Execution::Data::InstalledPackageVersion>()->GetMetadata()[Repository::PackageVersionMetadata::InstalledScope];
    284             const std::string installedArch = context.Get<Execution::Data::InstalledPackageVersion>()->GetMetadata()[Repository::PackageVersionMetadata::InstalledArchitecture];
    285             
    286             PortableInstaller portableInstaller = PortableInstaller(
    287                 Manifest::ConvertToScopeEnum(installedScope),
    288                 Utility::ConvertToArchitectureEnum(installedArch),
    289                 productCodes[0]);
    290             context.Add<Execution::Data::PortableInstaller>(std::move(portableInstaller));
    291             break;
    292         }
    293         default:
    294             THROW_HR(HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED));
    295         }
    296     }
    297 
    298     void ExecuteUninstaller(Execution::Context& context)
    299     {
    300         auto installedPackageVersion = context.Get<Execution::Data::InstalledPackageVersion>();
    301 
    302         if (!installedPackageVersion)
    303         {
    304             AICLI_LOG(CLI, Verbose, << "No installed package version; cannot uninstall.");
    305             return;
    306         }
    307 
    308         const std::string installedTypeString = installedPackageVersion->GetMetadata()[PackageVersionMetadata::InstalledType];
    309         InstallerTypeEnum installerType = ConvertToInstallerTypeEnum(installedTypeString);
    310 
    311         Synchronization::CrossProcessInstallLock lock;
    312         if (!ExemptFromSingleInstallLocking(installerType))
    313         {
    314             // Acquire install lock; if the operation is cancelled it will return false so we will also return.
    315             if (!context.Reporter.ExecuteWithProgress([&](IProgressCallback& callback)
    316                 {
    317                     callback.SetProgressMessage(Resource::String::InstallWaitingOnAnother());
    318                     return lock.Acquire(callback);
    319                 }))
    320             {
    321                 AICLI_LOG(CLI, Info, << "Abandoning attempt to acquire install lock due to cancellation");
    322                 return;
    323             }
    324         }
    325 
    326         switch (installerType)
    327         {
    328         case InstallerTypeEnum::Exe:
    329         case InstallerTypeEnum::Burn:
    330         case InstallerTypeEnum::Inno:
    331         case InstallerTypeEnum::Nullsoft:
    332             context <<
    333                 Workflow::ShellExecuteUninstallImpl <<
    334                 ReportUninstallerResult("UninstallString", APPINSTALLER_CLI_ERROR_EXEC_UNINSTALL_COMMAND_FAILED);
    335             break;
    336         case InstallerTypeEnum::Msi:
    337         case InstallerTypeEnum::Wix:
    338             context <<
    339                 Workflow::ShellExecuteMsiExecUninstall <<
    340                 ReportUninstallerResult("MsiExec", APPINSTALLER_CLI_ERROR_EXEC_UNINSTALL_COMMAND_FAILED);
    341             break;
    342         case InstallerTypeEnum::Msix:
    343         case InstallerTypeEnum::MSStore:
    344             context << Workflow::MsixUninstall;
    345             break;
    346         case InstallerTypeEnum::Portable:
    347             context <<
    348                 Workflow::PortableUninstallImpl <<
    349                 ReportUninstallerResult("PortableUninstall"sv, APPINSTALLER_CLI_ERROR_PORTABLE_UNINSTALL_FAILED, true);
    350             break;
    351         default:
    352         THROW_HR(HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED));
    353         }
    354     }
    355 
    356     void MsixUninstall(Execution::Context& context)
    357     {
    358         bool isMachineScope = Manifest::ConvertToScopeEnum(context.Args.GetArg(Execution::Args::Type::InstallScope)) == Manifest::ScopeEnum::Machine;
    359 
    360         // TODO: There was a bug in deployment api if deprovision api was called in packaged context.
    361         // Remove this check when the OS bug is fixed and back ported.
    362         if (isMachineScope && Runtime::IsRunningInPackagedContext())
    363         {
    364             context.Reporter.Error() << Resource::String::InstallFlowReturnCodeSystemNotSupported << std::endl;
    365             context.Add<Execution::Data::OperationReturnCode>(static_cast<DWORD>(APPINSTALLER_CLI_ERROR_INSTALL_SYSTEM_NOT_SUPPORTED));
    366             AICLI_LOG(CLI, Error, << "Device wide uninstall for msix type is not supported in packaged context.");
    367             AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_INSTALL_SYSTEM_NOT_SUPPORTED);
    368         }
    369 
    370         const auto& packageFamilyNames = context.Get<Execution::Data::PackageFamilyNames>();
    371         context.Reporter.Info() << Resource::String::UninstallFlowStartingPackageUninstall << std::endl;
    372 
    373         for (const auto& packageFamilyName : packageFamilyNames)
    374         {
    375             auto packageFullName = Msix::GetPackageFullNameFromFamilyName(packageFamilyName);
    376             if (!packageFullName.has_value())
    377             {
    378                 AICLI_LOG(CLI, Warning, << "No package found with family name: " << packageFamilyName);
    379                 continue;
    380             }
    381 
    382             AICLI_LOG(CLI, Info, << "Removing MSIX package: " << packageFullName.value());
    383             try
    384             {
    385                 if (isMachineScope)
    386                 {
    387                     context.Reporter.ExecuteWithProgress(std::bind(Deployment::RemovePackageMachineScope, packageFamilyName, packageFullName.value(), std::placeholders::_1));
    388                 }
    389                 else
    390                 {
    391                     context.Reporter.ExecuteWithProgress(std::bind(Deployment::RemovePackage, packageFullName.value(), winrt::Windows::Management::Deployment::RemovalOptions::None, std::placeholders::_1));
    392                 }
    393             }
    394             catch (const wil::ResultException& re)
    395             {
    396                 context.Add<Execution::Data::OperationReturnCode>(re.GetErrorCode());
    397                 context << ReportUninstallerResult("MSIXUninstall"sv, re.GetErrorCode(), /* isHResult */ true);
    398                 return;
    399             }
    400         }
    401 
    402         context.Reporter.Info() << Resource::String::UninstallFlowUninstallSuccess << std::endl;
    403     }
    404 
    405     void RecordUninstall(Context& context)
    406     {
    407         // In order to report an uninstall to every correlated tracking catalog, we first need to find them all.
    408         auto package = context.Get<Data::Package>();
    409         UninstallCorrelatedSources correlatedSources;
    410 
    411         // Start with the installed version
    412         correlatedSources.AddIfRemoteAndNotPresent(GetInstalledVersion(package));
    413 
    414         // Then look through all available versions
    415         for (const auto& availablePackage : package->GetAvailable())
    416         {
    417             correlatedSources.AddIfRemoteAndNotPresent(availablePackage);
    418         }
    419 
    420         // Finally record the uninstall for each found value
    421         for (const auto& item : correlatedSources.Items)
    422         {
    423             auto trackingCatalog = item.FromSource.GetTrackingCatalog();
    424             trackingCatalog.RecordUninstall(item.Identifier);
    425         }
    426     }
    427 
    428     void ReportUninstallerResult::operator()(Execution::Context& context) const
    429     {
    430         DWORD uninstallResult = context.Get<Execution::Data::OperationReturnCode>();
    431         if (uninstallResult != 0)
    432         {
    433             const auto installedPackageVersion = context.Get<Execution::Data::InstalledPackageVersion>();
    434             Logging::Telemetry().LogUninstallerFailure(
    435                 installedPackageVersion->GetProperty(PackageVersionProperty::Id),
    436                 installedPackageVersion->GetProperty(PackageVersionProperty::Version),
    437                 m_uninstallerType,
    438                 uninstallResult);
    439 
    440             if (m_isHResult)
    441             {
    442                 context.Reporter.Error()
    443                     << Resource::String::UninstallFailedWithCode(Utility::LocIndView{ GetUserPresentableMessage(uninstallResult) })
    444                     << std::endl;
    445             }
    446             else
    447             {
    448                 context.Reporter.Error()
    449                     << Resource::String::UninstallFailedWithCode(uninstallResult)
    450                     << std::endl;
    451             }
    452 
    453             // Show installer log path if exists
    454             if (context.Contains(Execution::Data::LogPath) && std::filesystem::exists(context.Get<Execution::Data::LogPath>()))
    455             {
    456                 auto installerLogPath = Utility::LocIndString{ context.Get<Execution::Data::LogPath>().u8string() };
    457                 context.Reporter.Info() << Resource::String::InstallerLogAvailable(installerLogPath) << std::endl;
    458             }
    459 
    460             AICLI_TERMINATE_CONTEXT(m_hr);
    461         }
    462         else
    463         {
    464             context.Reporter.Info() << Resource::String::UninstallFlowUninstallSuccess << std::endl;
    465         }
    466     }
    467 
    468     void EnsureSupportForUninstall(Execution::Context& context)
    469     {
    470         auto installedPackageVersion = context.Get<Execution::Data::InstalledPackageVersion>();
    471         const std::string installedTypeString = installedPackageVersion->GetMetadata()[PackageVersionMetadata::InstalledType];
    472         auto installedType = ConvertToInstallerTypeEnum(installedTypeString);
    473         if (installedType == InstallerTypeEnum::Portable)
    474         {
    475             const std::string installedScope = installedPackageVersion->GetMetadata()[Repository::PackageVersionMetadata::InstalledScope];
    476             if (Manifest::ConvertToScopeEnum(installedScope) == Manifest::ScopeEnum::Machine)
    477             {
    478                 context << EnsureRunningAsAdmin;
    479             }
    480         }
    481         else if (installedType == InstallerTypeEnum::Msix)
    482         {
    483             if (Manifest::ConvertToScopeEnum(context.Args.GetArg(Execution::Args::Type::InstallScope)) == Manifest::ScopeEnum::Machine)
    484             {
    485                 context << EnsureRunningAsAdmin;
    486             }
    487         }
    488     }
    489 }