【问题标题】:How do I use PowerShell to Validate XML files against an XSD?如何使用 PowerShell 针对 XSD 验证 XML 文件?
【发布时间】:2010-10-23 19:13:54
【问题描述】:

作为我开发的一部分,我希望能够针对单个 XSD 文件验证整个文件夹的 XML 文件价值。 PowerShell 函数似乎是一个很好的候选者,因为我可以像这样通过管道将文件列表传递给它: dir *.xml | Validate-Xml -Schema .\MySchema.xsd

我考虑过从Validating an Xml against Referenced XSD in C# 问题中移植 C# 代码,但我不知道如何在 PowerShell 中添加处理程序。

【问题讨论】:

  • 为什么仅仅因为你从标准输入读取文件列表就需要它是 PowerShell?
  • 我希望能够轻松地将其集成到自动构建脚本中。不想为了做到这一点而编译一个应用程序。 PowerShell 脚本似乎很适合这种事情。

标签: xml powershell xsd


【解决方案1】:

我想评论一下,当前接受的答案中的脚本不会验证有关xs:sequence 元素顺序不正确的错误。例如: 测试.xml

<addresses xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:noNamespaceSchemaLocation='test.xsd'>
  <address>
    <street>Baker street 5</street>
    <name>Joe Tester</name>
  </address>
</addresses>

test.xsd

<xs:schema xmlns:xs='http://www.w3.org/2001/XMLSchema'>    
<xs:element name="addresses">
      <xs:complexType>
       <xs:sequence>
         <xs:element ref="address" minOccurs='1' maxOccurs='unbounded'/>
       </xs:sequence>
     </xs:complexType>
    </xs:element>

     <xs:element name="address">
      <xs:complexType>
       <xs:sequence>
         <xs:element ref="name" minOccurs='0' maxOccurs='1'/>
         <xs:element ref="street" minOccurs='0' maxOccurs='1'/>
       </xs:sequence>
      </xs:complexType>
     </xs:element>

     <xs:element name="name" type='xs:string'/>
     <xs:element name="street" type='xs:string'/>
    </xs:schema>

我写了另一个版本可以报这个错误:

function Test-XmlFile
{
    <#
    .Synopsis
        Validates an xml file against an xml schema file.
    .Example
        PS> dir *.xml | Test-XmlFile schema.xsd
    #>
    [CmdletBinding()]
    param (     
        [Parameter(Mandatory=$true)]
        [string] $SchemaFile,

        [Parameter(ValueFromPipeline=$true, Mandatory=$true, ValueFromPipelineByPropertyName=$true)]
        [alias('Fullname')]
        [string] $XmlFile,

        [scriptblock] $ValidationEventHandler = { Write-Error $args[1].Exception }
    )

    begin {
        $schemaReader = New-Object System.Xml.XmlTextReader $SchemaFile
        $schema = [System.Xml.Schema.XmlSchema]::Read($schemaReader, $ValidationEventHandler)
    }

    process {
        $ret = $true
        try {
            $xml = New-Object System.Xml.XmlDocument
            $xml.Schemas.Add($schema) | Out-Null
            $xml.Load($XmlFile)
            $xml.Validate({
                    throw ([PsCustomObject] @{
                        SchemaFile = $SchemaFile
                        XmlFile = $XmlFile
                        Exception = $args[1].Exception
                    })
                })
        } catch {
            Write-Error $_
            $ret = $false
        }
        $ret
    }

    end {
        $schemaReader.Close()
    }
}

PS C:\temp\lab-xml-validation> 目录 test.xml | Test-XmlFile test.xsd

System.Xml.Schema.XmlSchemaValidationException: The element 'address' has invalid child element 'name'.
...

【讨论】:

  • 您的回答很棒、简短且有效 :) 您只是错过了$schemaReader.Dispose(),这会导致架构文件锁定
  • 谢谢;我已经用一个更新版本更新了我的答案,该版本被编写为一个可以包含在模块中并支持管道的函数。
  • 对于查看此内容的其他人,它确实适用于 PSv4(在 Windows Server 2012 R2 机器上运行)。通过扩展,这也适用于 PSv5 和 PSv5.1。不确定 PS Core,我没试过。
