Невозможно полностью разобрать 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 случайными, но добросовестными свойствами, имеющими приоритет. Дайте нам знать, если вы знаете больше об этом.

Другие вопросы по тегам