Update XML File using PowerShell


 

There are many blogs on internet already speaking about updating XML files in PowerShell, but I felt need of one consolidated blog where complex XML files can also be updated with long complex Hierarchy of XML nodes and attributes.

Below is an XML example which we will try in this blog to update at various level of node Hierarchy.

<?xml version="1.0" encoding="utf-8"?>
<Data version="2.0">
  <Roles>
    <Role Name="ManagementServer" Value="NewManagementServer" />
    <Role Name="ADServer" Value="NewADServer" />
    <Role Name="ADServer" Value="NewADServer" />
  </Roles>
  <SQL>
    <Instance Server="NewSQLServer" Instance="MSSQLSERVER" Version="SQL Server 2012">
      <Variable Name="SQLAdmin" Value="Domain\NewSQlAdmin" />
      <Variable Name="SQLUser" Value="domain\sqluser" />
    </Instance>
  </SQL>
  <VMs>
    <VM Type="ClientVM">
      <VMName>ClientVM</VMName>
    </VM>
    <VM Type="DNSServerVM">
      <VMName>NewDNSServer</VMName>
    </VM>
  </VMs>
  <Course>
    <Subject>History</Subject>
    <Subject>Computers</Subject>
  </Course>
  <AllContacts>
    <Contact>
      <ContactType Type="Mobile" />
      <Details Number="000000000" />
    </Contact>
    <Contact>
      <ContactType Type="Landline" />
      <Details Number="11111111111" />
    </Contact>
  </AllContacts>
</Data>

We will target to update Roles, Variables and VMName, Subject etc. in this XML file. Below are the steps given separately on how we can update and add nodes and their attributes at various levels.

1. Define the variable which are required to be modified:-

$path = 'C:\Users\sorastog\Desktop\blog\Variable.xml'
 
$ManagementServer = 'NewManagementServer'
$SQLServer = 'NewSQLServer'
$SQLAdmin = 'Domain\NewSQlAdmin'
$DNSServerVMName = 'NewDNSServer'
$NewNumber = 'NewContactNumber'

2. Reading the content of XML file.

$xml = [xml](Get-Content $path)

3. Reading List of Subject: Read Child Tags of Course Node

$xml.Data.Course.Subject

4.  Update ‘ManagementServer’: Changing Attribute value of node at level 3 based on ‘Name’ attribute on same level.

$node = $xml.Data.Roles.Role | where {$_.Name -eq 'ManagementServer'}
$node.Value = $ManagementServer

5. Update ‘SQLServer’: Changing Attribute value of node at level 3.

$node = $xml.Data.SQL.Instance
$node.Server = $SQLServer

6. Update ‘SQLAdmin’: Changing Attribute value of node at level 4 based on ‘Name’ attribute on same level.

$node = $xml.Data.SQL.Instance.Variable | where {$_.Name -eq 'SQLAdmin'}
$node.Value = $SQLAdmin

7. Update ‘DNSServerVM’: Changing Attribute value of node at level 4 based on ‘VMType’ attribute at above level.

$node = $xml.Data.VMs.VM | where {$_.Type -eq 'DNSServerVM'}
$node.VMName = $DNSServerVMName

8. Update Subject Maths: Update Child nodes based on ‘Text’ property where no attribute is available to differentiate.

$node = $xml.Data.Course.ChildNodes
foreach($subject in $node)
{
    if ($subject.'#text' -eq "Maths") 
    {
        $newChild = $xml.CreateElement("Subject")
        $newChild.set_InnerXML("History") 
        $xml.Data.Course.ReplaceChild($newChild, $subject)     
    }
}

9. Update Subject Science: Update Child node based on position of Child node

$node = $xml.Data.Course.ChildNodes
$newChild = $xml.CreateElement("Subject")
$newChild.set_InnerXML("Computers") 
$xml.Data.Course.ReplaceChild($newChild, $node.Item(1))

10. Adding Role: Adding New Node in XML Hierarchy

$newRole = $xml.CreateElement("Role")
$xml.Data.Roles.AppendChild($newRole)

11. Adding ‘Name’ and ‘Value’ attributes to Role node: Adding Attributes to XML node

