Изменить объект XML с помощью PowerShell

Я пытаюсь отредактировать объект xml в PowerShell, но получить доступ к объекту xml довольно сложно. Я прочитал довольно много сообщений и статей, и все они показывают простые объекты или файлы xml. И все они работают, пока xml-дерево простое и внутри него не отображаются теги с ':'. В моем случае объект xml получен из объекта групповой политики Active Directory с помощью этой команды:

[xml]$report = Get-GPOReport -Guid 'BEE66288-DF38-4E32-A6F6-9DF13BABFDDF' -ReportType XML -Server "fqdn"

Сгенерированный xml довольно длинный и полон конфиденциальных данных, поэтому мне пришлось обрезать его и довольно сильно очистить, но он дает идею:

<?xml version = "1.0" encoding = "utf-16"?>
<GPO xmlns:xsd = "http://www.w3.org/2001/XMLSchema" xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance" xmlns = "http://www.microsoft.com/GroupPolicy/Settings">
  <Computer>
    <VersionDirectory>51</VersionDirectory>
    <VersionSysvol>51</VersionSysvol>
    <Enabled>true</Enabled>
    <ExtensionData>
      <Extension xmlns:q1 = "http://www.microsoft.com/GroupPolicy/Settings/Files" xsi:type = "q1:FilesSettings">
        <q1:FilesSettings clsid = "{215B2E53-57CE-475c-80FE-9EEC14635851}">
        </q1:FilesSettings>
      </Extension>
      <Name>Files</Name>
    </ExtensionData>
    <ExtensionData>
      <Extension xmlns:q2 = "http://www.microsoft.com/GroupPolicy/Settings/ScheduledTasks" xsi:type = "q2:ScheduledTasksSettings">
        <q2:ScheduledTasks clsid = "{CC63F200-7309-4ba0-B154-A71CD118DBCC}">
          <q2:TaskV2 clsid = "{D8896631-B747-47a7-84A6-C155337F3BC8}" name = "VBS" image = "1" changed = "2022-12-01 09:28:19" uid = "{6128F739-7B10-4D53-B8B2-4F7D8D518B39}">
            <q2:GPOSettingOrder>1</q2:GPOSettingOrder>
            <q2:Properties action = "R" name = "VBS" runAs = "NT AUTHORITY\System" logonType = "S4U">
              <q2:Task version = "1.2">
                <q2:RegistrationInfo>
                  <q2:Description>VBS script installs windows update without rebooting</q2:Description>
                </q2:RegistrationInfo>
                <q2:Triggers>
                  <q2:CalendarTrigger>
                    <q2:Enabled>true</q2:Enabled>
                    <q2:StartBoundary>2022-03-27T14:30:00</q2:StartBoundary>
                    <q2:ExecutionTimeLimit>PT4H</q2:ExecutionTimeLimit>
                  </q2:CalendarTrigger>
                </q2:Triggers>
              </q2:Task>
            </q2:Properties>
            <q2:Filters />
          </q2:TaskV2>
          <q2:TaskV2 clsid = "{D8896631-B747-47a7-84A6-C155337F3BC8}" name = "PS" image = "1" changed = "2022-12-01 09:28:05" uid = "{27A6954B-DC81-42CE-ACA3-FB70CD1DDC98}">
            <q2:GPOSettingOrder>2</q2:GPOSettingOrder>
            <q2:Properties action = "R" name = "PS" runAs = "NT AUTHORITY\System" logonType = "S4U">
              <q2:Task version = "1.2">
                <q2:RegistrationInfo>
                  <q2:Description>PS script to schedule reboot</q2:Description>
                </q2:RegistrationInfo>
                <q2:Triggers>
                  <q2:CalendarTrigger>
                    <q2:Enabled>true</q2:Enabled>
                    <q2:StartBoundary>2022-03-27T14:30:05</q2:StartBoundary>
                    <q2:ExecutionTimeLimit>PT4H</q2:ExecutionTimeLimit>
                  </q2:CalendarTrigger>
                </q2:Triggers>
              </q2:Task>
            </q2:Properties>
            <q2:Filters />
          </q2:TaskV2>
        </q2:ScheduledTasks>
      </Extension>
      <Name>Scheduled Tasks</Name>
    </ExtensionData>
  </Computer>
</GPO>

моя цель — «выбрать» линии <q2:StartBoundary>2022-03-27T14:30:05</q2:StartBoundary> для каждого узла, затем отредактировать их и вернуть обратно в объект. В основном мне нужно изменить дату/время и сохранить их обратно в GPO. Я могу сделать сохранение обратно в GPO самостоятельно. Моя цель здесь состоит в том, чтобы иметь возможность выбирать эти строки. Я пробовал с:

$nodes = $report.GPO.ChildNodes.Item(0)

но я могу добраться только до тега ExtensionData. После этого содержимое становится пустым, и мне нечем манипулировать. я тоже пробовал

$nodes = $report.GPO.Computer.ExtensionData.Extension | Where-Object {$_.type -eq 'q2:ScheduledTasksSettings'}

но то же самое. Также это:

$xpath = "/GPO/Computer/ExtensionData/Extension/q2:ScheduledTasks/q2:TaskV2"
$nodesX = Select-Xml -Xml $report -XPath $xpath

но получаю ошибку: Select-Xml : Namespace Manager or XsltContext needed. This query has a prefix, variable, or user-defined function

Любой намек?

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
0
89
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Попробуйте xml-линк

