Я пытаюсь отредактировать объект 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
Любой намек?





Попробуйте 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)
Да запись не нужна. Мне было неясно, нужна ли вам просто startBoundary или другая информация. Вы можете использовать Descendants, чтобы просто получить startboundary из документа.
хорошо понял. В моем случае входной и выходной файл одинаковы. Но метод xDoc.Save() выдает ошибку: «Процесс не может получить доступ к файлу, поскольку он используется другим процессом». Должен ли я сохранить его под другим именем, а затем удалить оригинал или есть другое решение? Я читал, что где-то должен быть метод close, но на С#. Я не вижу метода закрытия в PowerShell.. иначе я напишу еще две строки, чтобы сохранить под другим именем, затем удалить, затем переименовать.. и еще раз спасибо
XDocument не имеет метода закрытия. Поэтому запись в новый файл и переименование — хорошее решение.
извините, что снова вас беспокою. Пример XML, использованный выше, содержит только 2 TaskV2, но на самом деле реальный файл xml содержит 3. Мне нужно изменить только первые 2, а не третий. Я думал, что могу просто сделать так: foreach($taskV2 в $tasksV2.Where({$_.Attribute('name').Value -ne 'wsus-extraMaintenance'})) { остальная часть кода } и из консоли кажется, это работает, так как я получаю только два хоста записи в соответствии с вашим кодом. Но xml-файл, написанный в конце, изменил также тот $taskV2, который был исключен. Как исключить TaskV2 из остальных?
Вы можете принудительно преобразовать psobject в массив, окружив его @(.......). Таким образом, вы можете использовать: @($StartBoundary)[1]
xml linq — это более новая сетевая библиотека для xml. XmlDocument — это более старая сетевая библиотека, которая требует больше инструкций, чтобы делать то же самое, что и linq. У меня больше операторов, потому что мой код возвращает больше значений.
жаль, что SO не допускает множественных ответов. Опять же, оба решения работают, я выбрал mklement0 просто потому, что он короче. Ничего больше.
Вы были на правильном пути. Чтобы ваш код работал:
Перейдите к интересующему родительскому элементу (элементам) с помощью пути к свойству, который — благодаря функции PowerShell перечисления членского доступа — неявно выполняет итерацию по всем элементам с одинаковым именем на заданном уровне иерархии.
q2: - удобная адаптация XML DOM PowerShell ([xml] (System.Xml.XmlDocument )) с точечной записью (см. этот ответ) не учитывает пространство имен и игнорирует пространства имен и их префиксы.Обработайте полученные элементы с помощью 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 требует явной обработки пространств имен — как путем объявления всех пространств имен заранее, так и путем необходимости выбирать элементы по именам с указанием пространства имен.
Смотрите этот ответ для примера.
да исправить. Оба решения работают, и ваше также содержит меньше кода. поэтому я использовал его.
большое спасибо!! Это сработало... кстати, я думаю, что переменную $records можно опустить, верно?