使用PowerShell解析XML:读取和验证

XML无处不在。尽管它使用了烦人的尖括号,但XML格式仍然被广泛使用。配置文件、RSS订阅、Office文件(.docx中的“x”)只是部分列表。使用PowerShell解析XML文件是您PowerShell之旅中的重要一步。

本教程将向您展示如何使用PowerShell解析XML文件并验证其有效性。这将带您从零基础到精通获取和评估XML数据的各个方面。您将获得工具,帮助您验证XML数据的完整性,并在脚本门前阻止错误数据的进入!

先决条件

要跟随所呈现的材料,您应具备:

  • PowerShell版本3.0及以上。示例是在Windows PowerShell v5.1上创建的。
  • Notepad++、Visual Studio Code或其他理解XML的文本编辑器。

使用Select-Xml解析PowerShell XML元素

首先让我们来介绍使用PowerShell解析XML的最流行和最简单的方法之一,那就是使用Select-XmlSelect-Xml cmdlet允许您提供一个XML文件或字符串,以及一个称为XPath的“过滤器”,从中提取特定信息。

XPath是一系列元素名称的链。它使用“路径”样式的语法来识别和导航XML文档中的节点。

假设您有一个带有许多计算机的XML文件,并希望使用PowerShell解析此XML文件。每台计算机都有诸如名称、IP地址和Include元素(用于报告中的包含)等各种元素。

元素是具有开头标签和结束标签的XML部分,中间可能包含一些文本,比如<Name>SRV-01</Name>

<Computers>
	<Computer>
		<Name>SRV-01</Name>
		<Ip>127.0.0.1</Ip>
		<Include>true</Include>
	</Computer>	
	<Computer>
		<Name>SRV-02</Name>
		<Ip>192.168.0.102</Ip>
		<Include>false</Include>
	</Computer>	
	<Computer>
		<Name>SRV-03</Name>
		<Ip>192.168.0.103</Ip>
		<Include>true</Include>
	</Computer>	
</Computers>

您希望使用PowerShell解析此XML文件以获取计算机名称。为此,您可以使用Select-Xml命令。

在上面的文件中,计算机名称出现在Name元素的InnerXML中。

InnerXML是两个元素标签之间的文本。

要查找计算机名称,首先需要提供适当的XPath(/Computers/Computer/Name)。此XPath语法将仅返回Computer元素下的Name节点。然后,为了仅获取每个Name元素的InnerXML,请使用Node.InnerXML属性并使用ForEach-Object循环。

Select-Xml -Path C:\Work\computers-elm.xml -XPath '/Computers/Computer/Name' | ForEach-Object { $_.Node.InnerXML }

使用PowerShell解析带有Select-Xml的XML属性

现在让我们从不同的角度来解决这个找到计算机名称的问题。这一次,与XML 元素代表的计算机描述符不同,它们是用XML 属性表示的。

一个属性是一个键/值部分,例如name="SRV-01"。属性总是出现在开标签内,在标签名之后。

下面是用属性表示的计算机描述符的XML文件。现在你可以把每个描述符看作是一个属性,而不是一个元素。

<Computers>
	<Computer name="SRV-01" ip="127.0.0.1" include="true" />
	<Computer name="SRV-02" ip="192.168.0.102" include="false" />
	<Computer name="SRV-03" ip="192.168.0.103" include="true" />
</Computers>

由于这次每个描述符都是一个属性,所以稍微调整一下XPath,只找到计算机元素。然后,再次使用ForEach-Object cmdlet,找到name属性的值。

Select-Xml -Path C:\Work\computers-attr.xml -XPath '/Computers/Computer' | ForEach-Object { $_.Node.name }

确实,这也带来了相同的结果:SRV-01SRV-02SRV-03:

Reading data using Select-Xml

在这两种情况下,无论你是读取元素还是属性,Select-Xml的语法都很麻烦:它强迫你使用XPath参数,然后将结果传递给一个循环,最后在Node属性下查找数据。

幸运的是,PowerShell提供了一种更方便、更直观的方法来读取XML文件。PowerShell允许你读取XML文件并将其转换为XML对象

相关内容: 使用PowerShell数据类型加速器加速编码

将XML字符串转换为对象

另一种使用 PowerShell 解析 XML 的方法是将 XML 转换为对象。这样做的最简单方法是使用 [xml] 类型加速器。

