Невозможно полностью разобрать XML в PowerShell
У меня есть XML-файл, который я хотел бы проанализировать и получить обратно конкретную информацию.
Чтобы было проще понять, вот скриншот того, как выглядит XML-файл:
Я хотел бы разобрать 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 | Выбор-Объект -Недвижимость, ИП Имя ИП --- -- 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 распространяемый золотой Microsoft Visual C++ 2008 SP1 распространяемый (x64) Gold
Теперь добавьте Item в, а Intellisense поставит скобку, как будто Item был методом $xmlobj.PatchScan.Machine.Product.Item(
← Видишь это? Вот почему я думаю, почему-то Item
узел делает что-то странное, и это мой контрольно-пропускной пункт.
Этот снимок экрана лучше показывает, как он запускается со многими папками продукта, а затем в каждой папке продукта находится множество папок элементов.
XML в папке продукта меня не волнует. Мне нужна индивидуальная информация в каждой папке элемента.
2 ответа
XML - это структурированный текстовый формат. Он ничего не знает о "папках". То, что вы видите на своих скриншотах, это то, как данные отображаются программой, которую вы используете для их отображения.
В любом случае, лучший способ получить то, что вы хотите, это использовать SelectNodes()
с выражением XPath. По-прежнему.
[xml]$xml = Get-Content 'X:\folder\my.xml'
$xml.SelectNodes('//Product/Item[@Class="Patch"]') |
Select-Object BulletinID, PatchName, Status
ТЛ; др
Как вы и подозревали, столкновение имен препятствовало доступу к .Item
свойство интересующих элементов XML; исправить проблему с явным перечислением родительских элементов:
$xml.PatchScan.Machine.Product | % { $_.Item | select BulletinId, PatchName, Status }
%
это встроенный псевдоним для ForEach-Object
командлет; см. нижний раздел для объяснения.
В качестве альтернативы, полезный ответ Ансгара Вихера предлагает краткое решение на основе XPath, которое является одновременно эффективным и допускает сложные запросы.
Кроме того, PowerShell v3+ поставляется с Select-Xml
Командлет, который принимает путь к файлу в качестве аргумента, что позволяет для решения с одним конвейером:
(Select-Xml -LiteralPath X:\folder\my.xml '//Product/Item[@Class="Patch"]').Node |
Select-Object BulletinId, PatchName, Status
Select-Xml
оборачивает совпадающие узлы XML во внешний объект, поэтому возникает необходимость доступа к .Node
имущество.
Справочная информация о доступе XML к точечной нотации в PowerShell:
PowerShell украшает иерархию объектов, содержащихся в [System.Xml.XmlDocument]
экземпляры, созданные с использованием cast [xml]
:
со свойствами, названными для определенных элементов входного документа и атрибутов [1] на каждом уровне,
даже неявное превращение нескольких элементов с одинаковым именем на заданном уровне иерархии в массивы (в частности, типа
[object[]]
).
Это позволяет получить доступ через удобное обозначение точки ($xml.PatchScan.Machine.[...]
), что вы пытались.
Недостатком является то, что могут быть конфликты имен, если случайное имя элемента input-XML совпадает с внутренним именем [System.Xml.XmlElement]
имя свойства (для одноэлементных свойств) или встроенное [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]
свойства в случае столкновения.
Обходной путь должен использовать явное перечисление свойств со значениями массива, используя 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 }
Вышеуказанные выходы:
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
Далее по кроличьей норе: какие свойства затемнены, когда:
Примечание. Под теневым копированием я подразумеваю, что в случае коллизии имен свойство "выигрыш" - то, чье значение сообщается - эффективно скрывает другое, тем самым "помещая его в тень".
В случае использования точечной нотации с массивами в игру вступает функция, называемая перечислением членов, которая применяется к любой коллекции в PowerShell v3+; другими словами: поведение не является специфическим для [xml]
тип.
Вкратце: доступ к свойству коллекции неявно обращается к свойству каждого члена коллекции (элемента в коллекции) и возвращает полученные значения в виде массива ([System.Object[]]
);.например:
# 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
В случае использования точечной нотации с [xml]
(PowerShell декорированной System.Xml.XmlDocument
а также System.Xml.XmlElement
случайные свойства, добавленные PowerShell, затеняют свойства, свойственные типу: [2]
Хотя это поведение легко понять, тот факт, что результат зависит от конкретного вклада, также может быть коварным:
Например, в следующем примере случайный 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>()
:
> ([xml] '<xml><name>foo</name></xml>').xml.get_Name()
xml # OK - intrinsic property value to use of .get_*()
[1] Если данный элемент имеет как атрибут, так и элемент с одним и тем же именем, PowerShell сообщает о них как об элементах массива. [object[]]
,
[2] Похоже, когда PowerShell адаптирует базовый System.Xml.XmlElement
вводить за кулисы, он не раскрывает свои свойства как таковые, а через get_*
методы доступа, которые по-прежнему разрешают доступ, как если бы они были свойствами, но с добавленными PowerShell случайными, но добросовестными свойствами, имеющими приоритет. Дайте нам знать, если вы знаете больше об этом.