So apparently XML validation with powershell is not a hot topic, but here it is, updated for 2024, running on Powershell Core 7.4.5. ...With some security and Quality of Life Improvements.
function Test-Xml {
[CmdletBinding()]
param(
[Parameter(Mandatory = $true, Position = 0)]
[string]$XmlPath,
[Parameter(Mandatory = $true, Position = 1)]
[string]$XsdPath
)
# Normalize the path
$XmlPath = (Resolve-Path -LiteralPath $XmlPath).ProviderPath
$XsdPath = (Resolve-Path -LiteralPath $XsdPath).ProviderPath
# Check if the XML and XSD files exist
if (-not (Test-Path $XmlPath)) {
Throw "XML file '$XmlPath' does not exist."
}
if (-not (Test-Path $XsdPath)) {
Throw "XSD file '$XsdPath' does not exist."
}
# Read the XSD schema
try {
$schemaReader = [System.Xml.XmlReader]::Create($XsdPath)
$schema = [System.Xml.Schema.XmlSchema]::Read($schemaReader, $null)
$schemaReader.Close()
}
catch {
Throw "Failed to read XSD schema from '$XsdPath': $_"
}
# Create an XmlSchemaSet and add the schema
$schemaSet = New-Object System.Xml.Schema.XmlSchemaSet
try {
$schemaSet.XmlResolver = $null # Disable external resource access
$schemaSet.Add($schema) | Out-Null
$schemaSet.Compile()
}
catch {
Throw "Failed to load or compile XSD schema from '$XsdPath': $_"
}
# Prepare to collect validation errors
$validationIssues = New-Object System.Collections.ArrayList
$eventHandler = {
param ($sender, $eventArgs)
$validationIssues.Add($eventArgs) | Out-Null
}
# Set up XmlReaderSettings with appropriate validation flags
$settings = New-Object System.Xml.XmlReaderSettings
$settings.ValidationType = [System.Xml.ValidationType]::Schema
$settings.Schemas = $schemaSet
$settings.ValidationFlags = `
[System.Xml.Schema.XmlSchemaValidationFlags]::ProcessSchemaLocation `
-bor [System.Xml.Schema.XmlSchemaValidationFlags]::ProcessInlineSchema `
-bor [System.Xml.Schema.XmlSchemaValidationFlags]::ReportValidationWarnings `
-bor [System.Xml.Schema.XmlSchemaValidationFlags]::ProcessIdentityConstraints
$settings.DtdProcessing = [System.Xml.DtdProcessing]::Prohibit
$settings.XmlResolver = $null # Disable external resource access
# Attach the event handler
$settings.add_ValidationEventHandler($eventHandler)
# Create an XmlReader for the XML document
try {
$xmlReader = [System.Xml.XmlReader]::Create($XmlPath, $settings)
}
catch {
Throw "Failed to read XML file '$XmlPath': $_"
}
# Parse and validate the XML document
try {
while ($xmlReader.Read()) {
# Reading the XML triggers validation
}
}
catch [System.Xml.XmlException] {
# Catch XML parsing errors (e.g., malformed XML)
Write-Verbose "XML parsing failed: $_"
return $false
}
finally {
$xmlReader.Close()
}
# Separate errors and warnings
$warnings = $validationIssues | Where-Object { $_.Severity -eq 'Warning' }
$errors = $validationIssues | Where-Object { $_.Severity -eq 'Error' }
# Determine validity based on the presence of errors
if ($errors.Count -gt 0) {
# There are validation errors
Write-Verbose "XML validation failed with the following errors:"
foreach ($error in $errors) {
Write-Verbose "Error: Line $($error.Exception.LineNumber), Position $($error.Exception.LinePosition): $($error.Message)"
}
if ($warnings.Count -gt 0) {
Write-Verbose "XML validation also produced warnings:"
foreach ($warning in $warnings) {
Write-Verbose "Warning: Line $($warning.Exception.LineNumber), Position $($warning.Exception.LinePosition): $($warning.Message)"
}
}
return [PSCustomObject]@{
IsValid = $false
Errors = $errors
Warnings = $warnings
}
} else {
# No validation errors; check for warnings
if ($warnings.Count -gt 0) {
# Only warnings present
Write-Verbose "XML validation succeeded with warnings:"
foreach ($warning in $warnings) {
Write-Verbose "Warning: Line $($warning.Exception.LineNumber), Position $($warning.Exception.LinePosition): $($warning.Message)"
}
} else {
# No errors or warnings
Write-Verbose "XML validation succeeded with no errors or warnings."
}
return [PSCustomObject]@{
IsValid = $true
Errors = $errors
Warnings = $warnings
}
}
}
$xmlFile = ".\WIMs\unattend.xml"
$xsdFile = ".\unattend.xsd"
Test-Xml -XmlPath $xmlFile -XsdPath $xsdFile -Verbose
returns:
VERBOSE: XML validation failed with the following errors:
VERBOSE: Error: Line 3, Position 13: The 'pass' attribute is invalid - The value 'oobeSystems' is invalid according to its datatype 'urn:schemas-microsoft-com:unattend:passType' - The Enumeration constraint failed.
VERBOSE: XML validation also produced warnings:
VERBOSE: Warning: Line 5, Position 8: Could not find schema information for the element 'urn:schemas-microsoft-com:unattend:UserAccounts'.
VERBOSE: Warning: Line 6, Position 10: Could not find schema information for the element 'urn:schemas-microsoft-com:unattend:LocalAccounts'.
VERBOSE: Warning: Line 7, Position 12: Could not find schema information for the element 'urn:schemas-microsoft-com:unattend:LocalAccount'.
VERBOSE: Warning: Line 7, Position 25: Could not find schema information for the attribute 'http://schemas.microsoft.com/WMIConfig/2002/State:action'.
VERBOSE: Warning: Line 8, Position 14: Could not find schema information for the element 'urn:schemas-microsoft-com:unattend:Name'.
VERBOSE: Warning: Line 9, Position 14: Could not find schema information for the element 'urn:schemas-microsoft-com:unattend:Password'.
VERBOSE: Warning: Line 10, Position 16: Could not find schema information for the element 'urn:schemas-microsoft-com:unattend:Value'.
VERBOSE: Warning: Line 11, Position 16: Could not find schema information for the element 'urn:schemas-microsoft-com:unattend:PlainText'.
VERBOSE: Warning: Line 13, Position 14: Could not find schema information for the element 'urn:schemas-microsoft-com:unattend:DisplayName'.
VERBOSE: Warning: Line 14, Position 14: Could not find schema information for the element 'urn:schemas-microsoft-com:unattend:Group'.
IsValid Errors Warnings
------- ------ --------
False System.Xml.Schema.ValidationEventArgs {System.Xml.Schema.ValidationEventArgs, System.Xml.S…