diff --git a/.github/actions/spelling/allow.txt b/.github/actions/spelling/allow.txt
index 595ea15c4f..796b923545 100644
--- a/.github/actions/spelling/allow.txt
+++ b/.github/actions/spelling/allow.txt
@@ -261,6 +261,7 @@ OSVERSIONINFOEXW
outfile
OUTOFMEMORY
OWC
+PACKAGESSCHEMA
Params
parentidx
pathpart
diff --git a/.github/actions/spelling/patterns.txt b/.github/actions/spelling/patterns.txt
index 6cf1760181..74ab869cb3 100644
--- a/.github/actions/spelling/patterns.txt
+++ b/.github/actions/spelling/patterns.txt
@@ -32,4 +32,4 @@ El proyecto .* diferentes
http://rfc3161.gtm.corp.microsoft.com/TSS/HttpTspServer
# schema regex
-"pattern": .*$
\ No newline at end of file
+"pattern": .*$
diff --git a/src/AppInstallerCLI.sln b/src/AppInstallerCLI.sln
index 5a2fbaf601..2b0bfe49db 100644
--- a/src/AppInstallerCLI.sln
+++ b/src/AppInstallerCLI.sln
@@ -17,10 +17,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Project", "Project", "{8D53
..\azure-pipelines.loc.yml = ..\azure-pipelines.loc.yml
..\azure-pipelines.yml = ..\azure-pipelines.yml
..\cgmanifest.json = ..\cgmanifest.json
- ..\doc\packages.schema.json = ..\doc\packages.schema.json
..\README.md = ..\README.md
..\doc\Settings.md = ..\doc\Settings.md
- ..\doc\settings.schema.json = ..\doc\settings.schema.json
EndProjectSection
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "catch2", "catch2\catch2.vcxitems", "{5295E21E-9868-4DE2-A177-FBB97B36579B}"
@@ -68,6 +66,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Valijson", "Valijson\Valijs
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ManifestSchema", "ManifestSchema\ManifestSchema.vcxitems", "{7D05F64D-CE5A-42AA-A2C1-E91458F061CF}"
EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "WinGetSchemas", "WinGetSchemas\WinGetSchemas.vcxitems", "{952B513F-8A00-4D74-9271-925AFB3C6252}"
+EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "spelling", "spelling", "{2ACDE176-F13F-42FA-8159-C34FA3D37837}"
ProjectSection(SolutionItems) = preProject
..\.github\actions\spelling\allow.txt = ..\.github\actions\spelling\allow.txt
@@ -79,16 +79,21 @@ EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
ManifestSchema\ManifestSchema.vcxitems*{1622da16-914f-4f57-a259-d5169003cc8c}*SharedItemsImports = 4
+ Valijson\Valijson.vcxitems*{1c6e0108-2860-4b17-9f7e-fa5c6c1f3d3d}*SharedItemsImports = 4
+ WinGetSchemas\WinGetSchemas.vcxitems*{1c6e0108-2860-4b17-9f7e-fa5c6c1f3d3d}*SharedItemsImports = 4
Valijson\Valijson.vcxitems*{358bc478-0624-4ad1-a933-0422b5292af8}*SharedItemsImports = 9
catch2\catch2.vcxitems*{5295e21e-9868-4de2-a177-fbb97b36579b}*SharedItemsImports = 9
ManifestSchema\ManifestSchema.vcxitems*{5890d6ed-7c3b-40f3-b436-b54f640d9e65}*SharedItemsImports = 4
Valijson\Valijson.vcxitems*{5890d6ed-7c3b-40f3-b436-b54f640d9e65}*SharedItemsImports = 4
binver\binver.vcxitems*{5b6f90df-fd19-4bae-83d9-24dad128e777}*SharedItemsImports = 4
ManifestSchema\ManifestSchema.vcxitems*{5b6f90df-fd19-4bae-83d9-24dad128e777}*SharedItemsImports = 4
+ WinGetSchemas\WinGetSchemas.vcxitems*{5b6f90df-fd19-4bae-83d9-24dad128e777}*SharedItemsImports = 4
binver\binver.vcxitems*{6e36ddd7-1602-474e-b1d7-d0a7e1d5ad86}*SharedItemsImports = 9
ManifestSchema\ManifestSchema.vcxitems*{7d05f64d-ce5a-42aa-a2c1-e91458f061cf}*SharedItemsImports = 9
catch2\catch2.vcxitems*{89b1aab4-2bbc-4b65-9ed7-a01d5cf88230}*SharedItemsImports = 4
ManifestSchema\ManifestSchema.vcxitems*{89b1aab4-2bbc-4b65-9ed7-a01d5cf88230}*SharedItemsImports = 4
+ WinGetSchemas\WinGetSchemas.vcxitems*{89b1aab4-2bbc-4b65-9ed7-a01d5cf88230}*SharedItemsImports = 4
+ WinGetSchemas\WinGetSchemas.vcxitems*{952b513f-8a00-4d74-9271-925afb3c6252}*SharedItemsImports = 9
binver\binver.vcxitems*{fb313532-38b0-4676-9303-ab200aa13576}*SharedItemsImports = 4
ManifestSchema\ManifestSchema.vcxitems*{fb313532-38b0-4676-9303-ab200aa13576}*SharedItemsImports = 4
EndGlobalSection
@@ -437,6 +442,7 @@ Global
{3BAF989F-7F65-465B-ACE8-BAFE42D1017E} = {EA8CD934-0702-4911-A2C5-A40600E616DE}
{358BC478-0624-4AD1-A933-0422B5292AF8} = {60618CAC-2995-4DF9-9914-45C6FC02C995}
{7D05F64D-CE5A-42AA-A2C1-E91458F061CF} = {8D53D749-D51C-46F8-A162-9371AAA6C2E7}
+ {952B513F-8A00-4D74-9271-925AFB3C6252} = {8D53D749-D51C-46F8-A162-9371AAA6C2E7}
{2ACDE176-F13F-42FA-8159-C34FA3D37837} = {8D53D749-D51C-46F8-A162-9371AAA6C2E7}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
diff --git a/src/AppInstallerCLI/AppInstallerCLI.vcxproj b/src/AppInstallerCLI/AppInstallerCLI.vcxproj
index bca2ba6d58..cdfa718e9e 100644
--- a/src/AppInstallerCLI/AppInstallerCLI.vcxproj
+++ b/src/AppInstallerCLI/AppInstallerCLI.vcxproj
@@ -71,6 +71,7 @@
+
diff --git a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj
index 4f49b21a6b..d99b7b790f 100644
--- a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj
+++ b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj
@@ -68,7 +68,10 @@
-
+
+
+
+
@@ -122,9 +125,9 @@
Disabled
_DEBUG;%(PreprocessorDefinitions);CLICOREDLLBUILD
- $(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\JsonCppLib\json;%(AdditionalIncludeDirectories)
- $(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\JsonCppLib\json;%(AdditionalIncludeDirectories)
- $(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\JsonCppLib\json;%(AdditionalIncludeDirectories)
+ $(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\JsonCppLib\json;$(ProjectDir)..\JsonCppLib;%(AdditionalIncludeDirectories)
+ $(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\JsonCppLib\json;$(ProjectDir)..\JsonCppLib;%(AdditionalIncludeDirectories)
+ $(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\JsonCppLib\json;$(ProjectDir)..\JsonCppLib;%(AdditionalIncludeDirectories)
true
true
true
@@ -139,7 +142,7 @@
WIN32;%(PreprocessorDefinitions);CLICOREDLLBUILD
- $(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\JsonCppLib\json;%(AdditionalIncludeDirectories)
+ $(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\JsonCppLib\json;$(ProjectDir)..\JsonCppLib;%(AdditionalIncludeDirectories)
true
@@ -152,10 +155,10 @@
true
true
NDEBUG;%(PreprocessorDefinitions);CLICOREDLLBUILD
- $(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\JsonCppLib\json;%(AdditionalIncludeDirectories)
- $(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\JsonCppLib\json;%(AdditionalIncludeDirectories)
- $(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\JsonCppLib\json;%(AdditionalIncludeDirectories)
- $(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\JsonCppLib\json;%(AdditionalIncludeDirectories)
+ $(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\JsonCppLib\json;$(ProjectDir)..\JsonCppLib;%(AdditionalIncludeDirectories)
+ $(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\JsonCppLib\json;$(ProjectDir)..\JsonCppLib;%(AdditionalIncludeDirectories)
+ $(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\JsonCppLib\json;$(ProjectDir)..\JsonCppLib;%(AdditionalIncludeDirectories)
+ $(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\JsonCppLib\json;$(ProjectDir)..\JsonCppLib;%(AdditionalIncludeDirectories)
true
true
true
diff --git a/src/AppInstallerCLICore/PackageCollection.cpp b/src/AppInstallerCLICore/PackageCollection.cpp
index bfcba49b44..072bb05ffd 100644
--- a/src/AppInstallerCLICore/PackageCollection.cpp
+++ b/src/AppInstallerCLICore/PackageCollection.cpp
@@ -3,7 +3,11 @@
#include "pch.h"
#include "PackageCollection.h"
+
#include "AppInstallerRuntime.h"
+#include "winget/JsonSchemaValidation.h"
+
+#include "PackagesSchema.h"
#include
#include
@@ -106,7 +110,7 @@ namespace AppInstaller::CLI
if (!version.empty())
{
packageNode[s_PackagesJson_Package_Version] = version;
- }
+ }
const std::string& channel = package.VersionAndChannel.GetChannel().ToString();
if (!channel.empty())
@@ -122,7 +126,6 @@ namespace AppInstaller::CLI
{
Json::Value sourceNode{ Json::ValueType::objectValue };
-
Json::Value sourceDetailsNode{ Json::ValueType::objectValue };
sourceDetailsNode[s_PackagesJson_Source_Name] = source.Details.Name;
sourceDetailsNode[s_PackagesJson_Source_Argument] = source.Details.Arg;
@@ -155,10 +158,38 @@ namespace AppInstaller::CLI
return root;
}
- std::optional TryParseJson(const Json::Value& root)
+ ParseResult TryParseJson(const Json::Value& root)
{
- // TODO: Embed schema in binaries & validate file. This will return nullopt on failure.
+ // Find the schema used for the JSON
+ if (!(root.isObject() && root.isMember(s_PackagesJson_Schema) && root[s_PackagesJson_Schema].isString()))
+ {
+ AICLI_LOG(CLI, Error, << "Import file is missing \"" << s_PackagesJson_Schema << "\" property");
+ return ParseResult{ ParseResult::Type::MissingSchema };
+ }
+
+ const auto& schemaUri = root[s_PackagesJson_Schema].asString();
+ Json::Value schemaJson;
+ if (schemaUri == s_PackagesJson_SchemaUri_v1_0)
+ {
+ schemaJson = JsonSchema::LoadResourceAsSchemaDoc(MAKEINTRESOURCE(IDX_PACKAGES_SCHEMA_V1), MAKEINTRESOURCE(PACKAGESSCHEMA_RESOURCE_TYPE));
+ }
+ else
+ {
+ AICLI_LOG(CLI, Error, << "Unrecognized schema for import file: " << schemaUri);
+ return ParseResult{ ParseResult::Type::UnrecognizedSchema };
+ }
+
+ // Validate the JSON against the schema.
+ valijson::Schema schema;
+ JsonSchema::PopulateSchema(schemaJson, schema);
+
+ valijson::ValidationResults results;
+ if (!JsonSchema::Validate(schema, root, results))
+ {
+ return ParseResult{ ParseResult::Type::SchemaValidationFailed, JsonSchema::GetErrorStringFromResults(results) };
+ }
+ // Extract the data from the JSON.
PackageCollection packages;
packages.ClientVersion = root[s_PackagesJson_WinGetVersion].asString();
for (const auto& sourceNode : root[s_PackagesJson_Sources])
@@ -175,7 +206,7 @@ namespace AppInstaller::CLI
}
}
- return packages;
+ return ParseResult{ std::move(packages) };
}
}
}
\ No newline at end of file
diff --git a/src/AppInstallerCLICore/PackageCollection.h b/src/AppInstallerCLICore/PackageCollection.h
index 29b3d6d3e4..3dd53e7161 100644
--- a/src/AppInstallerCLICore/PackageCollection.h
+++ b/src/AppInstallerCLICore/PackageCollection.h
@@ -53,9 +53,29 @@ namespace AppInstaller::CLI
namespace PackagesJson
{
+ struct ParseResult
+ {
+ enum class Type
+ {
+ MissingSchema,
+ UnrecognizedSchema,
+ SchemaValidationFailed,
+ Success,
+ };
+
+ ParseResult(Type result) : Result(result) {}
+ ParseResult(Type result, std::string_view errors) : Result(result), Errors(errors) {}
+ ParseResult(PackageCollection&& packages) : Result(Type::Success), Packages(std::move(packages)) {}
+
+ Type Result;
+ PackageCollection Packages;
+ std::string Errors;
+ };
+
// Converts a collection of packages to its JSON representation for exporting.
Json::Value CreateJson(const PackageCollection& packages);
- std::optional TryParseJson(const Json::Value& root);
+ // Tries to parse a JSON into a collection of packages.
+ ParseResult TryParseJson(const Json::Value& root);
}
}
\ No newline at end of file
diff --git a/src/AppInstallerCLICore/Resources.h b/src/AppInstallerCLICore/Resources.h
index a77af3c85f..7e63e21377 100644
--- a/src/AppInstallerCLICore/Resources.h
+++ b/src/AppInstallerCLICore/Resources.h
@@ -75,6 +75,7 @@ namespace AppInstaller::CLI::Resource
WINGET_DEFINE_RESOURCE_STRINGID(ImportCommandLongDescription);
WINGET_DEFINE_RESOURCE_STRINGID(ImportCommandShortDescription);
WINGET_DEFINE_RESOURCE_STRINGID(ImportFileArgumentDescription);
+ WINGET_DEFINE_RESOURCE_STRINGID(ImportFileHasInvalidSchema);
WINGET_DEFINE_RESOURCE_STRINGID(ImportIgnorePackageVersionsArgumentDescription);
WINGET_DEFINE_RESOURCE_STRINGID(ImportIgnoreUnavailableArgumentDescription);
WINGET_DEFINE_RESOURCE_STRINGID(ImportInstallFailed);
diff --git a/src/AppInstallerCLICore/Workflows/ImportExportFlow.cpp b/src/AppInstallerCLICore/Workflows/ImportExportFlow.cpp
index 2869c42599..bf04ded8e2 100644
--- a/src/AppInstallerCLICore/Workflows/ImportExportFlow.cpp
+++ b/src/AppInstallerCLICore/Workflows/ImportExportFlow.cpp
@@ -169,14 +169,25 @@ namespace AppInstaller::CLI::Workflow
AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_JSON_INVALID_FILE);
}
- auto packages = PackagesJson::TryParseJson(jsonRoot);
- if (!packages.has_value())
+ PackagesJson::ParseResult parseResult = PackagesJson::TryParseJson(jsonRoot);
+ if (parseResult.Result != PackagesJson::ParseResult::Type::Success)
{
context.Reporter.Error() << Resource::String::InvalidJsonFile << std::endl;
+ if (parseResult.Result == PackagesJson::ParseResult::Type::MissingSchema ||
+ parseResult.Result == PackagesJson::ParseResult::Type::UnrecognizedSchema)
+ {
+ context.Reporter.Error() << Resource::String::ImportFileHasInvalidSchema << std::endl;
+ }
+ else if (parseResult.Result == PackagesJson::ParseResult::Type::SchemaValidationFailed)
+ {
+ context.Reporter.Error() << parseResult.Errors << std::endl;
+ }
+
AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_JSON_INVALID_FILE);
}
- if (packages->Sources.empty())
+ PackageCollection& packages = parseResult.Packages;
+ if (packages.Sources.empty())
{
AICLI_LOG(CLI, Warning, << "No packages to install");
context.Reporter.Info() << Resource::String::NoPackagesFoundInImportFile << std::endl;
@@ -186,7 +197,7 @@ namespace AppInstaller::CLI::Workflow
if (context.Args.Contains(Execution::Args::Type::IgnoreVersions))
{
// Strip out all the version information as we don't need it.
- for (auto& source : packages->Sources)
+ for (auto& source : packages.Sources)
{
for (auto& package : source.Packages)
{
@@ -195,7 +206,7 @@ namespace AppInstaller::CLI::Workflow
}
}
- context.Add(packages.value());
+ context.Add(std::move(packages));
}
void OpenSourcesForImport(Execution::Context& context)
diff --git a/src/AppInstallerCLICore/pch.h b/src/AppInstallerCLICore/pch.h
index ef55d0a8c8..e62d38f92a 100644
--- a/src/AppInstallerCLICore/pch.h
+++ b/src/AppInstallerCLICore/pch.h
@@ -7,6 +7,14 @@
#include
#include
+#pragma warning( push )
+#pragma warning ( disable : 4458 4100 4702 )
+#include
+#include
+#include
+#include
+#pragma warning( pop )
+
#include
#include
#include
diff --git a/src/AppInstallerCLIE2ETests/ImportCommand.cs b/src/AppInstallerCLIE2ETests/ImportCommand.cs
index 912c7cc083..15e724140f 100644
--- a/src/AppInstallerCLIE2ETests/ImportCommand.cs
+++ b/src/AppInstallerCLIE2ETests/ImportCommand.cs
@@ -32,7 +32,7 @@ public void ImportSuccessful()
}
// Ignore while we don't have schema validation
- // [Test]
+ [Test]
public void ImportInvalidFile()
{
// Verify failure when trying to import with an invalid file
diff --git a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw
index 8f27b2687d..933e2d9097 100644
--- a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw
+++ b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw
@@ -814,4 +814,7 @@ They can be configured through the settings file 'winget settings'.
Path does not exist:
+
+ The JSON file does not specify a recognized schema.
+
\ No newline at end of file
diff --git a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj
index f14a5bd015..a32b767806 100644
--- a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj
+++ b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj
@@ -55,6 +55,7 @@
+
diff --git a/src/AppInstallerCLITests/PackageCollection.cpp b/src/AppInstallerCLITests/PackageCollection.cpp
index ce549d562e..32614bfd3a 100644
--- a/src/AppInstallerCLITests/PackageCollection.cpp
+++ b/src/AppInstallerCLITests/PackageCollection.cpp
@@ -195,8 +195,9 @@ TEST_CASE("PackageCollection_Read_SingleSource", "[PackageCollection]")
"WinGetVersion": "1.0.0"
})");
- auto parsed = PackagesJson::TryParseJson(json);
- REQUIRE(parsed.has_value());
+ auto parseResult = PackagesJson::TryParseJson(json);
+ REQUIRE(parseResult.Result == PackagesJson::ParseResult::Type::Success);
+ REQUIRE(parseResult.Errors.empty());
PackageCollection::Source source;
source.Details.Name = "TestSource";
@@ -213,7 +214,7 @@ TEST_CASE("PackageCollection_Read_SingleSource", "[PackageCollection]")
std::vector{ source }
};
- ValidateEqualCollections(parsed.value(), expected);
+ ValidateEqualCollections(parseResult.Packages, expected);
}
TEST_CASE("PackageCollection_Read_MultipleSources", "[PackageCollection]")
@@ -254,8 +255,10 @@ TEST_CASE("PackageCollection_Read_MultipleSources", "[PackageCollection]")
]
})");
- auto parsed = PackagesJson::TryParseJson(json);
- REQUIRE(parsed.has_value());
+
+ auto parseResult = PackagesJson::TryParseJson(json);
+ REQUIRE(parseResult.Result == PackagesJson::ParseResult::Type::Success);
+ REQUIRE(parseResult.Errors.empty());
PackageCollection::Source source1;
source1.Details.Name = "First";
@@ -277,7 +280,7 @@ TEST_CASE("PackageCollection_Read_MultipleSources", "[PackageCollection]")
std::vector{ source1, source2 }
};
- ValidateEqualCollections(parsed.value(), expected);
+ ValidateEqualCollections(parseResult.Packages, expected);
}
TEST_CASE("PackageCollection_Read_RepeatedSource", "[PackageCollection]")
@@ -332,8 +335,10 @@ TEST_CASE("PackageCollection_Read_RepeatedSource", "[PackageCollection]")
]
})");
- auto parsed = PackagesJson::TryParseJson(json);
- REQUIRE(parsed.has_value());
+
+ auto parseResult = PackagesJson::TryParseJson(json);
+ REQUIRE(parseResult.Result == PackagesJson::ParseResult::Type::Success);
+ REQUIRE(parseResult.Errors.empty());
PackageCollection::Source source1;
source1.Details.Name = "First";
@@ -356,5 +361,96 @@ TEST_CASE("PackageCollection_Read_RepeatedSource", "[PackageCollection]")
std::vector{ source1, source2 }
};
- ValidateEqualCollections(parsed.value(), expected);
+ ValidateEqualCollections(parseResult.Packages, expected);
+}
+
+TEST_CASE("PackageCollection_Read_MissingSchema", "[PackageCollection]")
+{
+ auto json = ParseJsonString(R"(
+ {
+ "CreationDate": "2021-01-01T12:00:00.000",
+ "Sources": [
+ {
+ "Packages": [
+ {
+ "Id": "test.test"
+ }
+ ],
+ "SourceDetails": {
+ "Argument": "https://aka.ms/winget",
+ "Identifier": "TestSourceId",
+ "Name": "TestSource",
+ "Type": "Microsoft.PreIndexed.Package"
+ }
+ }
+ ],
+ "WinGetVersion": "1.0.0"
+ })");
+
+ auto parseResult = PackagesJson::TryParseJson(json);
+ REQUIRE(parseResult.Result == PackagesJson::ParseResult::Type::MissingSchema);
+
+ json = ParseJsonString("\"Not even a JSON object\"");
+
+ parseResult = PackagesJson::TryParseJson(json);
+ REQUIRE(parseResult.Result == PackagesJson::ParseResult::Type::MissingSchema);
+}
+
+TEST_CASE("PackageCollection_Read_WrongSchema", "[PackageCollection]")
+{
+ auto json = ParseJsonString(R"(
+ {
+ "$schema": "https://aka.ms/winget-settings.schema.json",
+ "CreationDate": "2021-01-01T12:00:00.000",
+ "Sources": [
+ {
+ "Packages": [
+ {
+ "Id": "test.test"
+ }
+ ],
+ "SourceDetails": {
+ "Argument": "https://aka.ms/winget",
+ "Identifier": "TestSourceId",
+ "Name": "TestSource",
+ "Type": "Microsoft.PreIndexed.Package"
+ }
+ }
+ ],
+ "WinGetVersion": "1.0.0"
+ })");
+
+ auto parseResult = PackagesJson::TryParseJson(json);
+ REQUIRE(parseResult.Result == PackagesJson::ParseResult::Type::UnrecognizedSchema);
+}
+
+TEST_CASE("PackageCollection_Read_SchemaValidationFail", "[PackageCollection]")
+{
+ auto json = ParseJsonString(R"(
+ {
+ "$schema": "https://aka.ms/winget-packages.schema.1.0.json",
+ "CreationDate": "2021-01-01T12:00:00.000",
+ "NotSources": [
+ {
+ "Packages": [
+ {
+ "Id": "test.test"
+ }
+ ],
+ "SourceDetails": {
+ "Argument": "https://aka.ms/winget",
+ "Identifier": "TestSourceId",
+ "Name": "TestSource",
+ "Type": "Microsoft.PreIndexed.Package"
+ }
+ }
+ ],
+ "WinGetVersion": "1.0.0"
+ })");
+
+ auto parseResult = PackagesJson::TryParseJson(json);
+ INFO(parseResult.Errors);
+
+ REQUIRE(parseResult.Result == PackagesJson::ParseResult::Type::SchemaValidationFailed);
+ REQUIRE(parseResult.Errors.find("Missing required property 'Sources'.") != std::string::npos);
}
\ No newline at end of file
diff --git a/src/AppInstallerCLITests/WorkFlow.cpp b/src/AppInstallerCLITests/WorkFlow.cpp
index 47711e7bc0..81863b7bfc 100644
--- a/src/AppInstallerCLITests/WorkFlow.cpp
+++ b/src/AppInstallerCLITests/WorkFlow.cpp
@@ -1313,14 +1313,11 @@ TEST_CASE("ImportFlow_InvalidJsonFile", "[ImportFlow][workflow]")
context.Args.AddArg(Execution::Args::Type::ImportFile, TestDataFile("ImportFile-Bad-Invalid.json").GetPath().string());
ImportCommand importCommand({});
- // TODO: Enable when we have schema validation
- /*
importCommand.Execute(context);
INFO(importOutput.str());
// Command should have failed
REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_JSON_INVALID_FILE);
- */
}
void VerifyMotw(const std::filesystem::path& testFile, DWORD zone)
diff --git a/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj b/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj
index 62360f8bd9..c298f0310e 100644
--- a/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj
+++ b/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj
@@ -267,6 +267,7 @@
+
@@ -310,6 +311,7 @@
true
+
diff --git a/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj.filters b/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj.filters
index b547e5f2d9..c3ebb155ce 100644
--- a/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj.filters
+++ b/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj.filters
@@ -156,6 +156,9 @@
Public\winget
+
+ Public\winget
+
@@ -263,6 +266,9 @@
Manifest
+
+ Source Files
+
diff --git a/src/AppInstallerCommonCore/JsonSchemaValidation.cpp b/src/AppInstallerCommonCore/JsonSchemaValidation.cpp
new file mode 100644
index 0000000000..f0a57ac7a6
--- /dev/null
+++ b/src/AppInstallerCommonCore/JsonSchemaValidation.cpp
@@ -0,0 +1,89 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+#include "pch.h"
+#include "winget/JsonSchemaValidation.h"
+
+namespace AppInstaller::JsonSchema
+{
+ std::string LoadResourceAsString(PCWSTR resourceName, PCWSTR resourceType)
+ {
+ HMODULE resourceModule = NULL;
+ GetModuleHandleEx(
+ GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS,
+ (PCWSTR)LoadResourceAsString,
+ &resourceModule);
+ THROW_LAST_ERROR_IF_NULL(resourceModule);
+
+ HRSRC resourceInfoHandle = FindResource(resourceModule, resourceName, resourceType);
+ THROW_LAST_ERROR_IF_NULL(resourceInfoHandle);
+
+ HGLOBAL resourceMemoryHandle = LoadResource(resourceModule, resourceInfoHandle);
+ THROW_LAST_ERROR_IF_NULL(resourceMemoryHandle);
+
+ ULONG resourceSize = 0;
+ char* resourceContent = NULL;
+ resourceSize = SizeofResource(resourceModule, resourceInfoHandle);
+ THROW_LAST_ERROR_IF(resourceSize == 0);
+
+ resourceContent = reinterpret_cast(LockResource(resourceMemoryHandle));
+ THROW_HR_IF_NULL(E_UNEXPECTED, resourceContent);
+
+ std::string resourceStr;
+ resourceStr.assign(resourceContent, resourceSize);
+
+ return resourceStr;
+ }
+
+ Json::Value LoadSchemaDoc(const std::string& schemaStr)
+ {
+ Json::Value schemaJson;
+ int schemaLength = static_cast(schemaStr.length());
+ Json::CharReaderBuilder charReaderBuilder;
+ const std::unique_ptr jsonReader(charReaderBuilder.newCharReader());
+ std::string errorMsg;
+ if (!jsonReader->parse(schemaStr.c_str(), schemaStr.c_str() + schemaLength, &schemaJson, &errorMsg)) {
+ THROW_HR_MSG(E_UNEXPECTED, "Jsoncpp parser failed to parse the schema doc. Reason: %s", errorMsg.c_str());
+ }
+
+ return schemaJson;
+ }
+
+ Json::Value LoadResourceAsSchemaDoc(PCWSTR resourceName, PCWSTR resourceType)
+ {
+ return LoadSchemaDoc(LoadResourceAsString(resourceName, resourceType));
+ }
+
+ void PopulateSchema(const Json::Value& schemaJson, valijson::Schema& schema)
+ {
+ valijson::SchemaParser schemaParser;
+ valijson::adapters::JsonCppAdapter jsonSchemaAdapter(schemaJson);
+ schemaParser.populateSchema(jsonSchemaAdapter, schema);
+ }
+
+ bool Validate(const valijson::Schema& schema, const Json::Value& json, valijson::ValidationResults& results)
+ {
+ valijson::Validator schemaValidator;
+ valijson::adapters::JsonCppAdapter jsonAdapter(json);
+ return schemaValidator.validate(schema, jsonAdapter, &results);
+ }
+
+ std::string GetErrorStringFromResults(valijson::ValidationResults& results)
+ {
+ valijson::ValidationResults::Error error;
+ std::stringstream ss;
+
+ ss << "Schema validation failed." << std::endl;
+ while (results.popError(error))
+ {
+ std::string context;
+ for (auto itr = error.context.begin(); itr != error.context.end(); itr++)
+ {
+ context += *itr;
+ }
+
+ ss << "Error context: " << context << " Description: " << error.description << std::endl;
+ }
+
+ return ss.str();
+ }
+}
\ No newline at end of file
diff --git a/src/AppInstallerCommonCore/Manifest/ManifestSchemaValidation.cpp b/src/AppInstallerCommonCore/Manifest/ManifestSchemaValidation.cpp
index 3c272369ab..80c7c05d6f 100644
--- a/src/AppInstallerCommonCore/Manifest/ManifestSchemaValidation.cpp
+++ b/src/AppInstallerCommonCore/Manifest/ManifestSchemaValidation.cpp
@@ -2,6 +2,7 @@
// Licensed under the MIT License.
#include "pch.h"
#include "winget/Yaml.h"
+#include "winget/JsonSchemaValidation.h"
#include "winget/ManifestCommon.h"
#include "winget/ManifestSchemaValidation.h"
#include "winget/ManifestYamlParser.h"
@@ -86,35 +87,6 @@ namespace AppInstaller::Manifest::YamlParser
}
}
- std::string LoadResourceAsString(PCWSTR resourceName, PCWSTR resourceType)
- {
- HMODULE resourceModule = NULL;
- GetModuleHandleEx(
- GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS,
- (PCWSTR)LoadResourceAsString,
- &resourceModule);
- THROW_LAST_ERROR_IF_NULL(resourceModule);
-
- HRSRC resourceInfoHandle = FindResource(resourceModule, resourceName, resourceType);
- THROW_LAST_ERROR_IF_NULL(resourceInfoHandle);
-
- HGLOBAL resourceMemoryHandle = LoadResource(resourceModule, resourceInfoHandle);
- THROW_LAST_ERROR_IF_NULL(resourceMemoryHandle);
-
- ULONG resourceSize = 0;
- char* resourceContent = NULL;
- resourceSize = SizeofResource(resourceModule, resourceInfoHandle);
- THROW_LAST_ERROR_IF(resourceSize == 0);
-
- resourceContent = reinterpret_cast(LockResource(resourceMemoryHandle));
- THROW_HR_IF_NULL(E_UNEXPECTED, resourceContent);
-
- std::string resourceStr;
- resourceStr.assign(resourceContent, resourceSize);
-
- return resourceStr;
- }
-
Json::Value LoadSchemaDoc(const ManifestVer& manifestVersion, ManifestTypeEnum manifestType)
{
std::string schemaStr;
@@ -124,19 +96,19 @@ namespace AppInstaller::Manifest::YamlParser
switch (manifestType)
{
case AppInstaller::Manifest::ManifestTypeEnum::Singleton:
- schemaStr = LoadResourceAsString(MAKEINTRESOURCE(IDX_MANIFEST_SCHEMA_V1_SINGLETON), MAKEINTRESOURCE(MANIFESTSCHEMA_RESOURCE_TYPE));
+ schemaStr = JsonSchema::LoadResourceAsString(MAKEINTRESOURCE(IDX_MANIFEST_SCHEMA_V1_SINGLETON), MAKEINTRESOURCE(MANIFESTSCHEMA_RESOURCE_TYPE));
break;
case AppInstaller::Manifest::ManifestTypeEnum::Version:
- schemaStr = LoadResourceAsString(MAKEINTRESOURCE(IDX_MANIFEST_SCHEMA_V1_VERSION), MAKEINTRESOURCE(MANIFESTSCHEMA_RESOURCE_TYPE));
+ schemaStr = JsonSchema::LoadResourceAsString(MAKEINTRESOURCE(IDX_MANIFEST_SCHEMA_V1_VERSION), MAKEINTRESOURCE(MANIFESTSCHEMA_RESOURCE_TYPE));
break;
case AppInstaller::Manifest::ManifestTypeEnum::Installer:
- schemaStr = LoadResourceAsString(MAKEINTRESOURCE(IDX_MANIFEST_SCHEMA_V1_INSTALLER), MAKEINTRESOURCE(MANIFESTSCHEMA_RESOURCE_TYPE));
+ schemaStr = JsonSchema::LoadResourceAsString(MAKEINTRESOURCE(IDX_MANIFEST_SCHEMA_V1_INSTALLER), MAKEINTRESOURCE(MANIFESTSCHEMA_RESOURCE_TYPE));
break;
case AppInstaller::Manifest::ManifestTypeEnum::DefaultLocale:
- schemaStr = LoadResourceAsString(MAKEINTRESOURCE(IDX_MANIFEST_SCHEMA_V1_DEFAULTLOCALE), MAKEINTRESOURCE(MANIFESTSCHEMA_RESOURCE_TYPE));
+ schemaStr = JsonSchema::LoadResourceAsString(MAKEINTRESOURCE(IDX_MANIFEST_SCHEMA_V1_DEFAULTLOCALE), MAKEINTRESOURCE(MANIFESTSCHEMA_RESOURCE_TYPE));
break;
case AppInstaller::Manifest::ManifestTypeEnum::Locale:
- schemaStr = LoadResourceAsString(MAKEINTRESOURCE(IDX_MANIFEST_SCHEMA_V1_LOCALE), MAKEINTRESOURCE(MANIFESTSCHEMA_RESOURCE_TYPE));
+ schemaStr = JsonSchema::LoadResourceAsString(MAKEINTRESOURCE(IDX_MANIFEST_SCHEMA_V1_LOCALE), MAKEINTRESOURCE(MANIFESTSCHEMA_RESOURCE_TYPE));
break;
default:
THROW_HR(HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED));
@@ -144,19 +116,10 @@ namespace AppInstaller::Manifest::YamlParser
}
else
{
- schemaStr = LoadResourceAsString(MAKEINTRESOURCE(IDX_MANIFEST_SCHEMA_PREVIEW), MAKEINTRESOURCE(MANIFESTSCHEMA_RESOURCE_TYPE));
+ schemaStr = JsonSchema::LoadResourceAsString(MAKEINTRESOURCE(IDX_MANIFEST_SCHEMA_PREVIEW), MAKEINTRESOURCE(MANIFESTSCHEMA_RESOURCE_TYPE));
}
- Json::Value schemaJson;
- int schemaLength = static_cast(schemaStr.length());
- Json::CharReaderBuilder charReaderBuilder;
- const std::unique_ptr jsonReader(charReaderBuilder.newCharReader());
- std::string errorMsg;
- if (!jsonReader->parse(schemaStr.c_str(), schemaStr.c_str() + schemaLength, &schemaJson, &errorMsg)) {
- THROW_HR_MSG(E_UNEXPECTED, "Jsoncpp parser failed to parse the schema doc. Reason: %s", errorMsg.c_str());
- }
-
- return schemaJson;
+ return JsonSchema::LoadSchemaDoc(schemaStr);
}
std::vector ValidateAgainstSchema(const std::vector& manifestList, const ManifestVer& manifestVersion)
@@ -164,7 +127,6 @@ namespace AppInstaller::Manifest::YamlParser
std::vector errors;
// A list of schema validator to avoid multiple loadings of same schema
std::map schemaList;
- valijson::Validator schemaValidator;
for (const auto& entry : manifestList)
{
@@ -173,36 +135,17 @@ namespace AppInstaller::Manifest::YamlParser
// Copy constructor of valijson::Schema was private
valijson::Schema& newSchema = schemaList.emplace(
std::piecewise_construct, std::make_tuple(entry.ManifestType), std::make_tuple()).first->second;
- valijson::SchemaParser schemaParser;
Json::Value schemaJson = LoadSchemaDoc(manifestVersion, entry.ManifestType);
- valijson::adapters::JsonCppAdapter jsonSchemaAdapter(schemaJson);
- schemaParser.populateSchema(jsonSchemaAdapter, newSchema);
+ JsonSchema::PopulateSchema(schemaJson, newSchema);
}
const auto& schema = schemaList.find(entry.ManifestType)->second;
-
Json::Value manifestJson = ManifestYamlNodeToJson(entry.Root);
- valijson::adapters::JsonCppAdapter manifestJsonAdapter(manifestJson);
valijson::ValidationResults results;
- if (!schemaValidator.validate(schema, manifestJsonAdapter, &results))
+ if (!JsonSchema::Validate(schema, manifestJson, results))
{
- valijson::ValidationResults::Error error;
- std::stringstream ss;
-
- ss << "Schema validation failed." << std::endl;
- while (results.popError(error))
- {
- std::string context;
- for (auto itr = error.context.begin(); itr != error.context.end(); itr++)
- {
- context += *itr;
- }
-
- ss << "Error context: " << context << " Description: " << error.description << std::endl;
- }
-
- errors.emplace_back(ValidationError::MessageWithFile(ss.str(), entry.FileName));
+ errors.emplace_back(ValidationError::MessageWithFile(JsonSchema::GetErrorStringFromResults(results), entry.FileName));
}
}
diff --git a/src/AppInstallerCommonCore/Public/winget/JsonSchemaValidation.h b/src/AppInstallerCommonCore/Public/winget/JsonSchemaValidation.h
new file mode 100644
index 0000000000..1c921f8921
--- /dev/null
+++ b/src/AppInstallerCommonCore/Public/winget/JsonSchemaValidation.h
@@ -0,0 +1,26 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+#pragma once
+#include
+
+namespace AppInstaller::JsonSchema
+{
+ // Load an embedded resource from binary and return as std::string
+ std::string LoadResourceAsString(PCWSTR resourceName, PCWSTR resourceType);
+
+ // Load schema as parsed json doc
+ Json::Value LoadSchemaDoc(const std::string& schemaStr);
+
+ // Load an embedded resource from binary and return as Json::Value
+ Json::Value LoadResourceAsSchemaDoc(PCWSTR resourceName, PCWSTR resourceType);
+
+ // Populate a valijson Schema object from a json value
+ void PopulateSchema(const Json::Value& schemaJson, valijson::Schema& schema);
+
+ // Validate a json doc with a schema
+ // Returns whether it was successful and fills the results object
+ bool Validate(const valijson::Schema& schema, const Json::Value& json, valijson::ValidationResults& results);
+
+ // Extracts the error messages from a result into a single non-localized string
+ std::string GetErrorStringFromResults(valijson::ValidationResults& results);
+}
\ No newline at end of file
diff --git a/src/AppInstallerCommonCore/Public/winget/ManifestSchemaValidation.h b/src/AppInstallerCommonCore/Public/winget/ManifestSchemaValidation.h
index 1dc8793d5e..35488bae24 100644
--- a/src/AppInstallerCommonCore/Public/winget/ManifestSchemaValidation.h
+++ b/src/AppInstallerCommonCore/Public/winget/ManifestSchemaValidation.h
@@ -11,9 +11,6 @@ namespace AppInstaller::Manifest::YamlParser
// Forward declarations
struct YamlManifestInfo;
- // Load an embedded resource from binary and return as std::string
- std::string LoadResourceAsString(PCWSTR resourceName, PCWSTR resourceType);
-
// Load manifest schema as parsed json doc
Json::Value LoadSchemaDoc(const ManifestVer& manifestVersion, ManifestTypeEnum manifestType);
diff --git a/src/WinGetSchemas/PackagesSchema.h b/src/WinGetSchemas/PackagesSchema.h
new file mode 100644
index 0000000000..f9f0f49124
--- /dev/null
+++ b/src/WinGetSchemas/PackagesSchema.h
@@ -0,0 +1,7 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+#pragma once
+
+#define PACKAGESSCHEMA_RESOURCE_TYPE 300
+
+#define IDX_PACKAGES_SCHEMA_V1 301
diff --git a/src/WinGetSchemas/WinGetSchemas.rc b/src/WinGetSchemas/WinGetSchemas.rc
new file mode 100644
index 0000000000..dea581d7cc
--- /dev/null
+++ b/src/WinGetSchemas/WinGetSchemas.rc
@@ -0,0 +1,66 @@
+// Microsoft Visual C++ generated resource script.
+//
+#include "resource.h"
+#include "PackagesSchema.h"
+
+#define APSTUDIO_READONLY_SYMBOLS
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 2 resource.
+//
+#include "winres.h"
+
+/////////////////////////////////////////////////////////////////////////////
+#undef APSTUDIO_READONLY_SYMBOLS
+
+/////////////////////////////////////////////////////////////////////////////
+// English (United States) resources
+
+#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
+LANGUAGE 9, 1
+
+#ifdef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// TEXTINCLUDE
+//
+
+1 TEXTINCLUDE
+BEGIN
+ "resource.h\0"
+END
+
+2 TEXTINCLUDE
+BEGIN
+ "#include ""winres.h""\r\n"
+ "\0"
+END
+
+3 TEXTINCLUDE
+BEGIN
+ "\r\n"
+ "\0"
+END
+
+#endif // APSTUDIO_INVOKED
+
+#endif // English (United States) resources
+/////////////////////////////////////////////////////////////////////////////
+
+
+
+#ifndef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 3 resource.
+//
+
+
+/////////////////////////////////////////////////////////////////////////////
+#endif // not APSTUDIO_INVOKED
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Packages schema
+//
+IDX_PACKAGES_SCHEMA_V1 PACKAGESSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\packages\\packages.schema.1.0.json"
diff --git a/src/WinGetSchemas/WinGetSchemas.vcxitems b/src/WinGetSchemas/WinGetSchemas.vcxitems
new file mode 100644
index 0000000000..f171001df3
--- /dev/null
+++ b/src/WinGetSchemas/WinGetSchemas.vcxitems
@@ -0,0 +1,27 @@
+
+
+
+ $(MSBuildAllProjects);$(MSBuildThisFileFullPath)
+ true
+ {952b513f-8a00-4d74-9271-925afb3c6252}
+
+
+
+ %(AdditionalIncludeDirectories);$(MSBuildThisFileDirectory)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/WinGetSchemas/WinGetSchemas.vcxitems.filters b/src/WinGetSchemas/WinGetSchemas.vcxitems.filters
new file mode 100644
index 0000000000..08227f67a3
--- /dev/null
+++ b/src/WinGetSchemas/WinGetSchemas.vcxitems.filters
@@ -0,0 +1,26 @@
+
+
+
+
+ {9b8a4c46-6227-45fe-840b-8f50fb10ddb1}
+
+
+ {931a4cce-b01f-4d2f-b39a-8600f2010a97}
+
+
+
+
+ settings
+
+
+ packages
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/WinGetSchemas/resource.h b/src/WinGetSchemas/resource.h
new file mode 100644
index 0000000000..80bec7af27
--- /dev/null
+++ b/src/WinGetSchemas/resource.h
@@ -0,0 +1,14 @@
+//{{NO_DEPENDENCIES}}
+// Microsoft Visual C++ generated include file.
+// Used by WinGetSchemas.rc
+
+// Next default values for new objects
+//
+#ifdef APSTUDIO_INVOKED
+#ifndef APSTUDIO_READONLY_SYMBOLS
+#define _APS_NEXT_RESOURCE_VALUE 101
+#define _APS_NEXT_COMMAND_VALUE 40001
+#define _APS_NEXT_CONTROL_VALUE 1001
+#define _APS_NEXT_SYMED_VALUE 101
+#endif
+#endif