无法在PowerShell中完全解析XML

时间:2018-02-02 22:12:07

标签: xml powershell

我有一个XML文件,我想解析,并检索特定的信息。

为了便于理解,这里是XML文件的截图:

enter image description here

我想解析XML并为每个Item节点检索屏幕截图中指示的字段。检索到的每个值都需要按项目节点进行格式化。

最后,我希望能够指定要查找的条件,并且只检索找到的条件。

我一直在努力,没有运气。以下是我能够提出的:

[xml]$MyXMLFile = gc 'X:\folder\my.xml'
$XMLItem = $MyXMLFile.PatchScan.Machine.Product.Item
$Patch = $XMLItem | Where-Object {$_.Class -eq 'Patch'}
$Patch.BulletinID
$Patch.PatchName
$Patch.Status

当我运行上面的代码时,它不会返回任何结果。但是,仅出于测试目的,我删除了Item部分。现在,我可以通过修改上面的代码来实现它。

我将XML加载到XML对象中。现在我尝试将其遍历到产品并完美运行:

PS> $xmlobj.PatchScan.Machine.Product | Select-Object -Property Name, SP

Name SP
---- --
Windows 10 Pro (x64) 1607
Internet Explorer 11 (x64) Gold
Windows Media Player 12.0 Gold
MDAC 6.3 (x64) Gold
.NET Framework 4.7 (x64) Gold
MSXML 3.0 SP11
MSXML 6.0 (x64) SP3
DirectX 9.0c Gold
Adobe Flash 23 Gold
VMware Tools x64 Gold
Microsoft Visual C++ 2008 SP1 Redistributable Gold
Microsoft Visual C++ 2008 SP1 Redistributable (x64) Gold

现在添加Item in和Intellisense会设置一个括号,好像Item是一个方法$xmlobj.PatchScan.Machine.Product.Item(←看到了吗?所以这就是为什么我认为Item节点正在做一些奇怪的事情,这是我的障碍。

此屏幕截图更好地展示了它如何从许多产品文件夹开始,然后在每个产品文件夹中都有许多项目文件夹。

enter image description here

产品文件夹中的XML我不在乎。我需要每个项目文件夹中的个人信息。

2 个答案:

答案 0 :(得分:3)

XML是一种结构化文本格式。它对"文件夹"一无所知。您在屏幕截图中看到的是您用于显示数据的程序如何呈现数据。

无论如何,获得所需内容的最佳方法是使用带有XPath表达式的SelectNodes()。像往常一样。

[xml]$xml = Get-Content 'X:\folder\my.xml'
$xml.SelectNodes('//Product/Item[@Class="Patch"]') |
    Select-Object BulletinID, PatchName, Status

答案 1 :(得分:0)

<强> TL;博士

如您所料, 名称冲突阻止了对感兴趣的XML元素的.Item属性的访问; 修复 显式枚举元素的问题

$xml.PatchScan.Machine.Product | % { $_.Item | select BulletinId, PatchName, Status }

%ForEach-Object cmdlet的内置别名;请参阅底部部分以获得解释。

作为替代Ansgar Wiecher's helpful answer提供简洁的基于XPath的解决方案效率高且允许复杂查询

顺便说一下:PowerShell v3 +带有Select-Xml cmdlet,它以文件路径作为参数,允许单管道解决方案:

(Select-Xml -LiteralPath X:\folder\my.xml '//Product/Item[@Class="Patch"]').Node |
  Select-Object BulletinId, PatchName, Status

Select-Xml将匹配的XML节点包装在外部对象中,因此需要访问.Node属性。

PowerShell中点符号XML访问的背景信息:

PowerShell 修饰对象层次结构,其中包含使用强制转换[System.Xml.XmlDocument]创建的[xml]个实例:

  • 在每个级别为输入文档的特定元素和属性命名 [1] ,< / p>

  • 甚至将给定层次结构级别的多个同名元素隐式转换为数组 (具体地,类型为{{ 1}})。

这允许通过方便的点符号([object[]])进行访问,这是您尝试的。

如果偶然的输入-XML元素名称恰好与以下相同,则下方可能存在名称冲突 内部 $xml.PatchScan.Machine.[...]属性名称(用于单元素属性)或内在[System.Xml.XmlElement]属性名称(用于数组 -valued properties; [Array]派生自[System.Object[]])。

如果发生名称冲突:如果要访问的媒体包含:

  • 单个子元素[Array]), 偶然属性获胜

    • ,因为使内部类型属性无法预测 - 请参阅底部。
  • 子元素的数组 [System.Xml.XmlElement]类型的属性获胜。

    • 因此,以下元素名称使用数组值属性(使用反射命令获得)打破点表示法 [Array]):

      Get-Member -InputObject 1, 2 -Type Properties, ParameterizedProperty