通过在变量名前加上 [xml],PowerShell 将原始纯文本 XML 转换为您可以使用的对象。

[xml]$xmlElm = Get-Content -Path C:\Work\computers-elm.xml
[xml]$xmlAttr = Get-Content -Path C:\Work\computers-attr.xml

读取 XML 对象元素

现在,$xmlElm$xmlAttr 变量都是 XML 对象,允许您通过点表示法引用属性。也许您需要找到每个计算机元素的 IP 地址。由于 XML 文件是一个对象,因此您可以通过简单地引用 IP 元素来实现。

$xmlElm.Computers.Computer.ip

从 PowerShell 版本 3.0 开始,XML 对象使用相同的语法获取属性值,用于读取元素的内部文本。因此,IP 地址的值是使用与元素文件完全相同的语法从属性文件中读取的。

读取 XML 属性

使用完全相同的点表示法,您也可以读取 XML 属性,尽管 XML 结构有所不同。

$xmlElm.Computers.Computer.ip
$xmlAttr.Computers.Computer.ip

下面的结果显示,两者都获得了相同的数据,每个文件都来自其相应的文件:

PowerShell to Parse XML : Reading XML Attributes

此外,使用对象后,一旦加载到内存中,即使您正在使用 PowerShell ISE,您也可以获得 IntelliSense 来进行制表符补全。例如,如下所示。

IntelliSense and tab completion for XML object

遍历 XML 数据

一旦您绕过直接读取 XML 文件到 XML 对象(利用 [xml] 类型加速器的优势),您就拥有了 PowerShell 对象的全部功能。

假设,例如,您需要循环遍历出现在 XML 文件中具有 include=”true” 属性的所有计算机,以检查它们的连接状态。下面的代码显示了如何实现这一点。

这个脚本是:

  • 读取文件并将其转换为 XML 对象。
  • 遍历相关计算机以获取它们的连接状态。
  • 最后,将结果输出对象发送到管道。
 ## 将文件文本转换为 XML 对象
 [xml]$xmlAttr = Get-Content -Path C:\Work\computers-attr.xml

 ## 循环遍历设置 include="true" 的计算机
 $xmlAttr.Computers.Computer | Where-Object include -eq 'true' |  ForEach-Object {
     ## 查看当前计算机是否在线
     if(Test-Connection -ComputerName $_.ip -Count 1 -Quiet)
     {
         $status = 'Connection OK'
     }
     else
     {
         $status = 'No Connection'
     }

     ## 输出结果对象
     [pscustomobject]@{
         Name = $_.name
         Ip = $_.ip
         Status = $status
     }
 }

上述脚本的结果如下所示:

Connection status results

XML 模式

在前一节中,您看到了两种不同的 XML 文件以两种不同的方式表示数据集。这些方式称为 XML 模式。XML 模式定义了 特定 XML 文档的合法构建块:

  • 可以出现在该特定文档中的元素和属性的名称。
  • 子元素的数量和顺序。
  • 元素和属性的数据类型。

模式本质上定义了 XML 的结构。

验证 XML 数据

XML文件可能具有正确的语法(像Notepad++这样的编辑器会发出警告),但其数据可能不符合项目要求。这就是模式(schema)发挥作用的地方。当您依赖XML数据时,必须确保所有数据根据定义的模式是有效的。

您不希望发现运行时数据错误,这可能发生在脚本中间500行的深处。那时候,它可能已对文件系统和注册表执行了一些不可逆转的操作。

那么,如何提前检查数据是否正确呢?让我们首先看一些可能的错误类型。

XML数据可能的错误

一般来说,XML文件中发现的错误属于两类:元数据错误和数据本身的错误。

XML元数据错误

下面的文件MorePeople.xml在语法上是完全有效的。您可以看到文件中有一个单独的People元素(根元素),其中包含三个Person元素。这种结构是完全可以接受的。但是,它包含一个异常,您能看到吗?

<People>
	<Person Name="Debra" County="Canada" IsAdmin="true" />
	<Person Name="Jacob" Country="Israel" IsAdmin="true" />
	<Person Name="Olivia" Country="Cyprus" IsAdmin="false" />
</People>

如果您没有看到,不要担心,它只是隐藏起来了。问题出现在第一个内部元素中:

本应该是Country,但被拼错了,Canada 被降级为County

XML数据中的错误