$newRole.SetAttribute(“Name”,”ADServer”);
$newRole.SetAttribute(“Value”,”NewADServer”);

12. Updating Contact Number when Type is Mobile

$allContactsNodes = $xml.Data.AllContacts.Contact
foreach($node in $allContactsNodes)
{
   if($node.ContactType.Type -eq 'Mobile')
   {
       $updateNode = $node.Details
       $updateNode.Number = $NewNumber
       break
   }
}

13. Saving changes to XML file.

   1: $xml.Save($path)

The final PowerShell script would look like below:-

$path = 'C:\Users\sorastog\Desktop\Variable.xml'
 
$ManagementServer = 'NewManagementServer'
$SQLServer = 'NewSQLServer'
$SQLAdmin = 'Domain\NewSQlAdmin'
$DNSServerVMName = 'NewDNSServer'
$NewNumber = 'NewContactNumber'
 
$xml = [xml](Get-Content $path)
 
$xml.Data.Course.Subject
     
$node = $xml.Data.Roles.Role | where {$_.Name -eq 'ManagementServer'}
$node.Value = $ManagementServer
    
$node = $xml.Data.SQL.Instance
$node.Server = $SQLServer
 
$node = $xml.Data.SQL.Instance.Variable | where {$_.Name -eq 'SQLAdmin'}
$node.Value = $SQLAdmin
 
$node = $xml.Data.VMs.VM | where {$_.Type -eq 'DNSServerVM'}
$node.VMName = $DNSServerVMName
  
$node = $xml.Data.Course.ChildNodes
foreach($subject in $node)
{
   if ($subject.'#text' -eq 'Maths') 
   {
       $newChild = $xml.CreateElement("Subject")
       $newChild.set_InnerXML("History") 
       $xml.Data.Course.ReplaceChild($newChild, $subject)     
    }
}
  
$node = $xml.Data.Course.ChildNodes
$newChild = $xml.CreateElement("Subject")
$newChild.set_InnerXML("Computers") 
$xml.Data.Course.ReplaceChild($newChild, $node.Item(1))
 
$newRole = $xml.CreateElement("Role")
$xml.Data.Roles.AppendChild($newRole)
 
$newRole.SetAttribute(“Name”,”ADServer”);
$newRole.SetAttribute(“Value”,”NewADServer”);
 
$allContactsNodes = $xml.Data.AllContacts.Contact
foreach($node in $allContactsNodes)
{
   if($node.ContactType.Type -eq 'Mobile')
   {
       $updateNode = $node.Details
       $updateNode.Number = $NewNumber
       break
   }
}
 
$xml.Save($path)

You can create variable for old and new subjects to be replaced and created respectively.

We also need to consider situations  where the specified node is not found or the given attribute value is not present. We can always use the try-catch error handling approach in such scenarios, an example would be:-

   1: $node = $xml.Data.Roles.Role | where {$_.Name -eq 'ManagementServer'}
   2: if($node -eq $null)
   3: {
   4:     Write-Host "The Role 'ManagementServer' with Attribute 'Name' was not found"
   5: }
   6: else
   7: {
   8:     $node.Value = $ManagementServer
   9: }

 

Hope this will help you to update even complex XML files with multiple nodes and complex Hierarchy. If there are some XML nodes that you would like to update and the category is not included in this blog, please reply to this post and I will add it.

Till Then, Happy Scripting 🙂


