tl;dr
正如您所怀疑的,名称冲突阻止了对感兴趣的 XML 元素上的 .Item 属性的访问; 修复父元素显式枚举的问题:
$xml.PatchScan.Machine.Product | % { $_.Item | select BulletinId, PatchName, Status }
% 是ForEach-Object cmdlet 的内置别名;解释见底部。
作为替代方案,Ansgar Wiecher's helpful answer 提供了一个简洁的基于 XPath 的解决方案,它既高效又允许复杂的查询。 p>
顺便说一句: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 DOM 的改编(点表示法):
PowerShell 装饰[System.Xml.XmlDocument] 实例中包含的对象层次结构(例如,使用转换[xml] 创建):
-
在每一层都有属性为输入文档的特定元素和属性命名[1];例如:
([xml] '<foo><bar>baz</bar></foo>').foo.bar # -> 'baz'
([xml] '<foo><bar id="1" /></foo>').foo.bar.id # -> '1'
-
将给定层次结构级别的多个同名元素隐式转换为数组(具体而言,类型为[object[]]);例如:
([xml] '<foo><C>one</C><C>two</C></foo>').foo.C[1] # -> 'two'
如示例(以及问题中您自己的代码)所示,这允许通过方便的点符号进行访问。
注意:如果您使用点表示法来定位具有至少一个 attribute 和/或 child 元素的元素,则元素 itself返回(XmlElement 实例);否则为元素的文本内容;有关通过点符号更新 XML 文档的信息,请参阅this answer。
点表示法的缺点是,如果 偶然 input-XML 元素名称恰好相同,则可能存在 名称冲突作为 intrinsic [System.Xml.XmlElement] 属性名称(对于 单元素 属性)或固有 [Array] 属性名称(对于 array-有价值的属性;[System.Object[]] 派生自 [Array])。
如果发生名称冲突:如果正在访问的属性包含:
有关此差异的讨论以及如何在发生冲突时访问固有的[System.Xml.XmlElement] 属性,请参阅最后一节。
解决方法是使用显式数组值属性枚举,使用ForEach-Object cmdlet,如顶部所示。
这是一个完整的例子:
[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 }
以上产出:
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
进一步深入兔子洞:哪些属性在以下情况下被遮蔽:
注意:遮蔽我的意思是,在名称冲突的情况下,“获胜”属性——其值被报告的那个——有效地隐藏了另一个,从而“把它放在阴影”。
在对数组使用点表示法的情况下,一个名为member-access enumeration 的功能开始发挥作用,适用于任何 PowerShell v3+ 中的集合;换句话说:该行为并非特定于 [xml] 类型。
简而言之:访问集合上的属性会隐式访问集合(集合中的项目)的每个成员上的属性,并将结果值作为数组返回([System.Object[]]);例如:
# Using member-access 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-access 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
在将点表示法与[xml](PowerShell 装饰的System.Xml.XmlDocument 和System.Xml.XmlElement 实例)结合使用的情况下,PowerShell 添加的附带属性会影响类型固有个:[2]
虽然这种行为很容易掌握,但结果取决于具体的输入这一事实也可能是危险的:
例如,在以下示例中,附带的 name child 元素会隐藏 元素本身上的同名内在属性:
> ([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>():
> ([xml] '<xml><name>foo</name></xml>').xml.get_Name()
xml # OK - intrinsic property value to use of .get_*()
[1] 如果给定元素同时具有属性 and 和同名元素,PowerShell 会将 both 报告为 数组 [object[]].
[2] 貌似,当 PowerShell 在后台适配底层的System.Xml.XmlElement 类型时,它不会像这样公开其属性,而是通过get_* 访问器 methods,它仍然允许像 属性一样进行访问,但优先使用 PowerShell 添加的偶然但真正的属性。如果您对此有更多了解,请告诉我们。