在修复了MorePeople.xml上的Country问题后,另一个问题悄悄地潜入了:

<People>
	<Person Name="Debra" Country="Canada" IsAdmin="yes" />
	<Person Name="Jacob" Country="Israel" IsAdmin="true" />
	<Person Name="Olivia" Country="Cyprus" IsAdmin="false" />
</People>

元数据,即元素和属性,都很好。那问题出在哪里?这次问题仍然出在第一个Person 行的中的其中一个。有人决定yestrue的足够替代——但是像下面这样的代码将无法获得第一个元素,因为它正在寻找true,而不是yes

$peopleXml.People.Person | Where-Object IsAdmin -eq 'true'

创建 XML 模式

现在您知道可能出现的错误类型了,是时候展示一下模式文件如何帮助解决问题了。第一步是创建一个样本数据文件。这个样本可以是最小的示例,仅包含一个内部元素。对于上面的示例,让我们创建一个像这样的样本文件People.xml

<People>
	<Person Name="Jeff" Country="USA" IsAdmin="true" />
</People>

现在,在下面构建一个PowerShell 函数并使用它与样本数据一起创建.xsd 模式文件。

function New-XmlSchema
{
    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory)]
        [ValidateScript({ Test-Path -Path $_ })]
        [ValidatePattern('\.xml')]
        [string]$XmlExample
    ) 

    $item = Get-Item $XmlExample
    $dir = $item.Directory
    $name = $item.Name
    $baseName = $item.BaseName

    ## 通过将'.xml'替换为'.xsd'来构建模式路径
    $SchemaPath = "$dir\$baseName.xsd"

    try
    {
        $dataSet = New-Object -TypeName System.Data.DataSet
        $dataSet.ReadXml($XmlExample) | Out-Null
        $dataSet.WriteXmlSchema($SchemaPath)
        
        ## 显示生成的模式文件
        Get-Item $SchemaPath
    }
    catch
    {
        $err = $_.Exception.Message
        Write-Host "Failed to create XML schema for $XmlExample`nDetails: $err" -ForegroundColor Red
    }
}

将该函数复制到您的 ISE 或您喜欢的 Powershell 编辑器中,将其加载到内存中,并使用它创建模式。加载了函数后,用于创建模式的代码是这个一行代码:

New-XmlSchema -XmlExample 'C:\Work\People.xml'

结果将显示新创建的模式的路径:

Creating XML schema from a sample data file

使用模式文件验证您的数据

看一下上面结果指定的位置。如果.xsd文件存在,你正在正确的路径上查看验证的过程。在确认步骤中,请使用以下函数:

function Test-XmlBySchema
{
    [CmdletBinding()]
    [OutputType([bool])]
    param
    (
        [Parameter(Mandatory)]
        [ValidateScript({ Test-Path -Path $_ })]
        [ValidatePattern('\.xml')]
        [string]$XmlFile,
        [Parameter(Mandatory)]
        [ValidateScript({ Test-Path -Path $_ })]
        [ValidatePattern('\.xsd')]
        [string]$SchemaPath
    )

    try
    {
        [xml]$xml = Get-Content $XmlFile
        $xml.Schemas.Add('', $SchemaPath) | Out-Null
        $xml.Validate($null)
        Write-Verbose "Successfully validated $XmlFile against schema ($SchemaPath)"
        $result = $true
    }
    catch
    {
        $err = $_.Exception.Message
        Write-Verbose "Failed to validate $XmlFile against schema ($SchemaPath)`nDetails: $err"
        $result = $false
    }
    finally
    {
        $result
    }
}

将函数加载到内存中,并使用它来验证两个错误示例中的MorePeople.xml。要触发验证,请使用以下命令:

Test-XmlBySchema -XmlFile 'C:\Work\MorePeople.xml' -SchemaPath 'C:\Work\People.xsd' -Verbose

实际结果取决于MorePeople.xml的内容。

让我们看两个例子。请注意,当MorePeople.xml没有错误时,上面的函数将返回True

Validation success

MorePeople.xml文件包含错误的数据(Country键拼写错误为County)时,函数将返回一些失败细节并返回False

Validation failure – wrong attribute name detected

正如你所看到的,详细输出中指定的错误信息非常有信息量:它指向问题文件并指出了其中出现问题的确切组件。

调整验证模式

让我们看一下模式文件,然后看看我们如何让它更好。