Comments (41)

  1. MARTIN says:

    Hi,

    Really helpful.Please do share our GOOGLE blogs as well.

  2. IAHB URUG says:

    Thank you for the info.

    I love you 🙂

  3. Neeraj Tyagi says:

    Really helpful…

  4. Nimit Rastogi says:

    In all these examples, you have identified the target node based on some condition. Like in –

    $node = $xml.Data.VMs.VM | where {$_.Type -eq 'DNSServerVM'}

    It means you already knew that there is a node VM with Type=DNSServerVM, whose parent is VMs and grand parent is Data.

    I have below questions around it –

    1. What would happen in PowerShell does not find any VM node with this type ? or what if there is no VM Node either ? or What if there is no such attribute of VM node as 'Type' ?

    2. Can we directly identify the child tag ? (I am thinking of a scenario where we just know the name of Node which is to be updated and do not know the whole hierarchy)

    3. Think of a scenario where the node no not have any attribute. For example –

    <Parent>

    <Child>One</Child>

    <Child>xyz</Child>

    </Parent>

    Now I want to update the value of second child to 'Two'. Can we do that ?

    4. Can we add new attribute to a node ?

    5. Can we add a new node in the xml ?

  5. Sonam Rastogi says:

    Hi Nimit,

    Thanks for the feedback.

    Generally, you will always know the details of XML i.e.. the nodes and the attributes that you would like to update.

    But yes, considering typing errors etc., error handling is always recommended. Please find the answers below:-

    1. Error Handling – I have edit my post to show how to do error handling in case the node is not found.

    2. Read Child Tags – Yes, we can read the parent tag and can print the details of child tag. I have edited my post to include that scenario.

    3. Update child nodes with no attributes to differentiate – Updated post to include this scenario

    4. Adding an attribute to a node – Updated post to include this scenario

    5. Adding a new node in XML – Updated post to include this scenario

    Once again, thanks for your feedback.

  6. Crix says:

    Useful info .Appreciate it.

  7. Nimit Rastogi says:

    Very well answered 🙂

  8. Sonam Rastogi says:

    Thanks 🙂

  9. Bjorn says:

    Well written, easy to understand.

    brgs

    Bjorn

  10. Fred L says:

    I like it but it doesn't help me for my current issue

    Here is what I have:

    A XML file with multiple occurences such as:

       <connection class="dataengine" dbname="F:ReportsCOMSafetyCom.tde"

       <connection class="dataengine" dbname="F:ReportsPMCPMC_Report.tde"

    and I am trying to figure out the way to replace each string "F:Reports" with another one (ie "F:Tableau Reports" regardless of the filename included in the value

  11. Sonam Rastogi says:

    Hi,

    If your xml looks like below

    <?xml version="1.0" encoding="utf-8"?>

    <Data version="2.0">

    <connection class="dataengine" dbname="F:ReportsCOMSafetyCom.tde" />

    <connection class="dataengine" dbname="F:ReportsPMCPMC_Report.tde" />

    </Data>

    Use this code to update all dbname entries.

    $path = '*********'

    $xml = [xml](Get-Content $path)

    $xml.data.connection | % {$_.dbname = $_.dbname.Replace('F:Reports','F:TableReports')}

    $xml.Save($path)

    The final xml will look like below

    <?xml version="1.0" encoding="utf-8"?>

    <Data version="2.0">

    <connection class="dataengine" dbname="F:TableReportsCOMSafetyCom.tde" />

    <connection class="dataengine" dbname="F:TableReportsPMCPMC_Report.tde" />

    </Data>

  12. Ahmet Aylin Öztürk says:

    you got that! 🙂

    thanks for the post, wokring perfect…

  13. Rainbow Warrior says:

    Really helpful stuff!

  14. Kumar Saxena says:

    Hello-great blog. Do you know how I can change the value of  attributes to numbers starting from a particular value?

    For instance, I want to replace all instances of serial number to change to numbers starting from (say) 234.So the values to be replaced would be 234,235,236,… and so on.

    Thanks

  15. Sonam Rastogi says:

    hi Kumar,

    you can initialize a variable with value 234 and can use it to change value in xml file. After that, use incremental counter ++ to increase value of the variable and keep assigning to fields of xml.

  16. Praveen says:

    Hi, Please let me know how remove below tag from xml file:

    "<Role Name="ManagementServer" Value="NewManagementServer" />"

  17. Sonam Rastogi says:

    Hi Praveen,

    Below code will remove the 'ManagementServer' Role

    $path = 'C:Variable.xml'

    #Read file

    $xml = [xml](Get-Content $path)

    #Identify Node

    $node = $xml.Data.Roles.Role | where {$_.Name -eq 'ManagementServer'}

    #Remove Node

    $node.ParentNode.RemoveChild($node)

    #Save File

    $xml.Save($path)

  18. Wilja says:

    In your example there's already a value in Roles.

    How can I put a value in the xml file when you start with an empty Role?

    before:

    <Roles />

    and after:

     <Roles>

       <Role Name="ManagementServer" Value="NewManagementServer" />

       <Role Name="ADServer" Value="NewADServer" />

     </Roles>

  19. Sonam Rastogi says:

    Hi Wilja,

    If below is your XML file

    <?xml version="1.0" encoding="utf-8"?>

    <Data version="2.0">

     <Roles>

     </Roles>

    </Data>

    Then , you can use below code to add new Role in empty Roles XML container

    $path = 'C:MyData.xml'

    $xml = [xml](Get-Content $path)

    $newRole = $xml.CreateElement("Role")

    $xml.SelectSingleNode("//Data/Roles").AppendChild($newRole)

    $newRole.SetAttribute(“Name”,”ADServer”);

    $newRole.SetAttribute(“Value”,”NewADServer”);

    $xml.Save($path)

    Hope this helps 🙂

  20. Wilja says:

    I think I'm close to the solution, (it works if I change the xml like you're example) I still get this error:

    "You cannot call a method on a null-valued expression"

    The difference is: I don't have this in my xml:

    <Roles>

    </Roles>

    I only have <Roles />

    That doesn't work

  21. Wilja says:

    Maybe I first need to split this line:

    <Roles />

    And make it look like

    <Roles>

    </Roles>

    Then I can try to put the SetAttribute in it?but how do I change that?

  22. Wilja says:

    I don't know why it didn't work at first, but now it does work.

    It's important that this match the existing xml:

    $xml.SelectSingleNode("//Data/Roles").AppendChild($newRole)

    //Data/Roles must be identical

    Thank you very much for your help 🙂

  23. Sonam Rastogi says:

    Welcome 🙂

  24. Anonymous says:

    Hi Sonam,

    Can you please help me to set value of an System.Array object as below.

    XML Structure:

    <Server Type="AIX Blade">

     <Node Type="App" Health="Good" Location="3rd Floor" UserName="Test1" Passwd="Test1234" />

     <Node weight="100" Height="200" Power="1000" />

     <Node Port="1290" Protocol="HTTP" />

    </Server>

    I can access the value using below, but don't know how to set new value e.g

    $path="C:Userstest1.xml"

    $xml = New-Object -TypeName XML

    $xml.Load($path)

    $xml.Server.Node.Location

    I want to change the value of Location from "3rd Floor" to "Ground Floor" like below

     <Node Type="App" Health="Good" Location="Ground Floor" UserName="Test1" Passwd="Test1234" />

    Thanks in advance.

  25. Anonymous says:

    Hi Sonam,

    I found that I can modify the value, if I know the node array value, i.e. here I have three nodes with different attribute, if the attribute value is in

    first node) <Node Type="App" Health="Good" Location="3rd Floor" UserName="Test1" Passwd="Test1234" />

    then  $xml.Server.Node[0].Location="Ground Floor" will work

    second node) <Node weight="100" Height="200" Power="1000" />

    then  $xml.Server.Node[1].Power="5000" will work

    But I don't want to hard code the node location. I know which attribute value I required to change, but node location can be different. And I have to do it for similar files, so don't want to hard code.

    Please let me know if you can help here.

    Thanks.

  26. Hi Dave,

    Sorry for replying late. Let me know in case you still need any help with the script.

    Thanks.

  27. OscarP says:

    I am just starting out with PowerShell and needed to modify a web.config file.  I struggled trying to find examples on the web, mainly because I didn’t know what to look for, until I ran into your post.  Very well written, that I was able to understand it and used several examples from your post.  But, I also need to add these three lines under a node(?) called System.net and can’t figure it out.  Can you please help?  Need it to be the first thing under System.net.  Thank you.

    <?xml version="1.0" encoding="utf-8"?>

    <configuration>

    .

    .

     <system.net>

       <settings>  # New line 1

         <servicePointManager checkCertificateName="false" checkCertificateRevocationList="false" />  # New line 2

       </settings>  # New line 3

       .

       .

     <system.net>

    .

    .

    </configuration>

  28. Hi OscarP,

    Considering below XML File

    <?xml version="1.0" encoding="utf-8"?>

    <Configuration>

     <system.net>

     </system.net>

    </Configuration>

    Below code will add New node 'Settings' in the XML and then will add subNode in 'servicePointManager' with Attributes 'checkCertificateName' and 'checkCertificateRevocationList'

    $path = 'C:Variable.xml'

    $xml = [xml](Get-Content $path)

    $newNode = $xml.CreateElement("settings")

    $parentNode = $xml.SelectSingleNode('//system.net')

    $parentNode.AppendChild($newNode)

    $xml.Save($path)

    $parentNode = $xml.SelectSingleNode('//settings')

    $newNode = $xml.CreateElement("servicePointManager")

    $parentNode.AppendChild($newNode)

    $newNode.SetAttribute("checkCertificateName", "false")

    $newNode.SetAttribute("checkCertificateRevocationList", "false")

    $xml.Save($path)

    Final XML will look like

    <?xml version="1.0" encoding="utf-8"?>

    <Configuration>

     <system.net>

       <settings>

         <servicePointManager checkCertificateName="false" checkCertificateRevocationList="false" />

       </settings>

     </system.net>

    </Configuration>

  29. OscarP says:

    Thank you so much, for such a quick response.  Works great!

  30. Good to see it helped you. Welcome 🙂

  31. Suman says:

    Hi Sonam,

    I have to read data from .xml file and search the tag in another .config file and replace the value with the value of same tag from .xml file. The format of .xml file is :

    <deployment>

     <!– Wildcard Definitions –>

     <definition type="xpath">

       <xpath>configuration/appSettings/add[@key='UploadPath'][@value]</xpath>

       <attribute>value</attribute>

       <value>blobs:///resource/Products/Upload/</value>

     </definition>

     <definition type="wildcard">

       <find>User ID=chopper_web;Password=Ch0pp3r;</find>

       <replace>User ID=chopper_web;Password=Ch0pp3r!;</replace>

     </definition>

    </deployment>

    And the .config file looks like this:

    <configuration>

    <configSections>

    <section name="PackageViewConfiguration" type="Chopper.Web.Infrastructure.Configuration.PackageViewConfigurationSection, Chopper.Web.Infrastructure"/>

    <section name="QuarkPromoteCatalogs" type="Chopper.Web.Infrastructure.Configuration.CatalogsConfigurationSection, Chopper.Web.Infrastructure"/>

    <section name="sqlServerCatalogNameOverwrites" type="System.Configuration.NameValueSectionHandler"/>

    <sectionGroup name="elmah">

    <section name="security" requirePermission="false" type="Elmah.SecuritySectionHandler, Elmah"/>

    <section name="errorLog" requirePermission="false" type="Elmah.ErrorLogSectionHandler, Elmah"/>

    <section name="errorMail" requirePermission="false" type="Elmah.ErrorMailSectionHandler, Elmah"/>

    </sectionGroup>

    <section name="dataCacheClients" type="Microsoft.ApplicationServer.Caching.DataCacheClientsSection, Microsoft.ApplicationServer.Caching.Core" allowLocation="true" allowDefinition="Everywhere"/><section name="cacheDiagnostics" type="Microsoft.ApplicationServer.Caching.AzureCommon.DiagnosticsConfigurationSection, Microsoft.ApplicationServer.Caching.AzureCommon" allowLocation="true" allowDefinition="Everywhere"/></configSections>

    <connectionStrings>

    <add name="Promote Database" connectionString="Data Source=localhost;Initial Catalog=UserProvisioningDB;Persist Security Info=True;User ID=chopper_web;Password=Ch0pp3r;Application Name="Msel Data Access FW";User Instance=False" providerName="System.Data.SqlClient" />

    </connectionStrings>

    <appSettings>

    <add key="webpages:Version" value="1.0.0.0"/>

    <add key="ClientValidationEnabled" value="true"/>

    <add key="UnobtrusiveJavaScriptEnabled" value="true"/>

    <!– Page to show when not correctly logged in.–>

    <add key="NotAuthorizedPage" value="~/NotAuthorized.aspx"/>

    <!– configuration for the URL Rewriter–>

    <add key="ProductSearchPage" value="~/ProductSearch/ProductSearch.aspx?category={0}&subcat={1}&coltype={2}&collateral={3}"/>

    <!– Page to show for a 404. –>

    …………………………….

    Can you suggest?

    Thanks,

  32. Hi Suman,

    You can read value of a node like below as explained in point 3 in above post

    $xml.Data.Course.Subject

    You can replace value of a node like below as explained in point 4 in above post

    $node = $xml.Data.Roles.Role | where {$_.Name -eq 'ManagementServer'}

    $node.Value = $ManagementServer

  33. Gourav Kr Bid says:

    Hi,

    I am working on a tool which uses Powershell forms to input data and store them in diffrent variables and then replace the value of the variables from the .XML file. I am new with using Powershell as well as .XML files. Can you help me out with it.

    Thanks.

  34. Quite possibly the best and most concise blog article on the entire subject.  🙂  Thanks!

  35. Chris Cools says:

    Hi Sonam,

    Thank you for this blog, I agree with my fellow IT colleagues above, the best and most usefull info I've found on the subject so far. Very nice, thanks again for sharing the knowledge!

    Kind regards

    Chris

  36. Thanks Chris Taylor and Chris Cools 🙂

  37. Dinesh says:

    Hi Sonam,

    I am a newbie at powershell and have a daunting task. Yours is the only article i have come across that helps me but as i am new to powershell i am struggling to get my head around it. Here is what i am trying to achieve –

    XML file –

    true
    60
    false

    _

    -SA

    lonmstest


    Customer Channels Technology
    eCommerce Infrastructure
    Windows

    EMEA

    Core
    Core Windows
    <!–automonitor-windows–>

    ecomm

    lonrseg
    1234

    I am trying to create a script that gets the local host name and updates the following node –

    lonmstest

    And also when i run the following command for IIS applications –

    import-module webadministration
    Get-Website | Select-Object PhysicalPath

    which gets the path of applications being hosted

    D:\Inetpub\wwwroot\Default
    D:\Inetpub\wwwroot\Notifications

    I need to import the following results into the types node with out the D:\ and backslashes i.e.InetpubwwwrootNotifications

    Core
    Core Windows
    <!–automonitor-windows–>

    ecomm

    Any insight on how i can achieve this will be much appreciated.

    Thanks

    1. Hi Dinesh,

      Sorry for late reply. Let me know if you still need any help.

  38. Kaj Bonfils says:

    Great post. Just one note though which took me a while to get around.

    When saving the xml in the end, you must provide the complete path, e.g.
    $xml.Save((Resolve-Path “$path”))

    -Kaj

  39. Tanvi Rastogi says:

    Hi Sonam,

    Very nice and helpful blog.

    I am a beginner in power shell. With the help of your blog, i managed to search, edit and add the config in xml files. Thanks a ton!! 🙂

    I have a question where i am struggling these days…..

    I want to modify multiple xml files with the same config. So, i used the below code to set the path of all files altogether:

    $path = ‘D:\PowerShell\Xmls\File1.xml’
    $path = ‘D:\PowerShell\Xmls\File2.xml’
    $path = ‘D:\PowerShell\Xmls\File3.xml’
    $path = ‘D:\PowerShell\Xmls\File4.xml’
    $path = ‘D:\PowerShell\Xmls\File5.xml’
    :
    :
    further config…

    If the files exists on the mentioned path, it modifies the xmls but the file which does not exists throws the error that file does not exists (which is quite logical too!!!) Is there any way that if the file is not found, it silently skips and move further to check further paths without throwing any error?

    1. Hi Tanvi,

      I am glad that my blog helped you. I have written below steps as a solution to your problem.

      1. create path as an array
      2. Iterate through path in a loop
      3. Use try catch
      4. put your logic for processing the config files inside try block
      5. in case the file is not found, it will come inside catch block. there you can ignore the error

      $paths = {‘‘D:\PowerShell\Xmls\File1.xml’, ‘D:\PowerShell\Xmls\File2.xml’’}
      foreach($path in $paths)
      {
      try
      {
      //logic to go here
      }
      catch
      {
      //ignore error
      }
      }

Skip to main content