InstallFlow.cpp (45042B)
1 // Copyright (c) Microsoft Corporation. 2 // Licensed under the MIT License. 3 #include "pch.h" 4 #include "InstallFlow.h" 5 #include "DownloadFlow.h" 6 #include "UninstallFlow.h" 7 #include "UpdateFlow.h" 8 #include "ResumeFlow.h" 9 #include "ShowFlow.h" 10 #include "Resources.h" 11 #include "ShellExecuteInstallerHandler.h" 12 #include "MSStoreInstallerHandler.h" 13 #include "MsiInstallFlow.h" 14 #include "ArchiveFlow.h" 15 #include "PortableFlow.h" 16 #include "WorkflowBase.h" 17 #include "DependenciesFlow.h" 18 #include "PromptFlow.h" 19 #include "SourceFlow.h" 20 #include <AppInstallerMsixInfo.h> 21 #include <AppInstallerDeployment.h> 22 #include <AppInstallerSynchronization.h> 23 #include <Argument.h> 24 #include <Command.h> 25 #include <winget/ARPCorrelation.h> 26 #include <winget/Archive.h> 27 #include <winget/PathVariable.h> 28 #include <winget/Runtime.h> 29 30 using namespace winrt::Windows::Foundation; 31 using namespace winrt::Windows::Foundation::Collections; 32 using namespace winrt::Windows::Management::Deployment; 33 using namespace AppInstaller::CLI::Execution; 34 using namespace AppInstaller::Manifest; 35 using namespace AppInstaller::Repository; 36 using namespace AppInstaller::Registry::Environment; 37 using namespace AppInstaller::Settings; 38 using namespace AppInstaller::Utility; 39 using namespace AppInstaller::Utility::literals; 40 41 namespace AppInstaller::CLI::Workflow 42 { 43 namespace 44 { 45 bool MightWriteToARP(InstallerTypeEnum type) 46 { 47 switch (type) 48 { 49 case InstallerTypeEnum::Exe: 50 case InstallerTypeEnum::Burn: 51 case InstallerTypeEnum::Inno: 52 case InstallerTypeEnum::Msi: 53 case InstallerTypeEnum::Nullsoft: 54 case InstallerTypeEnum::Wix: 55 return true; 56 default: 57 return false; 58 } 59 } 60 61 bool ShouldUseDirectMSIInstall(InstallerTypeEnum type, bool isSilentInstall) 62 { 63 switch (type) 64 { 65 case InstallerTypeEnum::Msi: 66 case InstallerTypeEnum::Wix: 67 return isSilentInstall || ExperimentalFeature::IsEnabled(ExperimentalFeature::Feature::DirectMSI); 68 default: 69 return false; 70 } 71 } 72 73 bool ShouldErrorForUnsupportedArgument(UnsupportedArgumentEnum arg) 74 { 75 switch (arg) 76 { 77 case UnsupportedArgumentEnum::Location: 78 return true; 79 default: 80 return false; 81 } 82 } 83 84 Execution::Args::Type GetUnsupportedArgumentType(UnsupportedArgumentEnum unsupportedArgument) 85 { 86 Execution::Args::Type execArg; 87 88 switch (unsupportedArgument) 89 { 90 case UnsupportedArgumentEnum::Log: 91 execArg = Execution::Args::Type::Log; 92 break; 93 case UnsupportedArgumentEnum::Location: 94 execArg = Execution::Args::Type::InstallLocation; 95 break; 96 default: 97 THROW_HR(E_UNEXPECTED); 98 } 99 100 return execArg; 101 } 102 103 struct ExpectedReturnCode 104 { 105 ExpectedReturnCode(ExpectedReturnCodeEnum installerReturnCode, HRESULT hr, Resource::StringId message) : 106 InstallerReturnCode(installerReturnCode), HResult(hr), Message(message) {} 107 108 static ExpectedReturnCode GetExpectedReturnCode(ExpectedReturnCodeEnum returnCode) 109 { 110 switch (returnCode) 111 { 112 case ExpectedReturnCodeEnum::PackageInUse: 113 return ExpectedReturnCode(returnCode, APPINSTALLER_CLI_ERROR_INSTALL_PACKAGE_IN_USE, Resource::String::InstallFlowReturnCodePackageInUse); 114 case ExpectedReturnCodeEnum::PackageInUseByApplication: 115 return ExpectedReturnCode(returnCode, APPINSTALLER_CLI_ERROR_INSTALL_PACKAGE_IN_USE_BY_APPLICATION, Resource::String::InstallFlowReturnCodePackageInUseByApplication); 116 case ExpectedReturnCodeEnum::InstallInProgress: 117 return ExpectedReturnCode(returnCode, APPINSTALLER_CLI_ERROR_INSTALL_INSTALL_IN_PROGRESS, Resource::String::InstallFlowReturnCodeInstallInProgress); 118 case ExpectedReturnCodeEnum::FileInUse: 119 return ExpectedReturnCode(returnCode, APPINSTALLER_CLI_ERROR_INSTALL_FILE_IN_USE, Resource::String::InstallFlowReturnCodeFileInUse); 120 case ExpectedReturnCodeEnum::MissingDependency: 121 return ExpectedReturnCode(returnCode, APPINSTALLER_CLI_ERROR_INSTALL_MISSING_DEPENDENCY, Resource::String::InstallFlowReturnCodeMissingDependency); 122 case ExpectedReturnCodeEnum::DiskFull: 123 return ExpectedReturnCode(returnCode, APPINSTALLER_CLI_ERROR_INSTALL_DISK_FULL, Resource::String::InstallFlowReturnCodeDiskFull); 124 case ExpectedReturnCodeEnum::InsufficientMemory: 125 return ExpectedReturnCode(returnCode, APPINSTALLER_CLI_ERROR_INSTALL_INSUFFICIENT_MEMORY, Resource::String::InstallFlowReturnCodeInsufficientMemory); 126 case ExpectedReturnCodeEnum::InvalidParameter: 127 return ExpectedReturnCode(returnCode, APPINSTALLER_CLI_ERROR_INSTALL_INVALID_PARAMETER, Resource::String::InstallFlowReturnCodeInvalidParameter); 128 case ExpectedReturnCodeEnum::NoNetwork: 129 return ExpectedReturnCode(returnCode, APPINSTALLER_CLI_ERROR_INSTALL_NO_NETWORK, Resource::String::InstallFlowReturnCodeNoNetwork); 130 case ExpectedReturnCodeEnum::ContactSupport: 131 return ExpectedReturnCode(returnCode, APPINSTALLER_CLI_ERROR_INSTALL_CONTACT_SUPPORT, Resource::String::InstallFlowReturnCodeContactSupport); 132 case ExpectedReturnCodeEnum::RebootRequiredToFinish: 133 return ExpectedReturnCode(returnCode, APPINSTALLER_CLI_ERROR_INSTALL_REBOOT_REQUIRED_TO_FINISH, Resource::String::InstallFlowReturnCodeRebootRequiredToFinish); 134 case ExpectedReturnCodeEnum::RebootRequiredForInstall: 135 return ExpectedReturnCode(returnCode, APPINSTALLER_CLI_ERROR_INSTALL_REBOOT_REQUIRED_FOR_INSTALL, Resource::String::InstallFlowReturnCodeRebootRequiredForInstall); 136 case ExpectedReturnCodeEnum::RebootInitiated: 137 return ExpectedReturnCode(returnCode, APPINSTALLER_CLI_ERROR_INSTALL_REBOOT_INITIATED, Resource::String::InstallFlowReturnCodeRebootInitiated); 138 case ExpectedReturnCodeEnum::CancelledByUser: 139 return ExpectedReturnCode(returnCode, APPINSTALLER_CLI_ERROR_INSTALL_CANCELLED_BY_USER, Resource::String::InstallFlowReturnCodeCancelledByUser); 140 case ExpectedReturnCodeEnum::AlreadyInstalled: 141 return ExpectedReturnCode(returnCode, APPINSTALLER_CLI_ERROR_INSTALL_ALREADY_INSTALLED, Resource::String::InstallFlowReturnCodeAlreadyInstalled); 142 case ExpectedReturnCodeEnum::Downgrade: 143 return ExpectedReturnCode(returnCode, APPINSTALLER_CLI_ERROR_INSTALL_DOWNGRADE, Resource::String::InstallFlowReturnCodeDowngrade); 144 case ExpectedReturnCodeEnum::BlockedByPolicy: 145 return ExpectedReturnCode(returnCode, APPINSTALLER_CLI_ERROR_INSTALL_BLOCKED_BY_POLICY, Resource::String::InstallFlowReturnCodeBlockedByPolicy); 146 case ExpectedReturnCodeEnum::SystemNotSupported: 147 return ExpectedReturnCode(returnCode, APPINSTALLER_CLI_ERROR_INSTALL_SYSTEM_NOT_SUPPORTED, Resource::String::InstallFlowReturnCodeSystemNotSupported); 148 case ExpectedReturnCodeEnum::Custom: 149 return ExpectedReturnCode(returnCode, APPINSTALLER_CLI_ERROR_INSTALL_CUSTOM_ERROR, Resource::String::InstallFlowReturnCodeCustomError); 150 default: 151 THROW_HR(E_UNEXPECTED); 152 } 153 } 154 155 ExpectedReturnCodeEnum InstallerReturnCode; 156 HRESULT HResult; 157 Resource::StringId Message; 158 }; 159 } 160 161 namespace details 162 { 163 // Runs the installer via ShellExecute. 164 // Required Args: None 165 // Inputs: Installer, InstallerPath 166 // Outputs: None 167 void ShellExecuteInstall(Execution::Context& context) 168 { 169 context << 170 GetInstallerArgs << 171 ShellExecuteInstallImpl << 172 ReportInstallerResult("ShellExecute"sv, APPINSTALLER_CLI_ERROR_SHELLEXEC_INSTALL_FAILED); 173 } 174 175 // Runs an MSI installer directly via MSI APIs. 176 // Required Args: None 177 // Inputs: Installer, InstallerPath 178 // Outputs: None 179 void DirectMSIInstall(Execution::Context& context) 180 { 181 context << 182 GetInstallerArgs << 183 DirectMSIInstallImpl << 184 ReportInstallerResult("MsiInstallProduct"sv, APPINSTALLER_CLI_ERROR_MSI_INSTALL_FAILED); 185 } 186 187 // Deploys the MSIX. 188 // Required Args: None 189 // Inputs: Manifest?, Installer || InstallerPath 190 // Outputs: None 191 void MsixInstall(Execution::Context& context) 192 { 193 std::string uri; 194 Deployment::Options deploymentOptions; 195 if (context.Contains(Execution::Data::InstallerPath)) 196 { 197 uri = context.Get<Execution::Data::InstallerPath>().u8string(); 198 } 199 else 200 { 201 uri = context.Get<Execution::Data::Installer>()->Url; 202 deploymentOptions.ExpectedDigests = context.Get<Execution::Data::MsixDigests>(); 203 } 204 205 deploymentOptions.SkipReputationCheck = WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::InstallerTrusted); 206 207 bool isMachineScope = Manifest::ConvertToScopeEnum(context.Args.GetArg(Execution::Args::Type::InstallScope)) == Manifest::ScopeEnum::Machine; 208 209 // TODO: There was a bug in deployment api if provision api was called in packaged context. 210 // Remove this check when the OS bug is fixed and back ported. 211 if (isMachineScope && Runtime::IsRunningInPackagedContext()) 212 { 213 context.Reporter.Error() << Resource::String::InstallFlowReturnCodeSystemNotSupported << std::endl; 214 context.Add<Execution::Data::OperationReturnCode>(static_cast<DWORD>(APPINSTALLER_CLI_ERROR_INSTALL_SYSTEM_NOT_SUPPORTED)); 215 AICLI_LOG(CLI, Error, << "Device wide install for msix type is not supported in packaged context."); 216 AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_INSTALL_SYSTEM_NOT_SUPPORTED); 217 } 218 219 context.Reporter.Info() << Resource::String::InstallFlowStartingPackageInstall << std::endl; 220 221 bool registrationDeferred = false; 222 223 try 224 { 225 registrationDeferred = context.Reporter.ExecuteWithProgress([&](IProgressCallback& callback) 226 { 227 if (isMachineScope) 228 { 229 return Deployment::AddPackageMachineScope(uri, deploymentOptions, callback); 230 } 231 else 232 { 233 return Deployment::AddPackageWithDeferredFallback(uri, deploymentOptions, callback); 234 } 235 }); 236 } 237 catch (const wil::ResultException& re) 238 { 239 context.Add<Execution::Data::OperationReturnCode>(re.GetErrorCode()); 240 context << ReportInstallerResult("MSIX"sv, re.GetErrorCode(), /* isHResult */ true); 241 return; 242 } 243 244 if (registrationDeferred) 245 { 246 context.Reporter.Warn() << Resource::String::InstallFlowRegistrationDeferred << std::endl; 247 } 248 else 249 { 250 context.Reporter.Info() << Resource::String::InstallFlowInstallSuccess << std::endl; 251 } 252 } 253 254 // Runs the flow for installing a Portable package. 255 // Required Args: None 256 // Inputs: Installer, InstallerPath 257 // Outputs: None 258 void PortableInstall(Execution::Context& context) 259 { 260 context << 261 InitializePortableInstaller << 262 VerifyPackageAndSourceMatch << 263 PortableInstallImpl << 264 ReportInstallerResult("Portable"sv, APPINSTALLER_CLI_ERROR_PORTABLE_INSTALL_FAILED, true); 265 } 266 267 // Runs the flow for installing a package from an archive. 268 // Required Args: None 269 // Inputs: Installer, InstallerPath, Manifest 270 // Outputs: None 271 void ArchiveInstall(Execution::Context& context) 272 { 273 context << 274 ScanArchiveFromLocalManifest << 275 ExtractFilesFromArchive << 276 VerifyAndSetNestedInstaller << 277 ExecuteInstallerForType(context.Get<Execution::Data::Installer>().value().NestedInstallerType); 278 } 279 } 280 281 bool ExemptFromSingleInstallLocking(InstallerTypeEnum type) 282 { 283 switch (type) 284 { 285 // MSStore installs are always MSIX based; MSIX installs are safe to run in parallel. 286 case InstallerTypeEnum::Msix: 287 case InstallerTypeEnum::MSStore: 288 return true; 289 default: 290 return false; 291 } 292 } 293 294 void EnsureApplicableInstaller(Execution::Context& context) 295 { 296 const auto& installer = context.Get<Execution::Data::Installer>(); 297 298 if (!installer.has_value()) 299 { 300 context.Reporter.Error() << Resource::String::NoApplicableInstallers << std::endl; 301 AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_NO_APPLICABLE_INSTALLER); 302 } 303 304 context << 305 EnsureSupportForDownload << 306 EnsureSupportForInstall; 307 } 308 309 void CheckForUnsupportedArgs(Execution::Context& context) 310 { 311 bool messageDisplayed = false; 312 const auto& unsupportedArgs = context.Get<Execution::Data::Installer>()->UnsupportedArguments; 313 for (auto unsupportedArg : unsupportedArgs) 314 { 315 const auto& unsupportedArgType = GetUnsupportedArgumentType(unsupportedArg); 316 if (context.Args.Contains(unsupportedArgType)) 317 { 318 if (!messageDisplayed) 319 { 320 context.Reporter.Warn() << Resource::String::UnsupportedArgument << std::endl; 321 messageDisplayed = true; 322 } 323 324 const auto& executingCommand = context.GetExecutingCommand(); 325 if (executingCommand != nullptr) 326 { 327 const auto& commandArguments = executingCommand->GetArguments(); 328 for (const auto& argument : commandArguments) 329 { 330 if (unsupportedArgType == argument.ExecArgType()) 331 { 332 const auto& usageString = argument.GetUsageString(); 333 if (ShouldErrorForUnsupportedArgument(unsupportedArg)) 334 { 335 context.Reporter.Error() << usageString << std::endl; 336 AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_UNSUPPORTED_ARGUMENT); 337 } 338 else 339 { 340 context.Reporter.Warn() << usageString << std::endl; 341 break; 342 } 343 } 344 } 345 } 346 } 347 } 348 } 349 350 void ShowInstallationDisclaimer(Execution::Context& context) 351 { 352 auto installerType = context.Get<Execution::Data::Installer>().value().EffectiveInstallerType(); 353 354 if (installerType == InstallerTypeEnum::MSStore) 355 { 356 context.Reporter.Info() << Execution::PromptEmphasis << Resource::String::InstallationDisclaimerMSStore << std::endl; 357 } 358 else 359 { 360 context.Reporter.Info() << 361 Resource::String::InstallationDisclaimer1 << std::endl << 362 Resource::String::InstallationDisclaimer2 << std::endl; 363 } 364 } 365 366 void DisplayInstallationNotes(Execution::Context& context) 367 { 368 if (!Settings::User().Get<Settings::Setting::DisableInstallNotes>()) 369 { 370 const auto& manifest = context.Get<Execution::Data::Manifest>(); 371 auto installationNotes = manifest.CurrentLocalization.Get<AppInstaller::Manifest::Localization::InstallationNotes>(); 372 373 if (!installationNotes.empty()) 374 { 375 context.Reporter.Info() << Resource::String::Notes(installationNotes) << std::endl; 376 } 377 } 378 } 379 380 void ExecuteInstallerForType::operator()(Execution::Context& context) const 381 { 382 bool isUpdate = WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::InstallerExecutionUseUpdate); 383 UpdateBehaviorEnum updateBehavior = context.Get<Execution::Data::Installer>().value().UpdateBehavior; 384 bool doUninstallPrevious = isUpdate && (updateBehavior == UpdateBehaviorEnum::UninstallPrevious || context.Args.Contains(Execution::Args::Type::UninstallPrevious)); 385 386 Synchronization::CrossProcessInstallLock lock; 387 if (!ExemptFromSingleInstallLocking(m_installerType)) 388 { 389 // Acquire install lock; if the operation is cancelled it will return false so we will also return. 390 if (!context.Reporter.ExecuteWithProgress([&](IProgressCallback& callback) 391 { 392 callback.SetProgressMessage(Resource::String::InstallWaitingOnAnother()); 393 return lock.Acquire(callback); 394 })) 395 { 396 AICLI_LOG(CLI, Info, << "Abandoning attempt to acquire install lock due to cancellation"); 397 return; 398 } 399 } 400 401 switch (m_installerType) 402 { 403 case InstallerTypeEnum::Exe: 404 case InstallerTypeEnum::Burn: 405 case InstallerTypeEnum::Inno: 406 case InstallerTypeEnum::Msi: 407 case InstallerTypeEnum::Nullsoft: 408 case InstallerTypeEnum::Wix: 409 if (doUninstallPrevious) 410 { 411 context << 412 GetUninstallInfo << 413 ExecuteUninstaller; 414 context.ClearFlags(Execution::ContextFlag::InstallerExecutionUseUpdate); 415 } 416 if (ShouldUseDirectMSIInstall(m_installerType, context.Args.Contains(Execution::Args::Type::Silent))) 417 { 418 context << details::DirectMSIInstall; 419 } 420 else 421 { 422 context << details::ShellExecuteInstall; 423 } 424 break; 425 case InstallerTypeEnum::Msix: 426 context << details::MsixInstall; 427 break; 428 case InstallerTypeEnum::MSStore: 429 context << 430 EnsureStorePolicySatisfied << 431 (isUpdate ? MSStoreUpdate : MSStoreInstall); 432 break; 433 case InstallerTypeEnum::Portable: 434 if (doUninstallPrevious) 435 { 436 context << 437 GetUninstallInfo << 438 ExecuteUninstaller; 439 context.ClearFlags(Execution::ContextFlag::InstallerExecutionUseUpdate); 440 } 441 context << details::PortableInstall; 442 break; 443 case InstallerTypeEnum::Zip: 444 context << details::ArchiveInstall; 445 break; 446 default: 447 THROW_HR(HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED)); 448 } 449 } 450 451 void EnsureRunningAsAdminForMachineScopeInstall(Execution::Context& context) 452 { 453 // Admin is required for machine scope install for installer types like portable, msix and msstore. 454 auto installerType = context.Get<Execution::Data::Installer>().value().EffectiveInstallerType(); 455 456 if (Manifest::DoesInstallerTypeRequireAdminForMachineScopeInstall(installerType)) 457 { 458 Manifest::ScopeEnum scope = ConvertToScopeEnum(context.Args.GetArg(Execution::Args::Type::InstallScope)); 459 if (scope == Manifest::ScopeEnum::Machine) 460 { 461 context << Workflow::EnsureRunningAsAdmin; 462 } 463 } 464 } 465 466 void ExecuteInstaller(Execution::Context& context) 467 { 468 context << Workflow::ExecuteInstallerForType(context.Get<Execution::Data::Installer>().value().BaseInstallerType); 469 } 470 471 void ReportInstallerResult::operator()(Execution::Context& context) const 472 { 473 bool isRepair = WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::InstallerExecutionUseRepair); 474 475 DWORD installResult = context.Get<Execution::Data::OperationReturnCode>(); 476 const auto& additionalSuccessCodes = context.Get<Execution::Data::Installer>()->InstallerSuccessCodes; 477 if (installResult != 0 && (std::find(additionalSuccessCodes.begin(), additionalSuccessCodes.end(), installResult) == additionalSuccessCodes.end())) 478 { 479 HRESULT terminationHR = m_hr; 480 const auto& expectedReturnCodes = context.Get<Execution::Data::Installer>()->ExpectedReturnCodes; 481 auto expectedReturnCodeItr = expectedReturnCodes.find(installResult); 482 if (expectedReturnCodeItr != expectedReturnCodes.end() && expectedReturnCodeItr->second.ReturnResponseEnum != ExpectedReturnCodeEnum::Unknown) 483 { 484 auto returnCode = ExpectedReturnCode::GetExpectedReturnCode(expectedReturnCodeItr->second.ReturnResponseEnum); 485 terminationHR = returnCode.HResult; 486 487 switch (terminationHR) 488 { 489 case APPINSTALLER_CLI_ERROR_INSTALL_REBOOT_REQUIRED_TO_FINISH: 490 // REBOOT_REQUIRED_TO_FINISH is treated as a success since installation has completed but is pending a reboot. 491 context.SetFlags(ContextFlag::RebootRequired); 492 context.Reporter.Warn() << returnCode.Message << std::endl; 493 terminationHR = S_OK; 494 break; 495 case APPINSTALLER_CLI_ERROR_INSTALL_REBOOT_REQUIRED_FOR_INSTALL: 496 // REBOOT_REQUIRED_FOR_INSTALL is treated as an error since installation has not yet completed. 497 context.SetFlags(ContextFlag::RebootRequired); 498 // TODO: Add separate workflow to handle restart registration for resume. 499 context.SetFlags(ContextFlag::RegisterResume); 500 break; 501 } 502 503 if (FAILED(terminationHR)) 504 { 505 context.Reporter.Error() << returnCode.Message << std::endl; 506 const auto& returnResponseUrl = expectedReturnCodeItr->second.ReturnResponseUrl; 507 if (!returnResponseUrl.empty()) 508 { 509 context.Reporter.Error() << Resource::String::RelatedLink << ' ' << returnResponseUrl << std::endl; 510 } 511 } 512 } 513 514 if (FAILED(terminationHR)) 515 { 516 const auto& manifest = context.Get<Execution::Data::Manifest>(); 517 518 if (isRepair) 519 { 520 Logging::Telemetry().LogRepairFailure(manifest.Id, manifest.Version, m_installerType, installResult); 521 } 522 else 523 { 524 Logging::Telemetry().LogInstallerFailure(manifest.Id, manifest.Version, manifest.Channel, m_installerType, installResult); 525 } 526 527 if (m_isHResult) 528 { 529 context.Reporter.Error() 530 << Resource::String::InstallerFailedWithCode(Utility::LocIndView{ GetUserPresentableMessage(installResult) }) 531 << std::endl; 532 } 533 else 534 { 535 context.Reporter.Error() 536 << Resource::String::InstallerFailedWithCode(installResult) 537 << std::endl; 538 } 539 540 // Show installer log path if exists 541 if (context.Contains(Execution::Data::LogPath) && std::filesystem::exists(context.Get<Execution::Data::LogPath>())) 542 { 543 auto installerLogPath = Utility::LocIndString{ context.Get<Execution::Data::LogPath>().u8string() }; 544 context.Reporter.Info() << Resource::String::InstallerLogAvailable(installerLogPath) << std::endl; 545 } 546 547 AICLI_TERMINATE_CONTEXT(terminationHR); 548 } 549 } 550 else 551 { 552 if (isRepair) 553 { 554 context.Reporter.Info() << Resource::String::RepairFlowRepairSuccess << std::endl; 555 } 556 else 557 { 558 context.Reporter.Info() << Resource::String::InstallFlowInstallSuccess << std::endl; 559 } 560 } 561 } 562 563 void ReportIdentityAndInstallationDisclaimer(Execution::Context& context) 564 { 565 context << 566 Workflow::ReportManifestIdentityWithVersion() << 567 Workflow::ShowInstallationDisclaimer; 568 } 569 570 void InstallPackageInstaller(Execution::Context& context) 571 { 572 context << 573 Workflow::ReportExecutionStage(ExecutionStage::PreExecution) << 574 Workflow::SnapshotARPEntries << 575 Workflow::ReportExecutionStage(ExecutionStage::Execution) << 576 Workflow::ExecuteInstaller << 577 Workflow::ReportExecutionStage(ExecutionStage::PostExecution) << 578 Workflow::ReportARPChanges << 579 Workflow::RecordInstall << 580 Workflow::ForceInstalledCacheUpdate << 581 Workflow::RemoveInstaller << 582 Workflow::DisplayInstallationNotes; 583 } 584 585 void InstallDependencies(Execution::Context& context) 586 { 587 using Flags = ProcessMultiplePackages::Flags; 588 589 if (Settings::User().Get<Settings::Setting::InstallSkipDependencies>() || context.Args.Contains(Execution::Args::Type::SkipDependencies)) 590 { 591 context.Reporter.Warn() << Resource::String::DependenciesSkippedMessage << std::endl; 592 return; 593 } 594 595 context << 596 GetDependenciesFromInstaller << 597 ReportDependencies(Resource::String::PackageRequiresDependencies) << 598 EnableWindowsFeaturesDependencies << 599 ProcessMultiplePackages(Resource::String::PackageRequiresDependencies, APPINSTALLER_CLI_ERROR_INSTALL_DEPENDENCIES, Flags::IgnoreDependencies | Flags::StopOnFailure | Flags::RefreshPathVariable); 600 } 601 602 void DownloadPackageDependencies(Execution::Context& context) 603 { 604 using Flags = ProcessMultiplePackages::Flags; 605 606 if (Settings::User().Get<Settings::Setting::InstallSkipDependencies>() || context.Args.Contains(Execution::Args::Type::SkipDependencies)) 607 { 608 context.Reporter.Warn() << Resource::String::DependenciesSkippedMessage << std::endl; 609 return; 610 } 611 612 context << 613 GetDependenciesFromInstaller << 614 ReportDependencies(Resource::String::PackageRequiresDependencies) << 615 CreateDependencySubContexts(Resource::String::PackageRequiresDependencies) << 616 ProcessMultiplePackages(Resource::String::PackageRequiresDependencies, APPINSTALLER_CLI_ERROR_DOWNLOAD_DEPENDENCIES, Flags::IgnoreDependencies | Flags::StopOnFailure | Flags::DownloadOnly); 617 } 618 619 void InstallSinglePackage(Execution::Context& context) 620 { 621 context << 622 Workflow::CheckForUnsupportedArgs << 623 Workflow::ReportIdentityAndInstallationDisclaimer << 624 Workflow::ShowPromptsForSinglePackage(/* ensureAcceptance */ true) << 625 Workflow::CreateDependencySubContexts(Resource::String::PackageRequiresDependencies) << 626 Workflow::InstallDependencies << 627 Workflow::DownloadInstaller << 628 Workflow::InstallPackageInstaller << 629 Workflow::RegisterStartupAfterReboot(); 630 } 631 632 void EnsureSupportForInstall(Execution::Context& context) 633 { 634 if (WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::InstallerDownloadOnly)) 635 { 636 return; 637 } 638 639 const auto& installer = context.Get<Execution::Data::Installer>(); 640 641 // This check is only necessary for the Repair workflow when operating on an installer with RepairBehavior set to Installer. 642 if (WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::InstallerExecutionUseRepair)) 643 { 644 if (installer->RepairBehavior != RepairBehaviorEnum::Installer) 645 { 646 return; 647 } 648 649 // At present, the installer repair behavior scenario is restricted to Exe, Inno, Nullsoft, and Burn installer types. 650 if (!DoesInstallerTypeRequireRepairBehaviorForRepair(installer->EffectiveInstallerType())) 651 { 652 return; 653 } 654 } 655 656 // This installer cannot be run elevated, but we are running elevated. 657 // Implementation of de-elevation is complex; simply block for now. 658 if (installer->ElevationRequirement == ElevationRequirementEnum::ElevationProhibited && Runtime::IsRunningAsAdmin()) 659 { 660 AICLI_LOG(CLI, Error, << "The installer cannot be run from an administrator context."); 661 context.Reporter.Error() << Resource::String::InstallerProhibitsElevation << std::endl; 662 AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_INSTALLER_PROHIBITS_ELEVATION); 663 } 664 665 // This installer cannot be used to upgrade the currently installed application 666 // Because the upgrade mechanism may be package-specific, simply block. 667 bool isUpdate = WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::InstallerExecutionUseUpdate); 668 UpdateBehaviorEnum updateBehavior = installer->UpdateBehavior; 669 if (isUpdate && (updateBehavior == UpdateBehaviorEnum::Deny)) 670 { 671 AICLI_LOG(CLI, Error, << "Manifest specifies update behavior is denied. The attempt will be cancelled."); 672 context.Reporter.Error() << Resource::String::UpgradeBlockedByManifest << std::endl; 673 AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_INSTALL_UPGRADE_NOT_SUPPORTED); 674 } 675 676 context << 677 Workflow::EnsureRunningAsAdminForMachineScopeInstall << 678 Workflow::EnsureSupportForPortableInstall << 679 Workflow::EnsureValidNestedInstallerMetadataForArchiveInstall; 680 } 681 682 ProcessMultiplePackages::ProcessMultiplePackages( 683 StringResource::StringId dependenciesReportMessage, 684 HRESULT resultOnFailure, 685 Flags flags, 686 std::vector<HRESULT>&& ignorableInstallResults) : 687 WorkflowTask("ProcessMultiplePackages"), 688 m_dependenciesReportMessage(dependenciesReportMessage), 689 m_resultOnFailure(resultOnFailure), 690 m_ignorableInstallResults(std::move(ignorableInstallResults)) 691 { 692 // Inverted 693 m_ensurePackageAgreements = !WI_IsFlagSet(flags, Flags::SkipPackageAgreements); 694 695 m_ignorePackageDependencies = WI_IsFlagSet(flags, Flags::IgnoreDependencies); 696 m_stopOnFailure = WI_IsFlagSet(flags, Flags::StopOnFailure); 697 m_refreshPathVariable = WI_IsFlagSet(flags, Flags::RefreshPathVariable); 698 m_downloadOnly = WI_IsFlagSet(flags, Flags::DownloadOnly); 699 } 700 701 void ProcessMultiplePackages::operator()(Execution::Context& context) const 702 { 703 if (!context.Contains(Execution::Data::PackageSubContexts)) 704 { 705 return; 706 } 707 708 bool downloadInstallerOnly = m_downloadOnly ? true : WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::InstallerDownloadOnly); 709 710 // Show all prompts needed for every package before installing anything 711 context << Workflow::ShowPromptsForMultiplePackages(m_ensurePackageAgreements, downloadInstallerOnly); 712 713 if (context.IsTerminated()) 714 { 715 return; 716 } 717 718 auto& packageSubContexts = context.Get<Execution::Data::PackageSubContexts>(); 719 720 // Report dependencies 721 if (!m_ignorePackageDependencies) 722 { 723 DependencyList allDependencies; 724 725 for (auto& packageContext : packageSubContexts) 726 { 727 allDependencies.Add(packageContext->Get<Execution::Data::Installer>().value().Dependencies); 728 } 729 730 if (!allDependencies.Empty()) 731 { 732 if (downloadInstallerOnly) 733 { 734 context.Reporter.Info() << Resource::String::DependenciesFlowDownload << std::endl; 735 } 736 else 737 { 738 context.Reporter.Info() << Resource::String::DependenciesFlowInstall << std::endl; 739 } 740 } 741 742 context.Add<Execution::Data::Dependencies>(allDependencies); 743 context << Workflow::ReportDependencies(m_dependenciesReportMessage); 744 } 745 746 bool allSucceeded = true; 747 size_t packagesCount = packageSubContexts.size(); 748 size_t packagesProgress = 0; 749 750 for (auto& packageContext : packageSubContexts) 751 { 752 packagesProgress++; 753 context.Reporter.Info() << '(' << packagesProgress << '/' << packagesCount << ") "_liv; 754 755 // We want to do best effort to install all packages regardless of previous failures 756 Execution::Context& currentContext = *packageContext; 757 auto previousThreadGlobals = currentContext.SetForCurrentThread(); 758 759 currentContext << Workflow::ReportIdentityAndInstallationDisclaimer; 760 761 // Prevent individual exceptions from breaking out of the loop 762 try 763 { 764 // Handle dependencies if requested. 765 if (!m_ignorePackageDependencies && !downloadInstallerOnly) 766 { 767 currentContext << 768 Workflow::EnableWindowsFeaturesDependencies << 769 Workflow::CreateDependencySubContexts(m_dependenciesReportMessage) << 770 Workflow::ProcessMultiplePackages(m_dependenciesReportMessage, APPINSTALLER_CLI_ERROR_INSTALL_DEPENDENCIES, Flags::IgnoreDependencies | Flags::StopOnFailure | Flags::RefreshPathVariable); 771 } 772 773 currentContext << Workflow::DownloadInstaller; 774 775 if (!downloadInstallerOnly) 776 { 777 currentContext << Workflow::InstallPackageInstaller; 778 } 779 } 780 catch (...) 781 { 782 currentContext.SetTerminationHR(Workflow::HandleException(currentContext, std::current_exception())); 783 } 784 785 if (m_refreshPathVariable) 786 { 787 if (RefreshPathVariableForCurrentProcess()) 788 { 789 AICLI_LOG(CLI, Info, << "Successfully refreshed process PATH environment variable."); 790 } 791 else 792 { 793 AICLI_LOG(CLI, Warning, << "Failed to refresh process PATH environment variable."); 794 context.Reporter.Warn() << Resource::String::FailedToRefreshPathWarning << std::endl; 795 } 796 } 797 798 currentContext.Reporter.Info() << std::endl; 799 800 if (currentContext.IsTerminated()) 801 { 802 if (context.IsTerminated() && context.GetTerminationHR() == E_ABORT) 803 { 804 // This means that the subcontext being terminated is due to an overall abort 805 context.Reporter.Info() << Resource::String::Cancelled << std::endl; 806 return; 807 } 808 809 if (m_ignorableInstallResults.end() == std::find(m_ignorableInstallResults.begin(), m_ignorableInstallResults.end(), currentContext.GetTerminationHR())) 810 { 811 allSucceeded = false; 812 if (m_stopOnFailure) 813 { 814 break; 815 } 816 } 817 } 818 } 819 820 if (!allSucceeded) 821 { 822 AICLI_TERMINATE_CONTEXT(m_resultOnFailure); 823 } 824 } 825 826 void SnapshotARPEntries(Execution::Context& context) try 827 { 828 // Ensure that installer type might actually write to ARP, otherwise this is a waste of time 829 auto installer = context.Get<Execution::Data::Installer>(); 830 831 if (installer && MightWriteToARP(installer->EffectiveInstallerType())) 832 { 833 Repository::Correlation::ARPCorrelationData data; 834 data.CapturePreInstallSnapshot(); 835 context.Add<Execution::Data::ARPCorrelationData>(std::move(data)); 836 } 837 } 838 CATCH_LOG() 839 840 void ReportARPChanges(Execution::Context& context) try 841 { 842 if (!context.Contains(Execution::Data::ARPCorrelationData)) 843 { 844 return; 845 } 846 847 // If the installer claims to have a PackageFamilyName, and that family name is currently registered for the user, 848 // let that be the correlated item and skip any attempt at further ARP correlation. 849 const auto& installer = context.Get<Execution::Data::Installer>(); 850 851 if (installer && !installer->PackageFamilyName.empty() && Deployment::IsRegistered(installer->PackageFamilyName)) 852 { 853 return; 854 } 855 856 const auto& manifest = context.Get<Execution::Data::Manifest>(); 857 auto& arpCorrelationData = context.Get<Execution::Data::ARPCorrelationData>(); 858 859 arpCorrelationData.CapturePostInstallSnapshot(); 860 auto correlationResult = arpCorrelationData.CorrelateForNewlyInstalled(manifest); 861 862 // Store the ARP entry found to match the package to record it in the tracking catalog later 863 if (correlationResult.Package) 864 { 865 std::vector<AppsAndFeaturesEntry> entries; 866 867 auto metadata = correlationResult.Package->GetMetadata(); 868 869 AppsAndFeaturesEntry baseEntry; 870 871 // Display name and publisher are also available as multi properties, but 872 // for ARP there will always be only 0 or 1 values. 873 baseEntry.DisplayName = correlationResult.Package->GetProperty(PackageVersionProperty::Name).get(); 874 baseEntry.Publisher = correlationResult.Package->GetProperty(PackageVersionProperty::Publisher).get(); 875 baseEntry.DisplayVersion = correlationResult.Package->GetProperty(PackageVersionProperty::Version).get(); 876 baseEntry.InstallerType = Manifest::ConvertToInstallerTypeEnum(metadata[PackageVersionMetadata::InstalledType]); 877 878 auto productCodes = correlationResult.Package->GetMultiProperty(PackageVersionMultiProperty::ProductCode); 879 for (auto&& productCode : productCodes) 880 { 881 AppsAndFeaturesEntry entry = baseEntry; 882 entry.ProductCode = std::move(productCode).get(); 883 entries.push_back(std::move(entry)); 884 } 885 886 auto upgradeCodes = correlationResult.Package->GetMultiProperty(PackageVersionMultiProperty::UpgradeCode); 887 for (auto&& upgradeCode : upgradeCodes) 888 { 889 AppsAndFeaturesEntry entry = baseEntry; 890 entry.UpgradeCode = std::move(upgradeCode).get(); 891 entries.push_back(std::move(entry)); 892 } 893 894 context.Add<Data::CorrelatedAppsAndFeaturesEntries>(std::move(entries)); 895 } 896 897 // We can only get the source identifier from an active source 898 std::string sourceIdentifier; 899 if (context.Contains(Execution::Data::PackageVersion)) 900 { 901 sourceIdentifier = context.Get<Execution::Data::PackageVersion>()->GetProperty(PackageVersionProperty::SourceIdentifier); 902 } 903 904 IPackageVersion::Metadata arpEntryMetadata; 905 if (correlationResult.Package) 906 { 907 arpEntryMetadata = correlationResult.Package->GetMetadata(); 908 } 909 910 Logging::Telemetry().LogSuccessfulInstallARPChange( 911 sourceIdentifier, 912 manifest.Id, 913 manifest.Version, 914 manifest.Channel, 915 correlationResult.ChangesToARP, 916 correlationResult.MatchesInARP, 917 correlationResult.CountOfIntersectionOfChangesAndMatches, 918 correlationResult.Package ? static_cast<std::string>(correlationResult.Package->GetProperty(PackageVersionProperty::Name)) : "", 919 correlationResult.Package ? static_cast<std::string>(correlationResult.Package->GetProperty(PackageVersionProperty::Version)) : "", 920 correlationResult.Package ? static_cast<std::string>(correlationResult.Package->GetProperty(PackageVersionProperty::Publisher)) : "", 921 correlationResult.Package ? static_cast<std::string_view>(arpEntryMetadata[PackageVersionMetadata::InstalledLocale]) : "" 922 ); 923 } 924 CATCH_LOG(); 925 926 void RecordInstall(Context& context) 927 { 928 // Local manifest installs won't have a package version, and tracking them doesn't provide much 929 // value currently. If we ever do use our own database as a primary source of packages that we 930 // maintain, this decision will probably have to be reconsidered. 931 if (!context.Contains(Data::PackageVersion)) 932 { 933 return; 934 } 935 936 auto manifest = context.Get<Data::Manifest>(); 937 938 // If we have determined an ARP entry matches the installed package, 939 // we set its product code in the manifest we record to ensure we can 940 // find it in the future. 941 // Note that this may overwrite existing information. 942 if (context.Contains(Data::CorrelatedAppsAndFeaturesEntries)) 943 { 944 // Use a new Installer entry 945 manifest.Installers.emplace_back(); 946 manifest.Installers.back().AppsAndFeaturesEntries = context.Get<Data::CorrelatedAppsAndFeaturesEntries>(); 947 } 948 949 auto trackingCatalog = context.Get<Data::PackageVersion>()->GetSource().GetTrackingCatalog(); 950 951 auto version = trackingCatalog.RecordInstall( 952 manifest, 953 context.Get<Data::Installer>().value(), 954 WI_IsFlagSet(context.GetFlags(), ContextFlag::InstallerExecutionUseUpdate)); 955 956 // Record user intent values. Command args takes precedence. Then previous user intent values. 957 Repository::IPackageVersion::Metadata installedMetadata; 958 if (context.Contains(Data::InstalledPackageVersion) && context.Get<Execution::Data::InstalledPackageVersion>()) 959 { 960 installedMetadata = context.Get<Data::InstalledPackageVersion>()->GetMetadata(); 961 } 962 963 if (context.Args.Contains(Execution::Args::Type::InstallArchitecture)) 964 { 965 version.SetMetadata(Repository::PackageVersionMetadata::UserIntentArchitecture, context.Args.GetArg(Execution::Args::Type::InstallArchitecture)); 966 } 967 else 968 { 969 auto itr = installedMetadata.find(Repository::PackageVersionMetadata::UserIntentArchitecture); 970 if (itr != installedMetadata.end()) 971 { 972 version.SetMetadata(Repository::PackageVersionMetadata::UserIntentArchitecture, itr->second); 973 } 974 } 975 976 if (context.Args.Contains(Execution::Args::Type::Locale)) 977 { 978 version.SetMetadata(Repository::PackageVersionMetadata::UserIntentLocale, context.Args.GetArg(Execution::Args::Type::Locale)); 979 } 980 else 981 { 982 auto itr = installedMetadata.find(Repository::PackageVersionMetadata::UserIntentLocale); 983 if (itr != installedMetadata.end()) 984 { 985 version.SetMetadata(Repository::PackageVersionMetadata::UserIntentLocale, itr->second); 986 } 987 } 988 } 989 }