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 }