【解决方案2】:

PowerShell Community Extensions 有一个 Test-Xml cmdlet。唯一的缺点是扩展暂时没有更新,但大多数都可以在最新版本的 powershell(包括 Test-Xml)上工作。只需执行 Get-Childitem 并将列表传递给 foreach,在每个上调用 Test-Xml。

【讨论】:

  • v1.2 的扩展已发布以支持 v2 的 PowerShell。它们似乎都运行良好,所以我不确定有什么缺点。
【解决方案3】:

我为此编写了一个 PowerShell 函数:

用法:

目录 *.xml | Test-Xml -Schema ".\MySchemaFile.xsd" -Namespace "http://tempuri.org"

代码:

function Test-Xml {
param(
    $InputObject = $null,
    $Namespace = $null,
    $SchemaFile = $null
)

BEGIN {
    $failCount = 0
    $failureMessages = ""
    $fileName = ""
}

PROCESS {
    if ($InputObject -and $_) {
        throw 'ParameterBinderStrings\AmbiguousParameterSet'
        break
    } elseif ($InputObject) {
        $InputObject
    } elseif ($_) {
        $fileName = $_.FullName
        $readerSettings = New-Object -TypeName System.Xml.XmlReaderSettings
        $readerSettings.ValidationType = [System.Xml.ValidationType]::Schema
        $readerSettings.ValidationFlags = [System.Xml.Schema.XmlSchemaValidationFlags]::ProcessInlineSchema -bor
            [System.Xml.Schema.XmlSchemaValidationFlags]::ProcessSchemaLocation -bor 
            [System.Xml.Schema.XmlSchemaValidationFlags]::ReportValidationWarnings
        $readerSettings.Schemas.Add($Namespace, $SchemaFile) | Out-Null
        $readerSettings.add_ValidationEventHandler(
        {
            $failureMessages = $failureMessages + [System.Environment]::NewLine + $fileName + " - " + $_.Message
            $failCount = $failCount + 1
        });
        $reader = [System.Xml.XmlReader]::Create($_, $readerSettings)
        while ($reader.Read()) { }
        $reader.Close()
    } else {
        throw 'ParameterBinderStrings\InputObjectNotBound'
    }
}

END {
    $failureMessages
    "$failCount validation errors were found"
}
}

【讨论】:

  • 脚本有错误。该函数没有右括号。
  • $reader 应该在 while 循环之后关闭。否则,在 Finalizer-saftey-net 启动之前,您将无法编辑文件。
  • 这似乎不起作用:PS D:\projects\svcs> dir *.xml | Test-Xml 术语“Test-Xml”未被识别为 cmdlet、函数、脚本文件或可运行程序的名称。
  • 即使没有参数或错误的参数,它也不会做任何事情。 PS D:\projects\svcs> .\Test-Xml.ps1 PS D:\projects\svcs>
【解决方案4】:

我正在使用这个简单的 sn-p,它总是可以工作,你不需要复杂的功能。在这个示例中,我正在加载配置 xml,其中包含稍后用于部署和服务器配置的数据:

# You probably don't need this, it's just my way
$script:Context = New-Object -TypeName System.Management.Automation.PSObject
Add-Member -InputObject $Context -MemberType NoteProperty -Name Configuration -Value ""
$ConfigurationPath = $(Join-Path -Path $PWD -ChildPath "Configuration")

# Load xml and its schema
$Context.Configuration = [xml](Get-Content -LiteralPath $(Join-Path -Path $ConfigurationPath -ChildPath "Configuration.xml"))
$Context.Configuration.Schemas.Add($null, $(Join-Path -Path $ConfigurationPath -ChildPath "Configuration.xsd")) | Out-Null

