Reputation: 95
I am trying to parse an xml file using powershell. I want to print each node and its subnodes. I am new to xml parsing.
<?xml version="1.0" encoding="UTF-8"?>
<Inventory>
<Roles>
<Role Name="VirtualMachinePowerUser" Label="Virtual machine power user (sample)" Summary="Provides virtual machine interaction and configuration permissions">
<Privilege Name="Datastore.Browse" />
<Privilege Name="Global.CancelTask" />
<Privilege Name="ScheduledTask.Create" />
</Role>
<Role Name="VirtualMachineUser" Label="Virtual machine user (sample)" Summary="Provides virtual machine interaction permissions">
<Privilege Name="Global.CancelTask" />
<Privilege Name="ScheduledTask.Create" />
</Role>
My code below
[xml]$inputFile = Get-Content "C:\RolesnPer.xml"
$nodelist = $inputFile.Inventory.Roles.Role |Select-Object -Property Name
foreach ($Role in $nodelist)
{
$Role
$XMLprinterPath = $Role.selectSingleNode("Privilege").get_innerXml()
}
Required output:
Name Privilege
VirtualMachinePowerUser Datastore.Browse
Global.CancelTask
ScheduledTask.Create
VirtualMachineUser Global.CancelTask
ScheduledTask.Create
But I am getting this output:
Name
----
VirtualMachinePowerUser
Method invocation failed because [Selected.System.Xml.XmlElement] does not contain a method named 'selectSingleNode'.
At C:\Mandy\Code\transformation.ps1:25 char:1
+ $XMLprinterPath = $Role.selectSingleNode("Privilege").get_innerXml()
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (selectSingleNode:String) [], RuntimeException
+ FullyQualifiedErrorId : MethodNotFound
VirtualMachineUser
Method invocation failed because [Selected.System.Xml.XmlElement] does not contain a method named 'selectSingleNode'.
At C:\Mandy\Code\transformation.ps1:25 char:1
+ $XMLprinterPath = $Role.selectSingleNode("Privilege").get_innerXml()
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (selectSingleNode:String) [], RuntimeException
+ FullyQualifiedErrorId : MethodNotFound
Upvotes: 2
Views: 4097
Reputation: 437080
Deadrobot's helpful answer diagnoses your problem correctly (Select-Object
emits [pscustomobject]
instances, not XML nodes), but there's a simpler solution:
Select-Xml -LiteralPath C:\RolesnPer.xml '//Role' | ForEach-Object {
[pscustomobject] @{
Name = $_.Node.Name
Privilege = $_.Node.Privilege.Name
}
}
The above yields:
Name Privilege
---- ---------
VirtualMachinePowerUser {Datastore.Browse, Global.CancelTask, ScheduledTask.Create}
VirtualMachineUser {Global.CancelTask, ScheduledTask.Create}
Explanation:
The Select-Xml
cmdlet can operate on files directly for extracting matching nodes with XPath queries.
Each matching node can be accessed via $_.Node
inside the ForEach-Object
script block, and PowerShell conveniently exposes the child elements and attributes of XML element nodes as direct properties, so that .Name
reports the value of the Name
attribute, and .Privilege
returns all Privilege
child elements as an array, and, thanks to member-access enumeration, accessing their .Name
property returns an array of all their Name
attributes.
[pscustomobject] @{ ... }
is PSv3+ syntactic sugar for constructing a custom object using hashtable syntax.
Note: While the output format is not exactly what you asked for, outputting objects (rather than using string formatting) gives you more flexibility for subsequent programmatic processing.
If you do need the exact output format specified in your question:
# The format string to use with the -f operator below.
# The first column is 30 chars. wide; adjust as needed.
$fmtStr = '{0,-30} {1}'
# Output the header line.
$fmtStr -f 'Name', 'Privilege'
# Query the XML document and output the data lines.
Select-Xml -LiteralPath t.xml '//Role' | ForEach-Object {
$name = $_.Node.Name
foreach ($priv in $_.Node.Privilege.Name) {
$fmtStr -f $name, $priv
$name = ''
}
}
Upvotes: 2
Reputation: 25001
I know that answers have already been provided on accessing the element data, but this post is mainly to address the output formatting request:
$inputFile = [xml](Get-Content "C:\RolesnPer.xml")
$nodes = $inputFile.Inventory.Roles.Role | Select-Object -Property Name,Privilege
$NameColWidth = ($nodes.name | Foreach-Object { $_.length } | Sort-Object -Desc)[0] + 2
$FormattedOutput = @("{0,-$NameColWidth}{1}" -f "Name","Privilege") -as [collections.arraylist]
foreach ($node in $nodes) {
$null = $FormattedOutput.Add(("{0,-$NameColWidth}{1}" -f $node.name,($node.privilege.name | Select-Object -First 1)))
$node.Privilege | Select -expand Name -skip 1 | Foreach-Object {
$null = $FormattedOutput.Add(("{0,-$NameColWidth}{1}" -f " ",$_))
}
}
$FormattedOutput
If you only want the PowerShell object array with the two properties (Name and Privilege) without formatting, you only need the first two lines. Then output the variable $nodes
.
Upvotes: 1
Reputation: 43
This is occurs because Select-Object
on line 2 creates a new object that isn't xml.
I'm not an expert on manipulation XML, so there probably is a better way, but I would code it as such to get the output you want:
[xml]$inputFile = Get-Content "C:\RolesnPer.xml"
$Nodelist = $inputFile.SelectNodes("//Role")
$ParsedOutput = @()
foreach ($Role in $Nodelist) {
$Name = $Role.Name
$Privilege = $Role.Privilege |Select-Object -ExpandProperty name
$Obj = New-Object -TypeName psobject
$Obj | Add-Member -MemberType NoteProperty -Name Name -Value $Name
$Obj | Add-Member -MemberType NoteProperty -Name Privilege -Value $Privilege
$ParsedOutput += $Obj
}
$ParsedOutput
Upvotes: 1