winget-cli

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

commit 8a15f53b3d6750c868d5d04eca14e3ece8a85c3b
parent 020c2f0f8a759fb87d094a5ab6d98e825b442d2d
Author: Ruben Guerrero <rubengu@microsoft.com>
Date:   Tue,  5 Sep 2023 09:50:25 -0700

Enable configuration via IConfigurationStatics (#3576)

Winget configuration is not going to be enabled by default. The user needs to opt-in by calling winget configuration --enable. Callers that are interested in using the OOP Microsoft.Management.Configuration APIs need a way to ensure configuration is enabled and enable it if not. This PR address that.

New IConfigurationStatics methods
IsConfigurationEnabled - Checks if the configuration is enabled by checking if the current AppInstaller package is the stub or full package. Inproc implementation always returns true.
EnableConfigurationAsync - Asynchronously enables configuration. This changes the stub preference to full package and calls our own APIs to install AppIntaller. By design, this also updates AppInstaller to the newest version.
Caveats
Callers need to understand that this is effectively saying "winget please update yourself". Which means that once EnableConfigurationAsync is done, configuration isn't ready yet. At this point, deployment scheduled AppInstaller for an update. The server process that owns the configuration statics object is already gone (or is about to) and a new instance of ConfigurationStaticFunctions needs to be created in order validate configuration is enabled. It is possible that creating that object fails with an error that the upgrade is in progress. Yes, this is awkward.

Testing
It is complicated to write automated tests for this without faking most of the stack. I manually created an AppInstaller package with an older version to what we internally have to do these tests. Then I used the current OOP configuration tests to update it.
Diffstat:
Msrc/AppInstallerCommonCore/MSStore.cpp | 2--
Msrc/Microsoft.Management.Configuration/ConfigurationStaticFunctions.cpp | 5+++++
Msrc/Microsoft.Management.Configuration/ConfigurationStaticFunctions.h | 2++
Msrc/Microsoft.Management.Configuration/Microsoft.Management.Configuration.idl | 6++++++
Msrc/Microsoft.Management.Deployment/PackageManager.cpp | 6++++++
Msrc/WindowsPackageManager/ConfigurationStaticFunctions.cpp | 111++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
6 files changed, 129 insertions(+), 3 deletions(-)

diff --git a/src/AppInstallerCommonCore/MSStore.cpp b/src/AppInstallerCommonCore/MSStore.cpp @@ -4,13 +4,11 @@ #include <winget/MSStore.h> #include <winget/ManifestCommon.h> #include <winget/Runtime.h> -#include <winget/SelfManagement.h> #include <AppInstallerFileLogger.h> #include <AppInstallerErrors.h> namespace AppInstaller::MSStore { - using namespace AppInstaller::SelfManagement; using namespace std::string_view_literals; using namespace winrt::Windows::Foundation; using namespace winrt::Windows::Foundation::Collections; diff --git a/src/Microsoft.Management.Configuration/ConfigurationStaticFunctions.cpp b/src/Microsoft.Management.Configuration/ConfigurationStaticFunctions.cpp @@ -40,4 +40,9 @@ namespace winrt::Microsoft::Management::Configuration::implementation result->ConfigurationSetProcessorFactory(factory); return *result; } + + Windows::Foundation::IAsyncActionWithProgress<uint32_t> ConfigurationStaticFunctions::EnsureConfigurationAvailableAsync() + { + THROW_HR(E_NOTIMPL); + } } diff --git a/src/Microsoft.Management.Configuration/ConfigurationStaticFunctions.h b/src/Microsoft.Management.Configuration/ConfigurationStaticFunctions.h @@ -13,6 +13,8 @@ namespace winrt::Microsoft::Management::Configuration::implementation Configuration::ConfigurationSet CreateConfigurationSet(); Windows::Foundation::IAsyncOperation<IConfigurationSetProcessorFactory> CreateConfigurationSetProcessorFactoryAsync(hstring const& handler); Configuration::ConfigurationProcessor CreateConfigurationProcessor(IConfigurationSetProcessorFactory const& factory); + bool IsConfigurationAvailable() { return true; } + Windows::Foundation::IAsyncActionWithProgress<uint32_t> EnsureConfigurationAvailableAsync(); }; } namespace winrt::Microsoft::Management::Configuration::factory_implementation diff --git a/src/Microsoft.Management.Configuration/Microsoft.Management.Configuration.idl b/src/Microsoft.Management.Configuration/Microsoft.Management.Configuration.idl @@ -715,6 +715,12 @@ namespace Microsoft.Management.Configuration // Creates a processor from the given factory. ConfigurationProcessor CreateConfigurationProcessor(IConfigurationSetProcessorFactory factory); + + // Whether configuration is enabled. + Boolean IsConfigurationAvailable{ get; }; + + // Enables configuration. Requires store access. + Windows.Foundation.IAsyncActionWithProgress<UInt32> EnsureConfigurationAvailableAsync(); } // Top level entry point for configuration, enabling easier usage in out-of-process scenarios. diff --git a/src/Microsoft.Management.Deployment/PackageManager.cpp b/src/Microsoft.Management.Deployment/PackageManager.cpp @@ -54,6 +54,7 @@ namespace winrt::Microsoft::Management::Deployment::implementation } return catalogs.GetView(); } + winrt::Microsoft::Management::Deployment::PackageCatalogReference PackageManager::GetPredefinedPackageCatalog(winrt::Microsoft::Management::Deployment::PredefinedPackageCatalog const& predefinedPackageCatalog) { ::AppInstaller::Repository::Source source; @@ -77,6 +78,7 @@ namespace winrt::Microsoft::Management::Deployment::implementation packageCatalogRef->Initialize(*packageCatalogInfo, source); return *packageCatalogRef; } + winrt::Microsoft::Management::Deployment::PackageCatalogReference PackageManager::GetLocalPackageCatalog(winrt::Microsoft::Management::Deployment::LocalPackageCatalog const& localPackageCatalog) { ::AppInstaller::Repository::Source source; @@ -97,6 +99,7 @@ namespace winrt::Microsoft::Management::Deployment::implementation packageCatalogRef->Initialize(*packageCatalogInfo, source); return *packageCatalogRef; } + winrt::Microsoft::Management::Deployment::PackageCatalogReference PackageManager::GetPackageCatalogByName(hstring const& catalogName) { std::string name = winrt::to_string(catalogName); @@ -120,6 +123,7 @@ namespace winrt::Microsoft::Management::Deployment::implementation return nullptr; } } + void AddPackageManifestToContext(winrt::Microsoft::Management::Deployment::PackageVersionInfo packageVersionInfo, ::AppInstaller::CLI::Execution::Context* context) { winrt::Microsoft::Management::Deployment::implementation::PackageVersionInfo* packageVersionInfoImpl = get_self<winrt::Microsoft::Management::Deployment::implementation::PackageVersionInfo>(packageVersionInfo); @@ -138,12 +142,14 @@ namespace winrt::Microsoft::Management::Deployment::implementation context->Add<::AppInstaller::CLI::Execution::Data::Manifest>(std::move(manifest)); context->Add<::AppInstaller::CLI::Execution::Data::PackageVersion>(std::move(internalPackageVersion)); } + void AddInstalledVersionToContext(winrt::Microsoft::Management::Deployment::PackageVersionInfo installedVersionInfo, ::AppInstaller::CLI::Execution::Context* context) { winrt::Microsoft::Management::Deployment::implementation::PackageVersionInfo* installedVersionInfoImpl = get_self<winrt::Microsoft::Management::Deployment::implementation::PackageVersionInfo>(installedVersionInfo); std::shared_ptr<::AppInstaller::Repository::IPackageVersion> internalInstalledVersion = installedVersionInfoImpl->GetRepositoryPackageVersion(); context->Add<AppInstaller::CLI::Execution::Data::InstalledPackageVersion>(internalInstalledVersion); } + winrt::Microsoft::Management::Deployment::PackageCatalogReference PackageManager::CreateCompositePackageCatalog(winrt::Microsoft::Management::Deployment::CreateCompositePackageCatalogOptions const& options) { if (!options) diff --git a/src/WindowsPackageManager/ConfigurationStaticFunctions.cpp b/src/WindowsPackageManager/ConfigurationStaticFunctions.cpp @@ -13,9 +13,21 @@ #include <winget/GroupPolicy.h> #include <winget/Security.h> #include <winget/ThreadGlobals.h> +#include <winget/SelfManagement.h> +#include <winget/MSStore.h> +#include <winrt/Microsoft.Management.Deployment.h> + +using namespace AppInstaller::SelfManagement; +using namespace winrt::Microsoft::Management::Deployment; +using namespace winrt::Windows::Foundation::Collections; namespace ConfigurationShim { + namespace + { + static std::atomic_bool s_canBeCreated{ true }; + } + CLSID CLSID_ConfigurationObjectLifetimeWatcher = { 0x89a8f1d4,0x1e24,0x46a4,{0x9f,0x6c,0x65,0x78,0xb0,0x47,0xf2,0xf7} }; struct @@ -35,10 +47,22 @@ namespace ConfigurationShim diagnosticsLogger.EnableChannel(AppInstaller::Logging::Channel::All); diagnosticsLogger.SetLevel(AppInstaller::Logging::Level::Verbose); diagnosticsLogger.AddLogger(std::make_unique<AppInstaller::Logging::FileLogger>("ConfigStatics"sv)); + + if (IsConfigurationAvailable()) + { + m_statics = winrt::Microsoft::Management::Configuration::ConfigurationStaticFunctions(); + } } winrt::Microsoft::Management::Configuration::ConfigurationUnit CreateConfigurationUnit() { + THROW_HR_IF(CO_E_CLASS_DISABLED, !s_canBeCreated); + + if (!m_statics) + { + THROW_HR(APPINSTALLER_CLI_ERROR_PACKAGE_IS_STUB); + } + auto result = m_statics.CreateConfigurationUnit(); result.as<AppInstaller::WinRT::ILifetimeWatcher>()->SetLifetimeWatcher(CreateLifetimeWatcher()); return result; @@ -46,6 +70,13 @@ namespace ConfigurationShim winrt::Microsoft::Management::Configuration::ConfigurationSet CreateConfigurationSet() { + THROW_HR_IF(CO_E_CLASS_DISABLED, !s_canBeCreated); + + if (!m_statics) + { + THROW_HR(APPINSTALLER_CLI_ERROR_PACKAGE_IS_STUB); + } + auto result = m_statics.CreateConfigurationSet(); result.as<AppInstaller::WinRT::ILifetimeWatcher>()->SetLifetimeWatcher(CreateLifetimeWatcher()); return result; @@ -53,6 +84,8 @@ namespace ConfigurationShim winrt::Windows::Foundation::IAsyncOperation<winrt::Microsoft::Management::Configuration::IConfigurationSetProcessorFactory> CreateConfigurationSetProcessorFactoryAsync(winrt::hstring const& handler) { + THROW_HR_IF(CO_E_CLASS_DISABLED, !s_canBeCreated); + auto strong_this{ get_strong() }; std::wstring lowerHandler = AppInstaller::Utility::ToLower(handler); @@ -81,11 +114,85 @@ namespace ConfigurationShim winrt::Microsoft::Management::Configuration::ConfigurationProcessor CreateConfigurationProcessor(winrt::Microsoft::Management::Configuration::IConfigurationSetProcessorFactory const& factory) { + THROW_HR_IF(CO_E_CLASS_DISABLED, !s_canBeCreated); + + if (!m_statics) + { + THROW_HR(APPINSTALLER_CLI_ERROR_PACKAGE_IS_STUB); + } + auto result = m_statics.CreateConfigurationProcessor(factory); result.as<AppInstaller::WinRT::ILifetimeWatcher>()->SetLifetimeWatcher(CreateLifetimeWatcher()); return result; } + bool IsConfigurationAvailable() + { + return !IsStubPackage(); + } + + winrt::Windows::Foundation::IAsyncActionWithProgress<uint32_t> EnsureConfigurationAvailableAsync() + { + THROW_HR_IF(CO_E_CLASS_DISABLED, !s_canBeCreated); + + if (IsConfigurationAvailable()) + { + return; + } + + auto strong_this{ get_strong() }; + co_await winrt::resume_background(); + + SetStubPreferred(false); + + PackageManager packageManager; + PackageCatalogReference catalogRef{ packageManager.GetPredefinedPackageCatalog(PredefinedPackageCatalog::MicrosoftStore) }; + THROW_HR_IF(APPINSTALLER_CLI_ERROR_INTERNAL_ERROR, !catalogRef); + + ConnectResult connectResult = catalogRef.Connect(); + THROW_HR_IF(APPINSTALLER_CLI_ERROR_SOURCE_OPEN_FAILED, connectResult.Status() != ConnectResultStatus::Ok); + + PackageCatalog catalog = connectResult.PackageCatalog(); + + FindPackagesOptions findPackagesOptions; + PackageMatchFilter filter; + filter.Field(PackageMatchField::Id); + filter.Option(PackageFieldMatchOption::Equals); + filter.Value(AppInstaller::MSStore::s_AppInstallerProductId); + findPackagesOptions.Filters().Append(filter); + + FindPackagesResult findPackagesResult{ catalog.FindPackages(findPackagesOptions) }; + + auto matches = findPackagesResult.Matches(); + THROW_HR_IF(APPINSTALLER_CLI_ERROR_MISSING_PACKAGE, matches.Size() == 0); + auto catalogPackage = matches.GetAt(0).CatalogPackage(); + + InstallOptions installOptions; + installOptions.AcceptPackageAgreements(true); + installOptions.AllowUpgradeToUnknownVersion(true); + installOptions.Force(true); + + auto progress = co_await winrt::get_progress_token(); + + // Don't use UpgradePackageAsync, we don't support upgrade for packages from the msstore + // it has to be install and internally we know is an update. + auto installTask = packageManager.InstallPackageAsync(catalogPackage, installOptions); + installTask.Progress([progress](auto const&, InstallProgress installProgress) + { + if (installProgress.State == PackageInstallProgressState::Downloading && installProgress.BytesRequired != 0) + { + progress((uint32_t)(installProgress.DownloadProgress * 80)); + } + else if (installProgress.State == PackageInstallProgressState::Installing) + { + progress(((uint32_t)installProgress.InstallationProgress * 20) + 80); + } + }); + + co_await installTask; + s_canBeCreated = false; + } + private: // Returns a lifetime watcher object that is currently *unowned*. IUnknown* CreateLifetimeWatcher() @@ -97,7 +204,7 @@ namespace ConfigurationShim return out.detach(); } - winrt::Microsoft::Management::Configuration::ConfigurationStaticFunctions m_statics; + winrt::Microsoft::Management::Configuration::ConfigurationStaticFunctions m_statics = nullptr; AppInstaller::ThreadLocalStorage::WingetThreadGlobals m_threadGlobals; }; @@ -114,6 +221,8 @@ namespace ConfigurationShim // TODO: Review of security for configuration OOP RETURN_HR_IF(E_ACCESSDENIED, !::AppInstaller::Security::IsCOMCallerSameUserAndIntegrityLevel()); + RETURN_HR_IF(CO_E_CLASS_DISABLED, !s_canBeCreated); + return ::wil::wrl_factory_for_winrt_com_class<TCppWinRTClass>::CreateInstance(unknownOuter, riid, object); } CATCH_RETURN()