# Validate xml against schema
$Context.Configuration.Validate(
    {
        Write-Host "ERROR: The Configuration-File Configuration.xml is not valid. $($_.Message)" -ForegroundColor Red

        exit 1
    })

【讨论】:

  • 这是最简单(因此通常也是最好)的解决方案。唯一的问题是它不适用于具有除空字符串之外的目标命名空间的模式。要处理这种情况,您必须单独加载 XmlSchema 对象。
【解决方案5】:

(Flatliner DOA) 的解决方案在 PSv2 上运行良好,但在 Server 2012 PSv3 上却不行。

(wangzq)的解决方案正在PS2和PS3上运行!!

任何需要在 PS3 上进行 xml 验证的人都可以使用这个(基于 wangzq 的功能)

function Test-Xml {
    param (
    [Parameter(ValueFromPipeline=$true, Mandatory=$true)]
        [string] $XmlFile,

        [Parameter(Mandatory=$true)]
        [string] $SchemaFile
    )

    [string[]]$Script:XmlValidationErrorLog = @()
    [scriptblock] $ValidationEventHandler = {
        $Script:XmlValidationErrorLog += $args[1].Exception.Message
    }

    $xml = New-Object System.Xml.XmlDocument
    $schemaReader = New-Object System.Xml.XmlTextReader $SchemaFile
    $schema = [System.Xml.Schema.XmlSchema]::Read($schemaReader, $ValidationEventHandler)
    $xml.Schemas.Add($schema) | Out-Null
    $xml.Load($XmlFile)
    $xml.Validate($ValidationEventHandler)

    if ($Script:XmlValidationErrorLog) {
        Write-Warning "$($Script:XmlValidationErrorLog.Count) errors found"
        Write-Error "$Script:XmlValidationErrorLog"
    }
    else {
        Write-Host "The script is valid"
    }
}

Test-Xml -XmlFile $XmlFile -SchemaFile $SchemaFile

