Tuesday, February 26, 2013

PowerShell XML

Today I want to share with you several common approaches in XML exploration and modification.

This is our test XML:
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <configuration>  
  3.   <startup>  
  4.     <supportedRuntime version="4.0" />  
  5.   </startup>  
  6.   <appSettings>  
  7.     <add key="UseSuperpowers" value="false" />  
  8.     <add key="IgnoreKryptonite" value="true" />  
  9.   </appSettings>  
  10. </configuration>  

Load XML

  1. [xml]$configXml = Get-Content -Path C:\TestFile.xml  
Notice, that we cast loaded text to [xml] type. Actually $configXml is System.Xml.XmlDocument instance.
If file contains corrupted xml or no xml at all you will get exception.


This cast is very interesting fact, at first I thought that PS searches for constructor of given type which accepts argument of left expression. But XmlDocument has neither constructor nor static method (something like Load(string)) which accepts string. PowerShell magic.
This process is called Attribute Transformation. It allows to perform hidden transformation of types in freestyle way (like instantiate XmlDocument and invoke its method LoadXml(string) instead of type cast). You can start your own investigation from here: http://goo.gl/iZEGA.

Navigate XML

Another PS magic issue. As you can notice, we can use our xml variable like dynamic to access its attributes or nodes.
  1. # Access node  
  2. Write-Host $configXml.configuration.startup.supportedRuntime.OuterXml  
  3. # Output: <supportedRuntime version="4.0" />  
  4.   
  5. # Access attribute  
  6. Write-Host $configXml.configuration.startup.supportedRuntime.version   
  7. # Output: 4.0  
  8.   
  9. # Get colletion of nodes  
  10. $configXml.configuration.appSettings.add.Count                     
  11. # Output: 2  
  12.   
  13. # Iterate collection of nodes  
  14. $configXml.configuration.appSettings.add | %{ Write-Host $_.key }      
  15. # Output: UseSuperpowers IgnoreKryptonite  
If this 'dynamic path' points to several elements (like configuration.appSettings.add in our case) we will get array of nodes.

Navigate via XPath

You can use XPath to select nodes and attributes with Select-Xml cmdlet. It does not return elements itself but SelectXmlInfo instances. Use their Node property to get node itself:
  1. # Query node  
  2. $XPath = "/configuration/startup/supportedRuntime"  
  3. $node = ( $configXml | Select-Xml -XPath $XPath ).Node  
  4. Write-Host $node.OuterXml  
  5. # Output: <supportedRuntime version="4.0" />  
  6.   
  7. # Query attribute with condition  
  8. $XPath = "/configuration/appSettings/add[@key='IgnoreKryptonite']/@value"  
  9. $node = ( $configXml | Select-Xml -XPath $XPath ).Node  
  10. Write-Host $node.Value  
  11. # Output: false  

Edit XML

To edit XML you need to navigate to any element via one of approaches described and change its InnerXML or InnetText properties (in case of attribute you need to change Value property):
  1. # Change attribute 1  
  2. $configXml.configuration.startup.supportedRuntime.version = "5.0"  
  3. # Will change attribute: <supportedRuntime version="5.0" />  
  4.   
  5. # Change attribute 2  
  6. $XPath = "/configuration/appSettings/add[@key='UseSuperpowers']/@value"    
  7. $node = ( $configXml | Select-Xml -XPath $XPath ).Node  
  8. $node.Value = "true"  
  9. # Will change attribute: <add key="UseSuperpowers" value="true" />    
  10.   
  11. # Add XML  
  12. $configXml.InnerXml += "<!-- End -->"  
  13. # Will add this comment to the very end  

Create XML

  1. # Append attribute  
  2. $newAttribute = $configXml.CreateAttribute('levelOfAwesome')  
  3. $newAttribute.Value = "Highest"  
  4. $configXml.configuration.startup.supportedRuntime.Attributes.Append($newAttribute)  
  5.   
  6. # Insert comment before tag  
  7. $newComment = $configXml.CreateComment('This section is all about supported runtime')  
  8. $startupNode = $configXml.configuration.startup;  
  9. $startupNode.InsertBefore($newComment$startupNode.supportedRuntime)  
  10.   
  11. # Append node with attributes  
  12. $newNode = $configXml.CreateElement('add')  
  13.   
  14. $newAttribute = $configXml.CreateAttribute('key')  
  15. $newAttribute.Value = "wearUnderwearOverPants"  
  16. $newNode.Attributes.Append($newAttribute)  
  17.   
  18. $newAttribute = $configXml.CreateAttribute('value')  
  19. $newAttribute.Value = "true"  
  20. $newNode.Attributes.Append($newAttribute)  
  21.   
  22. $configXml.configuration.appSettings.AppendChild($newNode)  

Save XML

Use $configXml.Save($filePath) to save human readable XML, because other approaches (like Set-Content -Path $someFile -Value $configXml.InnerXml) will loose tabulation and formatting and save all XML in one line:
  1. Set-Content -Path C:\test.xml $configXml.InnerText  
  2. # Result:   
  3. #  <?xml version="1.0" encoding="utf-8"?><configuration><startup><!--This section is all about supported runtime--><supportedRuntime version="4.0" levelOfAwesome="Highest" /></startup><appSettings><add key="UseSuperpowers" value="false" /><add key="IgnoreKryptonite" value="true" /></appSettings></configuration>  
  4.   
  5. $configXml.Save('C:\test.xml')  
  6. # Result:  
  7. #  <?xml version="1.0" encoding="utf-8"?>    
  8. #  <configuration>    
  9. #    <startup>    
  10. #      <supportedRuntime version="4.0" />    
  11. #    </startup>    
  12. #    <appSettings>    
  13. #      <add key="UseSuperpowers" value="false" />    
  14. #      <add key="IgnoreKryptonite" value="true" />    
  15. #    </appSettings>    
  16. #  </configuration>  

No comments:

Post a Comment