Skip to content

Commit 14e5eed

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 14e5eed

File tree

7 files changed

+254
-0
lines changed

7 files changed

+254
-0
lines changed

.textlintrc.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
"/nat/gi",
3939
"NatNetwork",
4040
"nerdctl",
41+
"/nuget/gi",
4142
"PowerShell",
4243
"/PSCustomObject/gi",
4344
"subnet",

Tests/CommonToolUtilities.Tests.ps1

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -534,6 +534,15 @@ Describe "CommonToolUtilities.psm1" {
534534
-ParameterFilter { $Path -eq $Script:ChecksumFile }
535535
}
536536

537+
It "should use custom Test-JSON if command not exists" {
538+
Mock Get-Command -ModuleName "CommonToolUtilities" -MockWith { return $null }
539+
540+
# We rely on the absence of the required types (Newtonsoft.Json)
541+
# to confirm that your fallback logic is being exercised.
542+
{ & $Script:FunctionToCall } | Should -Throw "*Unable to find type*Newtonsoft.Json*"
543+
544+
}
545+
537546
It "should throw an error if checksum file does not exist" {
538547
Mock Test-Path -ModuleName "CommonToolUtilities" -MockWith { return $false } -ParameterFilter { $Path -eq $Script:ChecksumFile }
539548

build/hacks/Load-NewtonsoftDlls.ps1

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

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 (Join-Path $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.JSchema]::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.SchemaExtensions]::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)