From 65789699ef986d0f7c833aadfb94c7dc9eac7a36 Mon Sep 17 00:00:00 2001 From: John McPherson Date: Thu, 15 Jan 2026 17:20:44 -0800 Subject: [PATCH 1/8] Update schema versions --- .../latest/manifest.defaultLocale.latest.json | 6 +++--- .../latest/manifest.installer.latest.json | 6 +++--- .../latest/manifest.locale.latest.json | 6 +++--- .../latest/manifest.singleton.latest.json | 6 +++--- .../latest/manifest.version.latest.json | 6 +++--- src/ManifestSchema/ManifestSchema.vcxitems | 5 +++++ .../ManifestSchema.vcxitems.filters | 18 ++++++++++++++++++ 7 files changed, 38 insertions(+), 15 deletions(-) diff --git a/schemas/JSON/manifests/latest/manifest.defaultLocale.latest.json b/schemas/JSON/manifests/latest/manifest.defaultLocale.latest.json index 5d96d684e8..dd9e967b82 100644 --- a/schemas/JSON/manifests/latest/manifest.defaultLocale.latest.json +++ b/schemas/JSON/manifests/latest/manifest.defaultLocale.latest.json @@ -1,7 +1,7 @@ { - "$id": "https://aka.ms/winget-manifest.defaultlocale.1.12.0.schema.json", + "$id": "https://aka.ms/winget-manifest.defaultlocale.1.28.0.schema.json", "$schema": "http://json-schema.org/draft-07/schema#", - "description": "A representation of a multiple-file manifest representing a default app metadata in the OWC. v1.12.0", + "description": "A representation of a multiple-file manifest representing a default app metadata in the OWC. v1.28.0", "definitions": { "Url": { "type": [ "string", "null" ], @@ -261,7 +261,7 @@ }, "ManifestVersion": { "type": "string", - "default": "1.12.0", + "default": "1.28.0", "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])){2}$", "description": "The manifest syntax version" } diff --git a/schemas/JSON/manifests/latest/manifest.installer.latest.json b/schemas/JSON/manifests/latest/manifest.installer.latest.json index 2f634646d9..833557988a 100644 --- a/schemas/JSON/manifests/latest/manifest.installer.latest.json +++ b/schemas/JSON/manifests/latest/manifest.installer.latest.json @@ -1,7 +1,7 @@ { - "$id": "https://aka.ms/winget-manifest.installer.1.12.0.schema.json", + "$id": "https://aka.ms/winget-manifest.installer.1.28.0.schema.json", "$schema": "http://json-schema.org/draft-07/schema#", - "description": "A representation of a single-file manifest representing an app installers in the OWC. v1.12.0", + "description": "A representation of a single-file manifest representing an app installers in the OWC. v1.28.0", "definitions": { "PackageIdentifier": { "type": "string", @@ -906,7 +906,7 @@ }, "ManifestVersion": { "type": "string", - "default": "1.12.0", + "default": "1.28.0", "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])){2}$", "description": "The manifest syntax version" } diff --git a/schemas/JSON/manifests/latest/manifest.locale.latest.json b/schemas/JSON/manifests/latest/manifest.locale.latest.json index f21e130f61..e381fb7381 100644 --- a/schemas/JSON/manifests/latest/manifest.locale.latest.json +++ b/schemas/JSON/manifests/latest/manifest.locale.latest.json @@ -1,7 +1,7 @@ { - "$id": "https://aka.ms/winget-manifest.locale.1.12.0.schema.json", + "$id": "https://aka.ms/winget-manifest.locale.1.28.0.schema.json", "$schema": "http://json-schema.org/draft-07/schema#", - "description": "A representation of a multiple-file manifest representing app metadata in other locale in the OWC. v1.12.0", + "description": "A representation of a multiple-file manifest representing app metadata in other locale in the OWC. v1.28.0", "definitions": { "Url": { "type": [ "string", "null" ], @@ -256,7 +256,7 @@ }, "ManifestVersion": { "type": "string", - "default": "1.12.0", + "default": "1.28.0", "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])){2}$", "description": "The manifest syntax version" } diff --git a/schemas/JSON/manifests/latest/manifest.singleton.latest.json b/schemas/JSON/manifests/latest/manifest.singleton.latest.json index 9a44532d61..4365f9df7a 100644 --- a/schemas/JSON/manifests/latest/manifest.singleton.latest.json +++ b/schemas/JSON/manifests/latest/manifest.singleton.latest.json @@ -1,7 +1,7 @@ { - "$id": "https://aka.ms/winget-manifest.singleton.1.12.0.schema.json", + "$id": "https://aka.ms/winget-manifest.singleton.1.28.0.schema.json", "$schema": "http://json-schema.org/draft-07/schema#", - "description": "A representation of a single-file manifest representing an app in the OWC. v1.12.0", + "description": "A representation of a single-file manifest representing an app in the OWC. v1.28.0", "definitions": { "PackageIdentifier": { "type": "string", @@ -1130,7 +1130,7 @@ }, "ManifestVersion": { "type": "string", - "default": "1.12.0", + "default": "1.28.0", "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])){2}$", "description": "The manifest syntax version" } diff --git a/schemas/JSON/manifests/latest/manifest.version.latest.json b/schemas/JSON/manifests/latest/manifest.version.latest.json index 2e4a2c8671..7d36364257 100644 --- a/schemas/JSON/manifests/latest/manifest.version.latest.json +++ b/schemas/JSON/manifests/latest/manifest.version.latest.json @@ -1,7 +1,7 @@ { - "$id": "https://aka.ms/winget-manifest.version.1.12.0.schema.json", + "$id": "https://aka.ms/winget-manifest.version.1.28.0.schema.json", "$schema": "http://json-schema.org/draft-07/schema#", - "description": "A representation of a multi-file manifest representing an app version in the OWC. v1.12.0", + "description": "A representation of a multi-file manifest representing an app version in the OWC. v1.28.0", "type": "object", "properties": { "PackageIdentifier": { @@ -31,7 +31,7 @@ }, "ManifestVersion": { "type": "string", - "default": "1.12.0", + "default": "1.28.0", "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])){2}$", "description": "The manifest syntax version" } diff --git a/src/ManifestSchema/ManifestSchema.vcxitems b/src/ManifestSchema/ManifestSchema.vcxitems index 5a505bd79e..a45d704ce5 100644 --- a/src/ManifestSchema/ManifestSchema.vcxitems +++ b/src/ManifestSchema/ManifestSchema.vcxitems @@ -37,6 +37,11 @@ + + + + + diff --git a/src/ManifestSchema/ManifestSchema.vcxitems.filters b/src/ManifestSchema/ManifestSchema.vcxitems.filters index 74251027af..6bc5c732d7 100644 --- a/src/ManifestSchema/ManifestSchema.vcxitems.filters +++ b/src/ManifestSchema/ManifestSchema.vcxitems.filters @@ -37,6 +37,9 @@ {9c132cfd-cf4c-48d0-a32b-c52213f742fe} + + {99217106-7710-40c7-a2df-e5b69c758e5a} + @@ -199,5 +202,20 @@ schema\v1.10.0 + + schema\v1.12.0 + + + schema\v1.12.0 + + + schema\v1.12.0 + + + schema\v1.12.0 + + + schema\v1.12.0 + \ No newline at end of file From 78b5f4d6c2f41cc173c68f1a7e1e8f10984ed421 Mon Sep 17 00:00:00 2001 From: John McPherson Date: Mon, 19 Jan 2026 12:25:06 -0800 Subject: [PATCH 2/8] Add initial schema iteration --- .../latest/manifest.installer.latest.json | 75 +++++++++++++++++++ .../latest/manifest.singleton.latest.json | 75 +++++++++++++++++++ 2 files changed, 150 insertions(+) diff --git a/schemas/JSON/manifests/latest/manifest.installer.latest.json b/schemas/JSON/manifests/latest/manifest.installer.latest.json index 833557988a..9048183dfb 100644 --- a/schemas/JSON/manifests/latest/manifest.installer.latest.json +++ b/schemas/JSON/manifests/latest/manifest.installer.latest.json @@ -639,6 +639,75 @@ ], "description": "The authentication requirement for downloading the installer." }, + "DesiredStateConfiguration": { + "type": [ "object", "null" ], + "description": "References to desired state configuration (DSC) resources that are related to the package.", + "properties": { + "PowerShell": { + "type": [ "array", "null" ], + "description": "Contains data about DSC resources that are contained in PowerShell modules.", + "uniqueItems": true, + "maxItems": 16, + "items": { + "type": "object", + "title": "PowerShell DSC Module Item", + "properties": { + "RepositoryURL": { + "$ref": "#/definitions/Url" + }, + "ModuleName": { + "type": "string", + "description": "The name of the module containing resources.", + "pattern": "^[A-Za-z][-._A-Za-z0-9]*$", + "maxLength": 100 + }, + "Resources": { + "type": "array", + "description": "The resources contained within the module.", + "maxItems": 64, + "items": { + "type": "object", + "title": "PowerShell DSC Resource Item", + "properties": { + "Name": { + "type": "string", + "description": "The name of the resource.", + "pattern": "^[A-Za-z][-_A-Za-z0-9]*$", + "maxLength": 100 + } + } + } + } + }, + "required": [ "RepositoryURL", "ModuleName", "Resources" ] + } + }, + "DSCv3": { + "type": [ "object", "null" ], + "description": "Contains data about DSC resources that are contained in the package using the DSC v3 specification.", + "properties": { + "Resources": { + "type": "array", + "description": "The resources contained within the package.", + "maxItems": 128, + "items": { + "type": "object", + "title": "DSCv3 Resource Item", + "properties": { + "Type": { + "type": "string", + "description": "The name of the resource.", + "pattern": "^\\w+(\\.\\w+){0,2}\\/\\w+$", + "maxLength": 256 + } + } + } + } + }, + "required": [ "Resources" ] + } + } + }, "Installer": { "type": "object", "properties": { @@ -765,6 +834,9 @@ }, "Authentication": { "$ref": "#/definitions/Authentication" + }, + "DesiredStateConfiguration": { + "$ref": "#/definitions/DesiredStateConfiguration" } }, "required": [ @@ -890,6 +962,9 @@ "Authentication": { "$ref": "#/definitions/Authentication" }, + "DesiredStateConfiguration": { + "$ref": "#/definitions/DesiredStateConfiguration" + }, "Installers": { "type": "array", "items": { diff --git a/schemas/JSON/manifests/latest/manifest.singleton.latest.json b/schemas/JSON/manifests/latest/manifest.singleton.latest.json index 4365f9df7a..9eb5ce0f93 100644 --- a/schemas/JSON/manifests/latest/manifest.singleton.latest.json +++ b/schemas/JSON/manifests/latest/manifest.singleton.latest.json @@ -740,6 +740,75 @@ ], "description": "The authentication requirement for downloading the installer." }, + "DesiredStateConfiguration": { + "type": [ "object", "null" ], + "description": "References to desired state configuration (DSC) resources that are related to the package.", + "properties": { + "PowerShell": { + "type": [ "array", "null" ], + "description": "Contains data about DSC resources that are contained in PowerShell modules.", + "uniqueItems": true, + "maxItems": 16, + "items": { + "type": "object", + "title": "PowerShell DSC Module Item", + "properties": { + "RepositoryURL": { + "$ref": "#/definitions/Url" + }, + "ModuleName": { + "type": "string", + "description": "The name of the module containing resources.", + "pattern": "^[A-Za-z][-._A-Za-z0-9]*$", + "maxLength": 100 + }, + "Resources": { + "type": "array", + "description": "The resources contained within the module.", + "maxItems": 64, + "items": { + "type": "object", + "title": "PowerShell DSC Resource Item", + "properties": { + "Name": { + "type": "string", + "description": "The name of the resource.", + "pattern": "^[A-Za-z][-_A-Za-z0-9]*$", + "maxLength": 100 + } + } + } + } + }, + "required": [ "RepositoryURL", "ModuleName", "Resources" ] + } + }, + "DSCv3": { + "type": [ "object", "null" ], + "description": "Contains data about DSC resources that are contained in the package using the DSC v3 specification.", + "properties": { + "Resources": { + "type": "array", + "description": "The resources contained within the package.", + "maxItems": 128, + "items": { + "type": "object", + "title": "DSCv3 Resource Item", + "properties": { + "Type": { + "type": "string", + "description": "The name of the resource.", + "pattern": "^\\w+(\\.\\w+){0,2}\\/\\w+$", + "maxLength": 256 + } + } + } + } + }, + "required": [ "Resources" ] + } + } + }, "Installer": { "type": "object", "properties": { @@ -866,6 +935,9 @@ }, "Authentication": { "$ref": "#/definitions/Authentication" + }, + "DesiredStateConfiguration": { + "$ref": "#/definitions/DesiredStateConfiguration" } }, "required": [ @@ -1114,6 +1186,9 @@ "Authentication": { "$ref": "#/definitions/Authentication" }, + "DesiredStateConfiguration": { + "$ref": "#/definitions/DesiredStateConfiguration" + }, "Installers": { "type": "array", "items": { From b07a19e1d24fa24aa66c4ab5dc0511c2a2579118 Mon Sep 17 00:00:00 2001 From: John McPherson Date: Mon, 19 Jan 2026 12:25:35 -0800 Subject: [PATCH 3/8] Start adding parsing --- .../Manifest/ManifestYamlPopulator.cpp | 17 +++++++++---- .../Public/winget/ManifestCommon.h | 24 +++++++++++++++++++ .../Public/winget/ManifestInstaller.h | 2 ++ 3 files changed, 38 insertions(+), 5 deletions(-) diff --git a/src/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp b/src/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp index 2d46f60403..215593efc2 100644 --- a/src/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp +++ b/src/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp @@ -1127,15 +1127,22 @@ namespace AppInstaller::Manifest { ManifestInstaller installer = m_manifest.get().DefaultInstallerInfo; +#define WINGET_STASH_INSTALLER_PROPERTY(_property_,_clear_) \ + auto stashed ## _property_ = std::move(installer. _property_); \ + installer. _property_ . _clear_ (); + +#define WINGET_UNSTASH_INSTALLER_PROPERTY(_property_) \ + installer. _property_ = std::move(stashed ## _property_); + // Clear these defaults as PackageFamilyName, ProductCode, AppsAndFeaturesEntries need to be copied based on InstallerType - installer.PackageFamilyName.clear(); - installer.ProductCode.clear(); - installer.AppsAndFeaturesEntries.clear(); + WINGET_STASH_INSTALLER_PROPERTY(PackageFamilyName, clear); + WINGET_STASH_INSTALLER_PROPERTY(ProductCode, clear); + WINGET_STASH_INSTALLER_PROPERTY(AppsAndFeaturesEntries, clear); // Clear dependencies as installer overrides root dependencies - installer.Dependencies.Clear(); + WINGET_STASH_INSTALLER_PROPERTY(Dependencies, Clear); // Clear nested installers as it should only be copied for zip installerType. installer.NestedInstallerType = InstallerTypeEnum::Unknown; - installer.NestedInstallerFiles.clear(); + WINGET_STASH_INSTALLER_PROPERTY(NestedInstallerFiles, clear); auto errors = ValidateAndProcessFields(entry, InstallerFieldInfos, VariantManifestPtr(&installer)); std::move(errors.begin(), errors.end(), std::inserter(resultErrors, resultErrors.end())); diff --git a/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h b/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h index 780c0e1e4d..19a43000fb 100644 --- a/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h +++ b/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h @@ -7,6 +7,7 @@ #include #include #include +#include namespace AppInstaller::Manifest { @@ -372,6 +373,29 @@ namespace AppInstaller::Manifest void Clear() { DefaultInstallLocation.clear(); Files.clear(); } }; + // Information about a specific DSC resource. + struct DesiredStateConfigurationResourceInfo + { + string_t Name; + }; + + // The type of resource container. + enum class DesiredStateConfigurationContainerType + { + PowerShell, + DSCv3, + }; + + // Information about a DSC container. + // Contains the union of properties relevant to all container types. + struct DesiredStateConfigurationContainerInfo + { + DesiredStateConfigurationContainerType Type; + string_t RepositoryURL; + string_t ModuleName; + std::vector Resources; + }; + InstallerTypeEnum ConvertToInstallerTypeEnum(const std::string& in); UpdateBehaviorEnum ConvertToUpdateBehaviorEnum(const std::string& in); diff --git a/src/AppInstallerCommonCore/Public/winget/ManifestInstaller.h b/src/AppInstallerCommonCore/Public/winget/ManifestInstaller.h index 9812933410..3ec7c9346f 100644 --- a/src/AppInstallerCommonCore/Public/winget/ManifestInstaller.h +++ b/src/AppInstallerCommonCore/Public/winget/ManifestInstaller.h @@ -120,5 +120,7 @@ namespace AppInstaller::Manifest bool ArchiveBinariesDependOnPath = false; Authentication::AuthenticationInfo AuthInfo; + + std::vector DesiredStateConfiguration; }; } From 85aba216d16d5dd1cab4a1b0f47ed04d70c8b51a Mon Sep 17 00:00:00 2001 From: John McPherson Date: Mon, 19 Jan 2026 14:43:37 -0800 Subject: [PATCH 4/8] Populator and write completed --- .../Manifest/ManifestYamlPopulator.cpp | 167 +++++++++++++++++- .../Manifest/YamlWriter.cpp | 92 ++++++++++ .../Public/winget/ManifestCommon.h | 6 +- .../Public/winget/ManifestYamlPopulator.h | 37 +++- .../Manifest/ManifestVersion.cs | 5 + 5 files changed, 299 insertions(+), 8 deletions(-) diff --git a/src/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp b/src/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp index 785a4f704c..ad79a84c72 100644 --- a/src/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp +++ b/src/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp @@ -398,6 +398,22 @@ namespace AppInstaller::Manifest std::move(fields_v1_10.begin(), fields_v1_10.end(), std::inserter(result, result.end())); } + + if (m_manifestVersion.get() >= ManifestVer{ s_ManifestVersionV1_28 }) + { + std::vector fields_v1_28 = + { + { "DesiredStateConfiguration", [this](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors + { + auto* installer = GetManifestInstallerPtr(v); + installer->DesiredStateConfiguration.clear(); + return ValidateAndProcessFields(value, DesiredStateConfigurationFieldInfos, VariantManifestPtr(&(installer->DesiredStateConfiguration))); + } + }, + }; + + std::move(fields_v1_28.begin(), fields_v1_28.end(), std::inserter(result, result.end())); + } } return result; @@ -808,6 +824,91 @@ namespace AppInstaller::Manifest return result; } + std::vector ManifestYamlPopulator::GetDesiredStateConfigurationFieldInfos() + { + std::vector result = {}; + + if (m_manifestVersion.get() >= ManifestVer{ s_ManifestVersionV1_28 }) + { + result = + { + { "PowerShell", [this](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { return ProcessDSC_PowerShellModuleNode(value, variant_ptr>(v)); } }, + { "DSCv3", [this](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors + { + auto* variantValue = variant_ptr>(v); + variantValue->emplace_back(); + variantValue->back().Type = DesiredStateConfigurationContainerType::DSCv3; + return ValidateAndProcessFields(value, DesiredStateConfigurationDSCv3FieldInfos, VariantManifestPtr(&variantValue->back())); + } + }, + }; + } + + return result; + } + + std::vector ManifestYamlPopulator::GetDesiredStateConfigurationPowerShellModuleFieldInfos() + { + std::vector result = {}; + + if (m_manifestVersion.get() >= ManifestVer{ s_ManifestVersionV1_28 }) + { + result = + { + { "RepositoryURL", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->RepositoryURL = Utility::Trim(value.as()); return {}; } }, + { "ModuleName", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->ModuleName = Utility::Trim(value.as()); return {}; } }, + { "Resources", [this](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { return ProcessDSC_PowerShellResourcesNode(value, variant_ptr(v)); } }, + }; + } + + return result; + } + + std::vector ManifestYamlPopulator::GetDesiredStateConfigurationPowerShellResourceFieldInfos() + { + std::vector result = {}; + + if (m_manifestVersion.get() >= ManifestVer{ s_ManifestVersionV1_28 }) + { + result = + { + { "Name", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->Name = Utility::Trim(value.as()); return {}; } }, + }; + } + + return result; + } + + std::vector ManifestYamlPopulator::GetDesiredStateConfigurationDSCv3FieldInfos() + { + std::vector result = {}; + + if (m_manifestVersion.get() >= ManifestVer{ s_ManifestVersionV1_28 }) + { + result = + { + { "Resources", [this](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { return ProcessDSCv3ResourcesNode(value, variant_ptr(v)); } }, + }; + } + + return result; + } + + std::vector ManifestYamlPopulator::GetDesiredStateConfigurationDSCv3ResourceFieldInfos() + { + std::vector result = {}; + + if (m_manifestVersion.get() >= ManifestVer{ s_ManifestVersionV1_28 }) + { + result = + { + { "Type", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->Name = Utility::Trim(value.as()); return {}; } }, + }; + } + + return result; + } + ValidationErrors ManifestYamlPopulator::ValidateAndProcessFields( const YAML::Node& rootNode, const std::vector& fieldInfos, @@ -1084,6 +1185,55 @@ namespace AppInstaller::Manifest return resultErrors; } + std::vector ManifestYamlPopulator::ProcessDSC_PowerShellModuleNode(const YAML::Node& node, std::vector* containers) + { + THROW_HR_IF(E_INVALIDARG, !node.IsSequence()); + + ValidationErrors resultErrors; + + for (auto const& entry : node.Sequence()) + { + auto& containerInfo = containers->emplace_back(); + auto errors = ValidateAndProcessFields(entry, DesiredStateConfigurationPowerShellModuleFieldInfos, VariantManifestPtr(&containerInfo)); + std::move(errors.begin(), errors.end(), std::inserter(resultErrors, resultErrors.end())); + } + + return resultErrors; + } + + std::vector ManifestYamlPopulator::ProcessDSC_PowerShellResourcesNode(const YAML::Node& node, DesiredStateConfigurationContainerInfo* container) + { + THROW_HR_IF(E_INVALIDARG, !node.IsSequence()); + + ValidationErrors resultErrors; + + for (auto const& entry : node.Sequence()) + { + auto& resourceInfo = container->Resources.emplace_back(); + auto errors = ValidateAndProcessFields(entry, DesiredStateConfigurationPowerShellResourceFieldInfos, VariantManifestPtr(&resourceInfo)); + std::move(errors.begin(), errors.end(), std::inserter(resultErrors, resultErrors.end())); + } + + return resultErrors; + } + + std::vector ManifestYamlPopulator::ProcessDSCv3ResourcesNode(const YAML::Node& node, DesiredStateConfigurationContainerInfo* container) + { + THROW_HR_IF(E_INVALIDARG, !node.IsSequence()); + + ValidationErrors resultErrors; + + for (auto const& entry : node.Sequence()) + { + auto& resourceInfo = container->Resources.emplace_back(); + auto errors = ValidateAndProcessFields(entry, DesiredStateConfigurationDSCv3ResourceFieldInfos, VariantManifestPtr(&resourceInfo)); + std::move(errors.begin(), errors.end(), std::inserter(resultErrors, resultErrors.end())); + } + + return resultErrors; + } + + ManifestYamlPopulator::ManifestYamlPopulator(YAML::Node& rootNode, Manifest& manifest, const ManifestVer& manifestVersion, ManifestValidateOption validateOption) : m_rootNode(rootNode), m_manifest(manifest), m_manifestVersion(manifestVersion), m_validateOption(validateOption) { @@ -1114,6 +1264,11 @@ namespace AppInstaller::Manifest InstallationMetadataFilesFieldInfos = GetInstallationMetadataFilesFieldProcessInfo(); AuthenticationFieldInfos = GetAuthenticationFieldInfos(); MicrosoftEntraIdAuthenticationInfoFieldInfos = GetMicrosoftEntraIdAuthenticationInfoFieldInfos(); + DesiredStateConfigurationFieldInfos = GetDesiredStateConfigurationFieldInfos(); + DesiredStateConfigurationPowerShellModuleFieldInfos = GetDesiredStateConfigurationPowerShellModuleFieldInfos(); + DesiredStateConfigurationPowerShellResourceFieldInfos = GetDesiredStateConfigurationPowerShellResourceFieldInfos(); + DesiredStateConfigurationDSCv3FieldInfos = GetDesiredStateConfigurationDSCv3FieldInfos(); + DesiredStateConfigurationDSCv3ResourceFieldInfos = GetDesiredStateConfigurationDSCv3ResourceFieldInfos(); resultErrors = ValidateAndProcessFields(rootNode, RootFieldInfos, VariantManifestPtr(&(m_manifest.get()))); @@ -1138,7 +1293,7 @@ namespace AppInstaller::Manifest WINGET_STASH_INSTALLER_PROPERTY(PackageFamilyName, clear); WINGET_STASH_INSTALLER_PROPERTY(ProductCode, clear); WINGET_STASH_INSTALLER_PROPERTY(AppsAndFeaturesEntries, clear); - // Clear dependencies as installer overrides root dependencies + // Clear dependencies as installer specific overrides root WINGET_STASH_INSTALLER_PROPERTY(Dependencies, Clear); // Clear nested installers as it should only be copied for zip installerType. installer.NestedInstallerType = InstallerTypeEnum::Unknown; @@ -1152,7 +1307,7 @@ namespace AppInstaller::Manifest { if (installer.NestedInstallerFiles.empty()) { - installer.NestedInstallerFiles = m_manifest.get().DefaultInstallerInfo.NestedInstallerFiles; + WINGET_UNSTASH_INSTALLER_PROPERTY(NestedInstallerFiles); } if (installer.NestedInstallerType == InstallerTypeEnum::Unknown) @@ -1164,25 +1319,25 @@ namespace AppInstaller::Manifest // Copy in system reference strings from the root if not set in the installer and appropriate if (installer.AppsAndFeaturesEntries.empty() && DoesInstallerTypeWriteAppsAndFeaturesEntry(installer.EffectiveInstallerType())) { - installer.AppsAndFeaturesEntries = m_manifest.get().DefaultInstallerInfo.AppsAndFeaturesEntries; + WINGET_UNSTASH_INSTALLER_PROPERTY(AppsAndFeaturesEntries); } if (installer.PackageFamilyName.empty() && (DoesInstallerTypeUsePackageFamilyName(installer.EffectiveInstallerType()) || DoAnyAppsAndFeaturesEntriesUsePackageFamilyName(installer.AppsAndFeaturesEntries))) { - installer.PackageFamilyName = m_manifest.get().DefaultInstallerInfo.PackageFamilyName; + WINGET_UNSTASH_INSTALLER_PROPERTY(PackageFamilyName); } if (installer.ProductCode.empty() && DoesInstallerTypeUseProductCode(installer.EffectiveInstallerType())) { - installer.ProductCode = m_manifest.get().DefaultInstallerInfo.ProductCode; + WINGET_UNSTASH_INSTALLER_PROPERTY(ProductCode); } // If there are no dependencies on installer use default ones if (!installer.Dependencies.HasAny()) { - installer.Dependencies = m_manifest.get().DefaultInstallerInfo.Dependencies; + WINGET_UNSTASH_INSTALLER_PROPERTY(Dependencies); } // Populate installer default switches if not exists diff --git a/src/AppInstallerCommonCore/Manifest/YamlWriter.cpp b/src/AppInstallerCommonCore/Manifest/YamlWriter.cpp index 4483fbdaba..5f0c4b8411 100644 --- a/src/AppInstallerCommonCore/Manifest/YamlWriter.cpp +++ b/src/AppInstallerCommonCore/Manifest/YamlWriter.cpp @@ -73,6 +73,14 @@ namespace AppInstaller::Manifest::YamlWriter constexpr std::string_view MicrosoftEntraIdAuthenticationInfo = "MicrosoftEntraIdAuthenticationInfo"sv; constexpr std::string_view MicrosoftEntraIdResource = "Resource"sv; constexpr std::string_view MicrosoftEntraIdScope = "Scope"sv; + constexpr std::string_view DesiredStateConfiguration = "DesiredStateConfiguration"sv; + constexpr std::string_view DesiredStateConfigurationResources = "Resources"sv; + constexpr std::string_view DesiredStateConfigurationPowerShell = "PowerShell"sv; + constexpr std::string_view DesiredStateConfigurationPowerShellRepositoryURL = "RepositoryURL"sv; + constexpr std::string_view DesiredStateConfigurationPowerShellModuleName = "ModuleName"sv; + constexpr std::string_view DesiredStateConfigurationPowerShellResourceName = "Name"sv; + constexpr std::string_view DesiredStateConfigurationDSCv3 = "DSCv3"sv; + constexpr std::string_view DesiredStateConfigurationDSCv3ResourceType = "Type"sv; // Installer switches constexpr std::string_view InstallerSwitches = "InstallerSwitches"sv; @@ -584,6 +592,89 @@ namespace AppInstaller::Manifest::YamlWriter out << YAML::EndSeq; } + out << YAML::EndMap; + } + + void ProcessDesiredStateConfiguration(YAML::Emitter& out, const std::vector& containers) + { + if (containers.empty()) + { + return; + } + + out << YAML::Key << DesiredStateConfiguration; + out << YAML::BeginMap; + + // Process PowerShell containers + bool firstPowerShellContainer = true; + for (const auto& container : containers) + { + if (container.Type == DesiredStateConfigurationContainerType::PowerShell) + { + if (firstPowerShellContainer) + { + out << YAML::Key << DesiredStateConfigurationPowerShell; + out << YAML::BeginSeq; + firstPowerShellContainer = false; + } + + out << YAML::BeginMap; + + WRITE_PROPERTY(out, DesiredStateConfigurationPowerShellRepositoryURL, container.RepositoryURL); + WRITE_PROPERTY(out, DesiredStateConfigurationPowerShellModuleName, container.ModuleName); + + out << YAML::Key << DesiredStateConfigurationResources; + out << YAML::BeginSeq; + + for (const auto& resource : container.Resources) + { + out << YAML::BeginMap; + WRITE_PROPERTY(out, DesiredStateConfigurationPowerShellResourceName, resource.Name); + out << YAML::EndMap; + } + + out << YAML::EndSeq; + out << YAML::EndMap; + } + } + + if (!firstPowerShellContainer) + { + out << YAML::EndSeq; + } + + // Process DSCv3 container + bool firstDSCv3Container = true; + for (const auto& container : containers) + { + if (container.Type == DesiredStateConfigurationContainerType::DSCv3) + { + if (firstDSCv3Container) + { + out << YAML::Key << DesiredStateConfigurationDSCv3; + out << YAML::BeginMap; + firstDSCv3Container = false; + } + else + { + THROW_HR_MSG(E_INVALIDARG, "Cannot have multiple DSCv3 containers."); + } + + out << YAML::Key << DesiredStateConfigurationResources; + out << YAML::BeginSeq; + + for (const auto& resource : container.Resources) + { + out << YAML::BeginMap; + WRITE_PROPERTY(out, DesiredStateConfigurationDSCv3ResourceType, resource.Name); + out << YAML::EndMap; + } + + out << YAML::EndSeq; + out << YAML::EndMap; + } + } + out << YAML::EndMap; } @@ -632,6 +723,7 @@ namespace AppInstaller::Manifest::YamlWriter ProcessUnsupportedArguments(out, installer.UnsupportedArguments); ProcessUnsupportedOSArchitecture(out, installer.UnsupportedOSArchitectures); ProcessAuthentication(out, installer.AuthInfo); + ProcessDesiredStateConfiguration(out, installer.DesiredStateConfiguration); } void ProcessInstaller(YAML::Emitter& out, const ManifestInstaller& installer) diff --git a/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h b/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h index df2cc5024d..4c6918ed22 100644 --- a/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h +++ b/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h @@ -54,6 +54,9 @@ namespace AppInstaller::Manifest // V1.12 manifest version constexpr std::string_view s_ManifestVersionV1_12 = "1.12.0"sv; + // V1.28 manifest version + constexpr std::string_view s_ManifestVersionV1_28 = "1.28.0"sv; + // Any new manifest version must also be added to src\WinGetUtilInterop\Manifest\ManifestVersion.cs. // The manifest extension for the MS Store @@ -382,6 +385,7 @@ namespace AppInstaller::Manifest // The type of resource container. enum class DesiredStateConfigurationContainerType { + Unknown, PowerShell, DSCv3, }; @@ -390,7 +394,7 @@ namespace AppInstaller::Manifest // Contains the union of properties relevant to all container types. struct DesiredStateConfigurationContainerInfo { - DesiredStateConfigurationContainerType Type; + DesiredStateConfigurationContainerType Type = DesiredStateConfigurationContainerType::Unknown; string_t RepositoryURL; string_t ModuleName; std::vector Resources; diff --git a/src/AppInstallerCommonCore/Public/winget/ManifestYamlPopulator.h b/src/AppInstallerCommonCore/Public/winget/ManifestYamlPopulator.h index db7775afe2..a89be5e0ee 100644 --- a/src/AppInstallerCommonCore/Public/winget/ManifestYamlPopulator.h +++ b/src/AppInstallerCommonCore/Public/winget/ManifestYamlPopulator.h @@ -8,7 +8,29 @@ namespace AppInstaller::Manifest { // Add here new manifest pointer types. - using VariantManifestPtr = std::variant*, AppInstaller::Authentication::AuthenticationInfo*, AppInstaller::Authentication::MicrosoftEntraIdAuthenticationInfo*>; + using VariantManifestPtr = std::variant< + Agreement*, + AppsAndFeaturesEntry*, + Dependency*, + DependencyList*, + Documentation*, + ExpectedReturnCode*, + Icon*, + InstallationMetadataInfo*, + InstalledFile*, + Manifest*, + ManifestInstaller*, + ManifestLocalization*, + MarketsInfo*, + NestedInstallerFile*, + std::map*, + AppInstaller::Authentication::AuthenticationInfo*, + AppInstaller::Authentication::MicrosoftEntraIdAuthenticationInfo*, + std::vector*, + DesiredStateConfigurationContainerInfo*, + DesiredStateConfigurationResourceInfo* + >; struct ManifestYamlPopulator { @@ -57,6 +79,11 @@ namespace AppInstaller::Manifest std::vector InstallationMetadataFilesFieldInfos; std::vector AuthenticationFieldInfos; std::vector MicrosoftEntraIdAuthenticationInfoFieldInfos; + std::vector DesiredStateConfigurationFieldInfos; + std::vector DesiredStateConfigurationPowerShellModuleFieldInfos; + std::vector DesiredStateConfigurationPowerShellResourceFieldInfos; + std::vector DesiredStateConfigurationDSCv3FieldInfos; + std::vector DesiredStateConfigurationDSCv3ResourceFieldInfos; // Cache of Installers node and Localization node YAML::Node const* m_p_installersNode = nullptr; @@ -79,6 +106,11 @@ namespace AppInstaller::Manifest std::vector GetInstallationMetadataFilesFieldProcessInfo(); std::vector GetAuthenticationFieldInfos(); std::vector GetMicrosoftEntraIdAuthenticationInfoFieldInfos(); + std::vector GetDesiredStateConfigurationFieldInfos(); + std::vector GetDesiredStateConfigurationPowerShellModuleFieldInfos(); + std::vector GetDesiredStateConfigurationPowerShellResourceFieldInfos(); + std::vector GetDesiredStateConfigurationDSCv3FieldInfos(); + std::vector GetDesiredStateConfigurationDSCv3ResourceFieldInfos(); // Shadow std::vector GetShadowRootFieldProcessInfo(); @@ -103,6 +135,9 @@ namespace AppInstaller::Manifest std::vector ProcessNestedInstallerFilesNode(const YAML::Node& nestedInstallerFilesNode, AppInstaller::Manifest::ManifestInstaller* installer); std::vector ProcessInstallationMetadataFilesNode(const YAML::Node& installedFilesNode, InstallationMetadataInfo* installationMetadata); std::vector ProcessShadowLocalizationNode(const YAML::Node& localizationNode, Manifest* manifest); + std::vector ProcessDSC_PowerShellModuleNode(const YAML::Node& node, std::vector* containers); + std::vector ProcessDSC_PowerShellResourcesNode(const YAML::Node& node, DesiredStateConfigurationContainerInfo* container); + std::vector ProcessDSCv3ResourcesNode(const YAML::Node& node, DesiredStateConfigurationContainerInfo* container); std::vector PopulateManifestInternal(); std::vector InsertShadow(const YAML::Node& shadowNode); diff --git a/src/WinGetUtilInterop/Manifest/ManifestVersion.cs b/src/WinGetUtilInterop/Manifest/ManifestVersion.cs index 66fa61140b..2c6eebbb1c 100644 --- a/src/WinGetUtilInterop/Manifest/ManifestVersion.cs +++ b/src/WinGetUtilInterop/Manifest/ManifestVersion.cs @@ -63,6 +63,11 @@ public static class ManifestVersion /// public const string ManifestVersionV1_12 = "1.12.0"; + /// + /// V1.28 manifest version. + /// + public const string ManifestVersionV1_28 = "1.28.0"; + #pragma warning restore SA1310 // Field names should not contain underscore } } From bd211dd36e3513e30eaf50d3ae0cf46a22c81233 Mon Sep 17 00:00:00 2001 From: John McPherson Date: Tue, 20 Jan 2026 16:32:52 -0800 Subject: [PATCH 5/8] Tests largely added, need to debug them --- .github/actions/spelling/allow.txt | 1 + .../latest/manifest.installer.latest.json | 4 +- .../latest/manifest.singleton.latest.json | 4 +- .../AppInstallerCLITests.vcxproj | 18 ++ .../TestData/ManifestV1_28-PowerShellDSC.yaml | 226 +++++++++++++++++ .../TestData/ManifestV1_28-Singleton.yaml | 209 +++++++++++++++ ...ManifestV1_28-MultiFile-DefaultLocale.yaml | 41 +++ .../ManifestV1_28-MultiFile-Installer.yaml | 240 ++++++++++++++++++ .../ManifestV1_28-MultiFile-Locale.yaml | 40 +++ .../ManifestV1_28-MultiFile-Version.yaml | 7 + src/AppInstallerCLITests/YamlManifest.cpp | 205 ++++++++++++++- .../Manifest/ManifestCommon.cpp | 4 +- .../Manifest/ManifestSchemaValidation.cpp | 12 +- .../Manifest/ManifestValidation.cpp | 14 + .../Manifest/ManifestYamlPopulator.cpp | 7 +- .../Manifest/YamlWriter.cpp | 2 +- .../Public/winget/ManifestCommon.h | 15 +- src/ManifestSchema/ManifestSchema.h | 6 + src/ManifestSchema/ManifestSchema.rc | 16 +- 19 files changed, 1036 insertions(+), 35 deletions(-) create mode 100644 src/AppInstallerCLITests/TestData/ManifestV1_28-PowerShellDSC.yaml create mode 100644 src/AppInstallerCLITests/TestData/ManifestV1_28-Singleton.yaml create mode 100644 src/AppInstallerCLITests/TestData/MultiFileManifestV1_28/ManifestV1_28-MultiFile-DefaultLocale.yaml create mode 100644 src/AppInstallerCLITests/TestData/MultiFileManifestV1_28/ManifestV1_28-MultiFile-Installer.yaml create mode 100644 src/AppInstallerCLITests/TestData/MultiFileManifestV1_28/ManifestV1_28-MultiFile-Locale.yaml create mode 100644 src/AppInstallerCLITests/TestData/MultiFileManifestV1_28/ManifestV1_28-MultiFile-Version.yaml diff --git a/.github/actions/spelling/allow.txt b/.github/actions/spelling/allow.txt index 01ea0ee6ff..63f59bbe5c 100644 --- a/.github/actions/spelling/allow.txt +++ b/.github/actions/spelling/allow.txt @@ -378,6 +378,7 @@ UNICODESTRING uninstalling Unmarshal unskipped +unstash untimes updatefile updatemanifest diff --git a/schemas/JSON/manifests/latest/manifest.installer.latest.json b/schemas/JSON/manifests/latest/manifest.installer.latest.json index 9048183dfb..598ec4a32c 100644 --- a/schemas/JSON/manifests/latest/manifest.installer.latest.json +++ b/schemas/JSON/manifests/latest/manifest.installer.latest.json @@ -652,7 +652,7 @@ "type": "object", "title": "PowerShell DSC Module Item", "properties": { - "RepositoryURL": { + "RepositoryUrl": { "$ref": "#/definitions/Url" }, "ModuleName": { @@ -679,7 +679,7 @@ } } }, - "required": [ "RepositoryURL", "ModuleName", "Resources" ] + "required": [ "RepositoryUrl", "ModuleName", "Resources" ] } }, "DSCv3": { diff --git a/schemas/JSON/manifests/latest/manifest.singleton.latest.json b/schemas/JSON/manifests/latest/manifest.singleton.latest.json index 9eb5ce0f93..dc7b49db6c 100644 --- a/schemas/JSON/manifests/latest/manifest.singleton.latest.json +++ b/schemas/JSON/manifests/latest/manifest.singleton.latest.json @@ -753,7 +753,7 @@ "type": "object", "title": "PowerShell DSC Module Item", "properties": { - "RepositoryURL": { + "RepositoryUrl": { "$ref": "#/definitions/Url" }, "ModuleName": { @@ -780,7 +780,7 @@ } } }, - "required": [ "RepositoryURL", "ModuleName", "Resources" ] + "required": [ "RepositoryUrl", "ModuleName", "Resources" ] } }, "DSCv3": { diff --git a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj index 11148bc2ef..e2d439a275 100644 --- a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj +++ b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj @@ -1055,6 +1055,24 @@ true + + true + + + true + + + true + + + true + + + true + + + true + diff --git a/src/AppInstallerCLITests/TestData/ManifestV1_28-PowerShellDSC.yaml b/src/AppInstallerCLITests/TestData/ManifestV1_28-PowerShellDSC.yaml new file mode 100644 index 0000000000..a9630d5bf2 --- /dev/null +++ b/src/AppInstallerCLITests/TestData/ManifestV1_28-PowerShellDSC.yaml @@ -0,0 +1,226 @@ +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.28.0.schema.json + +PackageIdentifier: microsoft.msixsdk +PackageVersion: 1.7.32 +PackageLocale: en-US +Publisher: Microsoft +PublisherUrl: https://www.microsoft.com +PublisherSupportUrl: https://www.microsoft.com/support +PrivacyUrl: https://www.microsoft.com/privacy +Author: Microsoft +PackageName: MSIX SDK +PackageUrl: https://www.microsoft.com/msixsdk/home +License: MIT License +LicenseUrl: https://www.microsoft.com/msixsdk/license +Copyright: Copyright Microsoft Corporation +CopyrightUrl: https://www.microsoft.com/msixsdk/copyright +ShortDescription: This is MSIX SDK +Description: The MSIX SDK project is an effort to enable developers +Moniker: msixsdk +Tags: + - "appxsdk" + - "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 +Icons: + - IconUrl: https://testIcon-en-US + IconFileType: ico + IconResolution: custom + IconTheme: default + IconSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8123 +Agreements: + - AgreementLabel: DefaultLabel + Agreement: DefaultText + AgreementUrl: https://DefaultAgreementUrl.net +InstallerLocale: en-US +Platform: + - Windows.Desktop + - Windows.Universal +MinimumOSVersion: 10.0.0.0 +InstallerType: exe +Scope: machine +InstallModes: + - interactive + - silent + - silentWithProgress +InstallerSwitches: + Custom: /custom + SilentWithProgress: /silentwithprogress + Silent: /silence + Interactive: /interactive + Log: /log= + InstallLocation: /dir= + Upgrade: /upgrade + Repair: /repair +InstallerSuccessCodes: + - 1 + - 0x80070005 +UpgradeBehavior: uninstallPrevious +RepairBehavior: modify +Commands: + - makemsix + - makeappx +Protocols: + - protocol1 + - protocol2 +FileExtensions: + - appx + - msix + - appxbundle + - msixbundle +Dependencies: + WindowsFeatures: + - IIS + WindowsLibraries: + - VC Runtime + PackageDependencies: + - PackageIdentifier: Microsoft.MsixSdkDep + MinimumVersion: 1.0.0 + ExternalDependencies: + - Outside dependencies +Capabilities: + - internetClient +RestrictedCapabilities: + - runFullTrust +PackageFamilyName: Microsoft.DesktopAppInstaller_8wekyb3d8bbwe +ProductCode: "{Foo}" +ReleaseDate: 2021-01-01 +InstallerAbortsTerminal: true +InstallLocationRequired: true +RequireExplicitUpgrade: true +DisplayInstallWarnings: true +ElevationRequirement: elevatesSelf +UnsupportedOSArchitectures: + - arm +AppsAndFeaturesEntries: + - DisplayName: DisplayName + DisplayVersion: DisplayVersion + Publisher: Publisher + ProductCode: ProductCode + UpgradeCode: UpgradeCode + InstallerType: exe +Markets: + AllowedMarkets: + - US +ExpectedReturnCodes: + - InstallerReturnCode: 10 + ReturnResponse: packageInUse + ReturnResponseUrl: https://DefaultReturnResponseUrl.com +UnsupportedArguments: + - log +NestedInstallerType: msi +NestedInstallerFiles: + - RelativeFilePath: RelativeFilePath + PortableCommandAlias: PortableCommandAlias +InstallationMetadata: + DefaultInstallLocation: "%ProgramFiles%\\TestApp" + Files: + - RelativeFilePath: "main.exe" + FileSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 + FileType: launch + InvocationParameter: "/arg" + DisplayName: "DisplayName" +DownloadCommandProhibited: true +ArchiveBinariesDependOnPath: true +DesiredStateConfiguration: + PowerShell: + - RepositoryUrl: https://www.powershellgallery.com/api/v2 + ModuleName: Microsoft.WinGet.DSC + Resources: + - Name: WinGetUserSettings + - Name: WinGetAdminSettings + - Name: WinGetSource + - Name: WinGetPackageManager + - Name: WinGetPackage + - RepositoryUrl: https://mcr.microsoft.com/ + ModuleName: Microsoft.WinGet.DSC + Resources: + - Name: WinGetUserSettings + - Name: WinGetAdminSettings + - Name: WinGetSource + - Name: WinGetPackageManager + - Name: WinGetPackage + DSCv3: + Resources: + - Type: Microsoft.WinGet/AdminSettings + - Type: Microsoft.WinGet/Package + - Type: Microsoft.WinGet/Source + - Type: Microsoft.WinGet/UserSettingsFile + +Installers: + - Architecture: x86 + InstallerLocale: en-GB + Platform: + - Windows.Desktop + MinimumOSVersion: 10.0.1.0 + InstallerType: msix + InstallerUrl: https://www.microsoft.com/msixsdk/msixsdkx86.msix + InstallerSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 + SignatureSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 + Scope: user + InstallModes: + - interactive + InstallerSwitches: + Custom: /c + SilentWithProgress: /sp + Silent: /s + Interactive: /i + Log: /l= + InstallLocation: /d= + Upgrade: /u + Repair: /r + UpgradeBehavior: install + Commands: + - makemsixPreview + - makeappxPreview + Protocols: + - protocol1preview + - protocol2preview + FileExtensions: + - appxbundle + - msixbundle + - appx + - msix + Dependencies: + WindowsFeatures: + - PreviewIIS + WindowsLibraries: + - Preview VC Runtime + PackageDependencies: + - PackageIdentifier: Microsoft.MsixSdkDepPreview + MinimumVersion: 1.0.0 + ExternalDependencies: + - Preview Outside dependencies + PackageFamilyName: Microsoft.DesktopAppInstallerPreview_8wekyb3d8bbwe + Capabilities: + - internetClientPreview + RestrictedCapabilities: + - runFullTrustPreview + ReleaseDate: 2021-02-02 + InstallerAbortsTerminal: false + InstallLocationRequired: false + RequireExplicitUpgrade: false + DisplayInstallWarnings: false + ElevationRequirement: elevationRequired + UnsupportedArguments: + - location + UnsupportedOSArchitectures: + - arm64 + Markets: + ExcludedMarkets: + - "US" + ExpectedReturnCodes: + - InstallerReturnCode: 2 + ReturnResponse: contactSupport + - InstallerReturnCode: 3 + ReturnResponse: custom + ReturnResponseUrl: https://defaultReturnResponseUrl.com + DownloadCommandProhibited: false + ArchiveBinariesDependOnPath: false +ManifestType: singleton +ManifestVersion: 1.28.0 diff --git a/src/AppInstallerCLITests/TestData/ManifestV1_28-Singleton.yaml b/src/AppInstallerCLITests/TestData/ManifestV1_28-Singleton.yaml new file mode 100644 index 0000000000..1de1249422 --- /dev/null +++ b/src/AppInstallerCLITests/TestData/ManifestV1_28-Singleton.yaml @@ -0,0 +1,209 @@ +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.28.0.schema.json + +PackageIdentifier: microsoft.msixsdk +PackageVersion: 1.7.32 +PackageLocale: en-US +Publisher: Microsoft +PublisherUrl: https://www.microsoft.com +PublisherSupportUrl: https://www.microsoft.com/support +PrivacyUrl: https://www.microsoft.com/privacy +Author: Microsoft +PackageName: MSIX SDK +PackageUrl: https://www.microsoft.com/msixsdk/home +License: MIT License +LicenseUrl: https://www.microsoft.com/msixsdk/license +Copyright: Copyright Microsoft Corporation +CopyrightUrl: https://www.microsoft.com/msixsdk/copyright +ShortDescription: This is MSIX SDK +Description: The MSIX SDK project is an effort to enable developers +Moniker: msixsdk +Tags: + - "appxsdk" + - "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 +Icons: + - IconUrl: https://testIcon-en-US + IconFileType: ico + IconResolution: custom + IconTheme: default + IconSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8123 +Agreements: + - AgreementLabel: DefaultLabel + Agreement: DefaultText + AgreementUrl: https://DefaultAgreementUrl.net +InstallerLocale: en-US +Platform: + - Windows.Desktop + - Windows.Universal +MinimumOSVersion: 10.0.0.0 +InstallerType: exe +Scope: machine +InstallModes: + - interactive + - silent + - silentWithProgress +InstallerSwitches: + Custom: /custom + SilentWithProgress: /silentwithprogress + Silent: /silence + Interactive: /interactive + Log: /log= + InstallLocation: /dir= + Upgrade: /upgrade + Repair: /repair +InstallerSuccessCodes: + - 1 + - 0x80070005 +UpgradeBehavior: uninstallPrevious +RepairBehavior: modify +Commands: + - makemsix + - makeappx +Protocols: + - protocol1 + - protocol2 +FileExtensions: + - appx + - msix + - appxbundle + - msixbundle +Dependencies: + WindowsFeatures: + - IIS + WindowsLibraries: + - VC Runtime + PackageDependencies: + - PackageIdentifier: Microsoft.MsixSdkDep + MinimumVersion: 1.0.0 + ExternalDependencies: + - Outside dependencies +Capabilities: + - internetClient +RestrictedCapabilities: + - runFullTrust +PackageFamilyName: Microsoft.DesktopAppInstaller_8wekyb3d8bbwe +ProductCode: "{Foo}" +ReleaseDate: 2021-01-01 +InstallerAbortsTerminal: true +InstallLocationRequired: true +RequireExplicitUpgrade: true +DisplayInstallWarnings: true +ElevationRequirement: elevatesSelf +UnsupportedOSArchitectures: + - arm +AppsAndFeaturesEntries: + - DisplayName: DisplayName + DisplayVersion: DisplayVersion + Publisher: Publisher + ProductCode: ProductCode + UpgradeCode: UpgradeCode + InstallerType: exe +Markets: + AllowedMarkets: + - US +ExpectedReturnCodes: + - InstallerReturnCode: 10 + ReturnResponse: packageInUse + ReturnResponseUrl: https://DefaultReturnResponseUrl.com +UnsupportedArguments: + - log +NestedInstallerType: msi +NestedInstallerFiles: + - RelativeFilePath: RelativeFilePath + PortableCommandAlias: PortableCommandAlias +InstallationMetadata: + DefaultInstallLocation: "%ProgramFiles%\\TestApp" + Files: + - RelativeFilePath: "main.exe" + FileSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 + FileType: launch + InvocationParameter: "/arg" + DisplayName: "DisplayName" +DownloadCommandProhibited: true +ArchiveBinariesDependOnPath: true +DesiredStateConfiguration: + DSCv3: + Resources: + - Type: Microsoft.WinGet/AdminSettings + - Type: Microsoft.WinGet/Package + - Type: Microsoft.WinGet/Source + - Type: Microsoft.WinGet/UserSettingsFile + +Installers: + - Architecture: x86 + InstallerLocale: en-GB + Platform: + - Windows.Desktop + MinimumOSVersion: 10.0.1.0 + InstallerType: msix + InstallerUrl: https://www.microsoft.com/msixsdk/msixsdkx86.msix + InstallerSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 + SignatureSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 + Scope: user + InstallModes: + - interactive + InstallerSwitches: + Custom: /c + SilentWithProgress: /sp + Silent: /s + Interactive: /i + Log: /l= + InstallLocation: /d= + Upgrade: /u + Repair: /r + UpgradeBehavior: install + Commands: + - makemsixPreview + - makeappxPreview + Protocols: + - protocol1preview + - protocol2preview + FileExtensions: + - appxbundle + - msixbundle + - appx + - msix + Dependencies: + WindowsFeatures: + - PreviewIIS + WindowsLibraries: + - Preview VC Runtime + PackageDependencies: + - PackageIdentifier: Microsoft.MsixSdkDepPreview + MinimumVersion: 1.0.0 + ExternalDependencies: + - Preview Outside dependencies + PackageFamilyName: Microsoft.DesktopAppInstallerPreview_8wekyb3d8bbwe + Capabilities: + - internetClientPreview + RestrictedCapabilities: + - runFullTrustPreview + ReleaseDate: 2021-02-02 + InstallerAbortsTerminal: false + InstallLocationRequired: false + RequireExplicitUpgrade: false + DisplayInstallWarnings: false + ElevationRequirement: elevationRequired + UnsupportedArguments: + - location + UnsupportedOSArchitectures: + - arm64 + Markets: + ExcludedMarkets: + - "US" + ExpectedReturnCodes: + - InstallerReturnCode: 2 + ReturnResponse: contactSupport + - InstallerReturnCode: 3 + ReturnResponse: custom + ReturnResponseUrl: https://defaultReturnResponseUrl.com + DownloadCommandProhibited: false + ArchiveBinariesDependOnPath: false +ManifestType: singleton +ManifestVersion: 1.28.0 diff --git a/src/AppInstallerCLITests/TestData/MultiFileManifestV1_28/ManifestV1_28-MultiFile-DefaultLocale.yaml b/src/AppInstallerCLITests/TestData/MultiFileManifestV1_28/ManifestV1_28-MultiFile-DefaultLocale.yaml new file mode 100644 index 0000000000..2741bf0d26 --- /dev/null +++ b/src/AppInstallerCLITests/TestData/MultiFileManifestV1_28/ManifestV1_28-MultiFile-DefaultLocale.yaml @@ -0,0 +1,41 @@ +# yaml-language-server: $schema=https://aka.ms/winget-manifest.defaultLocale.1.28.0.schema.json + +PackageIdentifier: microsoft.msixsdk +PackageVersion: 1.7.32 +PackageLocale: en-US +Publisher: Microsoft +PublisherUrl: https://www.microsoft.com +PublisherSupportUrl: https://www.microsoft.com/support +PrivacyUrl: https://www.microsoft.com/privacy +Author: Microsoft +PackageName: MSIX SDK +PackageUrl: https://www.microsoft.com/msixsdk/home +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 +Tags: + - "appxsdk" + - "msixsdk" +ReleaseNotes: Default release notes +ReleaseNotesUrl: https://DefaultReleaseNotes.net +Documentations: + - DocumentLabel: Default document label + DocumentUrl: https://DefaultDocumentUrl.com +Icons: + - IconUrl: https://testIcon-en-US + IconFileType: ico + IconResolution: custom + IconTheme: default + IconSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8123 +Agreements: + - AgreementLabel: DefaultLabel + Agreement: DefaultText + AgreementUrl: https://DefaultAgreementUrl.net +ManifestType: defaultLocale +ManifestVersion: 1.28.0 diff --git a/src/AppInstallerCLITests/TestData/MultiFileManifestV1_28/ManifestV1_28-MultiFile-Installer.yaml b/src/AppInstallerCLITests/TestData/MultiFileManifestV1_28/ManifestV1_28-MultiFile-Installer.yaml new file mode 100644 index 0000000000..28b6363f2b --- /dev/null +++ b/src/AppInstallerCLITests/TestData/MultiFileManifestV1_28/ManifestV1_28-MultiFile-Installer.yaml @@ -0,0 +1,240 @@ +# yaml-language-server: $schema=https://aka.ms/winget-manifest.installer.1.28.0.schema.json + +PackageIdentifier: microsoft.msixsdk +PackageVersion: 1.7.32 +InstallerLocale: en-US +Platform: + - Windows.Desktop + - Windows.Universal +MinimumOSVersion: 10.0.0.0 +InstallerType: exe +Scope: machine +InstallModes: + - interactive + - silent + - silentWithProgress +InstallerSwitches: + Custom: /custom + SilentWithProgress: /silentwithprogress + Silent: /silence + Interactive: /interactive + Log: /log= + InstallLocation: /dir= + Upgrade: /upgrade + Repair: /repair +InstallerSuccessCodes: + - 1 + - 0x80070005 +UpgradeBehavior: uninstallPrevious +RepairBehavior: modify +Commands: + - makemsix + - makeappx +Protocols: + - protocol1 + - protocol2 +FileExtensions: + - appx + - msix + - appxbundle + - msixbundle +Dependencies: + WindowsFeatures: + - IIS + WindowsLibraries: + - VC Runtime + PackageDependencies: + - PackageIdentifier: Microsoft.MsixSdkDep + MinimumVersion: 1.0.0 + ExternalDependencies: + - Outside dependencies +Capabilities: + - internetClient +RestrictedCapabilities: + - runFullTrust +PackageFamilyName: Microsoft.DesktopAppInstaller_8wekyb3d8bbwe +ProductCode: "{Foo}" +ReleaseDate: 2021-01-01 +InstallerAbortsTerminal: true +InstallLocationRequired: true +RequireExplicitUpgrade: true +DisplayInstallWarnings: true +ElevationRequirement: elevatesSelf +UnsupportedOSArchitectures: + - arm +AppsAndFeaturesEntries: + - DisplayName: DisplayName + DisplayVersion: DisplayVersion + Publisher: Publisher + ProductCode: ProductCode + UpgradeCode: UpgradeCode + InstallerType: exe +Markets: + AllowedMarkets: + - "US" +ExpectedReturnCodes: + - InstallerReturnCode: 10 + ReturnResponse: packageInUse + ReturnResponseUrl: https://DefaultReturnResponseUrl.com +UnsupportedArguments: + - log +NestedInstallerType: msi +NestedInstallerFiles: + - RelativeFilePath: RelativeFilePath + PortableCommandAlias: PortableCommandAlias +InstallationMetadata: + DefaultInstallLocation: "%ProgramFiles%\\TestApp" + Files: + - RelativeFilePath: "main.exe" + FileSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 + FileType: launch + InvocationParameter: "/arg" + DisplayName: "DisplayName" +DownloadCommandProhibited: true +ArchiveBinariesDependOnPath: true +DesiredStateConfiguration: + DSCv3: + Resources: + - Type: Microsoft.WinGet/AdminSettings + - Type: Microsoft.WinGet/Package + - Type: Microsoft.WinGet/Source + - Type: Microsoft.WinGet/UserSettingsFile + +Installers: + - Architecture: x86 + InstallerLocale: en-GB + Platform: + - Windows.Desktop + MinimumOSVersion: 10.0.1.0 + InstallerType: msix + InstallerUrl: https://www.microsoft.com/msixsdk/msixsdkx86.msix + InstallerSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 + SignatureSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 + Scope: user + InstallModes: + - interactive + InstallerSwitches: + Custom: /c + SilentWithProgress: /sp + Silent: /s + Interactive: /i + Log: /l= + InstallLocation: /d= + Upgrade: /u + Repair: /r + UpgradeBehavior: install + Commands: + - makemsixPreview + - makeappxPreview + Protocols: + - protocol1preview + - protocol2preview + FileExtensions: + - appxbundle + - msixbundle + - appx + - msix + Dependencies: + WindowsFeatures: + - PreviewIIS + WindowsLibraries: + - Preview VC Runtime + PackageDependencies: + - PackageIdentifier: Microsoft.MsixSdkDepPreview + MinimumVersion: 1.0.0 + ExternalDependencies: + - Preview Outside dependencies + PackageFamilyName: Microsoft.DesktopAppInstallerPreview_8wekyb3d8bbwe + Capabilities: + - internetClientPreview + RestrictedCapabilities: + - runFullTrustPreview + ReleaseDate: 2021-02-02 + InstallerAbortsTerminal: false + InstallLocationRequired: false + RequireExplicitUpgrade: false + DisplayInstallWarnings: false + ElevationRequirement: elevationRequired + UnsupportedOSArchitectures: + - arm64 + Markets: + ExcludedMarkets: + - "US" + ExpectedReturnCodes: + - InstallerReturnCode: 2 + ReturnResponse: contactSupport + - InstallerReturnCode: 3 + ReturnResponse: custom + ReturnResponseUrl: https://defaultReturnResponseUrl.com + UnsupportedArguments: + - location + DownloadCommandProhibited: false + ArchiveBinariesDependOnPath: false + DesiredStateConfiguration: + - Architecture: x64 + InstallerType: exe + InstallerUrl: https://www.microsoft.com/msixsdk/msixsdkx64.exe + InstallerSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 + ProductCode: "{Bar}" + InstallerSwitches: + Repair: /r + UpgradeBehavior: deny + RepairBehavior: uninstaller + DesiredStateConfiguration: + DSCv3: + Resources: + - Type: None/None + - Architecture: x86 + 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 + - Architecture: x64 + InstallerType: zip + InstallerUrl: https://www.microsoft.com/msixsdk/msixsdkx64.exe + InstallerSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 + NestedInstallerType: portable + NestedInstallerFiles: + - RelativeFilePath: relativeFilePath1 + PortableCommandAlias: portableAlias1 + - RelativeFilePath: relativeFilePath2 + PortableCommandAlias: portableAlias2 + InstallationMetadata: + DefaultInstallLocation: "%ProgramFiles%\\TestApp2" + Files: + - RelativeFilePath: "main2.exe" + FileSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 + FileType: other + InvocationParameter: "/arg2" + DisplayName: "DisplayName2" + ArchiveBinariesDependOnPath: true + - Architecture: x64 + InstallerType: burn + InstallerUrl: https://www.microsoft.com/msixsdk/msixsdkx64.exe + InstallerSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 + ProductCode: "{Bar}" + UpgradeBehavior: deny + RepairBehavior: modify + - Architecture: neutral + InstallerType: zip + InstallerUrl: https://www.microsoft.com/msixsdk/msixsdkx64.exe + InstallerSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 + NestedInstallerType: font + NestedInstallerFiles: + - RelativeFilePath: relativeFilePath1.otf + - RelativeFilePath: relativeFilePath2.ttf + - RelativeFilePath: relativeFilePath3.fnt + - RelativeFilePath: relativeFilePath4.ttc + - RelativeFilePath: relativeFilePath5.otc + - Architecture: neutral + InstallerType: font + InstallerUrl: https://www.microsoft.com/msixsdk/msixsdkx64.exe + InstallerSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 +ManifestType: installer +ManifestVersion: 1.28.0 diff --git a/src/AppInstallerCLITests/TestData/MultiFileManifestV1_28/ManifestV1_28-MultiFile-Locale.yaml b/src/AppInstallerCLITests/TestData/MultiFileManifestV1_28/ManifestV1_28-MultiFile-Locale.yaml new file mode 100644 index 0000000000..1306ccd6b4 --- /dev/null +++ b/src/AppInstallerCLITests/TestData/MultiFileManifestV1_28/ManifestV1_28-MultiFile-Locale.yaml @@ -0,0 +1,40 @@ +# yaml-language-server: $schema=https://aka.ms/winget-manifest.locale.1.28.0.schema.json + +PackageIdentifier: microsoft.msixsdk +PackageVersion: 1.7.32 +PackageLocale: en-GB +Publisher: Microsoft UK +PublisherUrl: https://www.microsoft.com/UK +PublisherSupportUrl: https://www.microsoft.com/support/UK +PrivacyUrl: https://www.microsoft.com/privacy/UK +Author: Microsoft UK +PackageName: MSIX SDK UK +PackageUrl: https://www.microsoft.com/msixsdk/home/UK +License: MIT License UK +LicenseUrl: https://www.microsoft.com/msixsdk/license/UK +Copyright: Copyright Microsoft Corporation UK +CopyrightUrl: https://www.microsoft.com/msixsdk/copyright/UK +ShortDescription: This is MSIX SDK UK +Description: The MSIX SDK project is an effort to enable developers UK +Tags: + - "appxsdkUK" + - "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 +Icons: + - IconUrl: https://localeTestIcon-en-GB + IconFileType: png + IconResolution: 32x32 + IconTheme: light + IconSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8321 +ManifestType: locale +ManifestVersion: 1.28.0 diff --git a/src/AppInstallerCLITests/TestData/MultiFileManifestV1_28/ManifestV1_28-MultiFile-Version.yaml b/src/AppInstallerCLITests/TestData/MultiFileManifestV1_28/ManifestV1_28-MultiFile-Version.yaml new file mode 100644 index 0000000000..d7b510407b --- /dev/null +++ b/src/AppInstallerCLITests/TestData/MultiFileManifestV1_28/ManifestV1_28-MultiFile-Version.yaml @@ -0,0 +1,7 @@ +# yaml-language-server: $schema=https://aka.ms/winget-manifest.version.1.28.0.schema.json + +PackageIdentifier: microsoft.msixsdk +PackageVersion: 1.7.32 +DefaultLocale: en-US +ManifestType: version +ManifestVersion: 1.28.0 diff --git a/src/AppInstallerCLITests/YamlManifest.cpp b/src/AppInstallerCLITests/YamlManifest.cpp index fac1eee06e..4218fe3fad 100644 --- a/src/AppInstallerCLITests/YamlManifest.cpp +++ b/src/AppInstallerCLITests/YamlManifest.cpp @@ -3,6 +3,7 @@ #include "pch.h" #include "TestCommon.h" #include +#include #include #include #include @@ -122,6 +123,29 @@ namespace } } + void RequireContainerInfoPresent(const std::vector& containers, const DesiredStateConfigurationContainerInfo& info) + { + INFO("Looking for container info: " << AppInstaller::ToIntegral(info.Type) << " - " << info.RepositoryURL << " - " << info.ModuleName); + + for (const auto& container : containers) + { + if (container.Type == info.Type && container.RepositoryURL == info.RepositoryURL && container.ModuleName == info.ModuleName) + { + REQUIRE(container.Resources.size() == info.Resources.size()); + for (const auto& resource : info.Resources) + { + INFO("Looking for resource: " << resource.Name); + bool foundResource = std::any_of(container.Resources.begin(), container.Resources.end(), [&](const auto& a) { return a.Name == resource.Name; }); + REQUIRE(foundResource); + } + + return; + } + } + + FAIL("Did not find a matching container."); + } + void VerifyV1ManifestContent(const Manifest& manifest, bool isSingleton, ManifestVer manifestVer = { s_ManifestVersionV1 }, bool isExported = false) { REQUIRE(manifest.Id == "microsoft.msixsdk"); @@ -181,7 +205,7 @@ namespace REQUIRE(manifest.DefaultInstallerInfo.Scope == ScopeEnum::Machine); REQUIRE(manifest.DefaultInstallerInfo.InstallModes == std::vector{ InstallModeEnum::Interactive, InstallModeEnum::Silent, InstallModeEnum::SilentWithProgress }); - auto defaultSwitches = manifest.DefaultInstallerInfo.Switches; + const auto& defaultSwitches = manifest.DefaultInstallerInfo.Switches; REQUIRE(defaultSwitches.at(InstallerSwitchType::Custom) == "/custom"); REQUIRE(defaultSwitches.at(InstallerSwitchType::SilentWithProgress) == "/silentwithprogress"); REQUIRE(defaultSwitches.at(InstallerSwitchType::Silent) == "/silence"); @@ -196,7 +220,7 @@ namespace REQUIRE(manifest.DefaultInstallerInfo.Protocols == MultiValue{ "protocol1", "protocol2" }); REQUIRE(manifest.DefaultInstallerInfo.FileExtensions == MultiValue{ "appx", "msix", "appxbundle", "msixbundle" }); - auto dependencies = manifest.DefaultInstallerInfo.Dependencies; + const auto& dependencies = manifest.DefaultInstallerInfo.Dependencies; REQUIRE(dependencies.HasExactDependency(DependencyType::WindowsFeature, "IIS")); REQUIRE(dependencies.HasExactDependency(DependencyType::WindowsLibrary, "VC Runtime")); REQUIRE(dependencies.HasExactDependency(DependencyType::Package, "Microsoft.MsixSdkDep", "1.0.0")); @@ -296,7 +320,7 @@ namespace } } - ManifestInstaller installer1 = manifest.Installers.at(0); + const ManifestInstaller& installer1 = manifest.Installers.at(0); REQUIRE(installer1.Arch == Architecture::X86); REQUIRE(installer1.Locale == "en-GB"); REQUIRE(installer1.Platform == std::vector{ PlatformEnum::Desktop }); @@ -308,7 +332,7 @@ namespace REQUIRE(installer1.Scope == ScopeEnum::User); REQUIRE(installer1.InstallModes == std::vector{ InstallModeEnum::Interactive }); - auto installer1Switches = installer1.Switches; + const auto& installer1Switches = installer1.Switches; REQUIRE(installer1Switches.at(InstallerSwitchType::Custom) == "/c"); REQUIRE(installer1Switches.at(InstallerSwitchType::SilentWithProgress) == "/sp"); REQUIRE(installer1Switches.at(InstallerSwitchType::Silent) == "/s"); @@ -322,7 +346,7 @@ namespace REQUIRE(installer1.Protocols == MultiValue{ "protocol1preview", "protocol2preview" }); REQUIRE(installer1.FileExtensions == MultiValue{ "appxbundle", "msixbundle", "appx", "msix" }); - auto installer1Dependencies = installer1.Dependencies; + const auto& installer1Dependencies = installer1.Dependencies; REQUIRE(installer1Dependencies.HasExactDependency(DependencyType::WindowsFeature, "PreviewIIS")); REQUIRE(installer1Dependencies.HasExactDependency(DependencyType::WindowsLibrary, "Preview VC Runtime")); REQUIRE(installer1Dependencies.HasExactDependency(DependencyType::Package, "Microsoft.MsixSdkDepPreview", "1.0.0")); @@ -389,11 +413,16 @@ namespace REQUIRE_FALSE(installer1.ArchiveBinariesDependOnPath); } + if (manifestVer >= ManifestVer{ s_ManifestVersionV1_28 }) + { + RequireContainerInfoPresent(manifest.DefaultInstallerInfo.DesiredStateConfiguration, { { { "Microsoft.WinGet/AdminSettings" }, { "Microsoft.WinGet/Package" }, { "Microsoft.WinGet/Source" }, { "Microsoft.WinGet/UserSettingsFile" } } }); + } + if (!isSingleton) { if (!isExported) { - ManifestInstaller installer2 = manifest.Installers.at(1); + const ManifestInstaller& installer2 = manifest.Installers.at(1); REQUIRE(installer2.BaseInstallerType == InstallerTypeEnum::Exe); REQUIRE(installer2.Arch == Architecture::X64); REQUIRE(installer2.Url == "https://www.microsoft.com/msixsdk/msixsdkx64.exe"); @@ -424,7 +453,7 @@ namespace if (manifestVer >= ManifestVer{ s_ManifestVersionV1_2 }) { - ManifestInstaller installer3 = manifest.Installers.at(2); + const ManifestInstaller& installer3 = manifest.Installers.at(2); REQUIRE(installer3.BaseInstallerType == InstallerTypeEnum::Portable); REQUIRE(installer3.Arch == Architecture::X86); REQUIRE(installer3.Url == "https://www.microsoft.com/msixsdk/msixsdkx86.exe"); @@ -440,7 +469,7 @@ namespace if (manifestVer >= ManifestVer{ s_ManifestVersionV1_4 }) { - ManifestInstaller installer4 = manifest.Installers.at(3); + const ManifestInstaller& installer4 = manifest.Installers.at(3); REQUIRE(installer4.BaseInstallerType == InstallerTypeEnum::Zip); REQUIRE(installer4.Arch == Architecture::X64); REQUIRE(installer4.Url == "https://www.microsoft.com/msixsdk/msixsdkx64.exe"); @@ -472,7 +501,7 @@ namespace REQUIRE(installer2.RepairBehavior == RepairBehaviorEnum::Uninstaller); REQUIRE(installer2.Switches.at(InstallerSwitchType::Repair) == "/r"); - ManifestInstaller installer5 = manifest.Installers.at(4); + const ManifestInstaller& installer5 = manifest.Installers.at(4); REQUIRE(installer5.BaseInstallerType == InstallerTypeEnum::Burn); REQUIRE(installer5.Arch == Architecture::X64); REQUIRE(installer5.Url == "https://www.microsoft.com/msixsdk/msixsdkx64.exe"); @@ -484,13 +513,13 @@ namespace if (manifestVer >= ManifestVer{ s_ManifestVersionV1_9 }) { - ManifestInstaller installer4 = manifest.Installers.at(3); + const ManifestInstaller& installer4 = manifest.Installers.at(3); REQUIRE(installer4.ArchiveBinariesDependOnPath); } if (manifestVer >= ManifestVer{ s_ManifestVersionV1_12 }) { - ManifestInstaller installer6 = manifest.Installers.at(5); + const ManifestInstaller& installer6 = manifest.Installers.at(5); REQUIRE(installer6.BaseInstallerType == InstallerTypeEnum::Zip); REQUIRE(installer6.Arch == Architecture::Neutral); REQUIRE(installer6.Url == "https://www.microsoft.com/msixsdk/msixsdkx64.exe"); @@ -503,17 +532,23 @@ namespace REQUIRE(installer6.NestedInstallerFiles.at(3).RelativeFilePath == "relativeFilePath4.ttc"); REQUIRE(installer6.NestedInstallerFiles.at(4).RelativeFilePath == "relativeFilePath5.otc"); - ManifestInstaller installer7 = manifest.Installers.at(6); + const ManifestInstaller& installer7 = manifest.Installers.at(6); REQUIRE(installer7.BaseInstallerType == InstallerTypeEnum::Font); REQUIRE(installer7.Arch == Architecture::Neutral); REQUIRE(installer7.Url == "https://www.microsoft.com/msixsdk/msixsdkx64.exe"); REQUIRE(installer7.Sha256 == SHA256::ConvertToBytes("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82")); } + + if (manifestVer >= ManifestVer{ s_ManifestVersionV1_28 }) + { + REQUIRE(manifest.Installers[0].DesiredStateConfiguration.size() == 0); + RequireContainerInfoPresent(manifest.Installers[1].DesiredStateConfiguration, { {{"None/None"}} }); + } } // Localization REQUIRE(manifest.Localizations.size() == 1); - ManifestLocalization localization1 = manifest.Localizations.at(0); + const ManifestLocalization& localization1 = manifest.Localizations.at(0); REQUIRE(localization1.Locale == "en-GB"); REQUIRE(localization1.Get() == "Microsoft UK"); REQUIRE(localization1.Get() == "https://www.microsoft.com/UK"); @@ -1194,6 +1229,31 @@ TEST_CASE("ValidateV1_12GoodManifestAndVerifyContents", "[ManifestValidation]") VerifyV1ManifestContent(mergedManifest, false, ManifestVer{ s_ManifestVersionV1_12 }); } +TEST_CASE("ValidateV1_28GoodManifestAndVerifyContents", "[ManifestValidation]") +{ + ManifestValidateOption validateOption; + validateOption.FullValidation = true; + TempDirectory singletonDirectory{ "SingletonManifest" }; + CopyTestDataFilesToFolder({ "ManifestV1_28-Singleton.yaml" }, singletonDirectory); + Manifest singletonManifest = YamlParser::CreateFromPath(singletonDirectory, validateOption); + VerifyV1ManifestContent(singletonManifest, true, ManifestVer{ s_ManifestVersionV1_28 }); + + TempDirectory multiFileDirectory{ "MultiFileManifest" }; + CopyTestDataFilesToFolder({ + "ManifestV1_28-MultiFile-Version.yaml", + "ManifestV1_28-MultiFile-Installer.yaml", + "ManifestV1_28-MultiFile-DefaultLocale.yaml", + "ManifestV1_28-MultiFile-Locale.yaml" }, multiFileDirectory); + + TempFile mergedManifestFile{ "merged.yaml" }; + Manifest multiFileManifest = YamlParser::CreateFromPath(multiFileDirectory, validateOption, mergedManifestFile); + VerifyV1ManifestContent(multiFileManifest, false, ManifestVer{ s_ManifestVersionV1_28 }); + + // Read from merged manifest should have the same content as multi file manifest + Manifest mergedManifest = YamlParser::CreateFromPath(mergedManifestFile); + VerifyV1ManifestContent(mergedManifest, false, ManifestVer{ s_ManifestVersionV1_28 }); +} + TEST_CASE("WriteV1SingletonManifestAndVerifyContents", "[ManifestCreation]") { TempDirectory singletonDirectory{ "SingletonManifest" }; @@ -1482,6 +1542,12 @@ TEST_CASE("ReadWriteValidateV1_10ManifestWithInstallerAuthentication", "[Manifes CopyTestDataFilesToFolder({ "ManifestV1_10-InstallerAuthentication.yaml" }, testDirectory); Manifest testManifest = YamlParser::CreateFromPath(testDirectory); + // Validate schema + ManifestValidateOption validateOption; + validateOption.SchemaValidationOnly = true; + validateOption.ThrowOnWarning = true; + YamlParser::CreateFromPath(testDirectory, validateOption); + // Verify content REQUIRE(testManifest.ManifestVersion == AppInstaller::Manifest::ManifestVer{ s_ManifestVersionV1_10 }); REQUIRE(testManifest.DefaultInstallerInfo.AuthInfo.Type == AppInstaller::Authentication::AuthenticationType::MicrosoftEntraId); @@ -1508,7 +1574,7 @@ TEST_CASE("ReadWriteValidateV1_10ManifestWithInstallerAuthentication", "[Manifes // Read back and validate content REQUIRE(std::filesystem::exists(exportedManifestPath)); Manifest exportedManifest = YamlParser::CreateFromPath(exportedDirectory); - REQUIRE(testManifest.ManifestVersion == AppInstaller::Manifest::ManifestVer{ s_ManifestVersionV1_10 }); + REQUIRE(exportedManifest.ManifestVersion == AppInstaller::Manifest::ManifestVer{ s_ManifestVersionV1_10 }); REQUIRE(exportedManifest.Installers.size() == 1); REQUIRE(exportedManifest.Installers[0].AuthInfo.Type == AppInstaller::Authentication::AuthenticationType::MicrosoftEntraIdForAzureBlobStorage); REQUIRE(exportedManifest.Installers[0].AuthInfo.MicrosoftEntraIdInfo); @@ -1516,6 +1582,117 @@ TEST_CASE("ReadWriteValidateV1_10ManifestWithInstallerAuthentication", "[Manifes REQUIRE(exportedManifest.Installers[0].AuthInfo.MicrosoftEntraIdInfo->Scope.empty()); } +TEST_CASE("WriteV1_12SingletonManifestAndVerifyContents", "[ManifestCreation]") +{ + TempDirectory singletonDirectory{ "SingletonManifest" }; + CopyTestDataFilesToFolder({ "ManifestV1_12-Singleton.yaml" }, singletonDirectory); + Manifest singletonManifest = YamlParser::CreateFromPath(singletonDirectory); + + TempDirectory exportedSingletonDirectory{ "exportedSingleton" }; + std::filesystem::path generatedSingletonManifestPath = exportedSingletonDirectory.GetPath() / "testSingletonManifest.yaml"; + YamlWriter::OutputYamlFile(singletonManifest, singletonManifest.Installers[0], generatedSingletonManifestPath); + + REQUIRE(std::filesystem::exists(generatedSingletonManifestPath)); + Manifest generatedSingletonManifest = YamlParser::CreateFromPath(exportedSingletonDirectory); + VerifyV1ManifestContent(generatedSingletonManifest, true, ManifestVer{ s_ManifestVersionV1_12 }, true); + + TempDirectory multiFileDirectory{ "MultiFileManifest" }; + CopyTestDataFilesToFolder({ + "ManifestV1_12-MultiFile-Version.yaml", + "ManifestV1_12-MultiFile-Installer.yaml", + "ManifestV1_12-MultiFile-DefaultLocale.yaml", + "ManifestV1_12-MultiFile-Locale.yaml" }, multiFileDirectory); + + Manifest multiFileManifest = YamlParser::CreateFromPath(multiFileDirectory); + TempDirectory exportedMultiFileDirectory{ "exportedMultiFile" }; + std::filesystem::path generatedMultiFileManifestPath = exportedMultiFileDirectory.GetPath() / "testMultiFileManifest.yaml"; + YamlWriter::OutputYamlFile(multiFileManifest, multiFileManifest.Installers[0], generatedMultiFileManifestPath); + + REQUIRE(std::filesystem::exists(generatedMultiFileManifestPath)); + Manifest generatedMultiFileManifest = YamlParser::CreateFromPath(exportedMultiFileDirectory); + VerifyV1ManifestContent(generatedMultiFileManifest, false, ManifestVer{ s_ManifestVersionV1_12 }, true); +} + +TEST_CASE("WriteV1_28SingletonManifestAndVerifyContents", "[ManifestCreation]") +{ + TempDirectory singletonDirectory{ "SingletonManifest" }; + CopyTestDataFilesToFolder({ "ManifestV1_28-Singleton.yaml" }, singletonDirectory); + Manifest singletonManifest = YamlParser::CreateFromPath(singletonDirectory); + + TempDirectory exportedSingletonDirectory{ "exportedSingleton" }; + std::filesystem::path generatedSingletonManifestPath = exportedSingletonDirectory.GetPath() / "testSingletonManifest.yaml"; + YamlWriter::OutputYamlFile(singletonManifest, singletonManifest.Installers[0], generatedSingletonManifestPath); + + REQUIRE(std::filesystem::exists(generatedSingletonManifestPath)); + Manifest generatedSingletonManifest = YamlParser::CreateFromPath(exportedSingletonDirectory); + VerifyV1ManifestContent(generatedSingletonManifest, true, ManifestVer{ s_ManifestVersionV1_28 }, true); + + TempDirectory multiFileDirectory{ "MultiFileManifest" }; + CopyTestDataFilesToFolder({ + "ManifestV1_28-MultiFile-Version.yaml", + "ManifestV1_28-MultiFile-Installer.yaml", + "ManifestV1_28-MultiFile-DefaultLocale.yaml", + "ManifestV1_28-MultiFile-Locale.yaml" }, multiFileDirectory); + + Manifest multiFileManifest = YamlParser::CreateFromPath(multiFileDirectory); + TempDirectory exportedMultiFileDirectory{ "exportedMultiFile" }; + std::filesystem::path generatedMultiFileManifestPath = exportedMultiFileDirectory.GetPath() / "testMultiFileManifest.yaml"; + YamlWriter::OutputYamlFile(multiFileManifest, multiFileManifest.Installers[0], generatedMultiFileManifestPath); + + REQUIRE(std::filesystem::exists(generatedMultiFileManifestPath)); + Manifest generatedMultiFileManifest = YamlParser::CreateFromPath(exportedMultiFileDirectory); + VerifyV1ManifestContent(generatedMultiFileManifest, false, ManifestVer{ s_ManifestVersionV1_28 }, true); +} + +// PowerShell DSC is not supported in the community repo and will cause manifest validation failure. +TEST_CASE("ReadWriteValidateV1_28ManifestWithPowerShellDSC", "[ManifestValidation]") +{ + // Read manifest + TempDirectory testDirectory{ "TestManifest" }; + CopyTestDataFilesToFolder({ "ManifestV1_28-PowerShellDSC.yaml" }, testDirectory); + Manifest testManifest = YamlParser::CreateFromPath(testDirectory); + + // Validate schema + ManifestValidateOption validateOption; + validateOption.SchemaValidationOnly = true; + validateOption.ThrowOnWarning = true; + YamlParser::CreateFromPath(testDirectory, validateOption); + + // TODO: Update ValidateManifest + // TODO: Update this test with similar validations + + // Verify content + REQUIRE(testManifest.ManifestVersion == AppInstaller::Manifest::ManifestVer{ s_ManifestVersionV1_28 }); + REQUIRE(testManifest.Installers.size() == 1); + REQUIRE(testManifest.Installers[0].DesiredStateConfiguration.size() == 3); + + RequireContainerInfoPresent(testManifest.Installers[0].DesiredStateConfiguration, { "https://www.powershellgallery.com/api/v2", "Microsoft.WinGet.DSC", { { "WinGetUserSettings" }, { "WinGetAdminSettings" }, { "WinGetSource" }, { "WinGetPackageManager" }, { "WinGetPackage" } } }); + RequireContainerInfoPresent(testManifest.Installers[0].DesiredStateConfiguration, { "https://mcr.microsoft.com/", "Microsoft.WinGet.DSC", { { "WinGetUserSettings" }, { "WinGetAdminSettings" }, { "WinGetSource" }, { "WinGetPackageManager" }, { "WinGetPackage" } } }); + RequireContainerInfoPresent(testManifest.Installers[0].DesiredStateConfiguration, { { { "Microsoft.WinGet/AdminSettings" }, { "Microsoft.WinGet/Package" }, { "Microsoft.WinGet/Source" }, { "Microsoft.WinGet/UserSettingsFile" } } }); + + // Manifest Validation. Only error is "PowerShell not supported". + auto errors = ValidateManifest(testManifest, true); + REQUIRE(errors.size() == 1); + REQUIRE(errors[0].GetErrorMessage() == "Field is not supported."); + REQUIRE(errors[0].Context == "DesiredStateConfiguration.PowerShell"); + + // Write manifest + TempDirectory exportedDirectory{ "ExportedManifest" }; + std::filesystem::path exportedManifestPath = exportedDirectory.GetPath() / "ExportedManifest.yaml"; + YamlWriter::OutputYamlFile(testManifest, testManifest.Installers[0], exportedManifestPath); + + // Read back and validate content + REQUIRE(std::filesystem::exists(exportedManifestPath)); + Manifest exportedManifest = YamlParser::CreateFromPath(exportedDirectory); + REQUIRE(exportedManifest.ManifestVersion == AppInstaller::Manifest::ManifestVer{ s_ManifestVersionV1_28 }); + REQUIRE(exportedManifest.Installers.size() == 1); + REQUIRE(exportedManifest.Installers[0].DesiredStateConfiguration.size() == 3); + + RequireContainerInfoPresent(exportedManifest.Installers[0].DesiredStateConfiguration, { "https://www.powershellgallery.com/api/v2", "Microsoft.WinGet.DSC", { { "WinGetUserSettings" }, { "WinGetAdminSettings" }, { "WinGetSource" }, { "WinGetPackageManager" }, { "WinGetPackage" } } }); + RequireContainerInfoPresent(exportedManifest.Installers[0].DesiredStateConfiguration, { "https://mcr.microsoft.com/", "Microsoft.WinGet.DSC", { { "WinGetUserSettings" }, { "WinGetAdminSettings" }, { "WinGetSource" }, { "WinGetPackageManager" }, { "WinGetPackage" } } }); + RequireContainerInfoPresent(exportedManifest.Installers[0].DesiredStateConfiguration, { { { "Microsoft.WinGet/AdminSettings" }, { "Microsoft.WinGet/Package" }, { "Microsoft.WinGet/Source" }, { "Microsoft.WinGet/UserSettingsFile" } } }); +} + TEST_CASE("WriteManifestWithMultipleLocale", "[ManifestCreation]") { Manifest multiLocaleManifest = YamlParser::CreateFromPath(TestDataFile("Manifest-Good-MultiLocale.yaml")); diff --git a/src/AppInstallerCommonCore/Manifest/ManifestCommon.cpp b/src/AppInstallerCommonCore/Manifest/ManifestCommon.cpp index 544c82e36c..82454b1847 100644 --- a/src/AppInstallerCommonCore/Manifest/ManifestCommon.cpp +++ b/src/AppInstallerCommonCore/Manifest/ManifestCommon.cpp @@ -1188,7 +1188,7 @@ namespace AppInstaller::Manifest } // for testing purposes - bool DependencyList::HasExactDependency(DependencyType type, const string_t& id, const string_t& minVersion) + bool DependencyList::HasExactDependency(DependencyType type, const string_t& id, const string_t& minVersion) const { for (const auto& dependency : m_dependencies) { @@ -1207,7 +1207,7 @@ namespace AppInstaller::Manifest return false; } - size_t DependencyList::Size() + size_t DependencyList::Size() const { return m_dependencies.size(); } diff --git a/src/AppInstallerCommonCore/Manifest/ManifestSchemaValidation.cpp b/src/AppInstallerCommonCore/Manifest/ManifestSchemaValidation.cpp index 54ff6bbdb8..878afd0f7d 100644 --- a/src/AppInstallerCommonCore/Manifest/ManifestSchemaValidation.cpp +++ b/src/AppInstallerCommonCore/Manifest/ManifestSchemaValidation.cpp @@ -272,7 +272,17 @@ namespace AppInstaller::Manifest::YamlParser int idx = MANIFESTSCHEMA_NO_RESOURCE; std::map resourceMap; - if (manifestVersion >= ManifestVer{ s_ManifestVersionV1_12 }) + if (manifestVersion >= ManifestVer{ s_ManifestVersionV1_28 }) + { + resourceMap = { + { ManifestTypeEnum::Singleton, IDX_MANIFEST_SCHEMA_V1_28_SINGLETON }, + { ManifestTypeEnum::Version, IDX_MANIFEST_SCHEMA_V1_28_VERSION }, + { ManifestTypeEnum::Installer, IDX_MANIFEST_SCHEMA_V1_28_INSTALLER }, + { ManifestTypeEnum::DefaultLocale, IDX_MANIFEST_SCHEMA_V1_28_DEFAULTLOCALE }, + { ManifestTypeEnum::Locale, IDX_MANIFEST_SCHEMA_V1_28_LOCALE }, + }; + } + else if (manifestVersion >= ManifestVer{ s_ManifestVersionV1_12 }) { resourceMap = { { ManifestTypeEnum::Singleton, IDX_MANIFEST_SCHEMA_V1_12_SINGLETON }, diff --git a/src/AppInstallerCommonCore/Manifest/ManifestValidation.cpp b/src/AppInstallerCommonCore/Manifest/ManifestValidation.cpp index c9f87c516a..50ef2ad2a5 100644 --- a/src/AppInstallerCommonCore/Manifest/ManifestValidation.cpp +++ b/src/AppInstallerCommonCore/Manifest/ManifestValidation.cpp @@ -90,6 +90,7 @@ namespace AppInstaller::Manifest return ErrorIdToMessageMap; } } + std::vector ValidateManifest(const Manifest& manifest, bool fullValidation) { std::vector resultErrors; @@ -435,6 +436,19 @@ namespace AppInstaller::Manifest resultErrors.emplace_back(ManifestError::InvalidFieldValue, "Authentication"); } } + + if (fullValidation) + { + for (const auto& container : installer.DesiredStateConfiguration) + { + if (container.Type == DesiredStateConfigurationContainerType::PowerShell) + { + // PowerShell DSC is not supported in community repo. + resultErrors.emplace_back(ManifestError::FieldNotSupported, "DesiredStateConfiguration.PowerShell"); + break; + } + } + } } // Validate localizations diff --git a/src/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp b/src/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp index ad79a84c72..4fb2f2dcb7 100644 --- a/src/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp +++ b/src/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp @@ -836,8 +836,7 @@ namespace AppInstaller::Manifest { "DSCv3", [this](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { auto* variantValue = variant_ptr>(v); - variantValue->emplace_back(); - variantValue->back().Type = DesiredStateConfigurationContainerType::DSCv3; + variantValue->emplace_back(DesiredStateConfigurationContainerType::DSCv3); return ValidateAndProcessFields(value, DesiredStateConfigurationDSCv3FieldInfos, VariantManifestPtr(&variantValue->back())); } }, @@ -855,7 +854,7 @@ namespace AppInstaller::Manifest { result = { - { "RepositoryURL", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->RepositoryURL = Utility::Trim(value.as()); return {}; } }, + { "RepositoryUrl", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->RepositoryURL = Utility::Trim(value.as()); return {}; } }, { "ModuleName", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->ModuleName = Utility::Trim(value.as()); return {}; } }, { "Resources", [this](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { return ProcessDSC_PowerShellResourcesNode(value, variant_ptr(v)); } }, }; @@ -1193,7 +1192,7 @@ namespace AppInstaller::Manifest for (auto const& entry : node.Sequence()) { - auto& containerInfo = containers->emplace_back(); + auto& containerInfo = containers->emplace_back(DesiredStateConfigurationContainerType::PowerShell); auto errors = ValidateAndProcessFields(entry, DesiredStateConfigurationPowerShellModuleFieldInfos, VariantManifestPtr(&containerInfo)); std::move(errors.begin(), errors.end(), std::inserter(resultErrors, resultErrors.end())); } diff --git a/src/AppInstallerCommonCore/Manifest/YamlWriter.cpp b/src/AppInstallerCommonCore/Manifest/YamlWriter.cpp index 5f0c4b8411..ad2e381a60 100644 --- a/src/AppInstallerCommonCore/Manifest/YamlWriter.cpp +++ b/src/AppInstallerCommonCore/Manifest/YamlWriter.cpp @@ -76,7 +76,7 @@ namespace AppInstaller::Manifest::YamlWriter constexpr std::string_view DesiredStateConfiguration = "DesiredStateConfiguration"sv; constexpr std::string_view DesiredStateConfigurationResources = "Resources"sv; constexpr std::string_view DesiredStateConfigurationPowerShell = "PowerShell"sv; - constexpr std::string_view DesiredStateConfigurationPowerShellRepositoryURL = "RepositoryURL"sv; + constexpr std::string_view DesiredStateConfigurationPowerShellRepositoryURL = "RepositoryUrl"sv; constexpr std::string_view DesiredStateConfigurationPowerShellModuleName = "ModuleName"sv; constexpr std::string_view DesiredStateConfigurationPowerShellResourceName = "Name"sv; constexpr std::string_view DesiredStateConfigurationDSCv3 = "DSCv3"sv; diff --git a/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h b/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h index 4c6918ed22..009ff5603a 100644 --- a/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h +++ b/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h @@ -327,8 +327,8 @@ namespace AppInstaller::Manifest void ApplyToAll(std::function func) const; bool Empty() const; void Clear(); - bool HasExactDependency(DependencyType type, const string_t& id, const string_t& minVersion = ""); - size_t Size(); + bool HasExactDependency(DependencyType type, const string_t& id, const string_t& minVersion = "") const; + size_t Size() const; private: std::vector m_dependencies; @@ -385,7 +385,6 @@ namespace AppInstaller::Manifest // The type of resource container. enum class DesiredStateConfigurationContainerType { - Unknown, PowerShell, DSCv3, }; @@ -394,7 +393,15 @@ namespace AppInstaller::Manifest // Contains the union of properties relevant to all container types. struct DesiredStateConfigurationContainerInfo { - DesiredStateConfigurationContainerType Type = DesiredStateConfigurationContainerType::Unknown; + DesiredStateConfigurationContainerInfo(DesiredStateConfigurationContainerType type) : Type(type) {} + + DesiredStateConfigurationContainerInfo(const string_t& repositoryUrl, const string_t& moduleName, std::vector resources) : + Type(DesiredStateConfigurationContainerType::PowerShell), RepositoryURL(repositoryUrl), ModuleName(moduleName), Resources(std::move(resources)) {} + + DesiredStateConfigurationContainerInfo(std::vector resources) : + Type(DesiredStateConfigurationContainerType::DSCv3), Resources(std::move(resources)) {} + + DesiredStateConfigurationContainerType Type; string_t RepositoryURL; string_t ModuleName; std::vector Resources; diff --git a/src/ManifestSchema/ManifestSchema.h b/src/ManifestSchema/ManifestSchema.h index c683c777d3..f6d513cb69 100644 --- a/src/ManifestSchema/ManifestSchema.h +++ b/src/ManifestSchema/ManifestSchema.h @@ -68,3 +68,9 @@ #define IDX_MANIFEST_SCHEMA_V1_12_INSTALLER 249 #define IDX_MANIFEST_SCHEMA_V1_12_DEFAULTLOCALE 250 #define IDX_MANIFEST_SCHEMA_V1_12_LOCALE 251 + +#define IDX_MANIFEST_SCHEMA_V1_28_SINGLETON 252 +#define IDX_MANIFEST_SCHEMA_V1_28_VERSION 253 +#define IDX_MANIFEST_SCHEMA_V1_28_INSTALLER 254 +#define IDX_MANIFEST_SCHEMA_V1_28_DEFAULTLOCALE 255 +#define IDX_MANIFEST_SCHEMA_V1_28_LOCALE 256 diff --git a/src/ManifestSchema/ManifestSchema.rc b/src/ManifestSchema/ManifestSchema.rc index 23805ffe1f..ca98f61e7f 100644 --- a/src/ManifestSchema/ManifestSchema.rc +++ b/src/ManifestSchema/ManifestSchema.rc @@ -119,8 +119,14 @@ IDX_MANIFEST_SCHEMA_V1_10_INSTALLER MANIFESTSCHEMA_RESOURCE_TYPE "..\ IDX_MANIFEST_SCHEMA_V1_10_DEFAULTLOCALE MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.10.0\\manifest.defaultLocale.1.10.0.json" IDX_MANIFEST_SCHEMA_V1_10_LOCALE MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.10.0\\manifest.locale.1.10.0.json" -IDX_MANIFEST_SCHEMA_V1_12_SINGLETON MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\latest\\manifest.singleton.latest.json" -IDX_MANIFEST_SCHEMA_V1_12_VERSION MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\latest\\manifest.version.latest.json" -IDX_MANIFEST_SCHEMA_V1_12_INSTALLER MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\latest\\manifest.installer.latest.json" -IDX_MANIFEST_SCHEMA_V1_12_DEFAULTLOCALE MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\latest\\manifest.defaultLocale.latest.json" -IDX_MANIFEST_SCHEMA_V1_12_LOCALE MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\latest\\manifest.locale.latest.json" +IDX_MANIFEST_SCHEMA_V1_12_SINGLETON MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.12.0\\manifest.singleton.1.12.0.json" +IDX_MANIFEST_SCHEMA_V1_12_VERSION MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.12.0\\manifest.version.1.12.0.json" +IDX_MANIFEST_SCHEMA_V1_12_INSTALLER MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.12.0\\manifest.installer.1.12.0.json" +IDX_MANIFEST_SCHEMA_V1_12_DEFAULTLOCALE MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.12.0\\manifest.defaultLocale.1.12.0.json" +IDX_MANIFEST_SCHEMA_V1_12_LOCALE MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.12.0\\manifest.locale.1.12.0.json" + +IDX_MANIFEST_SCHEMA_V1_28_SINGLETON MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\latest\\manifest.singleton.latest.json" +IDX_MANIFEST_SCHEMA_V1_28_VERSION MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\latest\\manifest.version.latest.json" +IDX_MANIFEST_SCHEMA_V1_28_INSTALLER MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\latest\\manifest.installer.latest.json" +IDX_MANIFEST_SCHEMA_V1_28_DEFAULTLOCALE MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\latest\\manifest.defaultLocale.latest.json" +IDX_MANIFEST_SCHEMA_V1_28_LOCALE MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\latest\\manifest.locale.latest.json" From bb90ab11d675d67e376fdc9f71fa2502fa63a04a Mon Sep 17 00:00:00 2001 From: John McPherson Date: Wed, 21 Jan 2026 09:55:40 -0800 Subject: [PATCH 6/8] Test fixes --- .github/actions/spelling/expect.txt | 1 + .../AppInstallerCLITests.vcxproj.filters | 27 +++++++++++++++++++ .../ManifestV1_28-MultiFile-Installer.yaml | 7 ++--- src/AppInstallerCLITests/YamlManifest.cpp | 12 ++++++--- 4 files changed, 41 insertions(+), 6 deletions(-) diff --git a/.github/actions/spelling/expect.txt b/.github/actions/spelling/expect.txt index cd1f377635..4387a0a726 100644 --- a/.github/actions/spelling/expect.txt +++ b/.github/actions/spelling/expect.txt @@ -325,6 +325,7 @@ maxvalue maybenull MBH MBs +mcr mday mdmp mdmpto diff --git a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters index c63e833b68..643382697d 100644 --- a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters +++ b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters @@ -61,6 +61,9 @@ {6bd68492-3f96-4023-af35-63249cd18158} + + {0e5f9ff4-6f22-46eb-8538-1c560a008f06} + @@ -1113,5 +1116,29 @@ TestData + + TestData\MultiFileManifestV1_28 + + + TestData\MultiFileManifestV1_28 + + + TestData\MultiFileManifestV1_28 + + + TestData\MultiFileManifestV1_28 + + + TestData + + + TestData + + + TestData + + + TestData + \ No newline at end of file diff --git a/src/AppInstallerCLITests/TestData/MultiFileManifestV1_28/ManifestV1_28-MultiFile-Installer.yaml b/src/AppInstallerCLITests/TestData/MultiFileManifestV1_28/ManifestV1_28-MultiFile-Installer.yaml index 28b6363f2b..353d04cc90 100644 --- a/src/AppInstallerCLITests/TestData/MultiFileManifestV1_28/ManifestV1_28-MultiFile-Installer.yaml +++ b/src/AppInstallerCLITests/TestData/MultiFileManifestV1_28/ManifestV1_28-MultiFile-Installer.yaml @@ -169,8 +169,7 @@ Installers: UnsupportedArguments: - location DownloadCommandProhibited: false - ArchiveBinariesDependOnPath: false - DesiredStateConfiguration: + ArchiveBinariesDependOnPath: false - Architecture: x64 InstallerType: exe InstallerUrl: https://www.microsoft.com/msixsdk/msixsdkx64.exe @@ -194,7 +193,9 @@ Installers: ExpectedReturnCodes: - InstallerReturnCode: 11 ReturnResponse: custom - ReturnResponseUrl: https://defaultReturnResponseUrl.com + ReturnResponseUrl: https://defaultReturnResponseUrl.com + DesiredStateConfiguration: + DSCv3: - Architecture: x64 InstallerType: zip InstallerUrl: https://www.microsoft.com/msixsdk/msixsdkx64.exe diff --git a/src/AppInstallerCLITests/YamlManifest.cpp b/src/AppInstallerCLITests/YamlManifest.cpp index 4218fe3fad..955950fcd7 100644 --- a/src/AppInstallerCLITests/YamlManifest.cpp +++ b/src/AppInstallerCLITests/YamlManifest.cpp @@ -415,7 +415,13 @@ namespace if (manifestVer >= ManifestVer{ s_ManifestVersionV1_28 }) { - RequireContainerInfoPresent(manifest.DefaultInstallerInfo.DesiredStateConfiguration, { { { "Microsoft.WinGet/AdminSettings" }, { "Microsoft.WinGet/Package" }, { "Microsoft.WinGet/Source" }, { "Microsoft.WinGet/UserSettingsFile" } } }); + auto containers = &manifest.DefaultInstallerInfo.DesiredStateConfiguration; + if (isExported) + { + containers = &manifest.Installers[0].DesiredStateConfiguration; + } + + RequireContainerInfoPresent(*containers, { { { "Microsoft.WinGet/AdminSettings" }, { "Microsoft.WinGet/Package" }, { "Microsoft.WinGet/Source" }, { "Microsoft.WinGet/UserSettingsFile" } } }); } if (!isSingleton) @@ -541,8 +547,8 @@ namespace if (manifestVer >= ManifestVer{ s_ManifestVersionV1_28 }) { - REQUIRE(manifest.Installers[0].DesiredStateConfiguration.size() == 0); - RequireContainerInfoPresent(manifest.Installers[1].DesiredStateConfiguration, { {{"None/None"}} }); + RequireContainerInfoPresent(manifest.Installers[1].DesiredStateConfiguration, { { { "None/None" } } }); + REQUIRE(manifest.Installers[2].DesiredStateConfiguration.size() == 0); } } From d0ff888e6cb349cb829925251376a7283d454364 Mon Sep 17 00:00:00 2001 From: John McPherson Date: Thu, 22 Jan 2026 11:28:32 -0800 Subject: [PATCH 7/8] PR feedback; many comments, changed module regex to match nuget, macro-ized copy-paste tests --- .../latest/manifest.installer.latest.json | 5 +- .../latest/manifest.singleton.latest.json | 5 +- src/AppInstallerCLITests/YamlManifest.cpp | 634 ++---------------- .../Public/winget/ManifestCommon.h | 4 + .../Public/winget/ManifestYamlPopulator.h | 9 +- src/ManifestSchema/ManifestSchema.h | 4 + 6 files changed, 78 insertions(+), 583 deletions(-) diff --git a/schemas/JSON/manifests/latest/manifest.installer.latest.json b/schemas/JSON/manifests/latest/manifest.installer.latest.json index 598ec4a32c..1938bae9e6 100644 --- a/schemas/JSON/manifests/latest/manifest.installer.latest.json +++ b/schemas/JSON/manifests/latest/manifest.installer.latest.json @@ -658,7 +658,8 @@ "ModuleName": { "type": "string", "description": "The name of the module containing resources.", - "pattern": "^[A-Za-z][-._A-Za-z0-9]*$", + "$comment": "From nuget package id, although PowerShell convetion is slightle more strict: https://learn.microsoft.com/en-us/nuget/reference/errors-and-warnings/nu1017", + "pattern": "^\\w+([.-]\\w+)*$", "maxLength": 100 }, "Resources": { @@ -672,6 +673,7 @@ "Name": { "type": "string", "description": "The name of the resource.", + "$comment": "Needs to be an identifier in the various languages (MOF, PS class name), could not find any direct description.", "pattern": "^[A-Za-z][-_A-Za-z0-9]*$", "maxLength": 100 } @@ -697,6 +699,7 @@ "Type": { "type": "string", "description": "The name of the resource.", + "$comment": "Pulled from DSCv3 definition; matches `Publisher.Product.Component/ResourceName` where the Product and Component are optional.", "pattern": "^\\w+(\\.\\w+){0,2}\\/\\w+$", "maxLength": 256 } diff --git a/schemas/JSON/manifests/latest/manifest.singleton.latest.json b/schemas/JSON/manifests/latest/manifest.singleton.latest.json index dc7b49db6c..900cd3942a 100644 --- a/schemas/JSON/manifests/latest/manifest.singleton.latest.json +++ b/schemas/JSON/manifests/latest/manifest.singleton.latest.json @@ -759,7 +759,8 @@ "ModuleName": { "type": "string", "description": "The name of the module containing resources.", - "pattern": "^[A-Za-z][-._A-Za-z0-9]*$", + "$comment": "From nuget package id, although PowerShell convetion is slightle more strict: https://learn.microsoft.com/en-us/nuget/reference/errors-and-warnings/nu1017", + "pattern": "^\\w+([.-]\\w+)*$", "maxLength": 100 }, "Resources": { @@ -773,6 +774,7 @@ "Name": { "type": "string", "description": "The name of the resource.", + "$comment": "Needs to be an identifier in the various languages (MOF, PS class name), could not find any direct description.", "pattern": "^[A-Za-z][-_A-Za-z0-9]*$", "maxLength": 100 } @@ -798,6 +800,7 @@ "Type": { "type": "string", "description": "The name of the resource.", + "$comment": "Pulled from DSCv3 definition; matches `Publisher.Product.Component/ResourceName` where the Product and Component are optional.", "pattern": "^\\w+(\\.\\w+){0,2}\\/\\w+$", "maxLength": 256 } diff --git a/src/AppInstallerCLITests/YamlManifest.cpp b/src/AppInstallerCLITests/YamlManifest.cpp index 955950fcd7..4c5021887e 100644 --- a/src/AppInstallerCLITests/YamlManifest.cpp +++ b/src/AppInstallerCLITests/YamlManifest.cpp @@ -985,285 +985,56 @@ TEST_CASE("ManifestVersionExtensions", "[ManifestValidation]") REQUIRE(ManifestVer("1.0.0-msstore.2-other"sv).HasExtension("msstore")); } -TEST_CASE("ValidateV1GoodManifestAndVerifyContents", "[ManifestValidation]") +void ValidateGoodManifestAndVerifyContents(const std::vector& singleton, const std::vector& multiFiles, std::string_view version) { ManifestValidateOption validateOption; validateOption.FullValidation = true; TempDirectory singletonDirectory{ "SingletonManifest" }; - CopyTestDataFilesToFolder({ "ManifestV1-Singleton.yaml" }, singletonDirectory); + CopyTestDataFilesToFolder(singleton, singletonDirectory); Manifest singletonManifest = YamlParser::CreateFromPath(singletonDirectory, validateOption); - VerifyV1ManifestContent(singletonManifest, true); + VerifyV1ManifestContent(singletonManifest, true, ManifestVer{ version }); TempDirectory multiFileDirectory{ "MultiFileManifest" }; - CopyTestDataFilesToFolder({ - "ManifestV1-MultiFile-Version.yaml", - "ManifestV1-MultiFile-Installer.yaml", - "ManifestV1-MultiFile-DefaultLocale.yaml", - "ManifestV1-MultiFile-Locale.yaml" }, multiFileDirectory); - - TempFile mergedManifestFile{ "merged.yaml" }; - Manifest multiFileManifest = YamlParser::CreateFromPath(multiFileDirectory, validateOption, mergedManifestFile); - VerifyV1ManifestContent(multiFileManifest, false); - - // Read from merged manifest should have the same content as multi file manifest - Manifest mergedManifest = YamlParser::CreateFromPath(mergedManifestFile); - VerifyV1ManifestContent(mergedManifest, false); -} - -TEST_CASE("ValidateV1_1GoodManifestAndVerifyContents", "[ManifestValidation]") -{ - ManifestValidateOption validateOption; - validateOption.FullValidation = true; - TempDirectory singletonDirectory{ "SingletonManifest" }; - CopyTestDataFilesToFolder({ "ManifestV1_1-Singleton.yaml" }, singletonDirectory); - Manifest singletonManifest = YamlParser::CreateFromPath(singletonDirectory, validateOption); - VerifyV1ManifestContent(singletonManifest, true, ManifestVer{ s_ManifestVersionV1_1 }); - - TempDirectory multiFileDirectory{ "MultiFileManifest" }; - CopyTestDataFilesToFolder({ - "ManifestV1_1-MultiFile-Version.yaml", - "ManifestV1_1-MultiFile-Installer.yaml", - "ManifestV1_1-MultiFile-DefaultLocale.yaml", - "ManifestV1_1-MultiFile-Locale.yaml" }, multiFileDirectory); - - TempFile mergedManifestFile{ "merged.yaml" }; - Manifest multiFileManifest = YamlParser::CreateFromPath(multiFileDirectory, validateOption, mergedManifestFile); - VerifyV1ManifestContent(multiFileManifest, false, ManifestVer{ s_ManifestVersionV1_1 }); - - // Read from merged manifest should have the same content as multi file manifest - Manifest mergedManifest = YamlParser::CreateFromPath(mergedManifestFile); - VerifyV1ManifestContent(mergedManifest, false, ManifestVer{ s_ManifestVersionV1_1 }); -} - -TEST_CASE("ValidateV1_2GoodManifestAndVerifyContents", "[ManifestValidation]") -{ - ManifestValidateOption validateOption; - validateOption.FullValidation = true; - TempDirectory singletonDirectory{ "SingletonManifest" }; - CopyTestDataFilesToFolder({ "ManifestV1_2-Singleton.yaml" }, singletonDirectory); - Manifest singletonManifest = YamlParser::CreateFromPath(singletonDirectory, validateOption); - VerifyV1ManifestContent(singletonManifest, true, ManifestVer{ s_ManifestVersionV1_2 }); - - TempDirectory multiFileDirectory{ "MultiFileManifest" }; - CopyTestDataFilesToFolder({ - "ManifestV1_2-MultiFile-Version.yaml", - "ManifestV1_2-MultiFile-Installer.yaml", - "ManifestV1_2-MultiFile-DefaultLocale.yaml", - "ManifestV1_2-MultiFile-Locale.yaml" }, multiFileDirectory); - - TempFile mergedManifestFile{ "merged.yaml" }; - Manifest multiFileManifest = YamlParser::CreateFromPath(multiFileDirectory, validateOption, mergedManifestFile); - VerifyV1ManifestContent(multiFileManifest, false, ManifestVer{ s_ManifestVersionV1_2 }); - - // Read from merged manifest should have the same content as multi file manifest - Manifest mergedManifest = YamlParser::CreateFromPath(mergedManifestFile); - VerifyV1ManifestContent(mergedManifest, false, ManifestVer{ s_ManifestVersionV1_2 }); -} - -TEST_CASE("ValidateV1_4GoodManifestAndVerifyContents", "[ManifestValidation]") -{ - ManifestValidateOption validateOption; - validateOption.FullValidation = true; - TempDirectory singletonDirectory{ "SingletonManifest" }; - CopyTestDataFilesToFolder({ "ManifestV1_4-Singleton.yaml" }, singletonDirectory); - Manifest singletonManifest = YamlParser::CreateFromPath(singletonDirectory, validateOption); - VerifyV1ManifestContent(singletonManifest, true, ManifestVer{ s_ManifestVersionV1_4 }); - - TempDirectory multiFileDirectory{ "MultiFileManifest" }; - CopyTestDataFilesToFolder({ - "ManifestV1_4-MultiFile-Version.yaml", - "ManifestV1_4-MultiFile-Installer.yaml", - "ManifestV1_4-MultiFile-DefaultLocale.yaml", - "ManifestV1_4-MultiFile-Locale.yaml" }, multiFileDirectory); - - TempFile mergedManifestFile{ "merged.yaml" }; - Manifest multiFileManifest = YamlParser::CreateFromPath(multiFileDirectory, validateOption, mergedManifestFile); - VerifyV1ManifestContent(multiFileManifest, false, ManifestVer{ s_ManifestVersionV1_4 }); - - // Read from merged manifest should have the same content as multi file manifest - Manifest mergedManifest = YamlParser::CreateFromPath(mergedManifestFile); - VerifyV1ManifestContent(mergedManifest, false, ManifestVer{ s_ManifestVersionV1_4 }); -} - -TEST_CASE("ValidateV1_5GoodManifestAndVerifyContents", "[ManifestValidation]") -{ - ManifestValidateOption validateOption; - validateOption.FullValidation = true; - TempDirectory singletonDirectory{ "SingletonManifest" }; - CopyTestDataFilesToFolder({ "ManifestV1_5-Singleton.yaml" }, singletonDirectory); - Manifest singletonManifest = YamlParser::CreateFromPath(singletonDirectory, validateOption); - VerifyV1ManifestContent(singletonManifest, true, ManifestVer{ s_ManifestVersionV1_5 }); - - TempDirectory multiFileDirectory{ "MultiFileManifest" }; - CopyTestDataFilesToFolder({ - "ManifestV1_5-MultiFile-Version.yaml", - "ManifestV1_5-MultiFile-Installer.yaml", - "ManifestV1_5-MultiFile-DefaultLocale.yaml", - "ManifestV1_5-MultiFile-Locale.yaml" }, multiFileDirectory); - - TempFile mergedManifestFile{ "merged.yaml" }; - Manifest multiFileManifest = YamlParser::CreateFromPath(multiFileDirectory, validateOption, mergedManifestFile); - VerifyV1ManifestContent(multiFileManifest, false, ManifestVer{ s_ManifestVersionV1_5 }); - - // Read from merged manifest should have the same content as multi file manifest - Manifest mergedManifest = YamlParser::CreateFromPath(mergedManifestFile); - VerifyV1ManifestContent(mergedManifest, false, ManifestVer{ s_ManifestVersionV1_5 }); -} - -TEST_CASE("ValidateV1_6GoodManifestAndVerifyContents", "[ManifestValidation]") -{ - ManifestValidateOption validateOption; - validateOption.FullValidation = true; - TempDirectory singletonDirectory{ "SingletonManifest" }; - CopyTestDataFilesToFolder({ "ManifestV1_6-Singleton.yaml" }, singletonDirectory); - Manifest singletonManifest = YamlParser::CreateFromPath(singletonDirectory, validateOption); - VerifyV1ManifestContent(singletonManifest, true, ManifestVer{ s_ManifestVersionV1_6 }); - - TempDirectory multiFileDirectory{ "MultiFileManifest" }; - CopyTestDataFilesToFolder({ - "ManifestV1_6-MultiFile-Version.yaml", - "ManifestV1_6-MultiFile-Installer.yaml", - "ManifestV1_6-MultiFile-DefaultLocale.yaml", - "ManifestV1_6-MultiFile-Locale.yaml" }, multiFileDirectory); - - TempFile mergedManifestFile{ "merged.yaml" }; - Manifest multiFileManifest = YamlParser::CreateFromPath(multiFileDirectory, validateOption, mergedManifestFile); - VerifyV1ManifestContent(multiFileManifest, false, ManifestVer{ s_ManifestVersionV1_6 }); - - // Read from merged manifest should have the same content as multi file manifest - Manifest mergedManifest = YamlParser::CreateFromPath(mergedManifestFile); - VerifyV1ManifestContent(mergedManifest, false, ManifestVer{ s_ManifestVersionV1_6 }); -} - -TEST_CASE("ValidateV1_7GoodManifestAndVerifyContents", "[ManifestValidation]") -{ - ManifestValidateOption validateOption; - validateOption.FullValidation = true; - TempDirectory singletonDirectory{ "SingletonManifest" }; - CopyTestDataFilesToFolder({ "ManifestV1_7-Singleton.yaml" }, singletonDirectory); - Manifest singletonManifest = YamlParser::CreateFromPath(singletonDirectory, validateOption); - VerifyV1ManifestContent(singletonManifest, true, ManifestVer{ s_ManifestVersionV1_7 }); - - TempDirectory multiFileDirectory{ "MultiFileManifest" }; - CopyTestDataFilesToFolder({ - "ManifestV1_7-MultiFile-Version.yaml", - "ManifestV1_7-MultiFile-Installer.yaml", - "ManifestV1_7-MultiFile-DefaultLocale.yaml", - "ManifestV1_7-MultiFile-Locale.yaml" }, multiFileDirectory); - - TempFile mergedManifestFile{ "merged.yaml" }; - Manifest multiFileManifest = YamlParser::CreateFromPath(multiFileDirectory, validateOption, mergedManifestFile); - VerifyV1ManifestContent(multiFileManifest, false, ManifestVer{ s_ManifestVersionV1_7 }); - - // Read from merged manifest should have the same content as multi file manifest - Manifest mergedManifest = YamlParser::CreateFromPath(mergedManifestFile); - VerifyV1ManifestContent(mergedManifest, false, ManifestVer{ s_ManifestVersionV1_7 }); -} - -TEST_CASE("ValidateV1_9GoodManifestAndVerifyContents", "[ManifestValidation]") -{ - ManifestValidateOption validateOption; - validateOption.FullValidation = true; - TempDirectory singletonDirectory{ "SingletonManifest" }; - CopyTestDataFilesToFolder({ "ManifestV1_9-Singleton.yaml" }, singletonDirectory); - Manifest singletonManifest = YamlParser::CreateFromPath(singletonDirectory, validateOption); - VerifyV1ManifestContent(singletonManifest, true, ManifestVer{ s_ManifestVersionV1_9 }); - - TempDirectory multiFileDirectory{ "MultiFileManifest" }; - CopyTestDataFilesToFolder({ - "ManifestV1_9-MultiFile-Version.yaml", - "ManifestV1_9-MultiFile-Installer.yaml", - "ManifestV1_9-MultiFile-DefaultLocale.yaml", - "ManifestV1_9-MultiFile-Locale.yaml" }, multiFileDirectory); - - TempFile mergedManifestFile{ "merged.yaml" }; - Manifest multiFileManifest = YamlParser::CreateFromPath(multiFileDirectory, validateOption, mergedManifestFile); - VerifyV1ManifestContent(multiFileManifest, false, ManifestVer{ s_ManifestVersionV1_9 }); - - // Read from merged manifest should have the same content as multi file manifest - Manifest mergedManifest = YamlParser::CreateFromPath(mergedManifestFile); - VerifyV1ManifestContent(mergedManifest, false, ManifestVer{ s_ManifestVersionV1_9 }); -} - -TEST_CASE("ValidateV1_10GoodManifestAndVerifyContents", "[ManifestValidation]") -{ - ManifestValidateOption validateOption; - validateOption.FullValidation = true; - TempDirectory singletonDirectory{ "SingletonManifest" }; - CopyTestDataFilesToFolder({ "ManifestV1_10-Singleton.yaml" }, singletonDirectory); - Manifest singletonManifest = YamlParser::CreateFromPath(singletonDirectory, validateOption); - VerifyV1ManifestContent(singletonManifest, true, ManifestVer{ s_ManifestVersionV1_10 }); - - TempDirectory multiFileDirectory{ "MultiFileManifest" }; - CopyTestDataFilesToFolder({ - "ManifestV1_10-MultiFile-Version.yaml", - "ManifestV1_10-MultiFile-Installer.yaml", - "ManifestV1_10-MultiFile-DefaultLocale.yaml", - "ManifestV1_10-MultiFile-Locale.yaml" }, multiFileDirectory); - - TempFile mergedManifestFile{ "merged.yaml" }; - Manifest multiFileManifest = YamlParser::CreateFromPath(multiFileDirectory, validateOption, mergedManifestFile); - VerifyV1ManifestContent(multiFileManifest, false, ManifestVer{ s_ManifestVersionV1_10 }); - - // Read from merged manifest should have the same content as multi file manifest - Manifest mergedManifest = YamlParser::CreateFromPath(mergedManifestFile); - VerifyV1ManifestContent(mergedManifest, false, ManifestVer{ s_ManifestVersionV1_10 }); -} - -TEST_CASE("ValidateV1_12GoodManifestAndVerifyContents", "[ManifestValidation]") -{ - ManifestValidateOption validateOption; - validateOption.FullValidation = true; - TempDirectory singletonDirectory{ "SingletonManifest" }; - CopyTestDataFilesToFolder({ "ManifestV1_12-Singleton.yaml" }, singletonDirectory); - Manifest singletonManifest = YamlParser::CreateFromPath(singletonDirectory, validateOption); - VerifyV1ManifestContent(singletonManifest, true, ManifestVer{ s_ManifestVersionV1_12 }); - - TempDirectory multiFileDirectory{ "MultiFileManifest" }; - CopyTestDataFilesToFolder({ - "ManifestV1_12-MultiFile-Version.yaml", - "ManifestV1_12-MultiFile-Installer.yaml", - "ManifestV1_12-MultiFile-DefaultLocale.yaml", - "ManifestV1_12-MultiFile-Locale.yaml" }, multiFileDirectory); + CopyTestDataFilesToFolder(multiFiles, multiFileDirectory); TempFile mergedManifestFile{ "merged.yaml" }; Manifest multiFileManifest = YamlParser::CreateFromPath(multiFileDirectory, validateOption, mergedManifestFile); - VerifyV1ManifestContent(multiFileManifest, false, ManifestVer{ s_ManifestVersionV1_12 }); + VerifyV1ManifestContent(multiFileManifest, false, ManifestVer{ version }); // Read from merged manifest should have the same content as multi file manifest Manifest mergedManifest = YamlParser::CreateFromPath(mergedManifestFile); - VerifyV1ManifestContent(mergedManifest, false, ManifestVer{ s_ManifestVersionV1_12 }); + VerifyV1ManifestContent(mergedManifest, false, ManifestVer{ version }); } -TEST_CASE("ValidateV1_28GoodManifestAndVerifyContents", "[ManifestValidation]") -{ - ManifestValidateOption validateOption; - validateOption.FullValidation = true; - TempDirectory singletonDirectory{ "SingletonManifest" }; - CopyTestDataFilesToFolder({ "ManifestV1_28-Singleton.yaml" }, singletonDirectory); - Manifest singletonManifest = YamlParser::CreateFromPath(singletonDirectory, validateOption); - VerifyV1ManifestContent(singletonManifest, true, ManifestVer{ s_ManifestVersionV1_28 }); - - TempDirectory multiFileDirectory{ "MultiFileManifest" }; - CopyTestDataFilesToFolder({ - "ManifestV1_28-MultiFile-Version.yaml", - "ManifestV1_28-MultiFile-Installer.yaml", - "ManifestV1_28-MultiFile-DefaultLocale.yaml", - "ManifestV1_28-MultiFile-Locale.yaml" }, multiFileDirectory); - - TempFile mergedManifestFile{ "merged.yaml" }; - Manifest multiFileManifest = YamlParser::CreateFromPath(multiFileDirectory, validateOption, mergedManifestFile); - VerifyV1ManifestContent(multiFileManifest, false, ManifestVer{ s_ManifestVersionV1_28 }); - - // Read from merged manifest should have the same content as multi file manifest - Manifest mergedManifest = YamlParser::CreateFromPath(mergedManifestFile); - VerifyV1ManifestContent(mergedManifest, false, ManifestVer{ s_ManifestVersionV1_28 }); +#define WINGET_VALIDATE_GOOD_MANIFEST_VERSION(_version_) TEST_CASE("ValidateGoodManifestAndVerifyContents_" #_version_ , "[ManifestValidation][ManifestVersionValidation]") \ +{ \ + ValidateGoodManifestAndVerifyContents( \ + { "ManifestV" #_version_ "-Singleton.yaml" }, \ + { \ + "ManifestV" #_version_ "-MultiFile-Version.yaml", \ + "ManifestV" #_version_ "-MultiFile-Installer.yaml", \ + "ManifestV" #_version_ "-MultiFile-DefaultLocale.yaml", \ + "ManifestV" #_version_ "-MultiFile-Locale.yaml" \ + }, \ + s_ManifestVersionV ## _version_); \ } -TEST_CASE("WriteV1SingletonManifestAndVerifyContents", "[ManifestCreation]") +WINGET_VALIDATE_GOOD_MANIFEST_VERSION(1) +WINGET_VALIDATE_GOOD_MANIFEST_VERSION(1_1) +WINGET_VALIDATE_GOOD_MANIFEST_VERSION(1_2) +WINGET_VALIDATE_GOOD_MANIFEST_VERSION(1_4) +WINGET_VALIDATE_GOOD_MANIFEST_VERSION(1_5) +WINGET_VALIDATE_GOOD_MANIFEST_VERSION(1_6) +WINGET_VALIDATE_GOOD_MANIFEST_VERSION(1_7) +WINGET_VALIDATE_GOOD_MANIFEST_VERSION(1_9) +WINGET_VALIDATE_GOOD_MANIFEST_VERSION(1_10) +WINGET_VALIDATE_GOOD_MANIFEST_VERSION(1_12) +WINGET_VALIDATE_GOOD_MANIFEST_VERSION(1_28) + +void WriteSingletonManifestAndVerifyContents(const std::vector& singleton, const std::vector& multiFiles, std::string_view version) { TempDirectory singletonDirectory{ "SingletonManifest" }; - CopyTestDataFilesToFolder({ "ManifestV1_1-Singleton.yaml" }, singletonDirectory); + CopyTestDataFilesToFolder(singleton, singletonDirectory); Manifest singletonManifest = YamlParser::CreateFromPath(singletonDirectory); TempDirectory exportedSingletonDirectory{ "exportedSingleton" }; @@ -1272,14 +1043,10 @@ TEST_CASE("WriteV1SingletonManifestAndVerifyContents", "[ManifestCreation]") REQUIRE(std::filesystem::exists(generatedSingletonManifestPath)); Manifest generatedSingletonManifest = YamlParser::CreateFromPath(exportedSingletonDirectory); - VerifyV1ManifestContent(generatedSingletonManifest, true, ManifestVer{ s_ManifestVersionV1 }, true); + VerifyV1ManifestContent(generatedSingletonManifest, true, ManifestVer{ version }, true); TempDirectory multiFileDirectory{ "MultiFileManifest" }; - CopyTestDataFilesToFolder({ - "ManifestV1-MultiFile-Version.yaml", - "ManifestV1-MultiFile-Installer.yaml", - "ManifestV1-MultiFile-DefaultLocale.yaml", - "ManifestV1-MultiFile-Locale.yaml" }, multiFileDirectory); + CopyTestDataFilesToFolder(multiFiles, multiFileDirectory); Manifest multiFileManifest = YamlParser::CreateFromPath(multiFileDirectory); TempDirectory exportedMultiFileDirectory{ "exportedMultiFile" }; @@ -1288,260 +1055,37 @@ TEST_CASE("WriteV1SingletonManifestAndVerifyContents", "[ManifestCreation]") REQUIRE(std::filesystem::exists(generatedMultiFileManifestPath)); Manifest generatedMultiFileManifest = YamlParser::CreateFromPath(exportedMultiFileDirectory); - VerifyV1ManifestContent(generatedMultiFileManifest, false, ManifestVer{ s_ManifestVersionV1 }, true); + VerifyV1ManifestContent(generatedMultiFileManifest, false, ManifestVer{ version }, true); } -TEST_CASE("WriteV1_1SingletonManifestAndVerifyContents", "[ManifestCreation]") -{ - TempDirectory singletonDirectory{ "SingletonManifest" }; - CopyTestDataFilesToFolder({ "ManifestV1_1-Singleton.yaml" }, singletonDirectory); - Manifest singletonManifest = YamlParser::CreateFromPath(singletonDirectory); - - TempDirectory exportedSingletonDirectory{ "exportedSingleton" }; - std::filesystem::path generatedSingletonManifestPath = exportedSingletonDirectory.GetPath() / "testSingletonManifest.yaml"; - YamlWriter::OutputYamlFile(singletonManifest, singletonManifest.Installers[0], generatedSingletonManifestPath); - - REQUIRE(std::filesystem::exists(generatedSingletonManifestPath)); - Manifest generatedSingletonManifest = YamlParser::CreateFromPath(exportedSingletonDirectory); - VerifyV1ManifestContent(generatedSingletonManifest, true, ManifestVer{ s_ManifestVersionV1_1 }, true); - - TempDirectory multiFileDirectory{ "MultiFileManifest" }; - CopyTestDataFilesToFolder({ - "ManifestV1_1-MultiFile-Version.yaml", - "ManifestV1_1-MultiFile-Installer.yaml", - "ManifestV1_1-MultiFile-DefaultLocale.yaml", - "ManifestV1_1-MultiFile-Locale.yaml" }, multiFileDirectory); - - Manifest multiFileManifest = YamlParser::CreateFromPath(multiFileDirectory); - TempDirectory exportedMultiFileDirectory{ "exportedMultiFile" }; - std::filesystem::path generatedMultiFileManifestPath = exportedMultiFileDirectory.GetPath() / "testMultiFileManifest.yaml"; - YamlWriter::OutputYamlFile(multiFileManifest, multiFileManifest.Installers[0], generatedMultiFileManifestPath); - - REQUIRE(std::filesystem::exists(generatedMultiFileManifestPath)); - Manifest generatedMultiFileManifest = YamlParser::CreateFromPath(exportedMultiFileDirectory); - VerifyV1ManifestContent(generatedMultiFileManifest, false, ManifestVer{ s_ManifestVersionV1_1 }, true); +#define WINGET_WRITE_VERIFY_MANIFEST_VERSION(_version_) TEST_CASE("WriteSingletonManifestAndVerifyContents_" #_version_ , "[ManifestCreation][ManifestVersionCreation]") \ +{ \ + WriteSingletonManifestAndVerifyContents( \ + { "ManifestV" #_version_ "-Singleton.yaml" }, \ + { \ + "ManifestV" #_version_ "-MultiFile-Version.yaml", \ + "ManifestV" #_version_ "-MultiFile-Installer.yaml", \ + "ManifestV" #_version_ "-MultiFile-DefaultLocale.yaml", \ + "ManifestV" #_version_ "-MultiFile-Locale.yaml" \ + }, \ + s_ManifestVersionV ## _version_); \ } -TEST_CASE("WriteV1_2SingletonManifestAndVerifyContents", "[ManifestCreation]") -{ - TempDirectory singletonDirectory{ "SingletonManifest" }; - CopyTestDataFilesToFolder({ "ManifestV1_2-Singleton.yaml" }, singletonDirectory); - Manifest singletonManifest = YamlParser::CreateFromPath(singletonDirectory); - - TempDirectory exportedSingletonDirectory{ "exportedSingleton" }; - std::filesystem::path generatedSingletonManifestPath = exportedSingletonDirectory.GetPath() / "testSingletonManifest.yaml"; - YamlWriter::OutputYamlFile(singletonManifest, singletonManifest.Installers[0], generatedSingletonManifestPath); - - REQUIRE(std::filesystem::exists(generatedSingletonManifestPath)); - Manifest generatedSingletonManifest = YamlParser::CreateFromPath(exportedSingletonDirectory); - VerifyV1ManifestContent(generatedSingletonManifest, true, ManifestVer{ s_ManifestVersionV1_2 }, true); - - TempDirectory multiFileDirectory{ "MultiFileManifest" }; - CopyTestDataFilesToFolder({ - "ManifestV1_2-MultiFile-Version.yaml", - "ManifestV1_2-MultiFile-Installer.yaml", - "ManifestV1_2-MultiFile-DefaultLocale.yaml", - "ManifestV1_2-MultiFile-Locale.yaml" }, multiFileDirectory); - - Manifest multiFileManifest = YamlParser::CreateFromPath(multiFileDirectory); - TempDirectory exportedMultiFileDirectory{ "exportedMultiFile" }; - std::filesystem::path generatedMultiFileManifestPath = exportedMultiFileDirectory.GetPath() / "testMultiFileManifest.yaml"; - YamlWriter::OutputYamlFile(multiFileManifest, multiFileManifest.Installers[0], generatedMultiFileManifestPath); - - REQUIRE(std::filesystem::exists(generatedMultiFileManifestPath)); - Manifest generatedMultiFileManifest = YamlParser::CreateFromPath(exportedMultiFileDirectory); - VerifyV1ManifestContent(generatedMultiFileManifest, false, ManifestVer{ s_ManifestVersionV1_2 }, true); -} - -TEST_CASE("WriteV1_4SingletonManifestAndVerifyContents", "[ManifestCreation]") -{ - TempDirectory singletonDirectory{ "SingletonManifest" }; - CopyTestDataFilesToFolder({ "ManifestV1_4-Singleton.yaml" }, singletonDirectory); - Manifest singletonManifest = YamlParser::CreateFromPath(singletonDirectory); - - TempDirectory exportedSingletonDirectory{ "exportedSingleton" }; - std::filesystem::path generatedSingletonManifestPath = exportedSingletonDirectory.GetPath() / "testSingletonManifest.yaml"; - YamlWriter::OutputYamlFile(singletonManifest, singletonManifest.Installers[0], generatedSingletonManifestPath); - - REQUIRE(std::filesystem::exists(generatedSingletonManifestPath)); - Manifest generatedSingletonManifest = YamlParser::CreateFromPath(exportedSingletonDirectory); - VerifyV1ManifestContent(generatedSingletonManifest, true, ManifestVer{ s_ManifestVersionV1_4 }, true); - - TempDirectory multiFileDirectory{ "MultiFileManifest" }; - CopyTestDataFilesToFolder({ - "ManifestV1_4-MultiFile-Version.yaml", - "ManifestV1_4-MultiFile-Installer.yaml", - "ManifestV1_4-MultiFile-DefaultLocale.yaml", - "ManifestV1_4-MultiFile-Locale.yaml" }, multiFileDirectory); - - Manifest multiFileManifest = YamlParser::CreateFromPath(multiFileDirectory); - TempDirectory exportedMultiFileDirectory{ "exportedMultiFile" }; - std::filesystem::path generatedMultiFileManifestPath = exportedMultiFileDirectory.GetPath() / "testMultiFileManifest.yaml"; - YamlWriter::OutputYamlFile(multiFileManifest, multiFileManifest.Installers[0], generatedMultiFileManifestPath); - - REQUIRE(std::filesystem::exists(generatedMultiFileManifestPath)); - Manifest generatedMultiFileManifest = YamlParser::CreateFromPath(exportedMultiFileDirectory); - VerifyV1ManifestContent(generatedMultiFileManifest, false, ManifestVer{ s_ManifestVersionV1_4 }, true); -} - -TEST_CASE("WriteV1_5SingletonManifestAndVerifyContents", "[ManifestCreation]") -{ - TempDirectory singletonDirectory{ "SingletonManifest" }; - CopyTestDataFilesToFolder({ "ManifestV1_5-Singleton.yaml" }, singletonDirectory); - Manifest singletonManifest = YamlParser::CreateFromPath(singletonDirectory); - - TempDirectory exportedSingletonDirectory{ "exportedSingleton" }; - std::filesystem::path generatedSingletonManifestPath = exportedSingletonDirectory.GetPath() / "testSingletonManifest.yaml"; - YamlWriter::OutputYamlFile(singletonManifest, singletonManifest.Installers[0], generatedSingletonManifestPath); - - REQUIRE(std::filesystem::exists(generatedSingletonManifestPath)); - Manifest generatedSingletonManifest = YamlParser::CreateFromPath(exportedSingletonDirectory); - VerifyV1ManifestContent(generatedSingletonManifest, true, ManifestVer{ s_ManifestVersionV1_5 }, true); - - TempDirectory multiFileDirectory{ "MultiFileManifest" }; - CopyTestDataFilesToFolder({ - "ManifestV1_5-MultiFile-Version.yaml", - "ManifestV1_5-MultiFile-Installer.yaml", - "ManifestV1_5-MultiFile-DefaultLocale.yaml", - "ManifestV1_5-MultiFile-Locale.yaml" }, multiFileDirectory); - - Manifest multiFileManifest = YamlParser::CreateFromPath(multiFileDirectory); - TempDirectory exportedMultiFileDirectory{ "exportedMultiFile" }; - std::filesystem::path generatedMultiFileManifestPath = exportedMultiFileDirectory.GetPath() / "testMultiFileManifest.yaml"; - YamlWriter::OutputYamlFile(multiFileManifest, multiFileManifest.Installers[0], generatedMultiFileManifestPath); - - REQUIRE(std::filesystem::exists(generatedMultiFileManifestPath)); - Manifest generatedMultiFileManifest = YamlParser::CreateFromPath(exportedMultiFileDirectory); - VerifyV1ManifestContent(generatedMultiFileManifest, false, ManifestVer{ s_ManifestVersionV1_5 }, true); -} - -TEST_CASE("WriteV1_6SingletonManifestAndVerifyContents", "[ManifestCreation]") -{ - TempDirectory singletonDirectory{ "SingletonManifest" }; - CopyTestDataFilesToFolder({ "ManifestV1_6-Singleton.yaml" }, singletonDirectory); - Manifest singletonManifest = YamlParser::CreateFromPath(singletonDirectory); - - TempDirectory exportedSingletonDirectory{ "exportedSingleton" }; - std::filesystem::path generatedSingletonManifestPath = exportedSingletonDirectory.GetPath() / "testSingletonManifest.yaml"; - YamlWriter::OutputYamlFile(singletonManifest, singletonManifest.Installers[0], generatedSingletonManifestPath); - - REQUIRE(std::filesystem::exists(generatedSingletonManifestPath)); - Manifest generatedSingletonManifest = YamlParser::CreateFromPath(exportedSingletonDirectory); - VerifyV1ManifestContent(generatedSingletonManifest, true, ManifestVer{ s_ManifestVersionV1_6 }, true); - - TempDirectory multiFileDirectory{ "MultiFileManifest" }; - CopyTestDataFilesToFolder({ - "ManifestV1_6-MultiFile-Version.yaml", - "ManifestV1_6-MultiFile-Installer.yaml", - "ManifestV1_6-MultiFile-DefaultLocale.yaml", - "ManifestV1_6-MultiFile-Locale.yaml" }, multiFileDirectory); - - Manifest multiFileManifest = YamlParser::CreateFromPath(multiFileDirectory); - TempDirectory exportedMultiFileDirectory{ "exportedMultiFile" }; - std::filesystem::path generatedMultiFileManifestPath = exportedMultiFileDirectory.GetPath() / "testMultiFileManifest.yaml"; - YamlWriter::OutputYamlFile(multiFileManifest, multiFileManifest.Installers[0], generatedMultiFileManifestPath); - - REQUIRE(std::filesystem::exists(generatedMultiFileManifestPath)); - Manifest generatedMultiFileManifest = YamlParser::CreateFromPath(exportedMultiFileDirectory); - VerifyV1ManifestContent(generatedMultiFileManifest, false, ManifestVer{ s_ManifestVersionV1_6 }, true); -} - -TEST_CASE("WriteV1_7SingletonManifestAndVerifyContents", "[ManifestCreation]") -{ - TempDirectory singletonDirectory{ "SingletonManifest" }; - CopyTestDataFilesToFolder({ "ManifestV1_7-Singleton.yaml" }, singletonDirectory); - Manifest singletonManifest = YamlParser::CreateFromPath(singletonDirectory); - - TempDirectory exportedSingletonDirectory{ "exportedSingleton" }; - std::filesystem::path generatedSingletonManifestPath = exportedSingletonDirectory.GetPath() / "testSingletonManifest.yaml"; - YamlWriter::OutputYamlFile(singletonManifest, singletonManifest.Installers[0], generatedSingletonManifestPath); - - REQUIRE(std::filesystem::exists(generatedSingletonManifestPath)); - Manifest generatedSingletonManifest = YamlParser::CreateFromPath(exportedSingletonDirectory); - VerifyV1ManifestContent(generatedSingletonManifest, true, ManifestVer{ s_ManifestVersionV1_7 }, true); - - TempDirectory multiFileDirectory{ "MultiFileManifest" }; - CopyTestDataFilesToFolder({ - "ManifestV1_7-MultiFile-Version.yaml", - "ManifestV1_7-MultiFile-Installer.yaml", - "ManifestV1_7-MultiFile-DefaultLocale.yaml", - "ManifestV1_7-MultiFile-Locale.yaml" }, multiFileDirectory); - - Manifest multiFileManifest = YamlParser::CreateFromPath(multiFileDirectory); - TempDirectory exportedMultiFileDirectory{ "exportedMultiFile" }; - std::filesystem::path generatedMultiFileManifestPath = exportedMultiFileDirectory.GetPath() / "testMultiFileManifest.yaml"; - YamlWriter::OutputYamlFile(multiFileManifest, multiFileManifest.Installers[0], generatedMultiFileManifestPath); - - REQUIRE(std::filesystem::exists(generatedMultiFileManifestPath)); - Manifest generatedMultiFileManifest = YamlParser::CreateFromPath(exportedMultiFileDirectory); - VerifyV1ManifestContent(generatedMultiFileManifest, false, ManifestVer{ s_ManifestVersionV1_7 }, true); -} - -TEST_CASE("WriteV1_9SingletonManifestAndVerifyContents", "[ManifestCreation]") -{ - TempDirectory singletonDirectory{ "SingletonManifest" }; - CopyTestDataFilesToFolder({ "ManifestV1_9-Singleton.yaml" }, singletonDirectory); - Manifest singletonManifest = YamlParser::CreateFromPath(singletonDirectory); - - TempDirectory exportedSingletonDirectory{ "exportedSingleton" }; - std::filesystem::path generatedSingletonManifestPath = exportedSingletonDirectory.GetPath() / "testSingletonManifest.yaml"; - YamlWriter::OutputYamlFile(singletonManifest, singletonManifest.Installers[0], generatedSingletonManifestPath); - - REQUIRE(std::filesystem::exists(generatedSingletonManifestPath)); - Manifest generatedSingletonManifest = YamlParser::CreateFromPath(exportedSingletonDirectory); - VerifyV1ManifestContent(generatedSingletonManifest, true, ManifestVer{ s_ManifestVersionV1_9 }, true); - - TempDirectory multiFileDirectory{ "MultiFileManifest" }; - CopyTestDataFilesToFolder({ - "ManifestV1_9-MultiFile-Version.yaml", - "ManifestV1_9-MultiFile-Installer.yaml", - "ManifestV1_9-MultiFile-DefaultLocale.yaml", - "ManifestV1_9-MultiFile-Locale.yaml" }, multiFileDirectory); - - Manifest multiFileManifest = YamlParser::CreateFromPath(multiFileDirectory); - TempDirectory exportedMultiFileDirectory{ "exportedMultiFile" }; - std::filesystem::path generatedMultiFileManifestPath = exportedMultiFileDirectory.GetPath() / "testMultiFileManifest.yaml"; - YamlWriter::OutputYamlFile(multiFileManifest, multiFileManifest.Installers[0], generatedMultiFileManifestPath); - - REQUIRE(std::filesystem::exists(generatedMultiFileManifestPath)); - Manifest generatedMultiFileManifest = YamlParser::CreateFromPath(exportedMultiFileDirectory); - VerifyV1ManifestContent(generatedMultiFileManifest, false, ManifestVer{ s_ManifestVersionV1_9 }, true); -} - -TEST_CASE("WriteV1_10SingletonManifestAndVerifyContents", "[ManifestCreation]") -{ - TempDirectory singletonDirectory{ "SingletonManifest" }; - CopyTestDataFilesToFolder({ "ManifestV1_10-Singleton.yaml" }, singletonDirectory); - Manifest singletonManifest = YamlParser::CreateFromPath(singletonDirectory); - - TempDirectory exportedSingletonDirectory{ "exportedSingleton" }; - std::filesystem::path generatedSingletonManifestPath = exportedSingletonDirectory.GetPath() / "testSingletonManifest.yaml"; - YamlWriter::OutputYamlFile(singletonManifest, singletonManifest.Installers[0], generatedSingletonManifestPath); - - REQUIRE(std::filesystem::exists(generatedSingletonManifestPath)); - Manifest generatedSingletonManifest = YamlParser::CreateFromPath(exportedSingletonDirectory); - VerifyV1ManifestContent(generatedSingletonManifest, true, ManifestVer{ s_ManifestVersionV1_10 }, true); - - TempDirectory multiFileDirectory{ "MultiFileManifest" }; - CopyTestDataFilesToFolder({ - "ManifestV1_10-MultiFile-Version.yaml", - "ManifestV1_10-MultiFile-Installer.yaml", - "ManifestV1_10-MultiFile-DefaultLocale.yaml", - "ManifestV1_10-MultiFile-Locale.yaml" }, multiFileDirectory); - - Manifest multiFileManifest = YamlParser::CreateFromPath(multiFileDirectory); - TempDirectory exportedMultiFileDirectory{ "exportedMultiFile" }; - std::filesystem::path generatedMultiFileManifestPath = exportedMultiFileDirectory.GetPath() / "testMultiFileManifest.yaml"; - YamlWriter::OutputYamlFile(multiFileManifest, multiFileManifest.Installers[0], generatedMultiFileManifestPath); - - REQUIRE(std::filesystem::exists(generatedMultiFileManifestPath)); - Manifest generatedMultiFileManifest = YamlParser::CreateFromPath(exportedMultiFileDirectory); - VerifyV1ManifestContent(generatedMultiFileManifest, false, ManifestVer{ s_ManifestVersionV1_10 }, true); -} +WINGET_WRITE_VERIFY_MANIFEST_VERSION(1) +WINGET_WRITE_VERIFY_MANIFEST_VERSION(1_1) +WINGET_WRITE_VERIFY_MANIFEST_VERSION(1_2) +WINGET_WRITE_VERIFY_MANIFEST_VERSION(1_4) +WINGET_WRITE_VERIFY_MANIFEST_VERSION(1_5) +WINGET_WRITE_VERIFY_MANIFEST_VERSION(1_6) +WINGET_WRITE_VERIFY_MANIFEST_VERSION(1_7) +WINGET_WRITE_VERIFY_MANIFEST_VERSION(1_9) +WINGET_WRITE_VERIFY_MANIFEST_VERSION(1_10) +WINGET_WRITE_VERIFY_MANIFEST_VERSION(1_12) +WINGET_WRITE_VERIFY_MANIFEST_VERSION(1_28) // Since Authentication is not supported in community repo and will cause manifest validation failure, // we are not adding Authentication in v1_10 manifests. Instead a separate test is created for Authentication. -TEST_CASE("ReadWriteValidateV1_10ManifestWithInstallerAuthentication", "[ManifestValidation]") +TEST_CASE("ReadWriteValidateV1_10ManifestWithInstallerAuthentication", "[ManifestCreation][ManifestVersionCreation]") { // Read manifest TempDirectory testDirectory{ "TestManifest" }; @@ -1588,70 +1132,8 @@ TEST_CASE("ReadWriteValidateV1_10ManifestWithInstallerAuthentication", "[Manifes REQUIRE(exportedManifest.Installers[0].AuthInfo.MicrosoftEntraIdInfo->Scope.empty()); } -TEST_CASE("WriteV1_12SingletonManifestAndVerifyContents", "[ManifestCreation]") -{ - TempDirectory singletonDirectory{ "SingletonManifest" }; - CopyTestDataFilesToFolder({ "ManifestV1_12-Singleton.yaml" }, singletonDirectory); - Manifest singletonManifest = YamlParser::CreateFromPath(singletonDirectory); - - TempDirectory exportedSingletonDirectory{ "exportedSingleton" }; - std::filesystem::path generatedSingletonManifestPath = exportedSingletonDirectory.GetPath() / "testSingletonManifest.yaml"; - YamlWriter::OutputYamlFile(singletonManifest, singletonManifest.Installers[0], generatedSingletonManifestPath); - - REQUIRE(std::filesystem::exists(generatedSingletonManifestPath)); - Manifest generatedSingletonManifest = YamlParser::CreateFromPath(exportedSingletonDirectory); - VerifyV1ManifestContent(generatedSingletonManifest, true, ManifestVer{ s_ManifestVersionV1_12 }, true); - - TempDirectory multiFileDirectory{ "MultiFileManifest" }; - CopyTestDataFilesToFolder({ - "ManifestV1_12-MultiFile-Version.yaml", - "ManifestV1_12-MultiFile-Installer.yaml", - "ManifestV1_12-MultiFile-DefaultLocale.yaml", - "ManifestV1_12-MultiFile-Locale.yaml" }, multiFileDirectory); - - Manifest multiFileManifest = YamlParser::CreateFromPath(multiFileDirectory); - TempDirectory exportedMultiFileDirectory{ "exportedMultiFile" }; - std::filesystem::path generatedMultiFileManifestPath = exportedMultiFileDirectory.GetPath() / "testMultiFileManifest.yaml"; - YamlWriter::OutputYamlFile(multiFileManifest, multiFileManifest.Installers[0], generatedMultiFileManifestPath); - - REQUIRE(std::filesystem::exists(generatedMultiFileManifestPath)); - Manifest generatedMultiFileManifest = YamlParser::CreateFromPath(exportedMultiFileDirectory); - VerifyV1ManifestContent(generatedMultiFileManifest, false, ManifestVer{ s_ManifestVersionV1_12 }, true); -} - -TEST_CASE("WriteV1_28SingletonManifestAndVerifyContents", "[ManifestCreation]") -{ - TempDirectory singletonDirectory{ "SingletonManifest" }; - CopyTestDataFilesToFolder({ "ManifestV1_28-Singleton.yaml" }, singletonDirectory); - Manifest singletonManifest = YamlParser::CreateFromPath(singletonDirectory); - - TempDirectory exportedSingletonDirectory{ "exportedSingleton" }; - std::filesystem::path generatedSingletonManifestPath = exportedSingletonDirectory.GetPath() / "testSingletonManifest.yaml"; - YamlWriter::OutputYamlFile(singletonManifest, singletonManifest.Installers[0], generatedSingletonManifestPath); - - REQUIRE(std::filesystem::exists(generatedSingletonManifestPath)); - Manifest generatedSingletonManifest = YamlParser::CreateFromPath(exportedSingletonDirectory); - VerifyV1ManifestContent(generatedSingletonManifest, true, ManifestVer{ s_ManifestVersionV1_28 }, true); - - TempDirectory multiFileDirectory{ "MultiFileManifest" }; - CopyTestDataFilesToFolder({ - "ManifestV1_28-MultiFile-Version.yaml", - "ManifestV1_28-MultiFile-Installer.yaml", - "ManifestV1_28-MultiFile-DefaultLocale.yaml", - "ManifestV1_28-MultiFile-Locale.yaml" }, multiFileDirectory); - - Manifest multiFileManifest = YamlParser::CreateFromPath(multiFileDirectory); - TempDirectory exportedMultiFileDirectory{ "exportedMultiFile" }; - std::filesystem::path generatedMultiFileManifestPath = exportedMultiFileDirectory.GetPath() / "testMultiFileManifest.yaml"; - YamlWriter::OutputYamlFile(multiFileManifest, multiFileManifest.Installers[0], generatedMultiFileManifestPath); - - REQUIRE(std::filesystem::exists(generatedMultiFileManifestPath)); - Manifest generatedMultiFileManifest = YamlParser::CreateFromPath(exportedMultiFileDirectory); - VerifyV1ManifestContent(generatedMultiFileManifest, false, ManifestVer{ s_ManifestVersionV1_28 }, true); -} - // PowerShell DSC is not supported in the community repo and will cause manifest validation failure. -TEST_CASE("ReadWriteValidateV1_28ManifestWithPowerShellDSC", "[ManifestValidation]") +TEST_CASE("ReadWriteValidateV1_28ManifestWithPowerShellDSC", "[ManifestCreation][ManifestVersionCreation]") { // Read manifest TempDirectory testDirectory{ "TestManifest" }; diff --git a/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h b/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h index 009ff5603a..96b7affb01 100644 --- a/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h +++ b/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h @@ -402,8 +402,12 @@ namespace AppInstaller::Manifest Type(DesiredStateConfigurationContainerType::DSCv3), Resources(std::move(resources)) {} DesiredStateConfigurationContainerType Type; + + // For Type == PowerShell string_t RepositoryURL; string_t ModuleName; + + // For all types std::vector Resources; }; diff --git a/src/AppInstallerCommonCore/Public/winget/ManifestYamlPopulator.h b/src/AppInstallerCommonCore/Public/winget/ManifestYamlPopulator.h index a89be5e0ee..f7e32c5592 100644 --- a/src/AppInstallerCommonCore/Public/winget/ManifestYamlPopulator.h +++ b/src/AppInstallerCommonCore/Public/winget/ManifestYamlPopulator.h @@ -13,6 +13,8 @@ namespace AppInstaller::Manifest AppsAndFeaturesEntry*, Dependency*, DependencyList*, + DesiredStateConfigurationContainerInfo*, + DesiredStateConfigurationResourceInfo*, Documentation*, ExpectedReturnCode*, Icon*, @@ -23,13 +25,10 @@ namespace AppInstaller::Manifest ManifestLocalization*, MarketsInfo*, NestedInstallerFile*, - std::map*, AppInstaller::Authentication::AuthenticationInfo*, AppInstaller::Authentication::MicrosoftEntraIdAuthenticationInfo*, - std::vector*, - DesiredStateConfigurationContainerInfo*, - DesiredStateConfigurationResourceInfo* + std::map*, + std::vector* >; struct ManifestYamlPopulator diff --git a/src/ManifestSchema/ManifestSchema.h b/src/ManifestSchema/ManifestSchema.h index f6d513cb69..25a2fa9af8 100644 --- a/src/ManifestSchema/ManifestSchema.h +++ b/src/ManifestSchema/ManifestSchema.h @@ -74,3 +74,7 @@ #define IDX_MANIFEST_SCHEMA_V1_28_INSTALLER 254 #define IDX_MANIFEST_SCHEMA_V1_28_DEFAULTLOCALE 255 #define IDX_MANIFEST_SCHEMA_V1_28_LOCALE 256 + +// Packages schema starts at 300 +// Certificates start at 400 +// If we get to 300, either move the others or skip over them From c118257a3789b5f7b132d34a9ef464427abfa39e Mon Sep 17 00:00:00 2001 From: John McPherson Date: Thu, 22 Jan 2026 11:31:29 -0800 Subject: [PATCH 8/8] spelling --- schemas/JSON/manifests/latest/manifest.installer.latest.json | 2 +- schemas/JSON/manifests/latest/manifest.singleton.latest.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/schemas/JSON/manifests/latest/manifest.installer.latest.json b/schemas/JSON/manifests/latest/manifest.installer.latest.json index 1938bae9e6..5442e77897 100644 --- a/schemas/JSON/manifests/latest/manifest.installer.latest.json +++ b/schemas/JSON/manifests/latest/manifest.installer.latest.json @@ -658,7 +658,7 @@ "ModuleName": { "type": "string", "description": "The name of the module containing resources.", - "$comment": "From nuget package id, although PowerShell convetion is slightle more strict: https://learn.microsoft.com/en-us/nuget/reference/errors-and-warnings/nu1017", + "$comment": "From nuget package id, although PowerShell convention is slightly more strict: https://learn.microsoft.com/en-us/nuget/reference/errors-and-warnings/nu1017", "pattern": "^\\w+([.-]\\w+)*$", "maxLength": 100 }, diff --git a/schemas/JSON/manifests/latest/manifest.singleton.latest.json b/schemas/JSON/manifests/latest/manifest.singleton.latest.json index 900cd3942a..413f523c22 100644 --- a/schemas/JSON/manifests/latest/manifest.singleton.latest.json +++ b/schemas/JSON/manifests/latest/manifest.singleton.latest.json @@ -759,7 +759,7 @@ "ModuleName": { "type": "string", "description": "The name of the module containing resources.", - "$comment": "From nuget package id, although PowerShell convetion is slightle more strict: https://learn.microsoft.com/en-us/nuget/reference/errors-and-warnings/nu1017", + "$comment": "From nuget package id, although PowerShell convention is slightly more strict: https://learn.microsoft.com/en-us/nuget/reference/errors-and-warnings/nu1017", "pattern": "^\\w+([.-]\\w+)*$", "maxLength": 100 },