【讨论】:

    【解决方案6】:

    我创建了一个单独的 PowerShell 文件,它可以使用内联架构引用对 XML 文件执行 XSD 验证。效果很好。下载和操作方法可在https://knowledge.zomers.eu/PowerShell/Pages/How-to-validate-XML-against-an-XSD-schema-using-PowerShell.aspx上获得

    【讨论】:

      【解决方案7】:

      我意识到这是一个老问题,但是我尝试了提供的答案,但无法让它们在 Powershell 中成功运行。

      我创建了以下函数,它使用了这里描述的一些技术。我发现它非常可靠。

      之前我必须在不同时间验证 XML 文档,但我总是发现行号为 0。看来 XmlSchemaException.LineNumber 仅在加载文档时可用。

      如果您之后在 XmlDocument 上使用 Validate() 方法进行验证,则 LineNumber/LinePosition 将始终为 0。

      相反,您应该在阅读时使用XmlReader 进行验证,并将验证事件处理程序添加到脚本块。

      Function Test-Xml()
      {
          [CmdletBinding(PositionalBinding=$false)]
          param (
          [Parameter(ValueFromPipeline=$true, Mandatory=$true)]
              [string] [ValidateScript({Test-Path -Path $_})] $Path,
      
              [Parameter(Mandatory=$true)]
              [string] [ValidateScript({Test-Path -Path $_})] $SchemaFilePath,
      
              [Parameter(Mandatory=$false)]
              $Namespace = $null
          )
      
          [string[]]$Script:XmlValidationErrorLog = @()
          [scriptblock] $ValidationEventHandler = {
              $Script:XmlValidationErrorLog += "`n" + "Line: $($_.Exception.LineNumber) Offset: $($_.Exception.LinePosition) - $($_.Message)"
          }
      
          $readerSettings = New-Object -TypeName System.Xml.XmlReaderSettings
          $readerSettings.ValidationType = [System.Xml.ValidationType]::Schema
          $readerSettings.ValidationFlags = [System.Xml.Schema.XmlSchemaValidationFlags]::ProcessIdentityConstraints -bor
                  [System.Xml.Schema.XmlSchemaValidationFlags]::ProcessSchemaLocation -bor 
                  [System.Xml.Schema.XmlSchemaValidationFlags]::ReportValidationWarnings
          $readerSettings.Schemas.Add($Namespace, $SchemaFilePath) | Out-Null
          $readerSettings.add_ValidationEventHandler($ValidationEventHandler)
          try 
          {
              $reader = [System.Xml.XmlReader]::Create($Path, $readerSettings)
              while ($reader.Read()) { }
          }
      
          #handler to ensure we always close the reader sicne it locks files
          finally 
          {
              $reader.Close()
          }
      
          if ($Script:XmlValidationErrorLog) 
          {
              [string[]]$ValidationErrors = $Script:XmlValidationErrorLog
              Write-Warning "Xml file ""$Path"" is NOT valid according to schema ""$SchemaFilePath"""
              Write-Warning "$($Script:XmlValidationErrorLog.Count) errors found"
          }
          else 
          {
              Write-Host "Xml file ""$Path"" is valid according to schema ""$SchemaFilePath"""
          }
      
          Return ,$ValidationErrors #The comma prevents powershell from unravelling the collection http://bit.ly/1fcZovr
      }
      

      【讨论】:

        【解决方案8】:

        我重写了它(我知道坏习惯),但是@Flatliner_DOA 的起始脚本太好了,不能完全丢弃。

        function Test-Xml {
        [cmdletbinding()]
        param(
            [parameter(mandatory=$true)]$InputFile,
            $Namespace = $null,
            [parameter(mandatory=$true)]$SchemaFile
        )
        
        BEGIN {
            $failCount = 0
            $failureMessages = ""
            $fileName = ""
        }
        
        PROCESS {
            if ($inputfile)
            {
                write-verbose "input file: $inputfile"
                write-verbose "schemafile: $SchemaFile"
                $fileName = (resolve-path $inputfile).path
                if (-not (test-path $SchemaFile)) {throw "schemafile not found $schemafile"}
                $readerSettings = New-Object -TypeName System.Xml.XmlReaderSettings
                $readerSettings.ValidationType = [System.Xml.ValidationType]::Schema
                $readerSettings.ValidationFlags = [System.Xml.Schema.XmlSchemaValidationFlags]::ProcessIdentityConstraints -bor
                    [System.Xml.Schema.XmlSchemaValidationFlags]::ProcessSchemaLocation -bor 
                    [System.Xml.Schema.XmlSchemaValidationFlags]::ReportValidationWarnings
                $readerSettings.Schemas.Add($Namespace, $SchemaFile) | Out-Null
                $readerSettings.add_ValidationEventHandler(
                {
                    try {
                        $detail = $_.Message 
                        $detail += "`n" + "On Line: $($_.exception.linenumber) Offset: $($_.exception.lineposition)"
                    } catch {}
                    $failureMessages += $detail
                    $failCount = $failCount + 1
                });
                try {
                    $reader = [System.Xml.XmlReader]::Create($fileName, $readerSettings)
                    while ($reader.Read()) { }
                }
                #handler to ensure we always close the reader sicne it locks files
                finally {
                    $reader.Close()
                }
            } else {
                throw 'no input file'
            }
        }
        
        END {
            if ($failureMessages)
            { $failureMessages}
            write-verbose "$failCount validation errors were found"
        
        }
        }
        
        #example calling/useage  code follows:
        $erroractionpreference = 'stop'
        Set-strictmode -version 2
        
        $valid = @(Test-Xml -inputfile $inputfile -schemafile $XSDPath )
        write-host "Found ($($valid.count)) errors"
        if ($valid.count) {
            $valid |write-host -foregroundcolor red
        }
        

        该函数不再使用管道作为使用文件路径的替代方法,这是该用例不需要的复杂功能。随意破解开始/处理/结束处理程序。

        【讨论】:

          猜你喜欢
          • 2012-07-23
          • 2014-02-05
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2023-02-23
          • 1970-01-01
          相关资源
          最近更新 更多