New-XmlSchema默认创建的模式如下:

<?xml version="1.0" standalone="yes"?>
<xs:schema id="People" xmlns="" xmlns:xs="<http://www.w3.org/2001/XMLSchema>" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
  <xs:element name="People" msdata:IsDataSet="true" msdata:UseCurrentLocale="true">
    <xs:complexType>
      <xs:choice minOccurs="0" maxOccurs="unbounded">
        <xs:element name="Person">
          <xs:complexType>
            <xs:attribute name="Name" type="xs:string" />
            <xs:attribute name="Country" type="xs:string" />
            <xs:attribute name="IsAdmin" type="xs:string" />
          </xs:complexType>
        </xs:element>
      </xs:choice>
    </xs:complexType>
  </xs:element>
</xs:schema>

上面的默认模式很好,但不是完美的。确实,它捕捉到了Country属性的拼写错误。但是,如果保留模式不变,如果未满足其他期望,这些问题将不会被Test-XmlBySchema验证报告为错误。让我们解决这个问题。

下表列出了一些未被视为验证错误并且将被Test-XmlBySchema忽略的情况。在每一行中,右侧的列显示了如何手动更改模式以添加对所需保护的支持。

A modified version of the schema file, with all protections added, is shown right after the table.

添加到默认模式的设置 – 示例

Required Behavior Relevant setting on the schema file
At least one Person element Set the minOccurs value of the Person element to 1
Make Name, Country and IsAdmin attributes mandatory Add use=”required” to each of these attributes declaration
Allow only true/false values for IsAdmin Set type=”xs:boolean” for the IsAdmin declaration
Allow only Country names between 3 and 40 characters Use the xs:restriction (see detailed explanation after the schema text)
<?xml version="1.0" standalone="yes"?>
<xs:schema id="People" xmlns="" xmlns:xs="<http://www.w3.org/2001/XMLSchema>" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
  <xs:element name="People" msdata:IsDataSet="true" msdata:UseCurrentLocale="true">
    <xs:complexType>
      <xs:choice minOccurs="0" maxOccurs="unbounded">
        <xs:element name="Person">
          <xs:complexType>
            <xs:attribute name="Name" type="xs:string" use="required" />
	    <xs:attribute name="Country" use="required">
	      <xs:simpleType>
		<xs:restriction base="xs:string">
		  <xs:minLength value="3"/>
		  <xs:maxLength value="40"/>
		</xs:restriction>
	      </xs:simpleType>
	    </xs:attribute>	
            <xs:attribute name="IsAdmin" type="xs:boolean" use="required" />
          </xs:complexType>
        </xs:element>
      </xs:choice>
    </xs:complexType>
  </xs:element>
</xs:schema>

在示例中,对IsAdmin属性进行布尔限制,其值必须为小写 true或false。

使用xs:restriction进行字符串长度验证

字符串长度验证有点复杂。因此,即使它如上所示作为修改后模式的一部分,它也值得更多关注。

Country属性的原始模式项(在手动添加use=”required”后)如下所示:

<xs:attribute name="Country" type="xs:string" use="required" />

要添加长度保护,您应添加<xs:simpleType>元素,并在其中添加<xs:restriction base="xs:string">。这个限制又包含了在xs:minLengthxs:minLength上声明的必要限制。

在所有这些更改之后,最终的xs:attribute声明已从单行变成了一个巨大的8行节点:

<xs:attribute name="Country">
  <xs:simpleType>
	<xs:restriction base="xs:string">
	  <xs:minLength value="3"/>
	  <xs:maxLength value="40"/>
	</xs:restriction>
  </xs:simpleType>
</xs:attribute>

如果在上面的解释之后您的头脑没有眩晕,您有权看到验证的实际操作。要做到这一点,让我们故意将值Canada缩短为两个字符音节:Ca

使用简短的国家名称,并保存MorePeople.xml,您就可以运行以下验证命令:

Test-XmlBySchema -XmlFile 'C:\Work\MorePeople.xml' -SchemaPath 'C:\Work\People.xsd' -Verbose

而且结果确实显示复杂的模式已经发挥了作用:

A string length error detected by the schema validation

XML模式验证可能会变得非常复杂,并验证您能想到的几乎任何模式,特别是当与正则表达式结合使用时。

Source:
https://adamtheautomator.com/powershell-parse-xml/