Reputation: 69
I'm trying to edit a xml object in PowerShell but the xml object is quite hard to get to. I've read quite a few posts and articles and they all show easy xml objects or files. And they all work as long as the xml tree is simple and no tags with ':' inside them show up. In my case the xml object is obtained from an Active Directory GPO, through this command:
[xml]$report = Get-GPOReport -Guid 'BEE66288-DF38-4E32-A6F6-9DF13BABFDDF' -ReportType XML -Server "fqdn"
The xml that generates is quite long and full of sensitive data, so I had to trim it and sanitize it quite a lot, but it gives the idea:
<?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>
my goal is to 'select' the lines <q2:StartBoundary>2022-03-27T14:30:05</q2:StartBoundary>
for each node, then edit them and put them back in the object. Basically I need to change the date/time and save it back in the GPO. I can do the saving back in the GPO by myself. My objective here is to be able to select those lines. I've tried with:
$nodes = $report.GPO.ChildNodes.Item(0)
but I can only get as far as the ExtensionData tag. After that the content gets empty, and I have nothing to manipulate. I've also tried
$nodes = $report.GPO.Computer.ExtensionData.Extension | Where-Object {$_.type -eq 'q2:ScheduledTasksSettings'}
but same thing. Also this:
$xpath = "/GPO/Computer/ExtensionData/Extension/q2:ScheduledTasks/q2:TaskV2"
$nodesX = Select-Xml -Xml $report -XPath $xpath
but I get an error: Select-Xml : Namespace Manager or XsltContext needed. This query has a prefix, variable, or user-defined function
Any hint?
Upvotes: 1
Views: 157
Reputation: 437062
You were on the right track. To make your code work:
Drill down to the parent element(s) of interest via a property path, which - thanks to PowerShell's member-access enumeration feature - implicitly iterates over all elements that have the same name at a given level of the hierarchy.
q2:
- PowerShell's convenient adaption of the XML DOM ([xml]
(System.Xml.XmlDocument
)) with dot notation (see this answer) is not namespace-aware and ignores namespaces and their prefixes.Process the resulting elements with ForEach-Object
and update the child element of interest for each.
Note that even if the same value is to be assigned to all elements of interest, assigning directly to a property resulting in/from member-access enumeration does not work, because the latter only supports getting values - see this answer for details; e.g.:
# 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'
Therefore:
$report.GPO.Computer.ExtensionData.Extension.ScheduledTasks.TaskV2.Properties.Task.Triggers.CalendarTrigger |
ForEach-Object { $_.StartBoundary = Get-Date -Format s }
Note: It doesn't apply to the sample XML at hand, but it is possible for XML elements to be inaccessible with dot notation due to name collisions, necessitating a workaround, notably when trying to access <Item>
child elements on an array of elements resulting from member-access enumeration - see this answer.
As for the error you saw with Select-Xml
:
Unlike the dot-notation DOM adaptation, Select-Xml
does require explicit handling of namespaces - both by declaring all namespaces up front, and by needing to select elements by namespace-qualified names.
See this answer for an example.
Upvotes: 1
Reputation: 34421
Try xml linq
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)
Upvotes: 1