winget-cli

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

commit 8f653e0ccd0a290fd9be462a5a716a00a21ccaf6
parent 2536bde0f6e6f502fd0193e0580787ccd4960091
Author: JohnMcPMS <johnmcp@microsoft.com>
Date:   Wed, 25 Aug 2021 09:21:39 -0700

Add deferred registration for MSIX (#1397)

Prior to this change, MSIX install (and upgrade) would forcibly shut down the application so that it could upgrade the package during the command.  After, the new package will be staged and the upgrade will complete when the application is restarted.

This is done by first attempting `RequestAddPackageAsync` if SmartScreen is necessary.  Then if it that fails due to the package being in use (or it is not needed at all), we stage (place files onto the machine) then register (install package for user).  If the package is in use at this point, we detect it and change the success message to indicate that the user must restart the application:
```
Successfully installed. Restart the application to complete the upgrade.
```
Diffstat:
Msrc/AppInstallerCLICore/Commands/HashCommand.cpp | 2++
Msrc/AppInstallerCLICore/Resources.h | 1+
Msrc/AppInstallerCLICore/Workflows/InstallFlow.cpp | 30++++++++++++++++++++----------
Msrc/AppInstallerCLICore/Workflows/UninstallFlow.cpp | 3++-
Msrc/AppInstallerCLICore/pch.h | 2--
Msrc/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw | 6+++++-
Msrc/AppInstallerCommonCore/Deployment.cpp | 115+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------
Msrc/AppInstallerCommonCore/Public/AppInstallerDeployment.h | 10++++++++++
Msrc/AppInstallerRepositoryCore/Microsoft/PreIndexedPackageSourceFactory.cpp | 3+++
Msrc/AppInstallerRepositoryCore/pch.h | 2--
10 files changed, 150 insertions(+), 24 deletions(-)

diff --git a/src/AppInstallerCLICore/Commands/HashCommand.cpp b/src/AppInstallerCLICore/Commands/HashCommand.cpp @@ -5,6 +5,8 @@ #include "Workflows/WorkflowBase.h" #include "Resources.h" +#include <AppInstallerMsixInfo.h> + namespace AppInstaller::CLI { using namespace std::string_view_literals; diff --git a/src/AppInstallerCLICore/Resources.h b/src/AppInstallerCLICore/Resources.h @@ -100,6 +100,7 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(InstallerHashMismatchOverrideRequired); WINGET_DEFINE_RESOURCE_STRINGID(InstallerHashVerified); WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowInstallSuccess); + WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowRegistrationDeferred); WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowStartingPackageInstall); WINGET_DEFINE_RESOURCE_STRINGID(InstallForceArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(InstallScopeDescription); diff --git a/src/AppInstallerCLICore/Workflows/InstallFlow.cpp b/src/AppInstallerCLICore/Workflows/InstallFlow.cpp @@ -10,6 +10,9 @@ #include "WorkflowBase.h" #include "Workflows/DependenciesFlow.h" +#include <AppInstallerDeployment.h> +#include <AppInstallerMsixInfo.h> + namespace AppInstaller::CLI::Workflow { using namespace winrt::Windows::ApplicationModel::Store::Preview::InstallControl; @@ -408,26 +411,26 @@ namespace AppInstaller::CLI::Workflow void MsixInstall(Execution::Context& context) { - Uri uri = nullptr; + std::string uri; if (context.Contains(Execution::Data::InstallerPath)) { - uri = Uri(context.Get<Execution::Data::InstallerPath>().c_str()); + uri = context.Get<Execution::Data::InstallerPath>().u8string(); } else { - uri = Uri(Utility::ConvertToUTF16(context.Get<Execution::Data::Installer>()->Url)); + uri = context.Get<Execution::Data::Installer>()->Url; } context.Reporter.Info() << Resource::String::InstallFlowStartingPackageInstall << std::endl; + bool registrationDeferred = false; + try { - DeploymentOptions deploymentOptions = - DeploymentOptions::ForceApplicationShutdown | - DeploymentOptions::ForceTargetApplicationShutdown; - - context.Reporter.ExecuteWithProgress(std::bind(Deployment::AddPackage, uri, deploymentOptions, - WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::InstallerTrusted), std::placeholders::_1)); + registrationDeferred = context.Reporter.ExecuteWithProgress([&](IProgressCallback& callback) + { + return Deployment::AddPackageWithDeferredFallback(uri, WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::InstallerTrusted), callback); + }); } catch (const wil::ResultException& re) { @@ -438,7 +441,14 @@ namespace AppInstaller::CLI::Workflow AICLI_TERMINATE_CONTEXT(re.GetErrorCode()); } - context.Reporter.Info() << Resource::String::InstallFlowInstallSuccess << std::endl; + if (registrationDeferred) + { + context.Reporter.Warn() << Resource::String::InstallFlowRegistrationDeferred << std::endl; + } + else + { + context.Reporter.Info() << Resource::String::InstallFlowInstallSuccess << std::endl; + } } void RemoveInstaller(Execution::Context& context) diff --git a/src/AppInstallerCLICore/Workflows/UninstallFlow.cpp b/src/AppInstallerCLICore/Workflows/UninstallFlow.cpp @@ -1,12 +1,13 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. - #include "pch.h" #include "UninstallFlow.h" #include "WorkflowBase.h" #include "ShellExecuteInstallerHandler.h" #include "AppInstallerMsixInfo.h" +#include <AppInstallerDeployment.h> + using namespace AppInstaller::Manifest; using namespace AppInstaller::Msix; using namespace AppInstaller::Repository; diff --git a/src/AppInstallerCLICore/pch.h b/src/AppInstallerCLICore/pch.h @@ -49,11 +49,9 @@ #include <AppInstallerArchitecture.h> #include <AppInstallerDateTime.h> -#include <AppInstallerDeployment.h> #include <AppInstallerDownloader.h> #include <AppInstallerErrors.h> #include <AppInstallerLogging.h> -#include <AppInstallerMsixInfo.h> #include <AppInstallerRepositorySearch.h> #include <AppInstallerRepositorySource.h> #include <AppInstallerRuntime.h> diff --git a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw @@ -1019,4 +1019,7 @@ Do you agree to the terms?</value> <data name="PromptOptionYes" xml:space="preserve"> <value>Yes</value> </data> -</root> + <data name="InstallFlowRegistrationDeferred" xml:space="preserve"> + <value>Successfully installed. Restart the application to complete the upgrade.</value> + </data> +</root>+ \ No newline at end of file diff --git a/src/AppInstallerCommonCore/Deployment.cpp b/src/AppInstallerCommonCore/Deployment.cpp @@ -4,6 +4,8 @@ #include "pch.h" #include "Public/AppInstallerDeployment.h" #include "Public/AppInstallerLogging.h" +#include "Public/AppInstallerMsixInfo.h" +#include "Public/AppInstallerRuntime.h" #include "Public/AppInstallerStrings.h" namespace AppInstaller::Deployment @@ -19,12 +21,13 @@ namespace AppInstaller::Deployment return s_deploymentId.fetch_add(1); } - void WaitForDeployment( + HRESULT WaitForDeployment( IAsyncOperationWithProgress<DeploymentResult, DeploymentProgress>& deployOperation, size_t id, - IProgressCallback& callback) + IProgressCallback& callback, + bool throwOnError = true) { - AICLI_LOG(Core, Info, << "Begin waiting for deployment #" << id); + AICLI_LOG(Core, Info, << "Begin waiting for operation #" << id); AsyncOperationProgressHandler<DeploymentResult, DeploymentProgress> progressCallback( [&callback](const IAsyncOperationWithProgress<DeploymentResult, DeploymentProgress>&, DeploymentProgress progress) @@ -38,21 +41,31 @@ namespace AppInstaller::Deployment auto removeCancel = callback.SetCancellationFunction([&]() { deployOperation.Cancel(); }); - AICLI_LOG(Core, Info, << "Begin blocking for deployment #" << id); + AICLI_LOG(Core, Info, << "Begin blocking for operation #" << id); auto deployResult = deployOperation.get(); if (!SUCCEEDED(deployResult.ExtendedErrorCode())) { - AICLI_LOG(Core, Error, << "Deployment failed #" << id << ": " << Utility::ConvertToUTF8(deployResult.ErrorText())); + AICLI_LOG(Core, Error, << "Deployment operation #" << id << ": " << Utility::ConvertToUTF8(deployResult.ErrorText())); // Note that while the format string is char*, it gets converted to wchar before being used. - THROW_HR_MSG(deployResult.ExtendedErrorCode(), "Install failed: %ws", deployResult.ErrorText().c_str()); + if (throwOnError) + { + THROW_HR_MSG(deployResult.ExtendedErrorCode(), "Operation failed: %ws", deployResult.ErrorText().c_str()); + } + else + { + // Simple return because this path is generally used for recovery cases + return deployResult.ExtendedErrorCode(); + } } else { - AICLI_LOG(Core, Info, << "Successfully deployed #" << id); + AICLI_LOG(Core, Info, << "Successfully completed #" << id); } + + return S_OK; } } @@ -63,7 +76,7 @@ namespace AppInstaller::Deployment IProgressCallback& callback) { size_t id = GetDeploymentOperationId(); - AICLI_LOG(Core, Info, << "Starting AddPackage operation #" << id << ": " << Utility::ConvertToUTF8(uri.AbsoluteUri().c_str()) << "SkipSmartScreen: " << skipSmartScreen); + AICLI_LOG(Core, Info, << "Starting AddPackage operation #" << id << ": " << Utility::ConvertToUTF8(uri.AbsoluteUri().c_str()) << " SkipSmartScreen: " << skipSmartScreen); PackageManager packageManager; @@ -94,6 +107,92 @@ namespace AppInstaller::Deployment WaitForDeployment(deployOperation, id, callback); } + bool AddPackageWithDeferredFallback( + const std::string& uri, + bool skipSmartScreen, + IProgressCallback& callback) + { + PackageManager packageManager; + + // In the event of a failure we want to ensure that the package is not left on the system. + Msix::MsixInfo packageInfo{ uri }; + std::wstring packageFullName = packageInfo.GetPackageFullNameWide(); + auto removePackage = wil::scope_exit([&]() { + try + { + RemovePackage(Utility::ConvertToUTF8(packageFullName), callback); + } + CATCH_LOG(); + }); + + Uri uriObject(Utility::ConvertToUTF16(uri)); + + if (!skipSmartScreen) + { + // The only way to get SmartScreen is to use RequestAddPackageAsync, so we will have to start with that. + size_t id = GetDeploymentOperationId(); + AICLI_LOG(Core, Info, << "Starting RequestAddPackageAsync operation #" << id << ": " << uri); + + DeploymentOptions options = DeploymentOptions::None; + // Optimization to keep files if the package is in use. Only available in a newer OS per: + // https://docs.microsoft.com/en-us/uwp/api/Windows.Management.Deployment.DeploymentOptions + if (Runtime::IsCurrentOSVersionGreaterThanOrEqual(Utility::Version{ "10.0.18362.0" })) + { + options = DeploymentOptions::RetainFilesOnFailure; + } + + IAsyncOperationWithProgress<DeploymentResult, DeploymentProgress> deployOperation = packageManager.RequestAddPackageAsync( + uriObject, + nullptr, /*dependencyPackageUris*/ + options, + nullptr, /*targetVolume*/ + nullptr, /*optionalAndRelatedPackageFamilyNames*/ + nullptr /*relatedPackageUris*/); + + HRESULT hr = WaitForDeployment(deployOperation, id, callback, false); + + if (SUCCEEDED(hr)) + { + removePackage.release(); + return false; + } + + THROW_HR_IF(hr, FAILED(hr) && hr != HRESULT_FROM_WIN32(ERROR_PACKAGES_IN_USE)); + } + + // If we are skipping SmartScreen or the package was in use, stage then register the package. + { + size_t id = GetDeploymentOperationId(); + AICLI_LOG(Core, Info, << "Starting StagePackageAsync operation #" << id << ": " << uri); + + IAsyncOperationWithProgress<DeploymentResult, DeploymentProgress> stageOperation = packageManager.StagePackageAsync(uriObject, nullptr); + WaitForDeployment(stageOperation, id, callback); + } + + bool registrationDeferred = false; + + { + size_t id = GetDeploymentOperationId(); + AICLI_LOG(Core, Info, << "Starting RegisterPackageByFullNameAsync operation #" << id << ": " << Utility::ConvertToUTF8(packageFullName)); + + IAsyncOperationWithProgress<DeploymentResult, DeploymentProgress> registerOperation = + packageManager.RegisterPackageByFullNameAsync(packageFullName, nullptr, DeploymentOptions::None); + HRESULT hr = WaitForDeployment(registerOperation, id, callback, false); + + if (hr == HRESULT_FROM_WIN32(ERROR_PACKAGES_IN_USE)) + { + registrationDeferred = true; + } + else + { + THROW_IF_FAILED(hr); + } + } + + removePackage.release(); + return registrationDeferred; + } + void RemovePackage( std::string_view packageFullName, IProgressCallback& callback) diff --git a/src/AppInstallerCommonCore/Public/AppInstallerDeployment.h b/src/AppInstallerCommonCore/Public/AppInstallerDeployment.h @@ -15,6 +15,16 @@ namespace AppInstaller::Deployment bool skipSmartScreen, IProgressCallback& callback); + // Calls winrt::Windows::Management::Deployment::PackageManager::AddPackageAsync if skipSmartScreen is true, + // Otherwise, calls winrt::Windows::Management::Deployment::PackageManager::RequestAddPackageAsync. + // If the Add function fails due to the package being in use, we fall back to stage and register, which allows + // a deferred registration. + // Returns true if the registration was deferred; false if not. + bool AddPackageWithDeferredFallback( + const std::string& uri, + bool skipSmartScreen, + IProgressCallback& callback); + // Calls winrt::Windows::Management::Deployment::PackageManager::RemovePackageAsync void RemovePackage( std::string_view packageFullName, diff --git a/src/AppInstallerRepositoryCore/Microsoft/PreIndexedPackageSourceFactory.cpp b/src/AppInstallerRepositoryCore/Microsoft/PreIndexedPackageSourceFactory.cpp @@ -6,6 +6,9 @@ #include "Microsoft/SQLiteIndex.h" #include "Microsoft/SQLiteIndexSource.h" +#include <AppInstallerDeployment.h> +#include <AppInstallerMsixInfo.h> + using namespace std::string_literals; using namespace std::string_view_literals; diff --git a/src/AppInstallerRepositoryCore/pch.h b/src/AppInstallerRepositoryCore/pch.h @@ -15,11 +15,9 @@ #pragma warning( pop ) #include <AppInstallerDateTime.h> -#include <AppInstallerDeployment.h> #include <AppInstallerDownloader.h> #include <AppInstallerErrors.h> #include <AppInstallerLogging.h> -#include <AppInstallerMsixInfo.h> #include <AppInstallerRuntime.h> #include <AppInstallerSHA256.h> #include <AppInstallerStrings.h>