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 命令讓您可以提供一個 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 節點。然後,使用 ForEach-Object 循環對每個元素的 Node.InnerXML 屬性進行操作,以獲取每個 Name 元素的 InnerXML。

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 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++ 這樣的編輯器會提醒),但其資料可能不符合項目要求。這就是模式起作用的地方。當您使用 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 資料的錯誤

在修正了 Country 問題之後,MorePeople.xml 還出現了另一個問題:

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

元数据,即元素和属性,都没问题。那么问题出在哪里呢?这次问题还是出在第一个Person 行的一个上。有人决定用yes来替代true,但是像下面的代码将无法获取第一个元素,因为它在寻找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進行字符串長度驗證

字符串長度驗證有點複雜。因此,即使它在上面作為修改後的模式的一部分顯示出來,它仍然值得更多關注。

經手動添加use=”required”後,原始模式中Country屬性的項目如下所示:

<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/