Skip to content

Commit 83d9639

Browse files
committed
Backward compatibility for validating JSON files using Test-JSON command.
- `Test-JSON` command was introduced in PowerShell 6.1. This commit adds a backward compatibility test for JSON files by implementing a custom Test-JSON function that uses the `Newtonsoft.Json` library for versions prior to PowerShell 6.1.
1 parent 216fb37 commit 83d9639

File tree

5 files changed

+223
-0
lines changed

5 files changed

+223
-0
lines changed

build/hacks/Load-NewtonsoftDlls.ps1

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
function Get-DllFile {
2+
[CmdletBinding()]
3+
[OutputType([String])]
4+
param(
5+
[Parameter(Mandatory = $true)]
6+
[string]$Path,
7+
8+
[Parameter(Mandatory = $true)]
9+
[string]$PackageName
10+
)
11+
12+
$dllPath = Get-ChildItem -Path "$Path" -Filter "$PackageName.dll" -File -Recurse -ErrorAction SilentlyContinue | `
13+
Select-Object -First 1 -ExpandProperty FullName
14+
15+
return $dllPath
16+
}
17+
18+
function Import-NewtonsoftDll {
19+
[CmdletBinding()]
20+
param(
21+
[Parameter(Mandatory = $true)]
22+
[ValidateSet("Newtonsoft.Json", "Newtonsoft.Json.Schema")]
23+
[string]$PackageName,
24+
25+
[Parameter(Mandatory = $false)]
26+
[string]$Path
27+
)
28+
29+
Write-Host "Loading $PackageName assembly..." -ForegroundColor Cyan
30+
31+
# Check if the assembly is already loaded
32+
$loadedAssemblies = [AppDomain]::CurrentDomain.GetAssemblies() | Where-Object {
33+
$_.GetName().Name -eq "$PackageName"
34+
}
35+
if ($loadedAssemblies) {
36+
Write-Warning "$PackageName already loaded.`n`tLocation: '$($loadedAssemblies.Location)'"
37+
return
38+
}
39+
40+
# Set the default path if not provided
41+
if (-not $Path) {
42+
$profileDir = Split-Path -Parent "$($PROFILE.CurrentUserCurrentHost)"
43+
$Path = Join-Path -Path "$profileDir" -ChildPath "Modules\newtonsoft.json"
44+
}
45+
Write-Debug "Path: $Path"
46+
if (-not (Test-Path -Path $Path)) {
47+
New-Item -Path $Path -ItemType Directory -Force | Out-Null
48+
}
49+
50+
$framework = if ($PSVersionTable.PSEdition -eq "Core") { "netstandard2.0" } else { "net45" }
51+
$assembliesDir = Join-Path -Path $Path -ChildPath "$PackageName\lib\$framework"
52+
Write-Debug "Package directory: $assembliesDir"
53+
54+
# Install the package if it doesn't exist from nuget
55+
$dllPath = Get-DllFile -Path $assembliesDir -PackageName $PackageName
56+
57+
if (-not $dllPath) {
58+
# Check if nuget.exe is available
59+
if (-not (Get-Command nuget.exe -ErrorAction SilentlyContinue)) {
60+
Write-Error "'nuget.exe' not found in PATH. Please install or add it to PATH."
61+
return
62+
}
63+
64+
Write-Host "Installing $PackageName from NuGet." -ForegroundColor Cyan
65+
nuget.exe install $PackageName -NoHttpCache -ExcludeVersion -NonInteractive -Force `
66+
-OutputDirectory $Path `
67+
-Framework $framework
68+
69+
$dllPath = Get-DllFile -Path $assembliesDir -PackageName $PackageName
70+
if (-not $dllPath) {
71+
Write-Error "The $PackageName.dll file could not be found in the provided directory after install."
72+
return
73+
}
74+
}
75+
76+
# Load the assembly from the path
77+
try {
78+
Write-Host "Loading assembly from '$dllPath'." -ForegroundColor Magenta
79+
$asm = [Reflection.Assembly]::LoadFile($dllPath)
80+
Write-Debug $asm
81+
}
82+
catch {
83+
Write-Error "Failed to load assembly from '$dllPath'. $_"
84+
return
85+
}
86+
}
87+
88+
Import-NewtonsoftDll -PackageName "Newtonsoft.Json.Schema" -Path $Path
89+
Import-NewtonsoftDll -PackageName "Newtonsoft.Json" -Path $Path

containers-toolkit/Private/CommonToolUtilities.psm1

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -650,6 +650,13 @@ function ValidateJSONChecksumFile {
650650

651651
# Test JSON checksum file is valid
652652
try {
653+
# Check if Test-Json cmdlet is available
654+
if (-not (Get-Command -Name Test-Json -ErrorAction SilentlyContinue)) {
655+
Write-Debug "Using custom JSON validation module."
656+
Import-Module "$ModuleParentPath\Private\Json-Validator.psm1" -Force
657+
} else {
658+
Write-Debug "Using built-in Test-Json cmdlet."
659+
}
653660
$isValidJSON = Test-Json -Json "$(Get-Content -Path $ChecksumFilePath -Raw)" -Schema "$schemaFileContent"
654661
return $isValidJSON
655662
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
###########################################################################
2+
# #
3+
# Copyright (c) Microsoft Corporation. All rights reserved. #
4+
# #
5+
# This code is licensed under the MIT License (MIT). #
6+
# #
7+
###########################################################################
8+
9+
<#
10+
.Synopsis
11+
Validates JSON content against a specified schema.
12+
13+
.Description
14+
This module provides a function to validate JSON strings against a JSON schema.
15+
It uses Newtonsoft.Json and Newtonsoft.Json.Schema libraries for parsing and validation.
16+
It is used only when running in a PowerShell v5.1 or earlier environments that
17+
do not have the built-in `Test-Json` cmdlet available.
18+
`Test-Json` cmdlet is available in PowerShell 7+.
19+
#>
20+
21+
22+
class JsonValidator {
23+
# Static method for validating JSON against a schema
24+
static [bool] Validate([String]$jsonContent, [String]$schemaContent) {
25+
try {
26+
# Parse the JSON string into a JObject.
27+
# $jToken = [Newtonsoft.Json.Linq.JToken]::Parse($jsonContent)
28+
$jToken = [Newtonsoft.Json.Linq.JToken]::Parse($jsonContent)
29+
30+
# Parses the JSON schema.
31+
# $jSchema = [Newtonsoft.Json.Schema.JSchema]::Parse($schemaContent)
32+
$jSchema = [Newtonsoft.Json.Schema.JsonSchema]::Parse($schemaContent)
33+
34+
# Validate the JSON against the schema.
35+
$errors = New-Object System.Collections.Generic.List[string]
36+
# $isValid = [Newtonsoft.Json.Schema.SchemaExtensions]::IsValid($jToken, $jSchema, [ref]$errors)
37+
$isValid = [Newtonsoft.Json.Schema.Extensions]::IsValid($jToken, $jSchema, [ref]$errors)
38+
39+
if ($isValid) {
40+
return $true
41+
}
42+
else {
43+
# Write-Host "JSON is invalid according to the schema."
44+
# $errors | ForEach-Object { Write-Host $_ }
45+
Write-Error "The JSON is not valid with the schema: $($errors -join ', ')"
46+
return $false
47+
}
48+
}
49+
catch {
50+
Write-Error "An error occurred during validation: $_"
51+
return $false
52+
}
53+
}
54+
}
55+
56+
57+
function Test-Json {
58+
[CmdletBinding(
59+
SupportsShouldProcess = $true,
60+
ConfirmImpact = 'Low'
61+
)]
62+
param(
63+
[Parameter(Mandatory = $true, HelpMessage = "JSON string to test for validity.")]
64+
[String]$Json,
65+
[Parameter(Mandatory = $true, HelpMessage = "A schema to validate the JSON input against.")]
66+
[String]$Schema
67+
)
68+
69+
begin {
70+
$WhatIfMessage = "Validate JSON against schema."
71+
}
72+
73+
process {
74+
if ($PSCmdlet.ShouldProcess($env:COMPUTERNAME, $WhatIfMessage)) {
75+
return [JsonValidator]::Validate($Json, $Schema)
76+
}
77+
else {
78+
# Code that should be processed if doing a WhatIf operation
79+
# Must NOT change anything outside of the function / script
80+
return
81+
}
82+
}
83+
}
84+
85+
Export-ModuleMember -Function Test-Json

containers-toolkit/containers-toolkit.psd1

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ Note that the HostNetworkingService module is available only when the Hyper-V Wi
6969
NestedModules = @(
7070
'Private\CommonToolUtilities.psm1',
7171
'Private\UpdateEnvironmentPath.psm1',
72+
'Private\Json-Validator.psm1',
7273
'Public\AllToolsUtilities.psm1',
7374
'Public\BuildkitTools.psm1',
7475
'Public\ContainerdTools.psm1',
@@ -125,6 +126,7 @@ Note that the HostNetworkingService module is available only when the Hyper-V Wi
125126
FileList = @(
126127
'./Private/CommonToolUtilities.psm1',
127128
'./Private/UpdateEnvironmentPath.psm1',
129+
'./Private/Json-Validator.psm1',
128130
'./Public/AllToolsUtilities.psm1',
129131
'./Public/BuildkitTools.psm1',
130132
'./Public/ContainerdTools.psm1',

docs/FAQs.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
- [`Import-Module` issues](#import-module-issues)
66
- [Tool uninstall issues](#tool-uninstall-issues)
77
- [`Initialize-NatNetwork` issues](#initialize-natnetwork-issues)
8+
- [`'Test-Json' is not recognized as the name of a cmdlet`](#test-json-is-not-recognized-as-the-name-of-a-cmdlet)
89

910
## `Import-Module` issues
1011

@@ -107,3 +108,42 @@ Import-Module HNS
107108
> [!CAUTION]
108109
> **We do not recommend** using the [_third-party_ HNS module](https://www.powershellgallery.com/packages/HNS/0.2.4) available in the PowerShell Gallery. This module is **NOT** maintained and signed by Microsoft and may not be up-to-date with the latest changes in the HNS module.
109110
> _Microsoft does **NOT** take any responsibility for issues that may arise from installing and/or execution of commands in any third-party modules/scripts. Install the _third-party_ HNS module at your own risk!_
111+
112+
## `'Test-Json' is not recognized as the name of a cmdlet`
113+
114+
This error occurs because the `Test-Json` cmdlet was introduced in **PowerShell 6.1** and is not available in **Windows PowerShell 5.1** or earlier. To maintain compatibility, a custom implementation of `Test-Json` is provided for environments where the native cmdlet is unavailable.
115+
116+
To enable this compatibility, you must ensure that the `Newtonsoft.Json` and `Newtonsoft.Json.Schema` assemblies are loaded in your PowerShell session. You can do this in one of two ways:
117+
118+
**Option 1 – Use the loader script**
119+
120+
Use the `Load-NewtonsoftDlls` script to automatically download and load the required assemblies:
121+
122+
```powershell
123+
.\build\hacks\Load-NewtonsoftDlls.ps1
124+
```
125+
126+
**Option 2 – Load the assemblies manually**
127+
128+
1. **Download the NuGet package**:
129+
130+
```powershell
131+
$newtonsoftDir = \"$env:USERPROFILE\Documents\PowerShell\Modules\Newtonsoft.Json\"
132+
nuget.exe install Newtonsoft.Json.Schema -OutputDirectory $newtonsoftDir -Framework \"net45\" -ExcludeVersion
133+
```
134+
135+
2. **Load the assemblies**:
136+
137+
```powershell
138+
$jsondllPath = \"$newtonsoftDir\\Newtonsoft.Json\\lib\\net45\\Newtonsoft.Json.dll\"
139+
[Reflection.Assembly]::LoadFile($jsondllPath) | Out-Null
140+
141+
$schemadllPath = \"$newtonsoftDir\\Newtonsoft.Json.Schema\\lib\\net45\\Newtonsoft.Json.Schema.dll\"
142+
[Reflection.Assembly]::LoadFile($schemadllPath) | Out-Null
143+
```
144+
145+
### Reference
146+
147+
- [Test-Json](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/test-json?view=powershell-7.4)
148+
- [Newtonsoft.Json](https://www.nuget.org/packages/Newtonsoft.Json#supportedframeworks-body-tab)
149+
- [Differences between Windows PowerShell 5.1 and PowerShell 7.x](https://learn.microsoft.com/en-us/powershell/scripting/whats-new/differences-from-windows-powershell?view=powershell-7.5#new-test-json-cmdlet)

0 commit comments

Comments
 (0)