using assembly System 
using assembly System.Linq
using assembly System.Xml.Linq 

$inputFilename = "c:\temp\test.xml"
$outputFilename = "c:\temp\test1.xml"

$reader = [System.IO.StreamReader]::new($inputFilename)
# skip first line with utf-16
$reader.ReadLine()
$xDoc = [System.Xml.Linq.XDocument]::Load($reader)
$today = [DateTime]::Now
$tasksV2 = $xDoc.Descendants().Where( {$_.Name.LocalName -eq "TaskV2"})
foreach($taskV2 in $tasksV2)
{
   $name = $taskV2.Attribute("name").Value

   $startBoundary = $xDoc.Descendants().Where( {$_.Name.LocalName -eq "StartBoundary"})
   $oldDate = $startBoundary[0].Value
   $todayStr = $today.ToString("yyyy-MM-ddTHH:mm:ss")
   Write-Host "Task Name = " $name ",Old Date = " $oldDate ",Today Date = " $todayStr
   $startBoundary.SetValue($todayStr)
}
$xDoc.Save($outputFilename)

большое спасибо!! Это сработало... кстати, я думаю, что переменную $records можно опустить, верно?

sigfried 05.02.2023 12:21

Да запись не нужна. Мне было неясно, нужна ли вам просто startBoundary или другая информация. Вы можете использовать Descendants, чтобы просто получить startboundary из документа.

jdweng 05.02.2023 13:01

хорошо понял. В моем случае входной и выходной файл одинаковы. Но метод xDoc.Save() выдает ошибку: «Процесс не может получить доступ к файлу, поскольку он используется другим процессом». Должен ли я сохранить его под другим именем, а затем удалить оригинал или есть другое решение? Я читал, что где-то должен быть метод close, но на С#. Я не вижу метода закрытия в PowerShell.. иначе я напишу еще две строки, чтобы сохранить под другим именем, затем удалить, затем переименовать.. и еще раз спасибо

sigfried 05.02.2023 15:20

XDocument не имеет метода закрытия. Поэтому запись в новый файл и переименование — хорошее решение.

jdweng 05.02.2023 15:53

извините, что снова вас беспокою. Пример XML, использованный выше, содержит только 2 TaskV2, но на самом деле реальный файл xml содержит 3. Мне нужно изменить только первые 2, а не третий. Я думал, что могу просто сделать так: foreach($taskV2 в $tasksV2.Where({$_.Attribute('name').Value -ne 'wsus-extraMaintenance'})) { остальная часть кода } и из консоли кажется, это работает, так как я получаю только два хоста записи в соответствии с вашим кодом. Но xml-файл, написанный в конце, изменил также тот $taskV2, который был исключен. Как исключить TaskV2 из остальных?

sigfried 05.02.2023 18:30

Вы можете принудительно преобразовать psobject в массив, окружив его @(.......). Таким образом, вы можете использовать: @($StartBoundary)[1]

jdweng 05.02.2023 19:08

xml linq — это более новая сетевая библиотека для xml. XmlDocument — это более старая сетевая библиотека, которая требует больше инструкций, чтобы делать то же самое, что и linq. У меня больше операторов, потому что мой код возвращает больше значений.

jdweng 08.02.2023 05:07

жаль, что SO не допускает множественных ответов. Опять же, оба решения работают, я выбрал mklement0 просто потому, что он короче. Ничего больше.

sigfried 08.02.2023 12:02
Ответ принят как подходящий

Вы были на правильном пути. Чтобы ваш код работал:

  • Перейдите к интересующему родительскому элементу (элементам) с помощью пути к свойству, который — благодаря функции PowerShell перечисления членского доступа — неявно выполняет итерацию по всем элементам с одинаковым именем на заданном уровне иерархии.

  • Обработайте полученные элементы с помощью ForEach-Object и обновите интересующий дочерний элемент для каждого.

    • Обратите внимание, что даже если одно и то же значение должно быть присвоено всем интересующим элементам, присвоение непосредственно свойству, полученному в результате/из перечисления доступа к членам, не работает, потому что последнее поддерживает только получение значений - см. этот ответ для деталей ; например.:

      # OK - *getting* all <date> values.
      ([xml] '<d><date>1970</date><date>1971</date></d>').d.date
      
      # !! NOT SUPPORTED - *setting* all <date> values.
      ([xml] '<d><date>1970</date><date>1971</date></d>').d.date = '2001'
      

Поэтому:

$report.GPO.Computer.ExtensionData.Extension.ScheduledTasks.TaskV2.Properties.Task.Triggers.CalendarTrigger | 
  ForEach-Object { $_.StartBoundary = Get-Date -Format s }

Примечание. Это не относится к имеющемуся образцу XML, но элементы XML могут быть недоступны с точечной записью из-за конфликтов имен, что требует обходного пути, особенно при попытке доступа к <Item> дочерним элементам в массиве элементов, в результате из перечисления членского доступа - см. этот ответ.


Что касается ошибки, которую вы видели с Select-Xml:

  • В отличие от адаптации DOM с точечной нотацией, Select-Xml требует явной обработки пространств имен — как путем объявления всех пространств имен заранее, так и путем необходимости выбирать элементы по именам с указанием пространства имен.

  • Смотрите этот ответ для примера.

да исправить. Оба решения работают, и ваше также содержит меньше кода. поэтому я использовал его.

sigfried 07.02.2023 21:35

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