diff --git a/.version/PipelineAssemblyInfo.cs b/.version/PipelineAssemblyInfo.cs
index b1f2f756..f5cc4c28 100644
--- a/.version/PipelineAssemblyInfo.cs
+++ b/.version/PipelineAssemblyInfo.cs
@@ -5,4 +5,4 @@
 using System.Reflection;
 [assembly: AssemblyVersion("0.0.0.0")]
 [assembly: AssemblyFileVersion("0.0.0.0")]
-[assembly: AssemblyInformationalVersion("0.0.0.0-dev-00000000")]
+[assembly: AssemblyInformationalVersion("0.0.0.0-dev-00000000")]
\ No newline at end of file
diff --git a/docs/pa.yaml-schema.json b/docs/pa.yaml-schema.json
index cb97c8de..23bb5437 100644
--- a/docs/pa.yaml-schema.json
+++ b/docs/pa.yaml-schema.json
@@ -1,6 +1,6 @@
 {
   "$schema": "https://json-schema.org/draft/2020-12/schema",
-  "$id": "https://raw.githubusercontent.com/microsoft/PowerApps-Tooling/master/docs/pa.yaml-schema.json",
+  "$id": "pa.yaml-schema.json",
   "title": "Microsoft Power Apps",
   "description": "Canvas YAML",
   "oneOf": [
diff --git a/docs/subschemas/control-property-schema.json b/docs/subschemas/control-property-schema.json
index 6d7b3f6b..748af747 100644
--- a/docs/subschemas/control-property-schema.json
+++ b/docs/subschemas/control-property-schema.json
@@ -1,6 +1,6 @@
 {
   "$schema": "https://json-schema.org/draft/2020-12/schema",
-  "$id": "https://raw.githubusercontent.com/microsoft/PowerApps-Tooling/master/docs/control-type-schema.json",
+  "$id": "control-property-schema.json",
   "title": "Microsoft Power Apps Properties",
   "description": "The properties of the control",
   "type": "object",
diff --git a/docs/subschemas/control-type-schema.json b/docs/subschemas/control-type-schema.json
index 081e9761..2aac5d7d 100644
--- a/docs/subschemas/control-type-schema.json
+++ b/docs/subschemas/control-type-schema.json
@@ -1,6 +1,6 @@
 {
   "$schema": "https://json-schema.org/draft/2020-12/schema",
-  "$id": "https://raw.githubusercontent.com/microsoft/PowerApps-Tooling/master/docs/control-type-schema.json",
+  "$id": "control-type-schema.json",
   "title": "Microsoft Power Apps Control Type",
   "description": "The type of the control",
   "type": "string",
diff --git a/src/PASopa.sln b/src/PASopa.sln
index a7347547..f16b903d 100644
--- a/src/PASopa.sln
+++ b/src/PASopa.sln
@@ -20,6 +20,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.PowerPlatform.Pow
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Persistence.Tests", "Persistence.Tests\Persistence.Tests.csproj", "{8AB1C901-FE5E-44BF-AA21-B8F20A9D7CDD}"
 EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "YamlValidator", "YamlValidator\YamlValidator.csproj", "{F0AD11CE-E634-4945-A6B1-7866CDE0059C}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "YamlValidator.Tests", "YamlValidator.Tests\YamlValidator.Tests.csproj", "{8BA5DD4B-9423-4827-AF37-540E0300DB9A}"
+EndProject
 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{7361DB16-D534-4E0E-8597-BE22317DEF47}"
 	ProjectSection(SolutionItems) = preProject
 		Directory.Build.props = Directory.Build.props
@@ -52,6 +56,14 @@ Global
 		{8AB1C901-FE5E-44BF-AA21-B8F20A9D7CDD}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{8AB1C901-FE5E-44BF-AA21-B8F20A9D7CDD}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{8AB1C901-FE5E-44BF-AA21-B8F20A9D7CDD}.Release|Any CPU.Build.0 = Release|Any CPU
+		{F0AD11CE-E634-4945-A6B1-7866CDE0059C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{F0AD11CE-E634-4945-A6B1-7866CDE0059C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{F0AD11CE-E634-4945-A6B1-7866CDE0059C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{F0AD11CE-E634-4945-A6B1-7866CDE0059C}.Release|Any CPU.Build.0 = Release|Any CPU
+		{8BA5DD4B-9423-4827-AF37-540E0300DB9A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{8BA5DD4B-9423-4827-AF37-540E0300DB9A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{8BA5DD4B-9423-4827-AF37-540E0300DB9A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{8BA5DD4B-9423-4827-AF37-540E0300DB9A}.Release|Any CPU.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
@@ -59,6 +71,7 @@ Global
 	GlobalSection(NestedProjects) = preSolution
 		{8AD94CC0-7330-4880-A8E0-177B37CDB27B} = {4993E606-484B-46D9-892E-7AE9CE8D4423}
 		{8AB1C901-FE5E-44BF-AA21-B8F20A9D7CDD} = {4993E606-484B-46D9-892E-7AE9CE8D4423}
+		{8BA5DD4B-9423-4827-AF37-540E0300DB9A} = {4993E606-484B-46D9-892E-7AE9CE8D4423}
 		{7361DB16-D534-4E0E-8597-BE22317DEF47} = {794D8C68-BF6F-49C8-BCA5-AA52D8F45EF4}
 	EndGlobalSection
 	GlobalSection(ExtensibilityGlobals) = postSolution
diff --git a/src/YamlValidator.Tests/ValidatorTest.cs b/src/YamlValidator.Tests/ValidatorTest.cs
new file mode 100644
index 00000000..3408da83
--- /dev/null
+++ b/src/YamlValidator.Tests/ValidatorTest.cs
@@ -0,0 +1,46 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using Json.Schema;
+using Microsoft.PowerPlatform.PowerApps.Persistence.YamlValidator;
+
+namespace Persistence.Tests.YamlValidator;
+
+[TestClass]
+public class ValidatorTest
+{
+    private const string _validPath = @".\_TestData\ValidYaml";
+    private const string _invalidPath = @".\_TestData\InvalidYaml";
+    private const string _schemaPath = @"..\YamlValidator\schema\pa.yaml-schema.json";
+
+    private readonly JsonSchema _schema;
+    private readonly Validator _yamlValidator;
+
+    public ValidatorTest()
+    {
+        var schemaFileLoader = new SchemaLoader();
+        _schema = schemaFileLoader.Load(_schemaPath);
+        var resultVerbosity = new VerbosityData(Constants.Verbose);
+        _yamlValidator = new Validator(resultVerbosity.EvalOptions, resultVerbosity.JsonOutputOptions);
+    }
+
+    [TestMethod]
+    [DataRow($@"{_invalidPath}\ScreenWithNameNoColon.yaml", false)]
+    [DataRow($@"{_invalidPath}\ScreenWithNameNoValue.yaml", false)]
+    [DataRow($@"{_invalidPath}\ScreenWithoutControlProperty.yaml", false)]
+    [DataRow($@"{_invalidPath}\WrongControlDefinition.yaml", false)]
+    [DataRow($@"{_invalidPath}\ControlWithInvalidProperty.yaml", false)]
+    [DataRow($@"{_invalidPath}\EmptyArray.yaml", false)]
+    [DataRow($@"{_invalidPath}\Empty.yaml", false)]
+    [DataRow($@"{_invalidPath}\NamelessObjectNoControl.yaml", false)]
+    [DataRow($@"{_validPath}\NamelessObjectWithControl.yaml", true)]
+    [DataRow($@"{_validPath}\ValidScreen1.yaml", true)]
+    [DataRow($@"{_validPath}\SimpleNoRecursiveDefinition.yaml", true)]
+
+    public void TestValidation(string filepath, bool expectedResult)
+    {
+        var rawYaml = Utility.ReadFileData($@"{filepath}");
+        var result = _yamlValidator.Validate(_schema, rawYaml);
+        Assert.IsTrue(result.SchemaValid == expectedResult);
+    }
+}
diff --git a/src/YamlValidator.Tests/YamlValidator.Tests.csproj b/src/YamlValidator.Tests/YamlValidator.Tests.csproj
new file mode 100644
index 00000000..d77e2bc4
--- /dev/null
+++ b/src/YamlValidator.Tests/YamlValidator.Tests.csproj
@@ -0,0 +1,34 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>net8.0</TargetFramework>
+    <ImplicitUsings>enable</ImplicitUsings>
+    <Nullable>enable</Nullable>
+    <IsPackable>false</IsPackable>
+    <IsTestProject>true</IsTestProject>
+  </PropertyGroup>
+
+    <ItemGroup>
+        <None Include="_TestData\**\*">
+            <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+        </None>
+    </ItemGroup>
+
+    <ItemGroup>
+    <PackageReference Include="coverlet.collector" Version="6.0.0" />
+    <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.0-preview.5.24306.7" />
+    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
+    <PackageReference Include="MSTest.TestAdapter" Version="3.1.1" />
+    <PackageReference Include="MSTest.TestFramework" Version="3.1.1" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\YamlValidator\YamlValidator.csproj" />
+  </ItemGroup>
+
+
+  <ItemGroup>
+    <Using Include="Microsoft.VisualStudio.TestTools.UnitTesting" />
+  </ItemGroup>
+
+</Project>
diff --git a/src/YamlValidator.Tests/_TestData/InvalidYaml/ControlWithInvalidProperty.yaml b/src/YamlValidator.Tests/_TestData/InvalidYaml/ControlWithInvalidProperty.yaml
new file mode 100644
index 00000000..999ea429
--- /dev/null
+++ b/src/YamlValidator.Tests/_TestData/InvalidYaml/ControlWithInvalidProperty.yaml
@@ -0,0 +1,3 @@
+Screen2:
+  Control: Screen
+  InvalidProperty: "invalid"
diff --git a/src/YamlValidator.Tests/_TestData/InvalidYaml/Empty.yaml b/src/YamlValidator.Tests/_TestData/InvalidYaml/Empty.yaml
new file mode 100644
index 00000000..e69de29b
diff --git a/src/YamlValidator.Tests/_TestData/InvalidYaml/EmptyArray.yaml b/src/YamlValidator.Tests/_TestData/InvalidYaml/EmptyArray.yaml
new file mode 100644
index 00000000..c59ec775
--- /dev/null
+++ b/src/YamlValidator.Tests/_TestData/InvalidYaml/EmptyArray.yaml
@@ -0,0 +1,4 @@
+- 
+- 
+- 
+- 
diff --git a/src/YamlValidator.Tests/_TestData/InvalidYaml/NamelessObjectNoControl.yaml b/src/YamlValidator.Tests/_TestData/InvalidYaml/NamelessObjectNoControl.yaml
new file mode 100644
index 00000000..397db75f
--- /dev/null
+++ b/src/YamlValidator.Tests/_TestData/InvalidYaml/NamelessObjectNoControl.yaml
@@ -0,0 +1 @@
+:
diff --git a/src/YamlValidator.Tests/_TestData/InvalidYaml/ScreenWithNameNoColon.yaml b/src/YamlValidator.Tests/_TestData/InvalidYaml/ScreenWithNameNoColon.yaml
new file mode 100644
index 00000000..9daeafb9
--- /dev/null
+++ b/src/YamlValidator.Tests/_TestData/InvalidYaml/ScreenWithNameNoColon.yaml
@@ -0,0 +1 @@
+test
diff --git a/src/YamlValidator.Tests/_TestData/InvalidYaml/ScreenWithNameNoValue.yaml b/src/YamlValidator.Tests/_TestData/InvalidYaml/ScreenWithNameNoValue.yaml
new file mode 100644
index 00000000..e901b4d3
--- /dev/null
+++ b/src/YamlValidator.Tests/_TestData/InvalidYaml/ScreenWithNameNoValue.yaml
@@ -0,0 +1 @@
+test:
diff --git a/src/YamlValidator.Tests/_TestData/InvalidYaml/ScreenWithoutControlProperty.yaml b/src/YamlValidator.Tests/_TestData/InvalidYaml/ScreenWithoutControlProperty.yaml
new file mode 100644
index 00000000..eef6736d
--- /dev/null
+++ b/src/YamlValidator.Tests/_TestData/InvalidYaml/ScreenWithoutControlProperty.yaml
@@ -0,0 +1,2 @@
+Screen:
+  ComponentName: "test"
diff --git a/src/YamlValidator.Tests/_TestData/InvalidYaml/WrongControlDefinition.yaml b/src/YamlValidator.Tests/_TestData/InvalidYaml/WrongControlDefinition.yaml
new file mode 100644
index 00000000..b0bf6878
--- /dev/null
+++ b/src/YamlValidator.Tests/_TestData/InvalidYaml/WrongControlDefinition.yaml
@@ -0,0 +1,2 @@
+TestScreen:
+ "abcd"  
diff --git a/src/YamlValidator.Tests/_TestData/ValidYaml/NamelessObjectWithControl.yaml b/src/YamlValidator.Tests/_TestData/ValidYaml/NamelessObjectWithControl.yaml
new file mode 100644
index 00000000..b714ba34
--- /dev/null
+++ b/src/YamlValidator.Tests/_TestData/ValidYaml/NamelessObjectWithControl.yaml
@@ -0,0 +1,2 @@
+:
+ Control: "bdbd"
diff --git a/src/YamlValidator.Tests/_TestData/ValidYaml/SimpleNoRecursiveDefinition.yaml b/src/YamlValidator.Tests/_TestData/ValidYaml/SimpleNoRecursiveDefinition.yaml
new file mode 100644
index 00000000..616601da
--- /dev/null
+++ b/src/YamlValidator.Tests/_TestData/ValidYaml/SimpleNoRecursiveDefinition.yaml
@@ -0,0 +1,2 @@
+LoginPage:
+ Control: "Button"  
diff --git a/src/YamlValidator.Tests/_TestData/ValidYaml/ValidScreen1.yaml b/src/YamlValidator.Tests/_TestData/ValidYaml/ValidScreen1.yaml
new file mode 100644
index 00000000..0ed69b8a
--- /dev/null
+++ b/src/YamlValidator.Tests/_TestData/ValidYaml/ValidScreen1.yaml
@@ -0,0 +1,30 @@
+Screen2:
+  Control: Screen
+  Children:
+  - ButtonCanvas2:
+      Control: Button
+      Properties:
+        OnSelect: =Navigate(Screen1)
+        Text: ="Back"
+        Height: =53
+        Width: =172
+        X: =632
+        Y: =550
+  - TextCanvas1:
+      Control: Text
+      Properties:
+        Align: ='TextCanvas.Align'.Center
+        Size: =50
+        Text: ="Hello"
+        Height: =91
+        Width: =368
+        X: =517
+        Y: =44
+  - Image1:
+      Control: Image
+      Properties:
+        Image: ='pexels-pixabay-417173'
+        Height: =361
+        Width: =466
+        X: =447
+        Y: =135
diff --git a/src/YamlValidator/Constants.cs b/src/YamlValidator/Constants.cs
new file mode 100644
index 00000000..e377efd7
--- /dev/null
+++ b/src/YamlValidator/Constants.cs
@@ -0,0 +1,14 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+namespace Microsoft.PowerPlatform.PowerApps.Persistence.YamlValidator;
+public class Constants
+{
+    public const string FileTypeName = "file";
+    public const string FolderTypeName = "folder";
+    public const string YamlFileExtension = ".yaml";
+    public const string YmlFileExtension = ".yml";
+    public const string JsonFileExtension = ".json";
+
+    public const string Verbose = "verbose";
+}
diff --git a/src/YamlValidator/InputProcessor.cs b/src/YamlValidator/InputProcessor.cs
new file mode 100644
index 00000000..c3e8337e
--- /dev/null
+++ b/src/YamlValidator/InputProcessor.cs
@@ -0,0 +1,109 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.CommandLine;
+
+namespace Microsoft.PowerPlatform.PowerApps.Persistence.YamlValidator;
+public class InputProcessor
+{
+
+    private static void ProcessFiles(string path, string schema, string pathType)
+    {
+        // read only records
+        var filePathInfo = new ValidationRequest(path, schema, pathType);
+        var verbosityInfo = new VerbosityData(Constants.Verbose);
+
+        var validator = new Validator(verbosityInfo.EvalOptions, verbosityInfo.JsonOutputOptions);
+        var schemaLoader = new SchemaLoader();
+        var fileLoader = new YamlLoader();
+        var orchestrator = new Orchestrator(fileLoader, schemaLoader, validator);
+        orchestrator.RunValidation(filePathInfo);
+    }
+    public static RootCommand GetRootCommand()
+    {
+
+        var pathOption = new Option<string>(
+            name: "--path",
+            description: "The path to the input yaml file or directory of yaml files"
+        )
+        { IsRequired = true };
+
+        pathOption.AddValidator(result =>
+        {
+            var inputFilePath = result.GetValueForOption(pathOption);
+
+            // either file or folder must be passed
+            var pathType = string.Empty;
+            if (string.IsNullOrEmpty(inputFilePath))
+            {
+                result.ErrorMessage = "The input is invalid, input must be a filepath to a yaml file \\" +
+                "or a folder path to a folder of yaml files";
+            }
+            else if (!Directory.Exists(inputFilePath) && !File.Exists(inputFilePath))
+            {
+                result.ErrorMessage = "The input path does not exist";
+            }
+            else if (Directory.Exists(inputFilePath))
+            {
+                if (Directory.GetFiles(inputFilePath, $"*{Constants.YamlFileExtension}").Length == 0)
+                {
+                    result.ErrorMessage = "The input folder does not contain any yaml files";
+                }
+            }
+            else if (File.Exists(inputFilePath))
+            {
+                if (Path.GetExtension(inputFilePath) != Constants.YamlFileExtension)
+                {
+                    result.ErrorMessage = "The input file must be a yaml file";
+                }
+            }
+        });
+
+        // assume local schema file exists in nuget package, use relative filepath for now
+        var schemaOption = new Option<string>(
+            name: "--schema",
+            description: "The path to the schema json file",
+            getDefaultValue: () => @".\schema\pa.yaml-schema.json"
+            );
+
+        schemaOption.AddValidator(result =>
+        {
+            var schemaPath = result.GetValueForOption(schemaOption);
+            if (string.IsNullOrEmpty(schemaPath))
+            {
+                result.ErrorMessage = "Schema option selected, but no schema was provided";
+            }
+            else if (Path.GetExtension(schemaPath) != Constants.JsonFileExtension)
+            {
+                result.ErrorMessage = "The schema file must be a json file";
+            }
+            else if (!File.Exists(schemaPath))
+            {
+                result.ErrorMessage = "The schema file does not exist";
+            }
+        });
+
+        // define root
+        var rootCommand = new RootCommand("YAML validator cli-tool");
+
+        // validate command
+        var validateCommand = new Command("validate", "Validate the input yaml file")
+        {
+            pathOption,
+            schemaOption
+        };
+
+        validateCommand.SetHandler((pathOptionVal, schemaOptionVal) =>
+        {
+            var pathType = File.GetAttributes(pathOptionVal).HasFlag(FileAttributes.Directory) ? Constants.FolderTypeName :
+                                                                                                 Constants.FileTypeName;
+            ProcessFiles(pathOptionVal, schemaOptionVal, pathType);
+
+        }, pathOption, schemaOption);
+
+        rootCommand.AddCommand(validateCommand);
+
+        return rootCommand;
+
+    }
+}
diff --git a/src/YamlValidator/Orchestrator.cs b/src/YamlValidator/Orchestrator.cs
new file mode 100644
index 00000000..71d04971
--- /dev/null
+++ b/src/YamlValidator/Orchestrator.cs
@@ -0,0 +1,42 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+namespace Microsoft.PowerPlatform.PowerApps.Persistence.YamlValidator;
+public class Orchestrator
+{
+    private readonly YamlLoader _fileLoader;
+    private readonly SchemaLoader _schemaLoader;
+    private readonly Validator _validator;
+
+    public Orchestrator(YamlLoader fileLoader, SchemaLoader schemaLoader, Validator validator)
+    {
+        _fileLoader = fileLoader;
+        _schemaLoader = schemaLoader;
+        _validator = validator;
+    }
+
+    public void RunValidation(ValidationRequest inputData)
+    {
+        var schemaPath = inputData.SchemaPath;
+        var path = inputData.FilePath;
+        var pathType = inputData.FilePathType;
+
+        var yamlData = _fileLoader.Load(path, pathType);
+        var serializedSchema = _schemaLoader.Load(schemaPath);
+
+        foreach (var yamlFileData in yamlData)
+        {
+            Console.WriteLine($"Validation for {yamlFileData.Key}");
+            var result = _validator.Validate(serializedSchema, yamlFileData.Value);
+            Console.WriteLine($"Validation Result: {result.SchemaValid}");
+            foreach (var error in result.TraversalResults)
+            {
+                Console.WriteLine($"{error}");
+            }
+            Console.WriteLine();
+        }
+    }
+
+
+
+}
diff --git a/src/YamlValidator/Program.cs b/src/YamlValidator/Program.cs
new file mode 100644
index 00000000..c35c35d9
--- /dev/null
+++ b/src/YamlValidator/Program.cs
@@ -0,0 +1,15 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.CommandLine;
+
+namespace Microsoft.PowerPlatform.PowerApps.Persistence.YamlValidator;
+
+public class Program
+{
+    private static void Main(string[] args)
+    {
+        var inputProcessor = InputProcessor.GetRootCommand();
+        inputProcessor.Invoke(args);
+    }
+}
diff --git a/src/YamlValidator/SchemaLoader.cs b/src/YamlValidator/SchemaLoader.cs
new file mode 100644
index 00000000..edce53c7
--- /dev/null
+++ b/src/YamlValidator/SchemaLoader.cs
@@ -0,0 +1,28 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using Json.Schema;
+
+namespace Microsoft.PowerPlatform.PowerApps.Persistence.YamlValidator;
+public class SchemaLoader
+{
+    private const string _schemaFolderPath = "subschemas";
+
+    public JsonSchema Load(string schemaPath)
+    {
+        var node = JsonSchema.FromFile(schemaPath);
+        var schemaFolder = Path.GetDirectoryName(schemaPath);
+        var subschemaPaths = Directory.GetFiles($@"{schemaFolder}\{_schemaFolderPath}",
+            $"*{Constants.JsonFileExtension}");
+
+        foreach (var path in subschemaPaths)
+        {
+            var subschema = JsonSchema.FromFile(path);
+            SchemaRegistry.Global.Register(subschema);
+        }
+
+        return node;
+    }
+
+}
+
diff --git a/src/YamlValidator/Utility.cs b/src/YamlValidator/Utility.cs
new file mode 100644
index 00000000..c3ab709a
--- /dev/null
+++ b/src/YamlValidator/Utility.cs
@@ -0,0 +1,21 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using YamlDotNet.RepresentationModel;
+
+namespace Microsoft.PowerPlatform.PowerApps.Persistence.YamlValidator;
+public class Utility
+{
+    public static string ReadFileData(string filePath)
+    {
+        var yamlData = File.ReadAllText(filePath);
+        return yamlData;
+    }
+
+    public static YamlStream MakeYamlStream(string yamlString)
+    {
+        var stream = new YamlStream();
+        stream.Load(new StringReader(yamlString));
+        return stream;
+    }
+}
diff --git a/src/YamlValidator/ValidationRequest.cs b/src/YamlValidator/ValidationRequest.cs
new file mode 100644
index 00000000..46199731
--- /dev/null
+++ b/src/YamlValidator/ValidationRequest.cs
@@ -0,0 +1,5 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+namespace Microsoft.PowerPlatform.PowerApps.Persistence.YamlValidator;
+public readonly record struct ValidationRequest(string FilePath, string SchemaPath, string FilePathType);
diff --git a/src/YamlValidator/Validator.cs b/src/YamlValidator/Validator.cs
new file mode 100644
index 00000000..22e99756
--- /dev/null
+++ b/src/YamlValidator/Validator.cs
@@ -0,0 +1,59 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using Json.Schema;
+using Yaml2JsonNode;
+using System.Text.Json;
+
+namespace Microsoft.PowerPlatform.PowerApps.Persistence.YamlValidator;
+
+public class Validator
+{
+    private readonly EvaluationOptions _verbosityOptions;
+    private readonly JsonSerializerOptions _serializerOptions;
+
+
+    public Validator(EvaluationOptions options, JsonSerializerOptions resultSerializeOptions)
+    {
+        // to do: add verbosity flag and allow users to choose verbosity of evaluation
+        _verbosityOptions = options;
+        _serializerOptions = resultSerializeOptions;
+
+    }
+
+    public ValidatorResults Validate(JsonSchema schema, string yamlFileData)
+    {
+        var yamlStream = Utility.MakeYamlStream(yamlFileData);
+        var jsonData = yamlStream.Documents.Count > 0 ? yamlStream.Documents[0].ToJsonNode() : null;
+
+        // here we say that empty yaml is serialized as null json
+        if (jsonData == null)
+        {
+            return new ValidatorResults(false, new List<ValidatorError> { new("Empty YAML file") });
+        }
+        var results = schema.Evaluate(jsonData, _verbosityOptions);
+
+        // not used but may help if we ever need to serialize the evaluation results into json format to feed into
+        // a vscode extension or other tool
+        var output = JsonSerializer.Serialize(results, _serializerOptions);
+
+        var schemaValidity = results.IsValid;
+        // TBD: filter actual errors versus false positives
+        // we look for errors that are not valid, have errors, and have an instance location (i.e are not oneOf errors)
+        var yamlValidatorErrors = new List<ValidatorError>();
+        if (!schemaValidity)
+        {
+            IReadOnlyList<EvaluationResults> traceList = results.Details.Where(
+             node => !node.IsValid &&
+             node.HasErrors).ToList();
+            foreach (var trace in traceList)
+            {
+                yamlValidatorErrors.Add(new ValidatorError(trace));
+            }
+        }
+        IReadOnlyList<ValidatorError> fileErrors = yamlValidatorErrors;
+        var finalResults = new ValidatorResults(results.IsValid, fileErrors);
+        return finalResults;
+
+    }
+}
diff --git a/src/YamlValidator/ValidatorError.cs b/src/YamlValidator/ValidatorError.cs
new file mode 100644
index 00000000..3afec499
--- /dev/null
+++ b/src/YamlValidator/ValidatorError.cs
@@ -0,0 +1,39 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using Json.Schema;
+
+namespace Microsoft.PowerPlatform.PowerApps.Persistence.YamlValidator;
+public class ValidatorError
+{
+    public string InstanceLocation { get; }
+    public string SchemaPath { get; }
+    public IReadOnlyDictionary<string, string>? Errors { get; }
+
+    public ValidatorError(EvaluationResults results)
+    {
+        InstanceLocation = results.InstanceLocation.ToString();
+        SchemaPath = results.EvaluationPath.ToString();
+        Errors = results.Errors;
+    }
+    public ValidatorError(string error)
+    {
+        InstanceLocation = "";
+        SchemaPath = "";
+        Errors = new Dictionary<string, string> { { "", error } };
+    }
+
+    public override string ToString()
+    {
+        var errString = "";
+        if (Errors != null)
+        {
+            foreach (var error in Errors)
+            {
+                var errType = string.IsNullOrEmpty(error.Key) ? "Error" : error.Key;
+                errString += $"\t{errType}: {error.Value}\n";
+            }
+        }
+        return $"InstanceLocation: {InstanceLocation}\nSchemaPath: {SchemaPath}\nErrors:\n{errString}";
+    }
+}
diff --git a/src/YamlValidator/ValidatorResults.cs b/src/YamlValidator/ValidatorResults.cs
new file mode 100644
index 00000000..400e7a52
--- /dev/null
+++ b/src/YamlValidator/ValidatorResults.cs
@@ -0,0 +1,16 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+namespace Microsoft.PowerPlatform.PowerApps.Persistence.YamlValidator;
+public class ValidatorResults
+{
+    public bool SchemaValid { get; }
+    public IReadOnlyList<ValidatorError> TraversalResults { get; }
+
+    public ValidatorResults(bool schemaValid, IReadOnlyList<ValidatorError> traversalResults)
+    {
+        SchemaValid = schemaValid;
+        TraversalResults = traversalResults;
+
+    }
+}
diff --git a/src/YamlValidator/VerbosityData.cs b/src/YamlValidator/VerbosityData.cs
new file mode 100644
index 00000000..d8383614
--- /dev/null
+++ b/src/YamlValidator/VerbosityData.cs
@@ -0,0 +1,25 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+using System.Text.Json;
+using Json.Schema;
+
+namespace Microsoft.PowerPlatform.PowerApps.Persistence.YamlValidator;
+public readonly record struct VerbosityData
+{
+    public EvaluationOptions EvalOptions { get; }
+    public JsonSerializerOptions JsonOutputOptions { get; }
+
+    public VerbosityData(string verbosityLevel)
+    {
+        EvalOptions = new EvaluationOptions();
+        JsonOutputOptions = new JsonSerializerOptions { Converters = { new EvaluationResultsJsonConverter() } };
+
+        if (verbosityLevel == Constants.Verbose)
+        {
+            EvalOptions.OutputFormat = OutputFormat.List;
+            return;
+        }
+        EvalOptions.OutputFormat = OutputFormat.Flag;
+    }
+}
+
diff --git a/src/YamlValidator/YamlLoader.cs b/src/YamlValidator/YamlLoader.cs
new file mode 100644
index 00000000..500da4d7
--- /dev/null
+++ b/src/YamlValidator/YamlLoader.cs
@@ -0,0 +1,33 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Collections.ObjectModel;
+
+namespace Microsoft.PowerPlatform.PowerApps.Persistence.YamlValidator;
+public class YamlLoader
+{
+
+    public IReadOnlyDictionary<string, string> Load(string filePath, string pathType)
+    {
+        var deserializedYaml = new Dictionary<string, string>();
+        if (pathType == Constants.FileTypeName)
+        {
+            var fileName = Path.GetFileName(filePath);
+            var yamlText = Utility.ReadFileData(filePath);
+            deserializedYaml.Add(fileName, yamlText);
+            return new ReadOnlyDictionary<string, string>(deserializedYaml);
+        }
+
+        // to do: address edge case of .yml files
+        var files = Directory.GetFiles(filePath, $"*{Constants.YamlFileExtension}");
+        foreach (var file in files)
+        {
+            var fileName = Path.GetFileName(file);
+            var yamlText = Utility.ReadFileData(file);
+            deserializedYaml.Add(fileName, yamlText);
+        }
+
+        return new ReadOnlyDictionary<string, string>(deserializedYaml);
+    }
+
+}
diff --git a/src/YamlValidator/YamlValidator.csproj b/src/YamlValidator/YamlValidator.csproj
new file mode 100644
index 00000000..e6653634
--- /dev/null
+++ b/src/YamlValidator/YamlValidator.csproj
@@ -0,0 +1,37 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+    <PropertyGroup>
+        <OutputType>Exe</OutputType>
+        <TargetFramework>net8.0</TargetFramework>
+        <ImplicitUsings>enable</ImplicitUsings>
+        <Nullable>enable</Nullable>
+    </PropertyGroup>
+    
+    <!-- Supressed CA1822: Mark members as static to have stateless classes -->
+    <!-- Supressed 1591 from Directory.build.props -->
+    <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
+      <NoWarn>1591, CA1822</NoWarn>
+    </PropertyGroup>
+
+    <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
+      <NoWarn>1591, CA1822</NoWarn>
+    </PropertyGroup>
+
+    <ItemGroup>
+        <PackageReference Include="JsonSchema.Net" Version="7.0.3" />
+        <PackageReference Include="Microsoft.Extensions.Logging" Version="$(MicrosoftExtensionsLoggingVersion)" />
+        <PackageReference Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
+        <PackageReference Include="Yaml2JsonNode" Version="2.1.0" />
+    </ItemGroup>
+
+    <ItemGroup>
+        <ProjectReference Include="..\Persistence\Microsoft.PowerPlatform.PowerApps.Persistence.csproj" />
+    </ItemGroup>
+
+    <ItemGroup>
+        <Content Include="..\..\docs\pa.yaml-schema.json" Link="schema\pa.yaml-schema.json" CopyToOutputDirectory="PreserveNewest" />
+        <Content Include="..\..\docs\subschemas\control-type-schema.json" Link="schema\subschemas\control-type-schema.json" CopyToOutputDirectory="PreserveNewest" />
+        <Content Include="..\..\docs\subschemas\control-property-schema.json" Link="schema\subschemas\control-property-schema.json" CopyToOutputDirectory="PreserveNewest" />
+    </ItemGroup>
+
+</Project>