有关此差异的讨论以及如何在发生冲突时访问内在Item Count IsFixedSize IsReadOnly IsSynchronized Length LongLenth Rank SyncRoot 属性,请参阅上一节。

解决方法是使用[System.Xml.XmlElement] cmdlet,使用 显式枚举数组值属性,如顶部。
这是一个完整的例子:

ForEach-Object

以上产量:

[xml] $xml = @'
<PatchScan>
  <Machine>
    <Product>
      <Name>Windows 10 Pro (x64)</Name>
      <Item Class="Patch">
        <BulletinId>MSAF-054</BulletinId>
        <PatchName>windows10.0-kb3189031-x64.msu</PatchName>
        <Status>Installed</Status>
      </Item>
      <Item Class="Patch">
        <BulletinId>MSAF-055</BulletinId>
        <PatchName>windows10.0-kb3189032-x64.msu</PatchName>
        <Status>Not Installed</Status>
      </Item>
    </Product>
    <Product>
      <Name>Windows 7 Pro (x86)</Name>
      <Item Class="Patch">
        <BulletinId>MSAF-154</BulletinId>
        <PatchName>windows7-kb3189031-x86.msu</PatchName>
        <Status>Partly Installed</Status>
      </Item>
      <Item Class="Patch">
        <BulletinId>MSAF-155</BulletinId>
        <PatchName>windows7-kb3189032-x86.msu</PatchName>
        <Status>Uninstalled</Status>
      </Item>
    </Product>
  </Machine>
</PatchScan>
'@

# Enumerate the array-valued .Product property explicitly, so that
# the .Item property can successfully be accessed on each XmlElement instance.
$xml.PatchScan.Machine.Product | 
  ForEach-Object { $_.Item | Select-Object BulletinID, PatchName, Status }

进一步沿着兔子洞:在以下情况下被遮蔽的属性:

注意:通过阴影我的意思是,在名称冲突的情况下,&#34;赢得&#34;属性 - 报告其价值的属性 - 有效地隐藏另一个属性,从而将其置于阴影中#34;。

如果使用带有数组的点符号 ,则会启用名为member enumeration的功能,适用于任何< PowerShell v3 +中的/ em>集合;换句话说:行为并非特定于Class BulletinId PatchName Status ----- ---------- --------- ------ Patch MSAF-054 windows10.0-kb3189031-x64.msu Installed Patch MSAF-055 windows10.0-kb3189032-x64.msu Not Installed Patch MSAF-154 windows7-kb3189031-x86.msu Partly Installed Patch MSAF-155 windows7-kb3189032-x86.msu Uninstalled 类型。

简而言之:访问集合上的属性会隐式访问集合的每个成员上的属性(集合中的项目),并将结果值作为数组返回[xml]); .e.g:

[System.Object[]]

但是,如果集合类型本身具有该名称的属性,则集合的属性优先; e.g:

# Using member enumeration, collect the value of the .prop property from
# the array's individual *members*.
> ([pscustomobject] @{ prop = 10 }, [pscustomobject] @{ prop = 20 }).prop
10
20

如果使用带有# !! Since arrays themselves have a property named .Count, # !! member enumeration does NOT occur here. > ([pscustomobject] @{ count = 10 }, [pscustomobject] @{ count = 20 }).Count 2 # !! The *array's* count property was accessed, returning the count of elements 的点表示法(PowerShell装饰的[xml]System.Xml.XmlDocument个实例),添加了PowerShell,偶然属性影响类型 - 内在的 [2]

虽然这种行为很容易掌握,但结果取决于具体的输入这一事实也可能危险

例如,在以下示例中,附带的System.Xml.XmlElement 元素在元素本身上隐藏了同名的内在属性

name

如果您确实需要访问内在类型的属性,请使用 > ([xml] '<xml><child>foo</child></xml>').xml.Name xml # OK: The element's *own* name > ([xml] '<xml><name>foo</name></xml>').xml.Name foo # !! .name was interpreted as the incidental *child* element

.get_<property-name>()

[1]如果给定元素同时具有属性以及同名元素,则PowerShell会将两者报告为 array > ([xml] '<xml><name>foo</name></xml>').xml.get_Name() xml # OK - intrinsic property value to use of .get_*()

[2] 看似,当PowerShell在幕后调整基础[object[]]类型时,它不会将其属性暴露为 >,但是通过System.Xml.XmlElement访问者方法,它仍然允许访问,就好像他们属性一样,但是使用了PowerShell添加的偶然但真实的属性优先考虑。如果您对此有更多了解,请告诉我们。