winget-cli

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

commit 1b816a3d31737ab703ce30c7da41f687ccf674a7
parent ccedde1443dee4cd472f8508c135ee0f12e2a7e7
Author: Ruben Guerrero <rubengu@microsoft.com>
Date:   Wed, 21 Jun 2023 17:21:18 -0700

Stub upgrade self (#3299)

This is in preparation of the new AppInstaller bundle that will contain stub packages for all the application packages. The stub package doesn't contain any of the configuration binaries and it will be the preferred package installed inbox on Windows.

To install the main package winget configuration --enable must be called. This will detect which type of package is installed and install the full package if needed. winget configuration --disable will install the stub package.

By design we are not supporting winget configuration --file config.yaml --enable because winget itself is updating. Running the same command automatically after the update can only be done by starting a new shell window losing context of the current shell and making scripts hard to implement.

The next PR will be to handle gracefully when deployment is telling us to shut down.

Based on JohnMcPMS/stub-upgrade-self

Co-authored-by: JohnMcPMS <johnmcp@microsoft.com>
Diffstat:
M.github/actions/spelling/expect.txt | 3+++
Mdoc/windows/package-manager/winget/returnCodes.md | 1+
Msrc/AppInstallerCLICore/Argument.cpp | 4++++
Msrc/AppInstallerCLICore/Argument.h | 1+
Msrc/AppInstallerCLICore/Commands/ConfigureCommand.cpp | 52++++++++++++++++++++++++++++++++++++++++++----------
Msrc/AppInstallerCLICore/Commands/ConfigureCommand.h | 1+
Msrc/AppInstallerCLICore/ExecutionArgs.h | 2++
Msrc/AppInstallerCLICore/Resources.h | 10++++++++++
Msrc/AppInstallerCLICore/Workflows/ConfigurationFlow.cpp | 11+++++++++++
Msrc/AppInstallerCLICore/Workflows/ConfigurationFlow.h | 6++++++
Msrc/AppInstallerCLICore/Workflows/InstallFlow.cpp | 1-
Msrc/AppInstallerCLICore/Workflows/MSStoreInstallerHandler.cpp | 333+++++++++++++++++++++++++++++++------------------------------------------------
Msrc/AppInstallerCLICore/Workflows/MSStoreInstallerHandler.h | 16++++++++++++++++
Msrc/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw | 31+++++++++++++++++++++++++++++++
Msrc/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj | 4++++
Msrc/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj.filters | 12++++++++++++
Asrc/AppInstallerCommonCore/MSStore.cpp | 236+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/AppInstallerCommonCore/Public/winget/MSStore.h | 45+++++++++++++++++++++++++++++++++++++++++++++
Asrc/AppInstallerCommonCore/Public/winget/SelfManagement.h | 21+++++++++++++++++++++
Asrc/AppInstallerCommonCore/SelfManagement.cpp | 56++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/AppInstallerCommonCore/pch.h | 2++
Msrc/AppInstallerSharedLib/Errors.cpp | 2++
Msrc/AppInstallerSharedLib/Public/AppInstallerErrors.h | 1+
Msrc/AppInstallerSharedLib/Public/winget/Runtime.h | 3+++
Msrc/AppInstallerSharedLib/Runtime.cpp | 26+++++++++++++++++++++++++-
25 files changed, 664 insertions(+), 216 deletions(-)

diff --git a/.github/actions/spelling/expect.txt b/.github/actions/spelling/expect.txt @@ -179,6 +179,7 @@ insufficientmemory Intelli INTRESOURCE invalidparameter +IPackage isable ishelp ISQ @@ -260,11 +261,13 @@ mysilent mysilentwithprogress nameof nativehandle +NBLGGH NESTEDINSTALLER netfx netlify NETSDK Newtonsoft +NNS NOCRLF NOEXPAND NOLINKINFO diff --git a/doc/windows/package-manager/winget/returnCodes.md b/doc/windows/package-manager/winget/returnCodes.md @@ -116,6 +116,7 @@ ms.localizationpriority: medium | 0x8A150066 | -1978335130 | APPINSTALLER_CLI_ERROR_MULTIPLE_UNINSTALL_FAILED | One or more applications failed to uninstall | | 0x8A150067 | -1978335129 | APPINSTALLER_CLI_ERROR_NOT_ALL_QUERIES_FOUND_SINGLE | One or more queries did not return exactly one match | | 0x8A150068 | -1978335128 | APPINSTALLER_CLI_ERROR_PACKAGE_IS_PINNED | The package has a pin that prevents upgrade. | +| 0x8A150069 | -1978335127 | APPINSTALLER_CLI_ERROR_PACKAGE_IS_STUB | The package currently installed is the stub package | ## Install errors. diff --git a/src/AppInstallerCLICore/Argument.cpp b/src/AppInstallerCLICore/Argument.cpp @@ -176,6 +176,10 @@ namespace AppInstaller::CLI return { type, "file"_liv, 'f' }; case Execution::Args::Type::ConfigurationAcceptWarning: return { type, "accept-configuration-agreements"_liv }; + case Execution::Args::Type::ConfigurationEnable: + return { type, "enable"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::StubType }; + case Execution::Args::Type::ConfigurationDisable: + return { type, "disable"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::StubType }; // Common arguments case Execution::Args::Type::NoVT: diff --git a/src/AppInstallerCLICore/Argument.h b/src/AppInstallerCLICore/Argument.h @@ -80,6 +80,7 @@ namespace AppInstaller::CLI EnableDisable = 0x2, PurgePreserve = 0x4, PinType = 0x8, + StubType = 0x10, // This must always be at the end Max diff --git a/src/AppInstallerCLICore/Commands/ConfigureCommand.cpp b/src/AppInstallerCLICore/Commands/ConfigureCommand.cpp @@ -6,6 +6,7 @@ #include "ConfigureTestCommand.h" #include "ConfigureValidateCommand.h" #include "Workflows/ConfigurationFlow.h" +#include "Workflows/MSStoreInstallerHandler.h" using namespace AppInstaller::CLI::Workflow; @@ -29,9 +30,10 @@ namespace AppInstaller::CLI std::vector<Argument> ConfigureCommand::GetArguments() const { return { - // Required for now, make exclusive when history implemented - Argument{ Execution::Args::Type::ConfigurationFile, Resource::String::ConfigurationFileArgumentDescription, ArgumentType::Positional, true }, + Argument{ Execution::Args::Type::ConfigurationFile, Resource::String::ConfigurationFileArgumentDescription, ArgumentType::Positional }, Argument{ Execution::Args::Type::ConfigurationAcceptWarning, Resource::String::ConfigurationAcceptWarningArgumentDescription, ArgumentType::Flag }, + Argument{ Execution::Args::Type::ConfigurationEnable, Resource::String::ConfigurationEnableMessage, ArgumentType::Flag, Argument::Visibility::Help }, + Argument{ Execution::Args::Type::ConfigurationDisable, Resource::String::ConfigurationDisableMessage, ArgumentType::Flag, Argument::Visibility::Help }, }; } @@ -53,13 +55,43 @@ namespace AppInstaller::CLI void ConfigureCommand::ExecuteInternal(Execution::Context& context) const { - context << - VerifyFile(Execution::Args::Type::ConfigurationFile) << - CreateConfigurationProcessor << - OpenConfigurationSet << - ShowConfigurationSet << - ShowConfigurationSetConflicts << - ConfirmConfigurationProcessing << - ApplyConfigurationSet; + if (context.Args.Contains(Execution::Args::Type::ConfigurationEnable)) + { + context << + EnableConfiguration; + } + else if (context.Args.Contains(Execution::Args::Type::ConfigurationDisable)) + { + context << + DisableConfiguration; + } + else + { + context << + VerifyIsFullPackage << + VerifyFile(Execution::Args::Type::ConfigurationFile) << + CreateConfigurationProcessor << + OpenConfigurationSet << + ShowConfigurationSet << + ShowConfigurationSetConflicts << + ConfirmConfigurationProcessing << + ApplyConfigurationSet; + } + } + + void ConfigureCommand::ValidateArgumentsInternal(Execution::Args& execArgs) const + { + if (execArgs.Contains(Execution::Args::Type::ConfigurationEnable) || + execArgs.Contains(Execution::Args::Type::ConfigurationDisable)) + { + if (execArgs.GetArgsCount() > 1) + { + throw CommandException(Resource::String::ConfigurationEnableArgumentError); + } + } + else if (!execArgs.Contains(Execution::Args::Type::ConfigurationFile)) + { + throw CommandException(Resource::String::RequiredArgError("file"_liv)); + } } } diff --git a/src/AppInstallerCLICore/Commands/ConfigureCommand.h b/src/AppInstallerCLICore/Commands/ConfigureCommand.h @@ -20,5 +20,6 @@ namespace AppInstaller::CLI protected: void ExecuteInternal(Execution::Context& context) const override; + void ValidateArgumentsInternal(Execution::Args& execArgs) const override; }; } diff --git a/src/AppInstallerCLICore/ExecutionArgs.h b/src/AppInstallerCLICore/ExecutionArgs.h @@ -103,6 +103,8 @@ namespace AppInstaller::CLI::Execution // Configuration ConfigurationFile, ConfigurationAcceptWarning, + ConfigurationEnable, + ConfigurationDisable, // Common arguments NoVT, // Disable VirtualTerminal outputs diff --git a/src/AppInstallerCLICore/Resources.h b/src/AppInstallerCLICore/Resources.h @@ -53,6 +53,13 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationAssert); WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationDependencies); WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationDescriptionWasTruncated); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationDisableMessage); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationDisabledMessage); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationDisablingMessage); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationEnableArgumentError); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationEnableMessage); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationEnabledMessage); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationEnablingMessage); WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationFailedToApply); WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationFailedToGetDetails); WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationFieldInvalidType); @@ -68,6 +75,7 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationLocal); WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationModuleNameOnly); WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationModuleWithDetails); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationNotEnabledMessage); WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationReadingConfigFile); WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationSettings); WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationSuccessfullyApplied); @@ -234,8 +242,10 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowReturnCodeRebootRequiredToFinish); WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowReturnCodeSystemNotSupported); WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowStartingPackageInstall); + WINGET_DEFINE_RESOURCE_STRINGID(InstallFullPackageDescription); WINGET_DEFINE_RESOURCE_STRINGID(InstallLocationNotProvided); WINGET_DEFINE_RESOURCE_STRINGID(InstallScopeDescription); + WINGET_DEFINE_RESOURCE_STRINGID(InstallStubPackageDescription); WINGET_DEFINE_RESOURCE_STRINGID(InstallWaitingOnAnother); WINGET_DEFINE_RESOURCE_STRINGID(InteractiveArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(InvalidAliasError); diff --git a/src/AppInstallerCLICore/Workflows/ConfigurationFlow.cpp b/src/AppInstallerCLICore/Workflows/ConfigurationFlow.cpp @@ -6,6 +6,7 @@ #include "ConfigurationSetProcessorFactoryRemoting.h" #include <AppInstallerErrors.h> #include <winrt/Microsoft.Management.Configuration.h> +#include <winget/SelfManagement.h> using namespace AppInstaller::CLI::Execution; using namespace winrt::Microsoft::Management::Configuration; @@ -13,6 +14,7 @@ using namespace winrt::Windows::Foundation; using namespace winrt::Windows::Foundation::Collections; using namespace winrt::Windows::Storage; using namespace AppInstaller::Utility::literals; +using namespace AppInstaller::SelfManagement; namespace AppInstaller::CLI::Workflow { @@ -885,4 +887,13 @@ namespace AppInstaller::CLI::Workflow context.Reporter.Info() << Resource::String::ConfigurationSuccessfullyApplied << std::endl; } } + + void VerifyIsFullPackage(Execution::Context& context) + { + if (IsStubPackage()) + { + context.Reporter.Error() << Resource::String::ConfigurationNotEnabledMessage << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_PACKAGE_IS_STUB); + } + } } diff --git a/src/AppInstallerCLICore/Workflows/ConfigurationFlow.h b/src/AppInstallerCLICore/Workflows/ConfigurationFlow.h @@ -40,4 +40,10 @@ namespace AppInstaller::CLI::Workflow // Inputs: ConfigurationProcessor, ConfigurationSet // Outputs: None void ApplyConfigurationSet(Execution::Context& context); + + // Verifies the full package is installed. + // Required Args: None + // Inputs: None + // Outputs: None + void VerifyIsFullPackage(Execution::Context& context); } diff --git a/src/AppInstallerCLICore/Workflows/InstallFlow.cpp b/src/AppInstallerCLICore/Workflows/InstallFlow.cpp @@ -24,7 +24,6 @@ #include <AppInstallerSynchronization.h> #include <winget/Runtime.h> -using namespace winrt::Windows::ApplicationModel::Store::Preview::InstallControl; using namespace winrt::Windows::Foundation; using namespace winrt::Windows::Foundation::Collections; using namespace winrt::Windows::Management::Deployment; diff --git a/src/AppInstallerCLICore/Workflows/MSStoreInstallerHandler.cpp b/src/AppInstallerCLICore/Workflows/MSStoreInstallerHandler.cpp @@ -2,11 +2,13 @@ // Licensed under the MIT License. #include "pch.h" #include "MSStoreInstallerHandler.h" -#include <winget/ManifestCommon.h> -#include <winget/Runtime.h> +#include <winget/MSStore.h> +#include <winget/SelfManagement.h> namespace AppInstaller::CLI::Workflow { + using namespace AppInstaller::MSStore; + using namespace AppInstaller::SelfManagement; using namespace std::string_view_literals; using namespace winrt::Windows::Foundation; using namespace winrt::Windows::Foundation::Collections; @@ -14,270 +16,193 @@ namespace AppInstaller::CLI::Workflow namespace { - HRESULT WaitForMSStoreOperation(Execution::Context& context, IVectorView<AppInstallItem>& installItems) - { - bool isSilentMode = context.Args.Contains(Execution::Args::Type::Silent); - - for (auto const& installItem : installItems) - { - AICLI_LOG(CLI, Info, << - "Started MSStore package execution. ProductId: " << Utility::ConvertToUTF8(installItem.ProductId()) << - " PackageFamilyName: " << Utility::ConvertToUTF8(installItem.PackageFamilyName())); - - if (isSilentMode) - { - installItem.InstallInProgressToastNotificationMode(AppInstallationToastNotificationMode::NoToast); - installItem.CompletedInstallToastNotificationMode(AppInstallationToastNotificationMode::NoToast); - } - } - - HRESULT errorCode = S_OK; - context.Reporter.ExecuteWithProgress( - [&](IProgressCallback& progress) - { - // We are aggregating all AppInstallItem progresses into one. - // Averaging every progress for now until we have a better way to find overall progress. - uint64_t overallProgressMax = 100 * static_cast<uint64_t>(installItems.Size()); - uint64_t currentProgress = 0; - - while (currentProgress < overallProgressMax) - { - currentProgress = 0; - - for (auto const& installItem : installItems) - { - const auto& status = installItem.GetCurrentStatus(); - currentProgress += static_cast<uint64_t>(status.PercentComplete()); + static constexpr std::wstring_view s_AppInstallerProductId = L"9NBLGGH4NNS1"sv; - errorCode = status.ErrorCode(); - - if (!SUCCEEDED(errorCode)) - { - return; - } - } - - // It may take a while for Store client to pick up the install request. - // So we show indefinite progress here to avoid a progress bar stuck at 0. - if (currentProgress > 0) - { - progress.OnProgress(currentProgress, overallProgressMax, ProgressType::Percent); - } - - if (progress.IsCancelled()) - { - for (auto const& installItem : installItems) - { - installItem.Cancel(); - } - } - - Sleep(100); - } - }); - - return errorCode; + Utility::LocIndString GetErrorCodeString(const HRESULT errorCode) + { + std::ostringstream ssError; + ssError << WINGET_OSTREAM_FORMAT_HRESULT(errorCode); + return Utility::LocIndString{ ssError.str() }; } - // The type of entitlement we were able to acquire/ensure. - enum class EntitlementType + HRESULT EnsureStorePolicySatisfiedImpl(const std::wstring& productId, bool bypassPolicy) { - None, - User, - Device, - }; + constexpr std::wstring_view s_StoreClientName = L"Microsoft.WindowsStore"sv; + constexpr std::wstring_view s_StoreClientPublisher = L"CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US"sv; - EntitlementType EnsureFreeEntitlement(Execution::Context& context, const std::wstring& productId) - { + // Policy check AppInstallManager installManager; - AICLI_LOG(CLI, Error, << "Getting entitlement for ProductId: " << Utility::ConvertToUTF8(productId)); - - // Verifying/Acquiring product ownership - context.Reporter.Info() << Resource::String::MSStoreInstallTryGetEntitlement << std::endl; - - GetEntitlementResult entitlementResult{ nullptr }; - EntitlementType result = EntitlementType::None; - - if (Manifest::ConvertToScopeEnum(context.Args.GetArg(Execution::Args::Type::InstallScope)) == Manifest::ScopeEnum::Machine) + if (!bypassPolicy && installManager.IsStoreBlockedByPolicyAsync(s_StoreClientName, s_StoreClientPublisher).get()) { - AICLI_LOG(CLI, Info, << "Get device entitlement (machine scope install)."); - result = EntitlementType::Device; - entitlementResult = installManager.GetFreeDeviceEntitlementAsync(productId, winrt::hstring(), winrt::hstring()).get(); - } - else - { - AICLI_LOG(CLI, Info, << "Get user entitlement."); - result = EntitlementType::User; - entitlementResult = installManager.GetFreeUserEntitlementAsync(productId, winrt::hstring(), winrt::hstring()).get(); - - if (entitlementResult.Status() == GetEntitlementStatus::NoStoreAccount) - { - AICLI_LOG(CLI, Info, << "Get device entitlement (no store account)."); - result = EntitlementType::Device; - entitlementResult = installManager.GetFreeDeviceEntitlementAsync(productId, winrt::hstring(), winrt::hstring()).get(); - } + AICLI_LOG(CLI, Error, << "Store client is blocked by policy. MSStore execution failed."); + return APPINSTALLER_CLI_ERROR_MSSTORE_BLOCKED_BY_POLICY; } - if (entitlementResult.Status() == GetEntitlementStatus::Succeeded) + if (!installManager.GetIsAppAllowedToInstallAsync(productId).get()) { - AICLI_LOG(CLI, Info, << "Get entitlement succeeded."); + AICLI_LOG(CLI, Error, << "App is blocked by policy. MSStore execution failed. ProductId: " << Utility::ConvertToUTF8(productId)); + return APPINSTALLER_CLI_ERROR_MSSTORE_APP_BLOCKED_BY_POLICY; } - else - { - result = EntitlementType::None; - if (entitlementResult.Status() == GetEntitlementStatus::NetworkError) - { - AICLI_LOG(CLI, Error, << "Get entitlement failed. Network error."); - } - else if (entitlementResult.Status() == GetEntitlementStatus::ServerError) - { - AICLI_LOG(CLI, Error, << "Get entitlement failed. Server error."); - } - else + return S_OK; + } + + void AppInstallerUpdate(bool preferStub, bool bypassPolicy, Execution::Context& context) + { + auto appInstId = std::wstring{ s_AppInstallerProductId }; + THROW_IF_FAILED(EnsureStorePolicySatisfiedImpl(appInstId, bypassPolicy)); + SetStubPreferred(preferStub); + + auto installOperation = MSStoreOperation(MSStoreOperationType::Update, appInstId, Manifest::ScopeEnum::User, true, true); + + HRESULT hr = S_OK; + context.Reporter.ExecuteWithProgress( + [&](IProgressCallback& progress) { - AICLI_LOG(CLI, Error, << "Get entitlement failed. Unknown status: " << static_cast<int32_t>(entitlementResult.Status())); - } - } + hr = installOperation.StartAndWaitForOperation(progress); + }); - return result; + THROW_IF_FAILED(hr); } } - Utility::LocIndString GetErrorCodeString(const HRESULT errorCode) - { - std::ostringstream ssError; - ssError << WINGET_OSTREAM_FORMAT_HRESULT(errorCode); - return Utility::LocIndString{ ssError.str() }; - } - void MSStoreInstall(Execution::Context& context) { auto productId = Utility::ConvertToUTF16(context.Get<Execution::Data::Installer>()->ProductId); + auto scope = Manifest::ConvertToScopeEnum(context.Args.GetArg(Execution::Args::Type::InstallScope)); + bool isSilentMode = context.Args.Contains(Execution::Args::Type::Silent); + bool force = context.Args.Contains(Execution::Args::Type::Force); - // Best effort verifying/acquiring product ownership. - std::ignore = EnsureFreeEntitlement(context, productId); - - AppInstallManager installManager; - AppInstallOptions installOptions; + auto installOperation = MSStoreOperation(MSStoreOperationType::Install, productId, scope, isSilentMode, force); context.Reporter.Info() << Resource::String::InstallFlowStartingPackageInstall << std::endl; - if (context.Args.Contains(Execution::Args::Type::Silent)) + HRESULT hr = S_OK; + context.Reporter.ExecuteWithProgress( + [&](IProgressCallback& progress) + { + hr = installOperation.StartAndWaitForOperation(progress); + }); + + if (SUCCEEDED(hr)) { - installOptions.InstallInProgressToastNotificationMode(AppInstallationToastNotificationMode::NoToast); - installOptions.CompletedInstallToastNotificationMode(AppInstallationToastNotificationMode::NoToast); + context.Reporter.Info() << Resource::String::InstallFlowInstallSuccess << std::endl; } - - if (Manifest::ConvertToScopeEnum(context.Args.GetArg(Execution::Args::Type::InstallScope)) == Manifest::ScopeEnum::Machine) + else { - // TODO: There was a bug in InstallService where admin user is incorrectly identified as not admin, - // causing false access denied on many OS versions. - // Remove this check when the OS bug is fixed and back ported. - if (!Runtime::IsRunningAsSystem()) + if (hr == APPINSTALLER_CLI_ERROR_INSTALL_SYSTEM_NOT_SUPPORTED) { context.Reporter.Error() << Resource::String::InstallFlowReturnCodeSystemNotSupported << std::endl; context.Add<Execution::Data::OperationReturnCode>(static_cast<DWORD>(APPINSTALLER_CLI_ERROR_INSTALL_SYSTEM_NOT_SUPPORTED)); - AICLI_LOG(CLI, Error, << "Device wide install for msstore type is not supported under admin context."); - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_INSTALL_SYSTEM_NOT_SUPPORTED); + } + else + { + auto errorCodeString = GetErrorCodeString(hr); + context.Reporter.Error() << Resource::String::MSStoreInstallOrUpdateFailed(errorCodeString) << std::endl; + context.Add<Execution::Data::OperationReturnCode>(hr); + AICLI_LOG(CLI, Error, << "MSStore install failed. ProductId: " << Utility::ConvertToUTF8(productId) << " HResult: " << errorCodeString); } - installOptions.InstallForAllUsers(true); + AICLI_TERMINATE_CONTEXT(hr); } + } - IVectorView<AppInstallItem> installItems = installManager.StartProductInstallAsync( - productId, // ProductId - winrt::hstring(), // FlightId - L"WinGetCli", // ClientId - winrt::hstring(), - installOptions).get(); + void MSStoreUpdate(Execution::Context& context) + { + bool isSilentMode = context.Args.Contains(Execution::Args::Type::Silent); + auto productId = Utility::ConvertToUTF16(context.Get<Execution::Data::Installer>()->ProductId); + auto scope = Manifest::ConvertToScopeEnum(context.Args.GetArg(Execution::Args::Type::InstallScope)); + bool force = context.Args.Contains(Execution::Args::Type::Force); - HRESULT errorCode = WaitForMSStoreOperation(context, installItems); + auto installOperation = MSStoreOperation(MSStoreOperationType::Update, productId, scope, isSilentMode, force); - if (SUCCEEDED(errorCode)) + context.Reporter.Info() << Resource::String::InstallFlowStartingPackageInstall << std::endl; + + HRESULT hr = S_OK; + context.Reporter.ExecuteWithProgress( + [&](IProgressCallback& progress) + { + hr = installOperation.StartAndWaitForOperation(progress); + }); + + if (SUCCEEDED(hr)) { context.Reporter.Info() << Resource::String::InstallFlowInstallSuccess << std::endl; } else { - auto errorCodeString = GetErrorCodeString(errorCode); - context.Reporter.Error() << Resource::String::MSStoreInstallOrUpdateFailed(errorCodeString) << std::endl; - context.Add<Execution::Data::OperationReturnCode>(errorCode); - AICLI_LOG(CLI, Error, << "MSStore install failed. ProductId: " << Utility::ConvertToUTF8(productId) << " HResult: " << errorCodeString); - AICLI_TERMINATE_CONTEXT(errorCode); + if (hr == APPINSTALLER_CLI_ERROR_UPDATE_NOT_APPLICABLE) + { + context.Reporter.Info() << Resource::String::UpdateNotApplicable << std::endl + << Resource::String::UpdateNotApplicableReason << std::endl; + } + else + { + auto errorCodeString = GetErrorCodeString(hr); + context.Reporter.Error() << Resource::String::MSStoreInstallOrUpdateFailed(errorCodeString) << std::endl; + context.Add<Execution::Data::OperationReturnCode>(hr); + AICLI_LOG(CLI, Error, << "MSStore execution failed. ProductId: " << Utility::ConvertToUTF8(productId) << " HResult: " << errorCodeString); + } + + AICLI_TERMINATE_CONTEXT(hr); } } - void MSStoreUpdate(Execution::Context& context) + void EnsureStorePolicySatisfied(Execution::Context& context) { auto productId = Utility::ConvertToUTF16(context.Get<Execution::Data::Installer>()->ProductId); + bool bypassStorePolicy = WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::BypassIsStoreClientBlockedPolicyCheck); - // Best effort verifying/acquiring product ownership. - std::ignore = EnsureFreeEntitlement(context, productId); - - AppInstallManager installManager; - AppUpdateOptions updateOptions; - - context.Reporter.Info() << Resource::String::InstallFlowStartingPackageInstall << std::endl; - - // SearchForUpdateAsync will automatically trigger update if found. - AppInstallItem installItem = installManager.SearchForUpdatesAsync( - productId, // ProductId - winrt::hstring(), // SkuId - winrt::hstring(), - winrt::hstring(), // ClientId - updateOptions - ).get(); - - if (!installItem) + HRESULT hr = EnsureStorePolicySatisfiedImpl(productId, bypassStorePolicy); + if (FAILED(hr)) { - context.Reporter.Info() << Resource::String::UpdateNotApplicable << std::endl - << Resource::String::UpdateNotApplicableReason << std::endl; - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_UPDATE_NOT_APPLICABLE); - } - - std::vector<AppInstallItem> installItemVector{ installItem }; - IVectorView<AppInstallItem> installItems = winrt::single_threaded_vector(std::move(installItemVector)).GetView(); + if (hr == APPINSTALLER_CLI_ERROR_MSSTORE_BLOCKED_BY_POLICY) + { + context.Reporter.Error() << Resource::String::MSStoreStoreClientBlocked << std::endl; + } + else if (hr == APPINSTALLER_CLI_ERROR_MSSTORE_APP_BLOCKED_BY_POLICY) + { + context.Reporter.Error() << Resource::String::MSStoreAppBlocked << std::endl; + } - HRESULT errorCode = WaitForMSStoreOperation(context, installItems); + AICLI_TERMINATE_CONTEXT(hr); + } + } - if (SUCCEEDED(errorCode)) + void EnableConfiguration(Execution::Context& context) + { +#ifndef AICLI_DISABLE_TEST_HOOKS + AppInstallerUpdate(false, true, context); +#else + if (IsStubPackage()) { - context.Reporter.Info() << Resource::String::InstallFlowInstallSuccess << std::endl; + context.Reporter.Info() << Resource::String::ConfigurationEnablingMessage << std::endl; + bool bypassStorePolicy = WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::BypassIsStoreClientBlockedPolicyCheck); + AppInstallerUpdate(false, bypassStorePolicy, context.Reporter); } else { - auto errorCodeString = GetErrorCodeString(errorCode); - context.Reporter.Info() << Resource::String::MSStoreInstallOrUpdateFailed(errorCodeString) << std::endl; - context.Add<Execution::Data::OperationReturnCode>(errorCode); - AICLI_LOG(CLI, Error, << "MSStore execution failed. ProductId: " << Utility::ConvertToUTF8(productId) << " HResult: " << errorCodeString); - AICLI_TERMINATE_CONTEXT(errorCode); + context.Reporter.Info() << Resource::String::ConfigurationEnabledMessage << std::endl; } +#endif } - void EnsureStorePolicySatisfied(Execution::Context& context) + void DisableConfiguration(Execution::Context& context) { - auto productId = Utility::ConvertToUTF16(context.Get<Execution::Data::Installer>()->ProductId); - - constexpr std::wstring_view s_StoreClientName = L"Microsoft.WindowsStore"sv; - constexpr std::wstring_view s_StoreClientPublisher = L"CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US"sv; - - // Policy check - AppInstallManager installManager; - - if (!WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::BypassIsStoreClientBlockedPolicyCheck) && installManager.IsStoreBlockedByPolicyAsync(s_StoreClientName, s_StoreClientPublisher).get()) +#ifndef AICLI_DISABLE_TEST_HOOKS + AppInstallerUpdate(true, true, context); +#else + if (!IsStubPackage()) { - context.Reporter.Error() << Resource::String::MSStoreStoreClientBlocked << std::endl; - AICLI_LOG(CLI, Error, << "Store client is blocked by policy. MSStore execution failed."); - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_MSSTORE_BLOCKED_BY_POLICY); + context.Reporter.Info() << Resource::String::ConfigurationDisablingMessage << std::endl; + bool bypassStorePolicy = WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::BypassIsStoreClientBlockedPolicyCheck); + AppInstallerUpdate(true, bypassStorePolicy, context); } - - if (!installManager.GetIsAppAllowedToInstallAsync(productId).get()) + else { - context.Reporter.Error() << Resource::String::MSStoreAppBlocked << std::endl; - AICLI_LOG(CLI, Error, << "App is blocked by policy. MSStore execution failed. ProductId: " << Utility::ConvertToUTF8(productId)); - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_MSSTORE_APP_BLOCKED_BY_POLICY); + context.Reporter.Info() << Resource::String::ConfigurationDisabledMessage << std::endl; } +#endif } } diff --git a/src/AppInstallerCLICore/Workflows/MSStoreInstallerHandler.h b/src/AppInstallerCLICore/Workflows/MSStoreInstallerHandler.h @@ -25,4 +25,20 @@ namespace AppInstaller::CLI::Workflow // Inputs: Installer // Outputs: None void EnsureStorePolicySatisfied(Execution::Context& context); + + // Change stub preference to full and installs full package if needed. + // This should go into configuration flow once installing from the store is + // moved out of this work flow. + // Required Args: None + // Inputs: None + // Outputs: None + void EnableConfiguration(Execution::Context& context); + + // Change stub preference to stub and installs stub package if needed. + // This should go into configuration flow once installing from the store is + // moved out of this work flow. + // Required Args: None + // Inputs: None + // Outputs: None + void DisableConfiguration(Execution::Context& context); } \ No newline at end of file diff --git a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw @@ -1939,4 +1939,35 @@ Please specify one of them using the --source option to proceed.</value> <data name="CancellingOperation" xml:space="preserve"> <value>Cancelling operation</value> </data> + <data name="InstallStubPackageDescription" xml:space="preserve"> + <value>Install the stub package for AppInstaller</value> + </data> + <data name="InstallFullPackageDescription" xml:space="preserve"> + <value>Install the full package for AppInstaller</value> + </data> + <data name="ConfigurationEnableMessage" xml:space="preserve"> + <value>Enable configuration components. Requires store access.</value> + </data> + <data name="ConfigurationEnableArgumentError" xml:space="preserve"> + <value>Option '--enable' and '--disable' cannot be used with other arguments.</value> + <comment>{Locked="--enable", "--disable"}</comment> + </data> + <data name="ConfigurationEnablingMessage" xml:space="preserve"> + <value>Enabling configuration components. Requires store access.</value> + </data> + <data name="ConfigurationNotEnabledMessage" xml:space="preserve"> + <value>Configuration is not enabled. Run `winget configure --enable` to enable it.</value> + </data> + <data name="ConfigurationEnabledMessage" xml:space="preserve"> + <value>Configuration is enabled.</value> + </data> + <data name="ConfigurationDisableMessage" xml:space="preserve"> + <value>Disable configuration components. Requires store access.</value> + </data> + <data name="ConfigurationDisablingMessage" xml:space="preserve"> + <value>Disabling configuration components. Requires store access.</value> + </data> + <data name="ConfigurationDisabledMessage" xml:space="preserve"> + <value>Configuration is disabled.</value> + </data> </root> \ No newline at end of file diff --git a/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj b/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj @@ -412,6 +412,7 @@ <ClInclude Include="Public\winget\ManifestYamlPopulator.h" /> <ClInclude Include="Public\winget\MsiExecArguments.h" /> <ClInclude Include="Public\winget\MsixManifestValidation.h" /> + <ClInclude Include="Public\winget\MSStore.h" /> <ClInclude Include="Public\winget\NameNormalization.h" /> <ClInclude Include="Public\winget\Filesystem.h" /> <ClInclude Include="Public\winget\PackageDependenciesValidationUtil.h" /> @@ -422,6 +423,7 @@ <ClInclude Include="Public\winget\PortableARPEntry.h" /> <ClInclude Include="Public\winget\PortableFileEntry.h" /> <ClInclude Include="Public\winget\ManifestSchemaValidation.h" /> + <ClInclude Include="Public\winget\SelfManagement.h" /> <ClInclude Include="Public\winget\Settings.h" /> <ClInclude Include="Public\winget\ThreadGlobals.h" /> <ClInclude Include="Public\winget\TraceLogger.h" /> @@ -473,6 +475,7 @@ <ExcludedFromBuild Condition="'$(Configuration)'=='Fuzzing'">true</ExcludedFromBuild> </ClCompile> <ClCompile Include="MsixManifest.cpp" /> + <ClCompile Include="MSStore.cpp" /> <ClCompile Include="NameNormalization.cpp" /> <ClCompile Include="PackageDependenciesValidationUtil.cpp" /> <ClCompile Include="Pin.cpp" /> @@ -485,6 +488,7 @@ </ClCompile> <ClCompile Include="AppInstallerTelemetry.cpp" /> <ClCompile Include="Archive.cpp" /> + <ClCompile Include="SelfManagement.cpp" /> <ClCompile Include="Settings.cpp" /> <ClCompile Include="PathVariable.cpp" /> <ClCompile Include="PortableARPEntry.cpp" /> diff --git a/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj.filters b/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj.filters @@ -186,6 +186,12 @@ <ClInclude Include="Public\winget\WindowsFeature.h"> <Filter>Public\winget</Filter> </ClInclude> + <ClInclude Include="Public\winget\SelfManagement.h"> + <Filter>Public\winget</Filter> + </ClInclude> + <ClInclude Include="Public\winget\MSStore.h"> + <Filter>Public\winget</Filter> + </ClInclude> </ItemGroup> <ItemGroup> <ClCompile Include="pch.cpp"> @@ -335,6 +341,12 @@ <ClCompile Include="WindowsFeature.cpp"> <Filter>Source Files</Filter> </ClCompile> + <ClCompile Include="SelfManagement.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="MSStore.cpp"> + <Filter>Source Files</Filter> + </ClCompile> </ItemGroup> <ItemGroup> <None Include="PropertySheet.props" /> diff --git a/src/AppInstallerCommonCore/MSStore.cpp b/src/AppInstallerCommonCore/MSStore.cpp @@ -0,0 +1,236 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#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; + using namespace winrt::Windows::ApplicationModel::Store::Preview::InstallControl; + + namespace + { + // The type of entitlement we were able to acquire/ensure. + enum class EntitlementType + { + None, + User, + Device, + }; + + EntitlementType EnsureFreeEntitlement(const std::wstring& productId, Manifest::ScopeEnum scope) + { + AppInstallManager installManager; + + AICLI_LOG(Core, Info, << "Getting entitlement for ProductId: " << Utility::ConvertToUTF8(productId)); + + // Verifying/Acquiring product ownership + GetEntitlementResult entitlementResult{ nullptr }; + EntitlementType result = EntitlementType::None; + + if (scope == Manifest::ScopeEnum::Machine) + { + AICLI_LOG(Core, Info, << "Get device entitlement (machine scope install)."); + result = EntitlementType::Device; + try + { + entitlementResult = installManager.GetFreeDeviceEntitlementAsync(productId, winrt::hstring(), winrt::hstring()).get(); + } + CATCH_LOG(); + } + else + { + AICLI_LOG(Core, Info, << "Get user entitlement."); + result = EntitlementType::User; + try + { + entitlementResult = installManager.GetFreeUserEntitlementAsync(productId, winrt::hstring(), winrt::hstring()).get(); + } + CATCH_LOG(); + + if (!entitlementResult || entitlementResult.Status() == GetEntitlementStatus::NoStoreAccount) + { + AICLI_LOG(Core, Info, << "Get device entitlement (no store account)."); + result = EntitlementType::Device; + try + { + entitlementResult = installManager.GetFreeDeviceEntitlementAsync(productId, winrt::hstring(), winrt::hstring()).get(); + } + CATCH_LOG(); + } + } + + if (entitlementResult && entitlementResult.Status() == GetEntitlementStatus::Succeeded) + { + AICLI_LOG(Core, Info, << "Get entitlement succeeded."); + } + else if (entitlementResult) + { + result = EntitlementType::None; + + if (entitlementResult.Status() == GetEntitlementStatus::NetworkError) + { + AICLI_LOG(Core, Error, << "Get entitlement failed. Network error."); + } + else if (entitlementResult.Status() == GetEntitlementStatus::ServerError) + { + AICLI_LOG(Core, Error, << "Get entitlement failed. Server error."); + } + else + { + AICLI_LOG(Core, Error, << "Get entitlement failed. Unknown status: " << static_cast<int32_t>(entitlementResult.Status())); + } + } + else + { + result = EntitlementType::None; + AICLI_LOG(Core, Error, << "Get entitlement failed. Exception."); + } + + return result; + } + } + + HRESULT MSStoreOperation::StartAndWaitForOperation(IProgressCallback& progress) + { + // Best effort verifying/acquiring product ownership. + std::ignore = EnsureFreeEntitlement(m_productId, m_scope); + + if (m_type == MSStoreOperationType::Install) + { + return InstallPackage(progress); + } + + return UpdatePackage(progress); + } + + HRESULT MSStoreOperation::InstallPackage(IProgressCallback& progress) + { + AppInstallManager installManager; + AppInstallOptions installOptions; + + installOptions.AllowForcedAppRestart(m_force); + if (m_isSilentMode) + { + installOptions.InstallInProgressToastNotificationMode(AppInstallationToastNotificationMode::NoToast); + installOptions.CompletedInstallToastNotificationMode(AppInstallationToastNotificationMode::NoToast); + } + + if (m_scope == Manifest::ScopeEnum::Machine) + { + // TODO: There was a bug in InstallService where admin user is incorrectly identified as not admin, + // causing false access denied on many OS versions. + // Remove this check when the OS bug is fixed and back ported. + if (!Runtime::IsRunningAsSystem()) + { + AICLI_LOG(Core, Error, << "Device wide install for msstore type is not supported under admin context."); + return APPINSTALLER_CLI_ERROR_INSTALL_SYSTEM_NOT_SUPPORTED; + } + + installOptions.InstallForAllUsers(true); + } + + IVectorView<AppInstallItem> installItems = installManager.StartProductInstallAsync( + m_productId, // ProductId + winrt::hstring(), // FlightId + L"WinGetCli", // ClientId + winrt::hstring(), + installOptions).get(); + + return WaitForOperation(installItems, progress); + } + + HRESULT MSStoreOperation::UpdatePackage(IProgressCallback& progress) + { + AppInstallManager installManager; + AppUpdateOptions updateOptions; + updateOptions.AllowForcedAppRestart(m_force); + + // SearchForUpdateAsync will automatically trigger update if found. + AppInstallItem installItem = installManager.SearchForUpdatesAsync( + m_productId, // ProductId + winrt::hstring(), // SkuId + winrt::hstring(), + winrt::hstring(), // ClientId + updateOptions + ).get(); + + if (!installItem) + { + return APPINSTALLER_CLI_ERROR_UPDATE_NOT_APPLICABLE; + } + + std::vector<AppInstallItem> installItemVector{ installItem }; + IVectorView<AppInstallItem> installItems = winrt::single_threaded_vector(std::move(installItemVector)).GetView(); + + return WaitForOperation(installItems, progress); + } + + HRESULT MSStoreOperation::WaitForOperation(IVectorView<AppInstallItem>& installItems, IProgressCallback& progress) + { + for (auto const& installItem : installItems) + { + AICLI_LOG(Core, Info, << + "Started MSStore package execution. ProductId: " << Utility::ConvertToUTF8(installItem.ProductId()) << + " PackageFamilyName: " << Utility::ConvertToUTF8(installItem.PackageFamilyName())); + + if (m_isSilentMode) + { + installItem.InstallInProgressToastNotificationMode(AppInstallationToastNotificationMode::NoToast); + installItem.CompletedInstallToastNotificationMode(AppInstallationToastNotificationMode::NoToast); + } + } + + HRESULT errorCode = S_OK; + + // We are aggregating all AppInstallItem progresses into one. + // Averaging every progress for now until we have a better way to find overall progress. + uint64_t overallProgressMax = 100 * static_cast<uint64_t>(installItems.Size()); + uint64_t currentProgress = 0; + + while (currentProgress < overallProgressMax) + { + currentProgress = 0; + + for (auto const& installItem : installItems) + { + const auto& status = installItem.GetCurrentStatus(); + currentProgress += static_cast<uint64_t>(status.PercentComplete()); + + errorCode = status.ErrorCode(); + + if (!SUCCEEDED(errorCode)) + { + return errorCode; + } + } + + // It may take a while for Store client to pick up the install request. + // So we show indefinite progress here to avoid a progress bar stuck at 0. + if (currentProgress > 0) + { + progress.OnProgress(currentProgress, overallProgressMax, ProgressType::Percent); + } + + if (progress.IsCancelled()) + { + for (auto const& installItem : installItems) + { + installItem.Cancel(); + } + } + + Sleep(100); + } + + return errorCode; + } +} diff --git a/src/AppInstallerCommonCore/Public/winget/MSStore.h b/src/AppInstallerCommonCore/Public/winget/MSStore.h @@ -0,0 +1,44 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Manifest.h" +#include <AppInstallerProgress.h> + +#include <winrt/Windows.Foundation.Collections.h> +#include <winrt/Windows.ApplicationModel.Store.Preview.InstallControl.h> + +namespace AppInstaller::MSStore +{ + enum class MSStoreOperationType + { + Install, + Update, + }; + + struct MSStoreOperation + { + MSStoreOperation(MSStoreOperationType type, const std::wstring& productId, Manifest::ScopeEnum scope, bool isSilentMode, bool force) : + m_type(type), m_productId(productId), m_scope(scope), m_isSilentMode(isSilentMode), m_force(force) + { + } + + MSStoreOperation(const MSStoreOperation&) = delete; + MSStoreOperation& operator=(const MSStoreOperation&) = delete; + + MSStoreOperation(MSStoreOperation&&) = delete; + MSStoreOperation& operator=(MSStoreOperation&&) = delete; + + HRESULT StartAndWaitForOperation(IProgressCallback& progress); + + private: + HRESULT InstallPackage(IProgressCallback& progress); + HRESULT UpdatePackage(IProgressCallback& progress); + HRESULT WaitForOperation(winrt::Windows::Foundation::Collections::IVectorView<winrt::Windows::ApplicationModel::Store::Preview::InstallControl::AppInstallItem>& installItems, IProgressCallback& progress); + + MSStoreOperationType m_type; + std::wstring m_productId; + Manifest::ScopeEnum m_scope; + bool m_isSilentMode; + bool m_force; + }; +}+ \ No newline at end of file diff --git a/src/AppInstallerCommonCore/Public/winget/SelfManagement.h b/src/AppInstallerCommonCore/Public/winget/SelfManagement.h @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include <AppInstallerProgress.h> + +namespace AppInstaller::SelfManagement +{ + // Gets the stub preference for the current package. + // Returns true if the package is set to prefer stubs. + // Returns false if the package is set to prefer the full package, + // or the current process is not packaged. + bool IsStubPreferred(); + + // Sets the stub preference for the current package. + // It is an error to set the preference if the process is not packaged, + // or the preference can otherwise not be set (older version of Windows). + void SetStubPreferred(bool preferStub); + + // Gets a value indicating whether the current package is the stub package. + bool IsStubPackage(); +} diff --git a/src/AppInstallerCommonCore/SelfManagement.cpp b/src/AppInstallerCommonCore/SelfManagement.cpp @@ -0,0 +1,56 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "winget/ExperimentalFeature.h" +#include "winget/SelfManagement.h" +#include "AppInstallerRuntime.h" + +namespace AppInstaller::SelfManagement +{ + using namespace AppInstaller::Settings; + using namespace std::string_view_literals; + using namespace winrt::Windows::ApplicationModel; + using namespace winrt::Windows::Management::Deployment; + using namespace winrt::Windows::Services::Store; + + // Always use AppInstaller's package family name for wingetdev + static constexpr std::wstring_view s_AppInstallerPfn = L"Microsoft.DesktopAppInstaller_8wekyb3d8bbwe"sv; + constexpr std::wstring_view s_RemoteServerFileName = L"ConfigurationRemotingServer\\ConfigurationRemotingServer.exe"; + + bool IsStubPreferred() + { + winrt::hstring packageFamilyName{ s_AppInstallerPfn }; + + PackageManager packageManager; + auto packageManager9 = packageManager.try_as<IPackageManager9>(); + + if (!packageManager9) + { + // If the API isn't present, then the only option is full package. + return false; + } + + auto preference = packageManager9.GetPackageStubPreference(packageFamilyName); + + return preference == PackageStubPreference::Stub; + } + + void SetStubPreferred(bool preferStub) + { + winrt::hstring packageFamilyName{ s_AppInstallerPfn }; + + PackageManager packageManager; + auto packageManager9 = packageManager.try_as<IPackageManager9>(); + + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_OLD_WIN_VERSION), !packageManager9); + + packageManager9.SetPackageStubPreference(packageFamilyName, preferStub ? PackageStubPreference::Stub : PackageStubPreference::Full); + } + + bool IsStubPackage() + { + // The right way to do it is to call FindPackage APIs from PackageManager, but that requires admin. + std::filesystem::path serverPath = Runtime::GetPathTo(Runtime::PathName::SelfPackageRoot) / s_RemoteServerFileName; + return !std::filesystem::exists(serverPath); + } +} diff --git a/src/AppInstallerCommonCore/pch.h b/src/AppInstallerCommonCore/pch.h @@ -81,6 +81,7 @@ #include <winrt/Windows.Foundation.Collections.h> #include <winrt/Windows.Management.Deployment.h> #include <winrt/Windows.Security.Cryptography.h> +#include <winrt/Windows.Services.Store.h> #include <winrt/Windows.Storage.h> #include <winrt/Windows.Storage.Streams.h> #include <winrt/Windows.System.Profile.h> @@ -89,6 +90,7 @@ #include <winrt/Windows.Web.Http.Headers.h> #include <winrt/Windows.Web.Http.Filters.h> #include <winrt/Windows.Globalization.h> +#include <winrt/Windows.ApplicationModel.Store.Preview.InstallControl.h> #endif diff --git a/src/AppInstallerSharedLib/Errors.cpp b/src/AppInstallerSharedLib/Errors.cpp @@ -222,6 +222,8 @@ namespace AppInstaller return "One or more queries did not return exactly one match"; case APPINSTALLER_CLI_ERROR_PACKAGE_IS_PINNED: return "The package has a pin that prevents upgrade."; + case APPINSTALLER_CLI_ERROR_PACKAGE_IS_STUB: + return "The package currently installed is the stub package"; // Install errors case APPINSTALLER_CLI_ERROR_INSTALL_PACKAGE_IN_USE: diff --git a/src/AppInstallerSharedLib/Public/AppInstallerErrors.h b/src/AppInstallerSharedLib/Public/AppInstallerErrors.h @@ -117,6 +117,7 @@ #define APPINSTALLER_CLI_ERROR_MULTIPLE_UNINSTALL_FAILED ((HRESULT)0x8A150066) #define APPINSTALLER_CLI_ERROR_NOT_ALL_QUERIES_FOUND_SINGLE ((HRESULT)0x8A150067) #define APPINSTALLER_CLI_ERROR_PACKAGE_IS_PINNED ((HRESULT)0x8A150068) +#define APPINSTALLER_CLI_ERROR_PACKAGE_IS_STUB ((HRESULT)0x8A150069) // Install errors. #define APPINSTALLER_CLI_ERROR_INSTALL_PACKAGE_IN_USE ((HRESULT)0x8A150101) diff --git a/src/AppInstallerSharedLib/Public/winget/Runtime.h b/src/AppInstallerSharedLib/Public/winget/Runtime.h @@ -17,6 +17,9 @@ namespace AppInstaller::Runtime // Determines the current version of the client and returns it. Utility::LocIndString GetClientVersion(); + // Gets the package family name of the current package (or empty string if not packaged). + std::wstring GetPackageFamilyName(); + // Determines the current version of the package if running in a packaged context. Utility::LocIndString GetPackageVersion(); diff --git a/src/AppInstallerSharedLib/Runtime.cpp b/src/AppInstallerSharedLib/Runtime.cpp @@ -20,7 +20,7 @@ namespace AppInstaller::Runtime bool DoesCurrentProcessHaveIdentity() { UINT32 length = 0; - LONG result = GetPackageFamilyName(GetCurrentProcess(), &length, nullptr); + LONG result = ::GetPackageFamilyName(GetCurrentProcess(), &length, nullptr); return (result != APPMODEL_ERROR_NO_PACKAGE); } @@ -87,6 +87,30 @@ namespace AppInstaller::Runtime return LocIndString{ strstr.str() }; } + std::wstring GetPackageFamilyName() + { + UINT32 length = 0; + LONG returnValue = ::GetPackageFamilyName(GetCurrentProcess(), &length, nullptr); + + if (returnValue == APPMODEL_ERROR_NO_PACKAGE) + { + return {}; + } + + if (returnValue != ERROR_INSUFFICIENT_BUFFER) + { + THROW_IF_WIN32_ERROR(returnValue); + } + + std::wstring result(length, '\0'); + returnValue = ::GetPackageFamilyName(GetCurrentProcess(), &length, &result[0]); + THROW_IF_WIN32_ERROR(returnValue); + THROW_HR_IF(E_UNEXPECTED, length == 0); + + result.resize(length - 1); + return result; + } + LocIndString GetPackageVersion() { using namespace std::string_literals;