commit 5f7b46bad68161b06bda2d3fbff8b9e9132ec927 parent 4139d79bfc3fd23d3e120716453e3f4c4b9a5735 Author: Ryan <69221034+ryfu-msft@users.noreply.github.com> Date: Fri, 26 Apr 2024 02:35:15 -0400 Add initial support for msstore download experimental feature (#4386) Changes: Adds a new experimental feature flag called `storedownload`. Utilizes the HTTPClientHelper to make a get request to the msstore rest api to retrieve the wucategory for a given msstore package (to be consumed later). Also fixes fuzzing build... Verified manually. Diffstat:
23 files changed, 726 insertions(+), 27 deletions(-)
diff --git a/.github/actions/spelling/expect.txt b/.github/actions/spelling/expect.txt @@ -99,6 +99,7 @@ desktopappinstaller devhome dic diskfull +displaycatalog DISMAPI dnld Dobbeleer @@ -107,6 +108,8 @@ dsc DUPLICATEALIAS dustojnikhummer dvinns +EAppx +eappxbundle ecfr ecfrbrowse ECustom @@ -416,7 +419,7 @@ silentpreferred similarissues similaritytolerance Skipx -Sku +sku SMTO sortof SOURCESDIRECTORY diff --git a/doc/Settings.md b/doc/Settings.md @@ -332,4 +332,15 @@ Currently, this means that properly attributed configuration units (and only tho "experimentalFeatures": { "configureSelfElevate": true }, +``` + +### storeDownload + +This feature enables packages to be downloaded from the Microsoft Store. +You can enable the feature as shown below. + +```json + "experimentalFeatures": { + "storeDownload": true + }, ``` diff --git a/schemas/JSON/settings/settings.schema.0.2.json b/schemas/JSON/settings/settings.schema.0.2.json @@ -285,6 +285,11 @@ "description": "Enable configure commands request elevation as needed", "type": "boolean", "default": false + }, + "storeDownload": { + "description": "Enable support for downloading packages from the Microsoft Store", + "type": "boolean", + "default": false } } } diff --git a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj @@ -222,10 +222,10 @@ <ItemDefinitionGroup Condition="'$(Configuration)'=='Debug'"> <ClCompile> <Optimization>Disabled</Optimization> - <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions);CLICOREDLLBUILD</PreprocessorDefinitions> - <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">$(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\JsonCppLib\json;$(ProjectDir)..\JsonCppLib;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> - <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">$(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\JsonCppLib\json;$(ProjectDir)..\JsonCppLib;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> - <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\JsonCppLib\json;$(ProjectDir)..\JsonCppLib;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <PreprocessorDefinitions>_SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING;_DEBUG;%(PreprocessorDefinitions);CLICOREDLLBUILD</PreprocessorDefinitions> + <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">$(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\JsonCppLib\json;$(ProjectDir)..\JsonCppLib;$(ProjectDir)..\cpprestsdk\cpprestsdk\Release\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">$(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\JsonCppLib\json;$(ProjectDir)..\JsonCppLib;$(ProjectDir)..\cpprestsdk\cpprestsdk\Release\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\JsonCppLib\json;$(ProjectDir)..\JsonCppLib;$(ProjectDir)..\cpprestsdk\cpprestsdk\Release\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> <TreatWarningAsError Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">true</TreatWarningAsError> <TreatWarningAsError Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">true</TreatWarningAsError> <TreatWarningAsError Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</TreatWarningAsError> @@ -249,7 +249,7 @@ <ItemDefinitionGroup Condition="'$(Platform)'=='Win32'"> <ClCompile> <PreprocessorDefinitions>WIN32;%(PreprocessorDefinitions);CLICOREDLLBUILD</PreprocessorDefinitions> - <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\JsonCppLib\json;$(ProjectDir)..\JsonCppLib;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\JsonCppLib\json;$(ProjectDir)..\JsonCppLib;$(ProjectDir)..\cpprestsdk\cpprestsdk\Release\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> <TreatWarningAsError Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</TreatWarningAsError> <SDLCheck Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</SDLCheck> <EnablePREfast Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</EnablePREfast> @@ -265,10 +265,10 @@ <FunctionLevelLinking>true</FunctionLevelLinking> <IntrinsicFunctions>true</IntrinsicFunctions> <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions);CLICOREDLLBUILD</PreprocessorDefinitions> - <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">$(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\JsonCppLib\json;$(ProjectDir)..\JsonCppLib;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> - <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">$(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\JsonCppLib\json;$(ProjectDir)..\JsonCppLib;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> - <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\JsonCppLib\json;$(ProjectDir)..\JsonCppLib;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> - <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\JsonCppLib\json;$(ProjectDir)..\JsonCppLib;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">$(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\JsonCppLib\json;$(ProjectDir)..\JsonCppLib;$(ProjectDir)..\cpprestsdk\cpprestsdk\Release\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">$(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\JsonCppLib\json;$(ProjectDir)..\JsonCppLib;$(ProjectDir)..\cpprestsdk\cpprestsdk\Release\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\JsonCppLib\json;$(ProjectDir)..\JsonCppLib;$(ProjectDir)..\cpprestsdk\cpprestsdk\Release\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\JsonCppLib\json;$(ProjectDir)..\JsonCppLib;$(ProjectDir)..\cpprestsdk\cpprestsdk\Release\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> <TreatWarningAsError Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">true</TreatWarningAsError> <TreatWarningAsError Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">true</TreatWarningAsError> <TreatWarningAsError Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</TreatWarningAsError> @@ -302,10 +302,10 @@ <FunctionLevelLinking>true</FunctionLevelLinking> <IntrinsicFunctions>true</IntrinsicFunctions> <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions);CLICOREDLLBUILD</PreprocessorDefinitions> - <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='ReleaseStatic|ARM'">$(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\JsonCppLib\json;$(ProjectDir)..\JsonCppLib;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> - <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='ReleaseStatic|ARM64'">$(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\JsonCppLib\json;$(ProjectDir)..\JsonCppLib;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> - <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='ReleaseStatic|Win32'">$(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\JsonCppLib\json;$(ProjectDir)..\JsonCppLib;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> - <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='ReleaseStatic|x64'">$(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\JsonCppLib\json;$(ProjectDir)..\JsonCppLib;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='ReleaseStatic|ARM'">$(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\JsonCppLib\json;$(ProjectDir)..\JsonCppLib;$(ProjectDir)..\cpprestsdk\cpprestsdk\Release\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='ReleaseStatic|ARM64'">$(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\JsonCppLib\json;$(ProjectDir)..\JsonCppLib;$(ProjectDir)..\cpprestsdk\cpprestsdk\Release\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='ReleaseStatic|Win32'">$(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\JsonCppLib\json;$(ProjectDir)..\JsonCppLib;$(ProjectDir)..\cpprestsdk\cpprestsdk\Release\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='ReleaseStatic|x64'">$(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\JsonCppLib\json;$(ProjectDir)..\JsonCppLib;$(ProjectDir)..\cpprestsdk\cpprestsdk\Release\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> <TreatWarningAsError Condition="'$(Configuration)|$(Platform)'=='ReleaseStatic|ARM'">true</TreatWarningAsError> <TreatWarningAsError Condition="'$(Configuration)|$(Platform)'=='ReleaseStatic|ARM64'">true</TreatWarningAsError> <TreatWarningAsError Condition="'$(Configuration)|$(Platform)'=='ReleaseStatic|Win32'">true</TreatWarningAsError> @@ -549,4 +549,4 @@ </Reference> </ItemGroup> </Target> -</Project> +</Project>+ \ No newline at end of file diff --git a/src/AppInstallerCLICore/Resources.h b/src/AppInstallerCLICore/Resources.h @@ -322,6 +322,7 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(MsixArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(MsixSignatureHashFailed); WINGET_DEFINE_RESOURCE_STRINGID(MSStoreAppBlocked); + WINGET_DEFINE_RESOURCE_STRINGID(MSStoreDownloadPackageNotFound); WINGET_DEFINE_RESOURCE_STRINGID(MSStoreInstallOrUpdateFailed); WINGET_DEFINE_RESOURCE_STRINGID(MSStoreInstallTryGetEntitlement); WINGET_DEFINE_RESOURCE_STRINGID(MSStoreRepairFailed); diff --git a/src/AppInstallerCLICore/Workflows/DownloadFlow.cpp b/src/AppInstallerCLICore/Workflows/DownloadFlow.cpp @@ -2,6 +2,7 @@ // Licensed under the MIT License. #include "pch.h" #include "DownloadFlow.h" +#include "MSStoreInstallerHandler.h" #include <winget/Filesystem.h> #include <AppInstallerDownloader.h> #include <AppInstallerRuntime.h> @@ -10,6 +11,9 @@ #include <winget/GroupPolicy.h> #include <winget/ManifestYamlWriter.h> #include <winget/NetworkSettings.h> +#include <winget/HttpClientHelper.h> +#include <winget/Rest.h> + namespace AppInstaller::CLI::Workflow { @@ -232,7 +236,8 @@ namespace AppInstaller::CLI::Workflow case InstallerTypeEnum::MSStore: if (installerDownloadOnly) { - THROW_HR(HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED)); + context << MSStoreDownload; + break; } else { diff --git a/src/AppInstallerCLICore/Workflows/MSStoreInstallerHandler.cpp b/src/AppInstallerCLICore/Workflows/MSStoreInstallerHandler.cpp @@ -2,7 +2,9 @@ // Licensed under the MIT License. #include "pch.h" #include "MSStoreInstallerHandler.h" +#include <winget/HttpClientHelper.h> #include <winget/MSStore.h> +#include <winget/MSStoreDownload.h> #include <winget/SelfManagement.h> namespace AppInstaller::CLI::Workflow @@ -187,6 +189,60 @@ namespace AppInstaller::CLI::Workflow } } + void MSStoreDownload(Execution::Context& context) + { + if (Settings::ExperimentalFeature::IsEnabled(Settings::ExperimentalFeature::Feature::StoreDownload)) + { + const auto& installer = context.Get<Execution::Data::Installer>().value(); + std::string storeRestEndpoint = MSStore::GetMSStoreCatalogRestApi(installer.ProductId, installer.Locale); + + AppInstaller::Http::HttpClientHelper httpClientHelper; + std::optional<web::json::value> jsonObject = httpClientHelper.HandleGet(JSON::GetUtilityString(storeRestEndpoint)); + + if (!jsonObject) + { + AICLI_LOG(Core, Error, << "No json object found"); + } + + const auto& packages = MSStore::DeserializeMSStoreCatalogPackages(jsonObject.value()); + + // Language + std::vector<std::string> requiredLocale; + if (context.Args.Contains(Execution::Args::Type::Locale)) + { + requiredLocale.emplace_back(context.Args.GetArg(Execution::Args::Type::Locale)); + } + + // Architectures + std::vector<Utility::Architecture> allowedArchitectures; + if (context.Contains(Execution::Data::AllowedArchitectures)) + { + // Com caller can directly set allowed architectures + allowedArchitectures = context.Get<Execution::Data::AllowedArchitectures>(); + } + else if (context.Args.Contains(Execution::Args::Type::InstallArchitecture)) + { + allowedArchitectures.emplace_back(Utility::ConvertToArchitectureEnum(context.Args.GetArg(Execution::Args::Type::InstallArchitecture))); + } + + DisplayCatalogPackageComparator packageComparator(requiredLocale, allowedArchitectures); + auto result = packageComparator.GetPreferredPackage(packages); + + if (!result) + { + context.Reporter.Error() << Resource::String::MSStoreDownloadPackageNotFound << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_MSSTORE_NO_APPLICABLE_PACKAGE); + } + + auto preferredPackage = result.value(); + + AICLI_LOG(Core, Info, << "WuCategoryId: " << preferredPackage.WuCategoryId); + AICLI_LOG(Core, Info, << "ContentId: " << preferredPackage.ContentId); + } + + THROW_HR(HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED)); + } + void EnsureStorePolicySatisfied(Execution::Context& context) { auto productId = Utility::ConvertToUTF16(context.Get<Execution::Data::Installer>()->ProductId); diff --git a/src/AppInstallerCLICore/Workflows/MSStoreInstallerHandler.h b/src/AppInstallerCLICore/Workflows/MSStoreInstallerHandler.h @@ -26,6 +26,12 @@ namespace AppInstaller::CLI::Workflow // Outputs: None void MSStoreRepair(Execution::Context& context); + // Downloads the Store app installer. + // Required Args: None + // Inputs: Installer + // Outputs: None + void MSStoreDownload(Execution::Context& context); + // Ensure the Store app is not blocked by policy. // Required Args: None // Inputs: Installer @@ -47,4 +53,4 @@ namespace AppInstaller::CLI::Workflow // Inputs: None // Outputs: None void DisableConfiguration(Execution::Context& context); -}- \ No newline at end of file +} diff --git a/src/AppInstallerCLICore/Workflows/ManifestComparator.cpp b/src/AppInstallerCLICore/Workflows/ManifestComparator.cpp @@ -930,4 +930,4 @@ namespace AppInstaller::CLI::Workflow m_filters.emplace_back(std::move(comparator)); } } -}- \ No newline at end of file +} diff --git a/src/AppInstallerCLICore/pch.h b/src/AppInstallerCLICore/pch.h @@ -32,6 +32,12 @@ #include <string_view> #include <vector> +#pragma warning( push ) +#pragma warning ( disable : 26495 26439 ) +#include <cpprest/http_client.h> +#include <cpprest/json.h> +#pragma warning( pop ) + #include <winrt/Windows.Foundation.h> #include <winrt/Windows.Foundation.Collections.h> #include <winrt/Windows.Management.Deployment.h> diff --git a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw @@ -2883,4 +2883,10 @@ Please specify one of them using the --source option to proceed.</value> <data name="SourceListTrustLevel" xml:space="preserve"> <value>Trust Level</value> </data> + <data name="APPINSTALLER_CLI_ERROR_MSSTORE_NO_APPLICABLE_PACKAGE" xml:space="preserve"> + <value>No applicable MSStore package found.</value> + </data> + <data name="MSStoreDownloadPackageNotFound" xml:space="preserve"> + <value>The MSStore package could not be found.</value> + </data> </root> \ No newline at end of file diff --git a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters @@ -180,7 +180,7 @@ <Filter>Source Files\Common</Filter> </ClCompile> <ClCompile Include="Rest.cpp"> - <Filter>Source Files\Repository</Filter> + <Filter>Source Files\Common</Filter> </ClCompile> <ClCompile Include="RestClient.cpp"> <Filter>Source Files\Repository</Filter> diff --git a/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj b/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj @@ -367,7 +367,7 @@ <FunctionLevelLinking>true</FunctionLevelLinking> <IntrinsicFunctions>true</IntrinsicFunctions> <PreprocessorDefinitions>_NO_ASYNCRTIMP;NDEBUG;%(PreprocessorDefinitions);CLICOREDLLBUILD;WINGET_DISABLE_FOR_FUZZING;_DISABLE_VECTOR_ANNOTATION;_DISABLE_STRING_ANNOTATION</PreprocessorDefinitions> - <AdditionalIncludeDirectories>$(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\AppInstallerSharedLib;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\binver;$(ProjectDir)..\YamlCppLib\libyaml\include;$(ProjectDir)..\PureLib\pure;$(ProjectDir)..\JsonCppLib;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <AdditionalIncludeDirectories>$(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\AppInstallerSharedLib;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\binver;$(ProjectDir)..\YamlCppLib\libyaml\include;$(ProjectDir)..\cpprestsdk\cpprestsdk\Release\include;$(ProjectDir)..\PureLib\pure;$(ProjectDir)..\JsonCppLib;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> <TreatWarningAsError>true</TreatWarningAsError> <LanguageStandard>stdcpp17</LanguageStandard> <RuntimeLibrary>MultiThreaded</RuntimeLibrary> @@ -436,6 +436,9 @@ <ClInclude Include="Public\winget\MsiExecArguments.h" /> <ClInclude Include="Public\winget\MsixManifestValidation.h" /> <ClInclude Include="Public\winget\MSStore.h" /> + <ClInclude Include="Public\winget\MSStoreDownload.h"> + <ExcludedFromBuild Condition="'$(Configuration)'=='Fuzzing'">true</ExcludedFromBuild> + </ClInclude> <ClInclude Include="Public\winget\NameNormalization.h" /> <ClInclude Include="Public\winget\Filesystem.h" /> <ClInclude Include="Public\winget\NetworkSettings.h" /> @@ -489,6 +492,9 @@ <ClCompile Include="MsixInfo.cpp" /> <ClCompile Include="MsixManifest.cpp" /> <ClCompile Include="MSStore.cpp" /> + <ClCompile Include="MSStoreDownload.cpp"> + <ExcludedFromBuild Condition="'$(Configuration)'=='Fuzzing'">true</ExcludedFromBuild> + </ClCompile> <ClCompile Include="NameNormalization.cpp" /> <ClCompile Include="NetworkSettings.cpp" /> <ClCompile Include="PackageDependenciesValidationUtil.cpp" /> @@ -497,7 +503,9 @@ <ClCompile Include="Progress.cpp" /> <ClCompile Include="Reboot.cpp" /> <ClCompile Include="Regex.cpp" /> - <ClCompile Include="Rest.cpp" /> + <ClCompile Include="Rest.cpp"> + <ExcludedFromBuild Condition="'$(Configuration)'=='Fuzzing'">true</ExcludedFromBuild> + </ClCompile> <ClCompile Include="Runtime.cpp" /> <ClCompile Include="pch.cpp"> <PrecompiledHeader>Create</PrecompiledHeader> diff --git a/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj.filters b/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj.filters @@ -201,6 +201,9 @@ <ClInclude Include="Public\winget\Rest.h"> <Filter>Public\winget</Filter> </ClInclude> + <ClInclude Include="Public\winget\MSStoreDownload.h"> + <Filter>Public\winget</Filter> + </ClInclude> </ItemGroup> <ItemGroup> <ClCompile Include="pch.cpp"> @@ -362,6 +365,9 @@ <ClCompile Include="Rest.cpp"> <Filter>Source Files</Filter> </ClCompile> + <ClCompile Include="MSStoreDownload.cpp"> + <Filter>Source Files</Filter> + </ClCompile> </ItemGroup> <ItemGroup> <None Include="PropertySheet.props" /> diff --git a/src/AppInstallerCommonCore/ExperimentalFeature.cpp b/src/AppInstallerCommonCore/ExperimentalFeature.cpp @@ -49,7 +49,9 @@ namespace AppInstaller::Settings case ExperimentalFeature::Feature::Proxy: return userSettings.Get<Setting::EFProxy>(); case ExperimentalFeature::Feature::ConfigureSelfElevation: - return userSettings.Get<Setting::EFConfigureSelfElevation>(); + return userSettings.Get<Setting::EFConfigureSelfElevation>(); + case ExperimentalFeature::Feature::StoreDownload: + return userSettings.Get<Setting::EFStoreDownload>(); default: THROW_HR(E_UNEXPECTED); } @@ -89,6 +91,8 @@ namespace AppInstaller::Settings return ExperimentalFeature{ "Proxy", "proxy", "https://aka.ms/winget-settings", Feature::Proxy }; case Feature::ConfigureSelfElevation: return ExperimentalFeature{ "Configure Self Elevation", "configureSelfElevate", "https://aka.ms/winget-settings", Feature::ConfigureSelfElevation }; + case Feature::StoreDownload: + return ExperimentalFeature{ "Store Download", "storeDownload", "https://aka.ms/winget-settings", Feature::StoreDownload }; default: THROW_HR(E_UNEXPECTED); } diff --git a/src/AppInstallerCommonCore/MSStoreDownload.cpp b/src/AppInstallerCommonCore/MSStoreDownload.cpp @@ -0,0 +1,491 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include <AppInstallerStrings.h> +#include <AppInstallerErrors.h> +#include <AppinstallerLogging.h> +#include <AppInstallerRuntime.h> +#include <winget/Locale.h> +#include <winget/MSStoreDownload.h> +#include <winget/Rest.h> +#include <winget/UserSettings.h> + +namespace AppInstaller::MSStore +{ + using namespace std::string_view_literals; + + namespace + { + constexpr std::string_view Architectures = "Architectures"sv; + constexpr std::string_view ContentId = "ContentId"sv; + constexpr std::string_view Neutral = "Neutral"sv; + constexpr std::string_view DefaultSkuIdValue = "0010"sv; + constexpr std::string_view Details = "Details"sv; + constexpr std::string_view DisplaySkuAvailabilities = "DisplaySkuAvailabilities"sv; + constexpr std::string_view FulfillmentData = "FulfillmentData"sv; + constexpr std::string_view Languages = "Languages"sv; + constexpr std::string_view PackageFormat = "PackageFormat"sv; + constexpr std::string_view PackageId = "PackageId"sv; + constexpr std::string_view Packages = "Packages"sv; + constexpr std::string_view PreferredSkuId = "PreferredSkuId"sv; + constexpr std::string_view Product = "Product"sv; + constexpr std::string_view Properties = "Properties"sv; + constexpr std::string_view Sku = "Sku"sv; + constexpr std::string_view SkuId = "SkuId"sv; + constexpr std::string_view WuCategoryId = "WuCategoryId"sv; + constexpr std::string_view MSStoreCatalogRestApi = R"(https://displaycatalog.mp.microsoft.com/v7.0/products/{0}?fieldsTemplate={1}&market={2}&languages={3})"; + + bool IsPackageFormatBundle(PackageFormatEnum packageFormatEnum) + { + return + packageFormatEnum == PackageFormatEnum::AppxBundle || + packageFormatEnum == PackageFormatEnum::MsixBundle; + } + + struct PackageFormatComparator : public details::MSStoreCatalogPackageComparisonField + { + PackageFormatComparator() : details::MSStoreCatalogPackageComparisonField("Package Format") {} + + bool IsApplicable(const MSStoreCatalogPackage& package) override + { + if (package.PackageFormat == PackageFormatEnum::EAppxBundle) + { + return false; + } + + return true; + } + + bool IsFirstBetter(const MSStoreCatalogPackage& first, const MSStoreCatalogPackage& second) override + { + return IsPackageFormatBundle(first.PackageFormat) && !IsPackageFormatBundle(second.PackageFormat); + } + }; + + struct LanguageComparator : public details::MSStoreCatalogPackageComparisonField + { + LanguageComparator(std::vector<std::string> preference, std::vector<std::string> requirement) : + details::MSStoreCatalogPackageComparisonField("Language"), m_preference(std::move(preference)), m_requirement(std::move(requirement)) + { + AICLI_LOG(Core, Verbose, + << "Language Comparator created with Required Languages: " << Utility::ConvertContainerToString(m_requirement) + << " , Preferred Languages: " << Utility::ConvertContainerToString(m_preference)); + } + + static std::unique_ptr<LanguageComparator> Create(const std::vector<std::string>& requiredLanguages) + { + std::vector<std::string> requirement = requiredLanguages; + if (requirement.empty()) + { + requirement = Settings::User().Get<Settings::Setting::InstallLocaleRequirement>(); + } + + std::vector<std::string> preference = Settings::User().Get<Settings::Setting::InstallLocalePreference>(); + if (preference.empty()) + { + preference = AppInstaller::Locale::GetUserPreferredLanguages(); + } + + if (!preference.empty() || !requirement.empty()) + { + return std::make_unique<LanguageComparator>(preference, requirement); + } + else + { + return {}; + } + } + + bool IsApplicable(const MSStoreCatalogPackage& package) override + { + if (!m_requirement.empty()) + { + for (auto const& requiredLanguage : m_requirement) + { + double distanceScore = GetBestDistanceScoreFromList(requiredLanguage, package.Languages); + if (distanceScore >= Locale::MinimumDistanceScoreAsPerfectMatch) + { + return true; + } + } + + return false; + } + else + { + return true; + } + } + + bool IsFirstBetter(const MSStoreCatalogPackage& first, const MSStoreCatalogPackage& second) + { + if (m_preference.empty()) + { + return false; + } + + for (auto const& preferredLanguage : m_preference) + { + double firstScore = GetBestDistanceScoreFromList(preferredLanguage, first.Languages); + double secondScore = GetBestDistanceScoreFromList(preferredLanguage, second.Languages); + + if (firstScore >= Locale::MinimumDistanceScoreAsCompatibleMatch || secondScore >= Locale::MinimumDistanceScoreAsCompatibleMatch) + { + return firstScore > secondScore; + } + } + + return false; + } + + private: + double GetBestDistanceScoreFromList(std::string_view targetLanguage, const std::vector<std::string>& languages) + { + double finalScore{}; + for (auto const& language : languages) + { + double currentScore = Locale::GetDistanceOfLanguage(targetLanguage, language); + if (currentScore > finalScore) + { + finalScore = currentScore; + } + } + + return finalScore; + } + + std::vector<std::string> m_preference; + std::vector<std::string> m_requirement; + }; + + struct ArchitectureComparator : public details::MSStoreCatalogPackageComparisonField + { + ArchitectureComparator(std::vector<Utility::Architecture> requirement, std::vector<Utility::Architecture> preference) : + details::MSStoreCatalogPackageComparisonField("Architecture"), m_requirement(std::move(requirement)), m_preference(std::move(preference)) + { + AICLI_LOG(Core, Verbose, + << "Architecture Comparator created with required archs: " << Utility::ConvertContainerToString(m_requirement, Utility::ToString) + << " , Preferred archs: " << Utility::ConvertContainerToString(m_preference, Utility::ToString)); + } + + static std::unique_ptr<ArchitectureComparator> Create(const std::vector<Utility::Architecture>& allowedArchitectures) + { + std::vector<Utility::Architecture> requiredArchitectures = allowedArchitectures; + + if (requiredArchitectures.empty()) + { + requiredArchitectures = Settings::User().Get<Settings::Setting::InstallArchitectureRequirement>(); + } + + std::vector<Utility::Architecture> optionalArchitectures = Settings::User().Get<Settings::Setting::InstallArchitecturePreference>(); + + if (!requiredArchitectures.empty() || !optionalArchitectures.empty()) + { + return std::make_unique<ArchitectureComparator>(std::move(requiredArchitectures), std::move(optionalArchitectures)); + } + else + { + return {}; + } + } + + bool IsApplicable(const MSStoreCatalogPackage& package) override + { + if (!m_requirement.empty()) + { + return ContainCommonArchitectures(m_requirement, package.Architectures); + } + + return true; + } + + bool IsFirstBetter(const MSStoreCatalogPackage& first, const MSStoreCatalogPackage& second) override + { + if (!m_preference.empty()) + { + return (ContainCommonArchitectures(first.Architectures, m_preference) && !ContainCommonArchitectures(second.Architectures, m_preference)); + } + + return false; + } + + private: + // Checks if two lists of architectures have common elements. + bool ContainCommonArchitectures(const std::vector<Utility::Architecture>& firstList, const std::vector<Utility::Architecture>& secondList) + { + for (auto arch : firstList) + { + if (IsArchitectureInList(arch, secondList)) + { + return true; + } + } + + return false; + } + + bool IsArchitectureInList(Utility::Architecture arch, const std::vector<Utility::Architecture>& architectureList) + { + return architectureList.end() != std::find_if( + architectureList.begin(), + architectureList.end(), + [&](const auto& a) { return a == arch; }); + } + + std::vector<Utility::Architecture> m_requirement; + std::vector<Utility::Architecture> m_preference; + }; + } + + DisplayCatalogPackageComparator::DisplayCatalogPackageComparator(const std::vector<std::string>& requiredLanguages, const std::vector<Utility::Architecture>& requiredArchs) + { + // Order of comparators matters. + AddComparator(LanguageComparator::Create(requiredLanguages)); + AddComparator(ArchitectureComparator::Create(requiredArchs)); + AddComparator(std::make_unique<PackageFormatComparator>()); + } + + void DisplayCatalogPackageComparator::AddComparator(std::unique_ptr<details::MSStoreCatalogPackageComparisonField>&& comparator) + { + if (comparator) + { + m_comparators.emplace_back(std::move(comparator)); + } + } + + std::optional<MSStoreCatalogPackage> DisplayCatalogPackageComparator::GetPreferredPackage(const std::vector<MSStoreCatalogPackage>& packages) + { + AICLI_LOG(Core, Verbose, << "Starting MSStore package selection."); + + const MSStoreCatalogPackage* result = nullptr; + for (const auto& package : packages) + { + if (IsApplicable(package) && (!result || IsFirstBetter(package, *result))) + { + result = &package; + } + } + + if (result) + { + return *result; + } + else + { + return {}; + } + } + + bool DisplayCatalogPackageComparator::IsFirstBetter(const MSStoreCatalogPackage& first, const MSStoreCatalogPackage& second) + { + for (const auto& comparator : m_comparators) + { + bool forwardCompare = comparator->IsFirstBetter(first, second); + bool reverseCompare = comparator->IsFirstBetter(second, first); + + if (forwardCompare && reverseCompare) + { + AICLI_LOG(Core, Error, << "Packages are both better than each other?"); + THROW_HR(E_UNEXPECTED); + } + + if (forwardCompare && !reverseCompare) + { + AICLI_LOG(Core, Verbose, << "Package " << first.PackageId << " is better than " << second.PackageId); + return true; + } + } + + AICLI_LOG(Core, Verbose, << "Package " << first.PackageId << " is equivalent in priority to " << second.PackageId); + return false; + } + + bool DisplayCatalogPackageComparator::IsApplicable(const MSStoreCatalogPackage& package) + { + for (const auto& comparator : m_comparators) + { + bool result = comparator->IsApplicable(package); + if (!result) + { + return false; + } + } + + return true; + } + + PackageFormatEnum ConvertToPackageFormatEnum(std::string_view packageFormatStr) + { + std::string packageFormat = Utility::ToLower(packageFormatStr); + if (packageFormat == "appxbundle") + { + return PackageFormatEnum::AppxBundle; + } + else if (packageFormat == "eappxbundle") + { + return PackageFormatEnum::EAppxBundle; + } + else if (packageFormat == "msixbundle") + { + return PackageFormatEnum::MsixBundle; + } + else if (packageFormat == "appx") + { + return PackageFormatEnum::Appx; + } + else if (packageFormat == "msix") + { + return PackageFormatEnum::Msix; + } + + AICLI_LOG(Core, Info, << "ConvertToPackageFormatEnum: Unknown package format: " << packageFormatStr); + return PackageFormatEnum::Unknown; + } + + std::vector<MSStoreCatalogPackage> DeserializeMSStoreCatalogPackages(const web::json::value& jsonObject) + { + try + { + if (jsonObject.is_null()) + { + AICLI_LOG(Core, Error, << "Missing json object."); + return {}; + } + + std::optional<std::reference_wrapper<const web::json::value>> product = JSON::GetJsonValueFromNode(jsonObject, JSON::GetUtilityString(Product)); + if (!product) + { + AICLI_LOG(Core, Error, << "Missing Product"); + return {}; + } + + const auto& productValue = product.value().get(); + + std::optional<std::reference_wrapper<const web::json::value>> defaultSku; + + auto skuEntries = JSON::GetRawJsonArrayFromJsonNode(productValue, JSON::GetUtilityString(DisplaySkuAvailabilities)); + if (!skuEntries) + { + AICLI_LOG(Core, Error, << "Missing DisplaySkuAvailabilities"); + return {}; + } + + for (const auto& skuEntry : skuEntries.value().get()) + { + std::optional<std::reference_wrapper<const web::json::value>> sku = JSON::GetJsonValueFromNode(skuEntry, JSON::GetUtilityString(Sku)); + if (!sku) + { + AICLI_LOG(Core, Error, << "Missing Sku"); + return {}; + } + + const auto& skuValue = sku.value().get(); + std::optional<std::string> skuId = JSON::GetRawStringValueFromJsonNode(skuValue, JSON::GetUtilityString(SkuId)); + if (JSON::IsValidNonEmptyStringValue(skuId) && Utility::CaseInsensitiveEquals(skuId.value(), DefaultSkuIdValue)) + { + defaultSku = sku; + break; + } + } + + if (!defaultSku) + { + AICLI_LOG(Core, Error, << "Default Sku (" << DefaultSkuIdValue << ") not found"); + return {}; + } + + const auto& defaultSkuValue = defaultSku.value().get(); + std::optional<std::reference_wrapper<const web::json::value>> properties = JSON::GetJsonValueFromNode(defaultSkuValue, JSON::GetUtilityString(Properties)); + if (!properties) + { + AICLI_LOG(Core, Error, << "Missing Properties"); + return {}; + } + + const auto& propertiesValue = properties.value().get(); + auto packages = JSON::GetRawJsonArrayFromJsonNode(propertiesValue, JSON::GetUtilityString(Packages)); + if (!packages) + { + AICLI_LOG(Core, Error, << "Missing Packages"); + return {}; + } + + std::vector<MSStoreCatalogPackage> displayCatalogPackages; + + for (const auto& packageEntry : packages.value().get()) + { + MSStoreCatalogPackage catalogPackage; + + // Package Id + std::optional<std::string> packageId = JSON::GetRawStringValueFromJsonNode(packageEntry, JSON::GetUtilityString(PackageId)); + if (packageId) + { + catalogPackage.PackageId = packageId.value(); + } + + // Architectures + auto architectures = JSON::GetRawStringArrayFromJsonNode(packageEntry, JSON::GetUtilityString(Architectures)); + for (const auto& arch : architectures) + { + catalogPackage.Architectures.emplace_back(Utility::ConvertToArchitectureEnum(arch)); + } + + // Languages + auto languages = JSON::GetRawStringArrayFromJsonNode(packageEntry, JSON::GetUtilityString(Languages)); + for (const auto& language : languages) + { + catalogPackage.Languages.emplace_back(language); + } + + // Package Format + std::optional<std::string> packageFormat = JSON::GetRawStringValueFromJsonNode(packageEntry, JSON::GetUtilityString(PackageFormat)); + if (packageFormat) + { + catalogPackage.PackageFormat = ConvertToPackageFormatEnum(packageFormat.value()); + } + + // Content Id + std::optional<std::string> contentId = JSON::GetRawStringValueFromJsonNode(packageEntry, JSON::GetUtilityString(ContentId)); + if (contentId) + { + catalogPackage.ContentId = contentId.value(); + } + + // WuCategoryId + std::optional<std::reference_wrapper<const web::json::value>> fulfillmentData = JSON::GetJsonValueFromNode(propertiesValue, JSON::GetUtilityString(FulfillmentData)); + if (fulfillmentData) + { + const auto& fulfillmentDataValue = fulfillmentData.value().get(); + std::optional<std::string> wuCategoryId = JSON::GetRawStringValueFromJsonNode(fulfillmentDataValue, JSON::GetUtilityString(WuCategoryId)); + if (JSON::IsValidNonEmptyStringValue(wuCategoryId)) + { + catalogPackage.WuCategoryId = wuCategoryId.value(); + } + } + + displayCatalogPackages.emplace_back(catalogPackage); + } + + return displayCatalogPackages; + } + catch (const std::exception& e) + { + AICLI_LOG(Core, Error, << "Error encountered while deserializing MSStore request. Reason: " << e.what()); + } + catch (...) + { + AICLI_LOG(Core, Error, << "Received invalid information."); + } + } + + std::string GetMSStoreCatalogRestApi(const std::string& productId, const std::string& locale) + { + std::string languageValue = !locale.empty() ? locale : std::string{ Neutral }; + std::string market = AppInstaller::Runtime::GetOSRegion(); + + auto restEndpoint = AppInstaller::Utility::Format(std::string{ MSStoreCatalogRestApi }, productId, Details, market, languageValue); + THROW_HR_IF(APPINSTALLER_CLI_ERROR_RESTSOURCE_INVALID_URL, !AppInstaller::Rest::IsValidUri(JSON::GetUtilityString(restEndpoint))); + return restEndpoint; + } + +} diff --git a/src/AppInstallerCommonCore/Public/winget/ExperimentalFeature.h b/src/AppInstallerCommonCore/Public/winget/ExperimentalFeature.h @@ -27,7 +27,8 @@ namespace AppInstaller::Settings Configuration03 = 0x4, Proxy = 0x8, SideBySide = 0x10, - ConfigureSelfElevation = 0x20, + ConfigureSelfElevation = 0x20, + StoreDownload = 0x40, Max, // This MUST always be after all experimental features // Features listed after Max will not be shown with the features command diff --git a/src/AppInstallerCommonCore/Public/winget/MSStore.h b/src/AppInstallerCommonCore/Public/winget/MSStore.h @@ -47,4 +47,4 @@ namespace AppInstaller::MSStore bool m_isSilentMode; bool m_force; }; -}- \ No newline at end of file +} diff --git a/src/AppInstallerCommonCore/Public/winget/MSStoreDownload.h b/src/AppInstallerCommonCore/Public/winget/MSStoreDownload.h @@ -0,0 +1,87 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include <winget/JsonUtil.h> +#include <AppInstallerArchitecture.h> + +#include <string> +#include <optional> +#include <string_view> +#include <memory> +#include <vector> + +namespace AppInstaller::MSStore +{ + enum class PackageFormatEnum + { + Unknown = -1, + AppxBundle, + EAppxBundle, + MsixBundle, + Appx, + Msix, + }; + + struct MSStoreCatalogPackage + { + std::string PackageId; + + std::vector<AppInstaller::Utility::Architecture> Architectures; + + std::vector<std::string> Languages; + + PackageFormatEnum PackageFormat = PackageFormatEnum::Unknown; + + std::string WuCategoryId; + + std::string ContentId; + }; + + namespace details + { + struct MSStoreCatalogPackageComparisonField + { + MSStoreCatalogPackageComparisonField(std::string_view name) : m_name(name) {} + + virtual ~MSStoreCatalogPackageComparisonField() = default; + + std::string_view Name() const { return m_name; } + + virtual bool IsApplicable(const MSStoreCatalogPackage& package) = 0; + + virtual bool IsFirstBetter(const MSStoreCatalogPackage& first, const MSStore::MSStoreCatalogPackage& second) = 0; + + private: + std::string_view m_name; + }; + } + + // Class for comparing MSStore packages. + struct DisplayCatalogPackageComparator + { + DisplayCatalogPackageComparator(const std::vector<std::string>& requiredLanguages, const std::vector<AppInstaller::Utility::Architecture>& requiredArchs); + + // Gets the best installer from the manifest, if at least one is applicable. + std::optional<MSStoreCatalogPackage> GetPreferredPackage(const std::vector<MSStoreCatalogPackage>& package); + + // Determines if an installer is applicable. + bool IsApplicable(const MSStoreCatalogPackage& package); + + //// Determines if the first installer is a better choice. + bool IsFirstBetter(const MSStoreCatalogPackage& first, const MSStoreCatalogPackage& second); + + private: + void AddComparator(std::unique_ptr<details::MSStoreCatalogPackageComparisonField>&& comparator); + + std::vector<std::unique_ptr<details::MSStoreCatalogPackageComparisonField>> m_comparators; + }; + + // Deserializes the display catalog packages from the provided json object. + std::vector<MSStoreCatalogPackage> DeserializeMSStoreCatalogPackages(const web::json::value& jsonObject); + + // Constructs the MSStore catalog rest api with the provided product id and language. + std::string GetMSStoreCatalogRestApi(const std::string& productId, const std::string& language); + + // Coverts the package format string to the corresponding PackageFormatEnum. + PackageFormatEnum ConvertToPackageFormatEnum(std::string_view packageFormatStr); +} diff --git a/src/AppInstallerCommonCore/Public/winget/UserSettings.h b/src/AppInstallerCommonCore/Public/winget/UserSettings.h @@ -75,6 +75,7 @@ namespace AppInstaller::Settings EFSideBySide, EFProxy, EFConfigureSelfElevation, + EFStoreDownload, // Telemetry TelemetryDisable, // Install behavior @@ -157,6 +158,7 @@ namespace AppInstaller::Settings SETTINGMAPPING_SPECIALIZATION(Setting::EFSideBySide, bool, bool, false, ".experimentalFeatures.sideBySide"sv); SETTINGMAPPING_SPECIALIZATION(Setting::EFProxy, bool, bool, false, ".experimentalFeatures.proxy"sv); SETTINGMAPPING_SPECIALIZATION(Setting::EFConfigureSelfElevation, bool, bool, false, ".experimentalFeatures.configureSelfElevate"sv); + SETTINGMAPPING_SPECIALIZATION(Setting::EFStoreDownload, bool, bool, false, ".experimentalFeatures.storeDownload"sv); // Telemetry SETTINGMAPPING_SPECIALIZATION(Setting::TelemetryDisable, bool, bool, false, ".telemetry.disable"sv); // Install behavior diff --git a/src/AppInstallerCommonCore/UserSettings.cpp b/src/AppInstallerCommonCore/UserSettings.cpp @@ -264,6 +264,7 @@ namespace AppInstaller::Settings WINGET_VALIDATE_PASS_THROUGH(EFSideBySide) WINGET_VALIDATE_PASS_THROUGH(EFProxy) WINGET_VALIDATE_PASS_THROUGH(EFConfigureSelfElevation) + WINGET_VALIDATE_PASS_THROUGH(EFStoreDownload) WINGET_VALIDATE_PASS_THROUGH(AnonymizePathForDisplay) WINGET_VALIDATE_PASS_THROUGH(TelemetryDisable) WINGET_VALIDATE_PASS_THROUGH(InteractivityDisable) diff --git a/src/AppInstallerSharedLib/Errors.cpp b/src/AppInstallerSharedLib/Errors.cpp @@ -214,6 +214,7 @@ namespace AppInstaller WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_REPAIR_NOT_SUPPORTED, "The installer technology in use doesn't support repair."), WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_ADMIN_CONTEXT_REPAIR_PROHIBITED, "Repair operations involving administrator privileges are not permitted on packages installed within the user scope."), WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_SQLITE_CONNECTION_TERMINATED, "The SQLite connection was terminated to prevent corruption."), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_MSSTORE_NO_APPLICABLE_PACKAGE, "No applicable MSStore package found."), // Install errors. WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_INSTALL_PACKAGE_IN_USE, "Application is currently running. Exit the application then try again."), diff --git a/src/AppInstallerSharedLib/Public/AppInstallerErrors.h b/src/AppInstallerSharedLib/Public/AppInstallerErrors.h @@ -144,6 +144,7 @@ #define APPINSTALLER_CLI_ERROR_REPAIR_NOT_SUPPORTED ((HRESULT)0x8A15007C) #define APPINSTALLER_CLI_ERROR_ADMIN_CONTEXT_REPAIR_PROHIBITED ((HRESULT)0x8A15007D) #define APPINSTALLER_CLI_ERROR_SQLITE_CONNECTION_TERMINATED ((HRESULT)0x8A15007E) +#define APPINSTALLER_CLI_ERROR_MSSTORE_NO_APPLICABLE_PACKAGE ((HRESULT)0x8A15007F) // Install errors. #define APPINSTALLER_CLI_ERROR_INSTALL_PACKAGE_IN_USE ((HRESULT)0x8A150101)