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 )