diff --git a/ThirdPartyNotices.txt b/ThirdPartyNotices.txt
index 971899c4e05..755c5912d40 100644
--- a/ThirdPartyNotices.txt
+++ b/ThirdPartyNotices.txt
@@ -76,3 +76,30 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+
+
+-------------------------------------------------
+File: NJsonSchema
+-------------------------------------------------
+
+The MIT License (MIT)
+
+Copyright (c) 2016 Rico Suter
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
diff --git a/assets/files.wxs b/assets/files.wxs
index 1f3e7c00e90..4c03b334d20 100644
--- a/assets/files.wxs
+++ b/assets/files.wxs
@@ -4,6 +4,9 @@
+
+
+
@@ -1818,6 +1821,7 @@
+
diff --git a/src/Microsoft.PowerShell.Commands.Utility/Microsoft.PowerShell.Commands.Utility.csproj b/src/Microsoft.PowerShell.Commands.Utility/Microsoft.PowerShell.Commands.Utility.csproj
index 526ab1f361d..1c5919dfa18 100644
--- a/src/Microsoft.PowerShell.Commands.Utility/Microsoft.PowerShell.Commands.Utility.csproj
+++ b/src/Microsoft.PowerShell.Commands.Utility/Microsoft.PowerShell.Commands.Utility.csproj
@@ -78,6 +78,7 @@
+
diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/TestJsonCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/TestJsonCommand.cs
new file mode 100644
index 00000000000..8b08da6427d
--- /dev/null
+++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/TestJsonCommand.cs
@@ -0,0 +1,101 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+using System.Collections.Generic;
+using System.Management.Automation;
+using System.Management.Automation.Internal;
+
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using NJsonSchema;
+
+namespace Microsoft.PowerShell.Commands
+{
+ ///
+ /// This class implements Test-Json command.
+ ///
+ [Cmdlet(VerbsDiagnostic.Test, "Json", HelpUri = "")]
+ public class TestJsonCommand : PSCmdlet
+ {
+ ///
+ /// An JSON to be validated.
+ ///
+ [Parameter(Position = 0, Mandatory = true, ValueFromPipeline = true)]
+ public String Json { get; set; }
+
+ ///
+ /// A schema to validate the JSON against.
+ /// This is optional parameter.
+ /// If the parameter is absent the cmdlet only attempts to parse the JSON string.
+ /// If the parameter present the cmdlet attempts to parse the JSON string and
+ /// then validates the JSON against the schema. Before testing the JSON string,
+ /// the cmdlet parses the schema doing implicitly check the schema too.
+ ///
+ [Parameter(Position = 1)]
+ [ValidateNotNullOrEmpty()]
+ public String Schema { get; set; }
+
+ private JsonSchema4 _jschema;
+
+ ///
+ /// Prepare an JSON schema.
+ ///
+ protected override void BeginProcessing()
+ {
+ if (Schema != null)
+ {
+ try
+ {
+ _jschema = JsonSchema4.FromJsonAsync(Schema).Result;
+ }
+ catch (Exception exc)
+ {
+ Exception exception = new Exception(TestJsonCmdletStrings.InvalidJsonSchema, exc);
+ ThrowTerminatingError(new ErrorRecord(exception, "InvalidJsonSchema", ErrorCategory.InvalidData, null));
+ }
+ }
+ }
+
+ ///
+ /// Validate an JSON.
+ ///
+ protected override void ProcessRecord()
+ {
+ JObject parsedJson = null;
+ bool result = true;
+
+ try
+ {
+ parsedJson = JObject.Parse(Json);
+
+ if (_jschema != null)
+ {
+ var errorMessages = _jschema.Validate(parsedJson);
+ if (errorMessages != null && errorMessages.Count != 0)
+ {
+ result = false;
+
+ Exception exception = new Exception(TestJsonCmdletStrings.InvalidJsonAgainstSchema);
+
+ foreach (var message in errorMessages)
+ {
+ ErrorRecord errorRecord = new ErrorRecord(exception, "InvalidJsonAgainstSchema", ErrorCategory.InvalidData, null);
+ errorRecord.ErrorDetails = new ErrorDetails(message.ToString());
+ WriteError(errorRecord);
+ }
+ }
+ }
+ }
+ catch (Exception exc)
+ {
+ result = false;
+
+ Exception exception = new Exception(TestJsonCmdletStrings.InvalidJson, exc);
+ WriteError(new ErrorRecord(exception, "InvalidJson", ErrorCategory.InvalidData, Json));
+ }
+
+ WriteObject(result);
+ }
+ }
+}
diff --git a/src/Microsoft.PowerShell.Commands.Utility/resources/TestJsonCmdletStrings.resx b/src/Microsoft.PowerShell.Commands.Utility/resources/TestJsonCmdletStrings.resx
new file mode 100644
index 00000000000..a5c8d5d24d9
--- /dev/null
+++ b/src/Microsoft.PowerShell.Commands.Utility/resources/TestJsonCmdletStrings.resx
@@ -0,0 +1,129 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ Cannot parse the JSON schema.
+
+
+ Cannot parse the JSON.
+
+
+ The JSON is not valid with the schema.
+
+
diff --git a/src/Modules/Unix/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1 b/src/Modules/Unix/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1
index b6ae4542427..0785132ffd2 100644
--- a/src/Modules/Unix/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1
+++ b/src/Modules/Unix/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1
@@ -22,7 +22,8 @@ CmdletsToExport= "Format-List", "Format-Custom", "Format-Table", "Format-Wide",
"Get-PSBreakpoint", "Remove-PSBreakpoint", "Enable-PSBreakpoint", "Disable-PSBreakpoint", "Get-PSCallStack",
"Send-MailMessage", "Get-TraceSource", "Set-TraceSource", "Trace-Command", "Get-FileHash",
"Get-Runspace", "Debug-Runspace", "Enable-RunspaceDebug", "Disable-RunspaceDebug",
- "Get-RunspaceDebug", "Wait-Debugger" , "Get-Uptime", "New-TemporaryFile", "Get-Verb", "Format-Hex", "Remove-Alias"
+ "Get-RunspaceDebug", "Wait-Debugger" , "Get-Uptime", "New-TemporaryFile", "Get-Verb", "Format-Hex",
+ "Test-Json", "Remove-Alias"
FunctionsToExport= "Import-PowerShellDataFile"
AliasesToExport= "fhx"
NestedModules="Microsoft.PowerShell.Commands.Utility.dll","Microsoft.PowerShell.Utility.psm1"
diff --git a/src/Modules/Windows-Core/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1 b/src/Modules/Windows-Core/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1
index 35a987c1c8b..50f785f87b1 100644
--- a/src/Modules/Windows-Core/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1
+++ b/src/Modules/Windows-Core/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1
@@ -22,7 +22,8 @@ CmdletsToExport= "Format-List", "Format-Custom", "Format-Table", "Format-Wide",
"Get-PSBreakpoint", "Remove-PSBreakpoint", "New-TemporaryFile", "Enable-PSBreakpoint", "Disable-PSBreakpoint", "Get-PSCallStack",
"Send-MailMessage", "Get-TraceSource", "Set-TraceSource", "Trace-Command", "Get-FileHash",
"Unblock-File", "Get-Runspace", "Debug-Runspace", "Enable-RunspaceDebug", "Disable-RunspaceDebug",
- "Get-RunspaceDebug", "Wait-Debugger" , "Get-Uptime", "Get-Verb", "Format-Hex", "Remove-Alias"
+ "Get-RunspaceDebug", "Wait-Debugger" , "Get-Uptime", "Get-Verb", "Format-Hex",
+ "Test-Json", "Remove-Alias"
FunctionsToExport= "ConvertFrom-SddlString"
AliasesToExport= "fhx"
NestedModules="Microsoft.PowerShell.Commands.Utility.dll","Microsoft.PowerShell.Utility.psm1"
diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/Test-Json.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/Test-Json.Tests.ps1
new file mode 100644
index 00000000000..e7bc5c6106a
--- /dev/null
+++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/Test-Json.Tests.ps1
@@ -0,0 +1,106 @@
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# Licensed under the MIT License.
+
+Describe "Test-Json" -Tags "CI" {
+ BeforeAll {
+ $validSchemaJson = @"
+ {
+ 'description': 'A person',
+ 'type': 'object',
+ 'properties': {
+ 'name': {'type': 'string'},
+ 'hobbies': {
+ 'type': 'array',
+ 'items': {'type': 'string'}
+ }
+ }
+ }
+"@
+
+ $invalidSchemaJson = @"
+ {
+ 'description',
+ 'type': 'object',
+ 'properties': {
+ 'name': {'type': 'string'},
+ 'hobbies': {
+ 'type': 'array',
+ 'items': {'type': 'string'}
+ }
+ }
+ }
+"@
+
+ $validJson = @"
+ {
+ 'name': 'James',
+ 'hobbies': ['.NET', 'Blogging', 'Reading', 'Xbox', 'LOLCATS']
+ }
+"@
+
+ $invalidTypeInJson = @"
+ {
+ 'name': 123,
+ 'hobbies': ['.NET', 'Blogging', 'Reading', 'Xbox', 'LOLCATS']
+ }
+"@
+
+ $invalidTypeInJson2 = @"
+ {
+ 'name': 123,
+ 'hobbies': [456, 'Blogging', 'Reading', 'Xbox', 'LOLCATS']
+ }
+"@
+
+ $invalidNodeInJson = @"
+ {
+ 'name': 'James',
+ 'hobbies': ['.NET', 'Blogging', 'Reading', 'Xbox', 'LOLCATS']
+ errorNode
+ }
+"@
+}
+
+ It "Json is valid" {
+ Test-Json -Json $validJson | Should -BeTrue
+ }
+
+ It "Json is valid aganist a valid schema" {
+ Test-Json -Json $validJson -Schema $validSchemaJson | Should -BeTrue
+ }
+
+ It "Json is invalid" {
+ Test-Json -Json $invalidNodeInJson -ErrorAction SilentlyContinue | Should -BeFalse
+ }
+
+ It "Json is invalid aganist a valid schema" {
+ Test-Json -Json $invalidTypeInJson2 -Schema $validSchemaJson -ErrorAction SilentlyContinue | Should -BeFalse
+ Test-Json -Json $invalidNodeInJson -Schema $validSchemaJson -ErrorAction SilentlyContinue | Should -BeFalse
+ }
+
+ It "Test-Json throw if a schema is invalid" {
+ { Test-Json -Json $validJson -Schema $invalidSchemaJson -ErrorAction Stop } | ShouldBeErrorId "InvalidJsonSchema,Microsoft.PowerShell.Commands.TestJsonCommand"
+ }
+
+ It "Test-Json write an error on invalid () Json aganist a valid schema" -TestCases @(
+ @{ name = "type"; json = $invalidTypeInJson; error = "InvalidJsonAgainstSchema,Microsoft.PowerShell.Commands.TestJsonCommand" }
+ @{ name = "node"; json = $invalidNodeInJson; error = "InvalidJson,Microsoft.PowerShell.Commands.TestJsonCommand" }
+ ) {
+ param($json, $error)
+
+ $errorVar = $null
+ Test-Json -Json $json -Schema $validSchemaJson -ErrorVariable errorVar -ErrorAction SilentlyContinue
+
+ $errorVar.FullyQualifiedErrorId | Should -BeExactly $error
+ }
+
+ It "Test-Json return all errors when check invalid Json aganist a valid schema" {
+ $errorVar = $null
+ Test-Json -Json $invalidTypeInJson2 -Schema $validSchemaJson -ErrorVariable errorVar -ErrorAction SilentlyContinue
+
+ # '$invalidTypeInJson2' contains two errors in property types.
+ $errorVar.Count | Should -Be 2
+ $errorVar[0].FullyQualifiedErrorId | Should -BeExactly "InvalidJsonAgainstSchema,Microsoft.PowerShell.Commands.TestJsonCommand"
+ $errorVar[1].FullyQualifiedErrorId | Should -BeExactly "InvalidJsonAgainstSchema,Microsoft.PowerShell.Commands.TestJsonCommand"
+ }
+}
diff --git a/test/powershell/engine/Basic/DefaultCommands.Tests.ps1 b/test/powershell/engine/Basic/DefaultCommands.Tests.ps1
index 3171e6f5870..1907240b6a0 100644
--- a/test/powershell/engine/Basic/DefaultCommands.Tests.ps1
+++ b/test/powershell/engine/Basic/DefaultCommands.Tests.ps1
@@ -440,6 +440,7 @@ Describe "Verify approved aliases list" -Tags "CI" {
"Cmdlet", "Test-ComputerSecureChannel", , $($FullCLR )
"Cmdlet", "Test-FileCatalog", , $($FullCLR -or $CoreWindows )
"Cmdlet", "Test-ModuleManifest", , $($FullCLR -or $CoreWindows -or $CoreUnix)
+"Cmdlet", "Test-Json", , $( $CoreWindows -or $CoreUnix)
"Cmdlet", "Test-Path", , $($FullCLR -or $CoreWindows -or $CoreUnix)
"Cmdlet", "Test-PSSessionConfigurationFile", , $($FullCLR -or $CoreWindows )
"Cmdlet", "Test-WSMan", , $($FullCLR -or $CoreWindows )