winget-cli

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

commit 6e9e10e146200aa3b415e42c1bdbf47d33dfbee0
parent 93f8690d6c529c465ab0d255d7ea1b221025a3cb
Author: Ryan Fu <69221034+ryfu-msft@users.noreply.github.com>
Date:   Fri,  1 Apr 2022 14:27:37 -0700

Support for 1.2 Schema Additions (#2028)


Diffstat:
M.github/actions/spelling/allow.txt | 1+
Mschemas/JSON/manifests/v1.2.0/manifest.defaultLocale.1.2.0.json | 32++++++++++++++++++++++++++++++++
Mschemas/JSON/manifests/v1.2.0/manifest.installer.1.2.0.json | 60++++++++++++++++++++++++++++++++++++++++++++++++------------
Mschemas/JSON/manifests/v1.2.0/manifest.locale.1.2.0.json | 32++++++++++++++++++++++++++++++++
Mschemas/JSON/manifests/v1.2.0/manifest.singleton.1.2.0.json | 80+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------
Mschemas/JSON/settings/settings.schema.0.2.json | 33++++++++++++++++++++++++++++++++-
Msrc/AppInstallerCLICore/Argument.cpp | 8++++++++
Msrc/AppInstallerCLICore/ExecutionArgs.h | 6++++++
Msrc/AppInstallerCLICore/Resources.h | 4++++
Msrc/AppInstallerCLICore/Workflows/InstallFlow.cpp | 4++--
Msrc/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw | 12++++++++++++
Msrc/AppInstallerCLITests/AppInstallerCLITests.vcxproj | 9+++++++++
Msrc/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters | 9+++++++++
Msrc/AppInstallerCLITests/RestInterface_1_1.cpp | 2+-
Msrc/AppInstallerCLITests/TestData/Manifest-Bad-DuplicateReturnCode-ExpectedCodes.yaml | 8++++----
Asrc/AppInstallerCLITests/TestData/Manifest-Bad-InstallerTypePortable-InvalidAppsAndFeatures.yaml | 43+++++++++++++++++++++++++++++++++++++++++++
Asrc/AppInstallerCLITests/TestData/Manifest-Bad-InstallerTypePortable-InvalidCommands.yaml | 27+++++++++++++++++++++++++++
Asrc/AppInstallerCLITests/TestData/Manifest-Bad-InstallerTypePortable-InvalidScope.yaml | 23+++++++++++++++++++++++
Msrc/AppInstallerCLITests/TestData/ManifestV1_2-Singleton.yaml | 17+++++++++++++++++
Msrc/AppInstallerCLITests/TestData/MultiFileManifestV1_2/ManifestV1_2-MultiFile-DefaultLocale.yaml | 6+++++-
Msrc/AppInstallerCLITests/TestData/MultiFileManifestV1_2/ManifestV1_2-MultiFile-Installer.yaml | 14++++++++++++++
Msrc/AppInstallerCLITests/TestData/MultiFileManifestV1_2/ManifestV1_2-MultiFile-Locale.yaml | 6+++++-
Msrc/AppInstallerCLITests/UserSettings.cpp | 13+++++++++++++
Msrc/AppInstallerCLITests/YamlManifest.cpp | 46+++++++++++++++++++++++++++++++++++++++++++---
Msrc/AppInstallerCommonCore/ExperimentalFeature.cpp | 4++++
Msrc/AppInstallerCommonCore/Manifest/ManifestCommon.cpp | 20++++++++++++++++++++
Msrc/AppInstallerCommonCore/Manifest/ManifestSchemaValidation.cpp | 1+
Msrc/AppInstallerCommonCore/Manifest/ManifestValidation.cpp | 16++++++++++++++++
Msrc/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp | 91+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
Msrc/AppInstallerCommonCore/Public/AppInstallerRuntime.h | 6++++++
Msrc/AppInstallerCommonCore/Public/winget/ExperimentalFeature.h | 1+
Msrc/AppInstallerCommonCore/Public/winget/ManifestCommon.h | 23+++++++++++++++++------
Msrc/AppInstallerCommonCore/Public/winget/ManifestInstaller.h | 12+++++++++++-
Msrc/AppInstallerCommonCore/Public/winget/ManifestLocalization.h | 15+++++++++++++++
Msrc/AppInstallerCommonCore/Public/winget/ManifestValidation.h | 3+++
Msrc/AppInstallerCommonCore/Public/winget/ManifestYamlPopulator.h | 4++++
Msrc/AppInstallerCommonCore/Public/winget/UserSettings.h | 10++++++++++
Msrc/AppInstallerCommonCore/Runtime.cpp | 65+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/AppInstallerCommonCore/UserSettings.cpp | 19+++++++++++++++++++
Msrc/AppInstallerRepositoryCore/Rest/Schema/1_1/Json/ManifestDeserializer_1_1.cpp | 4++--
Msrc/ManifestSchema/ManifestSchema.vcxitems.filters | 8++++----
41 files changed, 743 insertions(+), 54 deletions(-)

diff --git a/.github/actions/spelling/allow.txt b/.github/actions/spelling/allow.txt @@ -393,6 +393,7 @@ PRIMARYKEY prioritization PRODUCTNAME PRODUCTVERSION +PROGRAMFILES PROGRESSONLY promptrestart PROPERTYDUMP diff --git a/schemas/JSON/manifests/v1.2.0/manifest.defaultLocale.1.2.0.json b/schemas/JSON/manifests/v1.2.0/manifest.defaultLocale.1.2.0.json @@ -35,6 +35,21 @@ "description": "The agreement URL." } } + }, + "Documentation": { + "type": "object", + "properties": { + "DocumentLabel": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 100, + "description": "The label of the documentation for providing software guides such as manuals and troubleshooting URLs." + }, + "DocumentUrl": { + "$ref": "#/definitions/Url", + "description": "The documentation URL." + } + } } }, "type": "object", @@ -154,6 +169,23 @@ "$ref": "#/definitions/Url", "description": "The package release notes url" }, + "PurchaseUrl": { + "$ref": "#/definitions/Url", + "description": "The purchase url for acquiring entitlement for the package." + }, + "InstallationNotes": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 256, + "description": "The notes displayed to the user upon completion of a package installation." + }, + "Documentations": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/Documentation" + }, + "maxItems": 256 + }, "ManifestType": { "type": "string", "default": "defaultLocale", diff --git a/schemas/JSON/manifests/v1.2.0/manifest.installer.1.2.0.json b/schemas/JSON/manifests/v1.2.0/manifest.installer.1.2.0.json @@ -46,6 +46,12 @@ "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){0,3}$", "description": "The installer minimum operating system version" }, + "Url": { + "type": [ "string", "null" ], + "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", + "maxLength": 2048, + "description": "Url type" + }, "InstallerType": { "type": [ "string", "null" ], "enum": [ @@ -63,6 +69,17 @@ ], "description": "Enumeration of supported installer types. InstallerType is required in either root level or individual Installer level" }, + "Architecture": { + "type": "string", + "enum": [ + "x86", + "x64", + "arm", + "arm64", + "neutral" + ], + "description": "The installer target architecture" + }, "Scope": { "type": [ "string", "null" ], "enum": [ @@ -178,8 +195,13 @@ "cancelledByUser", "alreadyInstalled", "downgrade", - "blockedByPolicy" + "blockedByPolicy", + "custom" ] + }, + "ReturnResponseUrl": { + "$ref": "#/definitions/Url", + "description": "The return response url to provide additional guidance for expected return codes" } } }, @@ -209,7 +231,6 @@ "type": [ "array", "null" ], "items": { "type": "string", - "pattern": "^[a-z][-a-z0-9\\.\\+]*$", "maxLength": 2048 }, "maxItems": 16, @@ -223,7 +244,7 @@ "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", "maxLength": 64 }, - "maxItems": 256, + "maxItems": 512, "uniqueItems": true, "description": "List of file extensions the package could support" }, @@ -370,6 +391,10 @@ "type": [ "boolean", "null" ], "description": "Indicates whether the installer should be pinned by default from upgrade" }, + "DisplayInstallWarnings": { + "type": [ "boolean", "null" ], + "description": "Indicates whether winget should display a warning message if the install or upgrade is known to interfere with running applications." + }, "UnsupportedOSArchitectures": { "type": [ "array", "null" ], "uniqueItems": true, @@ -385,6 +410,19 @@ }, "description": "List of OS architectures the installer does not support" }, + "UnsupportedArguments": { + "type": [ "array", "null" ], + "uniqueItems": true, + "items": { + "type": "string", + "title": "UnsupportedArgument", + "enum": [ + "log", + "location" + ] + }, + "description": "List of winget arguments the installer does not support" + }, "AppsAndFeaturesEntry": { "type": "object", "properties": { @@ -449,15 +487,7 @@ "$ref": "#/definitions/MinimumOSVersion" }, "Architecture": { - "type": "string", - "enum": [ - "x86", - "x64", - "arm", - "arm64", - "neutral" - ], - "description": "The installer target architecture" + "$ref": "#/definitions/Architecture" }, "InstallerType": { "$ref": "#/definitions/InstallerType" @@ -535,6 +565,9 @@ "RequireExplicitUpgrade": { "$ref": "#/definitions/RequireExplicitUpgrade" }, + "DisplayInstallWarnings": { + "$ref": "#/definitions/DisplayInstallWarnings" + }, "UnsupportedOSArchitectures": { "$ref": "#/definitions/UnsupportedOSArchitectures" }, @@ -641,6 +674,9 @@ "ElevationRequirement": { "$ref": "#/definitions/ElevationRequirement" }, + "DisplayInstallWarnings": { + "$ref": "#/definitions/DisplayInstallWarnings" + }, "Installers": { "type": "array", "items": { diff --git a/schemas/JSON/manifests/v1.2.0/manifest.locale.1.2.0.json b/schemas/JSON/manifests/v1.2.0/manifest.locale.1.2.0.json @@ -35,6 +35,21 @@ "description": "The agreement URL." } } + }, + "Documentation": { + "type": "object", + "properties": { + "DocumentLabel": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 100, + "description": "The label of the documentation for providing software guides such as manuals and troubleshooting URLs." + }, + "DocumentUrl": { + "$ref": "#/definitions/Url", + "description": "The documentation URL." + } + } } }, "type": "object", @@ -149,6 +164,23 @@ "$ref": "#/definitions/Url", "description": "The package release notes url" }, + "PurchaseUrl": { + "$ref": "#/definitions/Url", + "description": "The purchase url for acquiring entitlement for the package." + }, + "InstallationNotes": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 256, + "description": "The notes displayed to the user upon completion of a package installation." + }, + "Documentations": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/Documentation" + }, + "maxItems": 256 + }, "ManifestType": { "type": "string", "default": "locale", diff --git a/schemas/JSON/manifests/v1.2.0/manifest.singleton.1.2.0.json b/schemas/JSON/manifests/v1.2.0/manifest.singleton.1.2.0.json @@ -54,6 +54,21 @@ } } }, + "Documentation": { + "type": "object", + "properties": { + "DocumentLabel": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 100, + "description": "The label of the documentation for providing software guides such as manuals and troubleshooting URLs." + }, + "DocumentUrl": { + "$ref": "#/definitions/Url", + "description": "The documentation URL." + } + } + }, "Channel": { "type": [ "string", "null" ], "minLength": 1, @@ -96,6 +111,17 @@ ], "description": "Enumeration of supported installer types. InstallerType is required in either root level or individual Installer level" }, + "Architecture": { + "type": "string", + "enum": [ + "x86", + "x64", + "arm", + "arm64", + "neutral" + ], + "description": "The installer target architecture" + }, "Scope": { "type": [ "string", "null" ], "enum": [ @@ -211,8 +237,13 @@ "cancelledByUser", "alreadyInstalled", "downgrade", - "blockedByPolicy" + "blockedByPolicy", + "custom" ] + }, + "ReturnResponseUrl": { + "$ref": "#/definitions/Url", + "description": "The return response url to provide additional guidance for expected return codes" } } }, @@ -242,7 +273,6 @@ "type": [ "array", "null" ], "items": { "type": "string", - "pattern": "^[a-z][-a-z0-9\\.\\+]*$", "maxLength": 2048 }, "maxItems": 16, @@ -256,7 +286,7 @@ "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", "maxLength": 64 }, - "maxItems": 256, + "maxItems": 512, "uniqueItems": true, "description": "List of file extensions the package could support" }, @@ -402,6 +432,10 @@ "type": [ "boolean", "null" ], "description": "Indicates whether the installer should be pinned by default from upgrade" }, + "DisplayInstallWarnings": { + "type": [ "boolean", "null" ], + "description": "Indicates whether winget should display a warning message if the install or upgrade is known to interfere with running applications." + }, "UnsupportedOSArchitectures": { "type": [ "array", "null" ], "uniqueItems": true, @@ -417,6 +451,19 @@ }, "description": "List of OS architectures the installer does not support" }, + "UnsupportedArguments": { + "type": [ "array", "null" ], + "uniqueItems": true, + "items": { + "type": "string", + "title": "UnsupportedArgument", + "enum": [ + "log", + "location" + ] + }, + "description": "List of winget arguments the installer does not support" + }, "AppsAndFeaturesEntry": { "type": "object", "properties": { @@ -481,15 +528,7 @@ "$ref": "#/definitions/MinimumOSVersion" }, "Architecture": { - "type": "string", - "enum": [ - "x86", - "x64", - "arm", - "arm64", - "neutral" - ], - "description": "The installer target architecture" + "$ref": "#/definitions/Architecture" }, "InstallerType": { "$ref": "#/definitions/InstallerType" @@ -691,6 +730,23 @@ "$ref": "#/definitions/Url", "description": "The package release notes url" }, + "PurchaseUrl": { + "$ref": "#/definitions/Url", + "description": "The purchase url for acquiring entitlement for the package." + }, + "InstallationNotes": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 256, + "description": "The notes displayed to the user upon completion of a package installation." + }, + "Documentations": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/Documentation" + }, + "maxItems": 256 + }, "Channel": { "$ref": "#/definitions/Channel" }, diff --git a/schemas/JSON/settings/settings.schema.0.2.json b/schemas/JSON/settings/settings.schema.0.2.json @@ -96,7 +96,33 @@ "type": "object", "properties": { "preferences": { "$ref": "#/definitions/InstallPrefReq" }, - "requirements": { "$ref": "#/definitions/InstallPrefReq" } + "requirements": { "$ref": "#/definitions/InstallPrefReq" }, + "ignoreWarnings": { + "description": "Controls whether blocking warning messages shown to the user during an install or upgrade are ignored", + "type": "boolean", + "default": false + }, + "PortableAppUserRoot": { + "description": "The default root directory where packages are installed to under User scope. Applies to the portable installer type.", + "type": "string", + "default": "%LOCALAPPDATA%/Microsoft/WinGet/Packages/" + }, + "PortableAppMachineRoot": { + "description": "The default root directory where packages are installed to under Machine scope. Applies to the portable installer type.", + "type": "string", + "default": "%PROGRAMFILES%/WinGet/Packages/" + } + } + }, + "UninstallBehavior": { + "description": "Uninstall settings", + "type": "object", + "properties": { + "purgePortableApp": { + "description": "Controls whether the default behavior for uninstall removes all files and directories relevant to this package. Only applies to the portable installerType.", + "type": "boolean", + "default": false + } } }, "Telemetry": { @@ -156,6 +182,11 @@ "description": "Enable use of MSI APIs rather than msiexec for MSI installs", "type": "boolean", "default": false + }, + "portableInstall": { + "description": "Enable support for installing portable packages.", + "type": "boolean", + "default": false } } } diff --git a/src/AppInstallerCLICore/Argument.cpp b/src/AppInstallerCLICore/Argument.cpp @@ -86,6 +86,14 @@ namespace AppInstaller::CLI return Argument{ "accept-source-agreements", NoAlias, Args::Type::AcceptSourceAgreements, Resource::String::AcceptSourceAgreementsArgumentDescription, ArgumentType::Flag }; case Args::Type::ExperimentalArg: return Argument{ "arg", NoAlias, Args::Type::ExperimentalArg, Resource::String::ExperimentalArgumentDescription, ArgumentType::Flag, ExperimentalFeature::Feature::ExperimentalArg }; + case Args::Type::Rename: + return Argument{ "rename", NoAlias, Args::Type::Rename, Resource::String::RenameArgumentDescription, ArgumentType::Positional, false }; + case Args::Type::Purge: + return Argument{ "purge", NoAlias, Args::Type::Purge, Resource::String::PurgeArgumentDescription, ArgumentType::Flag, false }; + case Args::Type::Preserve: + return Argument{ "preserve", NoAlias, Args::Type::Preserve, Resource::String::PreserveArgumentDescription, ArgumentType::Flag, false }; + case Args::Type::Wait: + return Argument{ "wait", NoAlias, Args::Type::Wait, Resource::String::WaitArgumentDescription, ArgumentType::Flag, false }; default: THROW_HR(E_UNEXPECTED); } diff --git a/src/AppInstallerCLICore/ExecutionArgs.h b/src/AppInstallerCLICore/ExecutionArgs.h @@ -41,6 +41,11 @@ namespace AppInstaller::CLI::Execution InstallArchitecture, HashOverride, // Ignore hash mismatches AcceptPackageAgreements, // Accept all license agreements for packages + Rename, // Renames the file of the executable. Only applies to the portable installerType + + // Uninstall behavior + Purge, // Removes all files and directories related to a package during an uninstall. Only applies to the portable installerType. + Preserve, // Retains any files and directories created by the portable exe. //Source Command SourceName, @@ -86,6 +91,7 @@ namespace AppInstaller::CLI::Execution CustomHeader, // Optional Rest source header AcceptSourceAgreements, // Accept all source agreements IncludeUnknown, // Used in Upgrade command to allow upgrades of packages with unknown versions + Wait, // Prompts the user to press any key before exiting. // Used for demonstration purposes ExperimentalArg, diff --git a/src/AppInstallerCLICore/Resources.h b/src/AppInstallerCLICore/Resources.h @@ -206,11 +206,14 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(PoliciesPolicy); WINGET_DEFINE_RESOURCE_STRINGID(PoliciesState); WINGET_DEFINE_RESOURCE_STRINGID(PositionArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(PreserveArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(PrivacyStatement); WINGET_DEFINE_RESOURCE_STRINGID(PromptOptionNo); WINGET_DEFINE_RESOURCE_STRINGID(PromptOptionYes); + WINGET_DEFINE_RESOURCE_STRINGID(PurgeArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(QueryArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(RainbowArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(RenameArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(ReportIdentityFound); WINGET_DEFINE_RESOURCE_STRINGID(RequiredArgError); WINGET_DEFINE_RESOURCE_STRINGID(RetroArgumentDescription); @@ -358,6 +361,7 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(VerifyPathFailedNotExist); WINGET_DEFINE_RESOURCE_STRINGID(VersionArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(VersionsArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(WaitArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(WindowsFeaturesDependencies); WINGET_DEFINE_RESOURCE_STRINGID(WindowsLibrariesDependencies); WINGET_DEFINE_RESOURCE_STRINGID(WindowsStoreTerms); diff --git a/src/AppInstallerCLICore/Workflows/InstallFlow.cpp b/src/AppInstallerCLICore/Workflows/InstallFlow.cpp @@ -344,9 +344,9 @@ namespace AppInstaller::CLI::Workflow // Show a specific message if we can identify the return code const auto& expectedReturnCodes = context.Get<Execution::Data::Installer>()->ExpectedReturnCodes; auto expectedReturnCodeItr = expectedReturnCodes.find(installResult); - if (expectedReturnCodeItr != expectedReturnCodes.end() && expectedReturnCodeItr->second != ExpectedReturnCodeEnum::Unknown) + if (expectedReturnCodeItr != expectedReturnCodes.end() && expectedReturnCodeItr->second.ReturnResponseEnum != ExpectedReturnCodeEnum::Unknown) { - auto returnCode = ExpectedReturnCode::GetExpectedReturnCode(expectedReturnCodeItr->second); + auto returnCode = ExpectedReturnCode::GetExpectedReturnCode(expectedReturnCodeItr->second.ReturnResponseEnum); context.Reporter.Error() << returnCode.Message << std::endl; AICLI_TERMINATE_CONTEXT(returnCode.HResult); } diff --git a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw @@ -1275,4 +1275,16 @@ Please specify one of them using the `--source` option to proceed.</value> <data name="SystemArchitecture" xml:space="preserve"> <value>System Architecture</value> </data> + <data name="PreserveArgumentDescription" xml:space="preserve"> + <value>Retains all files and directories created by the package (portable)</value> + </data> + <data name="PurgeArgumentDescription" xml:space="preserve"> + <value>Deletes all files and directories in the package directory (portable)</value> + </data> + <data name="RenameArgumentDescription" xml:space="preserve"> + <value>The value to rename the executable file (portable)</value> + </data> + <data name="WaitArgumentDescription" xml:space="preserve"> + <value>Prompts the user to press any key before exiting</value> + </data> </root> \ No newline at end of file diff --git a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj @@ -377,6 +377,15 @@ <CopyFileToFolders Include="TestData\Manifest-Bad-InstallerTypeMissing.yaml"> <DeploymentContent>true</DeploymentContent> </CopyFileToFolders> + <CopyFileToFolders Include="TestData\Manifest-Bad-InstallerTypePortable-InvalidScope.yaml"> + <DeploymentContent>true</DeploymentContent> + </CopyFileToFolders> + <CopyFileToFolders Include="TestData\Manifest-Bad-InstallerTypePortable-InvalidAppsAndFeatures.yaml"> + <DeploymentContent>true</DeploymentContent> + </CopyFileToFolders> + <CopyFileToFolders Include="TestData\Manifest-Bad-InstallerTypePortable-InvalidCommands.yaml"> + <DeploymentContent>true</DeploymentContent> + </CopyFileToFolders> <CopyFileToFolders Include="TestData\Manifest-Bad-InstallerUniqueness-DefaultScope.yaml"> <DeploymentContent>true</DeploymentContent> </CopyFileToFolders> diff --git a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters @@ -258,6 +258,15 @@ <CopyFileToFolders Include="TestData\Manifest-Bad-InstallerTypeMissing.yaml"> <Filter>TestData</Filter> </CopyFileToFolders> + <CopyFileToFolders Include="TestData\Manifest-Bad-InstallerTypePortable-InvalidScope.yaml"> + <Filter>TestData</Filter> + </CopyFileToFolders> + <CopyFileToFolders Include="TestData\Manifest-Bad-InstallerTypePortable-InvalidAppsAndFeatures.yaml"> + <Filter>TestData</Filter> + </CopyFileToFolders> + <CopyFileToFolders Include="TestData\Manifest-Bad-InstallerTypePortable-InvalidCommands.yaml"> + <Filter>TestData</Filter> + </CopyFileToFolders> <CopyFileToFolders Include="TestData\Manifest-Bad-InstallerUniqueness.yaml"> <Filter>TestData</Filter> </CopyFileToFolders> diff --git a/src/AppInstallerCLITests/RestInterface_1_1.cpp b/src/AppInstallerCLITests/RestInterface_1_1.cpp @@ -297,7 +297,7 @@ namespace REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).InstallerType == InstallerTypeEnum::Exe); REQUIRE(actualInstaller.Markets.AllowedMarkets.size() == 1); REQUIRE(actualInstaller.Markets.AllowedMarkets.at(0) == "US"); - REQUIRE(actualInstaller.ExpectedReturnCodes.at(3) == ExpectedReturnCodeEnum::InstallInProgress); + REQUIRE(actualInstaller.ExpectedReturnCodes.at(3).ReturnResponseEnum == ExpectedReturnCodeEnum::InstallInProgress); } }; } diff --git a/src/AppInstallerCLITests/TestData/Manifest-Bad-DuplicateReturnCode-ExpectedCodes.yaml b/src/AppInstallerCLITests/TestData/Manifest-Bad-DuplicateReturnCode-ExpectedCodes.yaml @@ -13,9 +13,9 @@ Installers: InstallerType: exe InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B ExpectedReturnCodes: - - InstallerReturnCode: 1 - ReturnResponse: PackageInUse - - InstallerReturnCode: 1 - ReturnResponse: InstallInProgress + - InstallerReturnCode: 1 + ReturnResponse: packageInUse + - InstallerReturnCode: 1 + ReturnResponse: installInProgress ManifestType: singleton ManifestVersion: 1.1.0 diff --git a/src/AppInstallerCLITests/TestData/Manifest-Bad-InstallerTypePortable-InvalidAppsAndFeatures.yaml b/src/AppInstallerCLITests/TestData/Manifest-Bad-InstallerTypePortable-InvalidAppsAndFeatures.yaml @@ -0,0 +1,42 @@ +# Bad manifest. Installer Type portable can only have zero or one AppsAndFeatureEntry defined. +PackageIdentifier: TestInstaller.WithLicenseAgreement +PackageVersion: 1.0.0.0 +PackageLocale: en-US +PackageName: AppInstaller Test Installer +Publisher: Microsoft Corporation +Moniker: AICLITestExe +License: Test +ShortDescription: TestInstallerWithLicenseAgreement +AppsAndFeaturesEntries: + - DisplayName: DisplayName1 + DisplayVersion: DisplayVersion1 + Publisher: Publisher1 + ProductCode: ProductCode1 + UpgradeCode: UpgradeCode1 + - DisplayName: DisplayName2 + DisplayVersion: DisplayVersion2 + Publisher: Publisher2 + ProductCode: ProductCode2 + UpgradeCode: UpgradeCode2 +Installers: + - Architecture: x64 + InstallerUrl: https://ThisIsNotUsed + InstallerType: portable + InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B + - Architecture: x86 + InstallerUrl: https://ThisIsNotUsed2 + InstallerType: portable + InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B + AppsAndFeaturesEntries: + - DisplayName: DisplayName1 + DisplayVersion: DisplayVersion1 + Publisher: Publisher1 + ProductCode: ProductCode1 + UpgradeCode: UpgradeCode1 + - DisplayName: DisplayName2 + DisplayVersion: DisplayVersion2 + Publisher: Publisher2 + ProductCode: ProductCode2 + UpgradeCode: UpgradeCode2 +ManifestType: singleton +ManifestVersion: 1.2.0+ \ No newline at end of file diff --git a/src/AppInstallerCLITests/TestData/Manifest-Bad-InstallerTypePortable-InvalidCommands.yaml b/src/AppInstallerCLITests/TestData/Manifest-Bad-InstallerTypePortable-InvalidCommands.yaml @@ -0,0 +1,26 @@ +# Bad manifest. Installer Type portable can only have one or zero commands defined. +PackageIdentifier: TestInstaller.WithLicenseAgreement +PackageVersion: 1.0.0.0 +PackageLocale: en-US +PackageName: AppInstaller Test Installer +Publisher: Microsoft Corporation +Moniker: AICLITestExe +License: Test +ShortDescription: TestInstallerWithLicenseAgreement +Commands: + - Command1 + - Command2 +Installers: + - Architecture: x64 + InstallerUrl: https://ThisIsNotUsed + InstallerType: portable + InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B + - Architecture: x86 + InstallerUrl: https://ThisIsNotUsed2 + InstallerType: portable + InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B + Commands: + - Command1 + - Command2 +ManifestType: singleton +ManifestVersion: 1.2.0+ \ No newline at end of file diff --git a/src/AppInstallerCLITests/TestData/Manifest-Bad-InstallerTypePortable-InvalidScope.yaml b/src/AppInstallerCLITests/TestData/Manifest-Bad-InstallerTypePortable-InvalidScope.yaml @@ -0,0 +1,22 @@ +# Bad manifest. Installer Type portable does not support scope and should show a warning. +PackageIdentifier: TestInstaller.WithLicenseAgreement +PackageVersion: 1.0.0.0 +PackageLocale: en-US +PackageName: AppInstaller Test Installer +Publisher: Microsoft Corporation +Moniker: AICLITestExe +License: Test +ShortDescription: TestInstallerWithLicenseAgreement +Scope: User +Installers: + - Architecture: x64 + InstallerUrl: https://ThisIsNotUsed + InstallerType: portable + InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B + Scope: Machine + - Architecture: x86 + InstallerUrl: https://ThisIsNotUsed2 + InstallerType: portable + InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B +ManifestType: singleton +ManifestVersion: 1.2.0+ \ No newline at end of file diff --git a/src/AppInstallerCLITests/TestData/ManifestV1_2-Singleton.yaml b/src/AppInstallerCLITests/TestData/ManifestV1_2-Singleton.yaml @@ -20,6 +20,11 @@ Tags: - "msixsdk" ReleaseNotes: Default release notes ReleaseNotesUrl: https://DefaultReleaseNotes.net +PurchaseUrl: https://DefaultPurchaseUrl.com +InstallationNotes: Default installation notes +Documentations: + - DocumentLabel: Default document label + DocumentUrl: https://DefaultDocumentUrl.com Agreements: - AgreementLabel: DefaultLabel Agreement: DefaultText @@ -78,6 +83,7 @@ ReleaseDate: 2021-01-01 InstallerAbortsTerminal: true InstallLocationRequired: true RequireExplicitUpgrade: true +DisplayInstallWarnings: true ElevationRequirement: elevatesSelf UnsupportedOSArchitectures: - arm @@ -94,6 +100,10 @@ Markets: ExpectedReturnCodes: - InstallerReturnCode: 10 ReturnResponse: packageInUse + ReturnResponseUrl: https://DefaultReturnResponseUrl.com +UnsupportedArguments: + - log + - location Installers: - Architecture: x86 @@ -146,7 +156,11 @@ Installers: InstallerAbortsTerminal: false InstallLocationRequired: false RequireExplicitUpgrade: false + DisplayInstallWarnings: false ElevationRequirement: elevationRequired + UnsupportedArguments: + - log + - location UnsupportedOSArchitectures: - arm64 Markets: @@ -155,5 +169,8 @@ Installers: ExpectedReturnCodes: - InstallerReturnCode: 2 ReturnResponse: contactSupport + - InstallerReturnCode: 3 + ReturnResponse: custom + ReturnResponseUrl: https://defaultReturnResponseUrl.com ManifestType: singleton ManifestVersion: 1.2.0 diff --git a/src/AppInstallerCLITests/TestData/MultiFileManifestV1_2/ManifestV1_2-MultiFile-DefaultLocale.yaml b/src/AppInstallerCLITests/TestData/MultiFileManifestV1_2/ManifestV1_2-MultiFile-DefaultLocale.yaml @@ -12,6 +12,8 @@ License: MIT License LicenseUrl: https://www.microsoft.com/msixsdk/license Copyright: Copyright Microsoft Corporation CopyrightUrl: https://www.microsoft.com/msixsdk/copyright +PurchaseUrl: https://DefaultPurchaseUrl.com +InstallationNotes: "Default installation notes" ShortDescription: This is MSIX SDK Description: The MSIX SDK project is an effort to enable developers Moniker: msixsdk @@ -20,10 +22,12 @@ Tags: - "msixsdk" ReleaseNotes: Default release notes ReleaseNotesUrl: https://DefaultReleaseNotes.net +Documentations: + - DocumentLabel: Default document label + DocumentUrl: https://DefaultDocumentUrl.com Agreements: - AgreementLabel: DefaultLabel Agreement: DefaultText AgreementUrl: https://DefaultAgreementUrl.net - ManifestType: defaultLocale ManifestVersion: 1.2.0 diff --git a/src/AppInstallerCLITests/TestData/MultiFileManifestV1_2/ManifestV1_2-MultiFile-Installer.yaml b/src/AppInstallerCLITests/TestData/MultiFileManifestV1_2/ManifestV1_2-MultiFile-Installer.yaml @@ -54,6 +54,7 @@ ReleaseDate: 2021-01-01 InstallerAbortsTerminal: true InstallLocationRequired: true RequireExplicitUpgrade: true +DisplayInstallWarnings: true ElevationRequirement: elevatesSelf UnsupportedOSArchitectures: - arm @@ -70,6 +71,10 @@ Markets: ExpectedReturnCodes: - InstallerReturnCode: 10 ReturnResponse: packageInUse + ReturnResponseUrl: https://DefaultReturnResponseUrl.com +UnsupportedArguments: + - log + - location Installers: - Architecture: x86 @@ -122,6 +127,7 @@ Installers: InstallerAbortsTerminal: false InstallLocationRequired: false RequireExplicitUpgrade: false + DisplayInstallWarnings: false ElevationRequirement: elevationRequired UnsupportedOSArchitectures: - arm64 @@ -131,6 +137,9 @@ Installers: ExpectedReturnCodes: - InstallerReturnCode: 2 ReturnResponse: contactSupport + - InstallerReturnCode: 3 + ReturnResponse: custom + ReturnResponseUrl: https://defaultReturnResponseUrl.com - Architecture: x64 InstallerType: exe InstallerUrl: https://www.microsoft.com/msixsdk/msixsdkx64.exe @@ -140,7 +149,12 @@ Installers: InstallerType: portable InstallerUrl: https://www.microsoft.com/msixsdk/msixsdkx86.exe InstallerSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 + DisplayInstallWarnings: false Commands: - standalone + ExpectedReturnCodes: + - InstallerReturnCode: 11 + ReturnResponse: custom + ReturnResponseUrl: https://defaultReturnResponseUrl.com ManifestType: installer ManifestVersion: 1.2.0 \ No newline at end of file diff --git a/src/AppInstallerCLITests/TestData/MultiFileManifestV1_2/ManifestV1_2-MultiFile-Locale.yaml b/src/AppInstallerCLITests/TestData/MultiFileManifestV1_2/ManifestV1_2-MultiFile-Locale.yaml @@ -19,10 +19,14 @@ Tags: - "msixsdkUK" ReleaseNotes: Release notes ReleaseNotesUrl: https://ReleaseNotes.net +PurchaseUrl: https://DefaultPurchaseUrl.com +InstallationNotes: Default installation notes Agreements: - AgreementLabel: Label Agreement: Text AgreementUrl: https://AgreementUrl.net - +Documentations: + - DocumentLabel: Default document label + DocumentUrl: https://DefaultDocumentUrl.com ManifestType: locale ManifestVersion: 1.2.0 diff --git a/src/AppInstallerCLITests/UserSettings.cpp b/src/AppInstallerCLITests/UserSettings.cpp @@ -413,3 +413,16 @@ TEST_CASE("SettingsExperimentalCmd", "[settings]") REQUIRE(userSettingTest.GetWarnings().size() == 0); } } + +TEST_CASE("SettingsPortableAppRoot", "[settings]") +{ + SECTION("Relative path") + { + std::string_view json = R"({ "installBehavior": { "portableAppUserRoot": %LOCALAPPDATA%/Portable/Root } })"; + SetSetting(Stream::PrimaryUserSettings, json); + UserSettingsTest userSettingTest; + + REQUIRE(userSettingTest.Get<Setting::PortableAppUserRoot>().empty()); + REQUIRE(userSettingTest.GetWarnings().size() == 1); + } +} diff --git a/src/AppInstallerCLITests/YamlManifest.cpp b/src/AppInstallerCLITests/YamlManifest.cpp @@ -254,6 +254,9 @@ TEST_CASE("ReadBadManifests", "[ManifestValidation]") { "Manifest-Bad-InstallerTypeExeRoot-NoSilentRoot.yaml", "Silent and SilentWithProgress switches are not specified for InstallerType exe.", true }, { "Manifest-Bad-InstallerTypeInvalid.yaml", "Invalid field value. Field: InstallerType" }, { "Manifest-Bad-InstallerTypeMissing.yaml", "Invalid field value. Field: InstallerType" }, + { "Manifest-Bad-InstallerTypePortable-InvalidAppsAndFeatures.yaml", "Only zero or one entry for Apps and Features may be specified for InstallerType portable." }, + { "Manifest-Bad-InstallerTypePortable-InvalidCommands.yaml", "Only zero or one value for Commands may be specified for InstallerType portable." }, + { "Manifest-Bad-InstallerTypePortable-InvalidScope.yaml", "Scope is not supported for InstallerType portable." }, { "Manifest-Bad-InstallerUniqueness.yaml", "Duplicate installer entry found." }, { "Manifest-Bad-InstallerUniqueness-DefaultScope.yaml", "Duplicate installer entry found." }, { "Manifest-Bad-InstallerUniqueness-DefaultValues.yaml", "Duplicate installer entry found." }, @@ -395,6 +398,15 @@ void VerifyV1ManifestContent(const Manifest& manifest, bool isSingleton, Manifes REQUIRE(manifest.DefaultLocalization.Get<Localization::Agreements>().at(0).AgreementUrl == "https://DefaultAgreementUrl.net"); } + if (manifestVer >= ManifestVer{ s_ManifestVersionV1_2 }) + { + REQUIRE(manifest.DefaultLocalization.Get<Localization::PurchaseUrl>() == "https://DefaultPurchaseUrl.com"); + REQUIRE(manifest.DefaultLocalization.Get<Localization::InstallationNotes>() == "Default installation notes"); + REQUIRE(manifest.DefaultLocalization.Get<Localization::Documentations>().size() == 1); + REQUIRE(manifest.DefaultLocalization.Get<Localization::Documentations>().at(0).DocumentLabel == "Default document label"); + REQUIRE(manifest.DefaultLocalization.Get<Localization::Documentations>().at(0).DocumentUrl == "https://DefaultDocumentUrl.com"); + } + REQUIRE(manifest.DefaultInstallerInfo.Locale == "en-US"); REQUIRE(manifest.DefaultInstallerInfo.Platform == std::vector<PlatformEnum>{ PlatformEnum::Desktop, PlatformEnum::Universal }); REQUIRE(manifest.DefaultInstallerInfo.MinOSVersion == "10.0.0.0"); @@ -448,7 +460,15 @@ void VerifyV1ManifestContent(const Manifest& manifest, bool isSingleton, Manifes REQUIRE(manifest.DefaultInstallerInfo.Markets.AllowedMarkets.size() == 1); REQUIRE(manifest.DefaultInstallerInfo.Markets.AllowedMarkets.at(0) == "US"); REQUIRE(manifest.DefaultInstallerInfo.ExpectedReturnCodes.size() == 1); - REQUIRE(manifest.DefaultInstallerInfo.ExpectedReturnCodes.at(10) == ExpectedReturnCodeEnum::PackageInUse); + REQUIRE(manifest.DefaultInstallerInfo.ExpectedReturnCodes.at(10).ReturnResponseEnum == ExpectedReturnCodeEnum::PackageInUse); + } + + if (manifestVer >= ManifestVer{ s_ManifestVersionV1_2 }) + { + REQUIRE(manifest.DefaultInstallerInfo.DisplayInstallWarnings); + REQUIRE(manifest.DefaultInstallerInfo.UnsupportedArguments.size() == 2); + REQUIRE(manifest.DefaultInstallerInfo.UnsupportedArguments.at(0) == UnsupportedArgumentEnum::Log); + REQUIRE(manifest.DefaultInstallerInfo.UnsupportedArguments.at(1) == UnsupportedArgumentEnum::Location); } if (isSingleton) @@ -517,7 +537,14 @@ void VerifyV1ManifestContent(const Manifest& manifest, bool isSingleton, Manifes REQUIRE(installer1.Markets.AllowedMarkets.size() == 0); REQUIRE(installer1.Markets.ExcludedMarkets.size() == 1); REQUIRE(installer1.Markets.ExcludedMarkets.at(0) == "US"); - REQUIRE(installer1.ExpectedReturnCodes.at(2) == ExpectedReturnCodeEnum::ContactSupport); + REQUIRE(installer1.ExpectedReturnCodes.at(2).ReturnResponseEnum == ExpectedReturnCodeEnum::ContactSupport); + } + + if (manifestVer >= ManifestVer{ s_ManifestVersionV1_2 }) + { + REQUIRE_FALSE(installer1.DisplayInstallWarnings); + REQUIRE(installer1.ExpectedReturnCodes.at(3).ReturnResponseEnum == ExpectedReturnCodeEnum::Custom); + REQUIRE(installer1.ExpectedReturnCodes.at(3).ReturnResponseUrl == "https://defaultReturnResponseUrl.com"); } if (!isSingleton) @@ -548,7 +575,7 @@ void VerifyV1ManifestContent(const Manifest& manifest, bool isSingleton, Manifes REQUIRE(installer2.Markets.AllowedMarkets.size() == 1); REQUIRE(installer2.Markets.AllowedMarkets.at(0) == "US"); REQUIRE(installer2.ExpectedReturnCodes.size() == 1); - REQUIRE(installer2.ExpectedReturnCodes.at(10) == ExpectedReturnCodeEnum::PackageInUse); + REQUIRE(installer2.ExpectedReturnCodes.at(10).ReturnResponseEnum == ExpectedReturnCodeEnum::PackageInUse); } if (manifestVer >= ManifestVer{ s_ManifestVersionV1_2 }) @@ -559,6 +586,10 @@ void VerifyV1ManifestContent(const Manifest& manifest, bool isSingleton, Manifes REQUIRE(installer3.Url == "https://www.microsoft.com/msixsdk/msixsdkx86.exe"); REQUIRE(installer3.Sha256 == SHA256::ConvertToBytes("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82")); REQUIRE(installer3.Commands == MultiValue{ "standalone" }); + REQUIRE(installer3.ExpectedReturnCodes.size() == 1); + REQUIRE(installer3.ExpectedReturnCodes.at(11).ReturnResponseEnum == ExpectedReturnCodeEnum::Custom); + REQUIRE(installer3.ExpectedReturnCodes.at(11).ReturnResponseUrl == "https://defaultReturnResponseUrl.com"); + REQUIRE_FALSE(installer3.DisplayInstallWarnings); } // Localization @@ -589,6 +620,15 @@ void VerifyV1ManifestContent(const Manifest& manifest, bool isSingleton, Manifes REQUIRE(localization1.Get<Localization::Agreements>().at(0).AgreementText == "Text"); REQUIRE(localization1.Get<Localization::Agreements>().at(0).AgreementUrl == "https://AgreementUrl.net"); } + + if (manifestVer >= ManifestVer{ s_ManifestVersionV1_2 }) + { + REQUIRE(localization1.Get<Localization::PurchaseUrl>() == "https://DefaultPurchaseUrl.com"); + REQUIRE(localization1.Get<Localization::InstallationNotes>() == "Default installation notes"); + REQUIRE(localization1.Get<Localization::Documentations>().size() == 1); + REQUIRE(localization1.Get<Localization::Documentations>().at(0).DocumentLabel == "Default document label"); + REQUIRE(localization1.Get<Localization::Documentations>().at(0).DocumentUrl == "https://DefaultDocumentUrl.com"); + } } } diff --git a/src/AppInstallerCommonCore/ExperimentalFeature.cpp b/src/AppInstallerCommonCore/ExperimentalFeature.cpp @@ -42,6 +42,8 @@ namespace AppInstaller::Settings return userSettings.Get<Setting::EFDependencies>(); case ExperimentalFeature::Feature::DirectMSI: return userSettings.Get<Setting::EFDirectMSI>(); + case ExperimentalFeature::Feature::PortableInstall: + return userSettings.Get<Setting::EFPortableInstall>(); default: THROW_HR(E_UNEXPECTED); } @@ -73,6 +75,8 @@ namespace AppInstaller::Settings return ExperimentalFeature{ "Show Dependencies Information", "dependencies", "https://aka.ms/winget-settings", Feature::Dependencies }; case Feature::DirectMSI: return ExperimentalFeature{ "Direct MSI Installation", "directMSI", "https://aka.ms/winget-settings", Feature::DirectMSI }; + case Feature::PortableInstall: + return ExperimentalFeature{ "Portable Installation", "portableInstall", "https://aka.ms/winget-settings", Feature::PortableInstall }; default: THROW_HR(E_UNEXPECTED); } diff --git a/src/AppInstallerCommonCore/Manifest/ManifestCommon.cpp b/src/AppInstallerCommonCore/Manifest/ManifestCommon.cpp @@ -254,6 +254,22 @@ namespace AppInstaller::Manifest return result; } + UnsupportedArgumentEnum ConvertToUnsupportedArgumentEnum(const std::string& in) + { + UnsupportedArgumentEnum result = UnsupportedArgumentEnum::Unknown; + + if (Utility::CaseInsensitiveEquals(in, "log")) + { + result = UnsupportedArgumentEnum::Log; + } + else if (Utility::CaseInsensitiveEquals(in, "location")) + { + result = UnsupportedArgumentEnum::Location; + } + + return result; + } + ManifestTypeEnum ConvertToManifestTypeEnum(const std::string& in) { if (in == "singleton") @@ -351,6 +367,10 @@ namespace AppInstaller::Manifest { result = ExpectedReturnCodeEnum::BlockedByPolicy; } + else if (inStrLower == "custom") + { + result = ExpectedReturnCodeEnum::Custom; + } return result; } diff --git a/src/AppInstallerCommonCore/Manifest/ManifestSchemaValidation.cpp b/src/AppInstallerCommonCore/Manifest/ManifestSchemaValidation.cpp @@ -29,6 +29,7 @@ namespace AppInstaller::Manifest::YamlParser { "InstallerAbortsTerminal"sv, YamlScalarType::Bool }, { "InstallLocationRequired"sv, YamlScalarType::Bool }, { "RequireExplicitUpgrade"sv, YamlScalarType::Bool }, + { "DisplayInstallWarnings"sv, YamlScalarType::Bool }, { "InstallerReturnCode"sv, YamlScalarType::Int }, }; diff --git a/src/AppInstallerCommonCore/Manifest/ManifestValidation.cpp b/src/AppInstallerCommonCore/Manifest/ManifestValidation.cpp @@ -153,6 +153,22 @@ namespace AppInstaller::Manifest resultErrors.emplace_back(ManifestError::ExeInstallerMissingSilentSwitches, ValidationError::Level::Warning); } + if (installer.InstallerType == InstallerTypeEnum::Portable) + { + if (installer.AppsAndFeaturesEntries.size() > 1) + { + resultErrors.emplace_back(ManifestError::ExceededAppsAndFeaturesEntryLimit); + } + if (installer.Commands.size() > 1) + { + resultErrors.emplace_back(ManifestError::ExceededCommandsLimit); + } + if (installer.Scope != ScopeEnum::Unknown) + { + resultErrors.emplace_back(ManifestError::ScopeNotSupported, ValidationError::Level::Warning); + } + } + // Check empty string before calling IsValidUrl to avoid duplicate error reporting. if (!installer.Url.empty() && IsValidURL(NULL, Utility::ConvertToUTF16(installer.Url).c_str(), 0) == S_FALSE) { diff --git a/src/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp b/src/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp @@ -7,6 +7,7 @@ namespace AppInstaller::Manifest { using ValidationErrors = std::vector<ValidationError>; + using ExpectedReturnCodeInfo = AppInstaller::Manifest::ManifestInstaller::ExpectedReturnCodeInfo; namespace { @@ -115,6 +116,20 @@ namespace AppInstaller::Manifest return result; } + + std::vector<UnsupportedArgumentEnum> ProcessUnsupportedArgumentsSequenceNode(const YAML::Node& node) + { + THROW_HR_IF(E_INVALIDARG, !node.IsSequence()); + + std::vector<UnsupportedArgumentEnum> result; + + for (auto const& entry : node.Sequence()) + { + result.emplace_back(ConvertToUnsupportedArgumentEnum(entry.as<std::string>())); + } + + return result; + } } std::vector<ManifestYamlPopulator::FieldProcessInfo> ManifestYamlPopulator::GetRootFieldProcessInfo(const ManifestVer& manifestVersion) @@ -281,6 +296,17 @@ namespace AppInstaller::Manifest std::move(fields_v1_1.begin(), fields_v1_1.end(), std::inserter(result, result.end())); } + + if (manifestVersion >= ManifestVer{ s_ManifestVersionV1_2 }) + { + std::vector<FieldProcessInfo> fields_v1_2 = + { + { "UnsupportedArguments", [this](const YAML::Node& value)->ValidationErrors { m_p_installer->UnsupportedArguments = ProcessUnsupportedArgumentsSequenceNode(value); return {}; } }, + { "DisplayInstallWarnings", [this](const YAML::Node& value)->ValidationErrors { m_p_installer->DisplayInstallWarnings = value.as<bool>(); return {}; } }, + }; + + std::move(fields_v1_2.begin(), fields_v1_2.end(), std::inserter(result, result.end())); + } } return result; @@ -324,6 +350,11 @@ namespace AppInstaller::Manifest result.emplace_back("ReturnResponse", [this](const YAML::Node& value)->ValidationErrors { m_p_expectedReturnCode->ReturnResponse = ConvertToExpectedReturnCodeEnum(value.as<std::string>()); return {}; }); } + if (manifestVersion >= ManifestVer{ s_ManifestVersionV1_2 }) + { + result.emplace_back("ReturnResponseUrl", [this](const YAML::Node& value)->ValidationErrors { m_p_expectedReturnCode->ReturnResponseUrl = value.as<std::string>(); return {}; }); + } + return result; } @@ -399,6 +430,18 @@ namespace AppInstaller::Manifest std::move(fields_v1_1.begin(), fields_v1_1.end(), std::inserter(result, result.end())); } + + if (manifestVersion >= ManifestVer{ s_ManifestVersionV1_2 }) + { + std::vector<FieldProcessInfo> fields_v1_2 = + { + { "PurchaseUrl", [this](const YAML::Node& value)->ValidationErrors { m_p_localization->Add<Localization::PurchaseUrl>(value.as<std::string>()); return {}; } }, + { "InstallationNotes", [this](const YAML::Node& value)->ValidationErrors { m_p_localization->Add<Localization::InstallationNotes>(value.as<std::string>()); return {}; } }, + { "Documentations", [this](const YAML::Node& value)->ValidationErrors { return ProcessDocumentationsNode(value); }}, + }; + + std::move(fields_v1_2.begin(), fields_v1_2.end(), std::inserter(result, result.end())); + } } return result; @@ -500,6 +543,22 @@ namespace AppInstaller::Manifest return result; } + std::vector<ManifestYamlPopulator::FieldProcessInfo> ManifestYamlPopulator::GetDocumentationFieldProcessInfo(const ManifestVer& manifestVersion) + { + std::vector<FieldProcessInfo> result = {}; + + if (manifestVersion >= ManifestVer{ s_ManifestVersionV1_2 }) + { + result = + { + { "DocumentLabel", [this](const YAML::Node& value)->ValidationErrors { m_p_documentation->DocumentLabel = Utility::Trim(value.as<std::string>()); return {}; } }, + { "DocumentUrl", [this](const YAML::Node& value)->ValidationErrors { m_p_documentation->DocumentUrl = Utility::Trim(value.as<std::string>()); return {}; } }, + }; + } + + return result; + } + ValidationErrors ManifestYamlPopulator::ValidateAndProcessFields( const YAML::Node& rootNode, const std::vector<FieldProcessInfo>& fieldInfos) @@ -651,7 +710,7 @@ namespace AppInstaller::Manifest THROW_HR_IF(E_INVALIDARG, !returnCodesNode.IsSequence()); ValidationErrors resultErrors; - std::map<DWORD, ExpectedReturnCodeEnum> returnCodes; + std::map<DWORD, ExpectedReturnCodeInfo> returnCodes; for (auto const& entry : returnCodesNode.Sequence()) { @@ -659,8 +718,7 @@ namespace AppInstaller::Manifest m_p_expectedReturnCode = &returnCode; auto errors = ValidateAndProcessFields(entry, ExpectedReturnCodesFieldInfos); std::move(errors.begin(), errors.end(), std::inserter(resultErrors, resultErrors.end())); - - if (!returnCodes.insert({ returnCode.InstallerReturnCode, returnCode.ReturnResponse }).second) + if (!returnCodes.insert({ returnCode.InstallerReturnCode, {returnCode.ReturnResponse, returnCode.ReturnResponseUrl} }).second) { resultErrors.emplace_back(ManifestError::DuplicateReturnCodeEntry); } @@ -671,6 +729,30 @@ namespace AppInstaller::Manifest return resultErrors; } + ValidationErrors ManifestYamlPopulator::ProcessDocumentationsNode(const YAML::Node& documentationsNode) + { + THROW_HR_IF(E_INVALIDARG, !documentationsNode.IsSequence()); + + ValidationErrors resultErrors; + std::vector<Documentation> documentations; + + for (auto const& entry : documentationsNode.Sequence()) + { + Documentation documentation; + m_p_documentation = &documentation; + auto errors = ValidateAndProcessFields(entry, DocumentationFieldInfos); + std::move(errors.begin(), errors.end(), std::inserter(resultErrors, resultErrors.end())); + documentations.emplace_back(std::move(documentation)); + } + + if (!documentations.empty()) + { + m_p_localization->Add<Localization::Documentations>(std::move(documentations)); + } + + return resultErrors; + } + ValidationErrors ManifestYamlPopulator::PopulateManifestInternal( const YAML::Node& rootNode, Manifest& manifest, @@ -694,6 +776,7 @@ namespace AppInstaller::Manifest AgreementFieldInfos = GetAgreementFieldProcessInfo(manifestVersion); MarketsFieldInfos = GetMarketsFieldProcessInfo(manifestVersion); AppsAndFeaturesEntryFieldInfos = GetAppsAndFeaturesEntryFieldProcessInfo(manifestVersion); + DocumentationFieldInfos = GetDocumentationFieldProcessInfo(manifestVersion); // Populate root m_p_manifest = &manifest; @@ -761,7 +844,7 @@ namespace AppInstaller::Manifest if (installer.ExpectedReturnCodes.find(defaultReturnCode.first) == installer.ExpectedReturnCodes.end() && std::find(installer.InstallerSuccessCodes.begin(), installer.InstallerSuccessCodes.end(), defaultReturnCode.first) == installer.InstallerSuccessCodes.end()) { - installer.ExpectedReturnCodes[defaultReturnCode.first] = defaultReturnCode.second; + installer.ExpectedReturnCodes[defaultReturnCode.first].ReturnResponseEnum = defaultReturnCode.second; } } diff --git a/src/AppInstallerCommonCore/Public/AppInstallerRuntime.h b/src/AppInstallerCommonCore/Public/AppInstallerRuntime.h @@ -47,6 +47,12 @@ namespace AppInstaller::Runtime SecureSettings, // The value of %USERPROFILE%. UserProfile, + // The location where portable packages are installed to with user scope. + PortableAppUserRoot, + // The location where portable packages are installed to with machine scope (x64). + PortableAppMachineRootX64, + // The location where portable packages are installed to with machine scope (x86). + PortableAppMachineRootX86, }; // Gets the path to the requested location. diff --git a/src/AppInstallerCommonCore/Public/winget/ExperimentalFeature.h b/src/AppInstallerCommonCore/Public/winget/ExperimentalFeature.h @@ -23,6 +23,7 @@ namespace AppInstaller::Settings Dependencies = 0x1, // Before making DirectMSI non-experimental, it should be part of manifest validation. DirectMSI = 0x2, + PortableInstall = 0x4, 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/ManifestCommon.h b/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h @@ -130,12 +130,7 @@ bool HasExtension(std::string_view extension) const; AlreadyInstalled, Downgrade, BlockedByPolicy, - }; - - struct ExpectedReturnCode - { - DWORD InstallerReturnCode; - ExpectedReturnCodeEnum ReturnResponse; + Custom, }; enum class PlatformEnum @@ -153,6 +148,13 @@ bool HasExtension(std::string_view extension) const; ElevatesSelf, }; + enum class UnsupportedArgumentEnum + { + Unknown, + Log, + Location + }; + enum class ManifestTypeEnum { Singleton, @@ -172,6 +174,13 @@ bool HasExtension(std::string_view extension) const; External }; + struct ExpectedReturnCode + { + DWORD InstallerReturnCode = 0; + ExpectedReturnCodeEnum ReturnResponse = ExpectedReturnCodeEnum::Unknown; + string_t ReturnResponseUrl; + }; + struct Dependency { DependencyType Type; @@ -246,6 +255,8 @@ bool HasExtension(std::string_view extension) const; ElevationRequirementEnum ConvertToElevationRequirementEnum(const std::string& in); + UnsupportedArgumentEnum ConvertToUnsupportedArgumentEnum(const std::string& in); + ManifestTypeEnum ConvertToManifestTypeEnum(const std::string& in); ExpectedReturnCodeEnum ConvertToExpectedReturnCodeEnum(const std::string& in); diff --git a/src/AppInstallerCommonCore/Public/winget/ManifestInstaller.h b/src/AppInstallerCommonCore/Public/winget/ManifestInstaller.h @@ -51,7 +51,13 @@ namespace AppInstaller::Manifest std::vector<DWORD> InstallerSuccessCodes; - std::map<DWORD, ExpectedReturnCodeEnum> ExpectedReturnCodes; + struct ExpectedReturnCodeInfo + { + ExpectedReturnCodeEnum ReturnResponseEnum = ExpectedReturnCodeEnum::Unknown; + string_t ReturnResponseUrl; + }; + + std::map<DWORD, ExpectedReturnCodeInfo> ExpectedReturnCodes; UpdateBehaviorEnum UpdateBehavior = UpdateBehaviorEnum::Install; @@ -83,6 +89,10 @@ namespace AppInstaller::Manifest bool RequireExplicitUpgrade = false; + bool DisplayInstallWarnings = false; + + std::vector<UnsupportedArgumentEnum> UnsupportedArguments; + std::vector<AppInstaller::Utility::Architecture> UnsupportedOSArchitectures; std::vector<AppsAndFeaturesEntry> AppsAndFeaturesEntries; diff --git a/src/AppInstallerCommonCore/Public/winget/ManifestLocalization.h b/src/AppInstallerCommonCore/Public/winget/ManifestLocalization.h @@ -26,8 +26,11 @@ namespace AppInstaller::Manifest Description, Tags, Agreements, + Documentations, ReleaseNotes, ReleaseNotesUrl, + PurchaseUrl, + InstallationNotes, Max }; @@ -38,6 +41,12 @@ namespace AppInstaller::Manifest string_t AgreementUrl; }; + struct Documentation + { + string_t DocumentLabel; + string_t DocumentUrl; + }; + namespace details { template <Localization L> @@ -58,6 +67,12 @@ namespace AppInstaller::Manifest using value_t = std::vector<Agreement>; }; + template <> + struct LocalizationMapping<Localization::Documentations> + { + using value_t = std::vector<Documentation>; + }; + // Used to deduce the LocalizationVariant type; making a variant that includes std::monostate and all LocalizationMapping types. template <size_t... I> inline auto Deduce(std::index_sequence<I...>) { return std::variant<std::monostate, typename LocalizationMapping<static_cast<Localization>(I)>::value_t...>{}; } diff --git a/src/AppInstallerCommonCore/Public/winget/ManifestValidation.h b/src/AppInstallerCommonCore/Public/winget/ManifestValidation.h @@ -48,6 +48,9 @@ namespace AppInstaller::Manifest const char* const MissingManifestDependenciesNode = "Dependency not found: "; const char* const NoSuitableMinVersion = "No Suitable Minimum Version: "; const char* const FoundLoop = "Loop found."; + const char* const ExceededAppsAndFeaturesEntryLimit = "Only zero or one entry for Apps and Features may be specified for InstallerType portable."; + const char* const ExceededCommandsLimit = "Only zero or one value for Commands may be specified for InstallerType portable."; + const char* const ScopeNotSupported = "Scope is not supported for InstallerType portable."; } struct ValidationError diff --git a/src/AppInstallerCommonCore/Public/winget/ManifestYamlPopulator.h b/src/AppInstallerCommonCore/Public/winget/ManifestYamlPopulator.h @@ -41,6 +41,7 @@ namespace AppInstaller::Manifest std::vector<FieldProcessInfo> AgreementFieldInfos; std::vector<FieldProcessInfo> MarketsFieldInfos; std::vector<FieldProcessInfo> AppsAndFeaturesEntryFieldInfos; + std::vector<FieldProcessInfo> DocumentationFieldInfos; // These pointers are referenced in the processing functions in manifest field process info table. AppInstaller::Manifest::Manifest* m_p_manifest = nullptr; @@ -53,6 +54,7 @@ namespace AppInstaller::Manifest AppInstaller::Manifest::Agreement* m_p_agreement = nullptr; AppInstaller::Manifest::MarketsInfo* m_p_markets = nullptr; AppInstaller::Manifest::AppsAndFeaturesEntry* m_p_appsAndFeaturesEntry = nullptr; + AppInstaller::Manifest::Documentation* m_p_documentation = nullptr; // Cache of Installers node and Localization node YAML::Node const* m_p_installersNode = nullptr; @@ -68,6 +70,7 @@ namespace AppInstaller::Manifest std::vector<FieldProcessInfo> GetAgreementFieldProcessInfo(const ManifestVer& manifestVersion); std::vector<FieldProcessInfo> GetMarketsFieldProcessInfo(const ManifestVer& manifestVersion); std::vector<FieldProcessInfo> GetAppsAndFeaturesEntryFieldProcessInfo(const ManifestVer& manifestVersion); + std::vector<FieldProcessInfo> GetDocumentationFieldProcessInfo(const ManifestVer& manifestVersion); // This method takes YAML root node and list of manifest field info. // Yaml lib does not support case insensitive search and it allows duplicate keys. If duplicate keys exist, @@ -83,6 +86,7 @@ namespace AppInstaller::Manifest std::vector<ValidationError> ProcessMarketsNode(const YAML::Node& marketsNode); std::vector<ValidationError> ProcessAppsAndFeaturesEntriesNode(const YAML::Node& appsAndFeaturesEntriesNode); std::vector<ValidationError> ProcessExpectedReturnCodesNode(const YAML::Node& returnCodesNode); + std::vector<ValidationError> ProcessDocumentationsNode(const YAML::Node& documentations); std::vector<ValidationError> PopulateManifestInternal( const YAML::Node& rootNode, diff --git a/src/AppInstallerCommonCore/Public/winget/UserSettings.h b/src/AppInstallerCommonCore/Public/winget/UserSettings.h @@ -71,6 +71,7 @@ namespace AppInstaller::Settings EFExperimentalCmd, EFExperimentalArg, EFDependencies, + EFPortableInstall, TelemetryDisable, InstallScopePreference, InstallScopeRequirement, @@ -83,6 +84,10 @@ namespace AppInstaller::Settings EFDirectMSI, EnableSelfInitiatedMinidump, LoggingLevelPreference, + InstallIgnoreWarnings, + PortableAppUserRoot, + PortableAppMachineRoot, + UninstallPurgePortableApp, Max }; @@ -121,6 +126,7 @@ namespace AppInstaller::Settings SETTINGMAPPING_SPECIALIZATION(Setting::EFExperimentalCmd, bool, bool, false, ".experimentalFeatures.experimentalCmd"sv); SETTINGMAPPING_SPECIALIZATION(Setting::EFExperimentalArg, bool, bool, false, ".experimentalFeatures.experimentalArg"sv); SETTINGMAPPING_SPECIALIZATION(Setting::EFDependencies, bool, bool, false, ".experimentalFeatures.dependencies"sv); + SETTINGMAPPING_SPECIALIZATION(Setting::EFPortableInstall, bool, bool, false, ".experimentalFeatures.portableInstall"sv); SETTINGMAPPING_SPECIALIZATION(Setting::TelemetryDisable, bool, bool, false, ".telemetry.disable"sv); SETTINGMAPPING_SPECIALIZATION(Setting::InstallArchitecturePreference, std::vector<std::string>, std::vector<Utility::Architecture>, {}, ".installBehavior.preferences.architectures"sv); SETTINGMAPPING_SPECIALIZATION(Setting::InstallArchitectureRequirement, std::vector<std::string>, std::vector<Utility::Architecture>, {}, ".installBehavior.requirements.architectures"sv); @@ -130,6 +136,10 @@ namespace AppInstaller::Settings SETTINGMAPPING_SPECIALIZATION(Setting::NetworkDOProgressTimeoutInSeconds, uint32_t, std::chrono::seconds, 60s, ".network.doProgressTimeoutInSeconds"sv); SETTINGMAPPING_SPECIALIZATION(Setting::InstallLocalePreference, std::vector<std::string>, std::vector<std::string>, {}, ".installBehavior.preferences.locale"sv); SETTINGMAPPING_SPECIALIZATION(Setting::InstallLocaleRequirement, std::vector<std::string>, std::vector<std::string>, {}, ".installBehavior.requirements.locale"sv); + SETTINGMAPPING_SPECIALIZATION(Setting::InstallIgnoreWarnings, bool, bool, false, ".installBehavior.ignoreWarnings"sv); + SETTINGMAPPING_SPECIALIZATION(Setting::PortableAppUserRoot, std::string, std::filesystem::path, {}, ".installBehavior.portableAppUserRoot"sv); + SETTINGMAPPING_SPECIALIZATION(Setting::PortableAppMachineRoot, std::string, std::filesystem::path, {}, ".installBehavior.portableAppMachineRoot"sv); + SETTINGMAPPING_SPECIALIZATION(Setting::UninstallPurgePortableApp, bool, bool, false, ".uninstallBehavior.purgePortableApp"sv); SETTINGMAPPING_SPECIALIZATION(Setting::EFDirectMSI, bool, bool, false, ".experimentalFeatures.directMSI"sv); SETTINGMAPPING_SPECIALIZATION(Setting::EnableSelfInitiatedMinidump, bool, bool, false, ".debugging.enableSelfInitiatedMinidump"sv); SETTINGMAPPING_SPECIALIZATION(Setting::LoggingLevelPreference, std::string, Logging::Level, Logging::Level::Info, ".logging.level"sv); diff --git a/src/AppInstallerCommonCore/Runtime.cpp b/src/AppInstallerCommonCore/Runtime.cpp @@ -4,6 +4,7 @@ #include <binver/version.h> #include "Public/AppInstallerRuntime.h" #include "Public/AppInstallerStrings.h" +#include "Public/winget/UserSettings.h" #include <optional> @@ -12,6 +13,7 @@ namespace AppInstaller::Runtime { using namespace Utility; + using namespace Settings; namespace { @@ -22,6 +24,9 @@ namespace AppInstaller::Runtime constexpr std::string_view s_SecureSettings_Base = "Microsoft/WinGet"sv; constexpr std::string_view s_SecureSettings_UserRelative = "settings"sv; constexpr std::string_view s_SecureSettings_Relative_Unpackaged = "win"sv; + constexpr std::string_view s_PortableAppUserRoot = "Microsoft/WinGet"sv; + constexpr std::string_view s_PortableAppMachineRoot = "WinGet"sv; + constexpr std::string_view s_PortablePackagesDirectory = "Packages"sv; #ifndef WINGET_DISABLE_FOR_FUZZING constexpr std::string_view s_SecureSettings_Relative_Packaged = "pkg"sv; #endif @@ -309,6 +314,36 @@ namespace AppInstaller::Runtime result = GetKnownFolderPath(FOLDERID_Profile); create = false; break; + case PathName::PortableAppUserRoot: + result = Settings::User().Get<Setting::PortableAppUserRoot>(); + if (result.empty()) + { + result = GetKnownFolderPath(FOLDERID_LocalAppData); + result /= s_PortableAppUserRoot; + result /= s_PortablePackagesDirectory; + } + create = true; + break; + case PathName::PortableAppMachineRootX64: + result = Settings::User().Get<Setting::PortableAppMachineRoot>(); + if (result.empty()) + { + result = GetKnownFolderPath(FOLDERID_ProgramFilesX64); + result /= s_PortableAppMachineRoot; + result /= s_PortablePackagesDirectory; + } + create = true; + break; + case PathName::PortableAppMachineRootX86: + result = Settings::User().Get<Setting::PortableAppMachineRoot>(); + if (result.empty()) + { + result = GetKnownFolderPath(FOLDERID_ProgramFilesX86); + result /= s_PortableAppMachineRoot; + result /= s_PortablePackagesDirectory; + } + create = true; + break; default: THROW_HR(E_UNEXPECTED); } @@ -349,6 +384,36 @@ namespace AppInstaller::Runtime result = GetKnownFolderPath(FOLDERID_Profile); create = false; break; + case PathName::PortableAppUserRoot: + result = Settings::User().Get<Setting::PortableAppUserRoot>(); + if (result.empty()) + { + result = GetKnownFolderPath(FOLDERID_LocalAppData); + result /= s_PortableAppUserRoot; + result /= s_PortablePackagesDirectory; + } + create = true; + break; + case PathName::PortableAppMachineRootX64: + result = Settings::User().Get<Setting::PortableAppMachineRoot>(); + if (result.empty()) + { + result = GetKnownFolderPath(FOLDERID_ProgramFilesX64); + result /= s_PortableAppMachineRoot; + result /= s_PortablePackagesDirectory; + } + create = true; + break; + case PathName::PortableAppMachineRootX86: + result = Settings::User().Get<Setting::PortableAppMachineRoot>(); + if (result.empty()) + { + result = GetKnownFolderPath(FOLDERID_ProgramFilesX86); + result /= s_PortableAppMachineRoot; + result /= s_PortablePackagesDirectory; + } + create = true; + break; default: THROW_HR(E_UNEXPECTED); } diff --git a/src/AppInstallerCommonCore/UserSettings.cpp b/src/AppInstallerCommonCore/UserSettings.cpp @@ -228,9 +228,28 @@ namespace AppInstaller::Settings WINGET_VALIDATE_PASS_THROUGH(EFExperimentalCmd) WINGET_VALIDATE_PASS_THROUGH(EFExperimentalArg) WINGET_VALIDATE_PASS_THROUGH(EFDependencies) + WINGET_VALIDATE_PASS_THROUGH(EFPortableInstall) WINGET_VALIDATE_PASS_THROUGH(TelemetryDisable) WINGET_VALIDATE_PASS_THROUGH(EFDirectMSI) WINGET_VALIDATE_PASS_THROUGH(EnableSelfInitiatedMinidump) + WINGET_VALIDATE_PASS_THROUGH(InstallIgnoreWarnings) + WINGET_VALIDATE_PASS_THROUGH(UninstallPurgePortableApp) + + WINGET_VALIDATE_SIGNATURE(PortableAppUserRoot) + { + std::filesystem::path root = ConvertToUTF16(value); + if (!root.is_absolute()) + { + return {}; + } + + return root; + } + + WINGET_VALIDATE_SIGNATURE(PortableAppMachineRoot) + { + return SettingMapping<Setting::PortableAppUserRoot>::Validate(value); + } WINGET_VALIDATE_SIGNATURE(InstallArchitecturePreference) { diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/1_1/Json/ManifestDeserializer_1_1.cpp b/src/AppInstallerRepositoryCore/Rest/Schema/1_1/Json/ManifestDeserializer_1_1.cpp @@ -139,7 +139,7 @@ namespace AppInstaller::Repository::Rest::Schema::V1_1::Json // Only add when it is valid if (installerReturnCode != 0 && returnResponse != ExpectedReturnCodeEnum::Unknown) { - if (!installer.ExpectedReturnCodes.insert({ installerReturnCode, returnResponse }).second) + if (!installer.ExpectedReturnCodes.insert({ installerReturnCode, { returnResponse, "" } }).second) { AICLI_LOG(Repo, Error, << "Expected return codes cannot have repeated value."); return {}; @@ -155,7 +155,7 @@ namespace AppInstaller::Repository::Rest::Schema::V1_1::Json if (installer.ExpectedReturnCodes.find(defaultReturnCode.first) == installer.ExpectedReturnCodes.end() && std::find(installer.InstallerSuccessCodes.begin(), installer.InstallerSuccessCodes.end(), defaultReturnCode.first) == installer.InstallerSuccessCodes.end()) { - installer.ExpectedReturnCodes[defaultReturnCode.first] = defaultReturnCode.second; + installer.ExpectedReturnCodes[defaultReturnCode.first].ReturnResponseEnum = defaultReturnCode.second; } } } diff --git a/src/ManifestSchema/ManifestSchema.vcxitems.filters b/src/ManifestSchema/ManifestSchema.vcxitems.filters @@ -15,7 +15,7 @@ </Filter> <Filter Include="schema\v1.2.0"> <UniqueIdentifier>{fb3524f4-5541-468d-a33c-ddcdad90484d}</UniqueIdentifier> - </Filter> + </Filter> </ItemGroup> <ItemGroup> <ClInclude Include="$(MSBuildThisFileDirectory)ManifestSchema.h" /> @@ -58,6 +58,9 @@ <None Include="$(MSBuildThisFileDirectory)..\..\schemas\JSON\manifests\v1.1.0\manifest.version.1.1.0.json"> <Filter>schema\v1.1.0</Filter> </None> + <None Include="$(MSBuildThisFileDirectory)..\..\schemas\JSON\manifests\v1.2.0\manifest.version.1.2.0.json"> + <Filter>schema\v1.2.0</Filter> + </None> <None Include="$(MSBuildThisFileDirectory)..\..\schemas\JSON\manifests\v1.2.0\manifest.defaultLocale.1.2.0.json"> <Filter>schema\v1.2.0</Filter> </None> @@ -70,8 +73,5 @@ <None Include="$(MSBuildThisFileDirectory)..\..\schemas\JSON\manifests\v1.2.0\manifest.singleton.1.2.0.json"> <Filter>schema\v1.2.0</Filter> </None> - <None Include="$(MSBuildThisFileDirectory)..\..\schemas\JSON\manifests\v1.2.0\manifest.version.1.2.0.json"> - <Filter>schema\v1.2.0</Filter> - </None> </ItemGroup> </Project> \ No newline at end of file